From a55d75d0046e1c8c29d5f225850e291d5b9a1d7d Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Wed, 24 Sep 2025 14:39:24 +0200 Subject: [PATCH 0001/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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/1447] 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 0782f95881649a95ad18102b445c3c7cb1a30ee2 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 3 Mar 2022 02:20:39 +0900 Subject: [PATCH 0029/1447] arm64: dts: apple: 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. Signed-off-by: Hector Martin --- 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 5132a6d65f53b42eb62af96302d1a910b61ae4ca Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Sep 2025 16:03:11 +0200 Subject: [PATCH 0030/1447] 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 8b7b2788796874..582238a9b87793 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -909,6 +909,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 c2b926b449be2d03efae99df63ac19da4ecff95d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Sep 2025 16:02:26 +0200 Subject: [PATCH 0031/1447] 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 09e8a11829afcdb0acec9600fa5f2ed0fbfc7481 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Sep 2025 16:03:32 +0200 Subject: [PATCH 0032/1447] 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 3f79878b25af1f..904905f96c86ff 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -912,6 +912,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 b1ee949919c3d84448e16c99ebc48b2dd30a5ee9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Aug 2025 21:00:49 +0200 Subject: [PATCH 0033/1447] 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 0034/1447] 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 0035/1447] 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 0036/1447] 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 0037/1447] 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 0038/1447] 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 0039/1447] 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 0040/1447] 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 0041/1447] 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 0042/1447] 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 0043/1447] 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 0044/1447] 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 0045/1447] 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 0046/1447] 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 0047/1447] 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 0048/1447] 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 0049/1447] 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 0050/1447] 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 0051/1447] 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 0052/1447] 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 0053/1447] 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 0054/1447] 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 0055/1447] 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 0056/1447] 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 0057/1447] 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 0058/1447] 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 0059/1447] 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 0060/1447] 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 0061/1447] 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 0062/1447] 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 0063/1447] 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 0064/1447] 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 0065/1447] 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 0066/1447] 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 0067/1447] 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 0068/1447] 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 0069/1447] 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 0070/1447] 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 0071/1447] 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 0072/1447] 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 0073/1447] 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 0074/1447] 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 0075/1447] 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 0076/1447] 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 0077/1447] 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 0078/1447] 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 0079/1447] 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 0080/1447] 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 0081/1447] 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 0082/1447] 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 0083/1447] 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 0084/1447] 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 0085/1447] 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 0086/1447] 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 0087/1447] 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 0088/1447] 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 0089/1447] 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 0090/1447] 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 0091/1447] 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 0092/1447] 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 0093/1447] 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 0094/1447] 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 0095/1447] 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 0096/1447] 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 0097/1447] 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 0098/1447] 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 0099/1447] 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 0100/1447] 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 0101/1447] 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 0102/1447] 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 0103/1447] 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 0104/1447] 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 0105/1447] 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 0106/1447] 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 0107/1447] 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 0108/1447] 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 0109/1447] 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 0110/1447] 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 0111/1447] 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 0112/1447] 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 0113/1447] 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 0114/1447] 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 0115/1447] 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 0116/1447] 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 0117/1447] 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 0118/1447] 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 0119/1447] 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 0120/1447] 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 0121/1447] 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 0122/1447] 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 0123/1447] 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 0124/1447] 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 0125/1447] 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 0126/1447] 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 0127/1447] 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 0128/1447] 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 0129/1447] 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 0130/1447] 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 0131/1447] 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 0132/1447] 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 0133/1447] 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 0134/1447] 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 0135/1447] 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 0136/1447] 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 0137/1447] 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 0138/1447] 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 0139/1447] 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 0140/1447] 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 0141/1447] 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 0142/1447] 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 0143/1447] 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 0144/1447] 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 0145/1447] 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 0146/1447] 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 0147/1447] 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 0148/1447] 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 0149/1447] 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 0150/1447] 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 0151/1447] 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 0152/1447] 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 0153/1447] 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 0154/1447] 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 0155/1447] 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 0156/1447] 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 0157/1447] 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 0158/1447] 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 0159/1447] 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 0160/1447] 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 0161/1447] 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 0162/1447] 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 0163/1447] 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 0164/1447] 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 0165/1447] 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 0166/1447] 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 0167/1447] 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 0168/1447] 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 0169/1447] 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 0170/1447] 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 0171/1447] 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 0172/1447] 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 0173/1447] 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 0174/1447] 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 0175/1447] 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 0176/1447] 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 0177/1447] 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 0178/1447] 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 0179/1447] 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 0180/1447] 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 0181/1447] 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 0182/1447] 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 0183/1447] 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 0184/1447] 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 0185/1447] 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 0186/1447] 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 0187/1447] 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 0188/1447] 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 0189/1447] 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 0190/1447] 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 0191/1447] 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 0192/1447] 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 0193/1447] 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 0194/1447] 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 0195/1447] 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 0196/1447] 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 0197/1447] 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 0198/1447] 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 0199/1447] 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 0200/1447] 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 0201/1447] 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 0202/1447] 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 0203/1447] 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 0204/1447] 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 0205/1447] 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 0206/1447] 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 0207/1447] 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 0208/1447] 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 0209/1447] 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 0210/1447] 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 0211/1447] 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 0212/1447] 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 0213/1447] 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 0214/1447] 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 0215/1447] 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 0216/1447] 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 0217/1447] 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 0218/1447] 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 0219/1447] 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 0220/1447] 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 0221/1447] 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 0222/1447] 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 0223/1447] 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 0224/1447] 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 0225/1447] 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 0226/1447] 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 0227/1447] 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 0228/1447] 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 0229/1447] [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 0230/1447] [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 0231/1447] [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 0232/1447] [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 0233/1447] [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 0234/1447] [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 0235/1447] [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 0236/1447] [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 0237/1447] 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 0238/1447] 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 0239/1447] [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 0240/1447] [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 0241/1447] [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 0242/1447] [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 0243/1447] [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 0244/1447] [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 0245/1447] [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 0246/1447] [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 0247/1447] [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 0248/1447] 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 0249/1447] 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 0250/1447] 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 0251/1447] 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 0252/1447] 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 0253/1447] 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 0254/1447] 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 0255/1447] 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 0256/1447] 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 0257/1447] 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 0258/1447] 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 0259/1447] 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 0260/1447] 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 0261/1447] 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 0262/1447] 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 0263/1447] 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 0264/1447] 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 0265/1447] 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 0266/1447] 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 0267/1447] 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 0268/1447] 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 0269/1447] 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 0270/1447] 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 0271/1447] 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 0272/1447] 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 0273/1447] 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 0274/1447] 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 0275/1447] 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 0276/1447] 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 0277/1447] 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 0278/1447] 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 0279/1447] 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 0280/1447] 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 0281/1447] 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 0282/1447] 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 0283/1447] 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 0284/1447] 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 0285/1447] 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 0286/1447] 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 0287/1447] 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 0288/1447] 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 0289/1447] 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 0290/1447] 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 0291/1447] 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 0292/1447] 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 0293/1447] 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 0294/1447] 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 0295/1447] 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 0296/1447] 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 0297/1447] 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 0298/1447] 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 0299/1447] 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 0300/1447] 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 0301/1447] 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 0302/1447] 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 0303/1447] 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 0304/1447] 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 0305/1447] 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 0306/1447] 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 0307/1447] 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 0308/1447] 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 0309/1447] 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 0310/1447] 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 0311/1447] 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 0312/1447] 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 0313/1447] 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 0314/1447] 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 0315/1447] 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 0316/1447] 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 0317/1447] 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 0318/1447] 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 0319/1447] 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 0320/1447] 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 0321/1447] 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 0322/1447] 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 0323/1447] 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 0324/1447] 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 0325/1447] 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 0326/1447] 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 0327/1447] 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 8cb628ea2febcadb5ca46b822b0cacbeff89d1cf Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 18 Aug 2022 02:13:54 +0900 Subject: [PATCH 0328/1447] 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. Note: Although ARM64 support is not yet merged, this can be built on amd64 with CONFIG_COMPILE_TEST=y. FIXME: order in drivers/soc/apple/Kconfig to avoid merge conflicts in asahi tree Signed-off-by: Asahi Lina --- drivers/soc/apple/Kconfig | 5 + 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, 297 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..f9e78ac69d5831 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -38,6 +38,11 @@ config APPLE_SART Say 'y' here if you have an Apple SoC. +config RUST_APPLE_RTKIT + bool + depends on RUST + depends on 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..dd69db63677dd6 --- /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_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 438200f8b35ea06366b1a11774a02a0a11bc59e5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 3 Dec 2025 21:30:50 +0100 Subject: [PATCH 0329/1447] 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 7dbaef25e64f06bcdefde84bedfc79e830abdb92 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 22 Oct 2022 00:10:30 +0900 Subject: [PATCH 0330/1447] 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 8a33a772f7d73cc9cb83c339652be8d04171e6d1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Jul 2025 12:37:30 +0200 Subject: [PATCH 0331/1447] 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 3edfa200c39f0b92170fc23da2662f1a1faf6080 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 11 Jul 2025 20:23:49 +0200 Subject: [PATCH 0332/1447] 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 f8f938443a7decdfe019f3a530dad11b87c9e4d1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 13 Jul 2025 10:44:51 +0200 Subject: [PATCH 0333/1447] 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 89b7fc1864e25fcd2b9579039273c4f01c930018 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 03:19:54 +0900 Subject: [PATCH 0334/1447] 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 3ecafd505020550f9bec8e4feca4d39c2db651a3 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 16 Feb 2023 20:20:17 +0900 Subject: [PATCH 0335/1447] 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 c4b31194410fdcda64a585c4da5d1a97d98b476b Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:12:48 +0100 Subject: [PATCH 0336/1447] 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 cb6d3a9ae3af1bc0e24f138c668cf46ee269ab79 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Dec 2025 10:34:29 +0100 Subject: [PATCH 0337/1447] 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 e8390a63d364dad8190e54eed2e9dc6c513d7032 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:23:21 +0100 Subject: [PATCH 0338/1447] 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 4c968f7ef9d55d745832d38ec26c11f22fcb2840 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:33 -0500 Subject: [PATCH 0339/1447] 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 a68b06bdc17205d79196044192e436c55b239513 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Wed, 22 Oct 2025 10:43:35 +0700 Subject: [PATCH 0340/1447] 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 0341/1447] 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 0342/1447] 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 0343/1447] 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 0344/1447] 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 0345/1447] 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 0346/1447] 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 0347/1447] 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 0348/1447] 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 0349/1447] 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 0350/1447] 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 0351/1447] 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 0352/1447] 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 0353/1447] 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 0354/1447] 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 0355/1447] 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 0356/1447] 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 0357/1447] 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 0358/1447] 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 0359/1447] 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 0360/1447] 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 0361/1447] 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 0362/1447] 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 0363/1447] 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 0364/1447] 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 0365/1447] 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 0366/1447] 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 0367/1447] 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 0368/1447] 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 0369/1447] 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 0370/1447] 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 0371/1447] 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 0372/1447] 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 0373/1447] 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 0374/1447] 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 0375/1447] 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 0376/1447] 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 0377/1447] 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 0378/1447] 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 0379/1447] 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 0380/1447] 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 0381/1447] 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 0382/1447] 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 0383/1447] 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 0384/1447] 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 0385/1447] 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 0386/1447] 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 0387/1447] 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 0388/1447] 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 0389/1447] 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 0390/1447] 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 0391/1447] 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 0392/1447] 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 0393/1447] 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 0394/1447] 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 0395/1447] 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 0396/1447] 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 0397/1447] 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 0398/1447] 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 0399/1447] 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 0400/1447] 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 0401/1447] 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 0402/1447] 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 0403/1447] 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 0404/1447] 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 0405/1447] 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 0406/1447] 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 0407/1447] 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 0408/1447] 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 0409/1447] 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 0410/1447] 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 0411/1447] 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 0412/1447] 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 0413/1447] 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 0414/1447] 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 0415/1447] 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 0416/1447] 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 0417/1447] 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 0418/1447] 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 0419/1447] 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 0420/1447] 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 0421/1447] 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 0422/1447] 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 0423/1447] 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 0424/1447] 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 0425/1447] 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 0426/1447] 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 0427/1447] 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 0428/1447] 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 0429/1447] 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 0430/1447] 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 0431/1447] 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 0432/1447] 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 0433/1447] 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 0434/1447] 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 0435/1447] 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 0436/1447] 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 0437/1447] 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 0438/1447] 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 0439/1447] 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 0440/1447] 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 0441/1447] 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 0442/1447] 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 0443/1447] 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 0444/1447] 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 0445/1447] 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 0446/1447] 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 0447/1447] 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 0448/1447] 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 0449/1447] 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 0450/1447] 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 0451/1447] 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 0452/1447] 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 0453/1447] 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 0454/1447] 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 0455/1447] 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 0456/1447] 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 0457/1447] 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 0458/1447] 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 0459/1447] 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 0460/1447] 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 0461/1447] 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 0462/1447] 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 0463/1447] 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 0464/1447] 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 0465/1447] 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 0466/1447] 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 0467/1447] 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 0468/1447] 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 0469/1447] 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 0470/1447] 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 0471/1447] 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 0472/1447] 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 0473/1447] 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 0474/1447] 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 0475/1447] 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 0476/1447] 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 0477/1447] 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 0478/1447] 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 0479/1447] 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 0480/1447] 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 0481/1447] 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 0482/1447] 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 0483/1447] 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 0484/1447] 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 0485/1447] 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 0486/1447] 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 0487/1447] 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 0488/1447] 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 0489/1447] 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 0490/1447] 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 0491/1447] 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 0492/1447] 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 0493/1447] 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 0494/1447] 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 0495/1447] 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 0496/1447] 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 0497/1447] 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 0498/1447] 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 0499/1447] 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 0500/1447] 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 0501/1447] 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 0502/1447] 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 0503/1447] 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 0504/1447] 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 0505/1447] 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 0506/1447] 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 0507/1447] 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 0508/1447] 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 0509/1447] 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 0510/1447] 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 0511/1447] 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 0512/1447] 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 0513/1447] 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 0514/1447] 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 0515/1447] 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 0516/1447] 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 0517/1447] 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 0518/1447] 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 0519/1447] 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 0520/1447] 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 0521/1447] 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 0522/1447] 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 0523/1447] 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 0524/1447] 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 0525/1447] 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 0526/1447] 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 0527/1447] 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 0528/1447] 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 0529/1447] 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 0530/1447] 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 0531/1447] 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 0532/1447] 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 0533/1447] 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 0534/1447] 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 0535/1447] 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 0536/1447] 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 0537/1447] 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 0538/1447] 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 0539/1447] 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 0540/1447] 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 0541/1447] 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 0542/1447] 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 0543/1447] 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 0544/1447] 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 0545/1447] 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 0546/1447] 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 0547/1447] 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 0548/1447] 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 0549/1447] 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 0550/1447] 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 0551/1447] 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 0552/1447] 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 0553/1447] 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 0554/1447] 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 0555/1447] 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 0556/1447] 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 0557/1447] 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 0558/1447] 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 0559/1447] 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 0560/1447] 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 0561/1447] 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 0562/1447] 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 0563/1447] 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 0564/1447] 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 0565/1447] 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 0566/1447] 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 0567/1447] 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 0568/1447] 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 0569/1447] 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 0570/1447] 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 0571/1447] 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 0572/1447] 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 0573/1447] 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 0574/1447] 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 0575/1447] 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 0576/1447] 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 0577/1447] 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 0578/1447] 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 0579/1447] 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 0580/1447] 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 0581/1447] 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 0582/1447] 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 0583/1447] 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 0584/1447] 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 0585/1447] 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 0586/1447] 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 0587/1447] 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 0588/1447] 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 0589/1447] 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 0590/1447] 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 0591/1447] 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 0592/1447] 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 0593/1447] 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 0594/1447] 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 0595/1447] 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 0596/1447] 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 0597/1447] 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 0598/1447] 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 0599/1447] 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 0600/1447] 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 0601/1447] 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 0602/1447] 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 0603/1447] 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 0604/1447] 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 0605/1447] 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 0606/1447] 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 0607/1447] 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 0608/1447] 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 0609/1447] 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 0610/1447] 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 0611/1447] 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 0612/1447] 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 0613/1447] 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 0614/1447] 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 0615/1447] 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 0616/1447] 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 0617/1447] 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 0618/1447] 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 0619/1447] 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 0620/1447] 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 0621/1447] 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 0622/1447] 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 0623/1447] 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 0624/1447] 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 0625/1447] 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 0626/1447] 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 0627/1447] 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 0628/1447] 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 0629/1447] 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 0630/1447] 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 0631/1447] 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 0632/1447] 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 0633/1447] 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 0634/1447] 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 0635/1447] 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 0636/1447] 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 0637/1447] 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 0638/1447] 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 0639/1447] 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 0640/1447] 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 0641/1447] 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 0642/1447] 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 0643/1447] 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 0644/1447] 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 0645/1447] 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 0646/1447] 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 0647/1447] 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 0648/1447] 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 0649/1447] 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 0650/1447] 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 0651/1447] 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 0652/1447] 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 0653/1447] 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 0654/1447] 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 0655/1447] 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 0656/1447] 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 0657/1447] 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 0658/1447] 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 0659/1447] 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 0660/1447] 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 0661/1447] 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 0662/1447] 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 0663/1447] 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 0664/1447] 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 0665/1447] 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 0666/1447] 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 0667/1447] 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 0668/1447] 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 0669/1447] 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 0670/1447] 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 0671/1447] 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 0672/1447] 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 0673/1447] 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 0674/1447] 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 0675/1447] 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 0676/1447] 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 0677/1447] 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 0678/1447] 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 0679/1447] 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 0680/1447] 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 0681/1447] 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 0682/1447] 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 0683/1447] 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 0684/1447] 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 0685/1447] 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 0686/1447] 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 0687/1447] 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 0688/1447] 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 0689/1447] 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 0690/1447] 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 0691/1447] 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 0692/1447] 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 0693/1447] 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 0694/1447] 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 0695/1447] 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 0696/1447] 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 0697/1447] 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 0698/1447] 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 0699/1447] 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 0700/1447] 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 0701/1447] 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 0702/1447] 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 0703/1447] 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 0704/1447] 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 0705/1447] 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 0706/1447] 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 0707/1447] 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 0708/1447] 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 0709/1447] 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 0710/1447] 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 0711/1447] 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 0712/1447] 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 0713/1447] 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 0714/1447] 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 0715/1447] 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 0716/1447] 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 0717/1447] 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 0718/1447] 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 0719/1447] 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 0720/1447] 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 0721/1447] 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 0722/1447] 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 0723/1447] 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 0724/1447] 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 0725/1447] 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 0726/1447] 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 0727/1447] 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 0728/1447] 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 0729/1447] 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 0730/1447] 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 0731/1447] 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 0732/1447] 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 0733/1447] 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 0734/1447] 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 0735/1447] 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 0736/1447] 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 0737/1447] 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 0738/1447] 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 0739/1447] 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 0740/1447] 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 0741/1447] 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 0742/1447] 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 0743/1447] 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 0744/1447] 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 0745/1447] 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 0746/1447] 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 0747/1447] 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 0748/1447] 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 0749/1447] 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 0750/1447] 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 0751/1447] 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 0752/1447] 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 0753/1447] 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 0754/1447] 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 0755/1447] 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 0756/1447] 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 0757/1447] 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 0758/1447] 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 0759/1447] 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 0760/1447] 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 0761/1447] 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 0762/1447] 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 0763/1447] 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 0764/1447] 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 0765/1447] 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 0766/1447] 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 0767/1447] 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 0768/1447] 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 0769/1447] 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 0770/1447] 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 0771/1447] 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 0772/1447] 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 0773/1447] 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 0774/1447] 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 0775/1447] 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 0776/1447] 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 0777/1447] 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 0778/1447] 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 0779/1447] 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 0780/1447] 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 0781/1447] 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 0782/1447] 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 0783/1447] 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 0784/1447] 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 0785/1447] 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 0786/1447] 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 0787/1447] 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 0788/1447] 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 0789/1447] 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 0790/1447] 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 0791/1447] 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 0792/1447] 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 0793/1447] 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 0794/1447] 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 0795/1447] 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 0796/1447] 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 0797/1447] 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 0798/1447] 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 0799/1447] 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 0800/1447] 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 0801/1447] 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 0802/1447] 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 0803/1447] 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 0804/1447] 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 0805/1447] 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 0806/1447] 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 0807/1447] 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 0808/1447] 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 0809/1447] 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 0810/1447] 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 0811/1447] 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 0812/1447] 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 0813/1447] 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 0814/1447] 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 0815/1447] 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 0816/1447] 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 0817/1447] 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 0818/1447] 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 0819/1447] 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 0820/1447] 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 0821/1447] 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 0822/1447] 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 0823/1447] 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 0824/1447] 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 0825/1447] 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 0826/1447] 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 0827/1447] 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 0828/1447] 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 0829/1447] 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 0830/1447] 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 0831/1447] 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 0832/1447] 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 0833/1447] 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 0834/1447] 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 0835/1447] 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 0836/1447] 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 0837/1447] 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 0838/1447] 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 0839/1447] 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 0840/1447] 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 0841/1447] 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 0842/1447] 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 0843/1447] 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 0844/1447] 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 0845/1447] 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 0846/1447] 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 0847/1447] 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 0848/1447] 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 0849/1447] 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 0850/1447] 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 0851/1447] 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 0852/1447] 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 0853/1447] 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 0854/1447] 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 0855/1447] 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 0856/1447] 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 0857/1447] 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 0858/1447] 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 0859/1447] 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 0860/1447] 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 0861/1447] 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 0862/1447] 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 0863/1447] 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 0864/1447] 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 0865/1447] 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 0866/1447] 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 0867/1447] 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 0868/1447] 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 0869/1447] 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 0870/1447] 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 0871/1447] 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 0872/1447] 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 0873/1447] 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 0874/1447] 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 0875/1447] 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 0876/1447] 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 0877/1447] 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 0878/1447] 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 0879/1447] 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 0880/1447] 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 0881/1447] 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 0882/1447] 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 0883/1447] 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 0884/1447] 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 0885/1447] 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 0886/1447] 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 0887/1447] 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 0888/1447] 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 0889/1447] 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 0890/1447] 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 0891/1447] 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 0892/1447] 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 0893/1447] 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 0894/1447] 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 0895/1447] 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 0896/1447] 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 0897/1447] 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 0898/1447] 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 0899/1447] 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 0900/1447] 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 0901/1447] 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 0902/1447] 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 0903/1447] 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 0904/1447] 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 0905/1447] 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 0906/1447] 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 0907/1447] 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 0908/1447] 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 0909/1447] 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 0910/1447] 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 0911/1447] 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 0912/1447] 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 0913/1447] 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 0914/1447] 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 0915/1447] 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 0916/1447] 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 0917/1447] 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 0918/1447] 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 0919/1447] 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 0920/1447] 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 0921/1447] 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 0922/1447] 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 0923/1447] 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 0924/1447] 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 0925/1447] 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 0926/1447] 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 0927/1447] 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 0928/1447] 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 0929/1447] 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 0930/1447] 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 0931/1447] 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 0932/1447] 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 0933/1447] 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 0934/1447] 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 0935/1447] 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 0936/1447] 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 0937/1447] 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 0938/1447] 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 0939/1447] 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 0940/1447] 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 0941/1447] 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 0942/1447] 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 0943/1447] 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 0944/1447] 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 0945/1447] 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 0946/1447] 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 0947/1447] 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 0948/1447] 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 0949/1447] 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 0950/1447] 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 0951/1447] 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 0952/1447] 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 0953/1447] 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 0954/1447] 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 0955/1447] 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 0956/1447] 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 0957/1447] 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 0958/1447] 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 0959/1447] 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 0960/1447] 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 0961/1447] 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 0962/1447] 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 0963/1447] 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 0964/1447] 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 0965/1447] 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 0966/1447] 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 0967/1447] 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 0968/1447] 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 0969/1447] 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 0970/1447] 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 0971/1447] 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 0972/1447] 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 0973/1447] 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 0974/1447] 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 0975/1447] 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 0976/1447] 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 0977/1447] 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 0978/1447] 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 0979/1447] 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 0980/1447] 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 0981/1447] 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 0982/1447] 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 0983/1447] 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 0984/1447] 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 0985/1447] 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 0986/1447] 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 0987/1447] 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 0988/1447] 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 0989/1447] 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 0990/1447] 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 0991/1447] 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 0992/1447] 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 0993/1447] 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 0994/1447] 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 0995/1447] 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 0996/1447] 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 0997/1447] 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 8db70abde9df880fec32b31a92d2af8b99ffacee Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 16 Oct 2025 17:08:14 -0400 Subject: [PATCH 0998/1447] 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 4377082c87f80bbb2e673a76aed67b1ad92b8107 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 21 Oct 2025 13:21:36 -0400 Subject: [PATCH 0999/1447] 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 a171568960699ba6d26894874533a852ed28d84a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 23 Jun 2025 20:50:58 +0200 Subject: [PATCH 1000/1447] 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 b79e2f6dd3e625cafe529a18d391bdbd00f2230a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 29 Jun 2025 09:49:53 +0200 Subject: [PATCH 1001/1447] 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 0e90449e2b6f87ea3be6b814bca83e3873654ea5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 17 Dec 2025 08:41:50 +0100 Subject: [PATCH 1002/1447] 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 07493dfa3cc6c0044ba42679ea194267b55f4262 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 23 Jun 2025 17:06:51 +0200 Subject: [PATCH 1003/1447] 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 4795992f965ad1ae9151885db918fa5fb7ed1bc6 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 8 May 2025 14:52:43 -0400 Subject: [PATCH 1004/1447] 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 f04f0fc4773c5bd7da8bfe4cb25ee667519359f0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Dec 2025 12:19:58 +0100 Subject: [PATCH 1005/1447] 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 976cb044dc8d1f94c54fdc10e6505837701bf301 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 1 May 2025 12:57:20 -0400 Subject: [PATCH 1006/1447] 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 16b3cd66ab412f099e00faa5acd9ab4d8165f77a Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:27 -0500 Subject: [PATCH 1007/1447] 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 89537701261d101b2661a51a54f4f22f9b42caaa Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 2 Dec 2025 17:03:28 -0500 Subject: [PATCH 1008/1447] 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 93ecfe6558baf3ae42fe8761c5d6f6229daa8e90 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:29 -0500 Subject: [PATCH 1009/1447] 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 8640d819172cb9cd25720b9f162786dd55e18bf0 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:30 -0500 Subject: [PATCH 1010/1447] 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 2dc64bb74a8aa2046bbe4d968713e11d7e360331 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 2 Dec 2025 17:03:31 -0500 Subject: [PATCH 1011/1447] 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 05444f798a6e3557d39ff5b5d388fd0fd3a6316d Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:32 -0500 Subject: [PATCH 1012/1447] 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 404d38bfe18f9489de206873c59be14681d14e68 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 2 Dec 2025 17:03:34 -0500 Subject: [PATCH 1013/1447] 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 7eaa5343f3baf00842a508ee1b69997af8bfc62a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 18 Nov 2025 22:17:19 +0100 Subject: [PATCH 1014/1447] 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 5261bfec70f7f13352654e4deb03eed20dee70bc Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 16 May 2025 12:28:30 -0400 Subject: [PATCH 1015/1447] 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 20e9aa3d399bf41eb45a998fb26bab32a1211d1f Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 16 May 2025 17:26:11 -0400 Subject: [PATCH 1016/1447] 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 68bbdaa40637c393aa462ae54ce9dc3b80e3d27f Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 16 May 2025 12:25:57 -0400 Subject: [PATCH 1017/1447] 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 5918f55ce61b45761457c3f843da226637b07254 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 16:50:51 +0900 Subject: [PATCH 1018/1447] 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 d483a6f877de08f299c553d58be6982300b299df Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 16:56:21 +0900 Subject: [PATCH 1019/1447] 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 71009c80c68872661f959922634f26083e30a9a0 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 16:59:20 +0900 Subject: [PATCH 1020/1447] 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 42ec9aca28c1ca38bcbc8b5c0da69ca7e66ae73c Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 5 Apr 2023 17:44:13 +0900 Subject: [PATCH 1021/1447] 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 49dae79b5e2c7ca8fb26078b320c8f6b96d2c36f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 11 Feb 2023 17:08:36 +0900 Subject: [PATCH 1022/1447] 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 94c08fd3f0f1a5355fccb7a3e06489adb89d2e0c Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 8 May 2024 19:50:07 +0900 Subject: [PATCH 1023/1447] 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 4facd6a60850d80aa54af8d99482239522245627 Mon Sep 17 00:00:00 2001 From: Caterina Shablia Date: Tue, 2 Sep 2025 17:27:37 +0000 Subject: [PATCH 1024/1447] 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 b7883533abf41ce344a8adc2325da2f0b944f4c6 Mon Sep 17 00:00:00 2001 From: Caterina Shablia Date: Tue, 2 Sep 2025 17:33:38 +0000 Subject: [PATCH 1025/1447] 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 6748e038ec365a08622bcdd972b3c1ea3f5c0161 Mon Sep 17 00:00:00 2001 From: Caterina Shablia Date: Tue, 2 Sep 2025 17:43:06 +0000 Subject: [PATCH 1026/1447] 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 29ef77d931982927e8ec88d1c3e7366eed13368b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 13:04:32 +0100 Subject: [PATCH 1027/1447] 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 2d7ff9d86bde99061af17e7f9446365f7a007302 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 6 Oct 2025 12:05:55 +0000 Subject: [PATCH 1028/1447] 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 b7e646409b93a222964efe076ea8c4ee5b35eebf Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 6 Oct 2025 12:05:56 +0000 Subject: [PATCH 1029/1447] 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 e34ad054c0d68b1e788ec2bb1196708a86d93c0b Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 8 May 2024 14:17:05 +0900 Subject: [PATCH 1030/1447] 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 aa69e3f27ceba513eece4f28921bb924c4fe8e72 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 21 Jan 2025 23:51:38 +0900 Subject: [PATCH 1031/1447] 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 6bd24dbc214de29da361a955d7e8c242babad6fa Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Nov 2025 10:09:11 +0100 Subject: [PATCH 1032/1447] 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 79c6dcfce207866d3aae99a51b1268460de3bde6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Nov 2025 10:11:11 +0100 Subject: [PATCH 1033/1447] 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 2957bd84ec68f52875a856a7be0042fcfd0e2b9a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 22:41:26 +0100 Subject: [PATCH 1034/1447] 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 3757549f1a6b7c827fe6906f45f261424e0e0e44 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 22:42:41 +0100 Subject: [PATCH 1035/1447] 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 a31dbbcf71c53713e203e696a61cb476b36e62ee Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 23:44:23 +0100 Subject: [PATCH 1036/1447] 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 d3c5d0cc1210515f88eff65b422a606b4b2ff77b Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 17 Feb 2023 00:28:27 +0900 Subject: [PATCH 1037/1447] 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 1fd477c47e23122f3998d1c02fb76898a3fa5d0f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 17 Feb 2023 00:20:55 +0900 Subject: [PATCH 1038/1447] 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 84b952337dc4263cfe48a76c7b9f7cd5c69e8451 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 17 Feb 2023 00:31:51 +0900 Subject: [PATCH 1039/1447] 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 | 41 + 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, 19718 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..a3022e16485597 --- /dev/null +++ b/drivers/gpu/drm/asahi/Kconfig @@ -0,0 +1,41 @@ +# 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 IOMMU_IO_PGTABLE_LPAE + 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 5ca536ada163b4e38c4df2b8d73d699dbc3d532b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 4 Dec 2025 09:03:25 +0100 Subject: [PATCH 1040/1447] 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 faee89db4a9aef3881d216e9a3d517cfd638dd46 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 4 Dec 2025 21:50:30 +0100 Subject: [PATCH 1041/1447] 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 86ea6178270c5a813d916dbdb4e0b3a9f0e1b966 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 17 Dec 2025 08:56:19 +0100 Subject: [PATCH 1042/1447] 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 be73fa15995945c12acf8ef4443b58c3a4b626f4 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 03:29:46 +0900 Subject: [PATCH 1043/1447] 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 a9ea610356df6d0c91a864b37fd3253fc7cd8293 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 03:30:28 +0900 Subject: [PATCH 1044/1447] 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 b59c7e3bbcb7ffaf88c4ad1aeb28a41067984136 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 04:03:40 +0900 Subject: [PATCH 1045/1447] 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 ca395e59ffe08f0d0634598a7b6c144e21d6eeba Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 10 Dec 2024 04:04:55 +0900 Subject: [PATCH 1046/1447] 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 4d5d1f5306eea4e26260cfeadf2d82338b0d9432 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 20 Dec 2025 23:31:38 +0100 Subject: [PATCH 1047/1447] 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 5317f62a97108fdddc66d7f211508300a640cac2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 01:06:56 +0100 Subject: [PATCH 1048/1447] 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 e9c7c8f18ab2f9e594a7c7e0ec8f7ad0daa7353e Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 27 Jan 2025 23:02:59 +0900 Subject: [PATCH 1049/1447] 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 6611be42b8f86c2dc175f12133c945d233b3d36f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:12:01 +0900 Subject: [PATCH 1050/1447] 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 efb933840fc095586b2dba1e6d65665ad02d32fb Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:12:36 +0900 Subject: [PATCH 1051/1447] 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 2ebb30dbbc181029a46a852c1a0d07f94b4b70e9 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:28:01 +0900 Subject: [PATCH 1052/1447] 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 87d91b647848f43c6f1764875816dbb6e511eec2 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:36:19 +0900 Subject: [PATCH 1053/1447] 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 e28f2a922487c6bbc6a30c47d622b4025ddf120e Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 02:06:00 +0900 Subject: [PATCH 1054/1447] 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 c0d27cc0659412639449b15cc8ff60bafd19ab15 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 03:53:14 +0900 Subject: [PATCH 1055/1447] 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 5bf16d758c57a7c6b2eafea59441cd193f24551b Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Tue, 28 Jan 2025 03:54:28 +0900 Subject: [PATCH 1056/1447] 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 a3022e16485597..ee2e9a85cfb5bd 100644 --- a/drivers/gpu/drm/asahi/Kconfig +++ b/drivers/gpu/drm/asahi/Kconfig @@ -25,6 +25,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 2a094a49c89e7ec50d5e2070de1113a570a53223 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 11 May 2025 12:40:04 +0200 Subject: [PATCH 1057/1447] 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 7e2598cbda109431ec56674f1605213aa06bd829 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 24 Sep 2025 10:24:55 +0200 Subject: [PATCH 1058/1447] 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 a229835c2d2304a5055a43447405636a2ff09506 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Nov 2025 10:23:15 +0100 Subject: [PATCH 1059/1447] 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 cca8650d459e57ee8a40fff5f5c9442c4b1c185d Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 12 Jul 2025 23:18:42 +0200 Subject: [PATCH 1060/1447] 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 225a1cb2815bc5e7cbeb2dfe5a3b360f770361d9 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1061/1447] 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 e23280b1411a9701060417bbafd368d50b0fcab6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jun 2025 16:47:44 +0200 Subject: [PATCH 1062/1447] 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 9cd7e0e22f44e48e3dbab68592adbb05635ffaa2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 28 Nov 2024 08:18:45 +0100 Subject: [PATCH 1063/1447] 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 8496010a26d59b1fc0066020d5e7b2dc9e0d6d13 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 17:50:03 +0100 Subject: [PATCH 1064/1447] 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 2791fb893d069c50fad5f6128ba0876066b799da Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:01:54 +0100 Subject: [PATCH 1065/1447] 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 740d75d11e674cf53bbba4dbee50463aadf2933f Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1066/1447] 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 8c2db5cc2de6505ee798b52382572afe232b34a7 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1067/1447] 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 692a8d03391eebeddf7be4ec7d6515514b5102fa Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1068/1447] 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 0b0f4d27d8f7f71a26deb80d8611e83dc75d0dc7 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:27:59 +0100 Subject: [PATCH 1069/1447] 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 3b840930e2989861f693e665a910faf9b7482140 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:31:23 +0100 Subject: [PATCH 1070/1447] 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 | 11 + 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, 1001 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 f9e78ac69d5831..e09c2502fb6ccd 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -43,6 +43,17 @@ config RUST_APPLE_RTKIT depends on RUST depends on APPLE_RTKIT +config APPLE_AOP + tristate "Apple \"Always-on\" Processor" + depends on ARCH_APPLE || COMPILE_TEST + 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 dd69db63677dd6..dd0b018a4f8db1 100644 --- a/rust/kernel/soc/apple/mod.rs +++ b/rust/kernel/soc/apple/mod.rs @@ -4,3 +4,6 @@ #[cfg(CONFIG_APPLE_RTKIT = "y")] pub mod rtkit; + +#[cfg(any(CONFIG_APPLE_AOP = "y", CONFIG_APPLE_AOP = "m"))] +pub mod aop; From b7c87718911dd68704f957c569a4ced3703762de Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Dec 2025 10:26:13 +0100 Subject: [PATCH 1071/1447] 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 d3d6f6760809602cde7ee3d361f6a9266d2a6d9e Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Mon, 18 Nov 2024 00:03:20 +0100 Subject: [PATCH 1072/1447] 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 f899d300d5fddd323be279a101a44779f89030fe Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Feb 2025 11:57:07 +0100 Subject: [PATCH 1073/1447] 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 47cccc58886dd57f0ecd6608dfd94d1dcf8105ed Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:40:28 +0100 Subject: [PATCH 1074/1447] 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 | 129 +++++++++++++++++ drivers/iio/common/aop_sensors/aop_las.rs | 76 ++++++++++ rust/kernel/iio/common/aop_sensors.rs | 161 ++++++++++++++++++++++ rust/kernel/iio/common/mod.rs | 11 ++ rust/kernel/iio/mod.rs | 5 + rust/kernel/lib.rs | 2 + 10 files changed, 411 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..ed873c8c173509 --- /dev/null +++ b/drivers/iio/common/aop_sensors/aop_als.rs @@ -0,0 +1,129 @@ +// 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, + 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_SET_ALS_PROPERTY: u16 = 0x4; + +fn enable_als(aop: &dyn AOP, dev: &platform::Device, svc: &EPICService) -> Result<()> { + let fwnode = dev.as_ref().fwnode().ok_or(ENODEV)?; + let prop_name = c_str!("apple,als-calibration"); + if !fwnode.property_present(prop_name) { + dev_warn!( + dev.as_ref(), + "ALS Calibration not found, will not enable it" + ); + return Ok(()); + } + let calib_len = fwnode.property_count_elem::(prop_name)?; + let prop = fwnode + .property_read_array_vec::(prop_name, calib_len)? + .required_by(dev.as_ref())?; + + set_als_property(aop, svc, 0xb, &prop)?; + set_als_property(aop, svc, 0, &200000u32.to_le_bytes())?; + + Ok(()) +} +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 data = AopSensorData::new(dev.into(), ty, MsgProc(40))?; + 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"], +} 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..78730b53ea2577 --- /dev/null +++ b/rust/kernel/iio/common/aop_sensors.rs @@ -0,0 +1,161 @@ +// 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, +}; + +pub trait MessageProcessor { + fn process(&self, message: &[u8]) -> u32; +} + +pub struct AopSensorData { + dev: ARef, + ty: u32, + value: AtomicU32, + msg_proc: T, +} + +impl AopSensorData { + pub fn new(dev: platform::Device, 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, +} + +pub struct IIORegistration { + dev: *mut bindings::iio_dev, + spec: Pin>, + registered: bool, + _p: PhantomData>, +} + +impl IIORegistration { + 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 d62733c89a76c126539c4354fe0b70ccf6c1b515 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 24 Mar 2025 09:30:53 +0100 Subject: [PATCH 1075/1447] rust: iio: common: aop: Add TODO fn/struct docs Silences rust warnings. Signed-off-by: Janne Grunau --- rust/kernel/iio/common/aop_sensors.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rust/kernel/iio/common/aop_sensors.rs b/rust/kernel/iio/common/aop_sensors.rs index 78730b53ea2577..de0835784b66a8 100644 --- a/rust/kernel/iio/common/aop_sensors.rs +++ b/rust/kernel/iio/common/aop_sensors.rs @@ -17,10 +17,13 @@ use kernel::{ 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, @@ -29,7 +32,8 @@ pub struct AopSensorData { } impl AopSensorData { - pub fn new(dev: platform::Device, ty: u32, msg_proc: T) -> Result>> { + /// TODO: add documentation + pub fn new(dev: ARef, ty: u32, msg_proc: T) -> Result>> { Ok(Arc::new( AopSensorData { dev, @@ -80,6 +84,7 @@ struct IIOSpec { _p: PhantomPinned, } +/// TODO: add documentation pub struct IIORegistration { dev: *mut bindings::iio_dev, spec: Pin>, @@ -88,6 +93,7 @@ pub struct IIORegistration { } impl IIORegistration { + /// TODO: add documentation pub fn new( data: Arc>, name: &'static CStr, From 7cc11916ccd291ed901d3bd01526a2730f6d76a5 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:22:21 +0100 Subject: [PATCH 1076/1447] rust: soc: apple: Add Apple mailbox abstractions Signed-off-by: Sasha Finkelstein --- 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 + 6 files changed, 109 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/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 dd0b018a4f8db1..51149360872094 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_APPLE_MAILBOX = "y")] +pub mod mailbox; From e08116e258e28774a3770a80debc819b89928f45 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:24:07 +0100 Subject: [PATCH 1077/1447] 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 | 11 ++ drivers/soc/apple/Makefile | 2 + drivers/soc/apple/sep.rs | 353 +++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+) create mode 100644 drivers/soc/apple/sep.rs diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index e09c2502fb6ccd..894003cae2ae6f 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -54,6 +54,17 @@ 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 RUST + select RUST_APPLE_RTKIT + 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 44766346d2b5622ec8b2f512508edbc1e9c5901c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 26 Nov 2021 15:37:23 +0900 Subject: [PATCH 1078/1447] arm64: dts: apple: t8103: Add Apple Type-C PHY and dwc3 Add all nodes and connections required to make USB3 work on M1-based Apple machines. Co-developed-by: Hector Martin Signed-off-by: Hector Martin Signed-off-by: Sven Peter --- 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 | 132 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8103.dtsi | 109 ++++++++++++++++++ 7 files changed, 301 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..c3827d6bb7a771 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,113 @@ 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 { + 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 { + 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 582238a9b87793..975dde6c64e2d0 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"; @@ -1013,6 +1014,114 @@ 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + power-domains = <&ps_atc1_usb>; + }; + pcie0_dart_0: iommu@681008000 { compatible = "apple,t8103-dart"; reg = <0x6 0x81008000 0x0 0x4000>; From 6516418fc19f297843304e4d1d10eebd9e92a41a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 2 Feb 2023 12:50:56 +0100 Subject: [PATCH 1079/1447] arm64: dts: apple: t8112: Add Apple Type-C PHY and dwc3 Add all nodes and connections required to make USB3 work on M2-based Apple machines. Signed-off-by: Hector Martin Co-developed-by: Hector Martin 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 | 109 ++++++++++++++++++ 6 files changed, 291 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 06fe257f08be49..01ce9f52190dc5 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -21,6 +21,18 @@ }; }; +/* + * 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/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 904905f96c86ff..3765b46833e73a 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 / { @@ -1016,6 +1017,114 @@ 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>; + dma-coherent; + 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,t8112-dart", "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,t8112-dart", "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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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,t8112-dart", "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,t8112-dart", "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; + svid = <0xff01>, <0x8087>; + power-domains = <&ps_atc1_usb>; + }; + pcie0_dart: iommu@681008000 { compatible = "apple,t8110-dart"; reg = <0x6 0x81008000 0x0 0x4000>; From 6655e7bbfcf8b1c5e244bd96685c7c8897ebf744 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 17 May 2022 23:54:26 +0200 Subject: [PATCH 1080/1447] arm64: dts: apple: t600x: Add Apple Type-C PHY and dwc3 Add all nodes and connections required to make USB3 work on M1 Pro, Max and Ultra based Apple machines. Co-developed-by: R Signed-off-by: R 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 | 220 ++++++++++++++ .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 230 +++++++++++++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 270 ++++++++++++++++++ 6 files changed, 872 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..192c8f87485168 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -119,3 +119,223 @@ 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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..4fe2c7f48689fe 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,158 @@ 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>; + }; + }; + }; +}; + #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>; + }; + }; }; }; From 077730c0196bf784bbb0b7c8828608c2750b45ef Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 9 Apr 2023 23:48:38 +0900 Subject: [PATCH 1081/1447] arm64: dts: apple: t602x: Add Apple Type-C PHY and dwc3 Add all nodes and connections required to make USB3 work on M2 Pro, Max and Ultra based Apple machines. Signed-off-by: Hector Martin Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t6022-j180d.dts | 491 +++++++++++++++++++++ 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 | 220 +++++++++ 4 files changed, 875 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts index dca6bd167c225a..8b76c76029c5ce 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>; + }; + }; + }; + }; }; }; +&hpm4 { + label = "USB-C Back 3"; +}; + +&hpm5 { + 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,361 @@ 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>; + }; + }; + }; +}; + +/* 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>; + }; + }; }; }; 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..31452ac7c27364 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -126,3 +126,223 @@ 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + 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>; + dma-coherent; + 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; + svid = <0xff01>, <0x8087>; + power-domains = <&DIE_NODE(ps_atc3_usb)>; + }; From 6d59a4c5adca1c288bc771e778d0c43c04ddbf27 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Tue, 15 Nov 2022 12:09:48 +0100 Subject: [PATCH 1082/1447] 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 975dde6c64e2d0..6db675e09134b0 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>; @@ -1062,6 +1163,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; svid = <0xff01>, <0x8087>; @@ -1113,6 +1233,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 62c539c660c695d320c897db87ea08041c225a49 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 4 Sep 2025 11:01:38 +0200 Subject: [PATCH 1083/1447] 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 3765b46833e73a..99c5f13d2472ec 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>; @@ -1065,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; svid = <0xff01>, <0x8087>; @@ -1119,6 +1235,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; svid = <0xff01>, <0x8087>; From 8c547d08af0d890776f3152246509d622152164e Mon Sep 17 00:00:00 2001 From: R Date: Tue, 15 Nov 2022 12:09:52 +0100 Subject: [PATCH 1084/1447] 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 192c8f87485168..e8df550edeffdf 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>; @@ -169,6 +356,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; svid = <0xff01>, <0x8087>; @@ -224,6 +430,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; svid = <0xff01>, <0x8087>; @@ -279,6 +504,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; svid = <0xff01>, <0x8087>; @@ -334,6 +578,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; svid = <0xff01>, <0x8087>; From b72bd17b742ab430894a92136e78cdee68688e9c Mon Sep 17 00:00:00 2001 From: R Date: Tue, 15 Nov 2022 12:09:52 +0100 Subject: [PATCH 1085/1447] 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 31452ac7c27364..b385ef3a3a2ec5 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 69fb493efc4c8c521a12a5a7bf3c46fa6248f276 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 6 Feb 2022 21:22:29 +0900 Subject: [PATCH 1086/1447] 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 c3827d6bb7a771..f1873e01ae051e 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -203,6 +203,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 c8fd06c7c8551d6f292df6f3e6fd0439d93bda65 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 6 Feb 2022 21:22:29 +0900 Subject: [PATCH 1087/1447] 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 4fe2c7f48689fe..6b781f080cfb9e 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -175,6 +175,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>; @@ -194,6 +195,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 a4bb5cbb67a067..ce493f87de2d02 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -355,6 +355,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>; @@ -374,6 +375,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>; @@ -396,6 +398,7 @@ &port03 { /* USB xHCI */ bus-range = <4 4>; + pwren-gpios = <&smc_gpio 20 GPIO_ACTIVE_HIGH>; status = "okay"; }; From 15deaa33b808ba534b4616361ce142fbd26a075c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 14 Feb 2023 10:07:49 +0100 Subject: [PATCH 1088/1447] 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 01ce9f52190dc5..b74df94f5f2cd5 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -17,7 +17,9 @@ model = "Apple Mac mini (M2, 2023)"; aliases { + bluetooth0 = &bluetooth0; ethernet0 = ðernet0; + wifi0 = &wifi0; }; }; @@ -40,6 +42,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 fd318fc5e8e9e1c23c07bbb5475d3b448f006099 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 4 Feb 2022 12:59:39 +0900 Subject: [PATCH 1089/1447] 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 21e81a8899d8d7..eb593a4b57960a 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -42,6 +42,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 b4f72fbafaf104..540905cfcb0b29 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -42,6 +42,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 b74df94f5f2cd5..bad23526588c10 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -42,6 +42,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>; @@ -62,6 +63,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 f8e442152ff23f..2dab0195063096 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -90,6 +90,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 dbe83855435d4742f22700e5982301fbc842df5a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 17 Mar 2022 23:49:07 +0900 Subject: [PATCH 1090/1447] 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 4bfe0d2de30ad6..299b1f51b54179 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 44ed2c3cfbc402b5ec9ffa10dad330a30977b55b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 24 Apr 2023 23:31:20 +0900 Subject: [PATCH 1091/1447] 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 0439a377944cb0d58b7b539e33a90b588e93cadc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 26 Nov 2021 00:24:15 +0100 Subject: [PATCH 1092/1447] 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 678f89c3d47fbf..c8a2769f29ba44 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -58,6 +58,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 bce9b911009e2b..7b13e16957ead7 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -53,3 +53,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 248b4c9da66059b05d624bcd68b58f98dcb7e88e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Nov 2021 21:31:21 +0100 Subject: [PATCH 1093/1447] 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 6b781f080cfb9e..42f4cee1d84b14 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -171,6 +171,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 45a01553f533c7e71dbd5f25aa29101da7adc870 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 2 Feb 2023 11:15:35 +0100 Subject: [PATCH 1094/1447] 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 eb593a4b57960a..894c6bc9184866 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -19,6 +19,7 @@ aliases { bluetooth0 = &bluetooth0; + keyboard = &keyboard; wifi0 = &wifi0; }; @@ -91,3 +92,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 540905cfcb0b29..4ae8b2a1dc088f 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -19,6 +19,7 @@ aliases { bluetooth0 = &bluetooth0; + keyboard = &keyboard; wifi0 = &wifi0; }; @@ -91,3 +92,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 2dab0195063096..455f8efe5b59c9 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -23,6 +23,7 @@ */ aliases { bluetooth0 = &bluetooth0; + keyboard = &keyboard; touchbar0 = &touchbar0; wifi0 = &wifi0; }; @@ -146,3 +147,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 99c5f13d2472ec..1f735174e73c29 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 e809eec7229006c79df90a9ab48d675115aac099 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 9 Apr 2023 23:49:01 +0900 Subject: [PATCH 1095/1447] 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 0718b9870c65c1ce9ad25d0245a5a99b25878ad9 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:26 +0900 Subject: [PATCH 1096/1447] 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 6f6c8df54fc58857a1e508b1c960e4720acbe667 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:47 +0900 Subject: [PATCH 1097/1447] 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 299b1f51b54179..6ce7c439024d77 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 8040d1ca5d3f5ae0894c4172b67b9075f519ac23 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:56 +0900 Subject: [PATCH 1098/1447] 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 ffa9a2cdd88ecf105f265f98776f925b24f016ec 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 1099/1447] 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 968fe22163d443..fd48d748114bf1 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -70,6 +70,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 c8a2769f29ba44..27aa9cfbafa3bb 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -79,8 +79,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 { @@ -152,3 +228,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 7b13e16957ead7..c1cc9eb7756992 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -74,3 +74,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 9983e11cacdf19..7cf103418455a8 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -87,3 +87,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 6703f4a3d3db4c..5b9c69e28bfdd8 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -68,3 +68,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 12aa35de5a6694fbd8c2ff2597ae4758df8e3a52 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 1100/1447] 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 42f4cee1d84b14..75764f1cf7d283 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -167,6 +167,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>; }; @@ -383,4 +483,37 @@ }; }; +/ { + 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 ce493f87de2d02..6b7ceedee748d8 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -347,10 +347,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 f72a75efa62b92dba271ea788afd830ad1b20404 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 1101/1447] 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 894c6bc9184866..755b0bdc3d0f28 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -85,6 +85,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"; }; @@ -93,6 +163,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 4ae8b2a1dc088f..aba6966d382a78 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -85,6 +85,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"; }; @@ -93,6 +185,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 bad23526588c10..2607b5a95f4e12 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -84,3 +84,57 @@ &pcie2_dart { status = "okay"; }; + +&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 455f8efe5b59c9..b6d0fbc6991d6e 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -122,6 +122,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"; }; @@ -148,6 +218,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 b072ee45254d96701793bd102eb6b19cb4a651ee Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 17 Oct 2022 18:29:28 +0900 Subject: [PATCH 1102/1447] 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 8b67557c00b7e61f7b6e2a68f06abf78b0b14315 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 25 Jan 2022 21:50:59 +0100 Subject: [PATCH 1103/1447] 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 6ce7c439024d77..a3af38b953afca 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 72625145a48a10002c57c020a0de446f4d798c23 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 24 Apr 2022 11:20:31 +0200 Subject: [PATCH 1104/1447] 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 9c9714de04712c4e32095a3b06b20344ee37306c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:32:09 +0200 Subject: [PATCH 1105/1447] 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 6db675e09134b0..b6480f4d7f7a2c 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1328,6 +1328,8 @@ pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; + dma-coherent; + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; From 72e725635e03a74f1c6eee3bd282436b5013e227 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:32:09 +0200 Subject: [PATCH 1106/1447] 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 1f735174e73c29..f8d8a7008352e3 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1410,6 +1410,8 @@ pinctrl-0 = <&pcie_pins>; pinctrl-names = "default"; + dma-coherent; + port00: pci@0,0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; From fd5824cbae098cce539b4d98941efcf4243a9482 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 20 Sep 2021 02:27:09 +0900 Subject: [PATCH 1107/1447] 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 fd48d748114bf1..987661419ca2d3 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -21,6 +21,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 27aa9cfbafa3bb..81a237028641b9 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -38,6 +38,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 c1cc9eb7756992..c41a7301d4f36e 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -30,6 +30,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 7cf103418455a8..210cbc638b0807 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -21,6 +21,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 5b9c69e28bfdd8..783c92eaa47e0f 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -21,6 +21,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 f1873e01ae051e..a791db09eced5d 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 b6480f4d7f7a2c..c54f6a9770b68b 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 138561bfdb78507962bcf216f9f4877e7e459bac Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 11 Mar 2022 22:14:52 +0100 Subject: [PATCH 1108/1447] 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 75764f1cf7d283..bd7262b7a512f2 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -18,6 +18,9 @@ atcphy2 = &atcphy2; atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; + dcp = &dcp; + disp0 = &display; + disp0_piodma = &disp0_piodma; serial0 = &serial0; wifi0 = &wifi0; }; @@ -34,6 +37,7 @@ reg = <0 0 0 0>; /* To be filled by loader */ /* Format properties will be added by loader */ status = "disabled"; + panel = &panel; }; }; @@ -59,6 +63,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 6b7ceedee748d8..3a28dcc53eadf2 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -16,6 +16,9 @@ atcphy2 = &atcphy2; atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; + dcp = &dcp; + disp0 = &display; + disp0_piodma = &disp0_piodma; ethernet0 = ðernet0; serial0 = &serial0; wifi0 = &wifi0; @@ -46,6 +49,10 @@ status = "okay"; }; +&dcp { + apple,connector-type = "HDMI-A"; +}; + /* USB Type C */ &i2c0 { hpm0: usb-pd@38 { From aecca51e5b37fe67d20bb043615e8b17a74197d1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 20 Nov 2022 20:22:57 +0100 Subject: [PATCH 1109/1447] 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 755b0bdc3d0f28..5c561362e06f1c 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -36,6 +36,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 aba6966d382a78..81ca35fc7898bf 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -36,6 +36,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 2607b5a95f4e12..db9de64ccba46c 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -23,6 +23,15 @@ }; }; +&framebuffer0 { + power-domains = <&ps_disp0_cpu0>, <&ps_dptx_ext_phy>; +}; + +/* disable dcp until it is supported */ +&dcp { + status = "disabled"; +}; + /* * Provide labels for the USB type C ports. */ diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index b6d0fbc6991d6e..ad1e20cfdc8e86 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -51,6 +51,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 f8d8a7008352e3..5c340d42ed5719 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 08a810d95d197e56c1809677e49abedb41ed11ee Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2022 00:17:35 +0900 Subject: [PATCH 1110/1447] 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 bd7262b7a512f2..491885dfdcd150 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -37,7 +37,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 3a28dcc53eadf2..556ff5d14674a3 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -36,6 +36,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 32a7fffc41724433726b5f62c782c48bc5b68635 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2022 00:17:35 +0900 Subject: [PATCH 1111/1447] 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 a791db09eced5d..d9bf130e10ac93 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 a3af38b953afca..e74ae5b1d51f9c 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 { @@ -1007,7 +1006,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 c54f6a9770b68b..99734c97877614 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 ce2a8ffdeab1d2021147960291abf44c37065440 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2022 00:17:35 +0900 Subject: [PATCH 1112/1447] 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 5c340d42ed5719..797c9b390de7d2 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 1b7bfcd3cc0605c7178cb456a0952d9aaeadee3f Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 2 Nov 2022 15:58:07 +0900 Subject: [PATCH 1113/1447] 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 66531025f3d209bc8086b2623237c84e6f2ab850 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 18 Aug 2022 02:15:43 +0900 Subject: [PATCH 1114/1447] 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 987661419ca2d3..b20300a5b18306 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -130,3 +130,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 210cbc638b0807..9d355ef69336ce 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -132,3 +132,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 783c92eaa47e0f..5eaa1502324062 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -113,3 +113,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 99734c97877614..248132551335ea 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 2d1f4e5d2b34f560aa40937a97c4be9f0ed2d745 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 3 Nov 2022 01:03:44 +0900 Subject: [PATCH 1115/1447] 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 189995655c03adf78c1f4918b936d4bfacbf9667 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 25 Nov 2022 23:06:59 +0900 Subject: [PATCH 1116/1447] 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 db9de64ccba46c..686426f9468d18 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -147,3 +147,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 797c9b390de7d2..3ccd690a476156 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 f6c4bdd04b8d4e24606a11b2487c8390f43c49a5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 30 Dec 2022 01:15:57 +0100 Subject: [PATCH 1117/1447] arm64: dts: apple: t8103: Add missing ps_pmp dependency to ps_gfx AGX' ASC crashes shortly after ps_pmp is powered down due to dcp runtime PM suspend. 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 e74ae5b1d51f9c..f0ae11bf6ce688 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -739,6 +739,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "gfx"; + power-domains = <&ps_pmp>; }; ps_dcs4: power-controller@320 { From 93c345a768ccbf65302afd0e7a86185977ed7e58 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 24 Apr 2023 23:27:52 +0900 Subject: [PATCH 1118/1447] 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 823f075d3dab061cf9dbda5c5ce96e9b6192233e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 27 Apr 2023 13:53:35 +0900 Subject: [PATCH 1119/1447] 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 43285683b3d364b5eec0e86d8e0fe2570a60a1c5 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 27 Apr 2023 13:54:17 +0900 Subject: [PATCH 1120/1447] 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 248132551335ea..03f23eac6ed447 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 3ae3a8bd90ba44bf3eafca1c08b40fa6bb07cc7d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 27 Apr 2023 13:54:30 +0900 Subject: [PATCH 1121/1447] 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 3ccd690a476156..9149e5ca4f46ec 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 4248caf075560586be8c0096fff59fa1a702397d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 02:34:01 +0900 Subject: [PATCH 1122/1447] 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 03f23eac6ed447..04eff6f07058c0 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 9149e5ca4f46ec..b91d8968850823 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 05a3c35e6b4d6906acc34c2393ad18c8f049aab1 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 9 Apr 2023 23:48:38 +0900 Subject: [PATCH 1123/1447] 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 491885dfdcd150..ac77021772bc29 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -283,6 +283,7 @@ clock-frequency = <1068000000>; }; +#ifndef NO_SPI_TRACKPAD &spi3 { status = "okay"; @@ -303,6 +304,7 @@ interrupts-extended = <&pinctrl_nub 6 IRQ_TYPE_LEVEL_LOW>; }; }; +#endif /* PCIe devices */ &port00 { @@ -329,6 +331,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>; @@ -341,6 +344,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 556ff5d14674a3..14056a466593a1 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -16,9 +16,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 8b76c76029c5ce..bda0b27891cbed 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -24,8 +24,11 @@ atcphy5 = &atcphy1_die1; atcphy6 = &atcphy2_die1; atcphy7 = &atcphy3_die1; - nvram = &nvram; + //bluetooth0 = &bluetooth0; + //ethernet0 = ðernet0; + //ethernet1 = ðernet1; serial0 = &serial0; + //wifi0 = &wifi0; }; chosen { @@ -44,6 +47,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 2140fd57a319e78443d7ad5186a0f928bd442e18 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 16 Nov 2025 13:06:48 +0100 Subject: [PATCH 1124/1447] 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 b385ef3a3a2ec5..71ba45435dc527 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 04ddef2efda2b7790ac763ac9f4780e0df03afec Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 10 Apr 2023 18:15:33 +0900 Subject: [PATCH 1125/1447] 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 b91d8968850823..4f237f45ed0549 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 5c6a11838802c579c6a8ee3d9596de8f342bd5ff Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 12 Oct 2023 23:20:39 +0900 Subject: [PATCH 1126/1447] 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 b2a5f5bc2b9065e52300ec91a6e6bc2e479aa1dc Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 29 Oct 2023 08:55:40 +1000 Subject: [PATCH 1127/1447] 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 d36edcb71ec2c37d084893803ee7a08029510679 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 13 Oct 2023 01:37:23 +0900 Subject: [PATCH 1128/1447] 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 bda0b27891cbed..9e4c5921ff5b87 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -540,6 +540,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 { @@ -549,6 +551,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 7b0772285c214abd29655b5fd322a5f17659ead0 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 02:34:01 +0900 Subject: [PATCH 1129/1447] 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 2d8d622f8fd1732730b1b420036ddcf0dad321c7 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 18 Apr 2023 05:05:57 +0900 Subject: [PATCH 1130/1447] 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 aa7bc44c3ba239521c3645f718700c177eadb89b Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 26 Apr 2023 02:17:26 +0900 Subject: [PATCH 1131/1447] 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 56e8a8a307f533e7da33ac4e7cb314fec7281fdd Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 7 Aug 2023 19:53:50 +0900 Subject: [PATCH 1132/1447] 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 9e4c5921ff5b87..06cd0ed884ca65 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"; @@ -24,11 +25,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 { @@ -609,6 +610,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 4c82374f212642c32f69cb614380ac3749c62099 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 25 Sep 2023 19:55:58 +0200 Subject: [PATCH 1133/1447] 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 3a840c71a8dc2be3a832338be687ba4a1ecc074c Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 6 Nov 2023 20:33:51 +1000 Subject: [PATCH 1134/1447] 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 04eff6f07058c0..a398afacf255d2 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 84e036f4fbc1fc903921599c6952c58da4e15d7d Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Sun, 3 Sep 2023 16:41:27 +1000 Subject: [PATCH 1135/1447] 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 4f237f45ed0549..e349b467c2f784 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 a01d23196694c116d06d0732772bd880980431f9 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Thu, 31 Aug 2023 19:10:27 +0900 Subject: [PATCH 1136/1447] 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 a398afacf255d2..839ffd53660be6 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 93aeacf608d9707e578f8fdff1ec2b9d3647d78f Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Sat, 2 Sep 2023 01:39:10 +0900 Subject: [PATCH 1137/1447] 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 f030192fb8a266e0e9a4e899009effdf1479ae49 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 8 Sep 2023 00:46:11 +0900 Subject: [PATCH 1138/1447] 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 e349b467c2f784..7139ece4b8aed9 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 dee89aed07b68364e998a0a716d8afdf87f89917 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 24 Sep 2023 01:01:10 +0900 Subject: [PATCH 1139/1447] 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 c1b2bc4f6c1a94bbc33056b92532e4edeb0e5dbd Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Thu, 28 Sep 2023 02:02:43 +0900 Subject: [PATCH 1140/1447] 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 ac77021772bc29..48b9cf604501d5 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -536,3 +536,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 81a237028641b9..e748681dd5186c 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -272,3 +272,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 c41a7301d4f36e..5eaba5edd16614 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -167,3 +167,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 9d355ef69336ce..2f6f17329f573e 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -136,3 +136,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 5eaa1502324062..0abd05eb6e07c7 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -117,3 +117,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 5c561362e06f1c..08e8849b5e7028 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -242,3 +242,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 81ca35fc7898bf..cfaef0c2fb9cc9 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -268,3 +268,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 ad1e20cfdc8e86..1529ea1447c719 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -296,3 +296,9 @@ tp_accel { }; }; + +#include "isp-imx248.dtsi" + +&isp { + apple,platform-id = <6>; +}; From edee181c7f71aa56a5eac567bc2dcb8704a3344c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 Nov 2023 22:44:17 +0100 Subject: [PATCH 1141/1447] 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 81a8e3f07cb83abaec117a4f28bb1bc664446c5a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 7 Oct 2023 00:38:24 +0200 Subject: [PATCH 1142/1447] 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 733cdb7052696deb09d1e07a3bf229d66058975f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 8 Oct 2023 19:53:27 +0900 Subject: [PATCH 1143/1447] 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 7de0ce5a8df7ed615f3373b13cfae8dc657ca63d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:51:53 +0100 Subject: [PATCH 1144/1447] 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 45be09c225a38b5e35b7ca7781402818b0c3ea8e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:51:53 +0100 Subject: [PATCH 1145/1447] 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 839ffd53660be6..53ca189a493c57 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 ad9a193eaf7b08fa275f98c69ef23fa64110466a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 20:04:09 +0100 Subject: [PATCH 1146/1447] 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 7139ece4b8aed9..48509dd7222b61 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 a134703be3b0487f4d7d7299eba0862de3ba79db Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:46:53 +0100 Subject: [PATCH 1147/1447] 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 6d81eb94268dcc3fe7e8aab8e1214b9f8c51c198 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Fri, 27 Aug 2021 11:19:51 -0400 Subject: [PATCH 1148/1447] 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 1149/1447] 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 1150/1447] 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 1151/1447] 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 1152/1447] 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 1153/1447] 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 1154/1447] 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 1155/1447] 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 1156/1447] 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 1157/1447] 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 1158/1447] 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 1159/1447] 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 1160/1447] 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 1161/1447] 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 1162/1447] 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 1163/1447] 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 1164/1447] 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 1165/1447] 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 1166/1447] 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 1167/1447] 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 1168/1447] 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 1169/1447] 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 1170/1447] 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 1171/1447] 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 1172/1447] 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 1173/1447] 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 1174/1447] 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 1175/1447] 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 1176/1447] 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 1177/1447] 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 1178/1447] 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 1179/1447] 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 1180/1447] 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 1181/1447] 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 1182/1447] 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 1183/1447] 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 1184/1447] 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 1185/1447] 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 1186/1447] 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 1187/1447] 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 1188/1447] 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 1189/1447] 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 1190/1447] 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 1191/1447] 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 1192/1447] 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 1193/1447] 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 1194/1447] 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 1195/1447] 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 1196/1447] 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 1197/1447] 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 1198/1447] 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 1199/1447] 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 1200/1447] 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 1201/1447] 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 1202/1447] 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 1203/1447] 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 1204/1447] 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 1205/1447] 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 1206/1447] 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 1207/1447] 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 1208/1447] 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 1209/1447] 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 1210/1447] 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 1211/1447] 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 1212/1447] 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 1213/1447] 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 1214/1447] 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 1215/1447] 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 1216/1447] 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 1217/1447] 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 1218/1447] 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 1219/1447] 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 1220/1447] 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 1221/1447] 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 1222/1447] 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 1223/1447] 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 1224/1447] 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 1225/1447] 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 1226/1447] 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 1227/1447] 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 1228/1447] 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 1229/1447] 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 1230/1447] 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 1231/1447] 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 1232/1447] 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 1233/1447] 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 1234/1447] 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 1235/1447] 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 1236/1447] 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 1237/1447] 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 1238/1447] 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 1239/1447] 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 1240/1447] 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 1241/1447] 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 1242/1447] 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 1243/1447] 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 1244/1447] 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 1245/1447] 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 1246/1447] 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 1247/1447] 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 1248/1447] 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 1249/1447] 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 1250/1447] 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 1251/1447] 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 1252/1447] 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 1253/1447] 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 1254/1447] 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 1255/1447] 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 1256/1447] 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 1257/1447] 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 1258/1447] 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 1259/1447] 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 1260/1447] 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 1261/1447] 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 1262/1447] 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 1263/1447] 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 1264/1447] 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 1265/1447] 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 1266/1447] 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 1267/1447] 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 1268/1447] 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 1269/1447] 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 1270/1447] 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 1271/1447] 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 1272/1447] 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 1273/1447] 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 1274/1447] 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 1275/1447] 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 1276/1447] 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 1277/1447] 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 1278/1447] 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 1279/1447] 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 1280/1447] 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 1281/1447] 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 1282/1447] 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 1283/1447] 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 1284/1447] 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 1285/1447] 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 1286/1447] 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 1287/1447] 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 1288/1447] 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 1289/1447] 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 1290/1447] 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 1291/1447] 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 1292/1447] 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 1293/1447] 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 1294/1447] 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 1295/1447] 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 1296/1447] 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 1297/1447] 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 1298/1447] 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 1299/1447] 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 1300/1447] 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 1301/1447] 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 1302/1447] 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 1303/1447] 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 1304/1447] 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 1305/1447] 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 1306/1447] 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 1307/1447] 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 1308/1447] 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 1309/1447] 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 1310/1447] 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 1311/1447] 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 1312/1447] 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 1313/1447] 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 1314/1447] 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 1315/1447] 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 1316/1447] 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 1317/1447] 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 1318/1447] 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 1319/1447] 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 1320/1447] 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 1321/1447] 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 1322/1447] 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 1323/1447] 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 1324/1447] 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 1325/1447] 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 1326/1447] 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 1327/1447] 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 1328/1447] 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 1329/1447] 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 1330/1447] 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 1331/1447] 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 1332/1447] 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 1333/1447] 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 1334/1447] 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 1335/1447] 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 1336/1447] 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 1337/1447] 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 1338/1447] 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 1339/1447] 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 1340/1447] 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 1341/1447] 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 1342/1447] 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 1343/1447] 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 1344/1447] 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 1345/1447] 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 1346/1447] 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 1347/1447] 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 1348/1447] 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 1349/1447] 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 1350/1447] 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 1351/1447] 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 1352/1447] 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 1353/1447] 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 1354/1447] 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 1355/1447] 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 1356/1447] 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 1357/1447] 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 1358/1447] 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 1359/1447] 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 1360/1447] 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 1361/1447] 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 1362/1447] 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 1363/1447] 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 1364/1447] 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 1365/1447] 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 1366/1447] 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 1367/1447] 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 1368/1447] 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 1369/1447] 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 1370/1447] 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 1371/1447] 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 1372/1447] 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 1373/1447] 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 1374/1447] 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 1375/1447] 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 1376/1447] 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 1377/1447] 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 1378/1447] 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 1379/1447] 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 1380/1447] 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 1381/1447] 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 1382/1447] 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 1383/1447] 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 1384/1447] 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 1385/1447] 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 1386/1447] 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 1387/1447] 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 1388/1447] 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 1389/1447] 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 8b7e508a66a02b84b5beb5d98ecc54e00fb97701 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 30 Sep 2022 22:30:13 +0200 Subject: [PATCH 1390/1447] 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 53ca189a493c57..d19a1beb24abc4 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 50b2ad618bd72556eae371178081169c9576f025 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 3 Dec 2022 22:12:25 +0100 Subject: [PATCH 1391/1447] 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 48509dd7222b61..ab100bc1111523 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 d4f63c8abc7a7742a52dcece950e082d40f424a0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Oct 2022 20:44:02 +0200 Subject: [PATCH 1392/1447] 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 e8df550edeffdf..a590f747a39348 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 e28c7ca66f930d57aded2f84a34befb967a48855 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 19:45:46 +0200 Subject: [PATCH 1393/1447] 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 71ba45435dc527..368870059cb55c 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 7f02b4553f534177ca1734ef0be41111bc033b87 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 28 Oct 2023 23:12:33 +0200 Subject: [PATCH 1394/1447] 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 ab100bc1111523..d58f4c07a5b6ae 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 5b9a63a0ce4a0a22ba9544a34a617d94f5c21514 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 29 Oct 2023 10:39:17 +0100 Subject: [PATCH 1395/1447] 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 368870059cb55c..57044835c91462 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 0915d9ff183f20001871e779f9eb6d1ca6561c55 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 00:35:54 +0100 Subject: [PATCH 1396/1447] 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 a590f747a39348..9506b231b274f7 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -513,6 +513,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>; @@ -587,6 +595,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>; @@ -661,6 +677,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>; @@ -734,3 +758,11 @@ svid = <0xff01>, <0x8087>; 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 597c1f9365e1c7668ce9531ba897e41c73b749bf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 23:17:13 +0200 Subject: [PATCH 1397/1447] 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 57044835c91462..4f41b2dbca248d 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -332,6 +332,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>; @@ -387,6 +395,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>; @@ -442,6 +458,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>; @@ -496,3 +520,11 @@ svid = <0xff01>, <0x8087>; 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 af0cc0ce49798f07b9d0d351142f570ed17e684e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 28 Oct 2023 23:40:47 +0200 Subject: [PATCH 1398/1447] 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 686426f9468d18..8697eef5c7a3fc 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -18,20 +18,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>; +}; + /* * Provide labels for the USB type C ports. */ From 866acec4b1138ac4b6a06da3d7ca2dded9df46f7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 21:47:19 +0100 Subject: [PATCH 1399/1447] 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 475470625cb62326946a9108f832c5d21a5070e4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 4 Nov 2023 22:33:29 +0100 Subject: [PATCH 1400/1447] 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 0c49716c66fb4b49d0abb3aef9990c7b6bee954c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 20:39:42 +0100 Subject: [PATCH 1401/1447] 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 48b9cf604501d5..02bad0447eccee 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -19,6 +19,7 @@ atcphy3 = &atcphy3; bluetooth0 = &bluetooth0; dcp = &dcp; + dcpext0 = &dcpext0; disp0 = &display; disp0_piodma = &disp0_piodma; serial0 = &serial0; @@ -71,6 +72,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 491537c96e0d21c5a160ff4e7612e75770f7bedb 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 1402/1447] 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 e2c20e193547d17c67cbfb0c188eca6ebbd8c305 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Nov 2023 23:58:10 +0100 Subject: [PATCH 1403/1447] 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 25989dadfaa89a987e52431d7251b85d5b7caf5d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Apr 2024 18:01:37 +0200 Subject: [PATCH 1404/1447] 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 424bdbd4f72c525f806737897ad35a5a10227398 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Apr 2024 18:01:37 +0200 Subject: [PATCH 1405/1447] 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 a485c9b6ed0ddb4d62cda9dc07cc051cf1a143f0 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 1406/1447] 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 b20300a5b18306..b12dbecb5f0cd7 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -18,6 +18,7 @@ aliases { ethernet0 = ðernet0; + sio = &sio; }; }; @@ -25,6 +26,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 d19a1beb24abc4..8920f30e045cbe 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 a4041ffd490b38c7d671978e70e7ab3678d9e8d0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 13 Nov 2023 23:35:07 +0100 Subject: [PATCH 1407/1447] 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 8697eef5c7a3fc..5ad461092fbd7e 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -21,6 +21,7 @@ /delete-property/ dcp; dcpext = &dcpext; ethernet0 = ðernet0; + sio = &sio; wifi0 = &wifi0; }; }; @@ -64,6 +65,10 @@ apple,dptx-phy = <5>; }; +&dpaudio1 { + status = "okay"; +}; + /* * Provide labels for the USB type C ports. */ diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index d58f4c07a5b6ae..04595b05a6850f 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 a79915c80cca269fbca5ed07b3a854d829f7bfc7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 14:55:00 +0100 Subject: [PATCH 1408/1447] 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 9506b231b274f7..81762990ef6fc8 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 6d1eaeb567741396f10f9e5c72b3c0d5a85fd10c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 14:57:09 +0100 Subject: [PATCH 1409/1447] 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 81762990ef6fc8..5b401e901feac9 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 843a4b9de70fa29d1cba006fe795db3580218406 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Jan 2024 14:45:49 +0100 Subject: [PATCH 1410/1447] 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 4f41b2dbca248d..79762d476b1252 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 41c67da14cb5afe5d4df384cdeb76496dedad3e2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 7 Apr 2024 23:10:55 +0200 Subject: [PATCH 1411/1447] 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 02bad0447eccee..a2ed9c6595ab18 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -23,6 +23,7 @@ disp0 = &display; disp0_piodma = &disp0_piodma; serial0 = &serial0; + sio = &sio; wifi0 = &wifi0; }; @@ -106,6 +107,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 14056a466593a1..4066eea4f41864 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -23,6 +23,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 f124433aa07e48368805e4ada8f849784405fe77 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Apr 2024 12:57:52 +0200 Subject: [PATCH 1412/1447] 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 a2ed9c6595ab18..11c64455b59fc6 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -107,6 +107,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 4066eea4f41864..9914504b759bd2 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -57,6 +57,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 3f5e0d14fa8d8906199d1e89db650911b7f57c0f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Apr 2024 12:57:52 +0200 Subject: [PATCH 1413/1447] 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 b12dbecb5f0cd7..9f8712b5b6accc 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -26,6 +26,11 @@ apple,connector-type = "HDMI-A"; }; +/* remove once m1n1 enables sio nodes after setup */ +&sio { + status = "okay"; +}; + &dpaudio0 { status = "okay"; }; From adb2f2b85d3b820f65068b5784d24edc752a20c2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 27 Apr 2024 12:57:52 +0200 Subject: [PATCH 1414/1447] 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 5ad461092fbd7e..aa6be5e55e4ce9 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -65,6 +65,11 @@ apple,dptx-phy = <5>; }; +/* remove once m1n1 enables sio nodes after setup */ +&sio { + status = "okay"; +}; + &dpaudio1 { status = "okay"; }; From 7c7cce682f1004d9b83242481ac9b3a5f063ee85 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Apr 2025 15:25:24 +0200 Subject: [PATCH 1415/1447] 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 06cd0ed884ca65..c11b73d48c2ef0 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -26,6 +26,7 @@ atcphy6 = &atcphy2_die1; atcphy7 = &atcphy3_die1; bluetooth0 = &bluetooth0; + dcpext0 = &dcpext0; ethernet0 = ðernet0; ethernet1 = ðernet1; serial0 = &serial0; @@ -65,6 +66,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 811a5c12b2ea14ee761eceeb94f464f02ba58b2c Mon Sep 17 00:00:00 2001 From: James Calligeros Date: Mon, 1 Jul 2024 10:07:12 +1000 Subject: [PATCH 1416/1447] 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 9e471b8a80457798f721a910f570f08479b6c0b1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:29:10 +0200 Subject: [PATCH 1417/1447] 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 9f8712b5b6accc..d52a0b4525c041 100644 --- a/arch/arm64/boot/dts/apple/t8103-j274.dts +++ b/arch/arm64/boot/dts/apple/t8103-j274.dts @@ -144,3 +144,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 e748681dd5186c..f3b6a7c9f1e037 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -278,3 +278,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 5eaba5edd16614..cf460c5324ca5d 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -173,3 +173,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 2f6f17329f573e..c8c9fe69416169 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -142,3 +142,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 0abd05eb6e07c7..9dba7b1601609d 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -123,3 +123,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 d9bf130e10ac93..d956310d9359ab 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -235,4 +235,6 @@ clock-frequency = <900000000>; }; +#include "hwmon-common.dtsi" + #include "spi1-nvram.dtsi" From 9f8119da4f1621a71620ddacf1ac910f66563549 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:31:30 +0200 Subject: [PATCH 1418/1447] 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 08e8849b5e7028..7cf44c77ee20c2 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -249,3 +249,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 cfaef0c2fb9cc9..20d6c61ad343c2 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -275,3 +275,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 aa6be5e55e4ce9..0640843b378cfb 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -193,3 +193,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 1529ea1447c719..f022d61bf12014 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -302,3 +302,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 b72c74f360496bab26ac58272cb49c96a8c66a7d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:34:02 +0200 Subject: [PATCH 1419/1447] 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 11c64455b59fc6..ce5678812b0d63 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -591,3 +591,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 9914504b759bd2..3fae3e0cb4263f 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -484,3 +484,5 @@ }; #include "spi1-nvram.dtsi" + +#include "hwmon-common.dtsi" From bb0f7946c8c84edd4e30c55b25c7fd93efc1ab02 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 11 Jul 2024 21:36:29 +0200 Subject: [PATCH 1420/1447] 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 b9424a0cb79599ce5c960d23ec70baa8c690c897 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:44:45 +0100 Subject: [PATCH 1421/1447] 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 8920f30e045cbe..18078eab3e55bc 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 04595b05a6850f..c266fd2230bcbc 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 0c0104c62f7e7e82b569d255ccc2185ea5ff07ea Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 10 Nov 2024 23:25:31 +0100 Subject: [PATCH 1422/1447] 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 18078eab3e55bc..37d28da1235774 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 c266fd2230bcbc..0a990312ca113b 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 bbbfe59bdeb6f4261dc9d89fd182f663ce277aa4 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 9 Nov 2024 18:44:45 +0100 Subject: [PATCH 1423/1447] 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 f3b6a7c9f1e037..bbdaec696a58d0 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -243,6 +243,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 cf460c5324ca5d..b0253574865a13 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -139,6 +139,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 c8c9fe69416169..45e16a4d4be4cb 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -115,6 +115,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 9dba7b1601609d..edb0756b3a76d2 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -96,6 +96,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 7cf44c77ee20c2..2fda52f42dbaf5 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -178,6 +178,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 20d6c61ad343c2..55f7848a0fa031 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -200,6 +200,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 f022d61bf12014..155e81f4479c10 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -232,6 +232,11 @@ }; }; +&aop_audio { + apple,chassis-name = "J493"; + apple,machine-kind = "MacBook Pro"; +}; + / { sound { compatible = "apple,j493-macaudio", "apple,macaudio"; From f7af169caab9cb8c3f2d0a776a3e43f9349d664d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 14:55:05 +0100 Subject: [PATCH 1424/1447] 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 ce5678812b0d63..5cda12255c41d4 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -551,6 +551,22 @@ }; }; +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&aop_admac { + status = "okay"; +}; + +&aop { + status = "okay"; +}; + / { sound: sound { /* compatible is set per machine */ From 5c465ac5c7837df2518012693c172ec558101618 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:08:45 +0100 Subject: [PATCH 1425/1447] 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 bbdaec696a58d0..93c27924723699 100644 --- a/arch/arm64/boot/dts/apple/t8103-j293.dts +++ b/arch/arm64/boot/dts/apple/t8103-j293.dts @@ -23,6 +23,7 @@ */ aliases { touchbar0 = &touchbar0; + sep = &sep; }; led-controller { @@ -243,6 +244,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 74d787f0e1f993406fa48aed73a65e2da9844f93 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:09:31 +0100 Subject: [PATCH 1426/1447] 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 b0253574865a13..56ce0869ec596d 100644 --- a/arch/arm64/boot/dts/apple/t8103-j313.dts +++ b/arch/arm64/boot/dts/apple/t8103-j313.dts @@ -17,6 +17,10 @@ compatible = "apple,j313", "apple,t8103", "apple,arm-platform"; model = "Apple MacBook Air (M1, 2020)"; + aliases { + sep = &sep; + }; + led-controller { compatible = "pwm-leds"; led-0 { @@ -139,6 +143,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 9cc92b4ddf8c505fa85e317912511f19fc9ae84f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:19:31 +0100 Subject: [PATCH 1427/1447] 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 45e16a4d4be4cb..86c3bbc79993a5 100644 --- a/arch/arm64/boot/dts/apple/t8103-j456.dts +++ b/arch/arm64/boot/dts/apple/t8103-j456.dts @@ -115,9 +115,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 edb0756b3a76d2..04c69b342aadd3 100644 --- a/arch/arm64/boot/dts/apple/t8103-j457.dts +++ b/arch/arm64/boot/dts/apple/t8103-j457.dts @@ -96,9 +96,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 ff8ff2a62fcf7f386c159241d53538f8b6029375 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:10:42 +0100 Subject: [PATCH 1428/1447] 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 2fda52f42dbaf5..381437c4056d3b 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -178,6 +178,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 830d3ee8b34a8c5ae87c4126c5955a9234471904 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:11:36 +0100 Subject: [PATCH 1429/1447] 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 55f7848a0fa031..88ed6eec3e6e62 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -200,6 +200,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 2561a74e8dfcbb29808aa2f320207f10d6b0deeb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 30 Dec 2024 15:12:15 +0100 Subject: [PATCH 1430/1447] 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 155e81f4479c10..aca9951dcaca74 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -232,6 +232,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 1427600dd4ccbd2f2e43674493ae0876d0df87a9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Sep 2025 11:23:03 +0200 Subject: [PATCH 1431/1447] 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 0219bc6f0b19fdead269707c503ac4211cc23671 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Sep 2025 11:23:03 +0200 Subject: [PATCH 1432/1447] 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 37d28da1235774..e843bd79eba3da 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 9a747e645a6df079cc27c973adf28070874fa7ba Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 6 Sep 2025 11:23:04 +0200 Subject: [PATCH 1433/1447] 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 0a990312ca113b..c55a69f8652978 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 ae4e43852290547a160a401297b2e4e0ca1b6489 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:41:05 +0200 Subject: [PATCH 1434/1447] arm64: dts: apple: Add chassis-type property for all Macbooks 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 5cda12255c41d4..a83361becc37fb 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 93c27924723699..6fa04626b1d08b 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 56ce0869ec596d..883ba4a1f0100a 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"; aliases { sep = &sep; diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 381437c4056d3b..20285be747d965 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 88ed6eec3e6e62..c2c32ca5577eff 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 aca9951dcaca74..368c4a9cc01758 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 c338c0eb34382d71afa9429b0e8a519e38d8ac63 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:41:05 +0200 Subject: [PATCH 1435/1447] arm64: dts: apple: Add chassis-type property for Apple desktop devices Mac mini and Studio are desktop devices. 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 3fae3e0cb4263f..a5ee97241214d6 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 d52a0b4525c041..f55683c48784b8 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 0640843b378cfb..a82148adc799a3 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 { bluetooth0 = &bluetooth0; From 5a903378279b418af9e7d5bfc62a1e46207cc157 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:41:05 +0200 Subject: [PATCH 1436/1447] arm64: dts: apple: Add chassis-type property for Mac Pro "server" is a better fit for the Mac Pro than "desktop" because tower and rack-mount version are using the same DT. 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 c11b73d48c2ef0..cad37b54f3b632 100644 --- a/arch/arm64/boot/dts/apple/t6022-j180d.dts +++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts @@ -16,6 +16,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 47202050b97f3c25cc49cd989f7349afd2958311 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 19 Sep 2025 20:41:05 +0200 Subject: [PATCH 1437/1447] arm64: dts: apple: Add chassis-type property for Apple iMacs Apple iMac (M1, 2021) are all-in-one devices which have an integrated display. 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 86c3bbc79993a5..c7da4815fb94c0 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 04c69b342aadd3..fc0f28fb1c4367 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 08a5ee24e6a5672194232073c58889d288eee623 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 26 Sep 2025 10:24:04 +0200 Subject: [PATCH 1438/1447] 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 e843bd79eba3da..4dc9312de3baa8 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 c55a69f8652978..6f154a80d8825d 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 27b058006aef85e5c9cadbc2251f31a9dbd74fd4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 26 Sep 2025 10:25:37 +0200 Subject: [PATCH 1439/1447] 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 961be0386c4c7416bcd7280e73015b964e4f2c7e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 16 Dec 2025 21:50:05 +0100 Subject: [PATCH 1440/1447] 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 05517482ac69dcc86a1bbc2846d06f1d1f4756c0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 22 Dec 2025 22:33:21 +0100 Subject: [PATCH 1441/1447] 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 4dc9312de3baa8..35180d58ca8ea4 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1693,6 +1693,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>; @@ -1768,6 +1775,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 b7aa8b2bd8664c0ec0f4a8e4a2cfb928b51dc691 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 22 Dec 2025 22:33:21 +0100 Subject: [PATCH 1442/1447] 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 6f154a80d8825d..217133dc66b7d3 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1778,6 +1778,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>; @@ -1851,6 +1858,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 51bf311a24a70b86e55dc44e36185917f29e1676 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 21 Dec 2025 20:25:56 +0100 Subject: [PATCH 1443/1447] 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 | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index a83361becc37fb..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 */ @@ -553,6 +566,20 @@ }; }; +&atcphy3 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + atcphy3_dp: endpoint { + remote-endpoint = <&decpext0_dpout>; + }; + }; + }; +}; + &aop_mbox { status = "okay"; }; From b5af743bfdf698fd64d808db829679c10af5a88a Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 18 Dec 2025 09:11:26 +0100 Subject: [PATCH 1444/1447] Input: apple_z2: Fix reading incorrect reports after exiting sleep 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 --- 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 57df9f1198f8c5e323f991f44d522ed20ca3bc2d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 23 Dec 2025 07:53:09 +0100 Subject: [PATCH 1445/1447] drm/apple: Discard EDID on disconnect The next display might not the same and the EDID will not necessarily be replaced on the next connect. This results in confused user space with hilarious but broken results. In kwin it resulted in a gap between two displays makling it impossible to move the mouse pointer to the other display. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e7ff930d4bdc1c..5ee1abaefdcd25 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -255,9 +255,10 @@ static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_ dcp->crashed = true; dev_err(dcp->dev, "DCP has crashed\n"); if (dcp->connector) { - dcp->connector->connected = 0; - drm_edid_free(dcp->connector->drm_edid); + const struct drm_edid *edid = dcp->connector->drm_edid; dcp->connector->drm_edid = NULL; + drm_edid_free(edid); + dcp->connector->connected = 0; schedule_work(&dcp->connector->hotplug_wq); } complete(&dcp->start_done); @@ -427,6 +428,9 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) static void disconnected_hpd_event(struct apple_connector *con) { if (con) { + const struct drm_edid *edid = con->drm_edid; + con->drm_edid = NULL; + drm_edid_free(edid); con->connected = 0; drm_kms_helper_connector_hotplug_event(&con->base); } From 4b0726eb707c6728f65f9da996e2ff44c49c6ca7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 25 Dec 2025 17:47:37 +0100 Subject: [PATCH 1446/1447] 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 1cf69dc816c41d27fa71b0791f2440283cd7c832 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 1 Jan 2026 11:42:32 +0100 Subject: [PATCH 1447/1447] 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); } }