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 STORAGEOutput 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).
| Node | What we changed | Why |
|---|---|---|
cpu0_opp_table | Added 1512 MHz turbo OPP @ 1.4 V; kept full 408–1416 ladder | Reachable via "Enable CPU Overclock" |
vdd_arm regulator | regulator-max-microvolt 1.35 V → 1.45 V | Otherwise kernel rejects 1512 OPP silently |
gpu_opp_table | Restored 200/300/400 MHz lower OPPs; added 600/650 MHz top | Lower OPPs needed for boot; 650 MHz is daily turbo at 1.150 V |
dmc / dfi | Defined explicitly so devfreq can be controlled | Upstream px30.dtsi has them disabled |
gpu | Added clock-names="bus", resets, power_policy="coarse_demand", power_models | Required for modern panfrost/mali_kbase |
ethernet0 alias | Added | Future USB-Ethernet hot-plug naming |
| Power model | mali-simple-power-model + mali-g31-power-model | Thermal-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: eliminatesunbalanced disables for vdd_logicwarnings whenvdd_logicis shared with rockchip-pm-domain. Adds agpu_power_heldflag tracking the driver's own enable state.linux/0005-drm-panfrost-Add-SYSTEM_TIMESTAMP*.patch: required by Mesa 26.0.5 forGL_TIMESTAMPqueries andVK_KHR_calibrated_timestamps.linux/0006-drm-panfrost-Add-cycle-counter-job-requirement.patch:VK_KHR_shader_clockplumbing.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=128MBPer-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
| Original | Clone | |
|---|---|---|
| Source | bootloader/u-boot-rk3326/ (BSP) | mainline + ROCKNIX patches |
| Boot config | boot.ini | boot.scr (mkimage -T script) |
| Display | hwrev SARADC ch0 → board DTB → DRM → logo.bmp | none |
| Defconfig | odroidgoa | rk3326-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, screenshotsmkimage 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 playTrace 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 hardwareThe generator (config/mipi-generator/generator.sh → archr-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 + SRs258 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.