The SDK (sdk/lichee, from MagicX's Linux_SDK.zip) is proprietary and never
enters git. This ledger lists every change made to it, with the reason, so that a
fresh unpack can be brought to the same state. scripts/apply-sdk-mods.sh applies
the first two tables (idempotent; --check audits), scripts/build.sh applies the
third at build time. Originals of edited files are kept beside them as *.orig;
replaced package directories move to sdk/lichee/.oakmoss-orig/.
Paths are relative to sdk/lichee/. "Source" names where the change came from:
Moss = Shaun Inman's Moss-zero28 notes, deps = docs/DEPENDENCIES.md, oakMOSS =
found while porting the tree to GCC 13 / glibc 2.38.
| patch | path | change | reason | source |
|---|---|---|---|---|
010-rules-mk-external-toolchain-override |
rules.mk |
env-driven external-toolchain block (TINA_EXT_TOOLCHAIN_{ROOT,PREFIX,TARGET_NAME,SYSROOT}, TINA_EXT_KERNEL_CROSS), CONFIG_TOOLCHAIN_BIN_PATH="./wrap ./bin", glibc >= 2.34 file specs; the libc spec is set a second time after the SDK's late reassignment |
docs/toolchain.md | oakMOSS |
020-kernel-config-4.9 |
device/config/chips/a133/configs/aw3/linux/config-4.9 |
NLS_UTF8=y, FAT iocharset utf8, VIDEO_SUNXI_VIN off (Moss "changes not present in configs"); BLK_DEV_LOOP=y (MIN_COUNT 8), INPUT_UINPUT=y, INPUT_JOYDEV=y, INPUT_FF_MEMLESS=y, SND_USB_AUDIO=m, XR829_WLAN=m, SQUASHFS_LZ4/LZO=y (deps §7), SQUASHFS_ZLIB/ZSTD=y (round 17; LZ4 and gzip took effect only with 134); the 21 XRADIO*/XRMAC*/NF_LOG_COMMON/IPV6_FOU*/BT_XR_BLUEDROID_SUPPORT symbols olddefconfig resolved once XR829 was on (the build's non-interactive silentoldconfig aborts on new prompts) |
PortMaster loop mounts, gptokeyb uinput, joystick nodes, rumble, USB audio, onboard radio | Moss, deps, oakMOSS |
030-toolchain-gcc-7.5.0-option |
toolchain/gcc/{Config.version,Config.in,common.mk} |
new menu option GCC_USE_VERSION_7_5_0 (vanilla GNU gcc-7.5.0.tar.xz, md5 recorded, HOST_BUILD_DIR set) |
releases.linaro.org no longer serves gcc-linaro-7.4-2019.02.tar.xz; fallback toolchain only |
oakMOSS |
040-gpu-km-kernel-cross |
package/kernel/gpu-km/Makefile |
CROSS_COMPILE=$(KERNEL_CROSS) instead of $(TARGET_CROSS) |
the PowerVR rogue_km build keys its compiler config on the prefix and has no aarch64-none-linux-gnu.mk; the module belongs with the kernel's compiler |
oakMOSS |
050-libffi-no-multi-os-directory |
package/libs/libffi/Makefile |
CONFIGURE_ARGS += --disable-multi-os-directory, placed before $(eval $(call BuildPackage)) (OpenWrt expands the args at eval time; appending after has no effect) |
Arm's gcc reports a lib64 multi-os dir, libffi installed to usr/lib64 and packaging failed |
oakMOSS |
060-util-linux-disable-more |
package/utils/util-linux/Makefile |
--disable-more in the target configure args (the host block already had it) |
with --without-ncurses, util-linux 2.25's more.c has no TERM source and fails; nothing ships more |
oakMOSS |
| path | change | reason | source |
|---|---|---|---|
package/utils/busybox/patches/905-glibc-2.31-no-stime.patch |
date/rdate: stime() -> clock_settime(CLOCK_REALTIME) (as upstream busybox did) |
glibc >= 2.31 dropped stime() from the link interface |
oakMOSS |
package/utils/bluez/patches/905-glibc-2.38-sync-rename.patch |
obexd's file-scope driver sync -> sync_driver |
collides with glibc 2.38's sync() declaration |
oakMOSS |
package/utils/bluez/patches/906-glibc-2.38-pause-rename.patch |
media.c static pause() -> media_pause |
collides with glibc 2.38's pause() |
oakMOSS |
package/utils/e2fsprogs/patches/905-sysmacros.patch |
#include <sys/sysmacros.h> in every file using makedev/major/minor |
glibc >= 2.28 dropped them from <sys/types.h>; a forced -include via CFLAGS broke the large-file struct stat setup |
oakMOSS |
package/libs/libubox/patches/900-libubox-no-werror.patch |
drop -Werror from libubox's CMakeLists |
GCC 13 -Werror=array-bounds in blobmsg.c |
oakMOSS |
package/libs/ncurses/ |
REPLACED by OpenWrt v21.02.7's ncurses 6.2 package ($(INCLUDE_DIR) -> $(BUILD_DIR), --with-termlib=tinfo, libtinfo installed); 5.9 moved to .oakmoss-orig/ncurses-5.9 (left under package/, it stays registered and builds) |
5.9's wide-char build breaks under GCC 13 + glibc 2.38; spruce's rnano, mupen64plus and libmp3lame need libncursesw.so.6 + libtinfo.so.6 |
OpenWrt |
toolchain/gcc/patches/7.5.0/ |
OpenWrt v19.07.10's 22-patch GCC 7.5.0 set (18 byte-identical to the SDK's linaro-7.4 set) | goes with tree patch 030 | OpenWrt |
dl/ncurses-6.2.tar.gz (+ dl/gcc-7.5.0.tar.xz, dl/binutils-2.28.tar.gz for the fallback) |
pre-fetched sources | the SDK's download URLs for these are dead or absent | GNU |
lichee/linux-4.9/drivers/char/sunxi_encrypt/encrypt.zero28 |
backup of the SDK's original object (452120 B, 2024-12-16, md5 f28d35eb…) |
build.sh swaps the object per board |
oakMOSS |
target/allwinner/a133-aw3/defconfig.orig, …/base-files/etc/rc.local.orig, package/base-files/files/etc/banner.orig, …/boot-resource/bootlogo.bmp.orig |
backups | build.sh overwrites these per build; the logo reverts to the SDK's when overlay/bootlogo.bmp is absent |
oakMOSS |
| path | change | reason | source |
|---|---|---|---|
target/allwinner/a133-aw3/defconfig |
REPLACED by the build config (configs/ext-armgnu13.config; phase1/phase2* for the fallback). build/toplevel.mk copies this file over .config on every make when the variant is tina (lines 96-103) and menuconfig writes back here, so editing .config is futile. This is the mechanism behind Moss's "copying a config to .config re-enables everything" note; two builds were lost to it |
the config must live where the SDK reads it | oakMOSS |
target/allwinner/a133-aw3/base-files/etc/rc.local |
overlay/etc/rc.local: the SDK's mixer defaults plus exec of /usr/magicx/bin/runmagicx.sh |
the hand-off | Moss |
package/base-files/files/etc/banner |
overlay/etc/banner |
Moss's banner | Moss |
target/allwinner/generic/boot-resource/boot-resource/bootlogo.bmp |
overlay/bootlogo.bmp if present, else the SDK's original |
Moss's logo is his and is not shipped; scripts/make-boot-resource.py --logo zero28 overlay/bootlogo.bmp draws oakMOSS's own (the spruce tree, assets/spruce-tree.png). The Zero 28's boot-resource partition is 256 KiB, so its logo is 200 px |
oakMOSS |
package/add-rootfs-demo/{usr,etc} |
the whole overlay/ (/usr/magicx with bin/runmagicx.sh, the SDL2 blobs, the device marker; /etc/modules.d/net-xr829 empty; /etc/init.d/wpa_supplicant a no-op) plus boards/<board>/overlay/; add-rootfs-demo copies it into the rootfs and re-squashes, so these override what the packages installed |
/usr/magicx/device is what the launcher reads to tell zero28 from zero40; the empty modules file stops the XR829 driver autoloading and tearing down the Realtek 8189es's SDIO card (docs/hardware-notes.md); the init stub replaces wifimanager's S96 service, which started a second wpa_supplicant on wlan0 (empty /etc/wifi/wpa_supplicant.conf) that sent a DISCONNECT after every association the launcher's own supplicant made (Zero 28, 2026-09-16) - the launcher owns the radio |
Moss, oakMOSS |
package/add-rootfs-demo/usr/magicx/tina_config.gz |
gzip of the build's .config |
Moss's install.sh ships it in the image |
Moss |
lichee/linux-4.9/drivers/char/sunxi_encrypt/encrypt |
Zero 28: the SDK's object; Zero 40: MagicX's object from Zero40-lichee.zip (329272 B, 2025-09-22, md5 33a0a6d7…). The kernel's OpenWrt stamp does not track the prebuilt object, so build.sh records the linked md5 (.linked-md5) and forces a kernel relink when it changes |
the Zero 40 kernel needs its own object | MagicX |
| setting | reason |
|---|---|
USE_LIBSTDCXX=y (was USE_UCLIBCXX), CONFIG_PACKAGE_uclibcxx off |
build/uclibc++.mk makes every C++ package depend on uClibc++ and compile through its g++-uc wrapper; uClibc++ 0.2.4 does not build under GCC 13 and the Arm toolchain's full libstdc++ is shipped anyway |
TARGET_OPTIMIZATION + -fcommon + -Wno-error= format-truncation, format-overflow, array-bounds, stringop-truncation, stringop-overflow, maybe-uninitialized, deprecated-declarations, address, misleading-indentation, dangling-pointer, use-after-free |
OpenWrt core packages (ubox, netifd, libubox) build with -Werror; GCC 13 promotes these newer diagnostics; -fno-common became the default in GCC 10 |
kmod-net-xr829, xr829-firmware, XR829_BT, bluez-{libs,alsa,daemon,utils,utils-extra}, libical |
onboard radio and Bluetooth (deps §5/§7); bluez 5.54's obexd links libical and the ipkg dependency check needs the provider selected (Moss had turned it off together with bluez) |
libmad |
the TrimUI libSDL2_mixer blob NEEDs libmad.so.0; PyUI imports sdl2.sdlmixer at start-up and died before its first log line without it (first hardware boot, 2026-09-16). Also added to phase1/phase2* |
busybox UNZIP (+FEATURE_UNZIP_CDF), LOSETUP, STAT (+FEATURE_STAT_FORMAT), SHA1SUM, XXD |
spruce and PortMaster tooling (deps §6); BC does not exist in busybox 1.27.2 |
The reason for the round: MagicX's own kernel (which the Zero 40 hybrid boots) has no connection tracking, ours did, and the resulting struct sk_buff layout difference oopsed every module of ours loaded under theirs. sdk-patches/tree/020-kernel-config-4.9.patch is the regenerated pristine-to-round-8 diff; configs/ext-armgnu13.config is the round-8 userland config. Mod-set version 2.
| what | change | why | where |
|---|---|---|---|
device/config/chips/a133/configs/aw3/linux/config-4.9 (round 8, 2026-09-16) |
REGENERATED through the kernel's olddefconfig from the previous file plus: LOG_BUF_SHIFT=18, ZSMALLOC=y, ZRAM=m, CRYPTO_LZO/LZ4=y, SECCOMP=y, POSIX_MQUEUE=y, INPUT_JOYSTICK=y, JOYSTICK_XPAD=m (+FF, LEDS), HID_SONY=m (+SONY_FF), HID_MICROSOFT=m, HID_LOGITECH=m, BT_HIDP=m; the VIN and NF_LOG_COMMON lines the board-only resolve dropped are re-appended verbatim (they become reachable again once the kmod packages merge). Previous file kept as config-4.9.pre-round8. |
TODO.md | both |
target/allwinner/a133-aw3/defconfig = configs/ext-armgnu13.config (round 8) |
CONFIG_PACKAGE_softap/softap-demo/dnsmasq/hostapd OFF: softap's Makefile injects CONFIG_NF_CONNTRACK=y, NF_NAT=y, the CONNMARK/STATE/REDIRECT/NETMAP xt options into the kernel config (the board config-4.9 says conntrack is off, the real .config had it on). Conntrack adds a pointer to struct sk_buff, so our modules read skb->data 8 bytes past where MagicX's kernel keeps it - the Zero 40 xradio crash under the hybrid. CONFIG_PACKAGE_bash=y; BusyBox applets TIMEOUT, NOHUP, SETSID, PASTE, PKILL, FUSER, LSOF, WATCH on. wifimanager kept (its init script is stubbed by the overlay). Previous file kept as ext-armgnu13.config.pre-round8. |
Zero 40 xradio oops analysis; wishlist | ext builds |
config-4.9 (round 8b/8c) |
Three build aborts in a row taught the merge order: the kernel's silentoldconfig runs on config-4.9 PLUS the selected kmod packages' KCONFIG lines, so a board-only olddefconfig cannot see the prompts that appear once NETFILTER_XTABLES etc. are merged in (and softap used to answer XT_MATCH_QUOTA2, CONNSECMARK, SECMARK, REJECT_SKERR for us). Recipe that works: let the build write the merged lichee/linux-4.9/.config, run make olddefconfig on a copy inside the container, append every line whose VALUE changed (name-only diffs miss kconfig choices such as CCI), repeat until the build passes the config step. Appended this way: XT_MATCH_QUOTA2/CCI_TO_TWI/D3D/DISPPLAY_SYNC/BUF_AUTO_UPDATE/MULTI_FRAME/PIPELINE_RESET/ENABLE_SENSOR_FLIP_OPTION/WDR_COMPRESS_EN/BT_XR_BLUEDROID_SUPPORT/IPV6_FOU* not set, CSI_CCI=m, CCI=y, CGROUPS=y, NF_LOG_IPV4/IPV6=m, NF_REJECT_IPV4=m, SLABINFO=y; WDR forced back to not set (the resolver's default y does not compile: sunxi_isp.c references ISP_WDR_GAMMA_FE_MEM_OFS/bsp_isp_set_wdr_addr* that this tree lacks). |
this repo | both |
config-4.9 (round 8d) |
ZRAM, JOYSTICK_XPAD, HID_SONY, HID_MICROSOFT, HID_LOGITECH, BT_HIDP changed from =m to =y: the OpenWrt rootfs only carries modules that a kmod-* package installs, this SDK has none for them, and the .ko files stayed in the kernel tree (round 8 image had no zram.ko/xpad.ko). |
this repo | both |
Everything else in the config is Moss-zero28's phase-1 selection.
| what | where | why |
|---|---|---|
070-lcd-panels-rtp32hd016a-rtp40wv101b.patch |
lichee/linux-4.9/drivers/video/fbdev/sunxi/disp2/disp/lcd/ (rtp32hd016a.[ch], rtp40wv101b.[ch], panels.[ch], Kconfig, ../Makefile) |
The XU20's RTP32HD016A (1024x768) and the Zero 40's RTP40WV101B (480x800) two-lane DSI panels are in neither the SDK drop nor main-zero40. Their open/close flows, power and reset sequences and DSI init tables were read out of the boards' stock kernel binaries (XU20 developer user.img, kernel 4.9.170) with a symbol-resolving disassembly and reproduced call for call, delays included; the RTP32HD016A table keeps the stock driver's two malformed delay rows on purpose (they send DCS 0xfe with 120/20 bytes, which is what the shipped panel sees). Gamma/cmap setup is the SDK's standard one. |
020-kernel-config-4.9.patch (refreshed) |
device/config/chips/a133/configs/aw3/linux/config-4.9 |
CONFIG_LCD_SUPPORT_RTP32HD016A=y, CONFIG_LCD_SUPPORT_RTP40WV101B=y. |
boards/xu20/board.dts, boards/zero40/board.dts (not SDK patches; installed by build.sh per board) |
device/config/chips/a133/configs/aw3/board.dts |
Generated by scripts/make-board-dts.py from the Zero 28 tree plus each board's stock tree: lcd0 timings and GPIOs, the simplepad key map, the wlan node, the battery model. The Zero 28's own tree comes back for boards without one. |
mods_version 2 -> 3.
| what | where | why |
|---|---|---|
080-touch-hynitron-axs15205.patch |
lichee/linux-4.9/drivers/input/touchscreen/hynitron/ (Radxa allwinner-bsp hyn_ts, GPL-2.0, minus its firmware images and tool-debug file), .../axs15205/axs15205.c (ours), touchscreen/Kconfig + Makefile hooks |
The XU20's Hynitron CST340 (I2C 0x5a) and the Zero 40's AXS15205 (0x3b). Hynitron: address from the node's reg, axis flags from the ctp node, -EBUSY accepted on PB7/PB6 (the ctp framework requests them first), firmware table guarded behind the (off) auto-update switches. AXS15205: written from the stock kernel's own driver (plain 32-byte frame reads, five slots, 480x800); the public ugreen sources are a different protocol. Details: hardware-notes "Touch". |
020-kernel-config-4.9.patch (refreshed) |
device/config/chips/a133/configs/aw3/linux/config-4.9 |
CONFIG_TOUCHSCREEN_HYNITRON_TS=y, CONFIG_TOUCHSCREEN_AXS15205=y (INPUT_SENSORINIT was already on). |
boards/xu20/board.dts, boards/zero40/board.dts (regenerated) |
device/config/chips/a133/configs/aw3/board.dts |
make-board-dts.py now also moves twi1_pins_a/b to the stock PB4/PB5 (the Zero 28 tree has PH2/PH3), enables twi1 and carries the stock touch node (XU20 ctp, Zero 40 cap_touch@3b) with its gpio specs in label form. Axis flags stay at the stock 0: PyUI maps touch through the inverse of DISPLAY_ROTATION itself. |
boards/*/board.dts (regenerated again, same day) |
disp node | cldo2-supply = <®_cldo2> (every lcd_powerN rail the stock tree names becomes a disp supply): without it the disp gets a dummy regulator for lcd_power, the unused-regulator sweep turns cldo2 off at 3.8 s, and the touch controller's I2C lines lose their rail (hardware-notes "The touch rail"). |
| 085-simplepad-no-uart-thread-without-sticks.patch | lichee/linux-4.9/drivers/input/keyboard/simplepad.c | The pad driver opens its stick UART only when joysticks != 0 but always started the UART reader thread; on the XU20 (no sticks) the thread dereferenced the NULL file and oopsed at 8.4 s of every boot (joystick_thread_fn+0x48, 2026-09-17 log; the boot survived, the thread died). The thread now starts only with an open UART. |
| 086-simplepad-axes-without-sticks.patch | lichee/linux-4.9/drivers/input/keyboard/simplepad.c | A board without sticks (XU20) registered no EV_ABS axis at all, and SDL does not count a device without axes as a joystick: SDL 2.26 (vendor) and 2.32 (spruce) both reported 0 joysticks on the XU20 (2026-09-22), so PortMaster's GUI, gptokeyb and every SDL port saw no pad. The pad now advertises ABS_X/ABS_Y held at 0, the centre of the range, and never reports them - what TrimUI's inputd does on the stickless Brick. Key codes and SDL's button numbering are unchanged. |
mods_version 3 -> 5 (4 = touch, 5 = simplepad).
The script could not complete on an SDK tree that already carried some of these changes and died on its first patch, leaving the marker at version 3 while the scripts were at 5. Three defects, all of which mattered:
| defect | effect |
|---|---|
Target paths were read with grep '^+++ b/'. Some tree patches carry vendor sources whose non-ASCII comments make ugrep treat the whole patch as binary and print nothing. |
The .orig backup loop silently found no files, so a forward apply would have overwritten originals with no backup. Patch 080 hit this exactly. |
The same line split +++ b/<path>\t<timestamp> on whitespace. |
Patches written with timestamps (070, 080) yielded bogus extra filenames. |
A change already present in the tree under different comments failed patch -R --dry-run, and the script treated that as "not applied" and tried to apply it again. |
010 was force-applied a second time, leaving a duplicated (inert) toolchain-override block in rules.mk. The tree has been restored. |
Targets are now parsed with awk (binary-safe, strips the timestamp). A patch whose
added lines are all already present is reported in-tree rather than reapplied.
Package patches compare their diff body, ignoring the prose header, because the same
fixes can already be present with a different provenance line: four were being reported
missing and would have been overwritten with byte-identical diffs. A fifth was present
under a different filename entirely, and installing ours beside it made quilt apply the
change twice and fail the build, which is why the comparison scans the whole directory.
An unpacked SDK tree that other work has touched can carry a change genuinely in place
without this repository having put it there; the in-tree check exists for that case.
| what | where | why |
|---|---|---|
080-touch-hynitron-axs15205.patch (refreshed) |
touchscreen/Kconfig, hynitron/hynitron_core.c |
Both drivers are declared tristate. Built in, their late_initcall probes ran on the kernel's init thread and stalled boots at the logo (hardware-notes "Touch drivers stalled the boot"). The Hynitron probe no longer pulses reset a second time after requesting its IRQ: loaded late, that pulse froze the XU20. |
020-kernel-config-4.9.patch (refreshed) |
config-4.9 |
CONFIG_TOUCHSCREEN_HYNITRON_TS=m, CONFIG_TOUCHSCREEN_AXS15205=m (were =y). |
090-touch-modules.patch |
target/allwinner/a133-aw3/modules.mk |
kmod-touchscreen-hynitron and kmod-touchscreen-axs15205, with no AUTOLOAD: the launcher loads the board's module once the board has settled (spruceOS magicx_load_touch). Both configs select the packages. |
100-disp2-fb-g2d-rotation.patch |
drivers/char/sunxi_g2d/g2d_driver.c, drivers/video/fbdev/sunxi/disp2/disp/{dev_fb.c,fb_g2d_rot.c,fb_g2d_rot.h} |
Hardware framebuffer rotation through G2D on every flip (vendor Dechuang patch, a133-tina-bsp-update 2025-10-11, with the acmeplus gist fixes folded in), enabled per board by disp_rotation_used/degree0 in board.dts: Zero 28 a quarter turn, XU20 a half turn. G2D init moved to subsys_initcall so it exists before disp. |
101-disp2-fb-g2d-rotation-current-frame-flip-by-crop.patch |
drivers/video/fbdev/sunxi/disp2/disp/fb_g2d_rot.c |
Two defects of the vendor rotation, both measured on the XU20 2026-09-22. (1) It rotated fb->var.yoffset, the frame currently SHOWN - the fb core updates that field only after the pan returns - so every flip showed the previous frame and a press appeared one flip late (felt as lag in PortMaster). It now rotates the requested offset, read from crop.y. (2) It switched the layer buffer address every frame; the DE then raised vsync interrupts at ~34 Hz and a flip issued 1-11 ms after a vsync took two frames. The flip now keeps one address and selects the frame by crop.y over the two stacked rotation frames, as the un-rotated path does. |
102-uboot-fb-bootlogo-rotate.patch |
U-Boot boot logo (Zero 28 only) | Rotates the boot logo the same way the kernel rotates the framebuffer, so the logo is not sideways on the Zero 28's portrait panel. |
apply-sdk-mods.sh |
patch --no-backup-if-mismatch |
GNU patch otherwise saves a file it could not match as <file>.orig, the name this script keeps the pristine SDK copy under, so one failed attempt overwrote the pristine config-4.9.orig. Every line now has one owning tree patch: a later patch that edits a line an earlier one adds makes the earlier one read as missing. |
sdk-patches/debug/kdebug-mark.patch |
init/main.c, the two touch drivers, i2c-sunxi.c, and since 2026-09-29 the suspend/resume path (PM core, regulator core, display, GIC, pinctrl, watchdog, PSCI, panic/restart; a reserved-memory mark ring in sun50iw10p1.dtsi) |
Not applied by this script. build.sh KDEBUG_MARK=1 applies it and every other build reverts it - only ever exactly: when the patch and the tree disagree the build stops with the tree untouched (no forced apply). Edit it through scripts/kdebug.sh: stash a file before its first debug edit, edit the SDK tree, regen (rebuilds the patch from the recorded pristine copies and checks it), build <board>; check compares every touched file byte for byte with pristine + patch. With oakmoss_mark=<phys> on the command line the kernel writes its progress (initcall addresses, init stages, driver steps) to an RTC general-purpose register that survives a power-off; make-own-kernel-stock.sh KMARK=1 has U-Boot save it into the env on the next boot, and scripts/analyze-card-readback.py names it from System.map. Purely additive over the tree patches' lines. |
mods_version 5 -> 6.
The image had no way to trace, decode input or hash-and-ship a file: on 2026-09-24 a
frame-skip hunt on the Zero 28 ran without base64 (binaries had to be streamed through
gzip), without od (device-tree reads came back silently empty) and without ftrace (the
display driver's own DISP_TRACE markers compile to nothing). Everything below is from
the Tina package tree or BusyBox; nothing new is downloaded except strace's source.
| what | where | why |
|---|---|---|
configs/ext-armgnu13.config |
BusyBox applets | base64, od, nproc, taskset (+FEATURE_TASKSET_FANCY), devmem, iostat, mpstat, pmap, dc (+FEATURE_DC_LIBM). BusyBox is in custom mode here, so the CONFIG_BUSYBOX_CONFIG_* lines are the switches; the CONFIG_BUSYBOX_DEFAULT_* lines are only defaults (timeout, losetup, lsof, xxd and watch were already on). BusyBox 1.27.2 has no bc (added in 1.30); dc is the calculator it has. |
configs/ext-armgnu13.config |
packages | strace, gdbserver (gdb was already on), i2c-tools, trace-cmd, memtester, stress-ng, htop, iperf3, tcpdump (+libpcap), ethtool. The rootfs is a ~50 MiB squashfs in a 600 MiB partition. |
065-htop-link-libtinfo.patch |
package/utils/htop/Makefile |
TARGET_LDFLAGS += -ltinfo: our ncurses 6.2 is built --with-termlib=tinfo, so napms() and the other terminfo symbols are in libtinfo, and htop 2.0.2 links -lncursesw only ("DSO missing from command line"). |
066-strace-6.10.patch |
package/devel/strace/Makefile |
strace 5.0 -> 6.10 and PKG_FIXUP:=autoreconf dropped (6.10 ships its configure; the host autoconf is 2.69). 5.0 does not build against the GCC 13 / glibc 2.38 headers (struct ptrace_syscall_info "declared inside parameter list"). Source: https://strace.io/files/6.10/strace-6.10.tar.xz, sha256 765ec71a...cf07, signature checked against Dmitry V. Levin's release key (primary 296D 6F29 A020 808E 8717 A884 2DB5 BD89 A340 AEB7); pre-staged in dl/ for the first build. |
sdk-patches/package-patches/stress-ng/905-glibc-2.34-pthread-stack-min.patch |
package/allwinner/stress-ng/patches/ |
stress-ng 0.12.07 compares PTHREAD_STACK_MIN in #if; glibc >= 2.34 makes it sysconf(...), which the preprocessor cannot evaluate. The run-time #else branch is kept (what stress-ng does from 0.13). |
110-env-4.9-quiet-serial-console.patch |
device/config/chips/a133/configs/aw3/linux/env-4.9.cfg |
loglevel=8 -> 4. The Zero 28's image boots with this env (console=ttyS0,115200), and every kernel line was written synchronously to a 115200-baud UART - the RTW driver's power-save chatter comes in ~200-byte bursts, about 17 ms of UART, longer than a frame. Measured 2026-09-24: 30 injected 4-line log bursts cost 36 dropped frames (disp skip:) at loglevel 8 and 0 with the console quieted; the XU20 (console=tty0) dropped none either way. Warnings and errors still reach the UART; everything stays in dmesg and pstore. The XU20 and Zero 40 do not use this env (make-own-kernel-stock.sh sets their loglevel=7 with console=tty0). |
sdk-patches/debug/kdebug-ftrace.config |
config-4.9 |
Not applied by this script. build.sh KDEBUG_FTRACE=1 puts this block where # CONFIG_FTRACE is not set stands (function, function-graph and event tracing; every prompt answered, resolved with olddefconfig and checked with listnewconfig: none left) and every other build takes it out again. It also sets CONFIG_KERNEL_FTRACE, _FUNCTION_TRACER, _FUNCTION_GRAPH_TRACER and _DYNAMIC_FTRACE in the SDK's top-level defconfig: build/kernel-defaults.mk appends every CONFIG_KERNEL_* line to the kernel config and the later line wins, so the first debug kernel came out without tracing. Both files are rewritten only when their state is wrong (build.sh kdebug_block). Proven from built images 2026-09-24 with extract-ikconfig: KDEBUG_FTRACE=1 gives CONFIG_FTRACE/FUNCTION_TRACER/DYNAMIC_FTRACE/EVENT_TRACING=y, the next ordinary build is back to # CONFIG_FTRACE is not set with both files byte-identical to before. Debug cards only. |
mods_version 6 -> 7.
| what | where | why |
|---|---|---|
100-disp2-fb-g2d-rotation.patch (refreshed, same line count) |
dev_fb.c Fb_copy_boot_fb |
The 90/270 dst width/height swap is gone. info->var already holds the rotated frame (fb0_width/height are the panel's swapped), so the swap made the 270 copy (the Zero 28's degree0 1) start at row 639 of a 480-row buffer: 160 rows low and partly in the second buffer. Reported on the unit: the boot logo was right way up but "transposes down the screen after flickering" at the kernel's takeover. The 90 and 180 copies never read the swapped value. |
080-touch-hynitron-axs15205.patch (refreshed, same line count) |
hynitron_core.c |
The ctp node's ctp_revert_x/y_flag / ctp_exchange_x_y_flag are now the whole transform (? 1 : 0). Before, flag ? 1 : HYN_*_REVERT could only ADD a flip, and HYN_Y_REVERT 1 forced a Y flip whatever the node said, so no node could express "no Y flip". |
boards/xu20/board.dts, scripts/make-board-dts.py |
ctp node | ctp_revert_x_flag 1 -> 0 (Y stays 0). The panel frame needs X and Y flipped (TOUCH_PANEL_FRAME); the kernel shows the panel turned 180, which flips both back, so touch arrives in the frame every app draws in. touch_flags() derives the flags from TOUCH_PANEL_FRAME and FB_ROTATION (a quarter-turn with touch stops the generator: exchange is not modelled). Reported on the unit: XU20 touch inverted under the kernel rotation, as predicted from PyUI no longer inverting it. |
Images: builds/20260924-1710-zero28 (both rounds), builds/20260924-1713-xu20-own (md5 1f5ced17...; both DTB copies checked: revert x/y 0, degree0 2). Not yet run on hardware.
All three boards now flash the SDK chain (our U-Boot with their panels, SDK boot0 and ATF/SCP), which is what reaches charge mode; real suspend-to-RAM works on all three with the fixes below.
| what | where | why |
|---|---|---|
020-kernel-config-4.9.patch (refreshed) |
config-4.9 |
CONFIG_GPIO_SYSFS=y: spruce drives the MagicX rumble motor through /sys/class/gpio (PH3, gpio227 on the Zero 40 and XU20); without it the export node did not exist and rumble was silently unavailable. |
120-axp2202-gauge-model-content-check.patch |
drivers/power/supply/axp2202_battery.c |
The fuel gauge's 128-byte battery model is read back (the driver's own verify sequence) and reloaded when it differs from the device tree's. The driver loaded it only when the gauge's update mark was clear - after the PMIC lost power - and never compared, so a gauge kept whatever model it had. Paired with each board's own stock profile in boards/*/board.dts (make-board-dts.py; every board used to carry the SDK's). |
boards/*/board.dts, scripts/make-board-dts.py |
pad keys | One key code per press: the stock trees' second codes (linux,code2: A 353, and on the XU20 B 158 and MENU 256, their Android launcher's confirm and back keys) are dropped. The XU20's extra face button (PH11) was code 0, which the kernel drops; it is now KEY_HOMEPAGE (172), the TrimUI Smart Pro S's Home key. spruce reads the pad's key bitmap at run time, so a card works on older images too. |
103-uboot-lcd-panels-rtp32hd016a-rtp40wv101b.patch |
lichee/brandy-2.0/u-boot-2018/drivers/video/sunxi/disp2/disp/lcd/ (rtp32hd016a.[ch], rtp40wv101b.[ch], panels.[ch], Kconfig, ../Makefile) |
070's two panel drivers in U-Boot, so our own U-Boot lights the Zero 40 and XU20 panels (boot logo, charge-mode picture) on the SDK chain. U-Boot's panel API is the kernel's with three typedef spellings (panel_extend_para, disp_panel_para, __lcd_panel_t); sequences unchanged. Their stock U-Boots carry the same two drivers, and main-zero40's SDK U-Boot differs from the SDK's only by RTP40WV101B (2026-09-26 string diff). The stock XU20 U-Boot also has an RTP36HD029A, a panel variant neither our kernel nor this patch has. |
boards/<board>/sys_config.fex |
charge_mode = 1, battery_exist = 1 |
U-Boot draws bat/battery_charge.bmp when a board is plugged in while off and boots Linux with androidboot.mode=charger; runmagicx.sh then runs the charge screen (overlay/usr/magicx/bin/charge-screen.sh) instead of the launcher. |
boards/<board>/sys_partition.fex |
boot-resource partition 4 MiB | Room for the charge, low-battery (bat0, low_pwr) and logo pictures (the XU20's set is 2.9 MB). |
130-i2c-sunxi-no-irq-storm-on-bus-error.patch |
drivers/i2c/busses/i2c-sunxi.c |
A bus error (TWI status 0x00) left the controller's interrupt flag set - the vendor's flag clear sat after its goto - and the handler re-enabled the interrupt unconditionally, so it fired for ever: a hard lockup. On the XU20 with the touch driver loaded this froze every suspend: the display cuts the panel rail (cldo2) at the end of device suspend, the touch controller on twi1 goes down, the idle bus reports a bus error (captured 2026-09-29: stat 0x00, ctl 0xc8, no transfer in flight). Now the flag is cleared, the interrupt is re-enabled only while a transfer is in flight, and an interrupt with no transfer resets the controller and stays off (sunxi_i2c_do_xfer re-enables it). |
080-touch-hynitron-axs15205.patch (refreshed) |
axs15205/axs15205.c |
The Zero 40's touch interrupt comes back in the PM .complete step instead of .resume. The chip is powered with the panel and leaves reset through the panel's power-on sequence, which the display runs in its own resume - in parallel when devices resume asynchronously - so the edge latched while the chip lost power at suspend was replayed at once and its frame read hit a chip still in reset (i2c1 incomplete xfer (status 0x48, 0x3b), 19 ms into the display's resume, 2026-09-29). A flag keeps disable/enable balanced when a suspend is aborted. |
sdk-patches/debug/kdebug-mark.patch (extended) |
suspend/resume path | See its row under round 11: the marks, probes and switches that found the XU20 freeze. |
scripts/build.sh, scripts/apply-sdk-mods.sh, scripts/kdebug.sh |
patch application | Never forced: the debug patch is applied or reverted only exactly and a tree patch only after a clean dry run; when a patch and the tree disagree the scripts stop with the tree untouched. A forced partial apply had duplicated hunks in seven kernel files. kdebug.sh keeps the debug patch in step with the tree from recorded pristine copies. |
U-Boot is not rebuilt by make: the pack takes the binary at
device/config/chips/a133/bin/u-boot-sun50iw10p1.bin. After an SDK unpack or a U-Boot patch
(102, 103), run scripts/build.sh uboot: it builds the Tina defconfig
(sun50iw10p1_tina_defconfig; brandy's own build.sh -o uboot builds all five sun50iw10p1*
defconfigs and leaves whichever came last, and sun50iw10p1_defconfig is the Android one, with
AVB and A/B slots) and installs it. build.sh image refuses a binary without the panels and the
logo rotation, or an Android build, and records its md5 in BUILD-INFO.txt (uboot_md5).
mods_version 7 -> 8.
DraStic (trngaje's advdrastic, which finds the touchscreen through udev) saw no touchscreen on the Zero 40 and XU20, and once it did, no tap registered: the log showed each held touch re-reported as a new one.
| what | where | why |
|---|---|---|
131-input-drop-repeated-mt-values.patch |
drivers/input/input.c |
The vendor commented out the input core's "drop unchanged value" check (for a g-sensor that repeats samples), so every report passes. Our touch drivers use input_mt, which relies on that check: each report re-sent the slot's ABS_MT_TRACKING_ID (and -1 for the four empty slots; raw capture on the Zero 40, 2026-10-02), and SDL turns every repeated tracking ID into a finger up and down, so no app sees a held touch. The check comes back for multitouch values only; other axes keep the vendor behaviour. |
overlay/etc/udev/rules.d/60-oakmoss-touchscreen.rules |
/etc/udev/rules.d |
The image has no 60-input-id.rules, so udev tags no input device and libudev users find no touchscreen. The rule runs udev's input_id builtin on the two touch controllers only (axs_ts, hyn_ts): run on every input device it tags the pad a mouse (virtual-mouse buttons 272/273 plus ABS_X/ABS_Y), and SDL would stop treating it as a joystick. It also gives the XU20's touchscreen a calibration matrix (panel turned 180 degrees, axes inverted against it). |
132-bash-links-installed-readline.patch |
package/utils/bash/Makefile |
A build from an empty out/ failed on both build hosts (2026-10-02): Package bash is missing dependencies for the following libraries: libhistory.so.6. bash declared only libncurses, so whether it linked its bundled readline or the staged libreadline/libhistory depended on which package finished first; warm trees happened to win (the shipped bash needs only libtinfo and libc), a cold parallel build loses every time. bash now declares libreadline and links it (--with-installed-readline), as OpenWrt's own package does; bash 4.3 and readline 6.3 are a matched pair and the image already ships libreadline. |
mods_version 8 -> 10 (9 was round 15 without 132).
The Zero 40 (BlueZ 5.54 + the SDK's bluez-alsa 1.3.1, measured 2026-10-03) lost Bluetooth audio
for good after RetroArch's in-game menu or a game-switcher exit: bluealsa's main and control
threads sat in futex_wait with no IO thread left, bluetoothd logged Endpoint replied with an error: org.freedesktop.DBus.Error.NoReply and Unable to select a valid configuration, every
connect failed with org.bluez.Error.Failed, and amixer -D bluealsa scontrols blocked. In
1.3.1, ctl.c serves every request under config.devices_mutex and DRAIN waits on
pcm->drained with that mutex held, while the A2DP IO thread signals "drained" only from a
100 ms poll timeout and exits on FIFO EOF or a write error without signalling. Upstream rewrote
that locking (bbad9f5, e58331f, d14269a), all in 4.0.0. Separately, 1.3.1 without
--a2dp-volume copied the headset's AVRCP volume into its own software gain while the headset
applied it too: half volume on the headset played about 32 dB down. 4.0.0 no longer does: an A2DP
source in software-volume mode ignores the headset's volume reports, with or without that option.
4.0.0 still drains the same way in a narrower case: the PCM "Drain" command runs on the main GLib loop thread and waits there, with no timeout, for a signal only the IO thread's 100 ms poll timeout sends. A client that dies mid-drain is handled (the IO thread keeps running and signals), but an IO thread that exits mid-drain (headset disconnected or out of range, BT write error) leaves the main loop blocked for good, with the same symptoms. Upstream fixed this after 4.0.0, and its ALSA plugin waited for every control reply with no time limit; those fixes are backported (906, 907).
| what | where | why |
|---|---|---|
133-bluez-alsa-4.0.0.patch |
package/multimedia/bluez-alsa/Makefile |
1.3.1 (snapshot 20180913) -> 4.0.0. 4.0.0 is the newest this SDK can build (glib 2.50.1, sbc 1.3): 4.1.0 calls sbc_reinit_a2dp(), which libsbc exports from 1.5 (upstream 23662ac raised the declared floor in 4.1.1); 4.1.1 also generates its D-Bus code with gdbus-codegen --interface-info-body, which neither the SDK's host glib 2.50 nor the build container's (Ubuntu 18.04, glib 2.56) has; 4.2.0 and 4.3.x need glib >= 2.58.2 (ed24c9c) and bluez >= 5.51 (4.0.0 needs glib >= 2.32, sbc >= 1.2, dbus-1 >= 1.6; BlueZ 5.54 meets bluez >= 5.0). 4.3.0 also opens a new codec configuration whenever a client asks for another rate (13b0bf8), which made earbuds announce a disconnect on every app switch on another spruce base; 5.x renames the daemon (bluealsad) and CLI, which spruce calls by name. SBC only: AAC, aptX, aptX HD, FastStream, LC3plus, LDAC, MP3, mSBC, oFono, UPower and systemd off (fdk-aac no longer pulled in). Installs bluealsa, bluealsa-aplay, bluealsa-cli, the pcm/ctl plugins in /usr/lib/alsa-lib, 20-bluealsa.conf in /usr/share/alsa/alsa.conf.d (where this alsa-lib's alsa.conf hooks load add-ons) and the D-Bus policy /etc/dbus-1/system.d/bluealsa.conf (4.x owns org.bluealsa on the system bus). Declares bluez-libs and libdbus, which it links. Nothing starts the daemon at boot (no init script, procd entry or bt_init.sh line); spruce starts it (bluealsa -p a2dp-source). |
sdk-patches/package-patches/bluez-alsa/905-a2dp-stdlib-qsort.patch |
package/multimedia/bluez-alsa/patches/ |
src/a2dp.c calls qsort() without <stdlib.h>: an implicit declaration, a warning under GCC 13 and an error from GCC 14. |
sdk-patches/package-patches/bluez-alsa/906-drain-sync-backport.patch |
package/multimedia/bluez-alsa/patches/ |
The daemon's drain cannot block its main loop for good: upstream 7ae09a4 (4.1.0) and 0677f97 (4.2.0), adapted to 4.0.0 (see "Backported from upstream" below). |
sdk-patches/package-patches/bluez-alsa/907-pcm-ctrl-nosignal-timeout.patch |
package/multimedia/bluez-alsa/patches/ |
PCM clients (the ALSA plugin in every app, bluealsa-cli) neither hang on nor are killed by the PCM control socket: upstream 916c50f and 5f3eeef (4.2.0), as upstream wrote them. |
apply-sdk-mods.sh step 3b |
package/multimedia/bluez-alsa/patches/ |
The SDK's two patches for the 2018 source (0001-add-extern-hfp-over-pcm-support, 0002-fix-volume-adjustment-is-limited-by-name-length) do not apply to 4.0.0 and move to .oakmoss-orig/bluez-alsa-20180913-patches/. |
scripts/lib.sh, fetch-inputs.sh, apply-sdk-mods.sh step 5 |
dl/bluez-alsa-4.0.0.tar.gz |
The v4.0.0 tag archive is pinned (BLUEZALSA_*, sha256 = the Makefile's PKG_HASH), fetched into inputs/ and staged into dl/. |
mods_version 10 -> 11.
The source is the v4.0.0 tag; these upstream commits are carried as patches on it. Each patch's header names the commits and says what was taken and what was changed.
| upstream commit | release | what it fixes | in oakMOSS |
|---|---|---|---|
| 7ae09a4 Fix PCM drain synchronization - use mutex for sync | 4.1.0 | ba_transport_pcm_drain() used a bare pthread_cond_wait() with no condition to check: a signal sent before the wait, or none at all, left it waiting forever. Upstream waits in a loop for a synced flag set under the mutex. |
906. Taken: the flag, the loop, and the flag set in io.c's poll timeout. Changed: the flag stays under 4.0.0's dedicated synced_mtx, not pcm->mutex as upstream (in 4.0.0 the PCM open handler and the IO thread's blocking FIFO write hold pcm->mutex for long stretches; upstream moved those to a client mutex in 0959403). The condition variable is renamed synced -> synced_cond so the flag can take upstream's name. Upstream's th->changed -> th->cond rename is left out. |
| 0677f97 Fix PCM drain lockup caused by transport destroy | 4.2.0 | An IO thread cancelled or exiting mid-drain never signalled the drain. Upstream's per-PCM thread cleanup now marks the PCM synced. | 906. 4.0.0 has no per-PCM thread cleanup, so the same lines go in ba_transport_thread_cleanup(), for the PCMs whose th is the exiting thread, after the state is reset to NONE. |
| (none: oakMOSS) | - | A 4.0.0 IO thread that has exited keeps its ID until the main thread joins it, so a drain arriving after the cleanup ran would still wait forever. | 906. The drain checks the thread state under synced_mtx and returns ESRCH unless it is RUNNING. The cleanup resets the state before it takes synced_mtx, so a drain either sees the thread gone or gets the cleanup's wake-up. Lock order: synced_mtx -> state_mtx, and nothing takes them the other way round. |
| 916c50f Protect PCM client control socket from SIGPIPE | 4.2.0 | write() to a control socket the daemon had closed (daemon stopped or restarted) raised SIGPIPE and killed the app. |
907, as upstream: send(..., MSG_NOSIGNAL). |
| 5f3eeef Do not block forever PCM client on control command | 4.2.0 | The plugin waited for each control reply with poll(..., -1): an app froze for good if the daemon never answered. |
907, as upstream: 3000 ms for Drain, 200 ms for Drop, Pause and Resume, then EIO. 4.0.0's one direct caller, the plugin's bluealsa_pause(), passes 200 as upstream's does. |
Considered and not backported:
- f58b26b (4.1.0), 0bcebad and 3ee983c (4.2.0): rework of the plugin's local drain (wait until the
ring buffer reaches the FIFO,
snd_pcm_abort()support, a bounded local wait). 4.0.0's plugin drain does not wait locally (it sends Drain, which 907 now bounds), and 0bcebad builds on f58b26b and 4.2's plugin I/O thread. The cost is audio quality: the last fraction of a second of a stream can be cut at close. It does not hang. - 0959403 (4.1.0, a separate client mutex per PCM): a restructuring that 906 works around by
keeping
synced_mtx. - cccc177 (4.1.0): PCM open (also on the main loop) waits forever if an IO thread fails during setup. That is the open path, not drain; SBC setup fails only on an allocation or encoder-init error. Left as a known residual.
- 5466efc (4.1.0): fixes
ba_transport_stop()as 4.1 rewrote it around a TERMINATED thread state. 4.0.0 has no such state, and its stop asks the thread manager to cancel and then waits on the thread IDs.
spruce asked for zlib: squashfs images made with mksquashfs's default compressor, gzip, did not
mount (nearly all of PortMaster's runtimes; its 32-bit Godot 4.5 runtime is zstd). The built kernels (System.map) had only the XZ and LZO decompressors: SQUASHFS_LZ4=y
from round 8 had never taken effect either. Tina's KERNEL_SQUASHFS_{XZ,LZ4,ZLIB} are one choice,
the rootfs image's compressor (config/Config-images.in), and build/kernel-defaults.mk copied all
three into the kernel's configuration after config-4.9: the two not chosen arrived last as "is not
set" and switched those decompressors off.
| what | where | why |
|---|---|---|
020-kernel-config-4.9.patch |
device/config/chips/a133/configs/aw3/linux/config-4.9 |
SQUASHFS_ZLIB=y, SQUASHFS_ZSTD=y. ZLIB_INFLATE was already built in, so gzip adds only squashfs's zlib wrapper; zstd brings in the kernel's backported zstd decompressor (lib/zstd, hidden ZSTD_DECOMPRESS and XXHASH, no new prompts). |
134-kernel-keeps-squashfs-decompressors.patch |
build/kernel-defaults.mk |
The choice's unchosen "is not set" lines are no longer copied, so config-4.9 decides which decompressors the kernel has: XZ, LZ4, LZO, gzip and zstd. The rootfs image stays XZ. |
mods_version 11 -> 12.