Hi,
I’ve been diving deep into getting the ARM Statistical Profiling Extension (SPE) working on both the Radxa NIO 12L (MediaTek MT8395 / Genio 1200) and the Radxa Dragon Q6A (Qualcomm QCS6490 / Snapdragon).
Since ARM SPE requires modifications across EL3 (TF-A) and Device Tree, here is a detailed breakdown of what I discovered on both platforms, including the technical root causes for both the NIO 12L and Dragon Q6A.
Summary: I failed to get SPE fully working on both SBCs. (Radxa NIO 12L works without the interruption)
1. Radxa NIO 12L (MediaTek MT8395)
Status Overview
- What works: SPE instruction sampling & recording (~2MB of profiling data is successfully written to DRAM,
PMBPTR_EL1 advances, and PMBSR_EL1.S = 1 sets the buffer-full flag).
- What does NOT work: The hardware buffer-full interrupt (PPI 5) is not delivered properly when
PMBSR_EL1.S = 1.
Custom Firmware (TF-A / BL31) Modifications
To allow Non-Secure (Linux) access to SPE, the following modifications in TF-A BL31 are required:
SPE Access & EL3 Traps (MDCR_EL3)
I compiled TF-A with ENABLE_SPE_FOR_LOWER_ELS=1 (ENABLE_SPE_FOR_NS=1 for the current version of TF-A?).
- Clear
MDCR_EL3.SDD (Bit 16 = 0): Disables Secure Debug trap, allowing Non-Secure SPE access.
- Set
MDCR_EL3.NSPB (Bits [13:12]) to 0b11 (0x3000): Enables SPE for Non-Secure EL1/EL2.
Interrupt & Debug Subsystem Unlocking
(Attempted to configure CoreSight CTI for interrupt routing; however, CTI appears to be unrelated to SPE interrupt delivery on this SoC)
- DevAPC Firewall (MediaTek Security Firewall):
- Set
DEVAPC_DEBUGSYS_INDEX (Index 17: 0x0D000000 CoreSight / DEBUGSYS region) to NO_PROTECTION across all 16 security domains in devapc.c.
- CoreSight CTI (Cross Trigger Interface):
- Unlocked all CoreSight CTI instances using the standard ARM software lock key
0xC5ACCE55 and set CTICONTROL = 1.
Kernel Device Tree (DTS)
spe-pmu {
compatible = "arm,statistical-profiling-extension-v1";
interrupt-parent = <&gic>;
interrupts = <GIC_PPI 5 IRQ_TYPE_LEVEL_HIGH &ppi_cluster1>;
interrupt-affinity = <&cpu4>, <&cpu5>, <&cpu6>, <&cpu7>;
};
Adding the spe-pmu node as shown above enables the arm_spe_pmu kernel module to probe successfully.
However, the buffer-full interrupt does not fire when expected.
GIC_PPI 5 seems to be constantly asserted.
Conclusion for NIO 12L
SPE hardware profiling itself is fully functional, but the hardware interrupt delivery does not work as expected.
- SPE Hardware is Functional The Cortex-A78 cores successfully sample instructions and write ~2MB of profiling data into DRAM (
PMBPTR_EL1 advances and PMBSR_EL1.S = 1 sets the buffer-full flag).
- The Interrupt Issue (PPI 5 IRQ):
- Despite setting GIC
GICR_IGROUPR0 to Non-Secure Group 1, unlocking DevAPC, configuring MDCR_EL3, enabling the relevant debug power domains, and enabling CTI 8x8 routing, the hardware interrupt line (PPI 5 / IntID 21) is asserted to the GIC before PMBSR_EL1.S = 1 rather than when the buffer is full.
Possible root causes are:
- The SPE interrupt output pin is physically unwired in MediaTek’s MT8395 silicon design.
- The interrupt is routed to a different interrupt number (e.g., an SPI instead of PPI 5).
- A necessary debug/PMU clock or power domain remains powered down.
- Additional MediaTek-specific CoreSight/SPM configuration is required.
2. Radxa Dragon Q6A (Qualcomm QCS6490 / Snapdragon)
I also evaluated enabling SPE on the Radxa Dragon Q6A to see if a similar approach works. The QCS6490’s Cortex-A78 cores hardware-support SPE, but Non-Secure access is blocked by stock EL3 firmware.
Diagnostic Findings
With the following DTS node:
spe-pmu {
compatible = "arm,statistical-profiling-extension-v1";
interrupt-parent = <&gic>;
interrupts = <GIC_PPI 5 IRQ_TYPE_LEVEL_HIGH>;
};
Upon loading the arm_spe_pmu driver in Linux, the kernel logs the following error:
arm_spe_pmu spe-pmu: profiling buffer owned by higher exception level
This error occurs because the CPU’s PMBIDR_EL1 register indicates that SPE buffer programming is locked by a higher Exception Level. Specifically, Qualcomm’s proprietary EL3 firmware (tz.mbn / QTEE) leaves MDCR_EL3.NSPBE (Bit 18, Non-secure Statistical Profiling Buffer Enable) set to 0 (Disabled).
Custom Firmware Attempt & SBL1 Rejection
I attempted to build and flash open-source trusted firmware, but the board’s bootloader rejects it:
- Building Custom Open-Source TF-A:
- I built Qualcomm’s official open-source TF-A (qualcomm-linux/trusted-firmware-a,
PLAT=rb3gen2) and OP-TEE (qualcomm-linux/optee_os, PLATFORM_FLAVOR=kodiak) with ENABLE_SPE_FOR_NS=2 (ENABLE_SPE_FOR_LOWER_ELS=1).
- This successfully compiled
bl2.mbn (TF-A BL2) and fip.elf (TF-A BL31 + OP-TEE + UEFI).
- SBL1 Rejection:
Conclusion for Dragon Q6A
Unless Qualcomm or Radxa provides an officially signed tz.mbn (or signed TF-A release) with MDCR_EL3.NSPBE = 1 enabled, it seems end-users cannot simply run a custom, non-signed TF-A on the Dragon Q6A.
Summary & Takeaways
- Radxa NIO 12L (MT8395): SPE hardware works and records data to DRAM (~2MB written). Custom TF-A can be flashed without any issue (signing is not required). Interrupt delivery does not function (possibly unwired in silicon), but can be worked around via kernel polling (
hrtimer).
- Radxa Dragon Q6A (QCS6490): SPE is disabled by stock
tz.mbn (MDCR_EL3.NSPBE = 0). Replacing tz.mbn with custom TF-A triggers Error code 65 in XBL, which (may) indicates that SBL1 rejects non-signed TF-A images.
I hope a more detailed document for the MT8395 becomes available, or that Radxa releases updated firmware enabling SPE for Radxa Dragon Q6A(and Q8B).
If anyone would like my working code or repository, please let me know!