// SPDX-License-Identifier: GPL-2.0 or MIT //! # Definitions //! //! - **CEU**: Command Execution Unit - A hardware component that executes commands (instructions) //! from the command stream. //! - **CS**: Command Stream - A sequence of instructions (commands) used to control a particular //! job or sequence of jobs. The instructions exist in one or more command buffers. //! - **CSF**: Command Stream Frontend - The interface and implementation for job submission //! exposed to the host CPU driver. This includes the global interface, as well as CSG and CS //! interfaces. //! - **CSG**: Command Stream Group - A group of related command streams. The CSF manages multiple //! CSGs, and each CSG contains multiple CSs. //! - **CSHW**: Command Stream Hardware - The hardware interpreting command streams, including the //! iterator control aspects. Implements the CSF in conjunction with the MCU. //! - **GLB**: Global - Prefix for global interface registers that control operations common to //! all CSs. //! - **JASID**: Job Address Space ID - Identifies the address space for a job. //! - **MCU**: Microcontroller Unit - Implements the CSF in conjunction with the command stream //! hardware. //! - **MMU**: Memory Management Unit - Handles address translation and memory access protection. // We don't expect that all the registers and fields will be used, even in the // future. // // Nevertheless, it is useful to have most of them defined, like the C driver // does. #![allow(dead_code)] /// Combine two 32-bit values into a single 64-bit value. pub(crate) fn join_u64(lo: u32, hi: u32) -> u64 { (u64::from(lo)) | ((u64::from(hi)) << 32) } /// Read a logical 64-bit value from split 32-bit registers without tearing. pub(crate) fn read_u64_no_tearing(lo_read: impl Fn() -> u32, hi_read: impl Fn() -> u32) -> u64 { loop { let hi1 = hi_read(); let lo = lo_read(); let hi2 = hi_read(); if hi1 == hi2 { return join_u64(lo, hi1); } } } /// These registers correspond to the GPU_CONTROL register page. /// They are involved in GPU configuration and control. pub(crate) mod gpu_control { use core::convert::TryFrom; use kernel::{ error::{ code::EINVAL, Error, // }, num::Bounded, register, uapi, // }; use pin_init::Zeroable; register! { /// GPU identification register. pub(crate) GPU_ID(u32) @ 0x0 { /// Status of the GPU release. 3:0 ver_status; /// Minor release version number. 11:4 ver_minor; /// Major release version number. 15:12 ver_major; /// Product identifier. 19:16 prod_major; /// Architecture patch revision. 23:20 arch_rev; /// Architecture minor revision. 27:24 arch_minor; /// Architecture major revision. 31:28 arch_major; } /// Level 2 cache features register. pub(crate) L2_FEATURES(u32) @ 0x4 { /// Cache line size. 7:0 line_size; /// Cache associativity. 15:8 associativity; /// Cache slice size. 23:16 cache_size; /// External bus width. 31:24 bus_width; } /// Shader core features. pub(crate) CORE_FEATURES(u32) @ 0x8 { /// Shader core variant. 7:0 core_variant; } /// Tiler features. pub(crate) TILER_FEATURES(u32) @ 0xc { /// Log of the tiler's bin size. 5:0 bin_size; /// Maximum number of active levels. 11:8 max_levels; } /// Memory system features. pub(crate) MEM_FEATURES(u32) @ 0x10 { 0:0 coherent_core_group => bool; 1:1 coherent_super_group => bool; 11:8 l2_slices; } /// Memory management unit features. pub(crate) MMU_FEATURES(u32) @ 0x14 { /// Number of bits supported in virtual addresses. 7:0 va_bits; /// Number of bits supported in physical addresses. 15:8 pa_bits; } /// Address spaces present. pub(crate) AS_PRESENT(u32) @ 0x18 { 31:0 present; } /// CSF version information. pub(crate) CSF_ID(u32) @ 0x1c { /// MCU revision ID. 3:0 mcu_rev; /// MCU minor revision number. 9:4 mcu_minor; /// MCU major revision number. 15:10 mcu_major; /// CSHW revision ID. 19:16 cshw_rev; /// CSHW minor revision number. 25:20 cshw_minor; /// CSHW major revision number. 31:26 cshw_major; } /// IRQ sources raw status. /// Writing to this register forces bits on, but does not clear them. pub(crate) GPU_IRQ_RAWSTAT(u32) @ 0x20 { /// A GPU fault has occurred, a 1-bit boolean flag. 0:0 gpu_fault => bool; /// A GPU fault has occurred, a 1-bit boolean flag. 1:1 gpu_protected_fault => bool; /// Reset has completed, a 1-bit boolean flag. 8:8 reset_completed => bool; /// Set when a single power domain has powered up or down, a 1-bit boolean flag. 9:9 power_changed_single => bool; /// Set when the all pending power domain changes are completed, a 1-bit boolean flag. 10:10 power_changed_all => bool; /// Set when cache cleaning has completed, a 1-bit boolean flag. 17:17 clean_caches_completed => bool; /// Mirrors the doorbell interrupt line to the CPU, a 1-bit boolean flag. 18:18 doorbell_mirror => bool; /// MCU requires attention, a 1-bit boolean flag. 19:19 mcu_status => bool; } /// IRQ sources to clear. Write only. pub(crate) GPU_IRQ_CLEAR(u32) @ 0x24 { /// Clear the GPU_FAULT interrupt, a 1-bit boolean flag. 0:0 gpu_fault => bool; /// Clear the GPU_PROTECTED_FAULT interrupt, a 1-bit boolean flag. 1:1 gpu_protected_fault => bool; /// Clear the RESET_COMPLETED interrupt, a 1-bit boolean flag. 8:8 reset_completed => bool; /// Clear the POWER_CHANGED_SINGLE interrupt, a 1-bit boolean flag. 9:9 power_changed_single => bool; /// Clear the POWER_CHANGED_ALL interrupt, a 1-bit boolean flag. 10:10 power_changed_all => bool; /// Clear the CLEAN_CACHES_COMPLETED interrupt, a 1-bit boolean flag. 17:17 clean_caches_completed => bool; /// Clear the MCU_STATUS interrupt, a 1-bit boolean flag. 19:19 mcu_status => bool; } /// IRQ sources enabled. pub(crate) GPU_IRQ_MASK(u32) @ 0x28 { /// Enable the GPU_FAULT interrupt, a 1-bit boolean flag. 0:0 gpu_fault => bool; /// Enable the GPU_PROTECTED_FAULT interrupt, a 1-bit boolean flag. 1:1 gpu_protected_fault => bool; /// Enable the RESET_COMPLETED interrupt, a 1-bit boolean flag. 8:8 reset_completed => bool; /// Enable the POWER_CHANGED_SINGLE interrupt, a 1-bit boolean flag. 9:9 power_changed_single => bool; /// Enable the POWER_CHANGED_ALL interrupt, a 1-bit boolean flag. 10:10 power_changed_all => bool; /// Enable the CLEAN_CACHES_COMPLETED interrupt, a 1-bit boolean flag. 17:17 clean_caches_completed => bool; /// Enable the DOORBELL_MIRROR interrupt, a 1-bit boolean flag. 18:18 doorbell_mirror => bool; /// Enable the MCU_STATUS interrupt, a 1-bit boolean flag. 19:19 mcu_status => bool; } /// IRQ status for enabled sources. Read only. pub(crate) GPU_IRQ_STATUS(u32) @ 0x2c { /// GPU_FAULT interrupt status, a 1-bit boolean flag. 0:0 gpu_fault => bool; /// GPU_PROTECTED_FAULT interrupt status, a 1-bit boolean flag. 1:1 gpu_protected_fault => bool; /// RESET_COMPLETED interrupt status, a 1-bit boolean flag. 8:8 reset_completed => bool; /// POWER_CHANGED_SINGLE interrupt status, a 1-bit boolean flag. 9:9 power_changed_single => bool; /// POWER_CHANGED_ALL interrupt status, a 1-bit boolean flag. 10:10 power_changed_all => bool; /// CLEAN_CACHES_COMPLETED interrupt status, a 1-bit boolean flag. 17:17 clean_caches_completed => bool; /// DOORBELL_MIRROR interrupt status, a 1-bit boolean flag. 18:18 doorbell_mirror => bool; /// MCU_STATUS interrupt status, a 1-bit boolean flag. 19:19 mcu_status => bool; } } /// Helpers for GPU_COMMAND Register #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum GpuCommand { /// No operation. This is the default value. Nop = 0, /// Reset the GPU. Reset = 1, /// Flush caches. FlushCaches = 4, /// Clear GPU faults. ClearFault = 7, } impl TryFrom> for GpuCommand { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(GpuCommand::Nop), 1 => Ok(GpuCommand::Reset), 4 => Ok(GpuCommand::FlushCaches), 7 => Ok(GpuCommand::ClearFault), _ => Err(EINVAL), } } } impl From for Bounded { fn from(cmd: GpuCommand) -> Self { (cmd as u8).into() } } /// Reset mode for [`GPU_COMMAND::reset()`]. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum ResetMode { /// Stop all external bus interfaces, then reset the entire GPU. SoftReset = 1, /// Force a full GPU reset. HardReset = 2, } impl TryFrom> for ResetMode { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 1 => Ok(ResetMode::SoftReset), 2 => Ok(ResetMode::HardReset), _ => Err(EINVAL), } } } impl From for Bounded { fn from(mode: ResetMode) -> Self { Bounded::try_new(mode as u32).unwrap() } } /// Cache flush mode for [`GPU_COMMAND::flush_caches()`]. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum FlushMode { /// No flush. None = 0, /// Clean the caches. Clean = 1, /// Invalidate the caches. Invalidate = 2, /// Clean and invalidate the caches. CleanInvalidate = 3, } impl TryFrom> for FlushMode { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(FlushMode::None), 1 => Ok(FlushMode::Clean), 2 => Ok(FlushMode::Invalidate), 3 => Ok(FlushMode::CleanInvalidate), _ => Err(EINVAL), } } } impl From for Bounded { fn from(mode: FlushMode) -> Self { Bounded::try_new(mode as u32).unwrap() } } register! { /// GPU command register. /// /// Use the constructor methods to create commands: /// - [`GPU_COMMAND::nop()`] /// - [`GPU_COMMAND::reset()`] /// - [`GPU_COMMAND::flush_caches()`] /// - [`GPU_COMMAND::clear_fault()`] pub(crate) GPU_COMMAND (u32) @ 0x30 { 7:0 command ?=> GpuCommand; } /// Internal alias for GPU_COMMAND in reset mode. /// Use [`GPU_COMMAND::reset()`] instead. GPU_COMMAND_RESET (u32) => GPU_COMMAND { 7:0 command ?=> GpuCommand; 11:8 reset_mode ?=> ResetMode; } /// Internal alias for GPU_COMMAND in cache flush mode. /// Use [`GPU_COMMAND::flush_caches()`] instead. GPU_COMMAND_FLUSH (u32) => GPU_COMMAND { 7:0 command ?=> GpuCommand; /// L2 cache flush mode. 11:8 l2_flush ?=> FlushMode; /// Shader core load/store cache flush mode. 15:12 lsc_flush ?=> FlushMode; /// Shader core other caches flush mode. 19:16 other_flush ?=> FlushMode; } } impl GPU_COMMAND { /// Create a NOP command. pub(crate) fn nop() -> Self { Self::zeroed() } /// Create a reset command with the specified reset mode. pub(crate) fn reset(mode: ResetMode) -> Self { Self::from_raw( GPU_COMMAND_RESET::zeroed() .with_command(GpuCommand::Reset) .with_reset_mode(mode) .into_raw(), ) } /// Create a cache flush command with the specified flush modes. pub(crate) fn flush_caches(l2: FlushMode, lsc: FlushMode, other: FlushMode) -> Self { Self::from_raw( GPU_COMMAND_FLUSH::zeroed() .with_command(GpuCommand::FlushCaches) .with_l2_flush(l2) .with_lsc_flush(lsc) .with_other_flush(other) .into_raw(), ) } /// Create a clear fault command. pub(crate) fn clear_fault() -> Self { Self::zeroed().with_command(GpuCommand::ClearFault) } } register! { /// GPU status register. Read only. pub(crate) GPU_STATUS(u32) @ 0x34 { /// GPU active, a 1-bit boolean flag. 0:0 gpu_active => bool; /// Power manager active, a 1-bit boolean flag 1:1 pwr_active => bool; /// Page fault active, a 1-bit boolean flag. 4:4 page_fault => bool; /// Protected mode active, a 1-bit boolean flag. 7:7 protected_mode_active => bool; /// Debug mode active, a 1-bit boolean flag. 8:8 gpu_dbg_enabled => bool; } } #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum ExceptionType { /// Exception type: No error. Ok = 0x00, /// Exception type: GPU external bus error. GpuBusFault = 0x80, /// Exception type: GPU shareability error. GpuShareabilityFault = 0x88, /// Exception type: System shareability error. SystemShareabilityFault = 0x89, /// Exception type: GPU cacheability error. GpuCacheabilityFault = 0x8A, } impl TryFrom> for ExceptionType { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0x00 => Ok(ExceptionType::Ok), 0x80 => Ok(ExceptionType::GpuBusFault), 0x88 => Ok(ExceptionType::GpuShareabilityFault), 0x89 => Ok(ExceptionType::SystemShareabilityFault), 0x8A => Ok(ExceptionType::GpuCacheabilityFault), _ => Err(EINVAL), } } } impl From for Bounded { fn from(exc: ExceptionType) -> Self { (exc as u8).into() } } #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum AccessType { /// Access type: An atomic (read/write) transaction. Atomic = 0, /// Access type: An execute transaction. Execute = 1, /// Access type: A read transaction. Read = 2, /// Access type: A write transaction. Write = 3, } impl From> for AccessType { fn from(val: Bounded) -> Self { match val.get() { 0 => AccessType::Atomic, 1 => AccessType::Execute, 2 => AccessType::Read, 3 => AccessType::Write, _ => unreachable!(), } } } impl From for Bounded { fn from(access: AccessType) -> Self { Bounded::try_new(access as u32).unwrap() } } register! { /// GPU fault status register. Read only. pub(crate) GPU_FAULTSTATUS(u32) @ 0x3c { /// Exception type. 7:0 exception_type ?=> ExceptionType; /// Access type. 9:8 access_type => AccessType; /// The GPU_FAULTADDRESS is valid, a 1-bit boolean flag. 10:10 address_valid => bool; /// The JASID field is valid, a 1-bit boolean flag. 11:11 jasid_valid => bool; /// JASID of the fault, if known. 15:12 jasid; /// ID of the source that triggered the fault. 31:16 source_id; } /// GPU fault address. Read only. /// Once a fault is reported, it must be manually cleared by issuing a /// [`GPU_COMMAND::clear_fault()`] command to the [`GPU_COMMAND`] register. No further GPU /// faults will be reported until the previous fault has been cleared. pub(crate) GPU_FAULTADDRESS_LO(u32) @ 0x40 { 31:0 pointer; } pub(crate) GPU_FAULTADDRESS_HI(u32) @ 0x44 { 31:0 pointer; } /// Level 2 cache configuration. pub(crate) L2_CONFIG(u32) @ 0x48 { /// Requested cache size. 23:16 cache_size; /// Requested hash function index. 31:24 hash_function; } /// Global time stamp offset. pub(crate) TIMESTAMP_OFFSET_LO(u32) @ 0x88 { 31:0 offset; } pub(crate) TIMESTAMP_OFFSET_HI(u32) @ 0x8c { 31:0 offset; } /// GPU cycle counter. Read only. pub(crate) CYCLE_COUNT_LO(u32) @ 0x90 { 31:0 count; } pub(crate) CYCLE_COUNT_HI(u32) @ 0x94 { 31:0 count; } /// Global time stamp. Read only. pub(crate) TIMESTAMP_LO(u32) @ 0x98 { 31:0 timestamp; } pub(crate) TIMESTAMP_HI(u32) @ 0x9c { 31:0 timestamp; } /// Maximum number of threads per core. Read only constant. pub(crate) THREAD_MAX_THREADS(u32) @ 0xa0 { 31:0 threads; } /// Maximum number of threads per workgroup. Read only constant. pub(crate) THREAD_MAX_WORKGROUP_SIZE(u32) @ 0xa4 { 31:0 threads; } /// Maximum number of threads per barrier. Read only constant. pub(crate) THREAD_MAX_BARRIER_SIZE(u32) @ 0xa8 { 31:0 threads; } /// Thread features. Read only constant. pub(crate) THREAD_FEATURES(u32) @ 0xac { /// Total number of registers per core. 21:0 max_registers; /// Implementation technology type. 23:22 implementation_technology; /// Maximum number of compute tasks waiting. 31:24 max_task_queue; } /// Support flags for compressed texture formats. Read only constant. /// /// A bitmap where each bit indicates support for a specific compressed texture format. /// The bit position maps to an opaque format ID (`texture_features_key_t` in spec). pub(crate) TEXTURE_FEATURES(u32)[4] @ 0xb0 { 31:0 supported_formats; } /// Shader core present bitmap. Read only constant. pub(crate) SHADER_PRESENT_LO(u32) @ 0x100 { 31:0 value; } pub(crate) SHADER_PRESENT_HI(u32) @ 0x104 { 31:0 value; } /// Tiler present bitmap. Read only constant. pub(crate) TILER_PRESENT_LO(u32) @ 0x110 { 31:0 present; } pub(crate) TILER_PRESENT_HI(u32) @ 0x114 { 31:0 present; } /// L2 cache present bitmap. Read only constant. pub(crate) L2_PRESENT_LO(u32) @ 0x120 { 31:0 present; } pub(crate) L2_PRESENT_HI(u32) @ 0x124 { 31:0 present; } /// Shader core ready bitmap. Read only. pub(crate) SHADER_READY_LO(u32) @ 0x140 { 31:0 ready; } pub(crate) SHADER_READY_HI(u32) @ 0x144 { 31:0 ready; } /// Tiler ready bitmap. Read only. pub(crate) TILER_READY_LO(u32) @ 0x150 { 31:0 ready; } pub(crate) TILER_READY_HI(u32) @ 0x154 { 31:0 ready; } /// L2 ready bitmap. Read only. pub(crate) L2_READY_LO(u32) @ 0x160 { 31:0 ready; } pub(crate) L2_READY_HI(u32) @ 0x164 { 31:0 ready; } /// Shader core power up bitmap. pub(crate) SHADER_PWRON_LO(u32) @ 0x180 { 31:0 request; } pub(crate) SHADER_PWRON_HI(u32) @ 0x184 { 31:0 request; } /// Tiler power up bitmap. pub(crate) TILER_PWRON_LO(u32) @ 0x190 { 31:0 request; } pub(crate) TILER_PWRON_HI(u32) @ 0x194 { 31:0 request; } /// L2 power up bitmap. pub(crate) L2_PWRON_LO(u32) @ 0x1a0 { 31:0 request; } pub(crate) L2_PWRON_HI(u32) @ 0x1a4 { 31:0 request; } /// Shader core power down bitmap. pub(crate) SHADER_PWROFF_LO(u32) @ 0x1c0 { 31:0 request; } pub(crate) SHADER_PWROFF_HI(u32) @ 0x1c4 { 31:0 request; } /// Tiler power down bitmap. pub(crate) TILER_PWROFF_LO(u32) @ 0x1d0 { 31:0 request; } pub(crate) TILER_PWROFF_HI(u32) @ 0x1d4 { 31:0 request; } /// L2 power down bitmap. pub(crate) L2_PWROFF_LO(u32) @ 0x1e0 { 31:0 request; } pub(crate) L2_PWROFF_HI(u32) @ 0x1e4 { 31:0 request; } /// Shader core power transition bitmap. Read-only. pub(crate) SHADER_PWRTRANS_LO(u32) @ 0x200 { 31:0 changing; } pub(crate) SHADER_PWRTRANS_HI(u32) @ 0x204 { 31:0 changing; } /// Tiler power transition bitmap. Read-only. pub(crate) TILER_PWRTRANS_LO(u32) @ 0x210 { 31:0 changing; } pub(crate) TILER_PWRTRANS_HI(u32) @ 0x214 { 31:0 changing; } /// L2 power transition bitmap. Read-only. pub(crate) L2_PWRTRANS_LO(u32) @ 0x220 { 31:0 changing; } pub(crate) L2_PWRTRANS_HI(u32) @ 0x224 { 31:0 changing; } /// Shader core active bitmap. Read-only. pub(crate) SHADER_PWRACTIVE_LO(u32) @ 0x240 { 31:0 active; } pub(crate) SHADER_PWRACTIVE_HI(u32) @ 0x244 { 31:0 active; } /// Tiler active bitmap. Read-only. pub(crate) TILER_PWRACTIVE_LO(u32) @ 0x250 { 31:0 active; } pub(crate) TILER_PWRACTIVE_HI(u32) @ 0x254 { 31:0 active; } /// L2 active bitmap. Read-only. pub(crate) L2_PWRACTIVE_LO(u32) @ 0x260 { 31:0 active; } pub(crate) L2_PWRACTIVE_HI(u32) @ 0x264 { 31:0 active; } /// Revision ID. Read only constant. pub(crate) REVIDR(u32) @ 0x280 { 31:0 revision; } /// Coherency features present. Read only constant. /// Supported protocols on the interconnect between the GPU and the /// system into which it is integrated. pub(crate) COHERENCY_FEATURES(u32) @ 0x300 { /// ACE-Lite protocol supported, a 1-bit boolean flag. 0:0 ace_lite => bool; /// ACE protocol supported, a 1-bit boolean flag. 1:1 ace => bool; } } #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum CoherencyMode { /// ACE-Lite coherency protocol. AceLite = uapi::drm_panthor_gpu_coherency_DRM_PANTHOR_GPU_COHERENCY_ACE_LITE as u8, /// ACE coherency protocol. Ace = uapi::drm_panthor_gpu_coherency_DRM_PANTHOR_GPU_COHERENCY_ACE as u8, /// No coherency protocol. None = uapi::drm_panthor_gpu_coherency_DRM_PANTHOR_GPU_COHERENCY_NONE as u8, } impl TryFrom> for CoherencyMode { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(CoherencyMode::AceLite), 1 => Ok(CoherencyMode::Ace), 31 => Ok(CoherencyMode::None), _ => Err(EINVAL), } } } impl From for Bounded { fn from(mode: CoherencyMode) -> Self { (mode as u8).into() } } register! { /// Coherency enable. An index of which coherency protocols should be used. /// This register only selects the protocol for coherency messages on the /// interconnect. This is not to enable or disable coherency controlled by MMU. pub(crate) COHERENCY_ENABLE(u32) @ 0x304 { 31:0 l2_cache_protocol_select ?=> CoherencyMode; } } /// Helpers for MCU_CONTROL register #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum McuControlMode { /// Disable the MCU. Disable = 0, /// Enable the MCU. Enable = 1, /// Enable the MCU to execute and automatically reboot after a fast reset. Auto = 2, } impl TryFrom> for McuControlMode { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(McuControlMode::Disable), 1 => Ok(McuControlMode::Enable), 2 => Ok(McuControlMode::Auto), _ => Err(EINVAL), } } } impl From for Bounded { fn from(mode: McuControlMode) -> Self { Bounded::try_new(mode as u32).unwrap() } } register! { /// MCU control. pub(crate) MCU_CONTROL(u32) @ 0x700 { /// Request MCU state change. 1:0 req ?=> McuControlMode; } } /// Helpers for MCU_STATUS register #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum McuStatus { /// MCU is disabled. Disabled = 0, /// MCU is enabled. Enabled = 1, /// The MCU has halted by itself in an orderly manner to enable the core group to be /// powered down. Halt = 2, /// The MCU has encountered an error that prevents it from continuing. Fatal = 3, } impl From> for McuStatus { fn from(val: Bounded) -> Self { match val.get() { 0 => McuStatus::Disabled, 1 => McuStatus::Enabled, 2 => McuStatus::Halt, 3 => McuStatus::Fatal, _ => unreachable!(), } } } impl From for Bounded { fn from(status: McuStatus) -> Self { Bounded::try_new(status as u32).unwrap() } } register! { /// MCU status. Read only. pub(crate) MCU_STATUS(u32) @ 0x704 { /// Read current state of MCU. 1:0 value => McuStatus; } } } /// These registers correspond to the JOB_CONTROL register page. /// They are involved in communication between the firmware running on the MCU and the host. pub(crate) mod job_control { use kernel::register; register! { /// Raw status of job interrupts. /// /// Write to this register to trigger these interrupts. /// Writing a 1 to a bit forces that bit on. pub(crate) JOB_IRQ_RAWSTAT(u32) @ 0x1000 { /// CSG request. These bits indicate that CSGn requires attention from the host. 30:0 csg; /// GLB request. Indicates that the GLB interface requires attention from the host. 31:31 glb => bool; } /// Clear job interrupts. Write only. /// /// Write a 1 to a bit to clear the corresponding bit in [`JOB_IRQ_RAWSTAT`]. pub(crate) JOB_IRQ_CLEAR(u32) @ 0x1004 { /// Clear CSG request interrupts. 30:0 csg; /// Clear GLB request interrupt. 31:31 glb => bool; } /// Mask for job interrupts. /// /// Set each bit to 1 to enable the corresponding interrupt source or to 0 to disable it. pub(crate) JOB_IRQ_MASK(u32) @ 0x1008 { /// Enable CSG request interrupts. 30:0 csg; /// Enable GLB request interrupt. 31:31 glb => bool; } /// Active job interrupts. Read only. /// /// This register contains the result of ANDing together [`JOB_IRQ_RAWSTAT`] and /// [`JOB_IRQ_MASK`]. pub(crate) JOB_IRQ_STATUS(u32) @ 0x100c { /// CSG request interrupt status. 30:0 csg; /// GLB request interrupt status. 31:31 glb => bool; } } } /// These registers correspond to the MMU_CONTROL register page. /// They are involved in MMU configuration and control. pub(crate) mod mmu_control { use kernel::register; register! { /// IRQ sources raw status. /// /// This register contains the raw unmasked interrupt sources for MMU status and exception /// handling. /// /// Writing to this register forces bits on. /// Use [`IRQ_CLEAR`] to clear interrupts. pub(crate) IRQ_RAWSTAT(u32) @ 0x2000 { /// Page fault for address spaces. 15:0 page_fault; /// Command completed in address spaces. 31:16 command_completed; } /// IRQ sources to clear. /// Write a 1 to a bit to clear the corresponding bit in [`IRQ_RAWSTAT`]. pub(crate) IRQ_CLEAR(u32) @ 0x2004 { /// Clear the PAGE_FAULT interrupt. 15:0 page_fault; /// Clear the COMMAND_COMPLETED interrupt. 31:16 command_completed; } /// IRQ sources enabled. /// /// Set each bit to 1 to enable the corresponding interrupt source, and to 0 to disable it. pub(crate) IRQ_MASK(u32) @ 0x2008 { /// Enable the PAGE_FAULT interrupt. 15:0 page_fault; /// Enable the COMMAND_COMPLETED interrupt. 31:16 command_completed; } /// IRQ status for enabled sources. Read only. /// /// This register contains the result of ANDing together [`IRQ_RAWSTAT`] and [`IRQ_MASK`]. pub(crate) IRQ_STATUS(u32) @ 0x200c { /// PAGE_FAULT interrupt status. 15:0 page_fault; /// COMMAND_COMPLETED interrupt status. 31:16 command_completed; } } /// Per-address space registers ASn [0..15] within the MMU_CONTROL page. /// /// This array contains 16 instances of the MMU_AS_CONTROL register page. pub(crate) mod mmu_as_control { use core::convert::TryFrom; use kernel::{ error::{ code::EINVAL, Error, // }, num::Bounded, register, // }; /// Maximum number of hardware address space slots. /// The actual number of slots available is usually lower. pub(crate) const MAX_AS: usize = 16; /// Address space register stride. The elements in the array are spaced 64B apart. const STRIDE: usize = 0x40; register! { /// Translation table base address. A 64-bit pointer. /// /// This field contains the address of the top level of a translation table structure. /// This must be 16-byte-aligned, so address bits [3:0] are assumed to be zero. pub(crate) TRANSTAB(u64)[MAX_AS, stride = STRIDE] @ 0x2400 { /// Base address of the translation table. 63:0 base; } // TRANSTAB is a logical 64-bit register, but it is laid out in hardware as two // 32-bit halves. Define it as separate low/high u32 registers so accesses match // the MMIO register layout and do not rely on native 64-bit MMIO transactions. pub(crate) TRANSTAB_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2400 { 31:0 value; } pub(crate) TRANSTAB_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2404 { 31:0 value; } } /// Helpers for MEMATTR Register. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum AllocPolicySelect { /// Ignore ALLOC_R/ALLOC_W fields. Impl = 2, /// Use ALLOC_R/ALLOC_W fields for allocation policy. Alloc = 3, } impl TryFrom> for AllocPolicySelect { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 2 => Ok(Self::Impl), 3 => Ok(Self::Alloc), _ => Err(EINVAL), } } } impl From for Bounded { fn from(val: AllocPolicySelect) -> Self { Bounded::try_new(val as u8).unwrap() } } /// Coherency policy for memory attributes. Indicates the shareability of cached accesses. /// /// The hardware spec defines different interpretations of these values depending on /// whether TRANSCFG.MODE is set to IDENTITY or not. IDENTITY mode does not use translation /// tables (all input addresses map to the same output address); it is deprecated and not /// used by the driver. This enum assumes that TRANSCFG.MODE is not set to IDENTITY. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum Coherency { /// Midgard inner domain coherency. /// /// Most flexible mode - can map non-coherent, internally coherent, and system/IO /// coherent memory. Used for non-cacheable memory in MAIR conversion. MidgardInnerDomain = 0, /// CPU inner domain coherency. /// /// Can map non-coherent and system/IO coherent memory. Used for write-back /// cacheable memory in MAIR conversion to maintain CPU-GPU cache coherency. CpuInnerDomain = 1, /// CPU inner domain with shader coherency. /// /// Can map internally coherent and system/IO coherent memory. Used for /// GPU-internal shared buffers requiring shader coherency. CpuInnerDomainShaderCoh = 2, } impl TryFrom> for Coherency { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(Self::MidgardInnerDomain), 1 => Ok(Self::CpuInnerDomain), 2 => Ok(Self::CpuInnerDomainShaderCoh), _ => Err(EINVAL), } } } impl From for Bounded { fn from(val: Coherency) -> Self { Bounded::try_new(val as u8).unwrap() } } #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum MemoryType { /// Normal memory (shared). Shared = 0, /// Normal memory, inner/outer non-cacheable. NonCacheable = 1, /// Normal memory, inner/outer write-back cacheable. WriteBack = 2, /// Triggers MEMORY_ATTRIBUTE_FAULT. Fault = 3, } impl From> for MemoryType { fn from(val: Bounded) -> Self { match val.get() { 0 => Self::Shared, 1 => Self::NonCacheable, 2 => Self::WriteBack, 3 => Self::Fault, _ => unreachable!(), } } } impl From for Bounded { fn from(val: MemoryType) -> Self { Bounded::try_new(val as u8).unwrap() } } register! { /// Stage 1 memory attributes (8-bit bitfield). /// /// This is not an actual register, but a bitfield definition used by the MEMATTR /// register. Each of the 8 bytes in MEMATTR follows this layout. MMU_MEMATTR_STAGE1(u8) @ 0x0 { /// Inner cache write allocation policy. 0:0 alloc_w => bool; /// Inner cache read allocation policy. 1:1 alloc_r => bool; /// Inner allocation policy select. 3:2 alloc_sel ?=> AllocPolicySelect; /// Coherency policy. 5:4 coherency ?=> Coherency; /// Memory type. 7:6 memory_type => MemoryType; } } impl TryFrom> for MMU_MEMATTR_STAGE1 { type Error = Error; fn try_from(val: Bounded) -> Result { Ok(Self::from_raw(val.get() as u8)) } } impl From for Bounded { fn from(val: MMU_MEMATTR_STAGE1) -> Self { Bounded::try_new(u64::from(val.into_raw())).unwrap() } } register! { /// Memory attributes. /// /// Each address space can configure up to 8 different memory attribute profiles. /// Each attribute profile follows the MMU_MEMATTR_STAGE1 layout. pub(crate) MEMATTR(u64)[MAX_AS, stride = STRIDE] @ 0x2408 { 7:0 attribute0 ?=> MMU_MEMATTR_STAGE1; 15:8 attribute1 ?=> MMU_MEMATTR_STAGE1; 23:16 attribute2 ?=> MMU_MEMATTR_STAGE1; 31:24 attribute3 ?=> MMU_MEMATTR_STAGE1; 39:32 attribute4 ?=> MMU_MEMATTR_STAGE1; 47:40 attribute5 ?=> MMU_MEMATTR_STAGE1; 55:48 attribute6 ?=> MMU_MEMATTR_STAGE1; 63:56 attribute7 ?=> MMU_MEMATTR_STAGE1; } // MEMATTR is a logical 64-bit register, but it is laid out in hardware as two // 32-bit halves. Define it as separate low/high u32 registers so accesses match // the MMIO register layout and do not rely on native 64-bit MMIO transactions. pub(crate) MEMATTR_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2408 { 31:0 value; } pub(crate) MEMATTR_HI(u32)[MAX_AS, stride = STRIDE] @ 0x240c { 31:0 value; } /// Lock region address for each address space. pub(crate) LOCKADDR(u64)[MAX_AS, stride = STRIDE] @ 0x2410 { /// Lock region size. 5:0 size; /// Lock region base address. 63:12 base; } // LOCKADDR is a logical 64-bit register, but it is laid out in hardware as two // 32-bit halves. Define it as separate low/high u32 registers so accesses match // the MMIO register layout and do not rely on native 64-bit MMIO transactions. pub(crate) LOCKADDR_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2410 { 31:0 value; } pub(crate) LOCKADDR_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2414 { 31:0 value; } } /// Helpers for MMU COMMAND register. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum MmuCommand { /// No operation, nothing happens. Nop = 0, /// Propagate settings to the MMU. Update = 1, /// Lock an address region. Lock = 2, /// Unlock an address region. Unlock = 3, /// Clean and invalidate the L2 cache, then unlock. FlushPt = 4, /// Clean and invalidate all caches, then unlock. FlushMem = 5, } impl TryFrom> for MmuCommand { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(MmuCommand::Nop), 1 => Ok(MmuCommand::Update), 2 => Ok(MmuCommand::Lock), 3 => Ok(MmuCommand::Unlock), 4 => Ok(MmuCommand::FlushPt), 5 => Ok(MmuCommand::FlushMem), _ => Err(EINVAL), } } } impl From for Bounded { fn from(cmd: MmuCommand) -> Self { (cmd as u8).into() } } register! { /// MMU command register for each address space. Write only. pub(crate) COMMAND(u32)[MAX_AS, stride = STRIDE] @ 0x2418 { 7:0 command ?=> MmuCommand; } } /// MMU exception types for FAULTSTATUS register. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum MmuExceptionType { /// No error. Ok = 0x00, /// Invalid translation table entry, level 0. TranslationFault0 = 0xC0, /// Invalid translation table entry, level 1. TranslationFault1 = 0xC1, /// Invalid translation table entry, level 2. TranslationFault2 = 0xC2, /// Invalid translation table entry, level 3. TranslationFault3 = 0xC3, /// Invalid block descriptor. TranslationFault4 = 0xC4, /// Page permission error, level 0. PermissionFault0 = 0xC8, /// Page permission error, level 1. PermissionFault1 = 0xC9, /// Page permission error, level 2. PermissionFault2 = 0xCA, /// Page permission error, level 3. PermissionFault3 = 0xCB, /// Access flag not set, level 1. AccessFlag1 = 0xD9, /// Access flag not set, level 2. AccessFlag2 = 0xDA, /// Access flag not set, level 3. AccessFlag3 = 0xDB, /// Virtual address out of range. AddressSizeFaultIn = 0xE0, /// Physical address out of range, level 0. AddressSizeFaultOut0 = 0xE4, /// Physical address out of range, level 1. AddressSizeFaultOut1 = 0xE5, /// Physical address out of range, level 2. AddressSizeFaultOut2 = 0xE6, /// Physical address out of range, level 3. AddressSizeFaultOut3 = 0xE7, /// Page attribute error, level 0. MemoryAttributeFault0 = 0xE8, /// Page attribute error, level 1. MemoryAttributeFault1 = 0xE9, /// Page attribute error, level 2. MemoryAttributeFault2 = 0xEA, /// Page attribute error, level 3. MemoryAttributeFault3 = 0xEB, } impl TryFrom> for MmuExceptionType { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0x00 => Ok(MmuExceptionType::Ok), 0xC0 => Ok(MmuExceptionType::TranslationFault0), 0xC1 => Ok(MmuExceptionType::TranslationFault1), 0xC2 => Ok(MmuExceptionType::TranslationFault2), 0xC3 => Ok(MmuExceptionType::TranslationFault3), 0xC4 => Ok(MmuExceptionType::TranslationFault4), 0xC8 => Ok(MmuExceptionType::PermissionFault0), 0xC9 => Ok(MmuExceptionType::PermissionFault1), 0xCA => Ok(MmuExceptionType::PermissionFault2), 0xCB => Ok(MmuExceptionType::PermissionFault3), 0xD9 => Ok(MmuExceptionType::AccessFlag1), 0xDA => Ok(MmuExceptionType::AccessFlag2), 0xDB => Ok(MmuExceptionType::AccessFlag3), 0xE0 => Ok(MmuExceptionType::AddressSizeFaultIn), 0xE4 => Ok(MmuExceptionType::AddressSizeFaultOut0), 0xE5 => Ok(MmuExceptionType::AddressSizeFaultOut1), 0xE6 => Ok(MmuExceptionType::AddressSizeFaultOut2), 0xE7 => Ok(MmuExceptionType::AddressSizeFaultOut3), 0xE8 => Ok(MmuExceptionType::MemoryAttributeFault0), 0xE9 => Ok(MmuExceptionType::MemoryAttributeFault1), 0xEA => Ok(MmuExceptionType::MemoryAttributeFault2), 0xEB => Ok(MmuExceptionType::MemoryAttributeFault3), _ => Err(EINVAL), } } } impl From for Bounded { fn from(exc: MmuExceptionType) -> Self { (exc as u8).into() } } /// Access type for MMU faults. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum MmuAccessType { /// An atomic (read/write) transaction. Atomic = 0, /// An execute transaction. Execute = 1, /// A read transaction. Read = 2, /// A write transaction. Write = 3, } impl From> for MmuAccessType { fn from(val: Bounded) -> Self { match val.get() { 0 => MmuAccessType::Atomic, 1 => MmuAccessType::Execute, 2 => MmuAccessType::Read, 3 => MmuAccessType::Write, _ => unreachable!(), } } } impl From for Bounded { fn from(access: MmuAccessType) -> Self { Bounded::try_new(access as u32).unwrap() } } register! { /// Fault status register for each address space. Read only. pub(crate) FAULTSTATUS(u32)[MAX_AS, stride = STRIDE] @ 0x241c { /// Exception type. 7:0 exception_type ?=> MmuExceptionType; /// Access type. 9:8 access_type => MmuAccessType; /// ID of the source that triggered the fault. 31:16 source_id; } /// Fault address for each address space. Read only. pub(crate) FAULTADDRESS_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2420 { 31:0 pointer; } pub(crate) FAULTADDRESS_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2424 { 31:0 pointer; } /// MMU status register for each address space. Read only. pub(crate) STATUS(u32)[MAX_AS, stride = STRIDE] @ 0x2428 { /// External address space command is active, a 1-bit boolean flag. 0:0 active_ext => bool; /// Internal address space command is active, a 1-bit boolean flag. 1:1 active_int => bool; } } /// Helpers for TRANSCFG register. /// /// Address space mode for TRANSCFG register. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum AddressSpaceMode { /// The MMU forces all memory access to fail with a decode fault. Unmapped = 1, /// All input addresses map to the same output address (deprecated). Identity = 2, /// Translation tables interpreted according to AArch64 4kB granule specification. Aarch64_4K = 6, /// Translation tables interpreted according to AArch64 64kB granule specification. Aarch64_64K = 8, } impl TryFrom> for AddressSpaceMode { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 1 => Ok(AddressSpaceMode::Unmapped), 2 => Ok(AddressSpaceMode::Identity), 6 => Ok(AddressSpaceMode::Aarch64_4K), 8 => Ok(AddressSpaceMode::Aarch64_64K), _ => Err(EINVAL), } } } impl From for Bounded { fn from(mode: AddressSpaceMode) -> Self { Bounded::try_new(mode as u64).unwrap() } } /// Input address range restriction for TRANSCFG register. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum InaBits { /// Invalid VA range (reset value). Reset = 0, /// 48-bit VA range. Bits48 = 7, /// 47-bit VA range. Bits47 = 8, /// 46-bit VA range. Bits46 = 9, /// 45-bit VA range. Bits45 = 10, /// 44-bit VA range. Bits44 = 11, /// 43-bit VA range. Bits43 = 12, /// 42-bit VA range. Bits42 = 13, /// 41-bit VA range. Bits41 = 14, /// 40-bit VA range. Bits40 = 15, /// 39-bit VA range. Bits39 = 16, /// 38-bit VA range. Bits38 = 17, /// 37-bit VA range. Bits37 = 18, /// 36-bit VA range. Bits36 = 19, /// 35-bit VA range. Bits35 = 20, /// 34-bit VA range. Bits34 = 21, /// 33-bit VA range. Bits33 = 22, /// 32-bit VA range. Bits32 = 23, /// 31-bit VA range. Bits31 = 24, /// 30-bit VA range. Bits30 = 25, /// 29-bit VA range. Bits29 = 26, /// 28-bit VA range. Bits28 = 27, /// 27-bit VA range. Bits27 = 28, /// 26-bit VA range. Bits26 = 29, /// 25-bit VA range. Bits25 = 30, } impl TryFrom> for InaBits { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(InaBits::Reset), 7 => Ok(InaBits::Bits48), 8 => Ok(InaBits::Bits47), 9 => Ok(InaBits::Bits46), 10 => Ok(InaBits::Bits45), 11 => Ok(InaBits::Bits44), 12 => Ok(InaBits::Bits43), 13 => Ok(InaBits::Bits42), 14 => Ok(InaBits::Bits41), 15 => Ok(InaBits::Bits40), 16 => Ok(InaBits::Bits39), 17 => Ok(InaBits::Bits38), 18 => Ok(InaBits::Bits37), 19 => Ok(InaBits::Bits36), 20 => Ok(InaBits::Bits35), 21 => Ok(InaBits::Bits34), 22 => Ok(InaBits::Bits33), 23 => Ok(InaBits::Bits32), 24 => Ok(InaBits::Bits31), 25 => Ok(InaBits::Bits30), 26 => Ok(InaBits::Bits29), 27 => Ok(InaBits::Bits28), 28 => Ok(InaBits::Bits27), 29 => Ok(InaBits::Bits26), 30 => Ok(InaBits::Bits25), _ => Err(EINVAL), } } } impl From for Bounded { fn from(bits: InaBits) -> Self { Bounded::try_new(bits as u64).unwrap() } } /// Translation table memory attributes for TRANSCFG register. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub(crate) enum PtwMemattr { /// Invalid (reset value, not valid for enabled address space). Invalid = 0, /// Normal memory, inner/outer non-cacheable. NonCacheable = 1, /// Normal memory, inner/outer write-back cacheable. WriteBack = 2, } impl TryFrom> for PtwMemattr { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(PtwMemattr::Invalid), 1 => Ok(PtwMemattr::NonCacheable), 2 => Ok(PtwMemattr::WriteBack), _ => Err(EINVAL), } } } impl From for Bounded { fn from(attr: PtwMemattr) -> Self { Bounded::try_new(attr as u64).unwrap() } } /// Translation table memory shareability for TRANSCFG register. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] #[allow(clippy::enum_variant_names)] pub(crate) enum PtwShareability { /// Non-shareable. NonShareable = 0, /// Outer shareable. OuterShareable = 2, /// Inner shareable. InnerShareable = 3, } impl TryFrom> for PtwShareability { type Error = Error; fn try_from(val: Bounded) -> Result { match val.get() { 0 => Ok(PtwShareability::NonShareable), 2 => Ok(PtwShareability::OuterShareable), 3 => Ok(PtwShareability::InnerShareable), _ => Err(EINVAL), } } } impl From for Bounded { fn from(sh: PtwShareability) -> Self { Bounded::try_new(sh as u64).unwrap() } } register! { /// Translation configuration and control. pub(crate) TRANSCFG(u64)[MAX_AS, stride = STRIDE] @ 0x2430 { /// Address space mode. 3:0 mode ?=> AddressSpaceMode; /// Address input restriction. 10:6 ina_bits ?=> InaBits; /// Address output restriction. 18:14 outa_bits; /// Translation table concatenation enable, a 1-bit boolean flag. 22:22 sl_concat_en => bool; /// Translation table memory attributes. 25:24 ptw_memattr ?=> PtwMemattr; /// Translation table memory shareability. 29:28 ptw_sh ?=> PtwShareability; /// Inner read allocation hint for translation table walks, a 1-bit boolean flag. 30:30 r_allocate => bool; /// Disable hierarchical access permissions. 33:33 disable_hier_ap => bool; /// Disable access fault checking. 34:34 disable_af_fault => bool; /// Disable execution on all writable pages. 35:35 wxn => bool; /// Enable execution on readable pages. 36:36 xreadable => bool; /// Page-based hardware attributes for translation table walks. 63:60 ptw_pbha; } // TRANSCFG is a logical 64-bit register, but it is laid out in hardware as two // 32-bit halves. Define it as separate low/high u32 registers so accesses match // the MMIO register layout and do not rely on native 64-bit MMIO transactions. pub(crate) TRANSCFG_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2430 { 31:0 value; } pub(crate) TRANSCFG_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2434 { 31:0 value; } /// Extra fault information for each address space. Read only. pub(crate) FAULTEXTRA_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2438 { 31:0 value; } pub(crate) FAULTEXTRA_HI(u32)[MAX_AS, stride = STRIDE] @ 0x243c { 31:0 value; } } } } /// This module corresponds to the DOORBELL_BLOCK_n[0-63] register pages. pub(crate) mod doorbell_block { use kernel::register; /// Number of doorbells available. pub(crate) const NUM_DOORBELLS: usize = 64; /// Doorbell block stride (64KiB). /// /// Each block occupies a full page, allowing it to be mapped /// separately into a virtual address space. const STRIDE: usize = 0x10000; register! { /// Doorbell request register. Write-only. pub(crate) DOORBELL(u32)[NUM_DOORBELLS, stride = STRIDE] @ 0x80000 { /// Doorbell set. Writing 1 triggers the doorbell. 0:0 ring => bool; } } }