ARCH R

Technical reference

Kernel, Mesa, U-Boot, DTS, build pipeline

Build pipeline

make docker-RK3326 runs through the full LibreELEC-style stage system inside Docker. Roughly 700 install steps for ~263 packages.

toolchain    cross-gcc 14.2 / glibc / binutils, host stage
linux        6.12 LTS kernel + RK3326 DTS + ArchR patches
mali-bifrost out-of-tree mali_kbase + ArchR PM unbalance patch
mesa         26.0.5 Panfrost (GLES 3.1, +speed +lto)
zlib (-ng)   zlib-ng 2.2.4 in --zlib-compat mode (libz.so.1)
retroarch    1.x + libretro core suite
emulators    Flycast, PPSSPP, melonDS, DraStic, Yabasanshiro, Mupen64Plus standalones
es           EmulationStation (ROCKNIX fork)
image        FAT32 BOOT (272 MB, 4 KB clusters) + ext4 ROOT + ext4 STORAGE

Output in target/ArchR-R36S.aarch64-DATE-{original,clone}.img.gz.


Kernel

Source       Linux 6.12 LTS (BSP fork, Rockchip patches + ArchR)
Toolchain    gcc 14.2.0
Config       projects/ArchR/devices/RK3326/linux/linux.aarch64.conf
HZ           300
PREEMPT      CONFIG_PREEMPT=y (PREEMPT_VOLUNTARY A/B pending bench)
DEBUG        all sanitizers / KASAN / KMEMLEAK / FTRACE off in release
DEBUG_FS     y (intentional, needed for CMA debugfs and PSI introspection)
CMA          96 MB (raised from 64 MB after audit; see /docs/features)
ZRAM         lzo-rle by default (zstd opt-in via memory-manager)
ZSWAP        disabled (zswap.enabled=0 cmdline)

DTS

Source: rk3326-gameconsole-r36s.dts (board) + rk3326-gameconsole-r3xs.dtsi (board family) + px30.dtsi (SoC) + 000-rk3326-dts.patch (ArchR overrides).

NodeWhat we changedWhy
cpu0_opp_tableAdded 1512 MHz turbo OPP @ 1.4 V; kept full 408–1416 ladderReachable via "Enable CPU Overclock"
vdd_arm regulatorregulator-max-microvolt 1.35 V → 1.45 VOtherwise kernel rejects 1512 OPP silently
gpu_opp_tableRestored 200/300/400 MHz lower OPPs; added 600/650 MHz topLower OPPs needed for boot; 650 MHz is daily turbo at 1.150 V
dmc / dfiDefined explicitly so devfreq can be controlledUpstream px30.dtsi has them disabled
gpuAdded clock-names="bus", resets, power_policy="coarse_demand", power_modelsRequired for modern panfrost/mali_kbase
ethernet0 aliasAddedFuture USB-Ethernet hot-plug naming
Power modelmali-simple-power-model + mali-g31-power-modelThermal-aware DVFS

Kernel patches

Notable ArchR-specific kernel patches (projects/ArchR/packages/linux-drivers/mali-bifrost/patches/6.12-LTS/ and projects/ArchR/packages/linux/patches/6.12-LTS/):

  • mali-bifrost/002-fix-unbalanced-regulator-clk-pm.patch: eliminates unbalanced disables for vdd_logic warnings when vdd_logic is shared with rockchip-pm-domain. Adds a gpu_power_held flag tracking the driver's own enable state.
  • linux/0005-drm-panfrost-Add-SYSTEM_TIMESTAMP*.patch: required by Mesa 26.0.5 for GL_TIMESTAMP queries and VK_KHR_calibrated_timestamps.
  • linux/0006-drm-panfrost-Add-cycle-counter-job-requirement.patch: VK_KHR_shader_clock plumbing.
  • linux/0007-0010-drm-panfrost-Add-BO-labelling*.patch: debug aid; Mesa calls the new IOCTL even though the labels are debug-only.

Mesa 26.0.5

Built with +speed +lto in packages/graphics/mesa/package.mk. Configure flags:

-Dgallium-drivers=panfrost   -Dvulkan-drivers=
-Dgles1=disabled             -Dgles2=enabled
-Dglvnd=disabled             -Dplatforms=wl
-Dglx=disabled               -Dshader-cache=enabled
-Dopengl=true                -Dgbm=enabled
-Degl=enabled                -Dlibunwind=disabled
-Dbuild-tests=false          -Ddraw-use-llvm=false

-Dgallium-drivers=panfrost only, no software fallback shipped (LLVMpipe's overhead would dwarf hardware on G31). -Dvulkan-drivers= empty because PanVK isn't conformant on Bifrost v9.

Runtime env

Exported globally in /etc/profile.d/041-panfrost:

PAN_MESA_DEBUG=forcepack             # G31-specific format-pack hint
MESA_NO_ERROR=1
MESA_SHADER_CACHE_DIR=/storage/.cache/mesa_shader_cache
MESA_SHADER_CACHE_MAX_SIZE=128MB

Per-emulator MESA_GLTHREAD=true whitelist, only Flycast & PPSSPP standalone (see GPU driver and the in-repo docs/mesa-glthread-matrix.md).


libmali (alternative)

Out-of-tree mali_kbase r52p0-00eac0 with the regulator/clk PM patch. Switched at boot via /usr/bin/gpudriver --start reading /storage/.config/system.cfg → gpu.driver. Auto-fallback if either driver fails to attach to the GPU node.


U-Boot

OriginalClone
Sourcebootloader/u-boot-rk3326/ (BSP)mainline + ROCKNIX patches
Boot configboot.iniboot.scr (mkimage -T script)
Displayhwrev SARADC ch0 → board DTB → DRM → logo.bmpnone
Defconfigodroidgoark3326-handheld_defconfig

The two binaries differ enough that you can't boot a clone with the BSP build. ArchR ships both, the build picks based on --variant (or make docker-RK3326 produces both).


Image layout

GPT
├── 1   FAT32  BOOT     272 MB    65525+ clusters of 4 KB (FAT32 spec compliant)
│                       Kernel, DTBs, all 43 panel overlays, U-Boot script
├── 2   ext4   ROOT     ~4.6 GB   read-only system (squashfs in some legacy builds)
└── 3   ext4   STORAGE  rest      writable: configs, ROMs, save-states, screenshots

mkimage runs fsck.fat -a -w on the BOOT FAT before cementing the image, guarantees primary and backup boot sectors are byte-identical. The on-device fs-resize script does the same after fatlabel regenerates the UUID, so the post-resize boot stays consistent.


Boot flow

0.0   POR / SPL (ROM-resident)
0.x   U-Boot reads SARADC ch0 → board ID
0.5   loads board DTB + panel overlay from /flash/
0.7   initramfs splash (BMP via xxd, static aarch64 /init)
~7    kernel hand-off
~9    systemd target
~12   archr-autostart (quirks + governor + display + bluetooth + ssh check)
~17   EmulationStation
~19   ready to play

Trace is written to /storage/.boot_last_step before each autostart script runs and erased on success, if the previous boot hung, the file at /storage/.boot_last_hang names the script that hung. Load it for bug reports.


Display panels

43 panel overlays generated from vendor DTB dumps:

config/archr-dts/
├── original/    (15)   R36S V20–V22, OGS variants
├── clone/       (18)   K36, R33S, R36 Max, RX6S, multiple panel families
└── soysauce/    (10)   Y3506-based hardware

The generator (config/mipi-generator/generator.sharchr-dtbo.py) extracts panel init-sequence + timings from the vendor DTB, applies a base ArchR overlay template, and emits one .dtbo per motherboard revision. Six variants per panel are produced:

default                    K36 layout, no JP swap, normal audio
_JPk36                     K36 layout, JP A↔B / X↔Y
_JPmm                      MyMini layout, JP swap
_SRs                       force simple audio path
_JPk36_SRs                 K36 + JP + SRs
_JPmm_SRs                  MyMini + JP + SRs

258 DTBOs total. The Flasher (or manual copy) drops one of these into /flash/overlays/mipi-panel.dtbo at write time.


Pitfalls

Edge cases that bit during development. None of them apply to a normal release flash, they only matter if you're hacking on the build.

Hunk header counts in DTS patches matter. A wrong @@ -X,Y +A,B @@ count causes patch to silently abort the rest of the hunks, recovered "garbage". Validate with patch --dry-run --verbose on a pristine source after every DTS edit.

turbo-mode OPPs hide behind cpufreq/boost. Setting scaling_max_freq=1512000 directly does nothing if boost=0, the freq isn't in scaling_available_frequencies until you write 1 to cpufreq/boost. ArchR's set_cpu_max_freq highest reads both scaling_available_frequencies and scaling_boost_frequencies.

vdd_arm regulator-max gates OPP availability silently. If an OPP requests a voltage above regulator-max-microvolt, kernel drops the OPP with no dmesg, and policy_has_boost_freq() returns false → cpufreq/boost sysfs file is never created.

fatlabel -i -r desyncs FAT primary/backup. It rewrites the UUID/label only on sector 0; backup at sector 6 keeps the old values. Some U-Boot builds occasionally read the backup, causing intermittent boot hangs after resize. Solution: run fsck.fat -a -w immediately after.

patch.fat / cluster < 65525 warnings are mostly harmless but occasionally bite specific U-Boot ROM versions. ArchR uses BOOT_SIZE=272 MB with 4 KB clusters → 69500+ clusters → above spec.

Mesa shader cache root must exist. Mesa won't mkdir MESA_SHADER_CACHE_DIR itself, just falls through to no-cache mode silently. runemu.sh creates /storage/.cache/mesa_shader_cache if missing.

panfrost_devfreq needs low GPU OPPs to initialize. Removing 200/300/400 MHz OPPs (to force a higher floor) blocks GPU init at boot, the driver expects the lowest published OPP to be reachable. Keep the ladder; force a high floor at runtime with set_gpu_min_freq highest instead.

PREEMPT vs PREEMPT_VOLUNTARY is unbenched. Kernel currently uses PREEMPT; ROCKNIX uses VOLUNTARY. Pending A/B with the on-device bench.

Mali_kbase regulator unbalance is a kernel patch, not a quirk. Without 002-fix-unbalanced-regulator-clk-pm.patch, every PM transition emits unbalanced disables for vdd_logic and Enabling unprepared clk_gpu, audible as crackle in Mario 64.

On this page