sdlamp2/docs/rg35xx-plus.md
Michael Smith 06daec791e Move shutdown screen to wrapper, reuse stock firmware goodbye.png
The in-app shutdown visual didn't work because SDL cleanup wiped the
framebuffer. Instead of hacking around that, move the shutdown display
to the device wrapper where it belongs. The wrapper now decodes the
stock firmware's goodbye.png with Python3+PIL and writes raw BGRA
pixels directly to /dev/fb0 before calling poweroff.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:07:03 +01:00

8.2 KiB
Raw Blame History

Anbernic RG35XX Plus

Device-specific reference for the target hardware. For build instructions see TOOLCHAIN.md.

Hardware

  • Device: Anbernic RG35XX Plus
  • SoC: Allwinner H700 — quad-core ARM Cortex-A53 @ 1.5 GHz (AArch64 / ARMv8-A)
  • GPU: Mali G31 MP2
  • RAM: 1 GB LPDDR4
  • Display: 640×480 IPS

Software

  • OS: Ubuntu 22.04 LTS (Jammy Jellyfish) — modified stock firmware
  • Kernel: Linux 4.9.170 aarch64 (proprietary, no source released by Anbernic)
  • Architecture: aarch64 / arm64 (confirmed via dpkg --print-architecture)
  • glibc: 2.35
  • Userland: Debian/Ubuntu-based (dpkg, apt-get, systemd)

Libraries on Device

All required shared libraries are pre-installed. Most are at /usr/lib/, some at /usr/lib/aarch64-linux-gnu/ (Debian multiarch path):

Library Soname Notes
SDL2 libSDL2-2.0.so.0.12.0 SDL 2.0.12
SDL2_image libSDL2_image-2.0.so.0.900.0 SDL2_image 2.0.9, at multiarch path
libavcodec libavcodec.so.58 FFmpeg ~4.x
libavformat libavformat.so.58 FFmpeg ~4.x
libavutil libavutil.so.56 FFmpeg ~4.x
libswresample libswresample.so.3 FFmpeg ~4.x

Shared libraries are already present — no need to bundle or build them. A native aarch64 compile (e.g. inside the arm64 Docker container) produces working binaries. Must link against glibc ≤ 2.35.

Input Devices

Three input devices are registered via /proc/bus/input/devices:

Device Handlers Purpose
axp2202-pek event0 Power button (KEY_POWER, code 116)
ANBERNIC-keys event1, js0 Gamepad (d-pad, face buttons)
dierct-keys-polled event2 Shoulder buttons, menu/function keys
  • logind: HandlePowerKey=ignore in /etc/systemd/logind.conf — systemd does not act on the power button, leaving it free for userspace handling.
  • evtest: Available at /usr/bin/evtest for debugging input events.

Partition Layout

Device Mount point Filesystem Contents
/dev/mmcblk0p5 / ext4 Root filesystem
/dev/mmcblk0p6 /mnt/vendor ext4 Firmware, apps, scripts
/dev/mmcblk0p7 /mnt/data ext4 User data
/dev/mmcblk0p8 /mnt/mmc vfat ROMs
SD card slot /mnt/sdcard (varies) External storage

Boot Chain

The device uses systemd but boots its UI through a SysV init script that systemd auto-wraps via systemd-sysv-generator. The chain is intentionally layered: launcher.sh runs before partitions are mounted (hardware setup), loadapp.sh runs after mounts (filesystem/network setup + app restart loop), and dmenu_ln is the app dispatcher.

systemd (graphical.target)
  └─ launcher.service  (SysV → auto-generated by systemd-sysv-generator)
       └─ /etc/init.d/launcher.sh start
            ├── mounts /mnt/vendor
            ├── starts brightCtrl.bin (backlight daemon) &
            ├── starts cexpert &
            └── starts loadapp.sh &
                 ├── resizes root partition (first boot only)
                 ├── mounts /mnt/data, /mnt/mmc
                 ├── charging mode: if boot_mode==1 → charger UI → poweroff
                 ├── normal mode: starts NetworkManager, bluetooth, wifi
                 ├── syncs RTC, loads kernel modules, mounts SD card
                 ├── runs /mnt/mod/ctrl/autostart (if exists)
                 └── RESTART LOOP: runs /mnt/vendor/ctrl/dmenu_ln repeatedly
                      └─ /mnt/vendor/ctrl/dmenu_ln
                           ├── selects binary (default: dmenu.bin)
                           ├── config overrides: muos.ini → muos.bin, vpRun.ini → ...
                           ├── runs binary via app_scheduling()
                           └── on exit: executes /tmp/.next (app dispatch for menu)

Why the indirection?

  • launcher.sh — Pre-mount hardware setup: mounts /mnt/vendor, starts backlight and system daemons. Runs as a SysV init script so it integrates with systemd's boot ordering.
  • loadapp.sh — Post-mount filesystem/network setup: mounts data partitions, handles charging mode, starts networking, then enters the restart loop. Runs in the background (&) so launcher.sh can return.
  • dmenu_ln — App dispatcher: selects which binary to run based on config files, wraps execution in app_scheduling() (which sleeps 30s on crash before retrying), and supports /tmp/.next for menu-driven app switching.

app_scheduling behavior

The app_scheduling function in dmenu_ln runs the selected binary. If it exits with a non-zero status, it sleeps 30 seconds before returning, which prevents crash loops from consuming all CPU. The outer while true loop in loadapp.sh then re-invokes dmenu_ln, restarting the application.

Framebuffer

Property Value
Device /dev/fb0
Visible size 640x480
Virtual size 640x960 (double-buf)
Bits per pixel 32 (BGRA)
Stride 2560 bytes (640 * 4)

No standard framebuffer image tools (fbv, fbi, psplash) are installed. To display a PNG on the framebuffer, decode with Python3+PIL and write raw BGRA pixels to /dev/fb0.

Stock Firmware Assets

Shutdown-related assets in /mnt/vendor/res1/shutdown/:

File Description Size
goodbye.png Shutdown screen (RGB, 640x480) Matches display
lowpower.png Low battery warning

The boot logo is at /mnt/vendor/res1/boot/logo.png.

Deploying sdlamp2

Overview

The dmenu_ln script already supports switching the startup binary via config files (e.g. muos.inimuos.bin). We follow the same pattern to launch sdlamp2 instead of the default menu.

Setup

  1. Copy the binary and wrapper to the device:

    scp build/sdlamp2 root@rg35xx:/mnt/vendor/bin/sdlamp2
    scp tools/rg35xx-wrapper.sh root@rg35xx:/mnt/vendor/bin/rg35xx-wrapper.sh
    
  2. Add the config check to /mnt/vendor/ctrl/dmenu_ln. In the section where CMD overrides are checked (after the existing muos.ini / vpRun.ini checks, before the app_scheduling call), add:

    if [ -f "/mnt/vendor/sdlamp2.ini" ];then
        CMD="/mnt/vendor/bin/rg35xx-wrapper.sh"
    fi
    

    The wrapper script handles device-specific concerns (WiFi hotspot, power button monitoring) and launches sdlamp2 as its main foreground process. See tools/rg35xx-wrapper.sh for details.

  3. Enable sdlamp2 on boot:

    touch /mnt/vendor/sdlamp2.ini
    
  4. Disable (revert to normal menu):

    rm /mnt/vendor/sdlamp2.ini
    

What's preserved

Everything else in the boot chain continues to work:

  • Charging mode — handled in loadapp.sh before the restart loop
  • LED/backlight controlbrightCtrl.bin started by launcher.sh
  • Clean shutdown — sdlamp2 handles SIGTERM/SIGINT, saving position and volume before exit. The wrapper displays the stock goodbye.png on the framebuffer and calls poweroff
  • Restart on exit — if sdlamp2 exits cleanly (status 0), the restart loop in loadapp.sh re-launches it immediately
  • Crash recovery — if sdlamp2 crashes (non-zero exit), app_scheduling sleeps 30s then the loop retries
  • Easy revert — removing sdlamp2.ini restores the stock menu on next boot