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. The current build installs 543 packages into the image.

toolchain    cross-gcc / 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         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 (archr-linux/emulationstation-next)
image        FAT32 BOOT (256 MB) + ext4 ROOT (4608 MB) + ext4 STORAGE

Current versions: gcc 14.2.0, Linux 6.12.94, Mesa 26.1.3, systemd 255.8, U-Boot v2025.10, pacman 7.0.0.

Output in target/<Ver of="imageBase" />-{original,clone}.img.gz.


Kernel

Source       Linux 6.12 LTS (kernel.org, ArchR and Rockchip patches)
Toolchain    gcc from the ArchR toolchain stage
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 current Mesa 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

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/configs/system.cfg → gpu.driver. Auto-fallback if either driver fails to attach to the GPU node.


U-Boot

OriginalClone
Packageu-boot-legacyu-boot
SourceHardkernel BSP tree (archr-linux/hardkernel-uboot)mainline U-Boot v2025.10, plus one patch for the K36 clone with eMMC
Defconfigarchr_rk3326_defconfigrk3326-handheld_defconfig
Board selectionhwrev on SARADC channel 0 picks the DTB under /dtbs/same, through boot.scr

Both variants ship boot.scr and extlinux/extlinux.conf, and both load the panel overlay from FDTOVERLAYS /overlays/mipi-panel.dtbo. The two loaders are not interchangeable: a clone will not boot the BSP build. ArchR ships both and the build picks by variant (make docker-RK3326 produces both images).


Image layout

MBR (msdos), 3 primary partitions, first partition starts at sector 32768
├── 1   FAT32  ARCHR       256 MB    16 KB clusters, kernel, DTBs, overlays/, U-Boot
├── 2   ext4   ARCHR_ROOT  4608 MB   system, mounted read-only at runtime
└── 3   ext4   STORAGE     rest      writable: configs, ROMs, save states, screenshots

Partition sizes come from distributions/ArchR/options (BOOT_SIZE, ROOT_SIZE, SYSTEM_PART_START) and the labels from DISTRO_BOOTLABEL, DISTRO_ROOTLABEL and DISTRO_DISKLABEL. The third partition ships at 32 MB and grows to the rest of the card on first boot.

mkimage formats the boot partition with mkfs.vfat -F 32 -S 512 -s 32, which is 16 KB clusters, and then checks it with fsck.fat -n, read-only. Nothing in the build or on the device repairs that filesystem automatically.


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

ArchR does not ship pre-built panel overlays. There are too many board and panel combinations to enumerate, so the overlay is built per device from the DTB of the console's own original card.

The image carries an empty overlays/ directory on the boot partition, with a README in six languages pointing at the generator (projects/ArchR/devices/RK3326/packages/u-boot/config/overlays/README.txt). extlinux.conf loads exactly one file from it:

FDTOVERLAYS /overlays/mipi-panel.dtbo

The generator itself lives in two places, both running the same archr_dtbo.py:

  • the browser version at /overlay-generator, which runs the script under a WebAssembly Python runtime
  • archr-dtbo-offline.zip, the same script as a terminal tool with the fdt library and the base DTBs bundled

The device also carries a copy at /usr/libexec/generic-dsi/archr_dtbo.py, installed by the generic-dsi package.

The option string encodes the console and the board flags, for example SDORIG for an original R36S, SDCLONE for a clone and SDORIG-NAm for a Soysauce board.


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.

The FAT warnings after a resize are expected. fs-resize runs fatlabel -i -r to give the boot partition a fresh volume ID, and fatlabel prints two complaints while doing it: the cluster count is below the FAT32 minimum, and the primary boot sector differs from the backup. Both are on purpose. The 256 MB boot partition uses the same out-of-spec FAT32 geometry as the rest of the RK3326 ecosystem, because the vendor U-Boot builds are only tested against that geometry, and the boot-sector difference is the kernel vfat dirty flag, which is written to the primary sector only while /flash is mounted read-write. fs-resize writes the same note into /flash/fs-resize.log.

Do not "fix" the boot filesystem with fsck.fat -a -w. Rewriting the geometry to something spec compliant is what breaks boot on the vendor loaders. Both mkimage and the device side check the filesystem read-only, with fsck.fat -n.

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. The kernel config sets CONFIG_PREEMPT=y. Whether PREEMPT_VOLUNTARY would do better on the A35 is an open question, pending an A/B run 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