]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
Merge tag 'driver-core-6.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 6 Dec 2025 05:29:02 +0000 (21:29 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 6 Dec 2025 05:29:02 +0000 (21:29 -0800)
Pull driver core updates from Danilo Krummrich:
 "Arch Topology:
   - Move parse_acpi_topology() from arm64 to common code for reuse in
     RISC-V

  CPU:
   - Expose housekeeping CPUs through /sys/devices/system/cpu/housekeeping
   - Print a newline (or 0x0A) instead of '(null)' reading
     /sys/devices/system/cpu/nohz_full when nohz_full= is not set

  debugfs
   - Remove (broken) 'no-mount' mode
   - Remove redundant access mode checks in debugfs_get_tree() and
     debugfs_create_*() functions

  Devres:
   - Remove unused devm_free_percpu() helper
   - Move devm_alloc_percpu() from device.h to devres.h

  Firmware Loader:
   - Replace simple_strtol() with kstrtoint()
   - Do not call cancel_store() when no upload is in progress

  kernfs:
   - Increase struct super_block::maxbytes to MAX_LFS_FILESIZE
   - Fix a missing unwind path in __kernfs_new_node()

  Misc:
   - Increase the name size in struct auxiliary_device_id to 40
     characters
   - Replace system_unbound_wq with system_dfl_wq and add WQ_PERCPU to
     alloc_workqueue()

  Platform:
   - Replace ERR_PTR() with IOMEM_ERR_PTR() in platform ioremap
     functions

  Rust:
   - Auxiliary:
      - Unregister auxiliary device on parent device unbind
      - Move parent() to impl Device; implement device context aware
        parent() for Device<Bound>
      - Illustrate how to safely obtain a driver's device private data
        when calling from an auxiliary driver into the parant device
        driver

   - DebugFs:
      - Implement support for binary large objects

   - Device:
      - Let probe() return the driver's device private data as pinned
        initializer, i.e. impl PinInit<Self, Error>
      - Implement safe accessor for a driver's device private data for
        Device<Bound> (returned reference can't out-live driver binding
        and guarantees the correct private data type)
      - Implement AsBusDevice trait, to be used by class device
        abstractions to derive the bus device type of the parent device

   - DMA:
      - Store raw pointer of allocation as NonNull
      - Use start_ptr() and start_ptr_mut() to inherit correct
        mutability of self

   - FS:
      - Add file::Offset type alias

   - I2C:
      - Add abstractions for I2C device / driver infrastructure
      - Implement abstractions for manual I2C device registrations

   - I/O:
      - Use "kernel vertical" style for imports
      - Define ResourceSize as resource_size_t
      - Move ResourceSize to top-level I/O module
      - Add type alias for phys_addr_t
      - Implement Rust version of read_poll_timeout_atomic()

   - PCI:
      - Use "kernel vertical" style for imports
      - Move I/O and IRQ infrastructure to separate files
      - Add support for PCI interrupt vectors
      - Implement TryInto<IrqRequest<'a>> for IrqVector<'a> to convert
        an IrqVector bound to specific pci::Device into an IrqRequest
        bound to the same pci::Device's parent Device
      - Leverage pin_init_scope() to get rid of redundant Result in IRQ
        methods

   - PinInit:
      - Add {pin_}init_scope() to execute code before creating an
        initializer

   - Platform:
      - Leverage pin_init_scope() to get rid of redundant Result in IRQ
        methods

   - Timekeeping:
      - Implement abstraction of udelay()

   - Uaccess:
      - Implement read_slice_partial() and read_slice_file() for
        UserSliceReader
      - Implement write_slice_partial() and write_slice_file() for
        UserSliceWriter

  sysfs:
   - Prepare the constification of struct attribute"

* tag 'driver-core-6.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core: (75 commits)
  rust: pci: fix build failure when CONFIG_PCI_MSI is disabled
  debugfs: Fix default access mode config check
  debugfs: Remove broken no-mount mode
  debugfs: Remove redundant access mode checks
  driver core: Check drivers_autoprobe for all added devices
  driver core: WQ_PERCPU added to alloc_workqueue users
  driver core: replace use of system_unbound_wq with system_dfl_wq
  tick/nohz: Expose housekeeping CPUs in sysfs
  tick/nohz: avoid showing '(null)' if nohz_full= not set
  sysfs/cpu: Use DEVICE_ATTR_RO for nohz_full attribute
  kernfs: fix memory leak of kernfs_iattrs in __kernfs_new_node
  fs/kernfs: raise sb->maxbytes to MAX_LFS_FILESIZE
  mod_devicetable: Bump auxiliary_device_id name size
  sysfs: simplify attribute definition macros
  samples/kobject: constify 'struct foo_attribute'
  samples/kobject: add is_visible() callback to attribute group
  sysfs: attribute_group: enable const variants of is_visible()
  sysfs: introduce __SYSFS_FUNCTION_ALTERNATIVE()
  sysfs: transparently handle const pointers in ATTRIBUTE_GROUPS()
  sysfs: attribute_group: allow registration of const attribute
  ...

19 files changed:
1  2 
Documentation/admin-guide/kernel-parameters.txt
MAINTAINERS
drivers/gpu/nova-core/driver.rs
drivers/pwm/pwm_th1520.rs
fs/debugfs/inode.c
include/linux/arch_topology.h
include/linux/platform_device.h
lib/Kconfig.debug
rust/bindings/bindings_helper.h
rust/kernel/debugfs.rs
rust/kernel/debugfs/file_ops.rs
rust/kernel/debugfs/traits.rs
rust/kernel/device.rs
rust/kernel/devres.rs
rust/kernel/lib.rs
rust/kernel/pci/id.rs
samples/rust/rust_debugfs.rs
samples/rust/rust_debugfs_scoped.rs
samples/rust/rust_driver_platform.rs

diff --cc MAINTAINERS
Simple merge
index d91bbc50cde7e9da9d3050327c430493e3f1d40c,ca0d5f8ad54bb74e7b31d79525e3af64abebd7eb..b8b0cc0f2d93599358c8cd6562d23c27e4654a87
@@@ -1,20 -1,14 +1,21 @@@
  // SPDX-License-Identifier: GPL-2.0
  
  use kernel::{
 -    auxiliary, c_str,
 +    auxiliary,
 +    c_str,
      device::Core,
+     devres::Devres,
 +    dma::Device,
 +    dma::DmaMask,
      pci,
 -    pci::{Class, ClassMask, Vendor},
 +    pci::{
 +        Class,
 +        ClassMask,
 +        Vendor, //
 +    },
      prelude::*,
      sizes::SZ_16M,
 -    sync::Arc,
 +    sync::Arc, //
  };
  
  use crate::gpu::Gpu;
@@@ -67,31 -53,21 +69,26 @@@ impl pci::Driver for NovaCore 
      type IdInfo = ();
      const ID_TABLE: pci::IdTable<Self::IdInfo> = &PCI_TABLE;
  
-     fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> Result<Pin<KBox<Self>>> {
-         dev_dbg!(pdev.as_ref(), "Probe Nova Core GPU driver.\n");
-         pdev.enable_device_mem()?;
-         pdev.set_master();
+     fn probe(pdev: &pci::Device<Core>, _info: &Self::IdInfo) -> impl PinInit<Self, Error> {
+         pin_init::pin_init_scope(move || {
+             dev_dbg!(pdev.as_ref(), "Probe Nova Core GPU driver.\n");
  
-         // SAFETY: No concurrent DMA allocations or mappings can be made because
-         // the device is still being probed and therefore isn't being used by
-         // other threads of execution.
-         unsafe { pdev.dma_set_mask_and_coherent(DmaMask::new::<GPU_DMA_BITS>())? };
+             pdev.enable_device_mem()?;
+             pdev.set_master();
  
-         let devres_bar = Arc::pin_init(
-             pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0")),
-             GFP_KERNEL,
-         )?;
++            // SAFETY: No concurrent DMA allocations or mappings can be made because
++            // the device is still being probed and therefore isn't being used by
++            // other threads of execution.
++            unsafe { pdev.dma_set_mask_and_coherent(DmaMask::new::<GPU_DMA_BITS>())? };
 +
-         // 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 bar = Arc::pin_init(
+                 pdev.iomap_region_sized::<BAR0_SIZE>(0, c_str!("nova-core/bar0")),
+                 GFP_KERNEL,
+             )?;
  
-         let this = KBox::pin_init(
-             try_pin_init!(Self {
-                 gpu <- Gpu::new(pdev, devres_bar, bar),
-                 _reg: auxiliary::Registration::new(
+             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.
index 955c359b07fb4fc9d33c595a3c139d094976a4a3,0000000000000000000000000000000000000000..e3b7e77356fc2492077c519073e861beb3e44df9
mode 100644,000000..100644
--- /dev/null
@@@ -1,387 -1,0 +1,387 @@@
-     ) -> Result<Pin<KBox<Self>>> {
 +// SPDX-License-Identifier: GPL-2.0
 +// Copyright (c) 2025 Samsung Electronics Co., Ltd.
 +// Author: Michal Wilczynski <m.wilczynski@samsung.com>
 +
 +//! Rust T-HEAD TH1520 PWM driver
 +//!
 +//! Limitations:
 +//! - The period and duty cycle are controlled by 32-bit hardware registers,
 +//!   limiting the maximum resolution.
 +//! - The driver supports continuous output mode only; one-shot mode is not
 +//!   implemented.
 +//! - The controller hardware provides up to 6 PWM channels.
 +//! - Reconfiguration is glitch free - new period and duty cycle values are
 +//!   latched and take effect at the start of the next period.
 +//! - Polarity is handled via a simple hardware inversion bit; arbitrary
 +//!   duty cycle offsets are not supported.
 +//! - Disabling a channel is achieved by configuring its duty cycle to zero to
 +//!   produce a static low output. Clearing the `start` does not reliably
 +//!   force the static inactive level defined by the `INACTOUT` bit. Hence
 +//!   this method is not used in this driver.
 +//!
 +
 +use core::ops::Deref;
 +use kernel::{
 +    c_str,
 +    clk::Clk,
 +    device::{Bound, Core, Device},
 +    devres,
 +    io::mem::IoMem,
 +    of, platform,
 +    prelude::*,
 +    pwm, time,
 +};
 +
 +const TH1520_MAX_PWM_NUM: u32 = 6;
 +
 +// Register offsets
 +const fn th1520_pwm_chn_base(n: u32) -> usize {
 +    (n * 0x20) as usize
 +}
 +
 +const fn th1520_pwm_ctrl(n: u32) -> usize {
 +    th1520_pwm_chn_base(n)
 +}
 +
 +const fn th1520_pwm_per(n: u32) -> usize {
 +    th1520_pwm_chn_base(n) + 0x08
 +}
 +
 +const fn th1520_pwm_fp(n: u32) -> usize {
 +    th1520_pwm_chn_base(n) + 0x0c
 +}
 +
 +// Control register bits
 +const TH1520_PWM_START: u32 = 1 << 0;
 +const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2;
 +const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5;
 +const TH1520_PWM_FPOUT: u32 = 1 << 8;
 +
 +const TH1520_PWM_REG_SIZE: usize = 0xB0;
 +
 +fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 {
 +    const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
 +
 +    (match ns.checked_mul(rate_hz) {
 +        Some(product) => product,
 +        None => u64::MAX,
 +    }) / NSEC_PER_SEC_U64
 +}
 +
 +fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 {
 +    const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
 +
 +    // TODO: Replace with a kernel helper like `mul_u64_u64_div_u64_roundup`
 +    // once available in Rust.
 +    let numerator = cycles
 +        .saturating_mul(NSEC_PER_SEC_U64)
 +        .saturating_add(rate_hz - 1);
 +
 +    numerator / rate_hz
 +}
 +
 +/// Hardware-specific waveform representation for TH1520.
 +#[derive(Copy, Clone, Debug, Default)]
 +struct Th1520WfHw {
 +    period_cycles: u32,
 +    duty_cycles: u32,
 +    ctrl_val: u32,
 +    enabled: bool,
 +}
 +
 +/// The driver's private data struct. It holds all necessary devres managed resources.
 +#[pin_data(PinnedDrop)]
 +struct Th1520PwmDriverData {
 +    #[pin]
 +    iomem: devres::Devres<IoMem<TH1520_PWM_REG_SIZE>>,
 +    clk: Clk,
 +}
 +
 +// This `unsafe` implementation is a temporary necessity because the underlying `kernel::clk::Clk`
 +// type does not yet expose `Send` and `Sync` implementations. This block should be removed
 +// as soon as the clock abstraction provides these guarantees directly.
 +// TODO: Remove those unsafe impl's when Clk will support them itself.
 +
 +// SAFETY: The `devres` framework requires the driver's private data to be `Send` and `Sync`.
 +// We can guarantee this because the PWM core synchronizes all callbacks, preventing concurrent
 +// access to the contained `iomem` and `clk` resources.
 +unsafe impl Send for Th1520PwmDriverData {}
 +
 +// SAFETY: The same reasoning applies as for `Send`. The PWM core's synchronization
 +// guarantees that it is safe for multiple threads to have shared access (`&self`)
 +// to the driver data during callbacks.
 +unsafe impl Sync for Th1520PwmDriverData {}
 +
 +impl pwm::PwmOps for Th1520PwmDriverData {
 +    type WfHw = Th1520WfHw;
 +
 +    fn round_waveform_tohw(
 +        chip: &pwm::Chip<Self>,
 +        _pwm: &pwm::Device,
 +        wf: &pwm::Waveform,
 +    ) -> Result<pwm::RoundedWaveform<Self::WfHw>> {
 +        let data = chip.drvdata();
 +        let mut status = 0;
 +
 +        if wf.period_length_ns == 0 {
 +            dev_dbg!(chip.device(), "Requested period is 0, disabling PWM.\n");
 +
 +            return Ok(pwm::RoundedWaveform {
 +                status: 0,
 +                hardware_waveform: Th1520WfHw {
 +                    enabled: false,
 +                    ..Default::default()
 +                },
 +            });
 +        }
 +
 +        let rate_hz = data.clk.rate().as_hz() as u64;
 +
 +        let mut period_cycles = ns_to_cycles(wf.period_length_ns, rate_hz).min(u64::from(u32::MAX));
 +
 +        if period_cycles == 0 {
 +            dev_dbg!(
 +                chip.device(),
 +                "Requested period {} ns is too small for clock rate {} Hz, rounding up.\n",
 +                wf.period_length_ns,
 +                rate_hz
 +            );
 +
 +            period_cycles = 1;
 +            status = 1;
 +        }
 +
 +        let mut duty_cycles = ns_to_cycles(wf.duty_length_ns, rate_hz).min(u64::from(u32::MAX));
 +
 +        let mut ctrl_val = TH1520_PWM_CONTINUOUS_MODE;
 +
 +        let is_inversed = wf.duty_length_ns > 0
 +            && wf.duty_offset_ns > 0
 +            && wf.duty_offset_ns >= wf.period_length_ns.saturating_sub(wf.duty_length_ns);
 +        if is_inversed {
 +            duty_cycles = period_cycles - duty_cycles;
 +        } else {
 +            ctrl_val |= TH1520_PWM_FPOUT;
 +        }
 +
 +        let wfhw = Th1520WfHw {
 +            // The cast is safe because the value was clamped with `.min(u64::from(u32::MAX))`.
 +            period_cycles: period_cycles as u32,
 +            duty_cycles: duty_cycles as u32,
 +            ctrl_val,
 +            enabled: true,
 +        };
 +
 +        dev_dbg!(
 +            chip.device(),
 +            "Requested: {}/{} ns [+{} ns] -> HW: {}/{} cycles, ctrl 0x{:x}, rate {} Hz\n",
 +            wf.duty_length_ns,
 +            wf.period_length_ns,
 +            wf.duty_offset_ns,
 +            wfhw.duty_cycles,
 +            wfhw.period_cycles,
 +            wfhw.ctrl_val,
 +            rate_hz
 +        );
 +
 +        Ok(pwm::RoundedWaveform {
 +            status,
 +            hardware_waveform: wfhw,
 +        })
 +    }
 +
 +    fn round_waveform_fromhw(
 +        chip: &pwm::Chip<Self>,
 +        _pwm: &pwm::Device,
 +        wfhw: &Self::WfHw,
 +        wf: &mut pwm::Waveform,
 +    ) -> Result {
 +        let data = chip.drvdata();
 +        let rate_hz = data.clk.rate().as_hz() as u64;
 +
 +        if wfhw.period_cycles == 0 {
 +            dev_dbg!(
 +                chip.device(),
 +                "HW state has zero period, reporting as disabled.\n"
 +            );
 +            *wf = pwm::Waveform::default();
 +            return Ok(());
 +        }
 +
 +        wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz);
 +
 +        let duty_cycles = u64::from(wfhw.duty_cycles);
 +
 +        if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 {
 +            wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz);
 +            wf.duty_offset_ns = 0;
 +        } else {
 +            let period_cycles = u64::from(wfhw.period_cycles);
 +            let original_duty_cycles = period_cycles.saturating_sub(duty_cycles);
 +
 +            // For an inverted signal, `duty_length_ns` is the high time (period - low_time).
 +            wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz);
 +            // The offset is the initial low time, which is what the hardware register provides.
 +            wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz);
 +        }
 +
 +        Ok(())
 +    }
 +
 +    fn read_waveform(
 +        chip: &pwm::Chip<Self>,
 +        pwm: &pwm::Device,
 +        parent_dev: &Device<Bound>,
 +    ) -> Result<Self::WfHw> {
 +        let data = chip.drvdata();
 +        let hwpwm = pwm.hwpwm();
 +        let iomem_accessor = data.iomem.access(parent_dev)?;
 +        let iomap = iomem_accessor.deref();
 +
 +        let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?;
 +        let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?;
 +        let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
 +
 +        let wfhw = Th1520WfHw {
 +            period_cycles,
 +            duty_cycles,
 +            ctrl_val: ctrl,
 +            enabled: duty_cycles != 0,
 +        };
 +
 +        dev_dbg!(
 +            chip.device(),
 +            "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}",
 +            hwpwm,
 +            wfhw.period_cycles,
 +            wfhw.duty_cycles,
 +            wfhw.ctrl_val,
 +            wfhw.enabled
 +        );
 +
 +        Ok(wfhw)
 +    }
 +
 +    fn write_waveform(
 +        chip: &pwm::Chip<Self>,
 +        pwm: &pwm::Device,
 +        wfhw: &Self::WfHw,
 +        parent_dev: &Device<Bound>,
 +    ) -> Result {
 +        let data = chip.drvdata();
 +        let hwpwm = pwm.hwpwm();
 +        let iomem_accessor = data.iomem.access(parent_dev)?;
 +        let iomap = iomem_accessor.deref();
 +        let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
 +        let was_enabled = duty_cycles != 0;
 +
 +        if !wfhw.enabled {
 +            dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm);
 +            if was_enabled {
 +                iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
 +                iomap.try_write32(0, th1520_pwm_fp(hwpwm))?;
 +                iomap.try_write32(
 +                    wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
 +                    th1520_pwm_ctrl(hwpwm),
 +                )?;
 +            }
 +            return Ok(());
 +        }
 +
 +        iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
 +        iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?;
 +        iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?;
 +        iomap.try_write32(
 +            wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
 +            th1520_pwm_ctrl(hwpwm),
 +        )?;
 +
 +        // The `TH1520_PWM_START` bit must be written in a separate, final transaction, and
 +        // only when enabling the channel from a disabled state.
 +        if !was_enabled {
 +            iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?;
 +        }
 +
 +        dev_dbg!(
 +            chip.device(),
 +            "PWM-{}: Wrote {}/{} cycles",
 +            hwpwm,
 +            wfhw.duty_cycles,
 +            wfhw.period_cycles,
 +        );
 +
 +        Ok(())
 +    }
 +}
 +
 +#[pinned_drop]
 +impl PinnedDrop for Th1520PwmDriverData {
 +    fn drop(self: Pin<&mut Self>) {
 +        self.clk.disable_unprepare();
 +    }
 +}
 +
 +struct Th1520PwmPlatformDriver;
 +
 +kernel::of_device_table!(
 +    OF_TABLE,
 +    MODULE_OF_TABLE,
 +    <Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
 +    [(of::DeviceId::new(c_str!("thead,th1520-pwm")), ())]
 +);
 +
 +impl platform::Driver for Th1520PwmPlatformDriver {
 +    type IdInfo = ();
 +    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
 +
 +    fn probe(
 +        pdev: &platform::Device<Core>,
 +        _id_info: Option<&Self::IdInfo>,
-         Ok(KBox::new(Th1520PwmPlatformDriver, GFP_KERNEL)?.into())
++    ) -> impl PinInit<Self, Error> {
 +        let dev = pdev.as_ref();
 +        let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
 +
 +        let clk = Clk::get(dev, None)?;
 +
 +        clk.prepare_enable()?;
 +
 +        // TODO: Get exclusive ownership of the clock to prevent rate changes.
 +        // The Rust equivalent of `clk_rate_exclusive_get()` is not yet available.
 +        // This should be updated once it is implemented.
 +        let rate_hz = clk.rate().as_hz();
 +        if rate_hz == 0 {
 +            dev_err!(dev, "Clock rate is zero\n");
 +            return Err(EINVAL);
 +        }
 +
 +        if rate_hz > time::NSEC_PER_SEC as usize {
 +            dev_err!(
 +                dev,
 +                "Clock rate {} Hz is too high, not supported.\n",
 +                rate_hz
 +            );
 +            return Err(EINVAL);
 +        }
 +
 +        let chip = pwm::Chip::new(
 +            dev,
 +            TH1520_MAX_PWM_NUM,
 +            try_pin_init!(Th1520PwmDriverData {
 +                iomem <- request.iomap_sized::<TH1520_PWM_REG_SIZE>(),
 +                clk <- clk,
 +            }),
 +        )?;
 +
 +        pwm::Registration::register(dev, chip)?;
 +
++        Ok(Th1520PwmPlatformDriver)
 +    }
 +}
 +
 +kernel::module_pwm_platform_driver! {
 +    type: Th1520PwmPlatformDriver,
 +    name: "pwm-th1520",
 +    authors: ["Michal Wilczynski <m.wilczynski@samsung.com>"],
 +    description: "T-HEAD TH1520 PWM driver",
 +    license: "GPL v2",
 +}
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 9ad5e3fa6f699d348bc210f97116e11cda81ab31,6c8928902a0b8a7b9a2b4d3af48135a7b5e32584..8a0442d6dd7a4d7d9c54af204eef39a739b19994
@@@ -1,9 -1,9 +1,10 @@@
  // 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::fmt;
+ use crate::fs::file;
  use crate::prelude::*;
  use crate::seq_file::SeqFile;
  use crate::seq_print;
index e8a8a98f18dca33b9da068be2a21b82bb43817b8,82441ac8adaaba5e9c896b6848530000ec6d856e..3eee60463fd59e7334610dacc3cb109a92917f7a
@@@ -3,12 -3,20 +3,17 @@@
  
  //! Traits for rendering or updating values exported to DebugFS.
  
+ use crate::alloc::Allocator;
 +use crate::fmt;
+ use crate::fs::file;
  use crate::prelude::*;
 +use crate::sync::atomic::{Atomic, AtomicBasicOps, AtomicType, Relaxed};
+ use crate::sync::Arc;
  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::ops::{Deref, DerefMut};
  use core::str::FromStr;
 -use core::sync::atomic::{
 -    AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, AtomicU64,
 -    AtomicU8, AtomicUsize, Ordering,
 -};
  
  /// A trait for types that can be written into a string.
  ///
@@@ -63,21 -175,164 +172,148 @@@ impl<T: FromStr + Unpin> Reader for Mut
      }
  }
  
 -
 -macro_rules! impl_reader_for_atomic {
 -    ($(($atomic_type:ty, $int_type:ty)),*) => {
 -        $(
 -            impl Reader for $atomic_type {
 -                fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result {
 -                    let mut buf = [0u8; 21]; // Enough for a 64-bit number.
 -                    if reader.len() > buf.len() {
 -                        return Err(EINVAL);
 -                    }
 -                    let n = reader.len();
 -                    reader.read_slice(&mut buf[..n])?;
 -
 -                    let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?;
 -                    let val = s.trim().parse::<$int_type>().map_err(|_| EINVAL)?;
 -                    self.store(val, Ordering::Relaxed);
 -                    Ok(())
 -                }
 -            }
 -        )*
 -    };
 -}
 -
 -impl_reader_for_atomic!(
 -    (AtomicI16, i16),
 -    (AtomicI32, i32),
 -    (AtomicI64, i64),
 -    (AtomicI8, i8),
 -    (AtomicIsize, isize),
 -    (AtomicU16, u16),
 -    (AtomicU32, u32),
 -    (AtomicU64, u64),
 -    (AtomicU8, u8),
 -    (AtomicUsize, usize)
 -);
 +impl<T: AtomicType + FromStr> Reader for Atomic<T>
 +where
 +    T::Repr: AtomicBasicOps,
 +{
 +    fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result {
 +        let mut buf = [0u8; 21]; // Enough for a 64-bit number.
 +        if reader.len() > buf.len() {
 +            return Err(EINVAL);
 +        }
 +        let n = reader.len();
 +        reader.read_slice(&mut buf[..n])?;
 +
 +        let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?;
 +        let val = s.trim().parse::<T>().map_err(|_| EINVAL)?;
 +        self.store(val, Relaxed);
 +        Ok(())
 +    }
 +}
++
+ /// 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<usize>;
+ }
+ // Base implementation for any `T: AsBytes + FromBytes`.
+ impl<T: AsBytes + FromBytes> BinaryReaderMut for T {
+     fn read_from_slice_mut(
+         &mut self,
+         reader: &mut UserSliceReader,
+         offset: &mut file::Offset,
+     ) -> Result<usize> {
+         reader.read_slice_file(self.as_bytes_mut(), offset)
+     }
+ }
+ // Delegate for `Box<T, A>`: Support a `Box<T, A>` with an outer lock.
+ impl<T: ?Sized + BinaryReaderMut, A: Allocator> BinaryReaderMut for Box<T, A> {
+     fn read_from_slice_mut(
+         &mut self,
+         reader: &mut UserSliceReader,
+         offset: &mut file::Offset,
+     ) -> Result<usize> {
+         self.deref_mut().read_from_slice_mut(reader, offset)
+     }
+ }
+ // Delegate for `Vec<T, A>`: Support a `Vec<T, A>` with an outer lock.
+ impl<T, A> BinaryReaderMut for Vec<T, A>
+ where
+     T: AsBytes + FromBytes,
+     A: Allocator,
+ {
+     fn read_from_slice_mut(
+         &mut self,
+         reader: &mut UserSliceReader,
+         offset: &mut file::Offset,
+     ) -> Result<usize> {
+         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`.
+     ///
+     /// `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<usize>;
+ }
+ // Delegate for `Mutex<T>`: Support a `T` with an outer `Mutex`.
+ impl<T: BinaryReaderMut + Unpin> BinaryReader for Mutex<T> {
+     fn read_from_slice(
+         &self,
+         reader: &mut UserSliceReader,
+         offset: &mut file::Offset,
+     ) -> Result<usize> {
+         let mut this = self.lock();
+         this.read_from_slice_mut(reader, offset)
+     }
+ }
+ // Delegate for `Box<T, A>`: Support a `Box<T, A>` with an inner lock.
+ impl<T: ?Sized + BinaryReader, A: Allocator> BinaryReader for Box<T, A> {
+     fn read_from_slice(
+         &self,
+         reader: &mut UserSliceReader,
+         offset: &mut file::Offset,
+     ) -> Result<usize> {
+         self.deref().read_from_slice(reader, offset)
+     }
+ }
+ // Delegate for `Pin<Box<T, A>>`: Support a `Pin<Box<T, A>>` with an inner lock.
+ impl<T: ?Sized + BinaryReader, A: Allocator> BinaryReader for Pin<Box<T, A>> {
+     fn read_from_slice(
+         &self,
+         reader: &mut UserSliceReader,
+         offset: &mut file::Offset,
+     ) -> Result<usize> {
+         self.deref().read_from_slice(reader, offset)
+     }
+ }
+ // Delegate for `Arc<T>`: Support an `Arc<T>` with an inner lock.
+ impl<T: ?Sized + BinaryReader> BinaryReader for Arc<T> {
+     fn read_from_slice(
+         &self,
+         reader: &mut UserSliceReader,
+         offset: &mut file::Offset,
+     ) -> Result<usize> {
+         self.deref().read_from_slice(reader, offset)
+     }
+ }
Simple merge
Simple merge
Simple merge
index 5f5d59ff49fca052e661eb006874378d5b37cefe,a1de70b2176a76b64fdcad4cedb8387162dedf3d..c09125946d9e154372a53acdefd696cec1f4990e
@@@ -4,7 -4,11 +4,11 @@@
  //!
  //! This module contains PCI class codes, Vendor IDs, and supporting types.
  
- use crate::{bindings, error::code::EINVAL, error::Error, fmt, prelude::*};
+ use crate::{
+     bindings,
++    fmt,
+     prelude::*, //
+ };
 -use core::fmt;
  
  /// PCI device class codes.
  ///
index 711faa07bece3e9e487acae9f754dfde708bc831,c45b568d951b45e37373149ec23bceb33a172616..025e8f9d12dee64508ffab198054144c4a04c324
@@@ -36,8 -38,9 +36,9 @@@ use kernel::c_str
  use kernel::debugfs::{Dir, File};
  use kernel::new_mutex;
  use kernel::prelude::*;
+ use kernel::sizes::*;
 +use kernel::sync::atomic::{Atomic, Relaxed};
  use kernel::sync::Mutex;
 -
  use kernel::{acpi, device::Core, of, platform, str::CString, types::ARef};
  
  kernel::module_platform_driver! {
@@@ -57,9 -60,13 +58,13 @@@ struct RustDebugFs 
      #[pin]
      _compatible: File<CString>,
      #[pin]
 -    counter: File<AtomicUsize>,
 +    counter: File<Atomic<usize>>,
      #[pin]
      inner: File<Mutex<Inner>>,
+     #[pin]
+     array_blob: File<Mutex<[u8; 4]>>,
+     #[pin]
+     vector_blob: File<Mutex<KVec<u8>>>,
  }
  
  #[derive(Debug)]
@@@ -104,16 -111,17 +109,17 @@@ impl platform::Driver for RustDebugFs 
      fn probe(
          pdev: &platform::Device<Core>,
          _info: Option<&Self::IdInfo>,
-     ) -> Result<Pin<KBox<Self>>> {
-         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, Relaxed);
-         {
-             let mut guard = result.inner.lock();
-             guard.x = guard.y;
-             guard.y = 42;
-         }
-         Ok(result)
+     ) -> impl PinInit<Self, Error> {
+         RustDebugFs::new(pdev).pin_chain(|this| {
 -            this.counter.store(91, Ordering::Relaxed);
++            this.counter.store(91, Relaxed);
+             {
+                 let mut guard = this.inner.lock();
+                 guard.x = guard.y;
+                 guard.y = 42;
+             }
+             Ok(())
+         })
      }
  }
  
index 406e6588b17a18b7a8a7d5d57514728dc49de23d,c80312cf168d5e6e566acc8a6286fb2be3349f4b..702a6546d3fbeb6e5dc8fcc4ebcf6c0a6c19e9a7
@@@ -6,9 -6,10 +6,10 @@@
  //! `Scope::dir` to create a variety of files without the need to separately
  //! track them all.
  
 -use core::sync::atomic::AtomicUsize;
  use kernel::debugfs::{Dir, Scope};
  use kernel::prelude::*;
+ use kernel::sizes::*;
 +use kernel::sync::atomic::Atomic;
  use kernel::sync::Mutex;
  use kernel::{c_str, new_mutex, str::CString};
  
@@@ -109,7 -114,8 +114,8 @@@ impl ModuleData 
  
  struct DeviceData {
      name: CString,
 -    nums: KVec<AtomicUsize>,
 +    nums: KVec<Atomic<usize>>,
+     blob: Pin<KBox<Mutex<[u8; SZ_4K]>>>,
  }
  
  fn init_control(base_dir: &Dir, dyn_dirs: Dir) -> impl PinInit<Scope<ModuleData>> + '_ {
Simple merge