> On 14 Jul 2026, at 00:18, Deborah Brouwer <[email protected]> 
> wrote:
> 
> Add a firmware module to load, parse, and map the MCU firmware sections
> into shared GEM memory at the required virtual addresses accessible by the
> GPU.
> 
> Create a firmware instance during probe and store it inside the
> TyrDrmRegistrationData to keep it alive after probe. Use the firmware
> instance to boot the MCU.
> 
> Remove the dead-code annotations from the MMU, VM, slot manager, and
> kernel BO code now that these paths are used by the firmware module.
> 
> Update Kconfig to add the RUST_FW_LOADER_ABSTRACTIONS dependency
> required by this module.
> 
> Co-developed-by: Boris Brezillon <[email protected]>
> Signed-off-by: Boris Brezillon <[email protected]>
> Signed-off-by: Deborah Brouwer <[email protected]>
> ---
> drivers/gpu/drm/tyr/Kconfig   |   1 +
> drivers/gpu/drm/tyr/driver.rs |  17 ++-
> drivers/gpu/drm/tyr/fw.rs     | 263 ++++++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/tyr/gem.rs    |   3 -
> drivers/gpu/drm/tyr/tyr.rs    |   1 +
> drivers/gpu/drm/tyr/vm.rs     |   1 -
> 6 files changed, 281 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/gpu/drm/tyr/Kconfig b/drivers/gpu/drm/tyr/Kconfig
> index 79ea4bb214de..8f13e49f11f9 100644
> --- a/drivers/gpu/drm/tyr/Kconfig
> +++ b/drivers/gpu/drm/tyr/Kconfig
> @@ -13,6 +13,7 @@ config DRM_TYR
> select IOMMU_IO_PGTABLE_LPAE
> select RUST_DRM_GEM_SHMEM_HELPER
> select RUST_DRM_GPUVM
> + select RUST_FW_LOADER_ABSTRACTIONS
> help
>  Rust DRM driver for ARM Mali CSF-based GPUs.
> 
> diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
> index 9195c8be5203..46ba91af9aca 100644
> --- a/drivers/gpu/drm/tyr/driver.rs
> +++ b/drivers/gpu/drm/tyr/driver.rs
> @@ -37,6 +37,7 @@
> 
> use crate::{
>     file::TyrDrmFileData,
> +    fw::Firmware,
>     gem::Bo,
>     gpu,
>     gpu::GpuInfo,
> @@ -67,6 +68,9 @@ pub(crate) struct TyrDrmRegistrationData<'bound> {
>     /// Parent platform device.
>     pub(crate) pdev: &'bound platform::Device<Bound>,
> 
> +    /// Firmware sections.
> +    pub(crate) fw: Firmware<'bound>,
> +
>     #[pin]
>     clks: Mutex<Clocks>,
> 
> @@ -144,10 +148,21 @@ fn probe<'bound>(
> 
>         let unreg_dev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, 
> Ok(()))?;
> 
> -        let _mmu = Mmu::new(iomem.as_arc_borrow(), &gpu_info)?;
> +        let mmu = Mmu::new(iomem.as_arc_borrow(), &gpu_info)?;
> +
> +        let firmware = Firmware::new(
> +            pdev.as_ref(),
> +            iomem.clone(),
> +            &unreg_dev,
> +            mmu.as_arc_borrow(),
> +            &gpu_info,
> +        )?;
> +
> +        firmware.boot()?;
> 
>         let reg_data = try_pin_init!(TyrDrmRegistrationData {
>                 pdev,
> +                fw: firmware,
>                 clks <- new_mutex!(Clocks {
>                     core: core_clk,
>                     stacks: stacks_clk,
> diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs
> new file mode 100644
> index 000000000000..554808a792aa
> --- /dev/null
> +++ b/drivers/gpu/drm/tyr/fw.rs
> @@ -0,0 +1,263 @@
> +// SPDX-License-Identifier: GPL-2.0 or MIT
> +
> +//! Firmware loading and management for Mali CSF GPU.
> +//!
> +//! This module handles loading the Mali GPU firmware binary, parsing it 
> into sections,
> +//! and mapping those sections into the MCU's virtual address space. Each 
> firmware section
> +//! has specific properties (read/write/execute permissions, cache modes) 
> and must be loaded
> +//! at specific virtual addresses expected by the MCU.
> +//!
> +//! See [`Firmware`] for the main firmware management interface and 
> [`Section`] for
> +//! individual firmware sections.
> +//!
> +//! [`Firmware`]: crate::fw::Firmware
> +//! [`Section`]: crate::fw::Section
> +
> +use kernel::{
> +    bits::genmask_u32,
> +    device::{
> +        Bound,
> +        Device, //
> +    },
> +    drm::{
> +        gem::BaseObject, //
> +    },
> +    impl_flags,
> +    io::{
> +        poll,
> +        Io, //
> +    },
> +    prelude::*,
> +    str::CString,
> +    sync::{
> +        Arc,
> +        ArcBorrow, //
> +    },
> +    time, //
> +};
> +
> +use crate::{
> +    driver::{
> +        IoMem,
> +        TyrDrmDevice, //
> +    },
> +    fw::parser::{
> +        FwParser,
> +        ParsedSection, //
> +    },
> +    gem,
> +    gem::{
> +        KernelBo,
> +        KernelBoVaAlloc, //
> +    },
> +    gpu::GpuInfo,
> +
> +    mmu::Mmu,
> +    regs::{
> +        gpu_control::{
> +            McuControlMode,
> +            McuStatus,
> +            GPU_ID,
> +            MCU_CONTROL,
> +            MCU_STATUS, //
> +        }, //
> +        job_control::JOB_IRQ_RAWSTAT, //
> +    },
> +    vm::Vm, //
> +};
> +
> +mod parser;
> +
> +impl_flags!(
> +    #[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
> +    pub(super) struct SectionFlags(u32);
> +
> +    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
> +    pub(super) enum SectionFlag {
> +        Read = 1 << 0,
> +        Write = 1 << 1,
> +        Exec = 1 << 2,
> +        CacheModeNone = 0 << 3,
> +        CacheModeCached = 1 << 3,
> +        CacheModeUncachedCoherent = 2 << 3,
> +        CacheModeCachedCoherent = 3 << 3,
> +        Prot = 1 << 5,
> +        Shared = 1 << 30,
> +        Zero = 1 << 31,
> +    }
> +);

Can we please rewrite this with the bitfield macro instead? 

I think we can use something like:

4:3 cache_mode => CacheMode


> +
> +pub(super) const CACHE_MODE_MASK: SectionFlags = 
> SectionFlags(genmask_u32(3..=4));
> +
> +pub(super) const CSF_MCU_SHARED_REGION_START: u32 = 0x04000000;
> +
> +impl SectionFlags {
> +    fn cache_mode(&self) -> SectionFlags {
> +        *self & CACHE_MODE_MASK
> +    }
> +}
> +
> +impl TryFrom<u32> for SectionFlags {
> +    type Error = Error;
> +
> +    fn try_from(value: u32) -> Result<Self, Self::Error> {
> +        let valid_flags = SectionFlags::from(SectionFlag::Read)
> +            | SectionFlags::from(SectionFlag::Write)
> +            | SectionFlags::from(SectionFlag::Exec)
> +            | CACHE_MODE_MASK
> +            | SectionFlags::from(SectionFlag::Prot)
> +            | SectionFlags::from(SectionFlag::Shared)
> +            | SectionFlags::from(SectionFlag::Zero);
> +
> +        if value & valid_flags.0 != value {
> +            Err(EINVAL)
> +        } else {
> +            Ok(Self(value))
> +        }
> +    }
> +}
> +
> +/// A parsed section of the firmware binary.
> +struct Section<'bound> {
> +    // Raw firmware section data for reset purposes
> +    #[expect(dead_code)]
> +    data: KVec<u8>,
> +
> +    // Keep the BO backing this firmware section so that both the
> +    // GPU mapping and CPU mapping remain valid until the Section is dropped.
> +    #[expect(dead_code)]
> +    mem: gem::KernelBo<'bound>,
> +}
> +
> +/// Loaded firmware with sections mapped into MCU VM.
> +pub(crate) struct Firmware<'bound> {
> +    /// Iomem need to access registers.
> +    iomem: Arc<IoMem<'bound>>,
> +
> +    /// MCU VM.
> +    vm: Arc<Vm<'bound>>,
> +
> +    /// List of firmware sections.
> +    #[expect(dead_code)]
> +    sections: KVec<Section<'bound>>,
> +}
> +
> +impl<'bound> Drop for Firmware<'bound> {
> +    fn drop(&mut self) {
> +        // AS slots retain a VM ref, we need to kill the circular ref 
> manually.
> +        self.vm.kill();
> +    }
> +}
> +
> +impl<'bound> Firmware<'bound> {
> +    fn init_section_mem(mem: &mut KernelBo<'bound>, data: &KVec<u8>) -> 
> Result {
> +        if data.is_empty() {
> +            return Ok(());
> +        }
> +
> +        let vmap = mem.bo.vmap::<0>()?;
> +        let size = mem.bo.size();
> +
> +        if data.len() > size {
> +            pr_err!("fw section {} bigger than BO {}\n", data.len(), size);
> +            return Err(EINVAL);
> +        }
> +
> +        for (i, &byte) in data.iter().enumerate() {
> +            vmap.try_write8(byte, i)?;
> +        }
> +
> +        Ok(())
> +    }
> +
> +    fn request(ddev: &TyrDrmDevice, gpu_info: &GpuInfo) -> 
> Result<kernel::firmware::Firmware> {
> +        let gpu_id = GPU_ID::from_raw(gpu_info.gpu_id);
> +
> +        let path = CString::try_from_fmt(fmt!(
> +            "arm/mali/arch{}.{}/mali_csffw.bin",
> +            gpu_id.arch_major().get(),
> +            gpu_id.arch_minor().get()
> +        ))?;
> +
> +        kernel::firmware::Firmware::request(&path, ddev.as_ref().as_ref())
> +    }
> +
> +    fn load(
> +        ddev: &TyrDrmDevice,
> +        gpu_info: &GpuInfo,
> +    ) -> Result<(kernel::firmware::Firmware, KVec<ParsedSection>)> {
> +        let fw = Self::request(ddev, gpu_info)?;
> +        let mut parser = FwParser::new(fw.data());
> +
> +        let parsed_sections = parser.parse()?;
> +
> +        Ok((fw, parsed_sections))
> +    }
> +
> +    /// Load firmware and map sections into MCU VM.
> +    pub(crate) fn new(
> +        dev: &'bound Device<Bound>,
> +        iomem: Arc<IoMem<'bound>>,
> +        ddev: &TyrDrmDevice,
> +        mmu: ArcBorrow<'_, Mmu<'bound>>,
> +        gpu_info: &GpuInfo,
> +    ) -> Result<Firmware<'bound>> {
> +        let vm = Vm::new(dev, ddev, mmu, gpu_info)?;
> +        vm.activate()?;
> +
> +        let (fw, parsed_sections) = Self::load(ddev, gpu_info)?;
> +        let mut sections = KVec::new();
> +        for parsed in parsed_sections {
> +            let size = (parsed.va.end - parsed.va.start) as usize;
> +            let va = u64::from(parsed.va.start);
> +
> +            let mut mem = KernelBo::new(
> +                ddev,
> +                vm.clone(),
> +                size.try_into().unwrap(),
> +                KernelBoVaAlloc::Explicit(va),
> +                parsed.vm_map_flags,
> +            )?;
> +
> +            let section_start = parsed.data_range.start as usize;
> +            let section_end = parsed.data_range.end as usize;
> +            let mut data = KVec::new();
> +
> +            // Ensure that the firmware slice is not out of bounds.
> +            let fw_data = fw.data();
> +            let bytes = 
> fw_data.get(section_start..section_end).ok_or(EINVAL)?;
> +            data.extend_from_slice(bytes, GFP_KERNEL)?;
> +
> +            Self::init_section_mem(&mut mem, &data)?;
> +
> +            sections.push(Section { data, mem }, GFP_KERNEL)?;
> +        }
> +
> +        let firmware = Firmware {
> +            iomem,
> +            vm,
> +            sections,
> +        };
> +
> +        Ok(firmware)
> +    }
> +
> +    pub(crate) fn boot(&self) -> Result {
> +        let io = &self.iomem;
> +        io.write_reg(MCU_CONTROL::zeroed().with_req(McuControlMode::Auto));
> +
> +        if let Err(e) = poll::read_poll_timeout(
> +            || Ok((io.read(MCU_STATUS), io.read(JOB_IRQ_RAWSTAT))),
> +            |(mcu_status, irq_rawstat)| {
> +                mcu_status.value() == McuStatus::Enabled && irq_rawstat.glb()
> +            },
> +            time::Delta::from_millis(1),
> +            time::Delta::from_millis(100),
> +        ) {
> +            let status = io.read(MCU_STATUS);
> +            pr_err!("MCU failed to boot, status: {:?}", status.value());
> +            return Err(e);
> +        }
> +        Ok(())
> +    }
> +}
> diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
> index 47a05a33388e..e9bf35682a47 100644
> --- a/drivers/gpu/drm/tyr/gem.rs
> +++ b/drivers/gpu/drm/tyr/gem.rs
> @@ -72,7 +72,6 @@ pub(crate) fn new_dummy_object(ddev: &TyrDrmDevice) -> 
> Result<ARef<Bo>> {
> /// An automatic VA allocation strategy will be added in the future.
> pub(crate) enum KernelBoVaAlloc {
>     /// Explicit VA address specified by the caller.
> -    #[expect(dead_code)]
>     Explicit(u64),
> }
> 
> @@ -85,7 +84,6 @@ pub(crate) enum KernelBoVaAlloc {
> /// When dropped, the buffer is automatically unmapped from the GPU VA space.
> pub(crate) struct KernelBo<'bound> {
>     /// The underlying GEM buffer object.
> -    #[expect(dead_code)]
>     pub(crate) bo: ARef<Bo>,
>     /// The GPU VM this buffer is mapped into.
>     vm: Arc<Vm<'bound>>,
> @@ -99,7 +97,6 @@ impl<'bound> KernelBo<'bound> {
>     /// This function allocates a new shmem-backed GEM object and immediately 
> maps
>     /// it into the specified GPU virtual memory space. The mapping is 
> automatically
>     /// cleaned up when the [`KernelBo`] is dropped.
> -    #[expect(dead_code)]
>     pub(crate) fn new(
>         ddev: &TyrDrmDevice,
>         vm: Arc<Vm<'bound>>,
> diff --git a/drivers/gpu/drm/tyr/tyr.rs b/drivers/gpu/drm/tyr/tyr.rs
> index 92f6885cdaae..e7ec450bdc9c 100644
> --- a/drivers/gpu/drm/tyr/tyr.rs
> +++ b/drivers/gpu/drm/tyr/tyr.rs
> @@ -9,6 +9,7 @@
> 
> mod driver;
> mod file;
> +mod fw;
> mod gem;
> mod gpu;
> mod mmu;
> diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
> index 2358096afc7e..7534e68359bf 100644
> --- a/drivers/gpu/drm/tyr/vm.rs
> +++ b/drivers/gpu/drm/tyr/vm.rs
> @@ -6,7 +6,6 @@
> //! the illusion of owning the entire virtual address (VA) range, similar to 
> CPU virtual memory.
> //! Each virtual memory (VM) area is backed by ARM64 LPAE Stage 1 page tables 
> and can be
> //! mapped into hardware address space (AS) slots for GPU execution.
> -#![expect(dead_code)]
> 
> use core::marker::PhantomData;
> use core::ops::Range;
> 
> -- 
> 2.54.0
> 

Apart from the change I mentioned, this patch looks good to me.



Reply via email to