From 340ccc973544a6e7e331729bc4944603085cafab Mon Sep 17 00:00:00 2001 From: Joel Fernandes Date: Wed, 8 Oct 2025 15:07:26 -0400 Subject: rust: pci: Allocate and manage PCI interrupt vectors Add support to PCI rust module to allocate, free and manage IRQ vectors. Integrate with devres for managing the allocated resources. Signed-off-by: Joel Fernandes [ Add links in doc-comments; add missing invariant comment; re-format multiple safety requirements as list and fix missing backticks; refactor the example of alloc_irq_vectors() to compile. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 214 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 201 insertions(+), 13 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 7fcc5f6022c1..d91ec9f008ae 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -6,8 +6,9 @@ use crate::{ bindings, container_of, device, + device::Bound, device_id::{RawDeviceId, RawDeviceIdIndex}, - devres::Devres, + devres::{self, Devres}, driver, error::{from_result, to_result, Result}, io::{Io, IoRaw}, @@ -19,7 +20,7 @@ use crate::{ }; use core::{ marker::PhantomData, - ops::Deref, + ops::{Deref, RangeInclusive}, ptr::{addr_of_mut, NonNull}, }; use kernel::prelude::*; @@ -28,6 +29,58 @@ mod id; pub use self::id::{Class, ClassMask, Vendor}; +/// IRQ type flags for PCI interrupt allocation. +#[derive(Debug, Clone, Copy)] +pub enum IrqType { + /// INTx interrupts. + Intx, + /// Message Signaled Interrupts (MSI). + Msi, + /// Extended Message Signaled Interrupts (MSI-X). + MsiX, +} + +impl IrqType { + /// Convert to the corresponding kernel flags. + const fn as_raw(self) -> u32 { + match self { + IrqType::Intx => bindings::PCI_IRQ_INTX, + IrqType::Msi => bindings::PCI_IRQ_MSI, + IrqType::MsiX => bindings::PCI_IRQ_MSIX, + } + } +} + +/// Set of IRQ types that can be used for PCI interrupt allocation. +#[derive(Debug, Clone, Copy, Default)] +pub struct IrqTypes(u32); + +impl IrqTypes { + /// Create a set containing all IRQ types (MSI-X, MSI, and Legacy). + pub const fn all() -> Self { + Self(bindings::PCI_IRQ_ALL_TYPES) + } + + /// Build a set of IRQ types. + /// + /// # Examples + /// + /// ```ignore + /// // Create a set with only MSI and MSI-X (no legacy interrupts). + /// let msi_only = IrqTypes::default() + /// .with(IrqType::Msi) + /// .with(IrqType::MsiX); + /// ``` + pub const fn with(self, irq_type: IrqType) -> Self { + Self(self.0 | irq_type.as_raw()) + } + + /// Get the raw flags value. + const fn as_raw(self) -> u32 { + self.0 + } +} + /// An adapter for the registration of PCI drivers. pub struct Adapter(T); @@ -516,6 +569,92 @@ impl Device { } } +/// Represents an allocated IRQ vector for a specific PCI device. +/// +/// This type ties an IRQ vector to the device it was allocated for, +/// ensuring the vector is only used with the correct device. +#[derive(Clone, Copy)] +pub struct IrqVector<'a> { + dev: &'a Device, + index: u32, +} + +impl<'a> IrqVector<'a> { + /// Creates a new [`IrqVector`] for the given device and index. + /// + /// # Safety + /// + /// - `index` must be a valid IRQ vector index for `dev`. + /// - `dev` must point to a [`Device`] that has successfully allocated IRQ vectors. + unsafe fn new(dev: &'a Device, index: u32) -> Self { + Self { dev, index } + } + + /// Returns the raw vector index. + fn index(&self) -> u32 { + self.index + } +} + +/// Represents an IRQ vector allocation for a PCI device. +/// +/// This type ensures that IRQ vectors are properly allocated and freed by +/// tying the allocation to the lifetime of this registration object. +/// +/// # Invariants +/// +/// The [`Device`] has successfully allocated IRQ vectors. +struct IrqVectorRegistration { + dev: ARef, +} + +impl IrqVectorRegistration { + /// Allocate and register IRQ vectors for the given PCI device. + /// + /// Allocates IRQ vectors and registers them with devres for automatic cleanup. + /// Returns a range of valid IRQ vectors. + fn register<'a>( + dev: &'a Device, + min_vecs: u32, + max_vecs: u32, + irq_types: IrqTypes, + ) -> Result>> { + // SAFETY: + // - `dev.as_raw()` is guaranteed to be a valid pointer to a `struct pci_dev` + // by the type invariant of `Device`. + // - `pci_alloc_irq_vectors` internally validates all other parameters + // and returns error codes. + let ret = unsafe { + bindings::pci_alloc_irq_vectors(dev.as_raw(), min_vecs, max_vecs, irq_types.as_raw()) + }; + + to_result(ret)?; + let count = ret as u32; + + // SAFETY: + // - `pci_alloc_irq_vectors` returns the number of allocated vectors on success. + // - Vectors are 0-based, so valid indices are [0, count-1]. + // - `pci_alloc_irq_vectors` guarantees `count >= min_vecs > 0`, so both `0` and + // `count - 1` are valid IRQ vector indices for `dev`. + let range = unsafe { IrqVector::new(dev, 0)..=IrqVector::new(dev, count - 1) }; + + // INVARIANT: The IRQ vector allocation for `dev` above was successful. + let irq_vecs = Self { dev: dev.into() }; + devres::register(dev.as_ref(), irq_vecs, GFP_KERNEL)?; + + Ok(range) + } +} + +impl Drop for IrqVectorRegistration { + fn drop(&mut self) { + // SAFETY: + // - By the type invariant, `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. + // - `self.dev` has successfully allocated IRQ vectors. + unsafe { bindings::pci_free_irq_vectors(self.dev.as_raw()) }; + } +} + impl Device { /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks /// can be performed on compile time for offsets (plus the requested type size) < SIZE. @@ -536,10 +675,15 @@ impl Device { self.iomap_region_sized::<0>(bar, name) } - /// Returns an [`IrqRequest`] for the IRQ vector at the given index, if any. - pub fn irq_vector(&self, index: u32) -> Result> { + /// Returns an [`IrqRequest`] for the given IRQ vector. + pub fn irq_vector(&self, vector: IrqVector<'_>) -> Result> { + // Verify that the vector belongs to this device. + if !core::ptr::eq(vector.dev.as_raw(), self.as_raw()) { + return Err(EINVAL); + } + // SAFETY: `self.as_raw` returns a valid pointer to a `struct pci_dev`. - let irq = unsafe { crate::bindings::pci_irq_vector(self.as_raw(), index) }; + let irq = unsafe { crate::bindings::pci_irq_vector(self.as_raw(), vector.index()) }; if irq < 0 { return Err(crate::error::Error::from_errno(irq)); } @@ -547,35 +691,79 @@ impl Device { Ok(unsafe { IrqRequest::new(self.as_ref(), irq as u32) }) } - /// Returns a [`kernel::irq::Registration`] for the IRQ vector at the given - /// index. + /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. pub fn request_irq<'a, T: crate::irq::Handler + 'static>( &'a self, - index: u32, + vector: IrqVector<'_>, flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, ) -> Result, Error> + 'a> { - let request = self.irq_vector(index)?; + let request = self.irq_vector(vector)?; Ok(irq::Registration::::new(request, flags, name, handler)) } - /// Returns a [`kernel::irq::ThreadedRegistration`] for the IRQ vector at - /// the given index. + /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. pub fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'static>( &'a self, - index: u32, + vector: IrqVector<'_>, flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, ) -> Result, Error> + 'a> { - let request = self.irq_vector(index)?; + let request = self.irq_vector(vector)?; Ok(irq::ThreadedRegistration::::new( request, flags, name, handler, )) } + + /// Allocate IRQ vectors for this PCI device with automatic cleanup. + /// + /// Allocates between `min_vecs` and `max_vecs` interrupt vectors for the device. + /// The allocation will use MSI-X, MSI, or legacy interrupts based on the `irq_types` + /// parameter and hardware capabilities. When multiple types are specified, the kernel + /// will try them in order of preference: MSI-X first, then MSI, then legacy interrupts. + /// + /// The allocated vectors are automatically freed when the device is unbound, using the + /// devres (device resource management) system. + /// + /// # Arguments + /// + /// * `min_vecs` - Minimum number of vectors required. + /// * `max_vecs` - Maximum number of vectors to allocate. + /// * `irq_types` - Types of interrupts that can be used. + /// + /// # Returns + /// + /// Returns a range of IRQ vectors that were successfully allocated, or an error if the + /// allocation fails or cannot meet the minimum requirement. + /// + /// # Examples + /// + /// ``` + /// # use kernel::{ device::Bound, pci}; + /// # fn no_run(dev: &pci::Device) -> Result { + /// // Allocate using any available interrupt type in the order mentioned above. + /// let vectors = dev.alloc_irq_vectors(1, 32, pci::IrqTypes::all())?; + /// + /// // Allocate MSI or MSI-X only (no legacy interrupts). + /// let msi_only = pci::IrqTypes::default() + /// .with(pci::IrqType::Msi) + /// .with(pci::IrqType::MsiX); + /// let vectors = dev.alloc_irq_vectors(4, 16, msi_only)?; + /// # Ok(()) + /// # } + /// ``` + pub fn alloc_irq_vectors( + &self, + min_vecs: u32, + max_vecs: u32, + irq_types: IrqTypes, + ) -> Result>> { + IrqVectorRegistration::register(self, min_vecs, max_vecs, irq_types) + } } impl Device { -- cgit v1.2.3 From 651692d32c21a9165db60a9bc74600074cd4ed99 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 15 Oct 2025 20:14:29 +0200 Subject: rust: pci: implement TryInto> for IrqVector<'a> Implement TryInto> for IrqVector<'a> to directly convert a pci::IrqVector into a generic IrqRequest, instead of taking the indirection via an unrelated pci::Device method. Reviewed-by: Alice Ryhl Reviewed-by: Joel Fernandes Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index d91ec9f008ae..c6b750047b2e 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -596,6 +596,20 @@ impl<'a> IrqVector<'a> { } } +impl<'a> TryInto> for IrqVector<'a> { + type Error = Error; + + fn try_into(self) -> Result> { + // SAFETY: `self.as_raw` returns a valid pointer to a `struct pci_dev`. + let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), self.index()) }; + if irq < 0 { + return Err(crate::error::Error::from_errno(irq)); + } + // SAFETY: `irq` is guaranteed to be a valid IRQ number for `&self`. + Ok(unsafe { IrqRequest::new(self.dev.as_ref(), irq as u32) }) + } +} + /// Represents an IRQ vector allocation for a PCI device. /// /// This type ensures that IRQ vectors are properly allocated and freed by @@ -675,31 +689,15 @@ impl Device { self.iomap_region_sized::<0>(bar, name) } - /// Returns an [`IrqRequest`] for the given IRQ vector. - pub fn irq_vector(&self, vector: IrqVector<'_>) -> Result> { - // Verify that the vector belongs to this device. - if !core::ptr::eq(vector.dev.as_raw(), self.as_raw()) { - return Err(EINVAL); - } - - // SAFETY: `self.as_raw` returns a valid pointer to a `struct pci_dev`. - let irq = unsafe { crate::bindings::pci_irq_vector(self.as_raw(), vector.index()) }; - if irq < 0 { - return Err(crate::error::Error::from_errno(irq)); - } - // SAFETY: `irq` is guaranteed to be a valid IRQ number for `&self`. - Ok(unsafe { IrqRequest::new(self.as_ref(), irq as u32) }) - } - /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. pub fn request_irq<'a, T: crate::irq::Handler + 'static>( &'a self, - vector: IrqVector<'_>, + vector: IrqVector<'a>, flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, ) -> Result, Error> + 'a> { - let request = self.irq_vector(vector)?; + let request = vector.try_into()?; Ok(irq::Registration::::new(request, flags, name, handler)) } @@ -707,12 +705,12 @@ impl Device { /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. pub fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'static>( &'a self, - vector: IrqVector<'_>, + vector: IrqVector<'a>, flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, ) -> Result, Error> + 'a> { - let request = self.irq_vector(vector)?; + let request = vector.try_into()?; Ok(irq::ThreadedRegistration::::new( request, flags, name, handler, -- cgit v1.2.3 From 3c2e31d717ac89c59e35e98a7910a6daa9f15a06 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 15 Oct 2025 20:14:30 +0200 Subject: rust: pci: move I/O infrastructure to separate file Move the PCI I/O infrastructure to a separate sub-module in order to keep things organized. Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 133 ++--------------------------------------------- rust/kernel/pci/io.rs | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 129 deletions(-) create mode 100644 rust/kernel/pci/io.rs diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index c6b750047b2e..ed9d8619f944 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -8,10 +8,8 @@ use crate::{ bindings, container_of, device, device::Bound, device_id::{RawDeviceId, RawDeviceIdIndex}, - devres::{self, Devres}, - driver, + devres, driver, error::{from_result, to_result, Result}, - io::{Io, IoRaw}, irq::{self, IrqRequest}, str::CStr, sync::aref::ARef, @@ -20,14 +18,16 @@ use crate::{ }; use core::{ marker::PhantomData, - ops::{Deref, RangeInclusive}, + ops::RangeInclusive, ptr::{addr_of_mut, NonNull}, }; use kernel::prelude::*; mod id; +mod io; pub use self::id::{Class, ClassMask, Vendor}; +pub use self::io::Bar; /// IRQ type flags for PCI interrupt allocation. #[derive(Debug, Clone, Copy)] @@ -358,112 +358,6 @@ pub struct Device( PhantomData, ); -/// A PCI BAR to perform I/O-Operations on. -/// -/// # Invariants -/// -/// `Bar` always holds an `IoRaw` inststance that holds a valid pointer to the start of the I/O -/// memory mapped PCI bar and its size. -pub struct Bar { - pdev: ARef, - io: IoRaw, - num: i32, -} - -impl Bar { - fn new(pdev: &Device, num: u32, name: &CStr) -> Result { - let len = pdev.resource_len(num)?; - if len == 0 { - return Err(ENOMEM); - } - - // Convert to `i32`, since that's what all the C bindings use. - let num = i32::try_from(num)?; - - // SAFETY: - // `pdev` is valid by the invariants of `Device`. - // `num` is checked for validity by a previous call to `Device::resource_len`. - // `name` is always valid. - let ret = unsafe { bindings::pci_request_region(pdev.as_raw(), num, name.as_char_ptr()) }; - if ret != 0 { - return Err(EBUSY); - } - - // SAFETY: - // `pdev` is valid by the invariants of `Device`. - // `num` is checked for validity by a previous call to `Device::resource_len`. - // `name` is always valid. - let ioptr: usize = unsafe { bindings::pci_iomap(pdev.as_raw(), num, 0) } as usize; - if ioptr == 0 { - // SAFETY: - // `pdev` valid by the invariants of `Device`. - // `num` is checked for validity by a previous call to `Device::resource_len`. - unsafe { bindings::pci_release_region(pdev.as_raw(), num) }; - return Err(ENOMEM); - } - - let io = match IoRaw::new(ioptr, len as usize) { - Ok(io) => io, - Err(err) => { - // SAFETY: - // `pdev` is valid by the invariants of `Device`. - // `ioptr` is guaranteed to be the start of a valid I/O mapped memory region. - // `num` is checked for validity by a previous call to `Device::resource_len`. - unsafe { Self::do_release(pdev, ioptr, num) }; - return Err(err); - } - }; - - Ok(Bar { - pdev: pdev.into(), - io, - num, - }) - } - - /// # Safety - /// - /// `ioptr` must be a valid pointer to the memory mapped PCI bar number `num`. - unsafe fn do_release(pdev: &Device, ioptr: usize, num: i32) { - // SAFETY: - // `pdev` is valid by the invariants of `Device`. - // `ioptr` is valid by the safety requirements. - // `num` is valid by the safety requirements. - unsafe { - bindings::pci_iounmap(pdev.as_raw(), ioptr as *mut c_void); - bindings::pci_release_region(pdev.as_raw(), num); - } - } - - fn release(&self) { - // SAFETY: The safety requirements are guaranteed by the type invariant of `self.pdev`. - unsafe { Self::do_release(&self.pdev, self.io.addr(), self.num) }; - } -} - -impl Bar { - #[inline] - fn index_is_valid(index: u32) -> bool { - // A `struct pci_dev` owns an array of resources with at most `PCI_NUM_RESOURCES` entries. - index < bindings::PCI_NUM_RESOURCES - } -} - -impl Drop for Bar { - fn drop(&mut self) { - self.release(); - } -} - -impl Deref for Bar { - type Target = Io; - - fn deref(&self) -> &Self::Target { - // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped. - unsafe { Io::from_raw(&self.io) } - } -} - impl Device { #[inline] fn as_raw(&self) -> *mut bindings::pci_dev { @@ -670,25 +564,6 @@ impl Drop for IrqVectorRegistration { } impl Device { - /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks - /// can be performed on compile time for offsets (plus the requested type size) < SIZE. - pub fn iomap_region_sized<'a, const SIZE: usize>( - &'a self, - bar: u32, - name: &'a CStr, - ) -> impl PinInit>, Error> + 'a { - Devres::new(self.as_ref(), Bar::::new(self, bar, name)) - } - - /// Mapps an entire PCI-BAR after performing a region-request on it. - pub fn iomap_region<'a>( - &'a self, - bar: u32, - name: &'a CStr, - ) -> impl PinInit, Error> + 'a { - self.iomap_region_sized::<0>(bar, name) - } - /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. pub fn request_irq<'a, T: crate::irq::Handler + 'static>( &'a self, diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs new file mode 100644 index 000000000000..65151a0a1a41 --- /dev/null +++ b/rust/kernel/pci/io.rs @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! PCI memory-mapped I/O infrastructure. + +use super::Device; +use crate::{ + bindings, device, + devres::Devres, + io::{Io, IoRaw}, + str::CStr, + sync::aref::ARef, +}; +use core::ops::Deref; +use kernel::prelude::*; + +/// A PCI BAR to perform I/O-Operations on. +/// +/// # Invariants +/// +/// `Bar` always holds an `IoRaw` inststance that holds a valid pointer to the start of the I/O +/// memory mapped PCI bar and its size. +pub struct Bar { + pdev: ARef, + io: IoRaw, + num: i32, +} + +impl Bar { + pub(super) fn new(pdev: &Device, num: u32, name: &CStr) -> Result { + let len = pdev.resource_len(num)?; + if len == 0 { + return Err(ENOMEM); + } + + // Convert to `i32`, since that's what all the C bindings use. + let num = i32::try_from(num)?; + + // SAFETY: + // `pdev` is valid by the invariants of `Device`. + // `num` is checked for validity by a previous call to `Device::resource_len`. + // `name` is always valid. + let ret = unsafe { bindings::pci_request_region(pdev.as_raw(), num, name.as_char_ptr()) }; + if ret != 0 { + return Err(EBUSY); + } + + // SAFETY: + // `pdev` is valid by the invariants of `Device`. + // `num` is checked for validity by a previous call to `Device::resource_len`. + // `name` is always valid. + let ioptr: usize = unsafe { bindings::pci_iomap(pdev.as_raw(), num, 0) } as usize; + if ioptr == 0 { + // SAFETY: + // `pdev` valid by the invariants of `Device`. + // `num` is checked for validity by a previous call to `Device::resource_len`. + unsafe { bindings::pci_release_region(pdev.as_raw(), num) }; + return Err(ENOMEM); + } + + let io = match IoRaw::new(ioptr, len as usize) { + Ok(io) => io, + Err(err) => { + // SAFETY: + // `pdev` is valid by the invariants of `Device`. + // `ioptr` is guaranteed to be the start of a valid I/O mapped memory region. + // `num` is checked for validity by a previous call to `Device::resource_len`. + unsafe { Self::do_release(pdev, ioptr, num) }; + return Err(err); + } + }; + + Ok(Bar { + pdev: pdev.into(), + io, + num, + }) + } + + /// # Safety + /// + /// `ioptr` must be a valid pointer to the memory mapped PCI bar number `num`. + unsafe fn do_release(pdev: &Device, ioptr: usize, num: i32) { + // SAFETY: + // `pdev` is valid by the invariants of `Device`. + // `ioptr` is valid by the safety requirements. + // `num` is valid by the safety requirements. + unsafe { + bindings::pci_iounmap(pdev.as_raw(), ioptr as *mut c_void); + bindings::pci_release_region(pdev.as_raw(), num); + } + } + + fn release(&self) { + // SAFETY: The safety requirements are guaranteed by the type invariant of `self.pdev`. + unsafe { Self::do_release(&self.pdev, self.io.addr(), self.num) }; + } +} + +impl Bar { + #[inline] + pub(super) fn index_is_valid(index: u32) -> bool { + // A `struct pci_dev` owns an array of resources with at most `PCI_NUM_RESOURCES` entries. + index < bindings::PCI_NUM_RESOURCES + } +} + +impl Drop for Bar { + fn drop(&mut self) { + self.release(); + } +} + +impl Deref for Bar { + type Target = Io; + + fn deref(&self) -> &Self::Target { + // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped. + unsafe { Io::from_raw(&self.io) } + } +} + +impl Device { + /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks + /// can be performed on compile time for offsets (plus the requested type size) < SIZE. + pub fn iomap_region_sized<'a, const SIZE: usize>( + &'a self, + bar: u32, + name: &'a CStr, + ) -> impl PinInit>, Error> + 'a { + Devres::new(self.as_ref(), Bar::::new(self, bar, name)) + } + + /// Mapps an entire PCI-BAR after performing a region-request on it. + pub fn iomap_region<'a>( + &'a self, + bar: u32, + name: &'a CStr, + ) -> impl PinInit, Error> + 'a { + self.iomap_region_sized::<0>(bar, name) + } +} -- cgit v1.2.3 From e6901808a3b28d8bdabfa98a618b2eab6f8798e8 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 15 Oct 2025 20:14:31 +0200 Subject: rust: pci: move IRQ infrastructure to separate file Move the PCI interrupt infrastructure to a separate sub-module in order to keep things organized. Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 236 +---------------------------------------------- rust/kernel/pci/irq.rs | 244 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 233 deletions(-) create mode 100644 rust/kernel/pci/irq.rs diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index ed9d8619f944..ce612c9b7b56 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -6,80 +6,26 @@ use crate::{ bindings, container_of, device, - device::Bound, device_id::{RawDeviceId, RawDeviceIdIndex}, - devres, driver, + driver, error::{from_result, to_result, Result}, - irq::{self, IrqRequest}, str::CStr, - sync::aref::ARef, types::Opaque, ThisModule, }; use core::{ marker::PhantomData, - ops::RangeInclusive, ptr::{addr_of_mut, NonNull}, }; use kernel::prelude::*; mod id; mod io; +mod irq; pub use self::id::{Class, ClassMask, Vendor}; pub use self::io::Bar; - -/// IRQ type flags for PCI interrupt allocation. -#[derive(Debug, Clone, Copy)] -pub enum IrqType { - /// INTx interrupts. - Intx, - /// Message Signaled Interrupts (MSI). - Msi, - /// Extended Message Signaled Interrupts (MSI-X). - MsiX, -} - -impl IrqType { - /// Convert to the corresponding kernel flags. - const fn as_raw(self) -> u32 { - match self { - IrqType::Intx => bindings::PCI_IRQ_INTX, - IrqType::Msi => bindings::PCI_IRQ_MSI, - IrqType::MsiX => bindings::PCI_IRQ_MSIX, - } - } -} - -/// Set of IRQ types that can be used for PCI interrupt allocation. -#[derive(Debug, Clone, Copy, Default)] -pub struct IrqTypes(u32); - -impl IrqTypes { - /// Create a set containing all IRQ types (MSI-X, MSI, and Legacy). - pub const fn all() -> Self { - Self(bindings::PCI_IRQ_ALL_TYPES) - } - - /// Build a set of IRQ types. - /// - /// # Examples - /// - /// ```ignore - /// // Create a set with only MSI and MSI-X (no legacy interrupts). - /// let msi_only = IrqTypes::default() - /// .with(IrqType::Msi) - /// .with(IrqType::MsiX); - /// ``` - pub const fn with(self, irq_type: IrqType) -> Self { - Self(self.0 | irq_type.as_raw()) - } - - /// Get the raw flags value. - const fn as_raw(self) -> u32 { - self.0 - } -} +pub use self::irq::{IrqType, IrqTypes, IrqVector}; /// An adapter for the registration of PCI drivers. pub struct Adapter(T); @@ -463,182 +409,6 @@ impl Device { } } -/// Represents an allocated IRQ vector for a specific PCI device. -/// -/// This type ties an IRQ vector to the device it was allocated for, -/// ensuring the vector is only used with the correct device. -#[derive(Clone, Copy)] -pub struct IrqVector<'a> { - dev: &'a Device, - index: u32, -} - -impl<'a> IrqVector<'a> { - /// Creates a new [`IrqVector`] for the given device and index. - /// - /// # Safety - /// - /// - `index` must be a valid IRQ vector index for `dev`. - /// - `dev` must point to a [`Device`] that has successfully allocated IRQ vectors. - unsafe fn new(dev: &'a Device, index: u32) -> Self { - Self { dev, index } - } - - /// Returns the raw vector index. - fn index(&self) -> u32 { - self.index - } -} - -impl<'a> TryInto> for IrqVector<'a> { - type Error = Error; - - fn try_into(self) -> Result> { - // SAFETY: `self.as_raw` returns a valid pointer to a `struct pci_dev`. - let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), self.index()) }; - if irq < 0 { - return Err(crate::error::Error::from_errno(irq)); - } - // SAFETY: `irq` is guaranteed to be a valid IRQ number for `&self`. - Ok(unsafe { IrqRequest::new(self.dev.as_ref(), irq as u32) }) - } -} - -/// Represents an IRQ vector allocation for a PCI device. -/// -/// This type ensures that IRQ vectors are properly allocated and freed by -/// tying the allocation to the lifetime of this registration object. -/// -/// # Invariants -/// -/// The [`Device`] has successfully allocated IRQ vectors. -struct IrqVectorRegistration { - dev: ARef, -} - -impl IrqVectorRegistration { - /// Allocate and register IRQ vectors for the given PCI device. - /// - /// Allocates IRQ vectors and registers them with devres for automatic cleanup. - /// Returns a range of valid IRQ vectors. - fn register<'a>( - dev: &'a Device, - min_vecs: u32, - max_vecs: u32, - irq_types: IrqTypes, - ) -> Result>> { - // SAFETY: - // - `dev.as_raw()` is guaranteed to be a valid pointer to a `struct pci_dev` - // by the type invariant of `Device`. - // - `pci_alloc_irq_vectors` internally validates all other parameters - // and returns error codes. - let ret = unsafe { - bindings::pci_alloc_irq_vectors(dev.as_raw(), min_vecs, max_vecs, irq_types.as_raw()) - }; - - to_result(ret)?; - let count = ret as u32; - - // SAFETY: - // - `pci_alloc_irq_vectors` returns the number of allocated vectors on success. - // - Vectors are 0-based, so valid indices are [0, count-1]. - // - `pci_alloc_irq_vectors` guarantees `count >= min_vecs > 0`, so both `0` and - // `count - 1` are valid IRQ vector indices for `dev`. - let range = unsafe { IrqVector::new(dev, 0)..=IrqVector::new(dev, count - 1) }; - - // INVARIANT: The IRQ vector allocation for `dev` above was successful. - let irq_vecs = Self { dev: dev.into() }; - devres::register(dev.as_ref(), irq_vecs, GFP_KERNEL)?; - - Ok(range) - } -} - -impl Drop for IrqVectorRegistration { - fn drop(&mut self) { - // SAFETY: - // - By the type invariant, `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. - // - `self.dev` has successfully allocated IRQ vectors. - unsafe { bindings::pci_free_irq_vectors(self.dev.as_raw()) }; - } -} - -impl Device { - /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. - pub fn request_irq<'a, T: crate::irq::Handler + 'static>( - &'a self, - vector: IrqVector<'a>, - flags: irq::Flags, - name: &'static CStr, - handler: impl PinInit + 'a, - ) -> Result, Error> + 'a> { - let request = vector.try_into()?; - - Ok(irq::Registration::::new(request, flags, name, handler)) - } - - /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. - pub fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'static>( - &'a self, - vector: IrqVector<'a>, - flags: irq::Flags, - name: &'static CStr, - handler: impl PinInit + 'a, - ) -> Result, Error> + 'a> { - let request = vector.try_into()?; - - Ok(irq::ThreadedRegistration::::new( - request, flags, name, handler, - )) - } - - /// Allocate IRQ vectors for this PCI device with automatic cleanup. - /// - /// Allocates between `min_vecs` and `max_vecs` interrupt vectors for the device. - /// The allocation will use MSI-X, MSI, or legacy interrupts based on the `irq_types` - /// parameter and hardware capabilities. When multiple types are specified, the kernel - /// will try them in order of preference: MSI-X first, then MSI, then legacy interrupts. - /// - /// The allocated vectors are automatically freed when the device is unbound, using the - /// devres (device resource management) system. - /// - /// # Arguments - /// - /// * `min_vecs` - Minimum number of vectors required. - /// * `max_vecs` - Maximum number of vectors to allocate. - /// * `irq_types` - Types of interrupts that can be used. - /// - /// # Returns - /// - /// Returns a range of IRQ vectors that were successfully allocated, or an error if the - /// allocation fails or cannot meet the minimum requirement. - /// - /// # Examples - /// - /// ``` - /// # use kernel::{ device::Bound, pci}; - /// # fn no_run(dev: &pci::Device) -> Result { - /// // Allocate using any available interrupt type in the order mentioned above. - /// let vectors = dev.alloc_irq_vectors(1, 32, pci::IrqTypes::all())?; - /// - /// // Allocate MSI or MSI-X only (no legacy interrupts). - /// let msi_only = pci::IrqTypes::default() - /// .with(pci::IrqType::Msi) - /// .with(pci::IrqType::MsiX); - /// let vectors = dev.alloc_irq_vectors(4, 16, msi_only)?; - /// # Ok(()) - /// # } - /// ``` - pub fn alloc_irq_vectors( - &self, - min_vecs: u32, - max_vecs: u32, - irq_types: IrqTypes, - ) -> Result>> { - IrqVectorRegistration::register(self, min_vecs, max_vecs, irq_types) - } -} - impl Device { /// Enable memory resources for this device. pub fn enable_device_mem(&self) -> Result { diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs new file mode 100644 index 000000000000..77235c271876 --- /dev/null +++ b/rust/kernel/pci/irq.rs @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! PCI interrupt infrastructure. + +use super::Device; +use crate::{ + bindings, device, + device::Bound, + devres, + error::{to_result, Result}, + irq::{self, IrqRequest}, + str::CStr, + sync::aref::ARef, +}; +use core::ops::RangeInclusive; +use kernel::prelude::*; + +/// IRQ type flags for PCI interrupt allocation. +#[derive(Debug, Clone, Copy)] +pub enum IrqType { + /// INTx interrupts. + Intx, + /// Message Signaled Interrupts (MSI). + Msi, + /// Extended Message Signaled Interrupts (MSI-X). + MsiX, +} + +impl IrqType { + /// Convert to the corresponding kernel flags. + const fn as_raw(self) -> u32 { + match self { + IrqType::Intx => bindings::PCI_IRQ_INTX, + IrqType::Msi => bindings::PCI_IRQ_MSI, + IrqType::MsiX => bindings::PCI_IRQ_MSIX, + } + } +} + +/// Set of IRQ types that can be used for PCI interrupt allocation. +#[derive(Debug, Clone, Copy, Default)] +pub struct IrqTypes(u32); + +impl IrqTypes { + /// Create a set containing all IRQ types (MSI-X, MSI, and Legacy). + pub const fn all() -> Self { + Self(bindings::PCI_IRQ_ALL_TYPES) + } + + /// Build a set of IRQ types. + /// + /// # Examples + /// + /// ```ignore + /// // Create a set with only MSI and MSI-X (no legacy interrupts). + /// let msi_only = IrqTypes::default() + /// .with(IrqType::Msi) + /// .with(IrqType::MsiX); + /// ``` + pub const fn with(self, irq_type: IrqType) -> Self { + Self(self.0 | irq_type.as_raw()) + } + + /// Get the raw flags value. + const fn as_raw(self) -> u32 { + self.0 + } +} + +/// Represents an allocated IRQ vector for a specific PCI device. +/// +/// This type ties an IRQ vector to the device it was allocated for, +/// ensuring the vector is only used with the correct device. +#[derive(Clone, Copy)] +pub struct IrqVector<'a> { + dev: &'a Device, + index: u32, +} + +impl<'a> IrqVector<'a> { + /// Creates a new [`IrqVector`] for the given device and index. + /// + /// # Safety + /// + /// - `index` must be a valid IRQ vector index for `dev`. + /// - `dev` must point to a [`Device`] that has successfully allocated IRQ vectors. + unsafe fn new(dev: &'a Device, index: u32) -> Self { + Self { dev, index } + } + + /// Returns the raw vector index. + fn index(&self) -> u32 { + self.index + } +} + +impl<'a> TryInto> for IrqVector<'a> { + type Error = Error; + + fn try_into(self) -> Result> { + // SAFETY: `self.as_raw` returns a valid pointer to a `struct pci_dev`. + let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), self.index()) }; + if irq < 0 { + return Err(crate::error::Error::from_errno(irq)); + } + // SAFETY: `irq` is guaranteed to be a valid IRQ number for `&self`. + Ok(unsafe { IrqRequest::new(self.dev.as_ref(), irq as u32) }) + } +} + +/// Represents an IRQ vector allocation for a PCI device. +/// +/// This type ensures that IRQ vectors are properly allocated and freed by +/// tying the allocation to the lifetime of this registration object. +/// +/// # Invariants +/// +/// The [`Device`] has successfully allocated IRQ vectors. +struct IrqVectorRegistration { + dev: ARef, +} + +impl IrqVectorRegistration { + /// Allocate and register IRQ vectors for the given PCI device. + /// + /// Allocates IRQ vectors and registers them with devres for automatic cleanup. + /// Returns a range of valid IRQ vectors. + fn register<'a>( + dev: &'a Device, + min_vecs: u32, + max_vecs: u32, + irq_types: IrqTypes, + ) -> Result>> { + // SAFETY: + // - `dev.as_raw()` is guaranteed to be a valid pointer to a `struct pci_dev` + // by the type invariant of `Device`. + // - `pci_alloc_irq_vectors` internally validates all other parameters + // and returns error codes. + let ret = unsafe { + bindings::pci_alloc_irq_vectors(dev.as_raw(), min_vecs, max_vecs, irq_types.as_raw()) + }; + + to_result(ret)?; + let count = ret as u32; + + // SAFETY: + // - `pci_alloc_irq_vectors` returns the number of allocated vectors on success. + // - Vectors are 0-based, so valid indices are [0, count-1]. + // - `pci_alloc_irq_vectors` guarantees `count >= min_vecs > 0`, so both `0` and + // `count - 1` are valid IRQ vector indices for `dev`. + let range = unsafe { IrqVector::new(dev, 0)..=IrqVector::new(dev, count - 1) }; + + // INVARIANT: The IRQ vector allocation for `dev` above was successful. + let irq_vecs = Self { dev: dev.into() }; + devres::register(dev.as_ref(), irq_vecs, GFP_KERNEL)?; + + Ok(range) + } +} + +impl Drop for IrqVectorRegistration { + fn drop(&mut self) { + // SAFETY: + // - By the type invariant, `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. + // - `self.dev` has successfully allocated IRQ vectors. + unsafe { bindings::pci_free_irq_vectors(self.dev.as_raw()) }; + } +} + +impl Device { + /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. + pub fn request_irq<'a, T: crate::irq::Handler + 'static>( + &'a self, + vector: IrqVector<'a>, + flags: irq::Flags, + name: &'static CStr, + handler: impl PinInit + 'a, + ) -> Result, Error> + 'a> { + let request = vector.try_into()?; + + Ok(irq::Registration::::new(request, flags, name, handler)) + } + + /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. + pub fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'static>( + &'a self, + vector: IrqVector<'a>, + flags: irq::Flags, + name: &'static CStr, + handler: impl PinInit + 'a, + ) -> Result, Error> + 'a> { + let request = vector.try_into()?; + + Ok(irq::ThreadedRegistration::::new( + request, flags, name, handler, + )) + } + + /// Allocate IRQ vectors for this PCI device with automatic cleanup. + /// + /// Allocates between `min_vecs` and `max_vecs` interrupt vectors for the device. + /// The allocation will use MSI-X, MSI, or legacy interrupts based on the `irq_types` + /// parameter and hardware capabilities. When multiple types are specified, the kernel + /// will try them in order of preference: MSI-X first, then MSI, then legacy interrupts. + /// + /// The allocated vectors are automatically freed when the device is unbound, using the + /// devres (device resource management) system. + /// + /// # Arguments + /// + /// * `min_vecs` - Minimum number of vectors required. + /// * `max_vecs` - Maximum number of vectors to allocate. + /// * `irq_types` - Types of interrupts that can be used. + /// + /// # Returns + /// + /// Returns a range of IRQ vectors that were successfully allocated, or an error if the + /// allocation fails or cannot meet the minimum requirement. + /// + /// # Examples + /// + /// ``` + /// # use kernel::{ device::Bound, pci}; + /// # fn no_run(dev: &pci::Device) -> Result { + /// // Allocate using any available interrupt type in the order mentioned above. + /// let vectors = dev.alloc_irq_vectors(1, 32, pci::IrqTypes::all())?; + /// + /// // Allocate MSI or MSI-X only (no legacy interrupts). + /// let msi_only = pci::IrqTypes::default() + /// .with(pci::IrqType::Msi) + /// .with(pci::IrqType::MsiX); + /// let vectors = dev.alloc_irq_vectors(4, 16, msi_only)?; + /// # Ok(()) + /// # } + /// ``` + pub fn alloc_irq_vectors( + &self, + min_vecs: u32, + max_vecs: u32, + irq_types: IrqTypes, + ) -> Result>> { + IrqVectorRegistration::register(self, min_vecs, max_vecs, irq_types) + } +} -- cgit v1.2.3 From fc2b38de4c01710ecb9ebb9ecdce9a7bf433e9a8 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Thu, 16 Oct 2025 23:05:39 +0200 Subject: add `[pin_]init_scope` to execute code before creating an initializer In more complex cases, initializers need to run arbitrary code before assigning initializers to fields. While this is possible using the underscore codeblock feature (`_: {}`), values returned by such functions cannot be used from later field initializers. The two new functions `[pin_]init_scope` allow users to first run some fallible code and then return an initializer which the function turns into a single initializer. This permits using the same value multiple times by different fields. Reviewed-by: Gary Guo Reviewed-by: Danilo Krummrich Signed-off-by: Benno Lossin Reviewed-by: Alice Ryhl [ Fix typo in commit message: s/functinos/functions/. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/pin-init/src/lib.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index dd553212836e..8dc9dd5ac6fd 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -1392,6 +1392,93 @@ where unsafe { pin_init_from_closure(init) } } +/// Construct an initializer in a closure and run it. +/// +/// Returns an initializer that first runs the closure and then the initializer returned by it. +/// +/// See also [`init_scope`]. +/// +/// # Examples +/// +/// ``` +/// # use pin_init::*; +/// # #[pin_data] +/// # struct Foo { a: u64, b: isize } +/// # struct Bar { a: u32, b: isize } +/// # fn lookup_bar() -> Result { todo!() } +/// # struct Error; +/// fn init_foo() -> impl PinInit { +/// pin_init_scope(|| { +/// let bar = lookup_bar()?; +/// Ok(try_pin_init!(Foo { a: bar.a.into(), b: bar.b }? Error)) +/// }) +/// } +/// ``` +/// +/// This initializer will first execute `lookup_bar()`, match on it, if it returned an error, the +/// initializer itself will fail with that error. If it returned `Ok`, then it will run the +/// initializer returned by the [`try_pin_init!`] invocation. +pub fn pin_init_scope(make_init: F) -> impl PinInit +where + F: FnOnce() -> Result, + I: PinInit, +{ + // SAFETY: + // - If `make_init` returns `Err`, `Err` is returned and `slot` is completely uninitialized, + // - If `make_init` returns `Ok`, safety requirement are fulfilled by `init.__pinned_init`. + // - The safety requirements of `init.__pinned_init` are fulfilled, since it's being called + // from an initializer. + unsafe { + pin_init_from_closure(move |slot: *mut T| -> Result<(), E> { + let init = make_init()?; + init.__pinned_init(slot) + }) + } +} + +/// Construct an initializer in a closure and run it. +/// +/// Returns an initializer that first runs the closure and then the initializer returned by it. +/// +/// See also [`pin_init_scope`]. +/// +/// # Examples +/// +/// ``` +/// # use pin_init::*; +/// # struct Foo { a: u64, b: isize } +/// # struct Bar { a: u32, b: isize } +/// # fn lookup_bar() -> Result { todo!() } +/// # struct Error; +/// fn init_foo() -> impl Init { +/// init_scope(|| { +/// let bar = lookup_bar()?; +/// Ok(try_init!(Foo { a: bar.a.into(), b: bar.b }? Error)) +/// }) +/// } +/// ``` +/// +/// This initializer will first execute `lookup_bar()`, match on it, if it returned an error, the +/// initializer itself will fail with that error. If it returned `Ok`, then it will run the +/// initializer returned by the [`try_init!`] invocation. +pub fn init_scope(make_init: F) -> impl Init +where + F: FnOnce() -> Result, + I: Init, +{ + // SAFETY: + // - If `make_init` returns `Err`, `Err` is returned and `slot` is completely uninitialized, + // - If `make_init` returns `Ok`, safety requirement are fulfilled by `init.__init`. + // - The safety requirements of `init.__init` are fulfilled, since it's being called from an + // initializer. + unsafe { + init_from_closure(move |slot: *mut T| -> Result<(), E> { + let init = make_init()?; + init.__init(slot) + }) + } +} + // SAFETY: the `__init` function always returns `Ok(())` and initializes every field of `slot`. unsafe impl Init for T { unsafe fn __init(self, slot: *mut T) -> Result<(), Infallible> { -- cgit v1.2.3 From 0242623384c767b1156b61b67894b4ecf6682b8b Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 16 Oct 2025 14:55:28 +0200 Subject: rust: driver: let probe() return impl PinInit The driver model defines the lifetime of the private data stored in (and owned by) a bus device to be valid from when the driver is bound to a device (i.e. from successful probe()) until the driver is unbound from the device. This is already taken care of by the Rust implementation of the driver model. However, we still ask drivers to return a Result>> from probe(). Unlike in C, where we do not have the concept of initializers, but rather deal with uninitialized memory, drivers can just return an impl PinInit instead. This contributes to more clarity to the fact that a driver returns it's device private data in probe() and the Rust driver model owns the data, manages the lifetime and - considering the lifetime - provides (safe) accessors for the driver. Hence, let probe() functions return an impl PinInit instead of Result>>. Reviewed-by: Alice Ryhl Acked-by: Viresh Kumar Reviewed-by: Alexandre Courbot Acked-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- drivers/cpufreq/rcpufreq_dt.rs | 4 +-- drivers/gpu/drm/nova/driver.rs | 4 +-- drivers/gpu/drm/tyr/driver.rs | 4 +-- drivers/gpu/nova-core/driver.rs | 34 +++++++++------------- rust/kernel/auxiliary.rs | 6 ++-- rust/kernel/cpufreq.rs | 4 +-- rust/kernel/device.rs | 9 ++++-- rust/kernel/driver.rs | 4 +-- rust/kernel/io/mem.rs | 8 +++--- rust/kernel/pci.rs | 8 +++--- rust/kernel/platform.rs | 12 ++++---- rust/kernel/usb.rs | 8 +++--- samples/rust/rust_debugfs.rs | 21 +++++++------- samples/rust/rust_dma.rs | 37 +++++++++++------------- samples/rust/rust_driver_auxiliary.rs | 25 ++++++----------- samples/rust/rust_driver_pci.rs | 53 +++++++++++++++++------------------ samples/rust/rust_driver_platform.rs | 6 ++-- samples/rust/rust_driver_usb.rs | 5 ++-- 18 files changed, 119 insertions(+), 133 deletions(-) diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs index 53923b8ef7a1..31e07f0279db 100644 --- a/drivers/cpufreq/rcpufreq_dt.rs +++ b/drivers/cpufreq/rcpufreq_dt.rs @@ -207,9 +207,9 @@ impl platform::Driver for CPUFreqDTDriver { fn probe( pdev: &platform::Device, _id_info: Option<&Self::IdInfo>, - ) -> Result>> { + ) -> impl PinInit { cpufreq::Registration::::new_foreign_owned(pdev.as_ref())?; - Ok(KBox::new(Self {}, GFP_KERNEL)?.into()) + Ok(Self {}) } } diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs index 91b7380f83ab..2246d8e104e0 100644 --- a/drivers/gpu/drm/nova/driver.rs +++ b/drivers/gpu/drm/nova/driver.rs @@ -45,13 +45,13 @@ impl auxiliary::Driver for NovaDriver { type IdInfo = (); const ID_TABLE: auxiliary::IdTable = &AUX_TABLE; - fn probe(adev: &auxiliary::Device, _info: &Self::IdInfo) -> Result>> { + fn probe(adev: &auxiliary::Device, _info: &Self::IdInfo) -> impl PinInit { let data = try_pin_init!(NovaData { adev: adev.into() }); let drm = drm::Device::::new(adev.as_ref(), data)?; drm::Registration::new_foreign_owned(&drm, adev.as_ref(), 0)?; - Ok(KBox::new(Self { drm }, GFP_KERNEL)?.into()) + Ok(Self { drm }) } } diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index d5625dd1e41c..0389c558c036 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -103,7 +103,7 @@ impl platform::Driver for TyrDriver { fn probe( pdev: &platform::Device, _info: Option<&Self::IdInfo>, - ) -> Result>> { + ) -> impl PinInit { let core_clk = Clk::get(pdev.as_ref(), Some(c_str!("core")))?; let stacks_clk = OptionalClk::get(pdev.as_ref(), Some(c_str!("stacks")))?; let coregroup_clk = OptionalClk::get(pdev.as_ref(), Some(c_str!("coregroup")))?; @@ -143,7 +143,7 @@ impl platform::Driver for TyrDriver { let tdev: ARef = drm::Device::new(pdev.as_ref(), data)?; drm::driver::Registration::new_foreign_owned(&tdev, pdev.as_ref(), 0)?; - let driver = KBox::pin_init(try_pin_init!(TyrDriver { device: tdev }), GFP_KERNEL)?; + let driver = TyrDriver { device: tdev }; // We need this to be dev_info!() because dev_dbg!() does not work at // all in Rust for now, and we need to see whether probe succeeded. diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs index edc72052e27a..a83b86199182 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -51,36 +51,28 @@ impl pci::Driver for NovaCore { type IdInfo = (); const ID_TABLE: pci::IdTable = &PCI_TABLE; - fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> Result>> { - dev_dbg!(pdev.as_ref(), "Probe Nova Core GPU driver.\n"); + fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> impl PinInit { + pin_init::pin_init_scope(move || { + dev_dbg!(pdev.as_ref(), "Probe Nova Core GPU driver.\n"); - pdev.enable_device_mem()?; - pdev.set_master(); + pdev.enable_device_mem()?; + pdev.set_master(); - let devres_bar = Arc::pin_init( - pdev.iomap_region_sized::(0, c_str!("nova-core/bar0")), - GFP_KERNEL, - )?; + let bar = Arc::pin_init( + pdev.iomap_region_sized::(0, c_str!("nova-core/bar0")), + GFP_KERNEL, + )?; - // Used to provided a `&Bar0` to `Gpu::new` without tying it to the lifetime of - // `devres_bar`. - let bar_clone = Arc::clone(&devres_bar); - let bar = bar_clone.access(pdev.as_ref())?; - - let this = KBox::pin_init( - try_pin_init!(Self { - gpu <- Gpu::new(pdev, devres_bar, bar), + Ok(try_pin_init!(Self { + gpu <- Gpu::new(pdev, bar.clone(), bar.access(pdev.as_ref())?), _reg: auxiliary::Registration::new( pdev.as_ref(), c_str!("nova-drm"), 0, // TODO[XARR]: Once it lands, use XArray; for now we don't use the ID. crate::MODULE_NAME )?, - }), - GFP_KERNEL, - )?; - - Ok(this) + })) + }) } fn unbind(pdev: &pci::Device, this: Pin<&Self>) { diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index e11848bbf206..4163129b4103 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -68,9 +68,9 @@ impl Adapter { let info = T::ID_TABLE.info(id.index()); from_result(|| { - let data = T::probe(adev, info)?; + let data = T::probe(adev, info); - adev.as_ref().set_drvdata(data); + adev.as_ref().set_drvdata(data)?; Ok(0) }) } @@ -184,7 +184,7 @@ pub trait Driver { /// Auxiliary driver probe. /// /// Called when an auxiliary device is matches a corresponding driver. - fn probe(dev: &Device, id_info: &Self::IdInfo) -> Result>>; + fn probe(dev: &Device, id_info: &Self::IdInfo) -> impl PinInit; } /// The auxiliary device representation. diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index 21b5b9b8acc1..462a9ef8669e 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -894,9 +894,9 @@ pub trait Driver { /// fn probe( /// pdev: &platform::Device, /// _id_info: Option<&Self::IdInfo>, -/// ) -> Result>> { +/// ) -> impl PinInit { /// cpufreq::Registration::::new_foreign_owned(pdev.as_ref())?; -/// Ok(KBox::new(Self {}, GFP_KERNEL)?.into()) +/// Ok(Self {}) /// } /// } /// ``` diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 1321e6f0b53c..23a95324cb0f 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -6,6 +6,7 @@ use crate::{ bindings, fmt, + prelude::*, sync::aref::ARef, types::{ForeignOwnable, Opaque}, }; @@ -198,9 +199,13 @@ impl Device { impl Device { /// Store a pointer to the bound driver's private data. - pub fn set_drvdata(&self, data: impl ForeignOwnable) { + pub fn set_drvdata(&self, data: impl PinInit) -> Result { + let data = KBox::pin_init(data, GFP_KERNEL)?; + // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. - unsafe { bindings::dev_set_drvdata(self.as_raw(), data.into_foreign().cast()) } + unsafe { bindings::dev_set_drvdata(self.as_raw(), data.into_foreign().cast()) }; + + Ok(()) } /// Take ownership of the private data stored in this [`Device`]. diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs index 279e3af20682..9beae2e3d57e 100644 --- a/rust/kernel/driver.rs +++ b/rust/kernel/driver.rs @@ -24,7 +24,7 @@ //! const ACPI_ID_TABLE: Option> = None; //! //! /// Driver probe. -//! fn probe(dev: &Device, id_info: &Self::IdInfo) -> Result>>; +//! fn probe(dev: &Device, id_info: &Self::IdInfo) -> impl PinInit; //! //! /// Driver unbind (optional). //! fn unbind(dev: &Device, this: Pin<&Self>) { @@ -35,7 +35,7 @@ //! //! For specific examples see [`auxiliary::Driver`], [`pci::Driver`] and [`platform::Driver`]. //! -//! The `probe()` callback should return a `Result>>`, i.e. the driver's private +//! The `probe()` callback should return a `impl PinInit`, i.e. the driver's private //! data. The bus abstraction should store the pointer in the corresponding bus device. The generic //! [`Device`] infrastructure provides common helpers for this purpose on its //! [`Device`] implementation. diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index 6f99510bfc3a..59096d579644 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -53,7 +53,7 @@ impl<'a> IoRequest<'a> { /// fn probe( /// pdev: &platform::Device, /// info: Option<&Self::IdInfo>, - /// ) -> Result>> { + /// ) -> impl PinInit { /// let offset = 0; // Some offset. /// /// // If the size is known at compile time, use [`Self::iomap_sized`]. @@ -70,7 +70,7 @@ impl<'a> IoRequest<'a> { /// /// io.write32_relaxed(data, offset); /// - /// # Ok(KBox::new(SampleDriver, GFP_KERNEL)?.into()) + /// # Ok(SampleDriver) /// } /// } /// ``` @@ -111,7 +111,7 @@ impl<'a> IoRequest<'a> { /// fn probe( /// pdev: &platform::Device, /// info: Option<&Self::IdInfo>, - /// ) -> Result>> { + /// ) -> impl PinInit { /// let offset = 0; // Some offset. /// /// // Unlike [`Self::iomap_sized`], here the size of the memory region @@ -128,7 +128,7 @@ impl<'a> IoRequest<'a> { /// /// io.try_write32_relaxed(data, offset)?; /// - /// # Ok(KBox::new(SampleDriver, GFP_KERNEL)?.into()) + /// # Ok(SampleDriver) /// } /// } /// ``` diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index ce612c9b7b56..0d5b4b394bbf 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -77,9 +77,9 @@ impl Adapter { let info = T::ID_TABLE.info(id.index()); from_result(|| { - let data = T::probe(pdev, info)?; + let data = T::probe(pdev, info); - pdev.as_ref().set_drvdata(data); + pdev.as_ref().set_drvdata(data)?; Ok(0) }) } @@ -248,7 +248,7 @@ macro_rules! pci_device_table { /// fn probe( /// _pdev: &pci::Device, /// _id_info: &Self::IdInfo, -/// ) -> Result>> { +/// ) -> impl PinInit { /// Err(ENODEV) /// } /// } @@ -271,7 +271,7 @@ pub trait Driver: Send { /// /// Called when a new pci device is added or discovered. Implementers should /// attempt to initialize the device here. - fn probe(dev: &Device, id_info: &Self::IdInfo) -> Result>>; + fn probe(dev: &Device, id_info: &Self::IdInfo) -> impl PinInit; /// PCI driver unbind. /// diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 7205fe3416d3..043721fdb6d8 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -74,9 +74,9 @@ impl Adapter { let info = ::id_info(pdev.as_ref()); from_result(|| { - let data = T::probe(pdev, info)?; + let data = T::probe(pdev, info); - pdev.as_ref().set_drvdata(data); + pdev.as_ref().set_drvdata(data)?; Ok(0) }) } @@ -166,7 +166,7 @@ macro_rules! module_platform_driver { /// fn probe( /// _pdev: &platform::Device, /// _id_info: Option<&Self::IdInfo>, -/// ) -> Result>> { +/// ) -> impl PinInit { /// Err(ENODEV) /// } /// } @@ -190,8 +190,10 @@ pub trait Driver: Send { /// /// Called when a new platform device is added or discovered. /// Implementers should attempt to initialize the device here. - fn probe(dev: &Device, id_info: Option<&Self::IdInfo>) - -> Result>>; + fn probe( + dev: &Device, + id_info: Option<&Self::IdInfo>, + ) -> impl PinInit; /// Platform driver unbind. /// diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 14ddb711bab3..fa8367c0dbaa 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -67,10 +67,10 @@ impl Adapter { let id = unsafe { &*id.cast::() }; let info = T::ID_TABLE.info(id.index()); - let data = T::probe(intf, id, info)?; + let data = T::probe(intf, id, info); let dev: &device::Device = intf.as_ref(); - dev.set_drvdata(data); + dev.set_drvdata(data)?; Ok(0) }) } @@ -270,7 +270,7 @@ macro_rules! usb_device_table { /// _interface: &usb::Interface, /// _id: &usb::DeviceId, /// _info: &Self::IdInfo, -/// ) -> Result>> { +/// ) -> impl PinInit { /// Err(ENODEV) /// } /// @@ -292,7 +292,7 @@ pub trait Driver { interface: &Interface, id: &DeviceId, id_info: &Self::IdInfo, - ) -> Result>>; + ) -> impl PinInit; /// USB driver disconnect. /// diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs index 82b61a15a34b..d3f50f344856 100644 --- a/samples/rust/rust_debugfs.rs +++ b/samples/rust/rust_debugfs.rs @@ -106,16 +106,17 @@ impl platform::Driver for RustDebugFs { fn probe( pdev: &platform::Device, _info: Option<&Self::IdInfo>, - ) -> Result>> { - let result = KBox::try_pin_init(RustDebugFs::new(pdev), GFP_KERNEL)?; - // We can still mutate fields through the files which are atomic or mutexed: - result.counter.store(91, Ordering::Relaxed); - { - let mut guard = result.inner.lock(); - guard.x = guard.y; - guard.y = 42; - } - Ok(result) + ) -> impl PinInit { + RustDebugFs::new(pdev).pin_chain(|this| { + this.counter.store(91, Ordering::Relaxed); + { + let mut guard = this.inner.lock(); + guard.x = guard.y; + guard.y = 42; + } + + Ok(()) + }) } } diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index 4d324f06cc2a..f53bce2a73e3 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -55,36 +55,33 @@ impl pci::Driver for DmaSampleDriver { type IdInfo = (); const ID_TABLE: pci::IdTable = &PCI_TABLE; - fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> Result>> { - dev_info!(pdev.as_ref(), "Probe DMA test driver.\n"); + fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> impl PinInit { + pin_init::pin_init_scope(move || { + dev_info!(pdev.as_ref(), "Probe DMA test driver.\n"); - let mask = DmaMask::new::<64>(); + let mask = DmaMask::new::<64>(); - // SAFETY: There are no concurrent calls to DMA allocation and mapping primitives. - unsafe { pdev.dma_set_mask_and_coherent(mask)? }; + // SAFETY: There are no concurrent calls to DMA allocation and mapping primitives. + unsafe { pdev.dma_set_mask_and_coherent(mask)? }; - let ca: CoherentAllocation = - CoherentAllocation::alloc_coherent(pdev.as_ref(), TEST_VALUES.len(), GFP_KERNEL)?; + let ca: CoherentAllocation = + CoherentAllocation::alloc_coherent(pdev.as_ref(), TEST_VALUES.len(), GFP_KERNEL)?; - for (i, value) in TEST_VALUES.into_iter().enumerate() { - kernel::dma_write!(ca[i] = MyStruct::new(value.0, value.1))?; - } + for (i, value) in TEST_VALUES.into_iter().enumerate() { + kernel::dma_write!(ca[i] = MyStruct::new(value.0, value.1))?; + } - let size = 4 * page::PAGE_SIZE; - let pages = VVec::with_capacity(size, GFP_KERNEL)?; + let size = 4 * page::PAGE_SIZE; + let pages = VVec::with_capacity(size, GFP_KERNEL)?; - let sgt = SGTable::new(pdev.as_ref(), pages, DataDirection::ToDevice, GFP_KERNEL); + let sgt = SGTable::new(pdev.as_ref(), pages, DataDirection::ToDevice, GFP_KERNEL); - let drvdata = KBox::pin_init( - try_pin_init!(Self { + Ok(try_pin_init!(Self { pdev: pdev.into(), ca, sgt <- sgt, - }), - GFP_KERNEL, - )?; - - Ok(drvdata) + })) + }) } } diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 55ece336ee45..0e221abe4936 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -27,7 +27,7 @@ impl auxiliary::Driver for AuxiliaryDriver { const ID_TABLE: auxiliary::IdTable = &AUX_TABLE; - fn probe(adev: &auxiliary::Device, _info: &Self::IdInfo) -> Result>> { + fn probe(adev: &auxiliary::Device, _info: &Self::IdInfo) -> impl PinInit { dev_info!( adev.as_ref(), "Probing auxiliary driver for auxiliary device with id={}\n", @@ -36,9 +36,7 @@ impl auxiliary::Driver for AuxiliaryDriver { ParentDriver::connect(adev)?; - let this = KBox::new(Self, GFP_KERNEL)?; - - Ok(this.into()) + Ok(Self) } } @@ -58,18 +56,13 @@ impl pci::Driver for ParentDriver { const ID_TABLE: pci::IdTable = &PCI_TABLE; - fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> Result>> { - let this = KBox::new( - Self { - _reg: [ - auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 0, MODULE_NAME)?, - auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 1, MODULE_NAME)?, - ], - }, - GFP_KERNEL, - )?; - - Ok(this.into()) + fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> impl PinInit { + Ok(Self { + _reg: [ + auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 0, MODULE_NAME)?, + auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 1, MODULE_NAME)?, + ], + }) } } diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 55a683c39ed9..5823787bea8e 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -65,35 +65,34 @@ impl pci::Driver for SampleDriver { const ID_TABLE: pci::IdTable = &PCI_TABLE; - fn probe(pdev: &pci::Device, info: &Self::IdInfo) -> Result>> { - let vendor = pdev.vendor_id(); - dev_dbg!( - pdev.as_ref(), - "Probe Rust PCI driver sample (PCI ID: {}, 0x{:x}).\n", - vendor, - pdev.device_id() - ); - - pdev.enable_device_mem()?; - pdev.set_master(); - - let drvdata = KBox::pin_init( - try_pin_init!(Self { + fn probe(pdev: &pci::Device, info: &Self::IdInfo) -> impl PinInit { + pin_init::pin_init_scope(move || { + let vendor = pdev.vendor_id(); + dev_dbg!( + pdev.as_ref(), + "Probe Rust PCI driver sample (PCI ID: {}, 0x{:x}).\n", + vendor, + pdev.device_id() + ); + + pdev.enable_device_mem()?; + pdev.set_master(); + + Ok(try_pin_init!(Self { bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c_str!("rust_driver_pci")), - pdev: pdev.into(), index: *info, - }), - GFP_KERNEL, - )?; - - let bar = drvdata.bar.access(pdev.as_ref())?; - dev_info!( - pdev.as_ref(), - "pci-testdev data-match count: {}\n", - Self::testdev(info, bar)? - ); - - Ok(drvdata) + _: { + let bar = bar.access(pdev.as_ref())?; + + dev_info!( + pdev.as_ref(), + "pci-testdev data-match count: {}\n", + Self::testdev(info, bar)? + ); + }, + pdev: pdev.into(), + })) + }) } fn unbind(pdev: &pci::Device, this: Pin<&Self>) { diff --git a/samples/rust/rust_driver_platform.rs b/samples/rust/rust_driver_platform.rs index 6473baf4f120..d63adb422ba6 100644 --- a/samples/rust/rust_driver_platform.rs +++ b/samples/rust/rust_driver_platform.rs @@ -103,7 +103,7 @@ impl platform::Driver for SampleDriver { fn probe( pdev: &platform::Device, info: Option<&Self::IdInfo>, - ) -> Result>> { + ) -> impl PinInit { let dev = pdev.as_ref(); dev_dbg!(dev, "Probe Rust Platform driver sample.\n"); @@ -116,9 +116,7 @@ impl platform::Driver for SampleDriver { Self::properties_parse(dev)?; } - let drvdata = KBox::new(Self { pdev: pdev.into() }, GFP_KERNEL)?; - - Ok(drvdata.into()) + Ok(Self { pdev: pdev.into() }) } } diff --git a/samples/rust/rust_driver_usb.rs b/samples/rust/rust_driver_usb.rs index 5c396f421de7..4eaad14867b2 100644 --- a/samples/rust/rust_driver_usb.rs +++ b/samples/rust/rust_driver_usb.rs @@ -24,12 +24,11 @@ impl usb::Driver for SampleDriver { intf: &usb::Interface, _id: &usb::DeviceId, _info: &Self::IdInfo, - ) -> Result>> { + ) -> impl PinInit { let dev: &device::Device = intf.as_ref(); dev_info!(dev, "Rust USB driver sample probed\n"); - let drvdata = KBox::new(Self { _intf: intf.into() }, GFP_KERNEL)?; - Ok(drvdata.into()) + Ok(Self { _intf: intf.into() }) } fn disconnect(intf: &usb::Interface, _data: Pin<&Self>) { -- cgit v1.2.3 From fe6193a3187a1a7482ccfbc904bc527e7730a36c Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Thu, 25 Sep 2025 11:11:29 +0530 Subject: firmware_loader: Only call cancel when upload is active The cancel_store() function currently calls the firmware upload cancel operation even when no upload is in progress (i.e., when progress is FW_UPLOAD_PROG_IDLE). Update cancel_store() to only invoke the cancel operation when an upload is active. If the upload is idle, return -ENODEV without calling cancel. This change improves safety and correctness by ensuring driver operations are only called in valid states. Signed-off-by: Kaushlendra Kumar Link: https://patch.msgid.link/20250925054129.2199157-1-kaushlendra.kumar@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/sysfs_upload.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c index 829270067d16..c3797b93c5f5 100644 --- a/drivers/base/firmware_loader/sysfs_upload.c +++ b/drivers/base/firmware_loader/sysfs_upload.c @@ -100,8 +100,10 @@ static ssize_t cancel_store(struct device *dev, struct device_attribute *attr, return -EINVAL; mutex_lock(&fwlp->lock); - if (fwlp->progress == FW_UPLOAD_PROG_IDLE) - ret = -ENODEV; + if (fwlp->progress == FW_UPLOAD_PROG_IDLE) { + mutex_unlock(&fwlp->lock); + return -ENODEV; + } fwlp->ops->cancel(fwlp->fw_upload); mutex_unlock(&fwlp->lock); -- cgit v1.2.3 From b811e8a01d8db2ea605e6158063aac0bf00c77b8 Mon Sep 17 00:00:00 2001 From: Kaushlendra Kumar Date: Thu, 25 Sep 2025 12:08:12 +0530 Subject: firmware_loader: Replace simple_strtol() with kstrtoint() Replace deprecated simple_strtol() calls with kstrtoint() in timeout_store() and firmware_loading_store() functions to improve input validation and error handling. The simple_strtol() function does not provide proper error checking for invalid input, while kstrtoint() returns an error for malformed strings. This change adds proper validation for user input from sysfs attributes, returning -EINVAL for invalid numeric strings instead of silently accepting potentially malformed input. The behavior for valid numeric input remains unchanged. The simple_strtol() function is deprecated in favor of kstrtoint() family functions which provide better error handling and are recommended for new code and replacements. Signed-off-by: Kaushlendra Kumar Link: https://patch.msgid.link/20250925063812.2269501-1-kaushlendra.kumar@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_loader/sysfs.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/base/firmware_loader/sysfs.c b/drivers/base/firmware_loader/sysfs.c index add0b9b75edd..92e91050f96a 100644 --- a/drivers/base/firmware_loader/sysfs.c +++ b/drivers/base/firmware_loader/sysfs.c @@ -47,7 +47,10 @@ static ssize_t timeout_show(const struct class *class, const struct class_attrib static ssize_t timeout_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count) { - int tmp_loading_timeout = simple_strtol(buf, NULL, 10); + int tmp_loading_timeout; + + if (kstrtoint(buf, 10, &tmp_loading_timeout)) + return -EINVAL; if (tmp_loading_timeout < 0) tmp_loading_timeout = 0; @@ -157,7 +160,10 @@ static ssize_t firmware_loading_store(struct device *dev, struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); struct fw_priv *fw_priv; ssize_t written = count; - int loading = simple_strtol(buf, NULL, 10); + int loading; + + if (kstrtoint(buf, 10, &loading)) + return -EINVAL; mutex_lock(&fw_lock); fw_priv = fw_sysfs->fw_priv; -- cgit v1.2.3 From cebd22dd3a0ac76e0e1f2f369bba710bc6b1dc66 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Thu, 18 Sep 2025 10:54:02 +0800 Subject: platform: Use IOMEM_ERR_PTR for ioremap error returns Replace ERR_PTR() with IOMEM_ERR_PTR() in stubbed ioremap functions to maintain type consistency. The functions return void __iomem * pointers and IOMEM_ERR_PTR() provides proper type casting to avoid sparse warnings. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202509060307.JubgnLhc-lkp@intel.com/ Signed-off-by: Pei Xiao Link: https://patch.msgid.link/320f2cc9ada5cb66845daa6bf259000b4cffd8b3.1758163939.git.xiaopei01@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- include/linux/platform_device.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 074754c23d33..1d424fed1435 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -80,7 +80,7 @@ static inline void __iomem * devm_platform_get_and_ioremap_resource(struct platform_device *pdev, unsigned int index, struct resource **res) { - return ERR_PTR(-EINVAL); + return IOMEM_ERR_PTR(-EINVAL); } @@ -88,14 +88,14 @@ static inline void __iomem * devm_platform_ioremap_resource(struct platform_device *pdev, unsigned int index) { - return ERR_PTR(-EINVAL); + return IOMEM_ERR_PTR(-EINVAL); } static inline void __iomem * devm_platform_ioremap_resource_byname(struct platform_device *pdev, const char *name) { - return ERR_PTR(-EINVAL); + return IOMEM_ERR_PTR(-EINVAL); } #endif -- cgit v1.2.3 From 6d0ef68955d30be1e218caf160ec32eec23ebc6e Mon Sep 17 00:00:00 2001 From: Yunhui Cui Date: Tue, 23 Sep 2025 09:54:09 +0800 Subject: arch_topology: move parse_acpi_topology() to common code Currently, RISC-V lacks arch-specific registers for CPU topology properties and must get them from ACPI. Thus, parse_acpi_topology() is moved from arm64/ to drivers/ for RISC-V reuse. Signed-off-by: Yunhui Cui Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/20250923015409.15983-2-cuiyunhui@bytedance.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/include/asm/topology.h | 3 ++ arch/arm64/kernel/topology.c | 101 -------------------------------------- drivers/base/arch_topology.c | 96 +++++++++++++++++++++++++++++++++++- include/linux/arch_topology.h | 5 ++ 4 files changed, 103 insertions(+), 102 deletions(-) diff --git a/arch/arm64/include/asm/topology.h b/arch/arm64/include/asm/topology.h index 341174bf9106..b9eaf4ad7085 100644 --- a/arch/arm64/include/asm/topology.h +++ b/arch/arm64/include/asm/topology.h @@ -36,6 +36,9 @@ void update_freq_counters_refs(void); #define arch_scale_hw_pressure topology_get_hw_pressure #define arch_update_hw_pressure topology_update_hw_pressure +#undef arch_cpu_is_threaded +#define arch_cpu_is_threaded() (read_cpuid_mpidr() & MPIDR_MT_BITMASK) + #include #endif /* _ASM_ARM_TOPOLOGY_H */ diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index 5d07ee85bdae..5d24dc53799b 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -25,107 +25,6 @@ #include #include -#ifdef CONFIG_ACPI -static bool __init acpi_cpu_is_threaded(int cpu) -{ - int is_threaded = acpi_pptt_cpu_is_thread(cpu); - - /* - * if the PPTT doesn't have thread information, assume a homogeneous - * machine and return the current CPU's thread state. - */ - if (is_threaded < 0) - is_threaded = read_cpuid_mpidr() & MPIDR_MT_BITMASK; - - return !!is_threaded; -} - -struct cpu_smt_info { - unsigned int thread_num; - int core_id; -}; - -/* - * Propagate the topology information of the processor_topology_node tree to the - * cpu_topology array. - */ -int __init parse_acpi_topology(void) -{ - unsigned int max_smt_thread_num = 1; - struct cpu_smt_info *entry; - struct xarray hetero_cpu; - unsigned long hetero_id; - int cpu, topology_id; - - if (acpi_disabled) - return 0; - - xa_init(&hetero_cpu); - - for_each_possible_cpu(cpu) { - topology_id = find_acpi_cpu_topology(cpu, 0); - if (topology_id < 0) - return topology_id; - - if (acpi_cpu_is_threaded(cpu)) { - cpu_topology[cpu].thread_id = topology_id; - topology_id = find_acpi_cpu_topology(cpu, 1); - cpu_topology[cpu].core_id = topology_id; - - /* - * In the PPTT, CPUs below a node with the 'identical - * implementation' flag have the same number of threads. - * Count the number of threads for only one CPU (i.e. - * one core_id) among those with the same hetero_id. - * See the comment of find_acpi_cpu_topology_hetero_id() - * for more details. - * - * One entry is created for each node having: - * - the 'identical implementation' flag - * - its parent not having the flag - */ - hetero_id = find_acpi_cpu_topology_hetero_id(cpu); - entry = xa_load(&hetero_cpu, hetero_id); - if (!entry) { - entry = kzalloc(sizeof(*entry), GFP_KERNEL); - WARN_ON_ONCE(!entry); - - if (entry) { - entry->core_id = topology_id; - entry->thread_num = 1; - xa_store(&hetero_cpu, hetero_id, - entry, GFP_KERNEL); - } - } else if (entry->core_id == topology_id) { - entry->thread_num++; - } - } else { - cpu_topology[cpu].thread_id = -1; - cpu_topology[cpu].core_id = topology_id; - } - topology_id = find_acpi_cpu_topology_cluster(cpu); - cpu_topology[cpu].cluster_id = topology_id; - topology_id = find_acpi_cpu_topology_package(cpu); - cpu_topology[cpu].package_id = topology_id; - } - - /* - * This is a short loop since the number of XArray elements is the - * number of heterogeneous CPU clusters. On a homogeneous system - * there's only one entry in the XArray. - */ - xa_for_each(&hetero_cpu, hetero_id, entry) { - max_smt_thread_num = max(max_smt_thread_num, entry->thread_num); - xa_erase(&hetero_cpu, hetero_id); - kfree(entry); - } - - cpu_smt_set_num_threads(max_smt_thread_num, max_smt_thread_num); - xa_destroy(&hetero_cpu); - return 0; -} -#endif - #ifdef CONFIG_ARM64_AMU_EXTN #define read_corecnt() read_sysreg_s(SYS_AMEVCNTR0_CORE_EL0) #define read_constcnt() read_sysreg_s(SYS_AMEVCNTR0_CONST_EL0) diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c index 1037169abb45..1ccb1eda4ce8 100644 --- a/drivers/base/arch_topology.c +++ b/drivers/base/arch_topology.c @@ -823,12 +823,106 @@ void remove_cpu_topology(unsigned int cpu) clear_cpu_topology(cpu); } +#if defined(CONFIG_ARM64) || defined(CONFIG_RISCV) +struct cpu_smt_info { + unsigned int thread_num; + int core_id; +}; + +static bool __init acpi_cpu_is_threaded(int cpu) +{ + int is_threaded = acpi_pptt_cpu_is_thread(cpu); + + /* + * if the PPTT doesn't have thread information, check for architecture + * specific fallback if available + */ + if (is_threaded < 0) + is_threaded = arch_cpu_is_threaded(); + + return !!is_threaded; +} + +/* + * Propagate the topology information of the processor_topology_node tree to the + * cpu_topology array. + */ __weak int __init parse_acpi_topology(void) { + unsigned int max_smt_thread_num = 1; + struct cpu_smt_info *entry; + struct xarray hetero_cpu; + unsigned long hetero_id; + int cpu, topology_id; + + if (acpi_disabled) + return 0; + + xa_init(&hetero_cpu); + + for_each_possible_cpu(cpu) { + topology_id = find_acpi_cpu_topology(cpu, 0); + if (topology_id < 0) + return topology_id; + + if (acpi_cpu_is_threaded(cpu)) { + cpu_topology[cpu].thread_id = topology_id; + topology_id = find_acpi_cpu_topology(cpu, 1); + cpu_topology[cpu].core_id = topology_id; + + /* + * In the PPTT, CPUs below a node with the 'identical + * implementation' flag have the same number of threads. + * Count the number of threads for only one CPU (i.e. + * one core_id) among those with the same hetero_id. + * See the comment of find_acpi_cpu_topology_hetero_id() + * for more details. + * + * One entry is created for each node having: + * - the 'identical implementation' flag + * - its parent not having the flag + */ + hetero_id = find_acpi_cpu_topology_hetero_id(cpu); + entry = xa_load(&hetero_cpu, hetero_id); + if (!entry) { + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + WARN_ON_ONCE(!entry); + + if (entry) { + entry->core_id = topology_id; + entry->thread_num = 1; + xa_store(&hetero_cpu, hetero_id, + entry, GFP_KERNEL); + } + } else if (entry->core_id == topology_id) { + entry->thread_num++; + } + } else { + cpu_topology[cpu].thread_id = -1; + cpu_topology[cpu].core_id = topology_id; + } + topology_id = find_acpi_cpu_topology_cluster(cpu); + cpu_topology[cpu].cluster_id = topology_id; + topology_id = find_acpi_cpu_topology_package(cpu); + cpu_topology[cpu].package_id = topology_id; + } + + /* + * This is a short loop since the number of XArray elements is the + * number of heterogeneous CPU clusters. On a homogeneous system + * there's only one entry in the XArray. + */ + xa_for_each(&hetero_cpu, hetero_id, entry) { + max_smt_thread_num = max(max_smt_thread_num, entry->thread_num); + xa_erase(&hetero_cpu, hetero_id); + kfree(entry); + } + + cpu_smt_set_num_threads(max_smt_thread_num, max_smt_thread_num); + xa_destroy(&hetero_cpu); return 0; } -#if defined(CONFIG_ARM64) || defined(CONFIG_RISCV) void __init init_cpu_topology(void) { int cpu, ret; diff --git a/include/linux/arch_topology.h b/include/linux/arch_topology.h index d72d6e5aa200..766ed9cf0e54 100644 --- a/include/linux/arch_topology.h +++ b/include/linux/arch_topology.h @@ -80,6 +80,11 @@ extern struct cpu_topology cpu_topology[NR_CPUS]; #define topology_sibling_cpumask(cpu) (&cpu_topology[cpu].thread_sibling) #define topology_cluster_cpumask(cpu) (&cpu_topology[cpu].cluster_sibling) #define topology_llc_cpumask(cpu) (&cpu_topology[cpu].llc_sibling) + +#ifndef arch_cpu_is_threaded +#define arch_cpu_is_threaded() (0) +#endif + void init_cpu_topology(void); void store_cpu_topology(unsigned int cpuid); const struct cpumask *cpu_coregroup_mask(int cpu); -- cgit v1.2.3 From c7f6d5380f6ef5c328b63807c4e9b73eaef9aeda Mon Sep 17 00:00:00 2001 From: Peter Colberg Date: Mon, 20 Oct 2025 17:02:22 +0000 Subject: rust: pci: refer to legacy as INTx interrupts Consistently use INTx, as in the description of IrqType::Intx, to refer to the four legacy PCI interrupts, INTA#, INTB#, INTC#, and INTD#. Link: https://lore.kernel.org/rust-for-linux/20251015230209.GA960343@bhelgaas/ Link: https://github.com/Rust-for-Linux/linux/issues/1196 Suggested-by: Bjorn Helgaas Signed-off-by: Peter Colberg Signed-off-by: Danilo Krummrich --- rust/kernel/pci/irq.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index 77235c271876..782a524fe11c 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -42,7 +42,7 @@ impl IrqType { pub struct IrqTypes(u32); impl IrqTypes { - /// Create a set containing all IRQ types (MSI-X, MSI, and Legacy). + /// Create a set containing all IRQ types (MSI-X, MSI, and INTx). pub const fn all() -> Self { Self(bindings::PCI_IRQ_ALL_TYPES) } @@ -52,7 +52,7 @@ impl IrqTypes { /// # Examples /// /// ```ignore - /// // Create a set with only MSI and MSI-X (no legacy interrupts). + /// // Create a set with only MSI and MSI-X (no INTx interrupts). /// let msi_only = IrqTypes::default() /// .with(IrqType::Msi) /// .with(IrqType::MsiX); @@ -199,9 +199,9 @@ impl Device { /// Allocate IRQ vectors for this PCI device with automatic cleanup. /// /// Allocates between `min_vecs` and `max_vecs` interrupt vectors for the device. - /// The allocation will use MSI-X, MSI, or legacy interrupts based on the `irq_types` + /// The allocation will use MSI-X, MSI, or INTx interrupts based on the `irq_types` /// parameter and hardware capabilities. When multiple types are specified, the kernel - /// will try them in order of preference: MSI-X first, then MSI, then legacy interrupts. + /// will try them in order of preference: MSI-X first, then MSI, then INTx interrupts. /// /// The allocated vectors are automatically freed when the device is unbound, using the /// devres (device resource management) system. @@ -225,7 +225,7 @@ impl Device { /// // Allocate using any available interrupt type in the order mentioned above. /// let vectors = dev.alloc_irq_vectors(1, 32, pci::IrqTypes::all())?; /// - /// // Allocate MSI or MSI-X only (no legacy interrupts). + /// // Allocate MSI or MSI-X only (no INTx interrupts). /// let msi_only = pci::IrqTypes::default() /// .with(pci::IrqType::Msi) /// .with(pci::IrqType::MsiX); -- cgit v1.2.3 From 26c1a20bf7ce76e9afe4030f25bec20e3c63dcf8 Mon Sep 17 00:00:00 2001 From: Peter Colberg Date: Mon, 20 Oct 2025 17:02:23 +0000 Subject: rust: pci: normalise spelling of PCI BAR Consistently refer to PCI base address register as PCI BAR. Fix spelling mistake "Mapps" -> "Maps". Link: https://lore.kernel.org/rust-for-linux/20251015225827.GA960157@bhelgaas/ Link: https://github.com/Rust-for-Linux/linux/issues/1196 Suggested-by: Bjorn Helgaas Signed-off-by: Peter Colberg Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 4 ++-- rust/kernel/pci/io.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 0d5b4b394bbf..039a0d81d363 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -377,7 +377,7 @@ impl Device { unsafe { (*self.as_raw()).subsystem_device } } - /// Returns the start of the given PCI bar resource. + /// Returns the start of the given PCI BAR resource. pub fn resource_start(&self, bar: u32) -> Result { if !Bar::index_is_valid(bar) { return Err(EINVAL); @@ -389,7 +389,7 @@ impl Device { Ok(unsafe { bindings::pci_resource_start(self.as_raw(), bar.try_into()?) }) } - /// Returns the size of the given PCI bar resource. + /// Returns the size of the given PCI BAR resource. pub fn resource_len(&self, bar: u32) -> Result { if !Bar::index_is_valid(bar) { return Err(EINVAL); diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 65151a0a1a41..3684276b326b 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -18,7 +18,7 @@ use kernel::prelude::*; /// # Invariants /// /// `Bar` always holds an `IoRaw` inststance that holds a valid pointer to the start of the I/O -/// memory mapped PCI bar and its size. +/// memory mapped PCI BAR and its size. pub struct Bar { pdev: ARef, io: IoRaw, @@ -78,7 +78,7 @@ impl Bar { /// # Safety /// - /// `ioptr` must be a valid pointer to the memory mapped PCI bar number `num`. + /// `ioptr` must be a valid pointer to the memory mapped PCI BAR number `num`. unsafe fn do_release(pdev: &Device, ioptr: usize, num: i32) { // SAFETY: // `pdev` is valid by the invariants of `Device`. @@ -120,7 +120,7 @@ impl Deref for Bar { } impl Device { - /// Mapps an entire PCI-BAR after performing a region-request on it. I/O operation bound checks + /// Maps an entire PCI BAR after performing a region-request on it. I/O operation bound checks /// can be performed on compile time for offsets (plus the requested type size) < SIZE. pub fn iomap_region_sized<'a, const SIZE: usize>( &'a self, @@ -130,7 +130,7 @@ impl Device { Devres::new(self.as_ref(), Bar::::new(self, bar, name)) } - /// Mapps an entire PCI-BAR after performing a region-request on it. + /// Maps an entire PCI BAR after performing a region-request on it. pub fn iomap_region<'a>( &'a self, bar: u32, -- cgit v1.2.3 From aad1577ab950d1ad46e0dd0915bfbaf9fa9160e4 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Thu, 23 Oct 2025 15:11:24 +0900 Subject: rust: simplify read_poll_timeout's example code - Drop unnecessary Result's '<()>' - Use '?' instead of match Signed-off-by: FUJITA Tomonori Signed-off-by: Danilo Krummrich --- rust/kernel/io/poll.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs index 613eb25047ef..8f8886543f34 100644 --- a/rust/kernel/io/poll.rs +++ b/rust/kernel/io/poll.rs @@ -42,8 +42,8 @@ use crate::{ /// /// const HW_READY: u16 = 0x01; /// -/// fn wait_for_hardware(io: &Io) -> Result<()> { -/// match read_poll_timeout( +/// fn wait_for_hardware(io: &Io) -> Result { +/// read_poll_timeout( /// // The `op` closure reads the value of a specific status register. /// || io.try_read16(0x1000), /// // The `cond` closure takes a reference to the value returned by `op` @@ -51,14 +51,8 @@ use crate::{ /// |val: &u16| *val == HW_READY, /// Delta::from_millis(50), /// Delta::from_secs(3), -/// ) { -/// Ok(_) => { -/// // The hardware is ready. The returned value of the `op` closure -/// // isn't used. -/// Ok(()) -/// } -/// Err(e) => Err(e), -/// } +/// )?; +/// Ok(()) /// } /// ``` #[track_caller] -- cgit v1.2.3 From 6bbaa93912bfdfd5ffdc804275cc6a444c9400af Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:23 +0200 Subject: rust: device: narrow the generic of drvdata_obtain() Let T be the actual private driver data type without the surrounding box, as it leaves less room for potential bugs. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 2 +- rust/kernel/device.rs | 4 ++-- rust/kernel/pci.rs | 2 +- rust/kernel/platform.rs | 2 +- rust/kernel/usb.rs | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index e12f78734606..a6a2b23befce 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -85,7 +85,7 @@ impl Adapter { // SAFETY: `remove_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - drop(unsafe { adev.as_ref().drvdata_obtain::>>() }); + drop(unsafe { adev.as_ref().drvdata_obtain::() }); } } diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 343996027c89..106aa57a6385 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -215,7 +215,7 @@ impl Device { /// - Must only be called once after a preceding call to [`Device::set_drvdata`]. /// - The type `T` must match the type of the `ForeignOwnable` previously stored by /// [`Device::set_drvdata`]. - pub unsafe fn drvdata_obtain(&self) -> T { + pub unsafe fn drvdata_obtain(&self) -> Pin> { // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. let ptr = unsafe { bindings::dev_get_drvdata(self.as_raw()) }; @@ -224,7 +224,7 @@ impl Device { // `into_foreign()`. // - `dev_get_drvdata()` guarantees to return the same pointer given to `dev_set_drvdata()` // in `into_foreign()`. - unsafe { T::from_foreign(ptr.cast()) } + unsafe { Pin::>::from_foreign(ptr.cast()) } } /// Borrow the driver's private data bound to this [`Device`]. diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 039a0d81d363..b68ef4e575fc 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -94,7 +94,7 @@ impl Adapter { // SAFETY: `remove_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { pdev.as_ref().drvdata_obtain::>>() }; + let data = unsafe { pdev.as_ref().drvdata_obtain::() }; T::unbind(pdev, data.as_ref()); } diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 043721fdb6d8..8f7522c4cf89 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -91,7 +91,7 @@ impl Adapter { // SAFETY: `remove_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { pdev.as_ref().drvdata_obtain::>>() }; + let data = unsafe { pdev.as_ref().drvdata_obtain::() }; T::unbind(pdev, data.as_ref()); } diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index fa8367c0dbaa..92215fdc3c6a 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -87,9 +87,9 @@ impl Adapter { // SAFETY: `disconnect_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { dev.drvdata_obtain::>>() }; + let data = unsafe { dev.drvdata_obtain::() }; - T::disconnect(intf, data.as_ref()); + T::disconnect(intf, data.data()); } } -- cgit v1.2.3 From 6f61a2637abe4f89877da3280775565baedb60e0 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:24 +0200 Subject: rust: device: introduce Device::drvdata() In C dev_get_drvdata() has specific requirements under which it is valid to access the returned pointer. That is, drivers have to ensure that (1) for the duration the returned pointer is accessed the driver is bound and remains to be bound to the corresponding device, (2) the returned void * is treated according to the driver's private data type, i.e. according to what has been passed to dev_set_drvdata(). In Rust, (1) can be ensured by simply requiring the Bound device context, i.e. provide the drvdata() method for Device only. For (2) we would usually make the device type generic over the driver type, e.g. Device, where ::Data is the type of the driver's private data. However, a device does not have a driver type known at compile time and may be bound to multiple drivers throughout its lifetime. Hence, in order to be able to provide a safe accessor for the driver's device private data, we have to do the type check on runtime. This is achieved by letting a driver assert the expected type, which is then compared to a type hash stored in struct device_private when dev_set_drvdata() is called. Example: // `dev` is a `&Device`. let data = dev.drvdata::()?; There are two aspects to note: (1) Technically, the same check could be achieved by comparing the struct device_driver pointer of struct device with the struct device_driver pointer of the driver struct (e.g. struct pci_driver). However, this would - in addition the pointer comparison - require to tie back the private driver data type to the struct device_driver pointer of the driver struct to prove correctness. Besides that, accessing the driver struct (stored in the module structure) isn't trivial and would result into horrible code and API ergonomics. (2) Having a direct accessor to the driver's private data is not commonly required (at least in Rust): Bus callback methods already provide access to the driver's device private data through a &self argument, while other driver entry points such as IRQs, workqueues, timers, IOCTLs, etc. have their own private data with separate ownership and lifetime. In other words, a driver's device private data is only relevant for driver model contexts (such a file private is only relevant for file contexts). Having that said, the motivation for accessing the driver's device private data with Device::drvdata() are interactions between drivers. For instance, when an auxiliary driver calls back into its parent, the parent has to be capable to derive its private data from the corresponding device (i.e. the parent of the auxiliary device). Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman [ * Remove unnecessary `const _: ()` block, * rename type_id_{store,match}() to {set,match}_type_id(), * assert size_of::() >= size_of::(), * add missing check in case Device::drvdata() is called from probe(). - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/base/base.h | 16 ++++++++ rust/bindings/bindings_helper.h | 6 +++ rust/kernel/device.rs | 84 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/drivers/base/base.h b/drivers/base/base.h index 86fa7fbb3548..430cbefbc97f 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -85,6 +85,18 @@ struct driver_private { }; #define to_driver(obj) container_of(obj, struct driver_private, kobj) +#ifdef CONFIG_RUST +/** + * struct driver_type - Representation of a Rust driver type. + */ +struct driver_type { + /** + * @id: Representation of core::any::TypeId. + */ + u8 id[16]; +} __packed; +#endif + /** * struct device_private - structure to hold the private to the driver core portions of the device structure. * @@ -100,6 +112,7 @@ struct driver_private { * @async_driver - pointer to device driver awaiting probe via async_probe * @device - pointer back to the struct device that this structure is * associated with. + * @driver_type - The type of the bound Rust driver. * @dead - This device is currently either in the process of or has been * removed from the system. Any asynchronous events scheduled for this * device should exit without taking any action. @@ -116,6 +129,9 @@ struct device_private { const struct device_driver *async_driver; char *deferred_probe_reason; struct device *device; +#ifdef CONFIG_RUST + struct driver_type driver_type; +#endif u8 dead:1; }; #define to_device_private_parent(obj) \ diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 2e43c66635a2..a79fd111f886 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -85,6 +85,12 @@ #include #include +/* + * The driver-core Rust code needs to know about some C driver-core private + * structures. + */ +#include <../../drivers/base/base.h> + #if defined(CONFIG_DRM_PANIC_SCREEN_QR_CODE) // Used by `#[export]` in `drivers/gpu/drm/drm_panic_qr.rs`. #include diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 106aa57a6385..1a307be953c2 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -10,13 +10,16 @@ use crate::{ sync::aref::ARef, types::{ForeignOwnable, Opaque}, }; -use core::{marker::PhantomData, ptr}; +use core::{any::TypeId, marker::PhantomData, ptr}; #[cfg(CONFIG_PRINTK)] use crate::c_str; pub mod property; +// Assert that we can `read()` / `write()` a `TypeId` instance from / into `struct driver_type`. +static_assert!(core::mem::size_of::() >= core::mem::size_of::()); + /// The core representation of a device in the kernel's driver model. /// /// This structure represents the Rust abstraction for a C `struct device`. A [`Device`] can either @@ -198,12 +201,29 @@ impl Device { } impl Device { + fn set_type_id(&self) { + // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. + let private = unsafe { (*self.as_raw()).p }; + + // SAFETY: For a bound device (implied by the `CoreInternal` device context), `private` is + // guaranteed to be a valid pointer to a `struct device_private`. + let driver_type = unsafe { &raw mut (*private).driver_type }; + + // SAFETY: `driver_type` is valid for (unaligned) writes of a `TypeId`. + unsafe { + driver_type + .cast::() + .write_unaligned(TypeId::of::()) + }; + } + /// Store a pointer to the bound driver's private data. pub fn set_drvdata(&self, data: impl PinInit) -> Result { let data = KBox::pin_init(data, GFP_KERNEL)?; // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. unsafe { bindings::dev_set_drvdata(self.as_raw(), data.into_foreign().cast()) }; + self.set_type_id::(); Ok(()) } @@ -219,6 +239,9 @@ impl Device { // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. let ptr = unsafe { bindings::dev_get_drvdata(self.as_raw()) }; + // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. + unsafe { bindings::dev_set_drvdata(self.as_raw(), core::ptr::null_mut()) }; + // SAFETY: // - By the safety requirements of this function, `ptr` comes from a previous call to // `into_foreign()`. @@ -235,7 +258,23 @@ impl Device { /// [`Device::drvdata_obtain`]. /// - The type `T` must match the type of the `ForeignOwnable` previously stored by /// [`Device::set_drvdata`]. - pub unsafe fn drvdata_borrow(&self) -> T::Borrowed<'_> { + pub unsafe fn drvdata_borrow(&self) -> Pin<&T> { + // SAFETY: `drvdata_unchecked()` has the exact same safety requirements as the ones + // required by this method. + unsafe { self.drvdata_unchecked() } + } +} + +impl Device { + /// Borrow the driver's private data bound to this [`Device`]. + /// + /// # Safety + /// + /// - Must only be called after a preceding call to [`Device::set_drvdata`] and before + /// [`Device::drvdata_obtain`]. + /// - The type `T` must match the type of the `ForeignOwnable` previously stored by + /// [`Device::set_drvdata`]. + unsafe fn drvdata_unchecked(&self) -> Pin<&T> { // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. let ptr = unsafe { bindings::dev_get_drvdata(self.as_raw()) }; @@ -244,7 +283,46 @@ impl Device { // `into_foreign()`. // - `dev_get_drvdata()` guarantees to return the same pointer given to `dev_set_drvdata()` // in `into_foreign()`. - unsafe { T::borrow(ptr.cast()) } + unsafe { Pin::>::borrow(ptr.cast()) } + } + + fn match_type_id(&self) -> Result { + // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. + let private = unsafe { (*self.as_raw()).p }; + + // SAFETY: For a bound device, `private` is guaranteed to be a valid pointer to a + // `struct device_private`. + let driver_type = unsafe { &raw mut (*private).driver_type }; + + // SAFETY: + // - `driver_type` is valid for (unaligned) reads of a `TypeId`. + // - A bound device guarantees that `driver_type` contains a valid `TypeId` value. + let type_id = unsafe { driver_type.cast::().read_unaligned() }; + + if type_id != TypeId::of::() { + return Err(EINVAL); + } + + Ok(()) + } + + /// Access a driver's private data. + /// + /// Returns a pinned reference to the driver's private data or [`EINVAL`] if it doesn't match + /// the asserted type `T`. + pub fn drvdata(&self) -> Result> { + // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. + if unsafe { bindings::dev_get_drvdata(self.as_raw()) }.is_null() { + return Err(ENOENT); + } + + self.match_type_id::()?; + + // SAFETY: + // - The above check of `dev_get_drvdata()` guarantees that we are called after + // `set_drvdata()` and before `drvdata_obtain()`. + // - We've just checked that the type of the driver's private data is in fact `T`. + Ok(unsafe { self.drvdata_unchecked() }) } } -- cgit v1.2.3 From 589b061975db3c7e87b819cc9a8006eb99ac4b5f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:25 +0200 Subject: rust: auxiliary: consider auxiliary devices always have a parent An auxiliary device is guaranteed to always have a parent device (both in C and Rust), hence don't return an Option<&auxiliary::Device> in auxiliary::Device::parent(). Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nova/file.rs | 2 +- rust/kernel/auxiliary.rs | 7 ++++--- samples/rust/rust_driver_auxiliary.rs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/nova/file.rs b/drivers/gpu/drm/nova/file.rs index 90b9d2d0ec4a..a3b7bd36792c 100644 --- a/drivers/gpu/drm/nova/file.rs +++ b/drivers/gpu/drm/nova/file.rs @@ -28,7 +28,7 @@ impl File { _file: &drm::File, ) -> Result { let adev = &dev.adev; - let parent = adev.parent().ok_or(ENOENT)?; + let parent = adev.parent(); let pdev: &pci::Device = parent.try_into()?; let value = match getparam.param as u32 { diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index a6a2b23befce..e5bddb738d58 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -215,9 +215,10 @@ impl Device { unsafe { (*self.as_raw()).id } } - /// Returns a reference to the parent [`device::Device`], if any. - pub fn parent(&self) -> Option<&device::Device> { - self.as_ref().parent() + /// Returns a reference to the parent [`device::Device`]. + pub fn parent(&self) -> &device::Device { + // SAFETY: A `struct auxiliary_device` always has a parent. + unsafe { self.as_ref().parent().unwrap_unchecked() } } } diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 0e221abe4936..2e9afeb83d4f 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -68,7 +68,7 @@ impl pci::Driver for ParentDriver { impl ParentDriver { fn connect(adev: &auxiliary::Device) -> Result<()> { - let parent = adev.parent().ok_or(EINVAL)?; + let parent = adev.parent(); let pdev: &pci::Device = parent.try_into()?; let vendor = pdev.vendor_id(); -- cgit v1.2.3 From e4e679c8608e5c747081cc6ce63ee0b0e524c68d Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:26 +0200 Subject: rust: auxiliary: unregister on parent device unbind Guarantee that an auxiliary driver will be unbound before its parent is unbound; there is no point in operating an auxiliary device whose parent has been unbound. In practice, this guarantee allows us to assume that for a bound auxiliary device, also the parent device is bound. This is useful when an auxiliary driver calls into its parent, since it allows the parent to directly access device resources and its device private data due to the guaranteed bound device context. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- drivers/gpu/nova-core/driver.rs | 8 ++-- rust/kernel/auxiliary.rs | 89 ++++++++++++++++++++--------------- samples/rust/rust_driver_auxiliary.rs | 17 ++++--- 3 files changed, 66 insertions(+), 48 deletions(-) diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs index a83b86199182..ca0d5f8ad54b 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -3,6 +3,7 @@ use kernel::{ auxiliary, c_str, device::Core, + devres::Devres, pci, pci::{Class, ClassMask, Vendor}, prelude::*, @@ -16,7 +17,8 @@ use crate::gpu::Gpu; pub(crate) struct NovaCore { #[pin] pub(crate) gpu: Gpu, - _reg: auxiliary::Registration, + #[pin] + _reg: Devres, } const BAR0_SIZE: usize = SZ_16M; @@ -65,12 +67,12 @@ impl pci::Driver for NovaCore { Ok(try_pin_init!(Self { gpu <- Gpu::new(pdev, bar.clone(), bar.access(pdev.as_ref())?), - _reg: auxiliary::Registration::new( + _reg <- auxiliary::Registration::new( pdev.as_ref(), c_str!("nova-drm"), 0, // TODO[XARR]: Once it lands, use XArray; for now we don't use the ID. crate::MODULE_NAME - )?, + ), })) }) } diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index e5bddb738d58..8c0a2472c26a 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -7,6 +7,7 @@ use crate::{ bindings, container_of, device, device_id::{RawDeviceId, RawDeviceIdIndex}, + devres::Devres, driver, error::{from_result, to_result, Result}, prelude::*, @@ -279,8 +280,8 @@ unsafe impl Sync for Device {} /// The registration of an auxiliary device. /// -/// This type represents the registration of a [`struct auxiliary_device`]. When an instance of this -/// type is dropped, its respective auxiliary device will be unregistered from the system. +/// This type represents the registration of a [`struct auxiliary_device`]. When its parent device +/// is unbound, the corresponding auxiliary device will be unregistered from the system. /// /// # Invariants /// @@ -290,44 +291,56 @@ pub struct Registration(NonNull); impl Registration { /// Create and register a new auxiliary device. - pub fn new(parent: &device::Device, name: &CStr, id: u32, modname: &CStr) -> Result { - let boxed = KBox::new(Opaque::::zeroed(), GFP_KERNEL)?; - let adev = boxed.get(); + pub fn new<'a>( + parent: &'a device::Device, + name: &'a CStr, + id: u32, + modname: &'a CStr, + ) -> impl PinInit, Error> + 'a { + pin_init::pin_init_scope(move || { + let boxed = KBox::new(Opaque::::zeroed(), GFP_KERNEL)?; + let adev = boxed.get(); + + // SAFETY: It's safe to set the fields of `struct auxiliary_device` on initialization. + unsafe { + (*adev).dev.parent = parent.as_raw(); + (*adev).dev.release = Some(Device::release); + (*adev).name = name.as_char_ptr(); + (*adev).id = id; + } - // SAFETY: It's safe to set the fields of `struct auxiliary_device` on initialization. - unsafe { - (*adev).dev.parent = parent.as_raw(); - (*adev).dev.release = Some(Device::release); - (*adev).name = name.as_char_ptr(); - (*adev).id = id; - } - - // SAFETY: `adev` is guaranteed to be a valid pointer to a `struct auxiliary_device`, - // which has not been initialized yet. - unsafe { bindings::auxiliary_device_init(adev) }; - - // Now that `adev` is initialized, leak the `Box`; the corresponding memory will be freed - // by `Device::release` when the last reference to the `struct auxiliary_device` is dropped. - let _ = KBox::into_raw(boxed); - - // SAFETY: - // - `adev` is guaranteed to be a valid pointer to a `struct auxiliary_device`, which has - // been initialialized, - // - `modname.as_char_ptr()` is a NULL terminated string. - let ret = unsafe { bindings::__auxiliary_device_add(adev, modname.as_char_ptr()) }; - if ret != 0 { // SAFETY: `adev` is guaranteed to be a valid pointer to a `struct auxiliary_device`, - // which has been initialialized. - unsafe { bindings::auxiliary_device_uninit(adev) }; - - return Err(Error::from_errno(ret)); - } - - // SAFETY: `adev` is guaranteed to be non-null, since the `KBox` was allocated successfully. - // - // INVARIANT: The device will remain registered until `auxiliary_device_delete()` is called, - // which happens in `Self::drop()`. - Ok(Self(unsafe { NonNull::new_unchecked(adev) })) + // which has not been initialized yet. + unsafe { bindings::auxiliary_device_init(adev) }; + + // Now that `adev` is initialized, leak the `Box`; the corresponding memory will be + // freed by `Device::release` when the last reference to the `struct auxiliary_device` + // is dropped. + let _ = KBox::into_raw(boxed); + + // SAFETY: + // - `adev` is guaranteed to be a valid pointer to a `struct auxiliary_device`, which + // has been initialized, + // - `modname.as_char_ptr()` is a NULL terminated string. + let ret = unsafe { bindings::__auxiliary_device_add(adev, modname.as_char_ptr()) }; + if ret != 0 { + // SAFETY: `adev` is guaranteed to be a valid pointer to a + // `struct auxiliary_device`, which has been initialized. + unsafe { bindings::auxiliary_device_uninit(adev) }; + + return Err(Error::from_errno(ret)); + } + + // SAFETY: `adev` is guaranteed to be non-null, since the `KBox` was allocated + // successfully. + // + // INVARIANT: The device will remain registered until `auxiliary_device_delete()` is + // called, which happens in `Self::drop()`. + Ok(Devres::new( + parent, + Self(unsafe { NonNull::new_unchecked(adev) }), + )) + }) } } diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 2e9afeb83d4f..95c552ee9489 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -5,7 +5,8 @@ //! To make this driver probe, QEMU must be run with `-device pci-testdev`. use kernel::{ - auxiliary, c_str, device::Core, driver, error::Error, pci, prelude::*, InPlaceModule, + auxiliary, c_str, device::Core, devres::Devres, driver, error::Error, pci, prelude::*, + InPlaceModule, }; use pin_init::PinInit; @@ -40,8 +41,12 @@ impl auxiliary::Driver for AuxiliaryDriver { } } +#[pin_data] struct ParentDriver { - _reg: [auxiliary::Registration; 2], + #[pin] + _reg0: Devres, + #[pin] + _reg1: Devres, } kernel::pci_device_table!( @@ -57,11 +62,9 @@ impl pci::Driver for ParentDriver { const ID_TABLE: pci::IdTable = &PCI_TABLE; fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> impl PinInit { - Ok(Self { - _reg: [ - auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 0, MODULE_NAME)?, - auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 1, MODULE_NAME)?, - ], + try_pin_init!(Self { + _reg0 <- auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 0, MODULE_NAME), + _reg1 <- auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 1, MODULE_NAME), }) } } -- cgit v1.2.3 From b69165a09727b653993934d700a02d32a8961327 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:27 +0200 Subject: rust: auxiliary: move parent() to impl Device Currently, the parent method is implemented for any Device, i.e. any device context and returns a &device::Device. However, a subsequent patch will introduce impl Device { pub fn parent() -> device::Device { ... } } which takes advantage of the fact that if the auxiliary device is bound the parent is guaranteed to be bound as well. I.e. the behavior we want is that all device contexts that dereference to Bound, will use the implementation above, whereas the old implementation should only be implemented for Device. Hence, move the current implementation. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index 8c0a2472c26a..497601f7473b 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -215,15 +215,15 @@ impl Device { // `struct auxiliary_device`. unsafe { (*self.as_raw()).id } } +} +impl Device { /// Returns a reference to the parent [`device::Device`]. pub fn parent(&self) -> &device::Device { // SAFETY: A `struct auxiliary_device` always has a parent. unsafe { self.as_ref().parent().unwrap_unchecked() } } -} -impl Device { extern "C" fn release(dev: *mut bindings::device) { // SAFETY: By the type invariant `self.0.as_raw` is a pointer to the `struct device` // embedded in `struct auxiliary_device`. -- cgit v1.2.3 From 675e514edd659b5cfc15eb70bd8abf53568947cc Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:28 +0200 Subject: rust: auxiliary: implement parent() for Device Take advantage of the fact that if the auxiliary device is bound the parent is guaranteed to be bound as well and implement a separate parent() method for auxiliary::Device. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index 497601f7473b..cc67fa5ddde3 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -217,6 +217,16 @@ impl Device { } } +impl Device { + /// Returns a bound reference to the parent [`device::Device`]. + pub fn parent(&self) -> &device::Device { + let parent = (**self).parent(); + + // SAFETY: A bound auxiliary device always has a bound parent device. + unsafe { parent.as_bound() } + } +} + impl Device { /// Returns a reference to the parent [`device::Device`]. pub fn parent(&self) -> &device::Device { -- cgit v1.2.3 From 710ac546883c2cae6e8e7b5dcf7757b8a49d75a1 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:29 +0200 Subject: samples: rust: auxiliary: misc cleanup of ParentDriver::connect() In ParentDriver::connect() rename parent to dev, use it for the dev_info!() call, call pdev.vendor_() directly in the print statement and remove the unnecessary generic type of Result. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_auxiliary.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 95c552ee9489..a5d67d4d9e83 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -70,16 +70,15 @@ impl pci::Driver for ParentDriver { } impl ParentDriver { - fn connect(adev: &auxiliary::Device) -> Result<()> { - let parent = adev.parent(); - let pdev: &pci::Device = parent.try_into()?; + fn connect(adev: &auxiliary::Device) -> Result { + let dev = adev.parent(); + let pdev: &pci::Device = dev.try_into()?; - let vendor = pdev.vendor_id(); dev_info!( - adev.as_ref(), + dev, "Connect auxiliary {} with parent: VendorID={}, DeviceID={:#x}\n", adev.id(), - vendor, + pdev.vendor_id(), pdev.device_id() ); -- cgit v1.2.3 From b0b7301b004301afe920b3d08caa6171dd3f4011 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:34:30 +0200 Subject: samples: rust: auxiliary: illustrate driver interaction Illustrate how a parent driver of an auxiliary driver can take advantage of the device context guarantees given by the auxiliary bus and subsequently safely derive its device private data. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_auxiliary.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index a5d67d4d9e83..5761ea314f44 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -5,10 +5,17 @@ //! To make this driver probe, QEMU must be run with `-device pci-testdev`. use kernel::{ - auxiliary, c_str, device::Core, devres::Devres, driver, error::Error, pci, prelude::*, + auxiliary, c_str, + device::{Bound, Core}, + devres::Devres, + driver, + error::Error, + pci, + prelude::*, InPlaceModule, }; +use core::any::TypeId; use pin_init::PinInit; const MODULE_NAME: &CStr = ::NAME; @@ -43,6 +50,7 @@ impl auxiliary::Driver for AuxiliaryDriver { #[pin_data] struct ParentDriver { + private: TypeId, #[pin] _reg0: Devres, #[pin] @@ -63,6 +71,7 @@ impl pci::Driver for ParentDriver { fn probe(pdev: &pci::Device, _info: &Self::IdInfo) -> impl PinInit { try_pin_init!(Self { + private: TypeId::of::(), _reg0 <- auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 0, MODULE_NAME), _reg1 <- auxiliary::Registration::new(pdev.as_ref(), AUXILIARY_NAME, 1, MODULE_NAME), }) @@ -70,9 +79,10 @@ impl pci::Driver for ParentDriver { } impl ParentDriver { - fn connect(adev: &auxiliary::Device) -> Result { + fn connect(adev: &auxiliary::Device) -> Result { let dev = adev.parent(); - let pdev: &pci::Device = dev.try_into()?; + let pdev: &pci::Device = dev.try_into()?; + let drvdata = dev.drvdata::()?; dev_info!( dev, @@ -82,6 +92,12 @@ impl ParentDriver { pdev.device_id() ); + dev_info!( + dev, + "We have access to the private data of {:?}.\n", + drvdata.private + ); + Ok(()) } } -- cgit v1.2.3 From fde40a558dbd4945bba03cd7f425feab4c92e1d1 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 3 Nov 2025 12:01:03 +0100 Subject: rust: usb: fix broken call to T::disconnect() A refactoring of Device::drvdata_obtain() broke T::disconnect() in the USB abstractions. """ error[E0599]: no method named `data` found for struct `core::pin::Pin>` in the current scope --> rust/kernel/usb.rs:92:34 | 92 | T::disconnect(intf, data.data()); | ^^^^ method not found in `core::pin::Pin>` error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0599`. make[2]: *** [rust/Makefile:553: rust/kernel.o] Error 1 make[1]: *** [/builddir/build/BUILD/kernel-6.18.0-build/kernel-next-20251103/linux-6.18.0-0.0.next.20251103.436.vanilla.fc44.x86_64/Makefile:1316: prepare] Error 2 make: *** [Makefile:256: __sub-make] Error 2 """ This slipped through, since the USB abstractions are globally disabled. However, the USB tree recently enabled them, hence it showed up in linux-next. Reported-by: Thorsten Leemhuis Closes: https://lore.kernel.org/all/1c8afbc0-e888-4702-9e4e-fa8aef0f97ae@leemhuis.info/ Fixes: 6bbaa93912bf ("rust: device: narrow the generic of drvdata_obtain()") Reviewed-by: Alice Ryhl Tested-by: Thorsten Leemhuis Link: https://patch.msgid.link/20251103110115.1925072-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/usb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 92215fdc3c6a..534e3ded5442 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -89,7 +89,7 @@ impl Adapter { // and stored a `Pin>`. let data = unsafe { dev.drvdata_obtain::() }; - T::disconnect(intf, data.data()); + T::disconnect(intf, data.as_ref()); } } -- cgit v1.2.3 From ad68b55a9ed0e5ce16508cb10081a73ea8f4bbef Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Mon, 3 Nov 2025 20:29:57 +0900 Subject: rust: add udelay() function Add udelay() function, inserts a delay based on microseconds with busy waiting, in preparation for supporting read_poll_timeout_atomic(). Reviewed-by: Andreas Hindborg Reviewed-by: Alice Ryhl Signed-off-by: FUJITA Tomonori Acked-by: Andreas Hindborg Link: https://patch.msgid.link/20251103112958.2961517-2-fujita.tomonori@gmail.com Signed-off-by: Danilo Krummrich --- rust/helpers/time.c | 5 +++++ rust/kernel/time/delay.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/rust/helpers/time.c b/rust/helpers/time.c index a318e9fa4408..67a36ccc3ec4 100644 --- a/rust/helpers/time.c +++ b/rust/helpers/time.c @@ -33,3 +33,8 @@ s64 rust_helper_ktime_to_ms(const ktime_t kt) { return ktime_to_ms(kt); } + +void rust_helper_udelay(unsigned long usec) +{ + udelay(usec); +} diff --git a/rust/kernel/time/delay.rs b/rust/kernel/time/delay.rs index eb8838da62bc..b5b1b42797a0 100644 --- a/rust/kernel/time/delay.rs +++ b/rust/kernel/time/delay.rs @@ -47,3 +47,40 @@ pub fn fsleep(delta: Delta) { bindings::fsleep(delta.as_micros_ceil() as c_ulong) } } + +/// Inserts a delay based on microseconds with busy waiting. +/// +/// Equivalent to the C side [`udelay()`], which delays in microseconds. +/// +/// `delta` must be within `[0, MAX_UDELAY_MS]` in milliseconds; +/// otherwise, it is erroneous behavior. That is, it is considered a bug to +/// call this function with an out-of-range value. +/// +/// The behavior above differs from the C side [`udelay()`] for which out-of-range +/// values could lead to an overflow and unexpected behavior. +/// +/// [`udelay()`]: https://docs.kernel.org/timers/delay_sleep_functions.html#c.udelay +pub fn udelay(delta: Delta) { + const MAX_UDELAY_DELTA: Delta = Delta::from_millis(bindings::MAX_UDELAY_MS as i64); + + debug_assert!(delta.as_nanos() >= 0); + debug_assert!(delta <= MAX_UDELAY_DELTA); + + let delta = if (Delta::ZERO..=MAX_UDELAY_DELTA).contains(&delta) { + delta + } else { + MAX_UDELAY_DELTA + }; + + // SAFETY: It is always safe to call `udelay()` with any duration. + // Note that the kernel is compiled with `-fno-strict-overflow` + // so any out-of-range value could lead to unexpected behavior + // but won't lead to undefined behavior. + unsafe { + // Convert the duration to microseconds and round up to preserve + // the guarantee; `udelay()` inserts a delay for at least + // the provided duration, but that it may delay for longer + // under some circumstances. + bindings::udelay(delta.as_micros_ceil() as c_ulong) + } +} -- cgit v1.2.3 From 46f045db5a94cd50b24dc1449bdd444e4473b28b Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Mon, 3 Nov 2025 20:29:58 +0900 Subject: rust: Add read_poll_timeout_atomic function Add read_poll_timeout_atomic function which polls periodically until a condition is met, an error occurs, or the attempt limit is reached. The C's read_poll_timeout_atomic() is used for the similar purpose. In atomic context the timekeeping infrastructure is unavailable, so reliable time-based timeouts cannot be implemented. So instead, the helper accepts a maximum number of attempts and busy-waits (udelay + cpu_relax) between tries. Reviewed-by: Alice Ryhl Signed-off-by: FUJITA Tomonori Link: https://patch.msgid.link/20251103112958.2961517-3-fujita.tomonori@gmail.com [ Adjust imports to use "kernel vertical" style. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/io/poll.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs index 8f8886543f34..ebc29023d59d 100644 --- a/rust/kernel/io/poll.rs +++ b/rust/kernel/io/poll.rs @@ -8,7 +8,15 @@ use crate::{ error::{code::*, Result}, processor::cpu_relax, task::might_sleep, - time::{delay::fsleep, Delta, Instant, Monotonic}, + time::{ + delay::{ + fsleep, + udelay, // + }, + Delta, + Instant, + Monotonic, // + }, }; /// Polls periodically until a condition is met, an error occurs, @@ -96,3 +104,70 @@ where cpu_relax(); } } + +/// Polls periodically until a condition is met, an error occurs, +/// or the attempt limit is reached. +/// +/// The function repeatedly executes the given operation `op` closure and +/// checks its result using the condition closure `cond`. +/// +/// If `cond` returns `true`, the function returns successfully with the result of `op`. +/// Otherwise, it performs a busy wait for a duration specified by `delay_delta` +/// before executing `op` again. +/// +/// This process continues until either `op` returns an error, `cond` +/// returns `true`, or the attempt limit specified by `retry` is reached. +/// +/// # Errors +/// +/// If `op` returns an error, then that error is returned directly. +/// +/// If the attempt limit specified by `retry` is reached, then +/// `Err(ETIMEDOUT)` is returned. +/// +/// # Examples +/// +/// ```no_run +/// use kernel::io::{poll::read_poll_timeout_atomic, Io}; +/// use kernel::time::Delta; +/// +/// const HW_READY: u16 = 0x01; +/// +/// fn wait_for_hardware(io: &Io) -> Result { +/// read_poll_timeout_atomic( +/// // The `op` closure reads the value of a specific status register. +/// || io.try_read16(0x1000), +/// // The `cond` closure takes a reference to the value returned by `op` +/// // and checks whether the hardware is ready. +/// |val: &u16| *val == HW_READY, +/// Delta::from_micros(50), +/// 1000, +/// )?; +/// Ok(()) +/// } +/// ``` +pub fn read_poll_timeout_atomic( + mut op: Op, + mut cond: Cond, + delay_delta: Delta, + retry: usize, +) -> Result +where + Op: FnMut() -> Result, + Cond: FnMut(&T) -> bool, +{ + for _ in 0..retry { + let val = op()?; + if cond(&val) { + return Ok(val); + } + + if !delay_delta.is_zero() { + udelay(delay_delta); + } + + cpu_relax(); + } + + Err(ETIMEDOUT) +} -- cgit v1.2.3 From db7bd1affa852b61dbc0d2ae2809f0f5bf2e3d9d Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 21 Oct 2025 00:26:13 +0200 Subject: rust: fs: add file::Offset type alias Add a type alias for file offsets, i.e. bindings::loff_t. Trying to avoid using raw bindings types, this seems to be the better alternative compared to just using i64. Cc: Alexander Viro Cc: Christian Brauner Cc: Jan Kara Reviewed-by: Alice Ryhl Reviewed-by: Christian Brauner Link: https://patch.msgid.link/20251020222722.240473-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/fs/file.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/kernel/fs/file.rs b/rust/kernel/fs/file.rs index cd6987850332..23ee689bd240 100644 --- a/rust/kernel/fs/file.rs +++ b/rust/kernel/fs/file.rs @@ -17,6 +17,11 @@ use crate::{ }; use core::ptr; +/// Primitive type representing the offset within a [`File`]. +/// +/// Type alias for `bindings::loff_t`. +pub type Offset = bindings::loff_t; + /// Flags associated with a [`File`]. pub mod flags { /// File is opened in append mode. -- cgit v1.2.3 From f2af7b01b05545fff1cea0768c14e2da552a56ee Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:36 +0200 Subject: rust: uaccess: add UserSliceReader::read_slice_partial() The existing read_slice() method is a wrapper around copy_from_user() and expects the user buffer to be larger than the destination buffer. However, userspace may split up writes in multiple partial operations providing an offset into the destination buffer and a smaller user buffer. In order to support this common case, provide a helper for partial reads. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Matthew Maurer Reviewed-by: Alice Ryhl Reviewed-by: Alexandre Courbot Acked-by: Miguel Ojeda [ Replace map_or() with let-else; use saturating_add(). - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/uaccess.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rust/kernel/uaccess.rs b/rust/kernel/uaccess.rs index a8fb4764185a..1409cb907015 100644 --- a/rust/kernel/uaccess.rs +++ b/rust/kernel/uaccess.rs @@ -287,6 +287,23 @@ impl UserSliceReader { self.read_raw(out) } + /// Reads raw data from the user slice into a kernel buffer partially. + /// + /// This is the same as [`Self::read_slice`] but considers the given `offset` into `out` and + /// truncates the read to the boundaries of `self` and `out`. + /// + /// On success, returns the number of bytes read. + pub fn read_slice_partial(&mut self, out: &mut [u8], offset: usize) -> Result { + let end = offset.saturating_add(self.len()).min(out.len()); + + let Some(dst) = out.get_mut(offset..end) else { + return Ok(0); + }; + + self.read_slice(dst)?; + Ok(dst.len()) + } + /// Reads a value of the specified type. /// /// Fails with [`EFAULT`] if the read happens on a bad address, or if the read goes out of -- cgit v1.2.3 From 5829e330482b67a14c8c6d2d500431846d493d67 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:37 +0200 Subject: rust: uaccess: add UserSliceReader::read_slice_file() Add UserSliceReader::read_slice_file(), which is the same as UserSliceReader::read_slice_partial() but updates the given file::Offset by the number of bytes read. This is equivalent to C's `simple_write_to_buffer()` and useful when dealing with file offsets from file operations. Reviewed-by: Alice Ryhl Reviewed-by: Alexandre Courbot Acked-by: Miguel Ojeda [ Replace saturating_add() with the raw operator and a corresponding OVERFLOW comment. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/uaccess.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/rust/kernel/uaccess.rs b/rust/kernel/uaccess.rs index 1409cb907015..dde8a0abb46d 100644 --- a/rust/kernel/uaccess.rs +++ b/rust/kernel/uaccess.rs @@ -9,6 +9,7 @@ use crate::{ bindings, error::Result, ffi::{c_char, c_void}, + fs::file, prelude::*, transmute::{AsBytes, FromBytes}, }; @@ -304,6 +305,31 @@ impl UserSliceReader { Ok(dst.len()) } + /// Reads raw data from the user slice into a kernel buffer partially. + /// + /// This is the same as [`Self::read_slice_partial`] but updates the given [`file::Offset`] by + /// the number of bytes read. + /// + /// This is equivalent to C's `simple_write_to_buffer()`. + /// + /// On success, returns the number of bytes read. + pub fn read_slice_file(&mut self, out: &mut [u8], offset: &mut file::Offset) -> Result { + if offset.is_negative() { + return Err(EINVAL); + } + + let Ok(offset_index) = (*offset).try_into() else { + return Ok(0); + }; + + let read = self.read_slice_partial(out, offset_index)?; + + // OVERFLOW: `offset + read <= data.len() <= isize::MAX <= Offset::MAX` + *offset += read as i64; + + Ok(read) + } + /// Reads a value of the specified type. /// /// Fails with [`EFAULT`] if the read happens on a bad address, or if the read goes out of -- cgit v1.2.3 From 86150533476774ee6ad5875e764ff6acc9a2e48a Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:38 +0200 Subject: rust: uaccess: add UserSliceWriter::write_slice_partial() The existing write_slice() method is a wrapper around copy_to_user() and expects the user buffer to be larger than the source buffer. However, userspace may split up reads in multiple partial operations providing an offset into the source buffer and a smaller user buffer. In order to support this common case, provide a helper for partial writes. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Matthew Maurer Reviewed-by: Alice Ryhl Reviewed-by: Alexandre Courbot Acked-by: Miguel Ojeda [ Replace map_or() with let-else; use saturating_add(). - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/uaccess.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rust/kernel/uaccess.rs b/rust/kernel/uaccess.rs index dde8a0abb46d..38d1404ed9b4 100644 --- a/rust/kernel/uaccess.rs +++ b/rust/kernel/uaccess.rs @@ -481,6 +481,23 @@ impl UserSliceWriter { Ok(()) } + /// Writes raw data to this user pointer from a kernel buffer partially. + /// + /// This is the same as [`Self::write_slice`] but considers the given `offset` into `data` and + /// truncates the write to the boundaries of `self` and `data`. + /// + /// On success, returns the number of bytes written. + pub fn write_slice_partial(&mut self, data: &[u8], offset: usize) -> Result { + let end = offset.saturating_add(self.len()).min(data.len()); + + let Some(src) = data.get(offset..end) else { + return Ok(0); + }; + + self.write_slice(src)?; + Ok(src.len()) + } + /// Writes the provided Rust value to this userspace pointer. /// /// Fails with [`EFAULT`] if the write happens on a bad address, or if the write goes out of -- cgit v1.2.3 From 0ddceba2701e7012646f6df6d32c4e4b7c4dc938 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:39 +0200 Subject: rust: uaccess: add UserSliceWriter::write_slice_file() Add UserSliceWriter::write_slice_file(), which is the same as UserSliceWriter::write_slice_partial() but updates the given file::Offset by the number of bytes written. This is equivalent to C's `simple_read_from_buffer()` and useful when dealing with file offsets from file operations. Reviewed-by: Alice Ryhl Acked-by: Miguel Ojeda [ Replace saturating_add() with the raw operator and a corresponding OVERFLOW comment. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/uaccess.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rust/kernel/uaccess.rs b/rust/kernel/uaccess.rs index 38d1404ed9b4..f989539a31b4 100644 --- a/rust/kernel/uaccess.rs +++ b/rust/kernel/uaccess.rs @@ -498,6 +498,31 @@ impl UserSliceWriter { Ok(src.len()) } + /// Writes raw data to this user pointer from a kernel buffer partially. + /// + /// This is the same as [`Self::write_slice_partial`] but updates the given [`file::Offset`] by + /// the number of bytes written. + /// + /// This is equivalent to C's `simple_read_from_buffer()`. + /// + /// On success, returns the number of bytes written. + pub fn write_slice_file(&mut self, data: &[u8], offset: &mut file::Offset) -> Result { + if offset.is_negative() { + return Err(EINVAL); + } + + let Ok(offset_index) = (*offset).try_into() else { + return Ok(0); + }; + + let written = self.write_slice_partial(data, offset_index)?; + + // OVERFLOW: `offset + written <= data.len() <= isize::MAX <= Offset::MAX` + *offset += written as i64; + + Ok(written) + } + /// Writes the provided Rust value to this userspace pointer. /// /// Fails with [`EFAULT`] if the write happens on a bad address, or if the write goes out of -- cgit v1.2.3 From 9c804d9cf2dbe90cfde89c905b45aacbd07ee537 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:40 +0200 Subject: rust: debugfs: support for binary large objects Introduce support for read-only, write-only, and read-write binary files in Rust debugfs. This adds: - BinaryWriter and BinaryReader traits for writing to and reading from user slices in binary form. - New Dir methods: read_binary_file(), write_binary_file(), `read_write_binary_file`. - Corresponding FileOps implementations: BinaryReadFile, BinaryWriteFile, BinaryReadWriteFile. This allows kernel modules to expose arbitrary binary data through debugfs, with proper support for offsets and partial reads/writes. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Matthew Maurer Reviewed-by: Alice Ryhl Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs.rs | 66 +++++++++++++++++- rust/kernel/debugfs/file_ops.rs | 146 ++++++++++++++++++++++++++++++++++++++-- rust/kernel/debugfs/traits.rs | 68 ++++++++++++++++++- 3 files changed, 273 insertions(+), 7 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 381c23b3dd83..95cd3376ecbe 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -21,12 +21,15 @@ use core::mem::ManuallyDrop; use core::ops::Deref; mod traits; -pub use traits::{Reader, Writer}; +pub use traits::{BinaryReader, BinaryWriter, Reader, Writer}; mod callback_adapters; use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter}; mod file_ops; -use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile}; +use file_ops::{ + BinaryReadFile, BinaryReadWriteFile, BinaryWriteFile, FileOps, ReadFile, ReadWriteFile, + WriteFile, +}; #[cfg(CONFIG_DEBUG_FS)] mod entry; #[cfg(CONFIG_DEBUG_FS)] @@ -150,6 +153,32 @@ impl Dir { self.create_file(name, data, file_ops) } + /// Creates a read-only binary file in this directory. + /// + /// The file's contents are produced by invoking [`BinaryWriter::write_to_slice`] on the value + /// initialized by `data`. + /// + /// # Examples + /// + /// ``` + /// # use kernel::c_str; + /// # use kernel::debugfs::Dir; + /// # use kernel::prelude::*; + /// # let dir = Dir::new(c_str!("my_debugfs_dir")); + /// let file = KBox::pin_init(dir.read_binary_file(c_str!("foo"), [0x1, 0x2]), GFP_KERNEL)?; + /// # Ok::<(), Error>(()) + /// ``` + pub fn read_binary_file<'a, T, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + ) -> impl PinInit, E> + 'a + where + T: BinaryWriter + Send + Sync + 'static, + { + self.create_file(name, data, &T::FILE_OPS) + } + /// Creates a read-only file in this directory, with contents from a callback. /// /// `f` must be a function item or a non-capturing closure. @@ -206,6 +235,22 @@ impl Dir { self.create_file(name, data, file_ops) } + /// Creates a read-write binary file in this directory. + /// + /// Reading the file uses the [`BinaryWriter`] implementation. + /// Writing to the file uses the [`BinaryReader`] implementation. + pub fn read_write_binary_file<'a, T, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + ) -> impl PinInit, E> + 'a + where + T: BinaryWriter + BinaryReader + Send + Sync + 'static, + { + let file_ops = &>::FILE_OPS; + self.create_file(name, data, file_ops) + } + /// Creates a read-write file in this directory, with logic from callbacks. /// /// Reading from the file is handled by `f`. Writing to the file is handled by `w`. @@ -248,6 +293,23 @@ impl Dir { self.create_file(name, data, &T::FILE_OPS) } + /// Creates a write-only binary file in this directory. + /// + /// The file owns its backing data. Writing to the file uses the [`BinaryReader`] + /// implementation. + /// + /// The file is removed when the returned [`File`] is dropped. + pub fn write_binary_file<'a, T, E: 'a>( + &'a self, + name: &'a CStr, + data: impl PinInit + 'a, + ) -> impl PinInit, E> + 'a + where + T: BinaryReader + Send + Sync + 'static, + { + self.create_file(name, data, &T::FILE_OPS) + } + /// Creates a write-only file in this directory, with write logic from a callback. /// /// `w` must be a function item or a non-capturing closure. diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs index 50fead17b6f3..6c8928902a0b 100644 --- a/rust/kernel/debugfs/file_ops.rs +++ b/rust/kernel/debugfs/file_ops.rs @@ -1,13 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. -use super::{Reader, Writer}; +use super::{BinaryReader, BinaryWriter, Reader, Writer}; use crate::debugfs::callback_adapters::Adapter; +use crate::fs::file; use crate::prelude::*; use crate::seq_file::SeqFile; use crate::seq_print; use crate::uaccess::UserSlice; -use core::fmt::{Display, Formatter, Result}; +use core::fmt; use core::marker::PhantomData; #[cfg(CONFIG_DEBUG_FS)] @@ -65,8 +66,8 @@ impl Deref for FileOps { struct WriterAdapter(T); -impl<'a, T: Writer> Display for WriterAdapter<&'a T> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { +impl<'a, T: Writer> fmt::Display for WriterAdapter<&'a T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.write(f) } } @@ -245,3 +246,140 @@ impl WriteFile for T { unsafe { FileOps::new(operations, 0o200) } }; } + +extern "C" fn blob_read( + file: *mut bindings::file, + buf: *mut c_char, + count: usize, + ppos: *mut bindings::loff_t, +) -> isize { + // SAFETY: + // - `file` is a valid pointer to a `struct file`. + // - The type invariant of `FileOps` guarantees that `private_data` points to a valid `T`. + let this = unsafe { &*((*file).private_data.cast::()) }; + + // SAFETY: + // - `ppos` is a valid `file::Offset` pointer. + // - We have exclusive access to `ppos`. + let pos: &mut file::Offset = unsafe { &mut *ppos }; + + let mut writer = UserSlice::new(UserPtr::from_ptr(buf.cast()), count).writer(); + + let ret = || -> Result { + let written = this.write_to_slice(&mut writer, pos)?; + + Ok(written.try_into()?) + }(); + + match ret { + Ok(n) => n, + Err(e) => e.to_errno() as isize, + } +} + +/// Representation of [`FileOps`] for read only binary files. +pub(crate) trait BinaryReadFile { + const FILE_OPS: FileOps; +} + +impl BinaryReadFile for T { + const FILE_OPS: FileOps = { + let operations = bindings::file_operations { + read: Some(blob_read::), + llseek: Some(bindings::default_llseek), + open: Some(bindings::simple_open), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + + // SAFETY: + // - The private data of `struct inode` does always contain a pointer to a valid `T`. + // - `simple_open()` stores the `struct inode`'s private data in the private data of the + // corresponding `struct file`. + // - `blob_read()` re-creates a reference to `T` from the `struct file`'s private data. + // - `default_llseek()` does not access the `struct file`'s private data. + unsafe { FileOps::new(operations, 0o400) } + }; +} + +extern "C" fn blob_write( + file: *mut bindings::file, + buf: *const c_char, + count: usize, + ppos: *mut bindings::loff_t, +) -> isize { + // SAFETY: + // - `file` is a valid pointer to a `struct file`. + // - The type invariant of `FileOps` guarantees that `private_data` points to a valid `T`. + let this = unsafe { &*((*file).private_data.cast::()) }; + + // SAFETY: + // - `ppos` is a valid `file::Offset` pointer. + // - We have exclusive access to `ppos`. + let pos: &mut file::Offset = unsafe { &mut *ppos }; + + let mut reader = UserSlice::new(UserPtr::from_ptr(buf.cast_mut().cast()), count).reader(); + + let ret = || -> Result { + let read = this.read_from_slice(&mut reader, pos)?; + + Ok(read.try_into()?) + }(); + + match ret { + Ok(n) => n, + Err(e) => e.to_errno() as isize, + } +} + +/// Representation of [`FileOps`] for write only binary files. +pub(crate) trait BinaryWriteFile { + const FILE_OPS: FileOps; +} + +impl BinaryWriteFile for T { + const FILE_OPS: FileOps = { + let operations = bindings::file_operations { + write: Some(blob_write::), + llseek: Some(bindings::default_llseek), + open: Some(bindings::simple_open), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + + // SAFETY: + // - The private data of `struct inode` does always contain a pointer to a valid `T`. + // - `simple_open()` stores the `struct inode`'s private data in the private data of the + // corresponding `struct file`. + // - `blob_write()` re-creates a reference to `T` from the `struct file`'s private data. + // - `default_llseek()` does not access the `struct file`'s private data. + unsafe { FileOps::new(operations, 0o200) } + }; +} + +/// Representation of [`FileOps`] for read/write binary files. +pub(crate) trait BinaryReadWriteFile { + const FILE_OPS: FileOps; +} + +impl BinaryReadWriteFile for T { + const FILE_OPS: FileOps = { + let operations = bindings::file_operations { + read: Some(blob_read::), + write: Some(blob_write::), + llseek: Some(bindings::default_llseek), + open: Some(bindings::simple_open), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + + // SAFETY: + // - The private data of `struct inode` does always contain a pointer to a valid `T`. + // - `simple_open()` stores the `struct inode`'s private data in the private data of the + // corresponding `struct file`. + // - `blob_read()` re-creates a reference to `T` from the `struct file`'s private data. + // - `blob_write()` re-creates a reference to `T` from the `struct file`'s private data. + // - `default_llseek()` does not access the `struct file`'s private data. + unsafe { FileOps::new(operations, 0o600) } + }; +} diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index ab009eb254b3..bd38eb988d51 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -3,9 +3,11 @@ //! Traits for rendering or updating values exported to DebugFS. +use crate::fs::file; use crate::prelude::*; use crate::sync::Mutex; -use crate::uaccess::UserSliceReader; +use crate::transmute::{AsBytes, FromBytes}; +use crate::uaccess::{UserSliceReader, UserSliceWriter}; use core::fmt::{self, Debug, Formatter}; use core::str::FromStr; use core::sync::atomic::{ @@ -39,6 +41,44 @@ impl Writer for T { } } +/// Trait for types that can be written out as binary. +pub trait BinaryWriter { + /// Writes the binary form of `self` into `writer`. + /// + /// `offset` is the requested offset into the binary representation of `self`. + /// + /// On success, returns the number of bytes written in to `writer`. + fn write_to_slice( + &self, + writer: &mut UserSliceWriter, + offset: &mut file::Offset, + ) -> Result; +} + +// Base implementation for any `T: AsBytes`. +impl BinaryWriter for T { + fn write_to_slice( + &self, + writer: &mut UserSliceWriter, + offset: &mut file::Offset, + ) -> Result { + writer.write_slice_file(self.as_bytes(), offset) + } +} + +// Delegate for `Mutex`: Support a `T` with an outer mutex. +impl BinaryWriter for Mutex { + fn write_to_slice( + &self, + writer: &mut UserSliceWriter, + offset: &mut file::Offset, + ) -> Result { + let guard = self.lock(); + + guard.write_to_slice(writer, offset) + } +} + /// A trait for types that can be updated from a user slice. /// /// This works similarly to `FromStr`, but operates on a `UserSliceReader` rather than a &str. @@ -66,6 +106,32 @@ impl Reader for Mutex { } } +/// Trait for types that can be constructed from a binary representation. +pub trait BinaryReader { + /// Reads the binary form of `self` from `reader`. + /// + /// `offset` is the requested offset into the binary representation of `self`. + /// + /// On success, returns the number of bytes read from `reader`. + fn read_from_slice( + &self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result; +} + +impl BinaryReader for Mutex { + fn read_from_slice( + &self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result { + let mut this = self.lock(); + + reader.read_slice_file(this.as_bytes_mut(), offset) + } +} + macro_rules! impl_reader_for_atomic { ($(($atomic_type:ty, $int_type:ty)),*) => { $( -- cgit v1.2.3 From a9fca8a7b2c5078c00960866d2a512b8cea7ce3b Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:41 +0200 Subject: rust: debugfs: support blobs from smart pointers Extend Rust debugfs binary support to allow exposing data stored in common smart pointers and heap-allocated collections. - Implement BinaryWriter for Box, Pin>, Arc, and Vec. - Introduce BinaryReaderMut for mutable binary access with outer locks. - Implement BinaryReaderMut for Box, Vec, and base types. - Update BinaryReader to delegate to BinaryReaderMut for Mutex, Box, Pin> and Arc. This enables debugfs files to directly expose or update data stored inside heap-allocated, reference-counted, or lock-protected containers without manual dereferencing or locking. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Matthew Maurer Reviewed-by: Alice Ryhl Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs.rs | 2 +- rust/kernel/debugfs/traits.rs | 174 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 173 insertions(+), 3 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index 95cd3376ecbe..d2bc7550d81e 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -21,7 +21,7 @@ use core::mem::ManuallyDrop; use core::ops::Deref; mod traits; -pub use traits::{BinaryReader, BinaryWriter, Reader, Writer}; +pub use traits::{BinaryReader, BinaryReaderMut, BinaryWriter, Reader, Writer}; mod callback_adapters; use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter}; diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index bd38eb988d51..2c32ddf9826f 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -3,12 +3,15 @@ //! Traits for rendering or updating values exported to DebugFS. +use crate::alloc::Allocator; use crate::fs::file; use crate::prelude::*; +use crate::sync::Arc; use crate::sync::Mutex; use crate::transmute::{AsBytes, FromBytes}; use crate::uaccess::{UserSliceReader, UserSliceWriter}; use core::fmt::{self, Debug, Formatter}; +use core::ops::{Deref, DerefMut}; use core::str::FromStr; use core::sync::atomic::{ AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, AtomicU64, @@ -79,6 +82,72 @@ impl BinaryWriter for Mutex { } } +// Delegate for `Box`: Support a `Box` with no lock or an inner lock. +impl BinaryWriter for Box +where + T: BinaryWriter, + A: Allocator, +{ + fn write_to_slice( + &self, + writer: &mut UserSliceWriter, + offset: &mut file::Offset, + ) -> Result { + self.deref().write_to_slice(writer, offset) + } +} + +// Delegate for `Pin>`: Support a `Pin>` with no lock or an inner lock. +impl BinaryWriter for Pin> +where + T: BinaryWriter, + A: Allocator, +{ + fn write_to_slice( + &self, + writer: &mut UserSliceWriter, + offset: &mut file::Offset, + ) -> Result { + self.deref().write_to_slice(writer, offset) + } +} + +// Delegate for `Arc`: Support a `Arc` with no lock or an inner lock. +impl BinaryWriter for Arc +where + T: BinaryWriter, +{ + fn write_to_slice( + &self, + writer: &mut UserSliceWriter, + offset: &mut file::Offset, + ) -> Result { + self.deref().write_to_slice(writer, offset) + } +} + +// Delegate for `Vec`. +impl BinaryWriter for Vec +where + T: AsBytes, + A: Allocator, +{ + fn write_to_slice( + &self, + writer: &mut UserSliceWriter, + offset: &mut file::Offset, + ) -> Result { + let slice = self.as_slice(); + + // SAFETY: `T: AsBytes` allows us to treat `&[T]` as `&[u8]`. + let buffer = unsafe { + core::slice::from_raw_parts(slice.as_ptr().cast(), core::mem::size_of_val(slice)) + }; + + writer.write_slice_file(buffer, offset) + } +} + /// A trait for types that can be updated from a user slice. /// /// This works similarly to `FromStr`, but operates on a `UserSliceReader` rather than a &str. @@ -107,6 +176,73 @@ impl Reader for Mutex { } /// Trait for types that can be constructed from a binary representation. +/// +/// See also [`BinaryReader`] for interior mutability. +pub trait BinaryReaderMut { + /// Reads the binary form of `self` from `reader`. + /// + /// Same as [`BinaryReader::read_from_slice`], but takes a mutable reference. + /// + /// `offset` is the requested offset into the binary representation of `self`. + /// + /// On success, returns the number of bytes read from `reader`. + fn read_from_slice_mut( + &mut self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result; +} + +// Base implementation for any `T: AsBytes + FromBytes`. +impl BinaryReaderMut for T { + fn read_from_slice_mut( + &mut self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result { + reader.read_slice_file(self.as_bytes_mut(), offset) + } +} + +// Delegate for `Box`: Support a `Box` with an outer lock. +impl BinaryReaderMut for Box { + fn read_from_slice_mut( + &mut self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result { + self.deref_mut().read_from_slice_mut(reader, offset) + } +} + +// Delegate for `Vec`: Support a `Vec` with an outer lock. +impl BinaryReaderMut for Vec +where + T: AsBytes + FromBytes, + A: Allocator, +{ + fn read_from_slice_mut( + &mut self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result { + let slice = self.as_mut_slice(); + + // SAFETY: `T: AsBytes + FromBytes` allows us to treat `&mut [T]` as `&mut [u8]`. + let buffer = unsafe { + core::slice::from_raw_parts_mut( + slice.as_mut_ptr().cast(), + core::mem::size_of_val(slice), + ) + }; + + reader.read_slice_file(buffer, offset) + } +} + +/// Trait for types that can be constructed from a binary representation. +/// +/// See also [`BinaryReaderMut`] for the mutable version. pub trait BinaryReader { /// Reads the binary form of `self` from `reader`. /// @@ -120,7 +256,8 @@ pub trait BinaryReader { ) -> Result; } -impl BinaryReader for Mutex { +// Delegate for `Mutex`: Support a `T` with an outer `Mutex`. +impl BinaryReader for Mutex { fn read_from_slice( &self, reader: &mut UserSliceReader, @@ -128,7 +265,40 @@ impl BinaryReader for Mutex { ) -> Result { let mut this = self.lock(); - reader.read_slice_file(this.as_bytes_mut(), offset) + this.read_from_slice_mut(reader, offset) + } +} + +// Delegate for `Box`: Support a `Box` with an inner lock. +impl BinaryReader for Box { + fn read_from_slice( + &self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result { + self.deref().read_from_slice(reader, offset) + } +} + +// Delegate for `Pin>`: Support a `Pin>` with an inner lock. +impl BinaryReader for Pin> { + fn read_from_slice( + &self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result { + self.deref().read_from_slice(reader, offset) + } +} + +// Delegate for `Arc`: Support an `Arc` with an inner lock. +impl BinaryReader for Arc { + fn read_from_slice( + &self, + reader: &mut UserSliceReader, + offset: &mut file::Offset, + ) -> Result { + self.deref().read_from_slice(reader, offset) } } -- cgit v1.2.3 From 52af0c37964be5a685cfd7aa4127bdd29d629cc7 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:42 +0200 Subject: samples: rust: debugfs: add example for blobs Extend the Rust debugfs sample to demonstrate usage of binary file support. The example now shows how to expose both fixed-size arrays and dynamically sized vectors as binary blobs in debugfs. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Reviewed-by: Matthew Maurer Signed-off-by: Danilo Krummrich --- samples/rust/rust_debugfs.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs index d3f50f344856..c45b568d951b 100644 --- a/samples/rust/rust_debugfs.rs +++ b/samples/rust/rust_debugfs.rs @@ -38,6 +38,7 @@ use kernel::c_str; use kernel::debugfs::{Dir, File}; use kernel::new_mutex; use kernel::prelude::*; +use kernel::sizes::*; use kernel::sync::Mutex; use kernel::{acpi, device::Core, of, platform, str::CString, types::ARef}; @@ -62,6 +63,10 @@ struct RustDebugFs { counter: File, #[pin] inner: File>, + #[pin] + array_blob: File>, + #[pin] + vector_blob: File>>, } #[derive(Debug)] @@ -144,6 +149,14 @@ impl RustDebugFs { ), counter <- Self::build_counter(&debugfs), inner <- Self::build_inner(&debugfs), + array_blob <- debugfs.read_write_binary_file( + c_str!("array_blob"), + new_mutex!([0x62, 0x6c, 0x6f, 0x62]), + ), + vector_blob <- debugfs.read_write_binary_file( + c_str!("vector_blob"), + new_mutex!(kernel::kvec!(0x42; SZ_4K)?), + ), _debugfs: debugfs, pdev: pdev.into(), } -- cgit v1.2.3 From 35bd14d929af1b16f58d7b786c52187628427922 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:43 +0200 Subject: rust: debugfs: support binary large objects for ScopedDir Add support for creating binary debugfs files via ScopedDir. This mirrors the existing functionality for Dir, but without producing an owning handle -- files are automatically removed when the associated Scope is dropped. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Matthew Maurer Reviewed-by: Alice Ryhl Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index d2bc7550d81e..e8139d2e5730 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -530,6 +530,20 @@ impl<'data, 'dir> ScopedDir<'data, 'dir> { self.create_file(name, data, &T::FILE_OPS) } + /// Creates a read-only binary file in this directory. + /// + /// The file's contents are produced by invoking [`BinaryWriter::write_to_slice`]. + /// + /// This function does not produce an owning handle to the file. The created file is removed + /// when the [`Scope`] that this directory belongs to is dropped. + pub fn read_binary_file( + &self, + name: &CStr, + data: &'data T, + ) { + self.create_file(name, data, &T::FILE_OPS) + } + /// Creates a read-only file in this directory, with contents from a callback. /// /// The file contents are generated by calling `f` with `data`. @@ -567,6 +581,22 @@ impl<'data, 'dir> ScopedDir<'data, 'dir> { self.create_file(name, data, vtable) } + /// Creates a read-write binary file in this directory. + /// + /// Reading the file uses the [`BinaryWriter`] implementation on `data`. Writing to the file + /// uses the [`BinaryReader`] implementation on `data`. + /// + /// This function does not produce an owning handle to the file. The created file is removed + /// when the [`Scope`] that this directory belongs to is dropped. + pub fn read_write_binary_file( + &self, + name: &CStr, + data: &'data T, + ) { + let vtable = &>::FILE_OPS; + self.create_file(name, data, vtable) + } + /// Creates a read-write file in this directory, with logic from callbacks. /// /// Reading from the file is handled by `f`. Writing to the file is handled by `w`. @@ -606,6 +636,20 @@ impl<'data, 'dir> ScopedDir<'data, 'dir> { self.create_file(name, data, vtable) } + /// Creates a write-only binary file in this directory. + /// + /// Writing to the file uses the [`BinaryReader`] implementation on `data`. + /// + /// This function does not produce an owning handle to the file. The created file is removed + /// when the [`Scope`] that this directory belongs to is dropped. + pub fn write_binary_file( + &self, + name: &CStr, + data: &'data T, + ) { + self.create_file(name, data, &T::FILE_OPS) + } + /// Creates a write-only file in this directory, with write logic from a callback. /// /// Writing to the file is handled by `w`. -- cgit v1.2.3 From f656279afde16afee3ac163b90584ddceacb4e61 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 22 Oct 2025 16:30:44 +0200 Subject: samples: rust: debugfs_scoped: add example for blobs Extend the rust_debugfs_scoped sample to demonstrate how to export a large binary object through a ScopedDir. Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Reviewed-by: Matthew Maurer Signed-off-by: Danilo Krummrich --- samples/rust/rust_debugfs_scoped.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/samples/rust/rust_debugfs_scoped.rs b/samples/rust/rust_debugfs_scoped.rs index b0c4e76b123e..c80312cf168d 100644 --- a/samples/rust/rust_debugfs_scoped.rs +++ b/samples/rust/rust_debugfs_scoped.rs @@ -9,6 +9,7 @@ use core::sync::atomic::AtomicUsize; use kernel::debugfs::{Dir, Scope}; use kernel::prelude::*; +use kernel::sizes::*; use kernel::sync::Mutex; use kernel::{c_str, new_mutex, str::CString}; @@ -66,18 +67,22 @@ fn create_file_write( GFP_KERNEL, )?; } + let blob = KBox::pin_init(new_mutex!([0x42; SZ_4K]), GFP_KERNEL)?; let scope = KBox::pin_init( - mod_data - .device_dir - .scope(DeviceData { name, nums }, &file_name, |dev_data, dir| { + mod_data.device_dir.scope( + DeviceData { name, nums, blob }, + &file_name, + |dev_data, dir| { for (idx, val) in dev_data.nums.iter().enumerate() { let Ok(name) = CString::try_from_fmt(fmt!("{idx}")) else { return; }; dir.read_write_file(&name, val); } - }), + dir.read_write_binary_file(c_str!("blob"), &dev_data.blob); + }, + ), GFP_KERNEL, )?; (*mod_data.devices.lock()).push(scope, GFP_KERNEL)?; @@ -110,6 +115,7 @@ impl ModuleData { struct DeviceData { name: CString, nums: KVec, + blob: Pin>>, } fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit> + '_ { -- cgit v1.2.3 From 1bf5b90cd2f984e5d6ff6fd30d5d85f9f579b6f0 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 3 Nov 2025 21:39:18 +0100 Subject: rust: auxiliary: fix false positive warning for missing a safety comment Some older (yet supported) versions of clippy throw a false positive warning for missing a safety comment when the safety comment is on a multiline statement. warning: unsafe block missing a safety comment --> rust/kernel/auxiliary.rs:351:22 | 351 | Self(unsafe { NonNull::new_unchecked(adev) }), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider adding a safety comment on the preceding line = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks = note: requested on the command line with `-W clippy::undocumented-unsafe-blocks` warning: 1 warning emitted Fix this by placing the safety comment right on top of the same line introducing the unsafe block. Fixes: e4e679c8608e ("rust: auxiliary: unregister on parent device unbind") Reviewed-by: Alice Ryhl Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20251103203932.2361660-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index cc67fa5ddde3..618eeeec2bd0 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -341,13 +341,12 @@ impl Registration { return Err(Error::from_errno(ret)); } - // SAFETY: `adev` is guaranteed to be non-null, since the `KBox` was allocated - // successfully. - // // INVARIANT: The device will remain registered until `auxiliary_device_delete()` is // called, which happens in `Self::drop()`. Ok(Devres::new( parent, + // SAFETY: `adev` is guaranteed to be non-null, since the `KBox` was allocated + // successfully. Self(unsafe { NonNull::new_unchecked(adev) }), )) }) -- cgit v1.2.3 From 1f7b01661f5f62211ca079afee922e1b44e3ae5d Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 3 Nov 2025 21:30:12 +0100 Subject: rust: pci: get rid of redundant Result in IRQ methods Currently request_irq() returns Result, Error> + 'a> which may carry an error in the Result or the initializer; the same is true for request_threaded_irq(). Use pin_init::pin_init_scope() to get rid of this redundancy. Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251103203053.2348783-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/pci/irq.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index 782a524fe11c..03f2de559a8a 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -175,10 +175,12 @@ impl Device { flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, - ) -> Result, Error> + 'a> { - let request = vector.try_into()?; + ) -> impl PinInit, Error> + 'a { + pin_init::pin_init_scope(move || { + let request = vector.try_into()?; - Ok(irq::Registration::::new(request, flags, name, handler)) + Ok(irq::Registration::::new(request, flags, name, handler)) + }) } /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. @@ -188,12 +190,14 @@ impl Device { flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, - ) -> Result, Error> + 'a> { - let request = vector.try_into()?; - - Ok(irq::ThreadedRegistration::::new( - request, flags, name, handler, - )) + ) -> impl PinInit, Error> + 'a { + pin_init::pin_init_scope(move || { + let request = vector.try_into()?; + + Ok(irq::ThreadedRegistration::::new( + request, flags, name, handler, + )) + }) } /// Allocate IRQ vectors for this PCI device with automatic cleanup. -- cgit v1.2.3 From b892ed360de8227d700ef010b08564f87ad3a7ce Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 3 Nov 2025 21:30:13 +0100 Subject: rust: platform: get rid of redundant Result in IRQ methods Currently request_irq_by_index() returns Result, Error> + 'a> which may carry an error in the Result or the initializer; the same is true for the other IRQ methods. Use pin_init::pin_init_scope() to get rid of this redundancy. Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251103203053.2348783-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/platform.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 8f7522c4cf89..f4b617c570be 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -301,15 +301,17 @@ macro_rules! define_irq_accessor_by_index { index: u32, name: &'static CStr, handler: impl PinInit + 'a, - ) -> Result, Error> + 'a> { - let request = self.$request_fn(index)?; - - Ok(irq::$reg_type::::new( - request, - flags, - name, - handler, - )) + ) -> impl PinInit, Error> + 'a { + pin_init::pin_init_scope(move || { + let request = self.$request_fn(index)?; + + Ok(irq::$reg_type::::new( + request, + flags, + name, + handler, + )) + }) } }; } @@ -325,18 +327,20 @@ macro_rules! define_irq_accessor_by_name { pub fn $fn_name<'a, T: irq::$handler_trait + 'static>( &'a self, flags: irq::Flags, - irq_name: &CStr, + irq_name: &'a CStr, name: &'static CStr, handler: impl PinInit + 'a, - ) -> Result, Error> + 'a> { - let request = self.$request_fn(irq_name)?; - - Ok(irq::$reg_type::::new( - request, - flags, - name, - handler, - )) + ) -> impl PinInit, Error> + 'a { + pin_init::pin_init_scope(move || { + let request = self.$request_fn(irq_name)?; + + Ok(irq::$reg_type::::new( + request, + flags, + name, + handler, + )) + }) } }; } -- cgit v1.2.3 From 0bc605713f16ad77e0a3f30f992aa3794c381372 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 7 Nov 2025 10:16:07 +0100 Subject: rust: debugfs: Implement BinaryReader for Mutex only when T is Unpin Commit da123f0ee40f ("rust: lock: guard: Add T: Unpin bound to DerefMut") from tip/master adds an Unpin bound to T for Mutex, hence also restrict the implementation of BinaryReader for Mutex accordingly. Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/all/20251107134144.117905bd@canb.auug.org.au/ Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251107091612.2557480-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index 2c32ddf9826f..82441ac8adaa 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -257,7 +257,7 @@ pub trait BinaryReader { } // Delegate for `Mutex`: Support a `T` with an outer `Mutex`. -impl BinaryReader for Mutex { +impl BinaryReader for Mutex { fn read_from_slice( &self, reader: &mut UserSliceReader, -- cgit v1.2.3 From 9d39842f6afcd0438c8353a9764d624eef2d160a Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Tue, 4 Nov 2025 14:32:54 +0100 Subject: rust: io: cleanup imports and use "kernel vertical" style Commit 46f045db5a94 ("rust: Add read_poll_timeout_atomic function") initiated the first import change in the I/O module using the agreed "kernel vertical" import style [1]. For consistency throughout the module, adjust all other imports accordingly. While at it, drop unnecessary imports covered by prelude::*. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports [1] Reviewed-by: Zhi Wang Link: https://patch.msgid.link/20251104133301.59402-1-dakr@kernel.org [ Use prelude::* in io/poll.rs. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 6 ++++-- rust/kernel/io/mem.rs | 28 ++++++++++++++++++---------- rust/kernel/io/poll.rs | 2 +- rust/kernel/io/resource.rs | 16 ++++++++++------ 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index ee182b0b5452..1aa9495f7774 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -4,8 +4,10 @@ //! //! C header: [`include/asm-generic/io.h`](srctree/include/asm-generic/io.h) -use crate::error::{code::EINVAL, Result}; -use crate::{bindings, build_assert, ffi::c_void}; +use crate::{ + bindings, + prelude::*, // +}; pub mod mem; pub mod poll; diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index 59096d579644..b03b82cd531b 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -4,16 +4,24 @@ use core::ops::Deref; -use crate::c_str; -use crate::device::Bound; -use crate::device::Device; -use crate::devres::Devres; -use crate::io; -use crate::io::resource::Region; -use crate::io::resource::Resource; -use crate::io::Io; -use crate::io::IoRaw; -use crate::prelude::*; +use crate::{ + c_str, + device::{ + Bound, + Device, // + }, + devres::Devres, + io::{ + self, + resource::{ + Region, + Resource, // + }, + Io, + IoRaw, // + }, + prelude::*, +}; /// An IO request for a specific device and resource. pub struct IoRequest<'a> { diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs index ebc29023d59d..b1a2570364f4 100644 --- a/rust/kernel/io/poll.rs +++ b/rust/kernel/io/poll.rs @@ -5,7 +5,7 @@ //! C header: [`include/linux/iopoll.h`](srctree/include/linux/iopoll.h). use crate::{ - error::{code::*, Result}, + prelude::*, processor::cpu_relax, task::might_sleep, time::{ diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index bea3ee0ed87b..81220493a7f3 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -5,12 +5,16 @@ //! //! C header: [`include/linux/ioport.h`](srctree/include/linux/ioport.h) -use core::ops::Deref; -use core::ptr::NonNull; - -use crate::prelude::*; -use crate::str::{CStr, CString}; -use crate::types::Opaque; +use core::{ + ops::Deref, + ptr::NonNull, // +}; + +use crate::{ + prelude::*, + str::CString, + types::Opaque, // +}; /// Resource Size type. /// -- cgit v1.2.3 From d8407396f128d8bf4d06282b636df3f26db208c1 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 5 Nov 2025 13:03:28 +0100 Subject: rust: pci: use "kernel vertical" style for imports Convert all imports in the PCI Rust module to use "kernel vertical" style. With this subsequent patches neither introduce unrelated changes nor leave an inconsistent import pattern. While at it, drop unnecessary imports covered by prelude::*. Link: https://docs.kernel.org/rust/coding-guidelines.html#imports Reviewed-by: Zhi Wang Link: https://patch.msgid.link/20251105120352.77603-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 35 +++++++++++++++++++++++++++-------- rust/kernel/pci/id.rs | 5 ++++- rust/kernel/pci/io.rs | 13 ++++++++----- rust/kernel/pci/irq.rs | 14 +++++++++----- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index b68ef4e575fc..410b79d46632 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -5,27 +5,46 @@ //! C header: [`include/linux/pci.h`](srctree/include/linux/pci.h) use crate::{ - bindings, container_of, device, - device_id::{RawDeviceId, RawDeviceIdIndex}, + bindings, + container_of, + device, + device_id::{ + RawDeviceId, + RawDeviceIdIndex, // + }, driver, - error::{from_result, to_result, Result}, + error::{ + from_result, + to_result, // + }, + prelude::*, str::CStr, types::Opaque, - ThisModule, + ThisModule, // }; use core::{ marker::PhantomData, - ptr::{addr_of_mut, NonNull}, + ptr::{ + addr_of_mut, + NonNull, // + }, }; -use kernel::prelude::*; mod id; mod io; mod irq; -pub use self::id::{Class, ClassMask, Vendor}; +pub use self::id::{ + Class, + ClassMask, + Vendor, // +}; pub use self::io::Bar; -pub use self::irq::{IrqType, IrqTypes, IrqVector}; +pub use self::irq::{ + IrqType, + IrqTypes, + IrqVector, // +}; /// An adapter for the registration of PCI drivers. pub struct Adapter(T); diff --git a/rust/kernel/pci/id.rs b/rust/kernel/pci/id.rs index 7f2a7f57507f..a1de70b2176a 100644 --- a/rust/kernel/pci/id.rs +++ b/rust/kernel/pci/id.rs @@ -4,7 +4,10 @@ //! //! This module contains PCI class codes, Vendor IDs, and supporting types. -use crate::{bindings, error::code::EINVAL, error::Error, prelude::*}; +use crate::{ + bindings, + prelude::*, // +}; use core::fmt; /// PCI device class codes. diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 3684276b326b..0d55c3139b6f 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -4,14 +4,17 @@ use super::Device; use crate::{ - bindings, device, + bindings, + device, devres::Devres, - io::{Io, IoRaw}, - str::CStr, - sync::aref::ARef, + io::{ + Io, + IoRaw, // + }, + prelude::*, + sync::aref::ARef, // }; use core::ops::Deref; -use kernel::prelude::*; /// A PCI BAR to perform I/O-Operations on. /// diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index 03f2de559a8a..d9230e105541 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -4,16 +4,20 @@ use super::Device; use crate::{ - bindings, device, + bindings, + device, device::Bound, devres, - error::{to_result, Result}, - irq::{self, IrqRequest}, + error::to_result, + irq::{ + self, + IrqRequest, // + }, + prelude::*, str::CStr, - sync::aref::ARef, + sync::aref::ARef, // }; use core::ops::RangeInclusive; -use kernel::prelude::*; /// IRQ type flags for PCI interrupt allocation. #[derive(Debug, Clone, Copy)] -- cgit v1.2.3 From f7afdc473794ab71d38475755be72dc95e361c31 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 3 Nov 2025 20:06:49 +0100 Subject: rust: dma: make use of start_ptr() and start_ptr_mut() Using start_ptr() and start_ptr_mut() has the advantage that we inherit the requirements the a mutable or immutable reference from those methods. Hence, use them instead of self.cpu_addr. Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251103190655.2326191-1-dakr@kernel.org [ Keep using self.cpu_addr in item_from_index(). - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/dma.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index 4e0af3e1a3b9..ff2014cd39d1 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -505,7 +505,7 @@ impl CoherentAllocation { // data is also guaranteed by the safety requirements of the function. // - `offset + count` can't overflow since it is smaller than `self.count` and we've checked // that `self.count` won't overflow early in the constructor. - Ok(unsafe { core::slice::from_raw_parts(self.cpu_addr.add(offset), count) }) + Ok(unsafe { core::slice::from_raw_parts(self.start_ptr().add(offset), count) }) } /// Performs the same functionality as [`CoherentAllocation::as_slice`], except that a mutable @@ -525,7 +525,7 @@ impl CoherentAllocation { // data is also guaranteed by the safety requirements of the function. // - `offset + count` can't overflow since it is smaller than `self.count` and we've checked // that `self.count` won't overflow early in the constructor. - Ok(unsafe { core::slice::from_raw_parts_mut(self.cpu_addr.add(offset), count) }) + Ok(unsafe { core::slice::from_raw_parts_mut(self.start_ptr_mut().add(offset), count) }) } /// Writes data to the region starting from `offset`. `offset` is in units of `T`, not the @@ -557,7 +557,11 @@ impl CoherentAllocation { // - `offset + count` can't overflow since it is smaller than `self.count` and we've checked // that `self.count` won't overflow early in the constructor. unsafe { - core::ptr::copy_nonoverlapping(src.as_ptr(), self.cpu_addr.add(offset), src.len()) + core::ptr::copy_nonoverlapping( + src.as_ptr(), + self.start_ptr_mut().add(offset), + src.len(), + ) }; Ok(()) } @@ -637,7 +641,7 @@ impl Drop for CoherentAllocation { bindings::dma_free_attrs( self.dev.as_raw(), size, - self.cpu_addr.cast(), + self.start_ptr_mut().cast(), self.dma_handle, self.dma_attrs.as_raw(), ) -- cgit v1.2.3 From ededb7bcdfdbcfbb7af93e3a543165a9553e1683 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 3 Nov 2025 20:06:50 +0100 Subject: rust: dma: use NonNull instead of *mut T In struct CoherentAllocation, use NonNull instead of a raw *mut T for the CPU address; the CPU address of a valid CoherentAllocation won't ever be NULL. Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251103190655.2326191-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/dma.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index ff2014cd39d1..84d3c67269e8 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -12,6 +12,7 @@ use crate::{ sync::aref::ARef, transmute::{AsBytes, FromBytes}, }; +use core::ptr::NonNull; /// DMA address type. /// @@ -358,7 +359,7 @@ pub struct CoherentAllocation { dev: ARef, dma_handle: DmaAddress, count: usize, - cpu_addr: *mut T, + cpu_addr: NonNull, dma_attrs: Attrs, } @@ -392,7 +393,7 @@ impl CoherentAllocation { .ok_or(EOVERFLOW)?; let mut dma_handle = 0; // SAFETY: Device pointer is guaranteed as valid by the type invariant on `Device`. - let ret = unsafe { + let addr = unsafe { bindings::dma_alloc_attrs( dev.as_raw(), size, @@ -401,9 +402,7 @@ impl CoherentAllocation { dma_attrs.as_raw(), ) }; - if ret.is_null() { - return Err(ENOMEM); - } + let addr = NonNull::new(addr).ok_or(ENOMEM)?; // INVARIANT: // - We just successfully allocated a coherent region which is accessible for // `count` elements, hence the cpu address is valid. We also hold a refcounted reference @@ -414,7 +413,7 @@ impl CoherentAllocation { dev: dev.into(), dma_handle, count, - cpu_addr: ret.cast::(), + cpu_addr: addr.cast(), dma_attrs, }) } @@ -446,13 +445,13 @@ impl CoherentAllocation { /// Returns the base address to the allocated region in the CPU's virtual address space. pub fn start_ptr(&self) -> *const T { - self.cpu_addr + self.cpu_addr.as_ptr() } /// Returns the base address to the allocated region in the CPU's virtual address space as /// a mutable pointer. pub fn start_ptr_mut(&mut self) -> *mut T { - self.cpu_addr + self.cpu_addr.as_ptr() } /// Returns a DMA handle which may be given to the device as the DMA address base of @@ -580,7 +579,7 @@ impl CoherentAllocation { // and we've just checked that the range and index is within bounds. // - `offset` can't overflow since it is smaller than `self.count` and we've checked // that `self.count` won't overflow early in the constructor. - Ok(unsafe { self.cpu_addr.add(offset) }) + Ok(unsafe { self.cpu_addr.as_ptr().add(offset) }) } /// Reads the value of `field` and ensures that its type is [`FromBytes`]. -- cgit v1.2.3 From 919b72922717e396be9435c83916b9969505bd23 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 12 Nov 2025 09:48:32 +0000 Subject: rust: io: define ResourceSize as resource_size_t These typedefs are always equivalent so this should not change anything, but the code makes a lot more sense like this. Cc: stable@vger.kernel.org Signed-off-by: Alice Ryhl Fixes: 493fc33ec252 ("rust: io: add resource abstraction") Link: https://patch.msgid.link/20251112-resource-phys-typedefs-v2-1-538307384f82@google.com Signed-off-by: Danilo Krummrich --- rust/kernel/io/resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index 81220493a7f3..e294af96b374 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -20,7 +20,7 @@ use crate::{ /// /// This is a type alias to either `u32` or `u64` depending on the config option /// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. -pub type ResourceSize = bindings::phys_addr_t; +pub type ResourceSize = bindings::resource_size_t; /// A region allocated from a parent [`Resource`]. /// -- cgit v1.2.3 From dfd67993044f507ba8fd6ee9956f923ba4b7e851 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 12 Nov 2025 09:48:33 +0000 Subject: rust: io: move ResourceSize to top-level io module Resource sizes are a general concept for dealing with physical addresses, and not specific to the Resource type, which is just one way to access physical addresses. Thus, move the typedef to the io module. Still keep a re-export under resource. This avoids this commit from being a flag-day, but I also think it's a useful re-export in general so that you can import use kernel::io::resource::{Resource, ResourceSize}; instead of having to write use kernel::io::{ resource::Resource, ResourceSize, }; in the specific cases where you need ResourceSize because you are using the Resource type. Therefore I think it makes sense to keep this re-export indefinitely and it is *not* intended as a temporary re-export for migration purposes. Cc: stable@vger.kernel.org # for v6.18 [1] Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251112-resource-phys-typedefs-v2-2-538307384f82@google.com Link: https://lore.kernel.org/all/20251112-resource-phys-typedefs-v2-0-538307384f82@google.com/ [1] Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 6 ++++++ rust/kernel/io/resource.rs | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 1aa9495f7774..b8d0fb27f6ae 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -15,6 +15,12 @@ pub mod resource; pub use resource::Resource; +/// Resource Size type. +/// +/// This is a type alias to either `u32` or `u64` depending on the config option +/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. +pub type ResourceSize = bindings::resource_size_t; + /// Raw representation of an MMIO region. /// /// By itself, the existence of an instance of this structure does not provide any guarantees that diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index e294af96b374..eb25cf9b4c90 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -16,11 +16,7 @@ use crate::{ types::Opaque, // }; -/// Resource Size type. -/// -/// This is a type alias to either `u32` or `u64` depending on the config option -/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. -pub type ResourceSize = bindings::resource_size_t; +pub use super::ResourceSize; /// A region allocated from a parent [`Resource`]. /// -- cgit v1.2.3 From ee2776e54b2666f99ef5e3ad0b60889e1500dada Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 12 Nov 2025 09:48:34 +0000 Subject: rust: scatterlist: import ResourceSize from kernel::io Now that ResourceSize has been moved to kernel::io, import it from the io module instead of the io::resource sub-module. It makes sense in this case since the dma_len isn't really related to the Resource type even though both are sizes of allocations in physical ram. Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251112-resource-phys-typedefs-v2-3-538307384f82@google.com Signed-off-by: Danilo Krummrich --- rust/kernel/scatterlist.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/scatterlist.rs b/rust/kernel/scatterlist.rs index 9709dff60b5a..196fdb9a75e7 100644 --- a/rust/kernel/scatterlist.rs +++ b/rust/kernel/scatterlist.rs @@ -35,7 +35,7 @@ use crate::{ device::{Bound, Device}, devres::Devres, dma, error, - io::resource::ResourceSize, + io::ResourceSize, page, prelude::*, types::{ARef, Opaque}, -- cgit v1.2.3 From dd6ff5cf56fb183fce605ca6a5bfce228cd8888b Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 12 Nov 2025 09:48:35 +0000 Subject: rust: io: add typedef for phys_addr_t The C typedef phys_addr_t is missing an analogue in Rust, meaning that we end up using bindings::phys_addr_t or ResourceSize as a replacement in various places throughout the kernel. Fix that by introducing a new typedef on the Rust side. Place it next to the existing ResourceSize typedef since they're quite related to each other. Cc: stable@vger.kernel.org # for v6.18 [1] Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251112-resource-phys-typedefs-v2-4-538307384f82@google.com Link: https://lore.kernel.org/all/20251112-resource-phys-typedefs-v2-0-538307384f82@google.com/ [1] Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 18 +++++++++++++++--- rust/kernel/io.rs | 20 +++++++++++++++++--- rust/kernel/io/resource.rs | 9 ++++++--- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 10a6a1789854..e01e0d36702d 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -52,8 +52,20 @@ struct Inner { /// # Examples /// /// ```no_run -/// # use kernel::{bindings, device::{Bound, Device}, devres::Devres, io::{Io, IoRaw}}; -/// # use core::ops::Deref; +/// use kernel::{ +/// bindings, +/// device::{ +/// Bound, +/// Device, +/// }, +/// devres::Devres, +/// io::{ +/// Io, +/// IoRaw, +/// PhysAddr, +/// }, +/// }; +/// use core::ops::Deref; /// /// // See also [`pci::Bar`] for a real example. /// struct IoMem(IoRaw); @@ -66,7 +78,7 @@ struct Inner { /// unsafe fn new(paddr: usize) -> Result{ /// // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is /// // valid for `ioremap`. -/// let addr = unsafe { bindings::ioremap(paddr as bindings::phys_addr_t, SIZE) }; +/// let addr = unsafe { bindings::ioremap(paddr as PhysAddr, SIZE) }; /// if addr.is_null() { /// return Err(ENOMEM); /// } diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index b8d0fb27f6ae..98e8b84e68d1 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -15,6 +15,12 @@ pub mod resource; pub use resource::Resource; +/// Physical address type. +/// +/// This is a type alias to either `u32` or `u64` depending on the config option +/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. +pub type PhysAddr = bindings::phys_addr_t; + /// Resource Size type. /// /// This is a type alias to either `u32` or `u64` depending on the config option @@ -70,8 +76,16 @@ impl IoRaw { /// # Examples /// /// ```no_run -/// # use kernel::{bindings, ffi::c_void, io::{Io, IoRaw}}; -/// # use core::ops::Deref; +/// use kernel::{ +/// bindings, +/// ffi::c_void, +/// io::{ +/// Io, +/// IoRaw, +/// PhysAddr, +/// }, +/// }; +/// use core::ops::Deref; /// /// // See also [`pci::Bar`] for a real example. /// struct IoMem(IoRaw); @@ -84,7 +98,7 @@ impl IoRaw { /// unsafe fn new(paddr: usize) -> Result{ /// // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is /// // valid for `ioremap`. -/// let addr = unsafe { bindings::ioremap(paddr as bindings::phys_addr_t, SIZE) }; +/// let addr = unsafe { bindings::ioremap(paddr as PhysAddr, SIZE) }; /// if addr.is_null() { /// return Err(ENOMEM); /// } diff --git a/rust/kernel/io/resource.rs b/rust/kernel/io/resource.rs index eb25cf9b4c90..56cfde97ce87 100644 --- a/rust/kernel/io/resource.rs +++ b/rust/kernel/io/resource.rs @@ -16,7 +16,10 @@ use crate::{ types::Opaque, // }; -pub use super::ResourceSize; +pub use super::{ + PhysAddr, + ResourceSize, // +}; /// A region allocated from a parent [`Resource`]. /// @@ -97,7 +100,7 @@ impl Resource { /// the region, or a part of it, is already in use. pub fn request_region( &self, - start: ResourceSize, + start: PhysAddr, size: ResourceSize, name: CString, flags: Flags, @@ -131,7 +134,7 @@ impl Resource { } /// Returns the start address of the resource. - pub fn start(&self) -> ResourceSize { + pub fn start(&self) -> PhysAddr { let inner = self.0.get(); // SAFETY: Safe as per the invariants of `Resource`. unsafe { (*inner).start } -- cgit v1.2.3 From 57c5bd9aee944b0f5c2ab314e10a86fae51f7bf2 Mon Sep 17 00:00:00 2001 From: Igor Korotin Date: Sun, 16 Nov 2025 16:21:44 +0000 Subject: rust: i2c: add basic I2C device and driver abstractions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the core abstractions needed for I2C drivers, including: * `i2c::Driver` — the trait drivers must implement, including `probe` * `i2c::I2cClient` — a safe wrapper around `struct i2c_client` * `i2c::Adapter` — implements `driver::RegistrationOps` to hook into the generic `driver::Registration` machinery * `i2c::DeviceId` — a `RawDeviceIdIndex` implementation for I2C device IDs Acked-by: Wolfram Sang Signed-off-by: Igor Korotin Link: https://patch.msgid.link/20251116162144.171469-1-igor.korotin.linux@gmail.com [ Remove unnecessary safety comment; fix rustdoc `Device` -> `I2cClient`. - Danilo ] Signed-off-by: Danilo Krummrich --- MAINTAINERS | 8 + rust/bindings/bindings_helper.h | 1 + rust/kernel/i2c.rs | 434 ++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 4 files changed, 445 insertions(+) create mode 100644 rust/kernel/i2c.rs diff --git a/MAINTAINERS b/MAINTAINERS index 3da2c26a796b..a8dfde2f5633 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11743,6 +11743,14 @@ F: include/linux/i2c.h F: include/uapi/linux/i2c-*.h F: include/uapi/linux/i2c.h +I2C SUBSYSTEM [RUST] +M: Igor Korotin +R: Danilo Krummrich +R: Daniel Almeida +L: rust-for-linux@vger.kernel.org +S: Maintained +F: rust/kernel/i2c.rs + I2C SUBSYSTEM HOST DRIVERS M: Andi Shyti L: linux-i2c@vger.kernel.org diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index a79fd111f886..f92abb578b56 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs new file mode 100644 index 000000000000..c461686a636a --- /dev/null +++ b/rust/kernel/i2c.rs @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! I2C Driver subsystem + +// I2C Driver abstractions. +use crate::{ + acpi, + container_of, + device, + device_id::{ + RawDeviceId, + RawDeviceIdIndex, // + }, + driver, + error::*, + of, + prelude::*, + types::{ + AlwaysRefCounted, + Opaque, // + }, // +}; + +use core::{ + marker::PhantomData, + ptr::NonNull, // +}; + +/// An I2C device id table. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct DeviceId(bindings::i2c_device_id); + +impl DeviceId { + const I2C_NAME_SIZE: usize = 20; + + /// Create a new device id from an I2C 'id' string. + #[inline(always)] + pub const fn new(id: &'static CStr) -> Self { + build_assert!( + id.len_with_nul() <= Self::I2C_NAME_SIZE, + "ID exceeds 20 bytes" + ); + let src = id.as_bytes_with_nul(); + let mut i2c: bindings::i2c_device_id = pin_init::zeroed(); + let mut i = 0; + while i < src.len() { + i2c.name[i] = src[i]; + i += 1; + } + + Self(i2c) + } +} + +// SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `i2c_device_id` and does not add +// additional invariants, so it's safe to transmute to `RawType`. +unsafe impl RawDeviceId for DeviceId { + type RawType = bindings::i2c_device_id; +} + +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. +unsafe impl RawDeviceIdIndex for DeviceId { + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::i2c_device_id, driver_data); + + fn index(&self) -> usize { + self.0.driver_data + } +} + +/// IdTable type for I2C +pub type IdTable = &'static dyn kernel::device_id::IdTable; + +/// Create a I2C `IdTable` with its alias for modpost. +#[macro_export] +macro_rules! i2c_device_table { + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { + const $table_name: $crate::device_id::IdArray< + $crate::i2c::DeviceId, + $id_info_type, + { $table_data.len() }, + > = $crate::device_id::IdArray::new($table_data); + + $crate::module_device_table!("i2c", $module_table_name, $table_name); + }; +} + +/// An adapter for the registration of I2C drivers. +pub struct Adapter(T); + +// SAFETY: A call to `unregister` for a given instance of `RegType` is guaranteed to be valid if +// a preceding call to `register` has been successful. +unsafe impl driver::RegistrationOps for Adapter { + type RegType = bindings::i2c_driver; + + unsafe fn register( + idrv: &Opaque, + name: &'static CStr, + module: &'static ThisModule, + ) -> Result { + build_assert!( + T::ACPI_ID_TABLE.is_some() || T::OF_ID_TABLE.is_some() || T::I2C_ID_TABLE.is_some(), + "At least one of ACPI/OF/Legacy tables must be present when registering an i2c driver" + ); + + let i2c_table = match T::I2C_ID_TABLE { + Some(table) => table.as_ptr(), + None => core::ptr::null(), + }; + + let of_table = match T::OF_ID_TABLE { + Some(table) => table.as_ptr(), + None => core::ptr::null(), + }; + + let acpi_table = match T::ACPI_ID_TABLE { + Some(table) => table.as_ptr(), + None => core::ptr::null(), + }; + + // SAFETY: It's safe to set the fields of `struct i2c_client` on initialization. + unsafe { + (*idrv.get()).driver.name = name.as_char_ptr(); + (*idrv.get()).probe = Some(Self::probe_callback); + (*idrv.get()).remove = Some(Self::remove_callback); + (*idrv.get()).shutdown = Some(Self::shutdown_callback); + (*idrv.get()).id_table = i2c_table; + (*idrv.get()).driver.of_match_table = of_table; + (*idrv.get()).driver.acpi_match_table = acpi_table; + } + + // SAFETY: `idrv` is guaranteed to be a valid `RegType`. + to_result(unsafe { bindings::i2c_register_driver(module.0, idrv.get()) }) + } + + unsafe fn unregister(idrv: &Opaque) { + // SAFETY: `idrv` is guaranteed to be a valid `RegType`. + unsafe { bindings::i2c_del_driver(idrv.get()) } + } +} + +impl Adapter { + extern "C" fn probe_callback(idev: *mut bindings::i2c_client) -> kernel::ffi::c_int { + // SAFETY: The I2C bus only ever calls the probe callback with a valid pointer to a + // `struct i2c_client`. + // + // INVARIANT: `idev` is valid for the duration of `probe_callback()`. + let idev = unsafe { &*idev.cast::>() }; + + let info = + Self::i2c_id_info(idev).or_else(|| ::id_info(idev.as_ref())); + + from_result(|| { + let data = T::probe(idev, info); + + idev.as_ref().set_drvdata(data)?; + Ok(0) + }) + } + + extern "C" fn remove_callback(idev: *mut bindings::i2c_client) { + // SAFETY: `idev` is a valid pointer to a `struct i2c_client`. + let idev = unsafe { &*idev.cast::>() }; + + // SAFETY: `remove_callback` is only ever called after a successful call to + // `probe_callback`, hence it's guaranteed that `I2cClient::set_drvdata()` has been called + // and stored a `Pin>`. + let data = unsafe { idev.as_ref().drvdata_obtain::() }; + + T::unbind(idev, data.as_ref()); + } + + extern "C" fn shutdown_callback(idev: *mut bindings::i2c_client) { + // SAFETY: `shutdown_callback` is only ever called for a valid `idev` + let idev = unsafe { &*idev.cast::>() }; + + // SAFETY: `shutdown_callback` is only ever called after a successful call to + // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called + // and stored a `Pin>`. + let data = unsafe { idev.as_ref().drvdata_obtain::() }; + + T::shutdown(idev, data.as_ref()); + } + + /// The [`i2c::IdTable`] of the corresponding driver. + fn i2c_id_table() -> Option::IdInfo>> { + T::I2C_ID_TABLE + } + + /// Returns the driver's private data from the matching entry in the [`i2c::IdTable`], if any. + /// + /// If this returns `None`, it means there is no match with an entry in the [`i2c::IdTable`]. + fn i2c_id_info(dev: &I2cClient) -> Option<&'static ::IdInfo> { + let table = Self::i2c_id_table()?; + + // SAFETY: + // - `table` has static lifetime, hence it's valid for reads + // - `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`. + let raw_id = unsafe { bindings::i2c_match_id(table.as_ptr(), dev.as_raw()) }; + + if raw_id.is_null() { + return None; + } + + // SAFETY: `DeviceId` is a `#[repr(transparent)` wrapper of `struct i2c_device_id` and + // does not add additional invariants, so it's safe to transmute. + let id = unsafe { &*raw_id.cast::() }; + + Some(table.info(::index(id))) + } +} + +impl driver::Adapter for Adapter { + type IdInfo = T::IdInfo; + + fn of_id_table() -> Option> { + T::OF_ID_TABLE + } + + fn acpi_id_table() -> Option> { + T::ACPI_ID_TABLE + } +} + +/// Declares a kernel module that exposes a single i2c driver. +/// +/// # Examples +/// +/// ```ignore +/// kernel::module_i2c_driver! { +/// type: MyDriver, +/// name: "Module name", +/// authors: ["Author name"], +/// description: "Description", +/// license: "GPL v2", +/// } +/// ``` +#[macro_export] +macro_rules! module_i2c_driver { + ($($f:tt)*) => { + $crate::module_driver!(, $crate::i2c::Adapter, { $($f)* }); + }; +} + +/// The i2c driver trait. +/// +/// Drivers must implement this trait in order to get a i2c driver registered. +/// +/// # Example +/// +///``` +/// # use kernel::{acpi, bindings, c_str, device::Core, i2c, of}; +/// +/// struct MyDriver; +/// +/// kernel::acpi_device_table!( +/// ACPI_TABLE, +/// MODULE_ACPI_TABLE, +/// ::IdInfo, +/// [ +/// (acpi::DeviceId::new(c_str!("LNUXBEEF")), ()) +/// ] +/// ); +/// +/// kernel::i2c_device_table!( +/// I2C_TABLE, +/// MODULE_I2C_TABLE, +/// ::IdInfo, +/// [ +/// (i2c::DeviceId::new(c_str!("rust_driver_i2c")), ()) +/// ] +/// ); +/// +/// kernel::of_device_table!( +/// OF_TABLE, +/// MODULE_OF_TABLE, +/// ::IdInfo, +/// [ +/// (of::DeviceId::new(c_str!("test,device")), ()) +/// ] +/// ); +/// +/// impl i2c::Driver for MyDriver { +/// type IdInfo = (); +/// const I2C_ID_TABLE: Option> = Some(&I2C_TABLE); +/// const OF_ID_TABLE: Option> = Some(&OF_TABLE); +/// const ACPI_ID_TABLE: Option> = Some(&ACPI_TABLE); +/// +/// fn probe( +/// _idev: &i2c::I2cClient, +/// _id_info: Option<&Self::IdInfo>, +/// ) -> impl PinInit { +/// Err(ENODEV) +/// } +/// +/// fn shutdown(_idev: &i2c::I2cClient, this: Pin<&Self>) { +/// } +/// } +///``` +pub trait Driver: Send { + /// The type holding information about each device id supported by the driver. + // TODO: Use `associated_type_defaults` once stabilized: + // + // ``` + // type IdInfo: 'static = (); + // ``` + type IdInfo: 'static; + + /// The table of device ids supported by the driver. + const I2C_ID_TABLE: Option> = None; + + /// The table of OF device ids supported by the driver. + const OF_ID_TABLE: Option> = None; + + /// The table of ACPI device ids supported by the driver. + const ACPI_ID_TABLE: Option> = None; + + /// I2C driver probe. + /// + /// Called when a new i2c client is added or discovered. + /// Implementers should attempt to initialize the client here. + fn probe( + dev: &I2cClient, + id_info: Option<&Self::IdInfo>, + ) -> impl PinInit; + + /// I2C driver shutdown. + /// + /// Called by the kernel during system reboot or power-off to allow the [`Driver`] to bring the + /// [`I2cClient`] into a safe state. Implementing this callback is optional. + /// + /// Typical actions include stopping transfers, disabling interrupts, or resetting the hardware + /// to prevent undesired behavior during shutdown. + /// + /// This callback is distinct from final resource cleanup, as the driver instance remains valid + /// after it returns. Any deallocation or teardown of driver-owned resources should instead be + /// handled in `Self::drop`. + fn shutdown(dev: &I2cClient, this: Pin<&Self>) { + let _ = (dev, this); + } + + /// I2C driver unbind. + /// + /// Called when the [`I2cClient`] is unbound from its bound [`Driver`]. Implementing this + /// callback is optional. + /// + /// This callback serves as a place for drivers to perform teardown operations that require a + /// `&Device` or `&Device` reference. For instance, drivers may try to perform I/O + /// operations to gracefully tear down the device. + /// + /// Otherwise, release operations for driver resources should be performed in `Self::drop`. + fn unbind(dev: &I2cClient, this: Pin<&Self>) { + let _ = (dev, this); + } +} + +/// The i2c client representation. +/// +/// This structure represents the Rust abstraction for a C `struct i2c_client`. The +/// implementation abstracts the usage of an existing C `struct i2c_client` that +/// gets passed from the C side +/// +/// # Invariants +/// +/// A [`I2cClient`] instance represents a valid `struct i2c_client` created by the C portion of +/// the kernel. +#[repr(transparent)] +pub struct I2cClient( + Opaque, + PhantomData, +); + +impl I2cClient { + fn as_raw(&self) -> *mut bindings::i2c_client { + self.0.get() + } +} + +// SAFETY: `I2cClient` is a transparent wrapper of a type that doesn't depend on +// `I2cClient`'s generic argument. +kernel::impl_device_context_deref!(unsafe { I2cClient }); +kernel::impl_device_context_into_aref!(I2cClient); + +// SAFETY: Instances of `I2cClient` are always reference-counted. +unsafe impl AlwaysRefCounted for I2cClient { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero. + unsafe { bindings::get_device(self.as_ref().as_raw()) }; + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::put_device(&raw mut (*obj.as_ref().as_raw()).dev) } + } +} + +impl AsRef> for I2cClient { + fn as_ref(&self) -> &device::Device { + let raw = self.as_raw(); + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid + // `struct i2c_client`. + let dev = unsafe { &raw mut (*raw).dev }; + + // SAFETY: `dev` points to a valid `struct device`. + unsafe { device::Device::from_raw(dev) } + } +} + +impl TryFrom<&device::Device> for &I2cClient { + type Error = kernel::error::Error; + + fn try_from(dev: &device::Device) -> Result { + // SAFETY: By the type invariant of `Device`, `dev.as_raw()` is a valid pointer to a + // `struct device`. + if unsafe { bindings::i2c_verify_client(dev.as_raw()).is_null() } { + return Err(EINVAL); + } + + // SAFETY: We've just verified that the type of `dev` equals to + // `bindings::i2c_client_type`, hence `dev` must be embedded in a valid + // `struct i2c_client` as guaranteed by the corresponding C code. + let idev = unsafe { container_of!(dev.as_raw(), bindings::i2c_client, dev) }; + + // SAFETY: `idev` is a valid pointer to a `struct i2c_client`. + Ok(unsafe { &*idev.cast() }) + } +} + +// SAFETY: A `I2cClient` is always reference-counted and can be released from any thread. +unsafe impl Send for I2cClient {} + +// SAFETY: `I2cClient` can be shared among threads because all methods of `I2cClient` +// (i.e. `I2cClient) are thread safe. +unsafe impl Sync for I2cClient {} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 3dd7bebe7888..8c0070a8029e 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -94,6 +94,8 @@ pub mod faux; pub mod firmware; pub mod fmt; pub mod fs; +#[cfg(CONFIG_I2C = "y")] +pub mod i2c; pub mod id_pool; pub mod init; pub mod io; -- cgit v1.2.3 From f3cc26a417b77a07ff4418f7621dd318a1c533b1 Mon Sep 17 00:00:00 2001 From: Igor Korotin Date: Sun, 16 Nov 2025 16:21:54 +0000 Subject: rust: i2c: add manual I2C device creation abstractions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to the basic I2C device support, add rust abstractions upon `i2c_new_client_device`/`i2c_unregister_device` C functions. Implement the core abstractions needed for manual creation/deletion of I2C devices, including: * `i2c::Registration` — a NonNull pointer created by the function `i2c_new_client_device` * `i2c::I2cAdapter` — a ref counted wrapper around `struct i2c_adapter` * `i2c::I2cBoardInfo` — a safe wrapper around `struct i2c_board_info` Acked-by: Wolfram Sang Signed-off-by: Igor Korotin Link: https://patch.msgid.link/20251116162154.171493-1-igor.korotin.linux@gmail.com [ Remove unnecessary safety comment. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/i2c.rs | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index c461686a636a..aea1b44d189b 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -11,6 +11,7 @@ use crate::{ RawDeviceId, RawDeviceIdIndex, // }, + devres::Devres, driver, error::*, of, @@ -23,9 +24,14 @@ use crate::{ use core::{ marker::PhantomData, - ptr::NonNull, // + ptr::{ + from_ref, + NonNull, // + }, // }; +use kernel::types::ARef; + /// An I2C device id table. #[repr(transparent)] #[derive(Clone, Copy)] @@ -354,6 +360,100 @@ pub trait Driver: Send { } } +/// The i2c adapter representation. +/// +/// This structure represents the Rust abstraction for a C `struct i2c_adapter`. The +/// implementation abstracts the usage of an existing C `struct i2c_adapter` that +/// gets passed from the C side +/// +/// # Invariants +/// +/// A [`I2cAdapter`] instance represents a valid `struct i2c_adapter` created by the C portion of +/// the kernel. +#[repr(transparent)] +pub struct I2cAdapter( + Opaque, + PhantomData, +); + +impl I2cAdapter { + fn as_raw(&self) -> *mut bindings::i2c_adapter { + self.0.get() + } +} + +impl I2cAdapter { + /// Returns the I2C Adapter index. + #[inline] + pub fn index(&self) -> i32 { + // SAFETY: `self.as_raw` is a valid pointer to a `struct i2c_adapter`. + unsafe { (*self.as_raw()).nr } + } + + /// Gets pointer to an `i2c_adapter` by index. + pub fn get(index: i32) -> Result> { + // SAFETY: `index` must refer to a valid I2C adapter; the kernel + // guarantees that `i2c_get_adapter(index)` returns either a valid + // pointer or NULL. `NonNull::new` guarantees the correct check. + let adapter = NonNull::new(unsafe { bindings::i2c_get_adapter(index) }).ok_or(ENODEV)?; + + // SAFETY: `adapter` is non-null and points to a live `i2c_adapter`. + // `I2cAdapter` is #[repr(transparent)], so this cast is valid. + Ok(unsafe { (&*adapter.as_ptr().cast::>()).into() }) + } +} + +// SAFETY: `I2cAdapter` is a transparent wrapper of a type that doesn't depend on +// `I2cAdapter`'s generic argument. +kernel::impl_device_context_deref!(unsafe { I2cAdapter }); +kernel::impl_device_context_into_aref!(I2cAdapter); + +// SAFETY: Instances of `I2cAdapter` are always reference-counted. +unsafe impl crate::types::AlwaysRefCounted for I2cAdapter { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero. + unsafe { bindings::i2c_get_adapter(self.index()) }; + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::i2c_put_adapter(obj.as_ref().as_raw()) } + } +} + +/// The i2c board info representation +/// +/// This structure represents the Rust abstraction for a C `struct i2c_board_info` structure, +/// which is used for manual I2C client creation. +#[repr(transparent)] +pub struct I2cBoardInfo(bindings::i2c_board_info); + +impl I2cBoardInfo { + const I2C_TYPE_SIZE: usize = 20; + /// Create a new [`I2cBoardInfo`] for a kernel driver. + #[inline(always)] + pub const fn new(type_: &'static CStr, addr: u16) -> Self { + build_assert!( + type_.len_with_nul() <= Self::I2C_TYPE_SIZE, + "Type exceeds 20 bytes" + ); + let src = type_.as_bytes_with_nul(); + let mut i2c_board_info: bindings::i2c_board_info = pin_init::zeroed(); + let mut i: usize = 0; + while i < src.len() { + i2c_board_info.type_[i] = src[i]; + i += 1; + } + + i2c_board_info.addr = addr; + Self(i2c_board_info) + } + + fn as_raw(&self) -> *const bindings::i2c_board_info { + from_ref(&self.0) + } +} + /// The i2c client representation. /// /// This structure represents the Rust abstraction for a C `struct i2c_client`. The @@ -432,3 +532,54 @@ unsafe impl Send for I2cClient {} // SAFETY: `I2cClient` can be shared among threads because all methods of `I2cClient` // (i.e. `I2cClient) are thread safe. unsafe impl Sync for I2cClient {} + +/// The registration of an i2c client device. +/// +/// This type represents the registration of a [`struct i2c_client`]. When an instance of this +/// type is dropped, its respective i2c client device will be unregistered from the system. +/// +/// # Invariants +/// +/// `self.0` always holds a valid pointer to an initialized and registered +/// [`struct i2c_client`]. +#[repr(transparent)] +pub struct Registration(NonNull); + +impl Registration { + /// The C `i2c_new_client_device` function wrapper for manual I2C client creation. + pub fn new<'a>( + i2c_adapter: &I2cAdapter, + i2c_board_info: &I2cBoardInfo, + parent_dev: &'a device::Device, + ) -> impl PinInit, Error> + 'a { + Devres::new(parent_dev, Self::try_new(i2c_adapter, i2c_board_info)) + } + + fn try_new(i2c_adapter: &I2cAdapter, i2c_board_info: &I2cBoardInfo) -> Result { + // SAFETY: the kernel guarantees that `i2c_new_client_device()` returns either a valid + // pointer or NULL. `from_err_ptr` separates errors. Following `NonNull::new` + // checks for NULL. + let raw_dev = from_err_ptr(unsafe { + bindings::i2c_new_client_device(i2c_adapter.as_raw(), i2c_board_info.as_raw()) + })?; + + let dev_ptr = NonNull::new(raw_dev).ok_or(ENODEV)?; + + Ok(Self(dev_ptr)) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + // SAFETY: `Drop` is only called for a valid `Registration`, which by invariant + // always contains a non-null pointer to an `i2c_client`. + unsafe { bindings::i2c_unregister_device(self.0.as_ptr()) } + } +} + +// SAFETY: A `Registration` of a `struct i2c_client` can be released from any thread. +unsafe impl Send for Registration {} + +// SAFETY: `Registration` offers no interior mutability (no mutation through &self +// and no mutable access is exposed) +unsafe impl Sync for Registration {} -- cgit v1.2.3 From d05b8e97690fa19be39f0af03e7f117f601b6319 Mon Sep 17 00:00:00 2001 From: Igor Korotin Date: Sun, 16 Nov 2025 16:22:04 +0000 Subject: samples: rust: add Rust I2C sample driver Add a new `rust_driver_i2c` sample, showing how to create a new i2c driver using ACPI/OF/Legacy ID tables. Acked-by: Wolfram Sang Signed-off-by: Igor Korotin Link: https://patch.msgid.link/20251116162204.171518-1-igor.korotin.linux@gmail.com Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + samples/rust/Kconfig | 11 ++++++ samples/rust/Makefile | 1 + samples/rust/rust_driver_i2c.rs | 74 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 samples/rust/rust_driver_i2c.rs diff --git a/MAINTAINERS b/MAINTAINERS index a8dfde2f5633..8133987c4b54 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11750,6 +11750,7 @@ R: Daniel Almeida L: rust-for-linux@vger.kernel.org S: Maintained F: rust/kernel/i2c.rs +F: samples/rust/rust_driver_i2c.rs I2C SUBSYSTEM HOST DRIVERS M: Andi Shyti diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index c376eb899b7a..181466319588 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -84,6 +84,17 @@ config SAMPLE_RUST_DEBUGFS_SCOPED If unsure, say N. +config SAMPLE_RUST_DRIVER_I2C + tristate "I2C Driver" + depends on I2C=y + help + This option builds the Rust I2C driver sample. + + To compile this as a module, choose M here: + the module will be called rust_driver_i2c. + + If unsure, say N. + config SAMPLE_RUST_DRIVER_PCI tristate "PCI Driver" depends on PCI diff --git a/samples/rust/Makefile b/samples/rust/Makefile index cf8422f8f219..aec865e2adef 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o obj-$(CONFIG_SAMPLE_RUST_DEBUGFS) += rust_debugfs.o obj-$(CONFIG_SAMPLE_RUST_DEBUGFS_SCOPED) += rust_debugfs_scoped.o obj-$(CONFIG_SAMPLE_RUST_DMA) += rust_dma.o +obj-$(CONFIG_SAMPLE_RUST_DRIVER_I2C) += rust_driver_i2c.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) += rust_driver_pci.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o diff --git a/samples/rust/rust_driver_i2c.rs b/samples/rust/rust_driver_i2c.rs new file mode 100644 index 000000000000..ecefeca3e22f --- /dev/null +++ b/samples/rust/rust_driver_i2c.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust I2C driver sample. + +use kernel::{ + acpi, + c_str, + device::Core, + i2c, + of, + prelude::*, // +}; + +struct SampleDriver; + +kernel::acpi_device_table! { + ACPI_TABLE, + MODULE_ACPI_TABLE, + ::IdInfo, + [(acpi::DeviceId::new(c_str!("LNUXBEEF")), 0)] +} + +kernel::i2c_device_table! { + I2C_TABLE, + MODULE_I2C_TABLE, + ::IdInfo, + [(i2c::DeviceId::new(c_str!("rust_driver_i2c")), 0)] +} + +kernel::of_device_table! { + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [(of::DeviceId::new(c_str!("test,rust_driver_i2c")), 0)] +} + +impl i2c::Driver for SampleDriver { + type IdInfo = u32; + + const ACPI_ID_TABLE: Option> = Some(&ACPI_TABLE); + const I2C_ID_TABLE: Option> = Some(&I2C_TABLE); + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + idev: &i2c::I2cClient, + info: Option<&Self::IdInfo>, + ) -> impl PinInit { + let dev = idev.as_ref(); + + dev_info!(dev, "Probe Rust I2C driver sample.\n"); + + if let Some(info) = info { + dev_info!(dev, "Probed with info: '{}'.\n", info); + } + + Ok(Self) + } + + fn shutdown(idev: &i2c::I2cClient, _this: Pin<&Self>) { + dev_info!(idev.as_ref(), "Shutdown Rust I2C driver sample.\n"); + } + + fn unbind(idev: &i2c::I2cClient, _this: Pin<&Self>) { + dev_info!(idev.as_ref(), "Unbind Rust I2C driver sample.\n"); + } +} + +kernel::module_i2c_driver! { + type: SampleDriver, + name: "rust_driver_i2c", + authors: ["Igor Korotin"], + description: "Rust I2C driver", + license: "GPL v2", +} -- cgit v1.2.3 From 13ae55e24a854efd33eb84bbd19a74b933228ccd Mon Sep 17 00:00:00 2001 From: Igor Korotin Date: Sun, 16 Nov 2025 16:22:10 +0000 Subject: samples: rust: add Rust I2C client registration sample Add a new `rust_i2c_client` sample, showing how to create a new i2c client using `i2c::Registration` Acked-by: Wolfram Sang Signed-off-by: Igor Korotin Link: https://patch.msgid.link/20251116162210.171542-1-igor.korotin.linux@gmail.com [ * Remove dependency to I2C_CHARDEV, depend on I2C=y. * Remove unnecessary impl Drop for SampleDriver. * Rename i2c::Registration, import Devres. * Fixup module description. * Add new source file to MAINTAINERS. - Danilo ] Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + samples/rust/Kconfig | 12 ++++ samples/rust/Makefile | 1 + samples/rust/rust_i2c_client.rs | 147 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 samples/rust/rust_i2c_client.rs diff --git a/MAINTAINERS b/MAINTAINERS index 8133987c4b54..b71ea515240a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11751,6 +11751,7 @@ L: rust-for-linux@vger.kernel.org S: Maintained F: rust/kernel/i2c.rs F: samples/rust/rust_driver_i2c.rs +F: samples/rust/rust_i2c_client.rs I2C SUBSYSTEM HOST DRIVERS M: Andi Shyti diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 181466319588..b66bed5d3f36 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -95,6 +95,18 @@ config SAMPLE_RUST_DRIVER_I2C If unsure, say N. +config SAMPLE_RUST_I2C_CLIENT + tristate "I2C Client Registration" + depends on I2C=y + help + This option builds the Rust I2C client manual creation + sample. + + To compile this as a module, choose M here: + the module will be called rust_i2c_client. + + If unsure, say N. + config SAMPLE_RUST_DRIVER_PCI tristate "PCI Driver" depends on PCI diff --git a/samples/rust/Makefile b/samples/rust/Makefile index aec865e2adef..f65885d1d62b 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_SAMPLE_RUST_DEBUGFS) += rust_debugfs.o obj-$(CONFIG_SAMPLE_RUST_DEBUGFS_SCOPED) += rust_debugfs_scoped.o obj-$(CONFIG_SAMPLE_RUST_DMA) += rust_dma.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_I2C) += rust_driver_i2c.o +obj-$(CONFIG_SAMPLE_RUST_I2C_CLIENT) += rust_i2c_client.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) += rust_driver_pci.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o diff --git a/samples/rust/rust_i2c_client.rs b/samples/rust/rust_i2c_client.rs new file mode 100644 index 000000000000..f67938396dce --- /dev/null +++ b/samples/rust/rust_i2c_client.rs @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust I2C client registration sample. +//! +//! An I2C client in Rust cannot exist on its own. To register a new I2C client, +//! it must be bound to a parent device. In this sample driver, a platform device +//! is used as the parent. +//! + +//! ACPI match table test +//! +//! This demonstrates how to test an ACPI-based Rust I2C client registration driver +//! using QEMU with a custom SSDT. +//! +//! Steps: +//! +//! 1. **Create an SSDT source file** (`ssdt.dsl`) with the following content: +//! +//! ```asl +//! DefinitionBlock ("", "SSDT", 2, "TEST", "VIRTACPI", 0x00000001) +//! { +//! Scope (\_SB) +//! { +//! Device (T432) +//! { +//! Name (_HID, "LNUXBEEF") // ACPI hardware ID to match +//! Name (_UID, 1) +//! Name (_STA, 0x0F) // Device present, enabled +//! Name (_CRS, ResourceTemplate () +//! { +//! Memory32Fixed (ReadWrite, 0xFED00000, 0x1000) +//! }) +//! } +//! } +//! } +//! ``` +//! +//! 2. **Compile the table**: +//! +//! ```sh +//! iasl -tc ssdt.dsl +//! ``` +//! +//! This generates `ssdt.aml` +//! +//! 3. **Run QEMU** with the compiled AML file: +//! +//! ```sh +//! qemu-system-x86_64 -m 512M \ +//! -enable-kvm \ +//! -kernel path/to/bzImage \ +//! -append "root=/dev/sda console=ttyS0" \ +//! -hda rootfs.img \ +//! -serial stdio \ +//! -acpitable file=ssdt.aml +//! ``` +//! +//! Requirements: +//! - The `rust_driver_platform` must be present either: +//! - built directly into the kernel (`bzImage`), or +//! - available as a `.ko` file and loadable from `rootfs.img` +//! +//! 4. **Verify it worked** by checking `dmesg`: +//! +//! ``` +//! rust_driver_platform LNUXBEEF:00: Probed with info: '0'. +//! ``` +//! + +use kernel::{ + acpi, + c_str, + device, + devres::Devres, + i2c, + of, + platform, + prelude::*, + sync::aref::ARef, // +}; + +#[pin_data] +struct SampleDriver { + parent_dev: ARef, + #[pin] + _reg: Devres, +} + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [(of::DeviceId::new(c_str!("test,rust-device")), ())] +); + +kernel::acpi_device_table!( + ACPI_TABLE, + MODULE_ACPI_TABLE, + ::IdInfo, + [(acpi::DeviceId::new(c_str!("LNUXBEEF")), ())] +); + +const SAMPLE_I2C_CLIENT_ADDR: u16 = 0x30; +const SAMPLE_I2C_ADAPTER_INDEX: i32 = 0; +const BOARD_INFO: i2c::I2cBoardInfo = + i2c::I2cBoardInfo::new(c_str!("rust_driver_i2c"), SAMPLE_I2C_CLIENT_ADDR); + +impl platform::Driver for SampleDriver { + type IdInfo = (); + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + const ACPI_ID_TABLE: Option> = Some(&ACPI_TABLE); + + fn probe( + pdev: &platform::Device, + _info: Option<&Self::IdInfo>, + ) -> impl PinInit { + dev_info!( + pdev.as_ref(), + "Probe Rust I2C Client registration sample.\n" + ); + + kernel::try_pin_init!( Self { + parent_dev: pdev.into(), + + _reg <- { + let adapter = i2c::I2cAdapter::get(SAMPLE_I2C_ADAPTER_INDEX)?; + + i2c::Registration::new(&adapter, &BOARD_INFO, pdev.as_ref()) + } + }) + } + + fn unbind(pdev: &platform::Device, _this: Pin<&Self>) { + dev_info!( + pdev.as_ref(), + "Unbind Rust I2C Client registration sample.\n" + ); + } +} + +kernel::module_platform_driver! { + type: SampleDriver, + name: "rust_device_i2c", + authors: ["Danilo Krummrich", "Igor Korotin"], + description: "Rust I2C client registration", + license: "GPL v2", +} -- cgit v1.2.3 From e4addc7cc2dfcc19f1c8c8e47f3834b22cb21559 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Mon, 27 Oct 2025 20:06:03 +0000 Subject: rust: Add trait to convert a device reference to a bus device reference Implement the `AsBusDevice` trait for converting a `Device` reference to a bus device reference for all bus devices. The `AsBusDevice` trait allows abstractions to provide the bus device in class device callbacks. It must not be used by drivers and is intended for bus and class device abstractions only. Signed-off-by: Markus Probst Link: https://patch.msgid.link/20251027200547.1038967-2-markus.probst@posteo.de [ * Remove unused import. * Change visibility of AsBusDevice to public. * Fix build for USB. * Add impl for I2cClient. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 7 +++++++ rust/kernel/device.rs | 33 +++++++++++++++++++++++++++++++++ rust/kernel/i2c.rs | 7 +++++++ rust/kernel/pci.rs | 7 +++++++ rust/kernel/platform.rs | 7 +++++++ rust/kernel/usb.rs | 15 ++++++++++++++- 6 files changed, 75 insertions(+), 1 deletion(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index 618eeeec2bd0..56f3c180e8f6 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -16,6 +16,7 @@ use crate::{ }; use core::{ marker::PhantomData, + mem::offset_of, ptr::{addr_of_mut, NonNull}, }; @@ -245,6 +246,12 @@ impl Device { } } +// SAFETY: `auxiliary::Device` is a transparent wrapper of `struct auxiliary_device`. +// The offset is guaranteed to point to a valid device field inside `auxiliary::Device`. +unsafe impl device::AsBusDevice for Device { + const OFFSET: usize = offset_of!(bindings::auxiliary_device, dev); +} + // SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic // argument. kernel::impl_device_context_deref!(unsafe { Device }); diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 1a307be953c2..660cb2b48c07 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -594,6 +594,39 @@ impl DeviceContext for Core {} impl DeviceContext for CoreInternal {} impl DeviceContext for Normal {} +/// Convert device references to bus device references. +/// +/// Bus devices can implement this trait to allow abstractions to provide the bus device in +/// class device callbacks. +/// +/// This must not be used by drivers and is intended for bus and class device abstractions only. +/// +/// # Safety +/// +/// `AsBusDevice::OFFSET` must be the offset of the embedded base `struct device` field within a +/// bus device structure. +pub unsafe trait AsBusDevice: AsRef> { + /// The relative offset to the device field. + /// + /// Use `offset_of!(bindings, field)` macro to avoid breakage. + const OFFSET: usize; + + /// Convert a reference to [`Device`] into `Self`. + /// + /// # Safety + /// + /// `dev` must be contained in `Self`. + unsafe fn from_device(dev: &Device) -> &Self + where + Self: Sized, + { + let raw = dev.as_raw(); + // SAFETY: `raw - Self::OFFSET` is guaranteed by the safety requirements + // to be a valid pointer to `Self`. + unsafe { &*raw.byte_sub(Self::OFFSET).cast::() } + } +} + /// # Safety /// /// The type given as `$device` must be a transparent wrapper of a type that doesn't depend on the diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index aea1b44d189b..95b056cc1a71 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -24,6 +24,7 @@ use crate::{ use core::{ marker::PhantomData, + mem::offset_of, ptr::{ from_ref, NonNull, // @@ -476,6 +477,12 @@ impl I2cClient { } } +// SAFETY: `I2cClient` is a transparent wrapper of `struct i2c_client`. +// The offset is guaranteed to point to a valid device field inside `I2cClient`. +unsafe impl device::AsBusDevice for I2cClient { + const OFFSET: usize = offset_of!(bindings::i2c_client, dev); +} + // SAFETY: `I2cClient` is a transparent wrapper of a type that doesn't depend on // `I2cClient`'s generic argument. kernel::impl_device_context_deref!(unsafe { I2cClient }); diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 410b79d46632..82e128431f08 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -24,6 +24,7 @@ use crate::{ }; use core::{ marker::PhantomData, + mem::offset_of, ptr::{ addr_of_mut, NonNull, // @@ -443,6 +444,12 @@ impl Device { } } +// SAFETY: `pci::Device` is a transparent wrapper of `struct pci_dev`. +// The offset is guaranteed to point to a valid device field inside `pci::Device`. +unsafe impl device::AsBusDevice for Device { + const OFFSET: usize = offset_of!(bindings::pci_dev, dev); +} + // SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic // argument. kernel::impl_device_context_deref!(unsafe { Device }); diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index f4b617c570be..ed889f079cab 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -19,6 +19,7 @@ use crate::{ use core::{ marker::PhantomData, + mem::offset_of, ptr::{addr_of_mut, NonNull}, }; @@ -287,6 +288,12 @@ impl Device { } } +// SAFETY: `platform::Device` is a transparent wrapper of `struct platform_device`. +// The offset is guaranteed to point to a valid device field inside `platform::Device`. +unsafe impl device::AsBusDevice for Device { + const OFFSET: usize = offset_of!(bindings::platform_device, dev); +} + macro_rules! define_irq_accessor_by_index { ( $(#[$meta:meta])* $fn_name:ident, diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 534e3ded5442..d10b65e9fb6a 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -15,7 +15,14 @@ use crate::{ types::{AlwaysRefCounted, Opaque}, ThisModule, }; -use core::{marker::PhantomData, mem::MaybeUninit, ptr::NonNull}; +use core::{ + marker::PhantomData, + mem::{ + offset_of, + MaybeUninit, // + }, + ptr::NonNull, +}; /// An adapter for the registration of USB drivers. pub struct Adapter(T); @@ -324,6 +331,12 @@ impl Interface { } } +// SAFETY: `usb::Interface` is a transparent wrapper of `struct usb_interface`. +// The offset is guaranteed to point to a valid device field inside `usb::Interface`. +unsafe impl device::AsBusDevice for Interface { + const OFFSET: usize = offset_of!(bindings::usb_interface, dev); +} + // SAFETY: `Interface` is a transparent wrapper of a type that doesn't depend on // `Interface`'s generic argument. kernel::impl_device_context_deref!(unsafe { Interface }); -- cgit v1.2.3 From 4d24145a7833c14a6521dfab57c5f10076a0110f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 11 Nov 2025 15:49:45 +0100 Subject: devres: Remove unused devm_free_percpu() Remove unused devm_free_percpu(). By the way, it was never used in the drivers/ from day 1. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20251111145046.997309-2-andriy.shevchenko@linux.intel.com Signed-off-by: Danilo Krummrich --- Documentation/driver-api/driver-model/devres.rst | 1 - drivers/base/devres.c | 25 ------------------------ include/linux/device.h | 1 - 3 files changed, 27 deletions(-) diff --git a/Documentation/driver-api/driver-model/devres.rst b/Documentation/driver-api/driver-model/devres.rst index 2b36ebde9cec..0198ac65e874 100644 --- a/Documentation/driver-api/driver-model/devres.rst +++ b/Documentation/driver-api/driver-model/devres.rst @@ -383,7 +383,6 @@ NET PER-CPU MEM devm_alloc_percpu() - devm_free_percpu() PCI devm_pci_alloc_host_bridge() : managed PCI host bridge allocation diff --git a/drivers/base/devres.c b/drivers/base/devres.c index c948c88d3956..f54db6d138ab 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -1222,13 +1222,6 @@ static void devm_percpu_release(struct device *dev, void *pdata) free_percpu(p); } -static int devm_percpu_match(struct device *dev, void *data, void *p) -{ - struct devres *devr = container_of(data, struct devres, data); - - return *(void **)devr->data == p; -} - /** * __devm_alloc_percpu - Resource-managed alloc_percpu * @dev: Device to allocate per-cpu memory for @@ -1264,21 +1257,3 @@ void __percpu *__devm_alloc_percpu(struct device *dev, size_t size, return pcpu; } EXPORT_SYMBOL_GPL(__devm_alloc_percpu); - -/** - * devm_free_percpu - Resource-managed free_percpu - * @dev: Device this memory belongs to - * @pdata: Per-cpu memory to free - * - * Free memory allocated with devm_alloc_percpu(). - */ -void devm_free_percpu(struct device *dev, void __percpu *pdata) -{ - /* - * Use devres_release() to prevent memory leakage as - * devm_free_pages() does. - */ - WARN_ON(devres_release(dev, devm_percpu_release, devm_percpu_match, - (void *)(__force unsigned long)pdata)); -} -EXPORT_SYMBOL_GPL(devm_free_percpu); diff --git a/include/linux/device.h b/include/linux/device.h index b031ff71a5bd..0c6377f6631c 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -298,7 +298,6 @@ void device_remove_bin_file(struct device *dev, void __percpu *__devm_alloc_percpu(struct device *dev, size_t size, size_t align); -void devm_free_percpu(struct device *dev, void __percpu *pdata); struct device_dma_parameters { /* -- cgit v1.2.3 From 0a75f3d90e7ab9cd182327fca4b4e3bce379afe5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 11 Nov 2025 15:49:46 +0100 Subject: devres: Move devm_alloc_percpu() and related to devres.h Move devm_alloc_percpu() and related to devres.h where it belongs. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20251111145046.997309-3-andriy.shevchenko@linux.intel.com [ Fix minor typo in commit message. - Danilo ] Signed-off-by: Danilo Krummrich --- include/linux/device.h | 18 ------------------ include/linux/device/devres.h | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/include/linux/device.h b/include/linux/device.h index 0c6377f6631c..0be95294b6e6 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -281,24 +281,6 @@ int __must_check device_create_bin_file(struct device *dev, void device_remove_bin_file(struct device *dev, const struct bin_attribute *attr); -/** - * devm_alloc_percpu - Resource-managed alloc_percpu - * @dev: Device to allocate per-cpu memory for - * @type: Type to allocate per-cpu memory for - * - * Managed alloc_percpu. Per-cpu memory allocated with this function is - * automatically freed on driver detach. - * - * RETURNS: - * Pointer to allocated memory on success, NULL on failure. - */ -#define devm_alloc_percpu(dev, type) \ - ((typeof(type) __percpu *)__devm_alloc_percpu((dev), sizeof(type), \ - __alignof__(type))) - -void __percpu *__devm_alloc_percpu(struct device *dev, size_t size, - size_t align); - struct device_dma_parameters { /* * a low level driver may set these to teach IOMMU code about diff --git a/include/linux/device/devres.h b/include/linux/device/devres.h index 8c5f57e0d613..9c1e3d643d69 100644 --- a/include/linux/device/devres.h +++ b/include/linux/device/devres.h @@ -9,6 +9,7 @@ #include #include #include +#include struct device; struct device_node; @@ -96,6 +97,22 @@ devm_kvasprintf(struct device *dev, gfp_t gfp, const char *fmt, va_list ap); char * __printf(3, 4) __malloc devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...); +/** + * devm_alloc_percpu - Resource-managed alloc_percpu + * @dev: Device to allocate per-cpu memory for + * @type: Type to allocate per-cpu memory for + * + * Managed alloc_percpu. Per-cpu memory allocated with this function is + * automatically freed on driver detach. + * + * RETURNS: + * Pointer to allocated memory on success, NULL on failure. + */ +#define devm_alloc_percpu(dev, type) \ + ((typeof(type) __percpu *)__devm_alloc_percpu((dev), sizeof(type), __alignof__(type))) + +void __percpu *__devm_alloc_percpu(struct device *dev, size_t size, size_t align); + unsigned long devm_get_free_pages(struct device *dev, gfp_t gfp_mask, unsigned int order); void devm_free_pages(struct device *dev, unsigned long addr); -- cgit v1.2.3 From 4635406417bb842862d6322c461743c9b128c444 Mon Sep 17 00:00:00 2001 From: Peter Colberg Date: Thu, 20 Nov 2025 11:06:09 -0500 Subject: samples: rust: fix module name for Rust PCI driver sample Replace driver_pci -> rust_driver_pci in config SAMPLE_RUST_DRIVER_PCI. Signed-off-by: Peter Colberg Fixes: 685376d18e9a ("samples: rust: add Rust PCI sample driver") Link: https://patch.msgid.link/20251120-rust-driver-pci-fix-module-name-v1-1-430cee310dc0@redhat.com Signed-off-by: Danilo Krummrich --- samples/rust/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index b66bed5d3f36..cde1dc9451d3 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -114,7 +114,7 @@ config SAMPLE_RUST_DRIVER_PCI This option builds the Rust PCI driver sample. To compile this as a module, choose M here: - the module will be called driver_pci. + the module will be called rust_driver_pci. If unsure, say N. -- cgit v1.2.3 From a762f883ecf6eed3c0c57e7afa97fba09287a40a Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 23 Nov 2025 17:35:36 +0100 Subject: rust: i2c: prepare for `core::ffi::CStr` The rust-next tree contains commit: 3b83f5d5e78a ("rust: replace `CStr` with `core::ffi::CStr`") which, when merged together with commits: 57c5bd9aee94 ("rust: i2c: add basic I2C device and driver abstractions") f3cc26a417b7 ("rust: i2c: add manual I2C device creation abstractions") from this tree (driver-core), produces errors like the following: error[E0599]: no method named `len_with_nul` found for reference `&'static ffi::CStr` in the current scope --> rust/kernel/i2c.rs:48:16 | 48 | id.len_with_nul() <= Self::I2C_NAME_SIZE, | ^^^^^^^^^^^^ method not found in `&CStr` error[E0599]: no method named `as_bytes_with_nul` found for reference `&'static ffi::CStr` in the current scope --> rust/kernel/i2c.rs:51:22 | 51 | let src = id.as_bytes_with_nul(); | ^^^^^^^^^^^^^^^^^ | help: there is a method `to_bytes_with_nul` with a similar name | 51 | let src = id.to_bytes_with_nul(); | ~~~~~~~~~~~~~~~~~ which were detected in linux-next by Stephen [1]. The `i2c` code can be independently prepared to be ready for the change, thus do so. The change is similar to the one done by Tamir in commit 657403637f7d ("rust: acpi: use `core::ffi::CStr` method names"). Link: https://lore.kernel.org/all/20251120181111.65ce75a0@canb.auug.org.au/ [1] Signed-off-by: Miguel Ojeda Link: https://patch.msgid.link/20251123163536.1771801-1-ojeda@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/i2c.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index 95b056cc1a71..491e6cc25cf4 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -44,11 +44,8 @@ impl DeviceId { /// Create a new device id from an I2C 'id' string. #[inline(always)] pub const fn new(id: &'static CStr) -> Self { - build_assert!( - id.len_with_nul() <= Self::I2C_NAME_SIZE, - "ID exceeds 20 bytes" - ); - let src = id.as_bytes_with_nul(); + let src = id.to_bytes_with_nul(); + build_assert!(src.len() <= Self::I2C_NAME_SIZE, "ID exceeds 20 bytes"); let mut i2c: bindings::i2c_device_id = pin_init::zeroed(); let mut i = 0; while i < src.len() { @@ -434,11 +431,8 @@ impl I2cBoardInfo { /// Create a new [`I2cBoardInfo`] for a kernel driver. #[inline(always)] pub const fn new(type_: &'static CStr, addr: u16) -> Self { - build_assert!( - type_.len_with_nul() <= Self::I2C_TYPE_SIZE, - "Type exceeds 20 bytes" - ); - let src = type_.as_bytes_with_nul(); + let src = type_.to_bytes_with_nul(); + build_assert!(src.len() <= Self::I2C_TYPE_SIZE, "Type exceeds 20 bytes"); let mut i2c_board_info: bindings::i2c_board_info = pin_init::zeroed(); let mut i: usize = 0; while i < src.len() { -- cgit v1.2.3 From f85d90dd8d0efbc75e79698e147c6e682df22e1a Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 29 Oct 2025 09:12:10 +0100 Subject: sysfs: attribute_group: allow registration of const attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To be able to constify instances of struct attribute it has to be possible to add them to struct attribute_group. The current type of the attrs member however is not compatible with that. Introduce a union that allows registration of both const and non-const attributes to enable a piecewise transition. As both union member types are compatible no logic needs to be adapted. Technically it is now possible register a const struct attribute and receive it as mutable pointer in the callbacks. This is a soundness issue. But this same soundness issue already exists today in sysfs_create_file(). Also the struct definition and callback implementation are always closely linked and are meant to be moved to const in lockstep. Similar to commit 906c508afdca ("sysfs: attribute_group: allow registration of const bin_attribute") Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251029-sysfs-const-attr-prep-v5-1-ea7d745acff4@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- include/linux/sysfs.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 9a25a2911652..e34d6af96abb 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -105,7 +105,10 @@ struct attribute_group { size_t (*bin_size)(struct kobject *, const struct bin_attribute *, int); - struct attribute **attrs; + union { + struct attribute **attrs; + const struct attribute *const *attrs_const; + }; const struct bin_attribute *const *bin_attrs; }; -- cgit v1.2.3 From 964c93b1eef37e3bbe0edb37346c076217d71fe7 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 29 Oct 2025 09:12:11 +0100 Subject: sysfs: transparently handle const pointers in ATTRIBUTE_GROUPS() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To ease the constification process of 'struct attribute', transparently handle the const pointers in ATTRIBUTE_GROUPS(). A cast is used instead of assigning to .attrs_new as it keeps the macro smaller. As both members are aliased to each other the result is identical. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251029-sysfs-const-attr-prep-v5-2-ea7d745acff4@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- include/linux/sysfs.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index e34d6af96abb..92f82cee5f11 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -287,7 +287,12 @@ static const struct attribute_group *_name##_groups[] = { \ #define ATTRIBUTE_GROUPS(_name) \ static const struct attribute_group _name##_group = { \ - .attrs = _name##_attrs, \ + .attrs = _Generic(_name##_attrs, \ + struct attribute **: \ + _name##_attrs, \ + const struct attribute *const *: \ + (void *)_name##_attrs \ + ), \ }; \ __ATTRIBUTE_GROUPS(_name) -- cgit v1.2.3 From 02ac5335a55111d87a7a618355261b4407ed0f7f Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 29 Oct 2025 09:12:12 +0100 Subject: sysfs: introduce __SYSFS_FUNCTION_ALTERNATIVE() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For the constification phase of 'struct attribute' various callback struct members will need to exist in both const and non-const variants. Keeping both members in a union avoids memory and CPU overhead but will be detected and trapped by Control Flow Integrity (CFI). By deciding between a struct and a union depending whether CFI is enabled, most configurations can avoid this overhead. Code using these callbacks will still need to be updated to handle both members explicitly. In the union case the compiler will recognize that testing for one union member is enough and optimize away the code for the other one. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251029-sysfs-const-attr-prep-v5-3-ea7d745acff4@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- include/linux/sysfs.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 92f82cee5f11..9cef5bf24ba7 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -58,6 +58,12 @@ do { \ #define sysfs_attr_init(attr) do {} while (0) #endif +#ifdef CONFIG_CFI +#define __SYSFS_FUNCTION_ALTERNATIVE(MEMBERS...) struct { MEMBERS } +#else +#define __SYSFS_FUNCTION_ALTERNATIVE(MEMBERS...) union { MEMBERS } +#endif + /** * struct attribute_group - data structure used to declare an attribute group. * @name: Optional: Attribute group name -- cgit v1.2.3 From 7dd9fdb4939b972c1d0523e94fb3f70789653f0c Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 29 Oct 2025 09:12:13 +0100 Subject: sysfs: attribute_group: enable const variants of is_visible() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When constifying instances of struct attribute, for consistency the corresponding .is_visible() callback should be adapted, too. Introduce a temporary transition mechanism until all callbacks are converted. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251029-sysfs-const-attr-prep-v5-4-ea7d745acff4@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/group.c | 10 ++++++++-- include/linux/sysfs.h | 8 ++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index e142bac4f9f8..e1e639f515a0 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c @@ -36,6 +36,9 @@ static umode_t __first_visible(const struct attribute_group *grp, struct kobject if (grp->attrs && grp->attrs[0] && grp->is_visible) return grp->is_visible(kobj, grp->attrs[0], 0); + if (grp->attrs && grp->attrs[0] && grp->is_visible_const) + return grp->is_visible_const(kobj, grp->attrs[0], 0); + if (grp->bin_attrs && grp->bin_attrs[0] && grp->is_bin_visible) return grp->is_bin_visible(kobj, grp->bin_attrs[0], 0); @@ -61,8 +64,11 @@ static int create_files(struct kernfs_node *parent, struct kobject *kobj, */ if (update) kernfs_remove_by_name(parent, (*attr)->name); - if (grp->is_visible) { - mode = grp->is_visible(kobj, *attr, i); + if (grp->is_visible || grp->is_visible_const) { + if (grp->is_visible) + mode = grp->is_visible(kobj, *attr, i); + else + mode = grp->is_visible_const(kobj, *attr, i); mode &= ~SYSFS_GROUP_INVISIBLE; if (!mode) continue; diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 9cef5bf24ba7..592886ed6ca9 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -104,8 +104,12 @@ do { \ */ struct attribute_group { const char *name; - umode_t (*is_visible)(struct kobject *, - struct attribute *, int); + __SYSFS_FUNCTION_ALTERNATIVE( + umode_t (*is_visible)(struct kobject *, + struct attribute *, int); + umode_t (*is_visible_const)(struct kobject *, + const struct attribute *, int); + ); umode_t (*is_bin_visible)(struct kobject *, const struct bin_attribute *, int); size_t (*bin_size)(struct kobject *, -- cgit v1.2.3 From c301a2e2d78c2c20d466c7e38568406471ede17d Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 29 Oct 2025 09:12:14 +0100 Subject: samples/kobject: add is_visible() callback to attribute group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was no example for the is_visible() callback so far. It will also become an example and test for the constification of 'struct attribute' later. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251029-sysfs-const-attr-prep-v5-5-ea7d745acff4@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- samples/kobject/kset-example.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/samples/kobject/kset-example.c b/samples/kobject/kset-example.c index 579ce150217c..1aac595ed949 100644 --- a/samples/kobject/kset-example.c +++ b/samples/kobject/kset-example.c @@ -178,7 +178,22 @@ static struct attribute *foo_default_attrs[] = { &bar_attribute.attr, NULL, /* need to NULL terminate the list of attributes */ }; -ATTRIBUTE_GROUPS(foo_default); + +static umode_t foo_default_attrs_is_visible(struct kobject *kobj, + struct attribute *attr, + int n) +{ + /* Hide attributes with the same name as the kobject. */ + if (strcmp(kobject_name(kobj), attr->name) == 0) + return 0; + return attr->mode; +} + +static const struct attribute_group foo_default_group = { + .attrs = foo_default_attrs, + .is_visible = foo_default_attrs_is_visible, +}; +__ATTRIBUTE_GROUPS(foo_default); /* * Our own ktype for our kobjects. Here we specify our sysfs ops, the -- cgit v1.2.3 From 2d76fdc1c91a95c23cd3e47a4fd4315f1633019b Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 29 Oct 2025 09:12:15 +0100 Subject: samples/kobject: constify 'struct foo_attribute' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Showcase and test the new 'struct attribute' constification facilities. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251029-sysfs-const-attr-prep-v5-6-ea7d745acff4@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- samples/kobject/kset-example.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/samples/kobject/kset-example.c b/samples/kobject/kset-example.c index 1aac595ed949..d0103904e5dd 100644 --- a/samples/kobject/kset-example.c +++ b/samples/kobject/kset-example.c @@ -37,10 +37,11 @@ struct foo_obj { /* a custom attribute that works just for a struct foo_obj. */ struct foo_attribute { struct attribute attr; - ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf); - ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count); + ssize_t (*show)(struct foo_obj *foo, const struct foo_attribute *attr, char *buf); + ssize_t (*store)(struct foo_obj *foo, const struct foo_attribute *attr, + const char *buf, size_t count); }; -#define to_foo_attr(x) container_of(x, struct foo_attribute, attr) +#define to_foo_attr(x) container_of_const(x, struct foo_attribute, attr) /* * The default show function that must be passed to sysfs. This will be @@ -53,7 +54,7 @@ static ssize_t foo_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { - struct foo_attribute *attribute; + const struct foo_attribute *attribute; struct foo_obj *foo; attribute = to_foo_attr(attr); @@ -73,7 +74,7 @@ static ssize_t foo_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { - struct foo_attribute *attribute; + const struct foo_attribute *attribute; struct foo_obj *foo; attribute = to_foo_attr(attr); @@ -109,13 +110,13 @@ static void foo_release(struct kobject *kobj) /* * The "foo" file where the .foo variable is read from and written to. */ -static ssize_t foo_show(struct foo_obj *foo_obj, struct foo_attribute *attr, +static ssize_t foo_show(struct foo_obj *foo_obj, const struct foo_attribute *attr, char *buf) { return sysfs_emit(buf, "%d\n", foo_obj->foo); } -static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr, +static ssize_t foo_store(struct foo_obj *foo_obj, const struct foo_attribute *attr, const char *buf, size_t count) { int ret; @@ -128,14 +129,14 @@ static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr, } /* Sysfs attributes cannot be world-writable. */ -static struct foo_attribute foo_attribute = +static const struct foo_attribute foo_attribute = __ATTR(foo, 0664, foo_show, foo_store); /* * More complex function where we determine which variable is being accessed by * looking at the attribute for the "baz" and "bar" files. */ -static ssize_t b_show(struct foo_obj *foo_obj, struct foo_attribute *attr, +static ssize_t b_show(struct foo_obj *foo_obj, const struct foo_attribute *attr, char *buf) { int var; @@ -147,7 +148,7 @@ static ssize_t b_show(struct foo_obj *foo_obj, struct foo_attribute *attr, return sysfs_emit(buf, "%d\n", var); } -static ssize_t b_store(struct foo_obj *foo_obj, struct foo_attribute *attr, +static ssize_t b_store(struct foo_obj *foo_obj, const struct foo_attribute *attr, const char *buf, size_t count) { int var, ret; @@ -163,16 +164,16 @@ static ssize_t b_store(struct foo_obj *foo_obj, struct foo_attribute *attr, return count; } -static struct foo_attribute baz_attribute = +static const struct foo_attribute baz_attribute = __ATTR(baz, 0664, b_show, b_store); -static struct foo_attribute bar_attribute = +static const struct foo_attribute bar_attribute = __ATTR(bar, 0664, b_show, b_store); /* * Create a group of attributes so that we can create and destroy them all * at once. */ -static struct attribute *foo_default_attrs[] = { +static const struct attribute *const foo_default_attrs[] = { &foo_attribute.attr, &baz_attribute.attr, &bar_attribute.attr, @@ -180,7 +181,7 @@ static struct attribute *foo_default_attrs[] = { }; static umode_t foo_default_attrs_is_visible(struct kobject *kobj, - struct attribute *attr, + const struct attribute *attr, int n) { /* Hide attributes with the same name as the kobject. */ @@ -190,8 +191,8 @@ static umode_t foo_default_attrs_is_visible(struct kobject *kobj, } static const struct attribute_group foo_default_group = { - .attrs = foo_default_attrs, - .is_visible = foo_default_attrs_is_visible, + .attrs_const = foo_default_attrs, + .is_visible_const = foo_default_attrs_is_visible, }; __ATTRIBUTE_GROUPS(foo_default); -- cgit v1.2.3 From 71464949b1f5f8b8599d057fea525a2a520f84d8 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 29 Oct 2025 09:12:16 +0100 Subject: sysfs: simplify attribute definition macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define the macros in terms of each other. This makes them easier to understand and also will make it easier to implement the transition machinery for 'const struct attribute'. __ATTR_RO_MODE() can't be implemented in terms of __ATTR() as not all attributes have a .store callback. The same issue theoretically exists for __ATTR_WO(), but practically that does not occur today. Reorder __ATTR_RO() below __ATTR_RO_MODE() to keep the order of the macro definition consistent with respect to each other. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251029-sysfs-const-attr-prep-v5-7-ea7d745acff4@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- include/linux/sysfs.h | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 592886ed6ca9..c33a96b7391a 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -251,28 +251,20 @@ struct attribute_group { .store = _store, \ } -#define __ATTR_RO(_name) { \ - .attr = { .name = __stringify(_name), .mode = 0444 }, \ - .show = _name##_show, \ -} - #define __ATTR_RO_MODE(_name, _mode) { \ .attr = { .name = __stringify(_name), \ .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ .show = _name##_show, \ } -#define __ATTR_RW_MODE(_name, _mode) { \ - .attr = { .name = __stringify(_name), \ - .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ - .show = _name##_show, \ - .store = _name##_store, \ -} +#define __ATTR_RO(_name) \ + __ATTR_RO_MODE(_name, 0444) -#define __ATTR_WO(_name) { \ - .attr = { .name = __stringify(_name), .mode = 0200 }, \ - .store = _name##_store, \ -} +#define __ATTR_RW_MODE(_name, _mode) \ + __ATTR(_name, _mode, _name##_show, _name##_store) + +#define __ATTR_WO(_name) \ + __ATTR(_name, 0200, NULL, _name##_store) #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store) -- cgit v1.2.3 From d3d25f430cadc59d42965f54f54a8c0050931860 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Thu, 6 Nov 2025 10:58:38 +0530 Subject: mod_devicetable: Bump auxiliary_device_id name size We have an upcoming driver named "intel_ehl_pse_io". This creates an auxiliary child device for it's GPIO sub-functionality, which matches against "intel_ehl_pse_io.gpio-elkhartlake" and overshoots the current maximum limit of 32 bytes for auxiliary device id string. Bump the size to 40 bytes to satisfy such cases. Suggested-by: Andy Shevchenko Signed-off-by: Raag Jadav Link: https://patch.msgid.link/20251106052838.433673-1-raag.jadav@intel.com Signed-off-by: Greg Kroah-Hartman --- include/linux/mod_devicetable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 6077972e8b45..24eb5a88a5c5 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -867,7 +867,7 @@ struct mhi_device_id { kernel_ulong_t driver_data; }; -#define AUXILIARY_NAME_SIZE 32 +#define AUXILIARY_NAME_SIZE 40 #define AUXILIARY_MODULE_PREFIX "auxiliary:" struct auxiliary_device_id { -- cgit v1.2.3 From 2467f9928c9824e52718f977009ed3a0cee83dc5 Mon Sep 17 00:00:00 2001 From: Jane Chu Date: Tue, 11 Nov 2025 13:26:06 -0700 Subject: fs/kernfs: raise sb->maxbytes to MAX_LFS_FILESIZE On an ARM64 A1 system, it's possible to have physical memory span up to the 64T boundary, like below $ lsmem -b -r -n -o range,size 0x0000000080000000-0x00000000bfffffff 1073741824 0x0000080000000000-0x000008007fffffff 2147483648 0x00000800c0000000-0x0000087fffffffff 546534588416 0x0000400000000000-0x00004000bfffffff 3221225472 0x0000400100000000-0x0000407fffffffff 545460846592 So it's time to extend /sys/kernel/mm/page_idle/bitmap to be able to account for >2G number of pages, by raising the kernfs file size limit. Signed-off-by: Jane Chu Link: https://patch.msgid.link/20251111202606.1505437-1-jane.chu@oracle.com Signed-off-by: Greg Kroah-Hartman --- fs/kernfs/mount.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c index 76eaf64b9d9e..3ac52e141766 100644 --- a/fs/kernfs/mount.c +++ b/fs/kernfs/mount.c @@ -298,6 +298,7 @@ static int kernfs_fill_super(struct super_block *sb, struct kernfs_fs_context *k if (info->root->flags & KERNFS_ROOT_SUPPORT_EXPORTOP) sb->s_export_op = &kernfs_export_ops; sb->s_time_gran = 1; + sb->s_maxbytes = MAX_LFS_FILESIZE; /* sysfs dentries and inodes don't require IO to create */ sb->s_shrink->seeks = 0; -- cgit v1.2.3 From 382b1e8f30f779af8d6d33268e53df7de579ef3c Mon Sep 17 00:00:00 2001 From: Will Rosenberg Date: Tue, 25 Nov 2025 08:13:32 -0700 Subject: kernfs: fix memory leak of kernfs_iattrs in __kernfs_new_node There exists a memory leak of kernfs_iattrs contained as an element of kernfs_node allocated in __kernfs_new_node(). __kernfs_setattr() allocates kernfs_iattrs as a sub-object, and the LSM security check incorrectly errors out and does not free the kernfs_iattrs sub-object. Make an additional error out case that properly frees kernfs_iattrs if security_kernfs_init_security() fails. Fixes: e19dfdc83b60 ("kernfs: initialize security of newly created nodes") Co-developed-by: Oliver Rosenberg Signed-off-by: Oliver Rosenberg Signed-off-by: Will Rosenberg Link: https://patch.msgid.link/20251125151332.2010687-1-whrosenb@asu.edu Signed-off-by: Greg Kroah-Hartman --- fs/kernfs/dir.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index a670ba3e565e..5c0efd6b239f 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -675,11 +675,14 @@ static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root, if (parent) { ret = security_kernfs_init_security(parent, kn); if (ret) - goto err_out3; + goto err_out4; } return kn; + err_out4: + simple_xattrs_free(&kn->iattr->xattrs, NULL); + kmem_cache_free(kernfs_iattrs_cache, kn->iattr); err_out3: spin_lock(&root->kernfs_idr_lock); idr_remove(&root->ino_idr, (u32)kernfs_ino(kn)); -- cgit v1.2.3 From 76987bac71d5349a62f312ca1cd92de73778a652 Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Fri, 10 Oct 2025 21:18:29 -0400 Subject: sysfs/cpu: Use DEVICE_ATTR_RO for nohz_full attribute The /sys/devices/system/cpu/nohz_full file is a read-only attribute that reports the CPUs configured for tickless operation (CONFIG_NO_HZ_FULL=y). The current definition uses the generic DEVICE_ATTR macro, which unnecessarily requires specifying the file mode (0444) and a NULL store operation pointer. This patch converts the definition to use the dedicated DEVICE_ATTR_RO macro. This correctly expresses the read-only nature of the attribute, removes the redundant mode field, and simplifies the code. As a related cleanup, rename the show function from print_cpus_nohz_full() to the standard nohz_full_show() for consistency with common sysfs attribute naming conventions. Signed-off-by: Aaron Tomlin Link: https://patch.msgid.link/20251011011830.6670-2-atomlin@atomlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/cpu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index fa0a2eef93ac..c792ec66462d 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -301,12 +301,13 @@ static ssize_t print_cpus_isolated(struct device *dev, static DEVICE_ATTR(isolated, 0444, print_cpus_isolated, NULL); #ifdef CONFIG_NO_HZ_FULL -static ssize_t print_cpus_nohz_full(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t nohz_full_show(struct device *dev, + struct device_attribute *attr, + char *buf) { return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(tick_nohz_full_mask)); } -static DEVICE_ATTR(nohz_full, 0444, print_cpus_nohz_full, NULL); +static DEVICE_ATTR_RO(nohz_full); #endif #ifdef CONFIG_CRASH_HOTPLUG -- cgit v1.2.3 From f10c23fa159c5481dfe0025e619dc5ef844f6ce1 Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Fri, 10 Oct 2025 21:18:30 -0400 Subject: tick/nohz: avoid showing '(null)' if nohz_full= not set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the context of CONFIG_NO_HZ_FULL=y, tick_nohz_full_mask (of type cpumask_var_t) is initialised to 0. Memory is only allocated to the cpumask data structure, in tick_nohz_full_setup(), when Linux kernel boot-time parameter "nohz_full=" is correctly specified (see housekeeping_setup()). If "nohz_full=" is not set and an attempt is made to read /sys/devices/system/cpu/nohz_full, '(null)' can be displayed: ❯ cat /sys/devices/system/cpu/nohz_full (null) This patch changes the output to print a newline (or 0x0A) instead of '(null)', making it consistent with print_cpus_isolated() behaviour. Signed-off-by: Aaron Tomlin Link: https://patch.msgid.link/20251011011830.6670-3-atomlin@atomlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/cpu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index c792ec66462d..e3cb47eae982 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -305,7 +305,10 @@ static ssize_t nohz_full_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(tick_nohz_full_mask)); + if (cpumask_available(tick_nohz_full_mask)) + return sysfs_emit(buf, "%*pbl\n", + cpumask_pr_args(tick_nohz_full_mask)); + return sysfs_emit(buf, "\n"); } static DEVICE_ATTR_RO(nohz_full); #endif -- cgit v1.2.3 From cd22926af45400093738c758b6749de8035ed5a8 Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Fri, 10 Oct 2025 21:28:53 -0400 Subject: tick/nohz: Expose housekeeping CPUs in sysfs Expose the current system-defined list of housekeeping CPUs in a new sysfs file: /sys/devices/system/cpu/housekeeping. This provides userspace performance tuning tools and resource managers with a canonical, reliable method to accurately identify the cores responsible for essential kernel maintenance workloads (RCU, timer callbacks, and unbound workqueues). Currently, tooling must manually calculate the housekeeping set by parsing complex kernel boot parameters (like isolcpus= and nohz_full=) and system topology, which is prone to error. This dedicated file simplifies the configuration of low-latency workloads. Signed-off-by: Aaron Tomlin Link: https://patch.msgid.link/20251011012853.7539-2-atomlin@atomlin.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-devices-system-cpu | 11 +++++++++++ drivers/base/cpu.c | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index 8aed6d94c4cd..3a05604c21bf 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -764,6 +764,17 @@ Description: participate in load balancing. These CPUs are set by boot parameter "isolcpus=". +What: /sys/devices/system/cpu/housekeeping +Date: Oct 2025 +Contact: Linux kernel mailing list +Description: + (RO) the list of logical CPUs that are designated by the kernel as + "housekeeping". Each CPU are responsible for handling essential + system-wide background tasks, including RCU callbacks, delayed + timer callbacks, and unbound workqueues, minimizing scheduling + jitter on low-latency, isolated CPUs. These CPUs are set when boot + parameter "isolcpus=nohz" or "nohz_full=" is specified. + What: /sys/devices/system/cpu/crash_hotplug Date: Aug 2023 Contact: Linux kernel mailing list diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index e3cb47eae982..c6c57b6f61c6 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -300,6 +300,19 @@ static ssize_t print_cpus_isolated(struct device *dev, } static DEVICE_ATTR(isolated, 0444, print_cpus_isolated, NULL); +static ssize_t housekeeping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct cpumask *hk_mask; + + hk_mask = housekeeping_cpumask(HK_TYPE_KERNEL_NOISE); + + if (housekeeping_enabled(HK_TYPE_KERNEL_NOISE)) + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(hk_mask)); + return sysfs_emit(buf, "\n"); +} +static DEVICE_ATTR_RO(housekeeping); + #ifdef CONFIG_NO_HZ_FULL static ssize_t nohz_full_show(struct device *dev, struct device_attribute *attr, @@ -509,6 +522,7 @@ static struct attribute *cpu_root_attrs[] = { &dev_attr_offline.attr, &dev_attr_enabled.attr, &dev_attr_isolated.attr, + &dev_attr_housekeeping.attr, #ifdef CONFIG_NO_HZ_FULL &dev_attr_nohz_full.attr, #endif -- cgit v1.2.3 From e40ad215cea21464d516790f263dc3df69174efe Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 14 Nov 2025 15:16:17 +0100 Subject: driver core: replace use of system_unbound_wq with system_dfl_wq Currently if a user enqueue a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistentcy cannot be addressed without refactoring the API. This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") Switch to using system_dfl_wq because system_unbound_wq is going away as part of a workqueue restructuring. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Link: https://patch.msgid.link/20251114141618.172154-2-marco.crivellari@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/dd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 13ab98e033ea..4938a132a085 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -193,7 +193,7 @@ void driver_deferred_probe_trigger(void) * Kick the re-probe thread. It may already be scheduled, but it is * safe to kick it again. */ - queue_work(system_unbound_wq, &deferred_probe_work); + queue_work(system_dfl_wq, &deferred_probe_work); } /** -- cgit v1.2.3 From ac1ab906d7a98e34be95ef63b81ff828cc432346 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 14 Nov 2025 15:16:18 +0100 Subject: driver core: WQ_PERCPU added to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently if a user enqueue a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. alloc_workqueue() treats all queues as per-CPU by default, while unbound workqueues must opt-in via WQ_UNBOUND. This default is suboptimal: most workloads benefit from unbound queues, allowing the scheduler to place worker threads where they’re needed and reducing noise when CPUs are isolated. This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") This change adds a new WQ_PERCPU flag to explicitly request alloc_workqueue() to be per-cpu when WQ_UNBOUND has not been specified. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. Once migration is complete, WQ_UNBOUND can be removed and unbound will become the implicit default. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Link: https://patch.msgid.link/20251114141618.172154-3-marco.crivellari@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/core.c b/drivers/base/core.c index f69dc9c85954..40de2f51a1b1 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -4138,7 +4138,7 @@ int __init devices_init(void) sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj); if (!sysfs_dev_char_kobj) goto char_kobj_err; - device_link_wq = alloc_workqueue("device_link_wq", 0, 0); + device_link_wq = alloc_workqueue("device_link_wq", WQ_PERCPU, 0); if (!device_link_wq) goto wq_err; -- cgit v1.2.3 From ea34511aaf755349999a1067b2984a541bee1492 Mon Sep 17 00:00:00 2001 From: Vincent Liu Date: Wed, 22 Oct 2025 13:07:40 +0100 Subject: driver core: Check drivers_autoprobe for all added devices When a device is hot-plugged, the drivers_autoprobe sysfs attribute is not checked (at least for PCI devices). This means that drivers_autoprobe is not working as intended, e.g. hot-plugged PCI devices will still be autoprobed and bound to drivers even with drivers_autoprobe disabled. The problem likely started when device_add() was removed from pci_bus_add_device() in commit 4f535093cf8f ("PCI: Put pci_dev in device tree as early as possible") which means that the check for drivers_autoprobe which used to happen in bus_probe_device() is no longer present (previously bus_add_device() calls bus_probe_device()). Conveniently, in commit 91703041697c ("PCI: Allow built-in drivers to use async initial probing") device_attach() was replaced with device_initial_probe() which faciliates this change to push the check for drivers_autoprobe into device_initial_probe(). Make sure all devices check drivers_autoprobe by pushing the drivers_autoprobe check into device_initial_probe(). This will only affect devices on the PCI bus for now as device_initial_probe() is only called by pci_bus_add_device() and bus_probe_device(), but bus_probe_device() already checks for autoprobe, so callers of bus_probe_device() should not observe changes on autoprobing. Note also that pushing this check into device_initial_probe() rather than device_attach() makes it only affect automatic probing of drivers (e.g. when a device is hot-plugged), userspace can still choose to manually bind a driver by writing to drivers_probe sysfs attribute, even with autoprobe disabled. Any future callers of device_initial_probe() will respect the drivers_autoprobe sysfs attribute, which is the intended purpose of drivers_autoprobe. Signed-off-by: Vincent Liu Link: https://patch.msgid.link/20251022120740.2476482-1-vincent.liu@nutanix.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 3 +-- drivers/base/dd.c | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 5e75e1bce551..320e155c6be7 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -533,8 +533,7 @@ void bus_probe_device(struct device *dev) if (!sp) return; - if (sp->drivers_autoprobe) - device_initial_probe(dev); + device_initial_probe(dev); mutex_lock(&sp->mutex); list_for_each_entry(sif, &sp->interfaces, node) diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 4938a132a085..349f31bedfa1 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -1077,7 +1077,15 @@ EXPORT_SYMBOL_GPL(device_attach); void device_initial_probe(struct device *dev) { - __device_attach(dev, true); + struct subsys_private *sp = bus_to_subsys(dev->bus); + + if (!sp) + return; + + if (sp->drivers_autoprobe) + __device_attach(dev, true); + + subsys_put(sp); } /* -- cgit v1.2.3 From 3ae94a55d047d133fad1e6c811befe4347b75791 Mon Sep 17 00:00:00 2001 From: Aaron Thompson Date: Thu, 20 Nov 2025 10:26:32 +0000 Subject: debugfs: Remove redundant access mode checks debugfs_get_tree() can only be called if debugfs itself calls simple_pin_fs() or register_filesystem(), and those call paths also check the access mode. debugfs_start_creating() checks the access mode so the checks in the debugfs_create_* functions are unnecessary. An upcoming change will affect debugfs_allow, so doing this cleanup first will make that change simpler. Signed-off-by: Aaron Thompson Link: https://patch.msgid.link/20251120102222.18371-2-dev@null.aaront.org Signed-off-by: Greg Kroah-Hartman --- fs/debugfs/inode.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 661a99a7dfbe..b6e401c46b6b 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -287,9 +287,6 @@ static int debugfs_get_tree(struct fs_context *fc) { int err; - if (!(debugfs_allow & DEBUGFS_ALLOW_API)) - return -EPERM; - err = get_tree_single(fc, debugfs_fill_super); if (err) return err; @@ -434,11 +431,6 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode, if (IS_ERR(dentry)) return dentry; - if (!(debugfs_allow & DEBUGFS_ALLOW_API)) { - failed_creating(dentry); - return ERR_PTR(-EPERM); - } - inode = debugfs_get_inode(dentry->d_sb); if (unlikely(!inode)) { pr_err("out of free dentries, can not create file '%s'\n", @@ -584,11 +576,6 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent) if (IS_ERR(dentry)) return dentry; - if (!(debugfs_allow & DEBUGFS_ALLOW_API)) { - failed_creating(dentry); - return ERR_PTR(-EPERM); - } - inode = debugfs_get_inode(dentry->d_sb); if (unlikely(!inode)) { pr_err("out of free dentries, can not create directory '%s'\n", @@ -631,11 +618,6 @@ struct dentry *debugfs_create_automount(const char *name, if (IS_ERR(dentry)) return dentry; - if (!(debugfs_allow & DEBUGFS_ALLOW_API)) { - failed_creating(dentry); - return ERR_PTR(-EPERM); - } - inode = debugfs_get_inode(dentry->d_sb); if (unlikely(!inode)) { pr_err("out of free dentries, can not create automount '%s'\n", -- cgit v1.2.3 From f278809475f6835b56de78b28dc2cc0c7e2c20a4 Mon Sep 17 00:00:00 2001 From: Aaron Thompson Date: Thu, 20 Nov 2025 10:26:33 +0000 Subject: debugfs: Remove broken no-mount mode debugfs access modes were added in Linux 5.10 (Dec 2020) [1], but the no-mount mode has behaved effectively the same as the off mode since Linux 5.12 (Apr 2021) [2]. The only difference is the specific error code returned by the debugfs_create_* functions, which is -ENOENT in no-mount mode and -EPERM in off mode. Given that no-mount hasn't worked for several years with no complaints, just remove it. [1] a24c6f7bc923 ("debugfs: Add access restriction option") [2] bc6de804d36b ("debugfs: be more robust at handling improper input in debugfs_lookup()") 56348560d495 ("debugfs: do not attempt to create a new file before the filesystem is initalized") Signed-off-by: Aaron Thompson Link: https://patch.msgid.link/20251120102222.18371-3-dev@null.aaront.org Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 6 +----- fs/debugfs/inode.c | 18 +++++++++++------- fs/debugfs/internal.h | 13 ------------- lib/Kconfig.debug | 9 +-------- 4 files changed, 13 insertions(+), 33 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 6c42061ca20e..847a17efe289 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1113,12 +1113,8 @@ debugfs= [KNL,EARLY] This parameter enables what is exposed to userspace and debugfs internal clients. - Format: { on, no-mount, off } + Format: { on, off } on: All functions are enabled. - no-mount: - Filesystem is not registered but kernel clients can - access APIs and a crashkernel can be used to read - its content. There is nothing to mount. off: Filesystem is not registered and clients get a -EPERM as result when trying to register files or directories within debugfs. diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index b6e401c46b6b..0284b0256195 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -35,7 +35,7 @@ static struct vfsmount *debugfs_mount; static int debugfs_mount_count; static bool debugfs_registered; -static unsigned int debugfs_allow __ro_after_init = DEFAULT_DEBUGFS_ALLOW_BITS; +static bool debugfs_enabled __ro_after_init = IS_ENABLED(DEBUG_FS_ALLOW_ALL); /* * Don't allow access attributes to be changed whilst the kernel is locked down @@ -365,7 +365,7 @@ static struct dentry *debugfs_start_creating(const char *name, struct dentry *dentry; int error; - if (!(debugfs_allow & DEBUGFS_ALLOW_API)) + if (!debugfs_enabled) return ERR_PTR(-EPERM); if (!debugfs_initialized()) @@ -885,21 +885,25 @@ static int __init debugfs_kernel(char *str) { if (str) { if (!strcmp(str, "on")) - debugfs_allow = DEBUGFS_ALLOW_API | DEBUGFS_ALLOW_MOUNT; - else if (!strcmp(str, "no-mount")) - debugfs_allow = DEBUGFS_ALLOW_API; + debugfs_enabled = true; else if (!strcmp(str, "off")) - debugfs_allow = 0; + debugfs_enabled = false; + else if (!strcmp(str, "no-mount")) { + pr_notice("debugfs=no-mount is a deprecated alias " + "for debugfs=off\n"); + debugfs_enabled = false; + } } return 0; } early_param("debugfs", debugfs_kernel); + static int __init debugfs_init(void) { int retval; - if (!(debugfs_allow & DEBUGFS_ALLOW_MOUNT)) + if (!debugfs_enabled) return -EPERM; retval = sysfs_create_mount_point(kernel_kobj, "debug"); diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h index 427987f81571..c95699b27a56 100644 --- a/fs/debugfs/internal.h +++ b/fs/debugfs/internal.h @@ -55,17 +55,4 @@ enum { HAS_IOCTL = 16 }; -#define DEBUGFS_ALLOW_API BIT(0) -#define DEBUGFS_ALLOW_MOUNT BIT(1) - -#ifdef CONFIG_DEBUG_FS_ALLOW_ALL -#define DEFAULT_DEBUGFS_ALLOW_BITS (DEBUGFS_ALLOW_MOUNT | DEBUGFS_ALLOW_API) -#endif -#ifdef CONFIG_DEBUG_FS_DISALLOW_MOUNT -#define DEFAULT_DEBUGFS_ALLOW_BITS (DEBUGFS_ALLOW_API) -#endif -#ifdef CONFIG_DEBUG_FS_ALLOW_NONE -#define DEFAULT_DEBUGFS_ALLOW_BITS (0) -#endif - #endif /* _DEBUGFS_INTERNAL_H_ */ diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 3034e294d50d..d9ab42916143 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -679,7 +679,7 @@ choice help This selects the default access restrictions for debugfs. It can be overridden with kernel command line option - debugfs=[on,no-mount,off]. The restrictions apply for API access + debugfs=[on,off]. The restrictions apply for API access and filesystem registration. config DEBUG_FS_ALLOW_ALL @@ -688,13 +688,6 @@ config DEBUG_FS_ALLOW_ALL No restrictions apply. Both API and filesystem registration is on. This is the normal default operation. -config DEBUG_FS_DISALLOW_MOUNT - bool "Do not register debugfs as filesystem" - help - The API is open but filesystem is not loaded. Clients can still do - their work and read with debug tools that do not need - debugfs filesystem. - config DEBUG_FS_ALLOW_NONE bool "No access" help -- cgit v1.2.3 From d3666c1f8a31b7ff6805effcfedfac22454c6517 Mon Sep 17 00:00:00 2001 From: Aaron Thompson Date: Tue, 2 Dec 2025 07:12:40 +0000 Subject: debugfs: Fix default access mode config check This typo caused debugfs to always behave as if CONFIG_DEBUG_FS_ALLOW_NONE was selected. Fixes: f278809475f6 ("debugfs: Remove broken no-mount mode") Reported-by: Mark Brown Tested-by: Chen-Yu Tsai Signed-off-by: Aaron Thompson Link: https://patch.msgid.link/20251202070927.14198-1-dev@null.aaront.org Signed-off-by: Greg Kroah-Hartman --- fs/debugfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 0284b0256195..3b3713fcbbcb 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -35,7 +35,7 @@ static struct vfsmount *debugfs_mount; static int debugfs_mount_count; static bool debugfs_registered; -static bool debugfs_enabled __ro_after_init = IS_ENABLED(DEBUG_FS_ALLOW_ALL); +static bool debugfs_enabled __ro_after_init = IS_ENABLED(CONFIG_DEBUG_FS_ALLOW_ALL); /* * Don't allow access attributes to be changed whilst the kernel is locked down -- cgit v1.2.3 From 473b9f331718267815649cd93801da832200db71 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 3 Dec 2025 10:01:50 +1300 Subject: rust: pci: fix build failure when CONFIG_PCI_MSI is disabled When CONFIG_PCI_MSI is disabled pci_alloc_irq_vectors() and pci_free_irq_vectors() are defined as inline functions and hence require a Rust helper. error[E0425]: cannot find function `pci_alloc_irq_vectors` in crate `bindings` --> rust/kernel/pci/irq.rs:144:23 | 144 | ...s::pci_alloc_irq_vectors(dev.as_raw(), min_vecs, max_vecs, irq_types.as_raw()) | ^^^^^^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `pci_irq_vector` | ::: .../rust/bindings/bindings_helpers_generated.rs:1197:5 | 1197 | pub fn pci_irq_vector(pdev: *mut pci_dev, nvec: ffi::c_uint) -> ffi::c_int; | --------------------------------------------------------------------------- similarly named function `pci_irq_vector` defined here error[E0425]: cannot find function `pci_free_irq_vectors` in crate `bindings` --> rust/kernel/pci/irq.rs:170:28 | 170 | unsafe { bindings::pci_free_irq_vectors(self.dev.as_raw()) }; | ^^^^^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `pci_irq_vector` | ::: .../rust/bindings/bindings_helpers_generated.rs:1197:5 | 1197 | pub fn pci_irq_vector(pdev: *mut pci_dev, nvec: ffi::c_uint) -> ffi::c_int; | --------------------------------------------------------------------------- similarly named function `pci_irq_vector` defined here error: aborting due to 2 previous errors Fix this by adding the corresponding helpers. Fixes: 340ccc973544 ("rust: pci: Allocate and manage PCI interrupt vectors") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202512012238.YgVvRRUx-lkp@intel.com/ Reviewed-by: Alice Ryhl Reviewed-by: Joel Fernandes Link: https://patch.msgid.link/20251202210501.40998-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/helpers/pci.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rust/helpers/pci.c b/rust/helpers/pci.c index fb814572b236..bf8173979c5e 100644 --- a/rust/helpers/pci.c +++ b/rust/helpers/pci.c @@ -23,9 +23,21 @@ bool rust_helper_dev_is_pci(const struct device *dev) } #ifndef CONFIG_PCI_MSI +int rust_helper_pci_alloc_irq_vectors(struct pci_dev *dev, + unsigned int min_vecs, + unsigned int max_vecs, + unsigned int flags) +{ + return pci_alloc_irq_vectors(dev, min_vecs, max_vecs, flags); +} + +void rust_helper_pci_free_irq_vectors(struct pci_dev *dev) +{ + pci_free_irq_vectors(dev); +} + int rust_helper_pci_irq_vector(struct pci_dev *pdev, unsigned int nvec) { return pci_irq_vector(pdev, nvec); } - #endif -- cgit v1.2.3