From ed3452f3ecccf9de0d95ba869eff56fad61777ac Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Fri, 7 Aug 2026 18:52:06 +0900 Subject: [PATCH 01/26] odroid-m1-panfrost: switch GPU stack from Mali-G52 blob to Mesa panfrost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static compatibility check before flipping anything: panfrost's of_match_table matches this board's DTS gpu node exactly ("arm,mali- bifrost"), the driver's unnamed clk_get grabs the first listed clock (the SCMI-backed "clk_mali", so no clk-scmi -517 risk - CONFIG_COMMON_ CLK_SCMI is already =y), and panfrost's default regulator name ("mali") matches the board's "mali-supply" property exactly. The board dtsi override only adds mali-supply + status=okay, no custom kbase-only properties - this DTS was effectively already mainline-shaped, which was the main open risk per the Panfrost integration plan (Joplin "시스템별 ROM 테스트 → Panfrost 통합 지시서"). Kernel side (linux-defconfig.config): CONFIG_DRM_PANFROST m->y, CONFIG_MALI_BIFROST unset (child options under its menuconfig will be dropped by the standard olddefconfig pass during build). buildroot side: disable BR2_PACKAGE_MALI_G52_ODROIDM1, enable full BR2_PACKAGE_MESA3D with the panfrost gallium driver + EGL/GLES, which takes over as the HAS_LIBEGL/HAS_LIBGLES/HAS_LIBGBM provider. No Vulkan driver enabled - the A/B plan explicitly excludes Vulkan on both sides (blob has none, Mesa's PanVK is non-conformant on G52) to keep the comparison scoped to GLES only. Kronos's mesa3d-headers-vs-mesa3d conflict no longer applies on this branch (noted but not re-enabled, to keep this diff scoped to the GPU driver swap). Not yet boot-tested - this is the config-only half of the swap, before an actual hardware smoke test. --- .../bsp-odroidm1/linux-defconfig.config | 8 ++--- configs/batocera-rk3568-odroidm1.board | 35 ++++++++++++++++--- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/board/batocera/rockchip/bsp-odroidm1/linux-defconfig.config b/board/batocera/rockchip/bsp-odroidm1/linux-defconfig.config index 971ccdcb0d9..b6a5e3cb5af 100644 --- a/board/batocera/rockchip/bsp-odroidm1/linux-defconfig.config +++ b/board/batocera/rockchip/bsp-odroidm1/linux-defconfig.config @@ -6203,7 +6203,7 @@ CONFIG_TINYDRM_ST7735R=m # CONFIG_DRM_PL111 is not set # CONFIG_DRM_XEN_FRONTEND is not set # CONFIG_DRM_LIMA is not set -CONFIG_DRM_PANFROST=m +CONFIG_DRM_PANFROST=y # CONFIG_DRM_TIDSS is not set CONFIG_DRM_GUD=m # CONFIG_DRM_SSD130X is not set @@ -6231,9 +6231,9 @@ CONFIG_MALI_DEVFREQ=y # CONFIG_MALI_CORESTACK is not set CONFIG_MALI_PWRSOFT_765=y # CONFIG_MALI_KUTF is not set -CONFIG_MALI_BIFROST=y -CONFIG_MALI_PLATFORM_NAME="rk" -CONFIG_MALI_REAL_HW=y +# CONFIG_MALI_BIFROST is not set +# CONFIG_MALI_PLATFORM_NAME is not set +# CONFIG_MALI_REAL_HW is not set # CONFIG_MALI_BIFROST_NO_MALI is not set # CONFIG_MALI_IS_FPGA is not set diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index e739f2e1baa..3e674891e3e 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -47,10 +47,31 @@ BR2_PACKAGE_HOST_DTC=y BR2_PACKAGE_BATOCERA_KODI21=y BR2_TARGET_ROOTFS_SQUASHFS4_ZSTD=y -# GPU - Mali-G52 blob (hardkernel). The kernel also builds Panfrost as an -# unused module (matches the real device: loaded, refcount 0) but we use -# the proprietary blob, matching what's verified working on hardware. -BR2_PACKAGE_MALI_G52_ODROIDM1=y +# GPU - Panfrost (Mesa gallium), replacing the Mali-G52 blob for the +# Panfrost A/B experiment (branch odroid-m1-panfrost / kernel branch +# panfrost-enable). Static DTS check confirmed a clean match against +# panfrost's of_match_table before this switch: compatible = "arm,mali- +# bifrost" (drivers/gpu/drm/panfrost/panfrost_drv.c dt_match), clock +# lookup is unnamed/positional (devm_clk_get(dev, NULL) grabs the first +# listed clock, which is the SCMI-backed "clk_mali" - no clk-scmi -517 +# risk since CONFIG_COMMON_CLK_SCMI is already =y), and the "mali-supply" +# regulator property matches panfrost's default_supplies[] = {"mali", +# NULL} exactly. Board dtsi override (rk3568-evb.dtsi &gpu) only adds +# mali-supply + status=okay, no custom kbase-only properties - this DTS +# was effectively already mainline-shaped. Kernel side: CONFIG_MALI_ +# BIFROST unset, CONFIG_DRM_PANFROST=y (see linux-defconfig.config). +# BR2_PACKAGE_MALI_G52_ODROIDM1=y + +# Mesa3D with the panfrost gallium driver takes over as the HAS_LIBEGL/ +# HAS_LIBGLES/HAS_LIBGBM provider (mali-g52-odroidm1's Config.in used to +# do this via BR2_PACKAGE_PROVIDES_LIBEGL=mali-g52-odroidm1 etc.). No +# Vulkan driver enabled here on purpose - Joplin A/B plan explicitly +# excludes Vulkan from this comparison (blob has none, Mesa's PanVK is +# non-conformant on G52) to keep both sides GLES-only and comparable. +BR2_PACKAGE_MESA3D=y +BR2_PACKAGE_MESA3D_GALLIUM_DRIVER_PANFROST=y +BR2_PACKAGE_MESA3D_OPENGL_EGL=y +BR2_PACKAGE_MESA3D_OPENGL_ES=y # No compositor (labwc/Wayland): the vendor Mali blob's Wayland-client EGL # platform is broken (confirmed live on hardware - eglGetPlatformDisplayEXT @@ -105,6 +126,12 @@ BR2_PACKAGE_LIBRETRO_FBNEO_KOREAN=y # instead of full Mesa. Needs kronos.mk patched to not require full mesa3d # on this board (unclear yet whether it actually needs anything beyond # headers for the FORCE_GLES path) before this can be turned back on. +# NOTE (odroid-m1-panfrost branch only): this specific blocker (mesa3d- +# headers vs full mesa3d hard-stop) no longer applies here now that the +# board runs full BR2_PACKAGE_MESA3D for panfrost - kronos could likely +# just be turned on as-is on this branch. Left off anyway to keep the +# Panfrost A/B diff scoped to GPU driver only; worth revisiting once +# panfrost itself is confirmed working on hardware. # BR2_PACKAGE_LIBRETRO_KRONOS=y # NDS: upstream only selects libretro-melonds-ds when BR2_PACKAGE_ From a62a86e63ba90dac06d201a62d56b417755b5887 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Fri, 7 Aug 2026 19:24:04 +0900 Subject: [PATCH 02/26] batocera-system: gate TRX's auto-select on real desktop-GL capability, not the odroid-m1 blob package by name TRX (Tomb Raider I-III) was excluded on this board via `!BR2_PACKAGE_ MALI_G52_ODROIDM1` (commit c0d2cc968f) as a stand-in for "no desktop GL here" - correct at the time (only the blob existed), but using the package name instead of the actual capability flag meant disabling that package (for the Panfrost A/B swap) flipped the condition back on, hard-stopping the build: TRX still unconditionally selects libglew (package/batocera/ports/trx/Config.in has no guard of its own), which needs BR2_PACKAGE_HAS_LIBGL - false for both GPU stacks on this board (blob is GLES-only; the panfrost gallium driver here only has OPENGL_ES/OPENGL_EGL enabled, no desktop OPENGL) - so the custom dependency-consistency check ("libgl is in the dependency chain of libglew that has added it to its _DEPENDENCIES variable without selecting it") fires regardless of which stack backs the board. Switched the condition to check BR2_PACKAGE_HAS_LIBGL directly, which is correct for both stacks (and any future one) without needing a per-package name check. --- configs/batocera-rk3568-odroidm1.board | 8 ++++++++ package/batocera/core/batocera-system/Config.in | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index 3e674891e3e..2ef431f8cb7 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -62,6 +62,13 @@ BR2_TARGET_ROOTFS_SQUASHFS4_ZSTD=y # BIFROST unset, CONFIG_DRM_PANFROST=y (see linux-defconfig.config). # BR2_PACKAGE_MALI_G52_ODROIDM1=y +# (TRX/Tomb-Raider auto-select used to be gated on !BR2_PACKAGE_MALI_ +# G52_ODROIDM1 specifically as a stand-in for "this board has no desktop +# GL" - flipped back on and hard-stopped the build once that package was +# disabled here. Fixed at the real root cause instead: batocera-system/ +# Config.in's TRX select now checks BR2_PACKAGE_HAS_LIBGL directly, which +# is false for both the blob and Panfrost/GLES-only Mesa on this board.) + # Mesa3D with the panfrost gallium driver takes over as the HAS_LIBEGL/ # HAS_LIBGLES/HAS_LIBGBM provider (mali-g52-odroidm1's Config.in used to # do this via BR2_PACKAGE_PROVIDES_LIBEGL=mali-g52-odroidm1 etc.). No @@ -69,6 +76,7 @@ BR2_TARGET_ROOTFS_SQUASHFS4_ZSTD=y # excludes Vulkan from this comparison (blob has none, Mesa's PanVK is # non-conformant on G52) to keep both sides GLES-only and comparable. BR2_PACKAGE_MESA3D=y +BR2_PACKAGE_MESA3D_LLVM=y BR2_PACKAGE_MESA3D_GALLIUM_DRIVER_PANFROST=y BR2_PACKAGE_MESA3D_OPENGL_EGL=y BR2_PACKAGE_MESA3D_OPENGL_ES=y diff --git a/package/batocera/core/batocera-system/Config.in b/package/batocera/core/batocera-system/Config.in index f155f0ce28e..5b606310894 100644 --- a/package/batocera/core/batocera-system/Config.in +++ b/package/batocera/core/batocera-system/Config.in @@ -1225,7 +1225,7 @@ config BR2_PACKAGE_BATOCERA_PORTS_SYSTEMS select BR2_PACKAGE_LIBRETRO_BOOM3 if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY # Requires RA with OpenGL # Tomb Raider 1, 2 & 3 - select BR2_PACKAGE_TRX if BR2_PACKAGE_XORG7 && !BR2_PACKAGE_MALI_G52_ODROIDM1 + select BR2_PACKAGE_TRX if BR2_PACKAGE_XORG7 && BR2_PACKAGE_HAS_LIBGL # Quake 3 select BR2_PACKAGE_IOQUAKE3 if !BR2_PACKAGE_BATOCERA_TARGET_BCM2835 && \ From a7303971602f8bd2260d4d2a14f44566580585e2 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Sat, 8 Aug 2026 00:03:35 +0900 Subject: [PATCH 03/26] odroid-m1-panfrost: fix build-blocking Kconfig gaps found while bringing up the Panfrost image - batocera-system/Config.in: rocknix-joypad and sdl3's X11 backend were gated on !BR2_PACKAGE_MALI_G52_ODROIDM1 as a stand-in for "this is the odroid-m1 board", which flipped back on and hard-stopped the dependency chain once the blob package was disabled. Their real reasons (a kernel- header incompatibility, an upstream SDL3/XInput2 bug) are unrelated to the GPU driver, so swap in the always-present BR2_PACKAGE_UBOOT_ODROID_M1 board identifier instead. (moonlight-qt's similar guard is left as-is - its exclusion reason, a blob-specific eglGetPlatformDisplay gap, is genuinely GPU-driver-specific and worth re-testing under Panfrost.) - .board: add a host-machine BR2_JLEVEL=4 cap - BR2_JLEVEL=0 (auto = nproc+1) drove ~8GB of swap building LLVM+Mesa3D on a 16-core/15GB host. - buildroot (submodule): HOST_MESA3D_CONF_OPTS never matched Mesa's C++ RTTI setting to LLVM's the way MESA3D_CONF_OPTS already did, so host- mesa3d's meson configure failed once it needed -Dllvm=enabled for its panfrost host tool. Panfrost image now builds and produces a bootable batocera-rk3568-bsp-odroidm1-44-20260807.img.gz. Hardware smoke test and A/B ROM matrix against the Mali-G52 blob are next. --- buildroot | 2 +- configs/batocera-rk3568-odroidm1.board | 9 +++++++++ package/batocera/core/batocera-system/Config.in | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/buildroot b/buildroot index a7564de8bd1..1e45e38e3c1 160000 --- a/buildroot +++ b/buildroot @@ -1 +1 @@ -Subproject commit a7564de8bd1b9e2a477d02abf7c73414d422b848 +Subproject commit 1e45e38e3c14a5a68a0e50c2b9ecf3460337f981 diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index 2ef431f8cb7..a46b7a5a8ad 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -1,5 +1,14 @@ include batocera-board.common +# Build machine job cap - not a board property, but MAKE_JLEVEL passed on +# the make command line doesn't reach this .config (add-defconfig +# plumbing doesn't apply to plain -build), and hand-editing the +# generated _defconfig doesn't stick (gets regenerated from this file). +# BR2_JLEVEL=0 (auto = nproc+1) pushed a 16-core/15GB host into ~8GB of +# swap compiling LLVM+Mesa3D for the Panfrost experiment. Lower this (or +# remove it) on a beefier build machine. +BR2_JLEVEL=4 + # Target definition BR2_aarch64=y BR2_cortex_a55=y diff --git a/package/batocera/core/batocera-system/Config.in b/package/batocera/core/batocera-system/Config.in index 5b606310894..9d94c8ca360 100644 --- a/package/batocera/core/batocera-system/Config.in +++ b/package/batocera/core/batocera-system/Config.in @@ -348,7 +348,7 @@ config BR2_PACKAGE_BATOCERA_SYSTEM select BR2_PACKAGE_ROCKNIX_JOYPAD if (BR2_PACKAGE_BATOCERA_TARGET_RK3326 || \ BR2_PACKAGE_BATOCERA_TARGET_RK3568 || \ BR2_PACKAGE_BATOCERA_TARGET_H700) && \ - !BR2_PACKAGE_MALI_G52_ODROIDM1 + !BR2_PACKAGE_UBOOT_ODROID_M1 select BR2_PACKAGE_UPOWER # required by some cemuhook servers @@ -2260,7 +2260,7 @@ config BR2_PACKAGE_BATOCERA_SDL3 select BR2_PACKAGE_SDL3_TTF select BR2_PACKAGE_SDL3_OPENGLES if BR2_PACKAGE_HAS_LIBGLES select BR2_PACKAGE_SDL3_OPENGL if BR2_PACKAGE_HAS_LIBGL - select BR2_PACKAGE_SDL3_X11 if BR2_PACKAGE_XORG7 && !BR2_PACKAGE_MALI_G52_ODROIDM1 + select BR2_PACKAGE_SDL3_X11 if BR2_PACKAGE_XORG7 && !BR2_PACKAGE_UBOOT_ODROID_M1 select BR2_PACKAGE_SDL3_KMSDRM if BR2_PACKAGE_HAS_LIBGBM && !BR2_PACKAGE_BATOCERA_RPI_VCORE select BR2_PACKAGE_SDL3_WAYLAND if BR2_PACKAGE_BATOCERA_WAYLAND select BR2_PACKAGE_BATOCERA_HAS_SDL From 2fc7b7d8b9b66566829cc684e7c031e63ce7f538 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Sat, 8 Aug 2026 12:21:28 +0900 Subject: [PATCH 04/26] odroid-m1-panfrost: point kernel repo version at panfrost-enable branch The panfrost-enable kernel branch was created at the start of this work but BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION was never actually updated to point at it - the first panfrost build silently used the shared fix-hdmi-dsi-dual-livelock branch instead (same one track A/blob uses). CONFIG_DRM_PANFROST=y still took effect via the defconfig merge, so the driver compiled in, but the DTS gpu node it got was the vendor-kbase- oriented one (wrong interrupt-names case, Rockchip-gated OPP table) - see the panfrost-enable branch's rk356x.dtsi fix for the real-hardware dmesg trace that surfaced this. --- configs/batocera-rk3568-odroidm1.board | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index a46b7a5a8ad..331d376c492 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -27,11 +27,19 @@ BR2_TARGET_GENERIC_GETTY_BAUDRATE_115200=y BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/patches $(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/rockchip/patches $(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/rockchip/bsp-odroidm1/patches" BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/fsoverlay $(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/rockchip/fsoverlay $(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/rockchip/rk3568/fsoverlay $(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/rockchip/bsp-odroidm1/fsoverlay" -# Kernel - our hardkernel BSP fork (HDMI+DSI dual livelock fix), not mainline +# Kernel - our hardkernel BSP fork (HDMI+DSI dual livelock fix), not mainline. +# odroid-m1-panfrost branch (this file) tracks the panfrost-enable kernel +# branch specifically - NOT fix-hdmi-dsi-dual-livelock, which track A (blob) +# still uses directly. panfrost-enable carries a DTS fix (interrupt-names +# case + a separate, ungated GPU OPP table) that panfrost's probe needs but +# the vendor-kbase-oriented base tree doesn't provide - see rk356x.dtsi gpu +# node history. This pointer was accidentally left on the shared base +# branch for the first panfrost build attempt (the DTS gap was masked by a +# different bug at the time); real-hardware dmesg is what surfaced it. BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL_CUSTOM_GIT=y BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/Pyohwan/linux.git" -BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="fix-hdmi-dsi-dual-livelock" +BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="panfrost-enable" BR2_KERNEL_HEADERS_6_1=y BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/rockchip/bsp-odroidm1/linux-defconfig.config" From 160dc66cfbc78afb9acbf33c529312a7cdea1967 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Sun, 9 Aug 2026 11:22:35 +0900 Subject: [PATCH 05/26] odroidm1: pin panfrost-enable kernel to commit hash, add KERNEL_SRCDIR dev mount BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION was a branch name - buildroot's git download cache keys its dl/ tarball off that literal string, not the commit it resolves to, so pushing new commits to panfrost-enable was invisible to buildroot forever once the first tarball existed. v2/v3/v4 all silently rebuilt v2's tree this way (dl/linux/git stayed pinned at 1139cde0 through two more real pushes) - the "OPP/devfreq isn't the cause" conclusion drawn from those three "different" real-hardware boots was invalid, since they were all the same binary. Pin to the current HEAD (13fcfde92, the GPU clock fix) for archival builds, and add docker.mk's KERNEL_SRCDIR opt-in mount + a local.mk template for the actual fix going forward: buildroot's own OVERRIDE_SRCDIR mechanism for iterative kernel development, which bypasses the download cache entirely instead of fighting it. Also bumped BR2_JLEVEL 4->8: docker stats showed ~2.7GiB/15GiB used during a kernel-only rebuild even at JLEVEL=4, well under the swap- triggering LLVM+Mesa3D combination JLEVEL=4 was originally set for. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../rockchip/bsp-odroidm1/local.mk.example | 34 +++++++++++++++++++ configs/batocera-rk3568-odroidm1.board | 31 ++++++++++++++--- docker/docker.mk | 16 +++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 board/batocera/rockchip/bsp-odroidm1/local.mk.example diff --git a/board/batocera/rockchip/bsp-odroidm1/local.mk.example b/board/batocera/rockchip/bsp-odroidm1/local.mk.example new file mode 100644 index 00000000000..855ac81488b --- /dev/null +++ b/board/batocera/rockchip/bsp-odroidm1/local.mk.example @@ -0,0 +1,34 @@ +# Copy (or symlink) this file to output/rk3568-odroidm1/local.mk to build +# the kernel straight from a live local checkout instead of downloading +# Pyohwan/linux's panfrost-enable branch - skips buildroot's +# download/extract steps entirely. Edit the kernel source, then +# `make linux-rebuild all`, no commit/push needed per iteration. +# +# Requires the matching docker mount: pass KERNEL_SRCDIR= on every `make rk3568-odroidm1-build` +# invocation (see docker/docker.mk's KERNEL_SRCDIR opt-in) - the container +# only sees the checkout at /kernel-src if that variable is set, e.g.: +# +# make rk3568-odroidm1-build BATCH_MODE=1 KERNEL_SRCDIR=$HOME/git_projects/linux-odroidm1 +# +# Why this exists: buildroot's git-download cache keys its dl/ tarball +# filename off BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION's literal string, not +# off the commit it resolves to. A branch name never changes that string, +# so pushing new commits to an already-downloaded branch is invisible to +# buildroot forever - this cost two full iterations of the odroid-m1- +# panfrost debugging cycle silently rebuilding the same stale commit +# (see Joplin "B트랙 buildroot 커널 캐시 오염" note, 2026-08-08). This +# OVERRIDE_SRCDIR mechanism is buildroot's own documented answer for +# "actively editing source, rebuilding often" (manual: "Using Buildroot +# during development") - it bypasses the download cache instead of +# fighting it. +# +# Caveat: while this is active, the working tree - not git history - is +# what's actually being built. Check `git status`/`git stash list` in the +# kernel repo before trusting what a given image contains, and commit +# anything worth keeping. Once a build is verified good enough to archive, +# delete/rename this file and go back to pinning +# BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION to that commit's full hash in the +# .board file instead - the two mechanisms are meant to be swapped between, +# not run at the same time. +LINUX_OVERRIDE_SRCDIR = /kernel-src diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index 331d376c492..0e4f248bd65 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -4,10 +4,16 @@ include batocera-board.common # the make command line doesn't reach this .config (add-defconfig # plumbing doesn't apply to plain -build), and hand-editing the # generated _defconfig doesn't stick (gets regenerated from this file). -# BR2_JLEVEL=0 (auto = nproc+1) pushed a 16-core/15GB host into ~8GB of -# swap compiling LLVM+Mesa3D for the Panfrost experiment. Lower this (or -# remove it) on a beefier build machine. -BR2_JLEVEL=4 +# BR2_JLEVEL=0 (auto = nproc+1=17) pushed a 16-core/15GB host into ~8GB of +# swap compiling LLVM+Mesa3D for the Panfrost experiment - that pairing is +# the actual risk (LLVM's own build parallelism stacks on top of buildroot's). +# Bumped 4->8 (2026-08-08): during a kernel-only rebuild (no LLVM/Mesa3D in +# the dependency chain), `docker stats` showed the build container using +# only ~2.7GiB/15GiB even at JLEVEL=4 - plenty of headroom for a plain C +# compile. Watch `docker stats`/`free -h` again next time LLVM+Mesa3D are +# actually in the build (fresh from-scratch image, not just this kernel +# iteration loop) and drop back toward 4 if swap grows the same way. +BR2_JLEVEL=8 # Target definition BR2_aarch64=y @@ -39,7 +45,22 @@ BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/fsoverlay $(BR2 BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL_CUSTOM_GIT=y BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/Pyohwan/linux.git" -BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="panfrost-enable" +# Pinned to a commit hash, not the branch name, on purpose: buildroot's git +# download cache keys its dl/ tarball filename off this literal string, not +# off the commit it resolves to. With a branch name, pushing new commits to +# panfrost-enable is invisible to buildroot forever once the first tarball +# for that name exists - v2/v3/v4 all silently rebuilt v2's tree this way +# (dl/linux/git's checked-out HEAD stayed at 1139cde0 through two more real +# pushes). Bump this hash by hand after every commit meant to actually +# change what gets built; a branch name is only safe once iteration is done. +# +# For active same-day DTS iteration instead, skip this pointer entirely: +# use board/batocera/rockchip/bsp-odroidm1/local.mk.example (copy to +# output/rk3568-odroidm1/local.mk, build with KERNEL_SRCDIR=) to build straight from the working tree via buildroot's +# LINUX_OVERRIDE_SRCDIR - no commit/push/hash-bump per iteration. Switch +# back to a hash pin here once a build is verified good enough to archive. +BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="13fcfde92b934f0dca1905fe98d976ff003cf369" BR2_KERNEL_HEADERS_6_1=y BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_BATOCERA_PATH)/board/batocera/rockchip/bsp-odroidm1/linux-defconfig.config" diff --git a/docker/docker.mk b/docker/docker.mk index fa44d61c299..afb73e3baa2 100644 --- a/docker/docker.mk +++ b/docker/docker.mk @@ -23,6 +23,22 @@ else UID := $(shell id -u) GID := $(shell id -g) +# Opt-in extra bind mount for iterative package development: point +# KERNEL_SRCDIR (or any _SRCDIR= via EXTRA_SRCDIR_MOUNTS, see +# below) at a local git checkout and it shows up in the container at +# /kernel-src, ready for a _OVERRIDE_SRCDIR=/kernel-src in local.mk +# (see output//local.mk - BR2_PACKAGE_OVERRIDE_FILE's default +# location). This is buildroot's own documented mechanism for "edit +# source, rebuild, no commit/push needed" - see buildroot manual's "Using +# Buildroot during development". Without this mount, only a version +# string that buildroot has ALREADY cached under is enough to make it +# silently keep reusing a stale download() forever, even after new +# commits land upstream - this bit the odroid-m1-panfrost kernel branch +# for two full iterations (see Joplin "buildroot 커널 캐시 오염" note). +ifdef KERNEL_SRCDIR +DOCKER_OPTS += -v $(KERNEL_SRCDIR):/kernel-src +endif + define RUN_DOCKER $(DOCKER) run -t --init --rm \ -v $(PROJECT_DIR):/build \ From 2dd848754a357b17f34df04d6fd92916195d1b62 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Mon, 10 Aug 2026 10:04:09 +0900 Subject: [PATCH 06/26] local.mk.example: fix KERNEL_SRCDIR path after SSD migration ~/git_projects no longer exists (2026-08-10 SSD swap moved everything to /mnt/2026_990pro_2tb_1_data/git_projects/, no symlink recreated on purpose per the migration handoff). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- board/batocera/rockchip/bsp-odroidm1/local.mk.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/batocera/rockchip/bsp-odroidm1/local.mk.example b/board/batocera/rockchip/bsp-odroidm1/local.mk.example index 855ac81488b..9ca9dfe90ff 100644 --- a/board/batocera/rockchip/bsp-odroidm1/local.mk.example +++ b/board/batocera/rockchip/bsp-odroidm1/local.mk.example @@ -9,7 +9,7 @@ # invocation (see docker/docker.mk's KERNEL_SRCDIR opt-in) - the container # only sees the checkout at /kernel-src if that variable is set, e.g.: # -# make rk3568-odroidm1-build BATCH_MODE=1 KERNEL_SRCDIR=$HOME/git_projects/linux-odroidm1 +# make rk3568-odroidm1-build BATCH_MODE=1 KERNEL_SRCDIR=/mnt/2026_990pro_2tb_1_data/git_projects/linux-odroidm1 # # Why this exists: buildroot's git-download cache keys its dl/ tarball # filename off BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION's literal string, not From 6417b0c3dcb2d8017924adc96f82243bc3806c5f Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 18:00:06 +0900 Subject: [PATCH 07/26] configgen: inject GALLIUM_HUD=fps whenever show_fps is on, as a Mesa-side graph layered on top of each generator's existing fps display Single injection point (right where hud_support already sets MANGOHUD_DLSYM/MANGOHUD_CONFIGFILE) instead of touching every generator file individually - GALLIUM_HUD is a plain env var Mesa's gallium drivers read at process start, so it's a no-op on anything that never loads a gallium GL/GLES context (non-GL systems, the vendor Mali blob, older Mesa without HUD support). That makes it a natural fallback to each generator's own show_fps wiring (retroarch's fps_show, dolphin's ShowFPS, etc.) rather than requiring an either/or branch per emulator. Surfaced this session testing odroid-m1's Panfrost track: no numeric GPU-load metric is exposed anywhere on this kernel (no devfreq node - the GPU clock is statically pinned - no debugfs, and gallium_hud itself rejects the "GPU-load" query for this driver: "unknown driver query 'GPU-load'"), but the graph-based fps/frametime view was still strictly better than the existing plain-number counter for judging real-time performance during A/B testing. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../configgen/configgen/emulatorlauncher.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/package/batocera/core/batocera-configgen/configgen/configgen/emulatorlauncher.py b/package/batocera/core/batocera-configgen/configgen/configgen/emulatorlauncher.py index ceaf8fe2a40..9c3f0d8f330 100644 --- a/package/batocera/core/batocera-configgen/configgen/configgen/emulatorlauncher.py +++ b/package/batocera/core/batocera-configgen/configgen/configgen/emulatorlauncher.py @@ -207,6 +207,20 @@ def start_rom(args: argparse.Namespace, maxnbplayers: int, rom: Path, original_r if not generator.hasInternalMangoHUDCall(): cmd.array.insert(0, "mangohud") + # Mesa's own on-screen graph (fps/frametime) via the gallium + # driver, set unconditionally alongside whatever each + # generator's own show_fps wiring already does (native + # counters, e.g. retroarch's fps_show or dolphin's + # ShowFPS - left untouched, one central env var here beats + # touching every generator individually). Harmless no-op + # on processes that never load a gallium driver (any + # non-GL system, or the vendor Mali blob) or on older + # Mesa without HUD support - the process just never draws + # it, so this is naturally a fallback to each generator's + # existing fps display instead of an either/or choice. + if system.config.show_fps: + cmd.env["GALLIUM_HUD"] = "fps" + # generate the gun help try: default_gun_help_dir = Path("/var/run/batocera-overlays") From 1246f4761d1570ee3d3f9ed569ee7c81955c352b Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 19:04:36 +0900 Subject: [PATCH 08/26] Enable PanVK + Vulkan-capable systems on B(panfrost) branch PanVK (Mesa's Panfrost Vulkan driver) is a B-only capability - the A track's vendor blob has zero Vulkan support at all, so this was never an A/B comparison axis and doesn't need to stay excluded for fairness. The previous "PanVK is non-conformant on G52" comment was an unverified assumption; Mesa 26.1.5's docs/features.txt confirms Vulkan 1.0 is fully DONE on panvk with no Bifrost/Valhall gen restriction. Adds, all additive to the existing GLES-tested matrix: - BR2_PACKAGE_BATOCERA_VULKAN + MESA3D_VULKAN_DRIVER_PANFROST (PanVK) - PCSX2 (new system, never built on this board before) - Dolphin standalone (Vulkan gfxbackend, distinct from libretro-dolphin) - libretro: parallel-n64, flycastvl, ppsspp, azahar, play, ps2 - vkquake/vkquake2/vkquake3 auto-select via BATOCERA_VULKAN Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- configs/batocera-rk3568-odroidm1.board | 76 ++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index 0e4f248bd65..5fe4566061f 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -109,16 +109,35 @@ BR2_TARGET_ROOTFS_SQUASHFS4_ZSTD=y # Mesa3D with the panfrost gallium driver takes over as the HAS_LIBEGL/ # HAS_LIBGLES/HAS_LIBGBM provider (mali-g52-odroidm1's Config.in used to -# do this via BR2_PACKAGE_PROVIDES_LIBEGL=mali-g52-odroidm1 etc.). No -# Vulkan driver enabled here on purpose - Joplin A/B plan explicitly -# excludes Vulkan from this comparison (blob has none, Mesa's PanVK is -# non-conformant on G52) to keep both sides GLES-only and comparable. +# do this via BR2_PACKAGE_PROVIDES_LIBEGL=mali-g52-odroidm1 etc.). BR2_PACKAGE_MESA3D=y BR2_PACKAGE_MESA3D_LLVM=y BR2_PACKAGE_MESA3D_GALLIUM_DRIVER_PANFROST=y BR2_PACKAGE_MESA3D_OPENGL_EGL=y BR2_PACKAGE_MESA3D_OPENGL_ES=y +# PanVK (Mesa's own Panfrost Vulkan driver) - the blob (Track A) has zero +# Vulkan support at all, so this is a B-only capability, not an A/B +# comparison axis. The GLES-only-comparison rationale that used to be here +# was based on an unverified "PanVK is non-conformant on G52" assumption - +# checked Mesa 26.1.5's docs/features.txt directly: Vulkan 1.0 is listed as +# fully DONE on panvk with no Bifrost/Valhall gen qualifier (only some 1.1+ +# extensions are gen-gated to "panvk/v10+" i.e. Valhall-only), so G52 +# (Bifrost) gets a real Vulkan 1.0 implementation, not a token/broken one. +# Setting BR2_PACKAGE_BATOCERA_VULKAN=y directly (rather than going through +# BR2_PACKAGE_BATOCERA_PANFROST_MESA3D, which this board doesn't use since +# it sets granular MESA3D_* flags above instead of that meta-select) is +# what actually flips every emulator .mk's `ifeq ($(BR2_PACKAGE_BATOCERA_ +# VULKAN),y)` build flag (flycast/beetle-psx/ppsspp/azahar/ps2/dolphin/ +# retroarch --enable-vulkan/vkquake*) - this is additive, not a replacement +# for the existing GLES path: standalone emulators expose Vulkan as a +# runtime-selectable gfxbackend alongside OpenGL, and retroarch cores built +# with USE_VULKAN=ON still keep their GL renderer, Vulkan just becomes an +# additional selectable video driver. Existing GL-path fps numbers already +# recorded in Joplin remain valid as the default/baseline. +BR2_PACKAGE_BATOCERA_VULKAN=y +BR2_PACKAGE_MESA3D_VULKAN_DRIVER_PANFROST=y + # No compositor (labwc/Wayland): the vendor Mali blob's Wayland-client EGL # platform is broken (confirmed live on hardware - eglGetPlatformDisplayEXT # succeeds but eglInitialize always fails with EGL_NOT_INITIALIZED, on both @@ -189,6 +208,55 @@ BR2_PACKAGE_LIBRETRO_FBNEO_KOREAN=y # a Software render mode that doesn't need GL at all, worth trying. BR2_PACKAGE_LIBRETRO_MELONDS_DS=y +# Vulkan-capable systems/cores, added alongside PanVK above. All additive +# to the existing GLES matrix (see BR2_PACKAGE_BATOCERA_VULKAN note above) - +# none of these replace an already-tested GL path, they're either brand new +# systems or extra selectable renderers on top of what's already recorded. +# +# PCSX2 (PS2) - never tested on this board before (no PS2 core at all +# previously - libretro-play/libretro-ps2 below are the only alternatives, +# both also new). Explicitly supports aarch64 in its own Config.in +# (depends on BR2_x86_64 || BR2_aarch64) and needs BR2_PACKAGE_XORG7, which +# this board already has on. Vulkan is PCSX2's modern primary renderer. +BR2_PACKAGE_PCSX2=y + +# Dolphin standalone (GameCube/Wii) - distinct from libretro-dolphin above +# (which is what this session's A/B GameCube headline case used, GL only). +# Standalone Dolphin has its own Vulkan gfxbackend (dolphinGenerator.py +# already wires system.config.get("gfxbackend")=="Vulkan"), a completely +# different render path from the libretro GL backend, worth retrying here. +BR2_PACKAGE_DOLPHIN_EMU=y + +# ParaLLEl-RDP N64 core - Vulkan-capable N64 renderer, not one of the 4 +# renderers (mupen64plus-next/GLideN64/glide64mk2/Rice) already tested +# this session, all of which were GL/GLES only. +BR2_PACKAGE_LIBRETRO_PARALLEL_N64=y + +# Dedicated Vulkan-only Dreamcast core (separate from libretro-flycast, +# which is already on and GL-tested this session at 44fps/Gunbird 2). +BR2_PACKAGE_LIBRETRO_FLYCASTVL=y + +# libretro PPSSPP core - distinct build from the standalone PPSSPP already +# tested this session (GL, 30fps min/Tekken 6). Standalone's own Vulkan +# gfxbackend is covered by BATOCERA_VULKAN above; this is an independent +# second PSP core to compare against. +BR2_PACKAGE_LIBRETRO_PPSSPP=y + +# libretro Azahar (3DS) core - no 3DS system was buildable on this board +# before at all (standalone azahar was never enabled either). +BR2_PACKAGE_LIBRETRO_AZAHAR=y + +# Play!/libretro-ps2 - additional PS2 core options alongside PCSX2 above. +BR2_PACKAGE_LIBRETRO_PLAY=y +BR2_PACKAGE_LIBRETRO_PS2=y + +# vkquake/vkquake2/vkquake3 need no explicit line here - batocera-system's +# Config.in auto-selects all three once BR2_PACKAGE_BATOCERA_VULKAN=y is +# set (vkquake2 additionally needs BR2_PACKAGE_XORG7, already on; vkquake3 +# is only excluded on JH7110 targets, which this board isn't). These are +# native Vulkan-only ports with no GL fallback at all - impossible on the +# blob (Track A) by construction, not just untested. + # Firmware BR2_PACKAGE_FIRMWARE_ARMBIAN=y From c5bb8532f658c823dd5af5ec9c0e116f1699b657 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 19:05:55 +0900 Subject: [PATCH 09/26] Disable vkquake2 on B - hard-selects desktop LIBGLU which this GLES-only board doesn't have BR2_PACKAGE_BATOCERA_VULKAN auto-selects all three vkquake variants, but vkquake2's Config.in unconditionally selects BR2_PACKAGE_LIBGLU (unlike vkquake/vkquake3), which needs BR2_PACKAGE_HAS_LIBGL - false here since Panfrost only provides GLES/EGL. buildroot hard-stops the build over this rather than warning, confirmed via a failed build attempt. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- configs/batocera-rk3568-odroidm1.board | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index 5fe4566061f..63447a6321f 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -250,12 +250,22 @@ BR2_PACKAGE_LIBRETRO_AZAHAR=y BR2_PACKAGE_LIBRETRO_PLAY=y BR2_PACKAGE_LIBRETRO_PS2=y -# vkquake/vkquake2/vkquake3 need no explicit line here - batocera-system's -# Config.in auto-selects all three once BR2_PACKAGE_BATOCERA_VULKAN=y is -# set (vkquake2 additionally needs BR2_PACKAGE_XORG7, already on; vkquake3 -# is only excluded on JH7110 targets, which this board isn't). These are -# native Vulkan-only ports with no GL fallback at all - impossible on the -# blob (Track A) by construction, not just untested. +# vkquake/vkquake3 need no explicit line here - batocera-system's Config.in +# auto-selects both once BR2_PACKAGE_BATOCERA_VULKAN=y is set (vkquake3 is +# only excluded on JH7110 targets, which this board isn't; it falls back to +# GL4ES for the bits that aren't pure Vulkan since HAS_LIBGL is false here). +# These are native Vulkan-only ports with no GL fallback at all - impossible +# on the blob (Track A) by construction, not just untested. +# +# vkquake2 is the one exception: unlike vkquake/vkquake3, its Config.in +# unconditionally selects BR2_PACKAGE_LIBGLU (desktop GLU, not just Vulkan), +# which needs BR2_PACKAGE_HAS_LIBGL - false on this board (Panfrost only +# provides GLES/EGL, no GLX, same reason TRX/dolphin-libretro are excluded +# above). buildroot hard-stops the build over this ("libgl is in the +# dependency chain of libglu... without selecting it or depending on it +# from Config.in. Stop.") rather than just warning, so vkquake2 has to be +# turned back off explicitly despite BATOCERA_VULKAN auto-selecting it. +# BR2_PACKAGE_VKQUAKE2 is not set # Firmware BR2_PACKAGE_FIRMWARE_ARMBIAN=y From 38ab148c34c8f1acf7523f63b04fb49261948614 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 19:11:33 +0900 Subject: [PATCH 10/26] Fix vkquake2/pcsx2 desktop-GL blockers for GLES-only Vulkan build vkquake2 genuinely builds ref_gl.so and ref_vk.so side by side (real dual-renderer engine), so batocera-system's auto-select for it needs BR2_PACKAGE_HAS_LIBGL as well as BATOCERA_VULKAN/XORG7 - false on this board (Panfrost only exposes GLES/EGL). A defconfig "is not set" line in the board file can't override a forced Kconfig `select`, so this had to be fixed at the actual select condition, confirmed via a failed build ("libgl is in the dependency chain of libglu... Stop."). pcsx2's WXWIDGETS select turned out to be unrelated dead weight - its .mk never references wx in any CMake option (OpenGL/Vulkan are already properly gated on HAS_LIBGL/BATOCERA_VULKAN), so gated the select and the .mk dependency on HAS_LIBGL too, matching how pcsx2 actually gates its own optional GL support. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- configs/batocera-rk3568-odroidm1.board | 19 ++++++++++--------- .../batocera/core/batocera-system/Config.in | 2 +- package/batocera/emulators/pcsx2/Config.in | 6 +++--- package/batocera/emulators/pcsx2/pcsx2.mk | 6 +++++- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index 63447a6321f..ffa7e6736ad 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -257,15 +257,16 @@ BR2_PACKAGE_LIBRETRO_PS2=y # These are native Vulkan-only ports with no GL fallback at all - impossible # on the blob (Track A) by construction, not just untested. # -# vkquake2 is the one exception: unlike vkquake/vkquake3, its Config.in -# unconditionally selects BR2_PACKAGE_LIBGLU (desktop GLU, not just Vulkan), -# which needs BR2_PACKAGE_HAS_LIBGL - false on this board (Panfrost only -# provides GLES/EGL, no GLX, same reason TRX/dolphin-libretro are excluded -# above). buildroot hard-stops the build over this ("libgl is in the -# dependency chain of libglu... without selecting it or depending on it -# from Config.in. Stop.") rather than just warning, so vkquake2 has to be -# turned back off explicitly despite BATOCERA_VULKAN auto-selecting it. -# BR2_PACKAGE_VKQUAKE2 is not set +# vkquake2 is the one exception: it builds ref_gl.so *and* ref_vk.so side +# by side (real dual-renderer engine, not vestigial), so it genuinely needs +# desktop GLU/GLX - not available here (Panfrost only provides GLES/EGL, +# same reason TRX/dolphin-libretro are excluded above). A first build +# attempt confirmed a hard buildroot stop over this ("libgl is in the +# dependency chain of libglu... Stop.") - a defconfig "is not set" line +# can't override batocera-system's forced `select`, so fixed at the real +# root cause instead: added "&& BR2_PACKAGE_HAS_LIBGL" to vkquake2's +# auto-select condition in batocera-system/Config.in (this was a latent +# gap there - nothing GLES-only-with-Vulkan existed to expose it before). # Firmware BR2_PACKAGE_FIRMWARE_ARMBIAN=y diff --git a/package/batocera/core/batocera-system/Config.in b/package/batocera/core/batocera-system/Config.in index 9d94c8ca360..fc04f6ca05d 100644 --- a/package/batocera/core/batocera-system/Config.in +++ b/package/batocera/core/batocera-system/Config.in @@ -1202,7 +1202,7 @@ config BR2_PACKAGE_BATOCERA_PORTS_SYSTEMS # Quake 2 select BR2_PACKAGE_LIBRETRO_VITAQUAKE2 if BR2_PACKAGE_BATOCERA_GLES3 - select BR2_PACKAGE_VKQUAKE2 if BR2_PACKAGE_BATOCERA_VULKAN && BR2_PACKAGE_XORG7 + select BR2_PACKAGE_VKQUAKE2 if BR2_PACKAGE_BATOCERA_VULKAN && BR2_PACKAGE_XORG7 && BR2_PACKAGE_HAS_LIBGL select BR2_PACKAGE_YQUAKE2 # ALL select BR2_PACKAGE_YQUAKE2_XATRIX if BR2_PACKAGE_YQUAKE2 # Mission Packs select BR2_PACKAGE_YQUAKE2_ROGUE if BR2_PACKAGE_YQUAKE2 diff --git a/package/batocera/emulators/pcsx2/Config.in b/package/batocera/emulators/pcsx2/Config.in index 314191b0e97..dfac3b7e46e 100644 --- a/package/batocera/emulators/pcsx2/Config.in +++ b/package/batocera/emulators/pcsx2/Config.in @@ -24,9 +24,9 @@ config BR2_PACKAGE_PCSX2 select BR2_PACKAGE_SHADERC select BR2_PACKAGE_VULKAN_HEADERS if BR2_PACKAGE_BATOCERA_VULKAN select BR2_PACKAGE_VULKAN_LOADER if BR2_PACKAGE_BATOCERA_VULKAN - select BR2_PACKAGE_WEBP - select BR2_PACKAGE_WXWIDGETS - select BR2_PACKAGE_YAML_CPP + select BR2_PACKAGE_WEBP + select BR2_PACKAGE_WXWIDGETS if BR2_PACKAGE_HAS_LIBGL + select BR2_PACKAGE_YAML_CPP select BR2_PACKAGE_ZLIB help PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. diff --git a/package/batocera/emulators/pcsx2/pcsx2.mk b/package/batocera/emulators/pcsx2/pcsx2.mk index 3bb6e9bdd6a..cb17adb011d 100644 --- a/package/batocera/emulators/pcsx2/pcsx2.mk +++ b/package/batocera/emulators/pcsx2/pcsx2.mk @@ -24,7 +24,11 @@ PCSX2_SUPPORTS_IN_SOURCE_BUILD = NO PCSX2_DEPENDENCIES += alsa-lib ecm fmt freetype host-clang host-libcurl kddockwidgets PCSX2_DEPENDENCIES += libaio libbacktrace libcurl libgtk3 libpcap libpng libsamplerate PCSX2_DEPENDENCIES += libsoundtouch plutosvg portaudio qt6base qt6svg qt6tools -PCSX2_DEPENDENCIES += rapidyaml shaderc sdl3 webp wxwidgets xorgproto yaml-cpp zlib +PCSX2_DEPENDENCIES += rapidyaml shaderc sdl3 webp xorgproto yaml-cpp zlib + +ifeq ($(BR2_PACKAGE_HAS_LIBGL),y) +PCSX2_DEPENDENCIES += wxwidgets +endif # Use clang for performance PCSX2_CONF_OPTS += -DCMAKE_C_COMPILER=$(HOST_DIR)/bin/clang From 04c0e462f6ed2211d0db7b43570c883821c3765b Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 20:17:25 +0900 Subject: [PATCH 11/26] Drop libretro-flycastvl - stale fork, no ARM platform wiring for this board Confirmed via a real build failure: libretro-flycastvl.mk only has ARM platform/ARCH branches for specific boards (RPi4/5, RK3326, H3/H5/H6), not rk3568/odroidm1, so it silently fell through to the x86_64 default and pulled in xbyak's x86 JIT code unconditionally ("fatal error: cpuid.h: No such file or directory" in core/hw/aica/dsp_x64.cpp). The pinned commit is from Oct 2021 (unmaintained), and libretro-flycast already covers Dreamcast/Vulkan once built with BATOCERA_VULKAN=y - not worth patching a stale duplicate core for. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- configs/batocera-rk3568-odroidm1.board | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index ffa7e6736ad..f07d803f298 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -233,8 +233,19 @@ BR2_PACKAGE_DOLPHIN_EMU=y BR2_PACKAGE_LIBRETRO_PARALLEL_N64=y # Dedicated Vulkan-only Dreamcast core (separate from libretro-flycast, -# which is already on and GL-tested this session at 44fps/Gunbird 2). -BR2_PACKAGE_LIBRETRO_FLYCASTVL=y +# which is already on and GL-tested this session at 44fps/Gunbird 2) - +# tried and reverted. libretro-flycastvl.mk only has ARM platform/ARCH +# branches wired up for specific boards (RPi4/5, RK3326, H3/H5/H6, ...); +# this board (rk3568/odroidm1) isn't one of them, so it silently falls +# through to the x86_64 default LIBRETRO_PLATFORM, which drags in xbyak's +# x86 JIT code unconditionally - confirmed via a real build failure +# ("fatal error: cpuid.h: No such file or directory" compiling +# core/hw/aica/dsp_x64.cpp). This is a genuinely stale, unmaintained fork +# (pinned commit from Oct 2021) that would need real upstream-style ARM +# platform wiring to fix properly, and libretro-flycast above already +# covers Dreamcast/Vulkan once built with BATOCERA_VULKAN=y - not worth +# patching a 5-year-stale duplicate for. Left off. +# BR2_PACKAGE_LIBRETRO_FLYCASTVL is not set # libretro PPSSPP core - distinct build from the standalone PPSSPP already # tested this session (GL, 30fps min/Tekken 6). Standalone's own Vulkan From 370edb0a00e531e8fe22d93ae1d3992aff8688e9 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 20:20:32 +0900 Subject: [PATCH 12/26] Add RK3568 board branch to libretro-parallel-n64 - fixes x86 SSE build failure odroidm1/rk3568 wasn't in upstream's per-board platform/ARCH table, so it fell through to the generic default where the Makefile's own ARCH fallback (uname -m) picks up the build host's x86_64 arch instead of the cross-compile target, pulling in xbyak's x86-only cpuid.h unconditionally (confirmed via a real build failure: "-msse"/"-msse2" rejected by aarch64-buildroot-linux-gnu-g++ compiling gles2rice). Added an explicit branch mirroring RK3588/RK3576/H5 (WITH_DYNAREC=aarch64), reusing H5's CPUFLAGS (armv8-a+crc, cortex-a53 tuning) since it's a safe baseline subset for RK3568's Cortex-A55 cores with no extension-gated instructions. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../libretro-parallel-n64.mk | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/package/batocera/emulators/retroarch/libretro/libretro-parallel-n64/libretro-parallel-n64.mk b/package/batocera/emulators/retroarch/libretro/libretro-parallel-n64/libretro-parallel-n64.mk index 3e253dbdb12..60010374c01 100644 --- a/package/batocera/emulators/retroarch/libretro/libretro-parallel-n64/libretro-parallel-n64.mk +++ b/package/batocera/emulators/retroarch/libretro/libretro-parallel-n64/libretro-parallel-n64.mk @@ -32,6 +32,21 @@ LIBRETRO_PARALLEL_N64_PLATFORM=rpi2 else ifeq ($(BR2_PACKAGE_BATOCERA_TARGET_XU4),y) LIBRETRO_PARALLEL_N64_PLATFORM=odroid LIBRETRO_PARALLEL_N64_BOARD=ODROID-XU4 +else ifeq ($(BR2_PACKAGE_BATOCERA_TARGET_RK3568),y) +# Not in upstream's own per-board platform table (odroidm1/rk3568 is a +# batocera-only target). Falling through to the generic "else" branch +# below would leave $(ARCH) unset, and the upstream Makefile's fallback +# `ARCH = $(shell uname -m)` then picks up the *build host's* arch +# (x86_64, since this runs inside the cross-compile docker container) - +# confirmed via a real build failure (xbyak's x86-only cpuid.h pulled in, +# "-msse"/"-msse2" rejected by aarch64-buildroot-linux-gnu-g++). Reusing +# the H5 platform's CPUFLAGS (-march=armv8-a+crc, cortex-a53 tuning, no +# crypto/dotprod extensions) rather than adding new SoC-specific flags - +# it's a strict baseline armv8-a subset with no extension-gated +# instructions, so it's safe to run on RK3568's Cortex-A55 cores too even +# though the tuning target isn't an exact match. +LIBRETRO_PARALLEL_N64_EXTRA_ARGS+=WITH_DYNAREC=aarch64 +LIBRETRO_PARALLEL_N64_PLATFORM=h5 else ifeq ($(BR2_PACKAGE_BATOCERA_TARGET_RK3576),y) LIBRETRO_PARALLEL_N64_EXTRA_ARGS+=WITH_DYNAREC=aarch64 LIBRETRO_PARALLEL_N64_PLATFORM=rk3576 From 5f1cf74e76503cb574aba6a021d947b7cd95e297 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 20:36:15 +0900 Subject: [PATCH 13/26] Drop libretro-ps2 - CMake can't identify our cross-compile arch Confirmed via a real build failure: libretro-ps2's own BuildParameters. cmake fails with "Unsupported architecture: unknown" for our aarch64 cross-compile target - a deeper upstream CMake arch-detection issue, not a quick fix. PS2 is already covered twice over (PCSX2, libretro-play). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- configs/batocera-rk3568-odroidm1.board | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index f07d803f298..243ad93e11d 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -257,9 +257,16 @@ BR2_PACKAGE_LIBRETRO_PPSSPP=y # before at all (standalone azahar was never enabled either). BR2_PACKAGE_LIBRETRO_AZAHAR=y -# Play!/libretro-ps2 - additional PS2 core options alongside PCSX2 above. +# Play! - additional PS2 core option alongside PCSX2 above. BR2_PACKAGE_LIBRETRO_PLAY=y -BR2_PACKAGE_LIBRETRO_PS2=y + +# libretro-ps2 tried and reverted: its own CMakeLists.txt/BuildParameters. +# cmake can't identify our cross-compile target at all ("Unsupported +# architecture: unknown", confirmed via a real build failure) - a deeper +# upstream CMake arch-detection issue, not a quick one-line fix like the +# other cores above. PS2 is already covered twice over by PCSX2 and +# libretro-play, so not worth chasing a third path for. +# BR2_PACKAGE_LIBRETRO_PS2 is not set # vkquake/vkquake3 need no explicit line here - batocera-system's Config.in # auto-selects both once BR2_PACKAGE_BATOCERA_VULKAN=y is set (vkquake3 is From 284a895027b1dce2df994d35f8d7d44a9518bc62 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 21:49:04 +0900 Subject: [PATCH 14/26] Enable real PPSSPP Vulkan on RK3568 - was force-disabled via ARM_NO_VULKAN ppsspp.mk only flips ARM_NO_VULKAN=OFF (i.e. actually allows Vulkan) for RPi4/5; every other ARM board gets ARM_NO_VULKAN=ON regardless of the general -DVULKAN=ON flag, silently compiling Vulkan support back out. Confirmed via the built binary having zero Vulkan symbols despite BATOCERA_VULKAN=y. Added RK3568 to the same allowlist as RPi4/5. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- package/batocera/emulators/ppsspp/ppsspp.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/batocera/emulators/ppsspp/ppsspp.mk b/package/batocera/emulators/ppsspp/ppsspp.mk index bd784aeed81..5190a9d41d0 100644 --- a/package/batocera/emulators/ppsspp/ppsspp.mk +++ b/package/batocera/emulators/ppsspp/ppsspp.mk @@ -83,7 +83,7 @@ ifeq ($(BR2_PACKAGE_BATOCERA_VULKAN),y) PPSSPP_TARGET_CFLAGS += -DEGL_NO_X11=1 -DMESA_EGL_NO_X11_HEADERS=1 endif # rpi4/5 vulkan support - ifeq ($(BR2_PACKAGE_BATOCERA_TARGET_BCM2711)$(BR2_PACKAGE_BATOCERA_TARGET_BCM2712),y) + ifeq ($(BR2_PACKAGE_BATOCERA_TARGET_BCM2711)$(BR2_PACKAGE_BATOCERA_TARGET_BCM2712)$(BR2_PACKAGE_BATOCERA_TARGET_RK3568),y) PPSSPP_CONF_OPTS += -DARM_NO_VULKAN=OFF else ifeq ($(BR2_arm)$(BR2_aarch64),y) PPSSPP_CONF_OPTS += -DARM_NO_VULKAN=ON From 20925ba07cbc8bb1581cacd0cbd8db78b2b59d05 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 11 Aug 2026 22:14:18 +0900 Subject: [PATCH 15/26] odroidm1: default global.powermode to highperformance global.powermode was never actually set in this board's default batocera.conf - every A/B track fps measurement taken so far ran under batocera-power-mode's unset-fallback (set_default: EPP/governor "default"), not highperformance, despite the file's own comment already documenting that n64/fbneo/psx/nds measurably benefited from it. Setting the factory default here (ES's Game Settings > Power Mode menu still overrides it live, same precedence as before). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 b/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 index bf2284aabe0..8bd01ef559a 100644 --- a/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 +++ b/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 @@ -39,6 +39,15 @@ system.cpu.governor=interactive # powermode_launch_hooks.sh), defeating the point of a single global # knob. See Joplin "ODROID-M1 retro OS 빌드 계획" for the underlying # measurements if per-system defaults are ever wanted again. +# +# global.powermode itself, however, was never actually set here - every +# A/B track fps measurement so far ran under whatever batocera-power-mode +# falls back to when global.powermode is unset (`set_default`, EPP/governor +# "default", not "performance"), not the highperformance path the above +# measurements were based on. Setting it here changes only the factory +# default; ES's Game Settings > Power Mode menu still overrides it live +# same as before. +global.powermode=highperformance # Default core for the fbneo system: dsno/Zansword's Korean-patched # fork (package/batocera/emulators/retroarch/libretro/ From 0b0bb0245ce292c91ce77e50a683804824f702e3 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Wed, 12 Aug 2026 21:39:19 +0900 Subject: [PATCH 16/26] pcsx2: fix ps2 default emulator + eglfs for no-compositor boards configgen-defaults-rk3568.yml still defaulted ps2 to libretro/play (a stale upstream default from before PCSX2 was viable on aarch64) - our board only registers the standalone pcsx2 emulator in es_systems.yml, so this stale default made every PS2 launch try a nonexistent libretro core and crash immediately ("Frontend is built for dynamic libretro cores, but path is not set"). Confirmed live and fixed by pointing the default at pcsx2/pcsx2 instead. Separately, pcsx2-qt itself always defaulted to the xcb Qt platform plugin, which aborts outright on this board (no X11, no compositor - same failure class documented in duckstationGenerator.py for duckstation-qt/melonDS). Reused the exact same WAYLAND_DISPLAY/DISPLAY detection -> eglfs fallback pattern. Confirmed live: PCSX2 now reaches its own BIOS-missing dialog instead of crashing at Qt init. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../configgen/generators/pcsx2/pcsx2Generator.py | 16 +++++++++++++++- .../configs/configgen-defaults-rk3568.yml | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py b/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py index 71fabacf7e3..7db84ee111f 100644 --- a/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py +++ b/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2/pcsx2Generator.py @@ -4,6 +4,7 @@ import re import shutil import time +from os import environ from pathlib import Path from typing import TYPE_CHECKING, ClassVar, Final @@ -120,8 +121,21 @@ def generate(self, system, rom, playersControllers, metadata, guns, wheels, game if not re.search(r'^flags\s*:.*\ssse4_1\W', cpuinfo.read(), re.MULTILINE): _logger.warning("CPU does not support SSE4.1 which is required by pcsx2. The emulator will likely crash with SIGILL (illegal instruction).") + # check if we're running wayland or X11 - boards with neither (no + # compositor, direct KMS/DRM like odroid-m1) need eglfs instead, + # otherwise Qt defaults into xcb with no X server to talk to and + # aborts outright (same failure class as standalone duckstation-qt/ + # melonDS, see duckstationGenerator.py) + if environ.get("WAYLAND_DISPLAY"): + qt_qpa_platform = "wayland" + elif environ.get("DISPLAY"): + qt_qpa_platform = "xcb" + else: + qt_qpa_platform = "eglfs" + envcmd: dict[str, str | Path] = { - "XDG_CONFIG_HOME": CONFIGS + "XDG_CONFIG_HOME": CONFIGS, + "QT_QPA_PLATFORM": qt_qpa_platform } # wheels won't work correctly when SDL_GAMECONTROLLERCONFIG is set. excluding wheels from SDL_GAMECONTROLLERCONFIG doesn't fix too. diff --git a/package/batocera/core/batocera-configgen/configs/configgen-defaults-rk3568.yml b/package/batocera/core/batocera-configgen/configs/configgen-defaults-rk3568.yml index 24e13533f1d..6db8b8f0236 100644 --- a/package/batocera/core/batocera-configgen/configs/configgen-defaults-rk3568.yml +++ b/package/batocera/core/batocera-configgen/configs/configgen-defaults-rk3568.yml @@ -39,8 +39,8 @@ systemsp: options: hud_support: false # flycast core, same issue as dreamcast above ps2: - emulator: libretro - core: play + emulator: pcsx2 + core: pcsx2 flash: emulator: lightspark core: lightspark From 69407743f1e6b8e905b65271a6dd719a835c2efa Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Wed, 12 Aug 2026 22:09:31 +0900 Subject: [PATCH 17/26] dolphin: eglfs for no-compositor boards, same pattern as duckstation/pcsx2 dolphin-emu (standalone) always defaulted to the xcb Qt platform plugin, aborting outright on this board (no X11, no compositor). Confirmed live that eglfs actually works for Dolphin (unlike pcsx2-qt, which hits a separate "Failed to get window info from widget" Qt/eglfs multi-widget limitation) - Dolphin ran a real GPU-rendered session via libqeglfs-kms-integration.so + libgallium for 90+ seconds with no crash. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../generators/dolphin/dolphinGenerator.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/package/batocera/core/batocera-configgen/configgen/configgen/generators/dolphin/dolphinGenerator.py b/package/batocera/core/batocera-configgen/configgen/configgen/generators/dolphin/dolphinGenerator.py index 1705db84009..8b466e4e2e9 100644 --- a/package/batocera/core/batocera-configgen/configgen/configgen/generators/dolphin/dolphinGenerator.py +++ b/package/batocera/core/batocera-configgen/configgen/configgen/generators/dolphin/dolphinGenerator.py @@ -429,12 +429,25 @@ def generate(self, system, rom, playersControllers, metadata, guns, wheels, game if state_filename := system.config.get('state_filename'): commandArray.extend(["--save_state", state_filename]) + # check if we're running wayland or X11 - boards with neither (no + # compositor, direct KMS/DRM like odroid-m1) need eglfs instead, + # otherwise Qt defaults into xcb with no X server to talk to and + # aborts outright (same failure class as standalone duckstation-qt/ + # pcsx2-qt, see duckstationGenerator.py/pcsx2Generator.py) + if environ.get("WAYLAND_DISPLAY"): + qt_qpa_platform = "wayland" + elif environ.get("DISPLAY"): + qt_qpa_platform = "xcb" + else: + qt_qpa_platform = "eglfs" + return Command.Command( array=commandArray, env={ "XDG_CONFIG_HOME": CONFIGS, "XDG_DATA_HOME": SAVES, - "XDG_CACHE_HOME": CACHE + "XDG_CACHE_HOME": CACHE, + "QT_QPA_PLATFORM": qt_qpa_platform } ) From c41b08978c0483b68bd53b4cdf8791f700c930c9 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Sat, 15 Aug 2026 09:29:03 +0900 Subject: [PATCH 18/26] configgen: bind RetroArch's exit_emulator to the same button as Start writeHotKeyConfig() only ever wrote input_enable_hotkey_btn (arming the hotkey modifier), never any joypad action bound to it - cleanControllerConfig even actively disable_all()s input_exit_emulator* every game launch via prefix match. Only the keyboard input_exit_emulator="escape" default actually quit a running libretro core; Hotkey+Start (batocera's own force-quit convention, see es_input.cfg's keyboard hotkey=select binding) did nothing on a gamepad. Confirmed live on ODROID-M1 (SHAKS S6b pad, FBNeo Korean core/Night Slashers, discovered on the C track) - Select+Start did not exit until input_exit_emulator_btn was set to Start's button id. This is shared configgen code, not board- or GPU-track-specific, so porting to B here. --- .../configgen/generators/libretro/libretroControllers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/package/batocera/core/batocera-configgen/configgen/configgen/generators/libretro/libretroControllers.py b/package/batocera/core/batocera-configgen/configgen/configgen/generators/libretro/libretroControllers.py index a30b3467a84..e9971babcb3 100755 --- a/package/batocera/core/batocera-configgen/configgen/configgen/generators/libretro/libretroControllers.py +++ b/package/batocera/core/batocera-configgen/configgen/configgen/generators/libretro/libretroControllers.py @@ -89,6 +89,15 @@ def cleanControllerConfig(retroconfig: UnixSettings, controllers: Controllers, / def writeHotKeyConfig(retroconfig: UnixSettings, controllers: Controllers, /) -> None: if controllers and 'hotkey' in controllers[0].inputs and controllers[0].inputs['hotkey'].type == 'button': retroconfig.save('input_enable_hotkey_btn', controllers[0].inputs['hotkey'].id) + # Without this, input_enable_hotkey_btn alone arms the hotkey modifier + # but no joypad action is bound to it, so Hotkey+Start (the batocera + # keyboard convention for force-quitting a game, see es_input.cfg's + # keyboard hotkey=select binding) does nothing on a gamepad - only the + # keyboard input_exit_emulator="escape" above ever quits a running + # libretro core. Confirmed live on hardware (ODROID-M1, SHAKS S6b pad): + # Hotkey+Start did not exit FBNeo until this was added. + if controllers and 'start' in controllers[0].inputs and controllers[0].inputs['start'].type == 'button': + retroconfig.save('input_exit_emulator_btn', controllers[0].inputs['start'].id) # Write a configuration for a specified controller def writeControllerConfig( From 506ab9af6d0ab1d7ec05b3200f8d60872b99bc75 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Mon, 17 Aug 2026 10:55:32 +0900 Subject: [PATCH 19/26] batocera-system: add board-specific ODROIDM1 identity symbol Same fix as odroid-m1-bsp-kernel: BR2_PACKAGE_UBOOT_ODROID_M1 was being used as a "not this board" proxy in two Config.in gates (rocknix-joypad, SDL3_X11), but the shared 11-board rk3568 target also sets that symbol, so both gates were silently dropping rocknix-joypad and SDL3_X11 for the other 10 boards too. Introduces a real per-target identity symbol (BR2_PACKAGE_BATOCERA_TARGET_BSP_ODROIDM1, set only by this dedicated target's own .board file) and switches both gates to it. Line 1228 (TRX) already used a real capability check (BR2_PACKAGE_HAS_LIBGL) and needed no change. --- configs/batocera-rk3568-odroidm1.board | 5 +++++ package/batocera/core/batocera-system/Config.in | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/configs/batocera-rk3568-odroidm1.board b/configs/batocera-rk3568-odroidm1.board index 243ad93e11d..24f90ad6d4e 100644 --- a/configs/batocera-rk3568-odroidm1.board +++ b/configs/batocera-rk3568-odroidm1.board @@ -20,6 +20,11 @@ BR2_aarch64=y BR2_cortex_a55=y BR2_ARM_FPU_NEON_FP_ARMV8=y BR2_PACKAGE_BATOCERA_TARGET_RK3568=y +# This dedicated target's own board identity, distinct from +# BR2_PACKAGE_UBOOT_ODROID_M1 (also set by the shared 11-board +# batocera-rk3568.board target, which builds an odroid-m1 image variant +# too - not a valid "is this our dedicated target" proxy). +BR2_PACKAGE_BATOCERA_TARGET_BSP_ODROIDM1=y BR2_TARGET_OPTIMIZATION="-pipe -fsigned-char" BR2_TARGET_GENERIC_GETTY_BAUDRATE_115200=y # NOT rk3568/patches: its ffmpeg/mpv patches add v4l2-request HEVC support diff --git a/package/batocera/core/batocera-system/Config.in b/package/batocera/core/batocera-system/Config.in index fc04f6ca05d..9b1e3c82ed3 100644 --- a/package/batocera/core/batocera-system/Config.in +++ b/package/batocera/core/batocera-system/Config.in @@ -348,7 +348,7 @@ config BR2_PACKAGE_BATOCERA_SYSTEM select BR2_PACKAGE_ROCKNIX_JOYPAD if (BR2_PACKAGE_BATOCERA_TARGET_RK3326 || \ BR2_PACKAGE_BATOCERA_TARGET_RK3568 || \ BR2_PACKAGE_BATOCERA_TARGET_H700) && \ - !BR2_PACKAGE_UBOOT_ODROID_M1 + !BR2_PACKAGE_BATOCERA_TARGET_BSP_ODROIDM1 select BR2_PACKAGE_UPOWER # required by some cemuhook servers @@ -797,6 +797,15 @@ config BR2_PACKAGE_BATOCERA_TARGET_JH7110 endchoice +# Board identity for the ODROID-M1 dedicated target (configs/batocera-rk3568-odroidm1.board), +# as opposed to BR2_PACKAGE_UBOOT_ODROID_M1, which the shared 11-board rk3568 +# target (configs/batocera-rk3568.board) also selects - that symbol is a +# bootloader package flag, not a board identity, and using it as a "not this +# board" proxy silently affects the other 10 boards on the shared target too. +config BR2_PACKAGE_BATOCERA_TARGET_BSP_ODROIDM1 + bool + default n + config BR2_PACKAGE_BATOCERA_EXTRAS bool "batocera.linux tools packages selection" @@ -2260,7 +2269,7 @@ config BR2_PACKAGE_BATOCERA_SDL3 select BR2_PACKAGE_SDL3_TTF select BR2_PACKAGE_SDL3_OPENGLES if BR2_PACKAGE_HAS_LIBGLES select BR2_PACKAGE_SDL3_OPENGL if BR2_PACKAGE_HAS_LIBGL - select BR2_PACKAGE_SDL3_X11 if BR2_PACKAGE_XORG7 && !BR2_PACKAGE_UBOOT_ODROID_M1 + select BR2_PACKAGE_SDL3_X11 if BR2_PACKAGE_XORG7 && !BR2_PACKAGE_BATOCERA_TARGET_BSP_ODROIDM1 select BR2_PACKAGE_SDL3_KMSDRM if BR2_PACKAGE_HAS_LIBGBM && !BR2_PACKAGE_BATOCERA_RPI_VCORE select BR2_PACKAGE_SDL3_WAYLAND if BR2_PACKAGE_BATOCERA_WAYLAND select BR2_PACKAGE_BATOCERA_HAS_SDL From 9d0d091d9c8c881e1c7df222e203951377092ff0 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Mon, 17 Aug 2026 10:55:32 +0900 Subject: [PATCH 20/26] docker.mk: drop comment reference to unimplemented EXTRA_SRCDIR_MOUNTS Only KERNEL_SRCDIR is actually wired up as an opt-in bind mount; the parenthetical describing a generic _SRCDIR= mechanism via EXTRA_SRCDIR_MOUNTS never existed. --- docker/docker.mk | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docker/docker.mk b/docker/docker.mk index afb73e3baa2..68b05667fbb 100644 --- a/docker/docker.mk +++ b/docker/docker.mk @@ -24,9 +24,8 @@ UID := $(shell id -u) GID := $(shell id -g) # Opt-in extra bind mount for iterative package development: point -# KERNEL_SRCDIR (or any _SRCDIR= via EXTRA_SRCDIR_MOUNTS, see -# below) at a local git checkout and it shows up in the container at -# /kernel-src, ready for a _OVERRIDE_SRCDIR=/kernel-src in local.mk +# KERNEL_SRCDIR at a local git checkout and it shows up in the container +# at /kernel-src, ready for a _OVERRIDE_SRCDIR=/kernel-src in local.mk # (see output//local.mk - BR2_PACKAGE_OVERRIDE_FILE's default # location). This is buildroot's own documented mechanism for "edit # source, rebuild, no commit/push needed" - see buildroot manual's "Using From 7c6e0c3d0799b3c56805eaf64be61ae1cedd6616 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Mon, 17 Aug 2026 11:09:18 +0900 Subject: [PATCH 21/26] batocera-system: give BSP_ODROIDM1 identity symbol a prompt - prompt-less bool wasn't settable from defconfig A bare "bool" (no prompt string) config isn't directly settable via a defconfig/.board file's "=y" line - Kconfig accepts the assignment without error but the value never actually takes, defeating the whole point of a board-identity symbol meant to be set from configs/batocera-rk3568-odroidm1.board. Confirmed via a real defconfig+conf run: rocknix-joypad/SDL3_X11 stayed dropped on the shared rk3568 target (BSP_ODROIDM1 silently not set at all) until this fix, and now correctly restored there while staying off on this dedicated target. --- package/batocera/core/batocera-system/Config.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/batocera/core/batocera-system/Config.in b/package/batocera/core/batocera-system/Config.in index 9b1e3c82ed3..71c15dcaa15 100644 --- a/package/batocera/core/batocera-system/Config.in +++ b/package/batocera/core/batocera-system/Config.in @@ -803,7 +803,7 @@ endchoice # bootloader package flag, not a board identity, and using it as a "not this # board" proxy silently affects the other 10 boards on the shared target too. config BR2_PACKAGE_BATOCERA_TARGET_BSP_ODROIDM1 - bool + bool "odroidm1 dedicated bsp target" default n config BR2_PACKAGE_BATOCERA_EXTRAS From 26ccdce16e9b38a16aef09bf2d48e0c1e119f83e Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 18 Aug 2026 15:58:08 +0900 Subject: [PATCH 22/26] odroidm1: rebind Xbox Wireless Controller hotkey from Guide to Select This pad's wireless receiver never forwards a Guide-button (BTN_MODE) press to the OS in PC mode - confirmed live via evtest on the raw device: a real button press showed nothing, while the one time BTN_MODE did register turned out to be spurious (unrelated to the physical press). Hotkey+Start was therefore unreachable on this exact pad, so the documented "Select+Start" force-quit convention (0435f075f5) silently never worked. Rebind hotkey to id 6 / BTN_SELECT, matching batocera's own Select=hotkey convention and this project's SHAKS S6b precedent (bcf49a8e75) of overriding the build-time es_input.cfg default for a pad actually used with this board, rather than relying on a per-user /userdata override that a fresh reflash would wipe. Confirmed live (Track C image): Select+Start now force-quits a running libretro core. --- .../controllers/es_input.cfg | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/package/batocera/emulationstation/batocera-emulationstation/controllers/es_input.cfg b/package/batocera/emulationstation/batocera-emulationstation/controllers/es_input.cfg index 5349649e806..de1d620ccdf 100644 --- a/package/batocera/emulationstation/batocera-emulationstation/controllers/es_input.cfg +++ b/package/batocera/emulationstation/batocera-emulationstation/controllers/es_input.cfg @@ -3694,7 +3694,16 @@ - + + From 72f3ad476123a663c6080b990639521011cd96a3 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 18 Aug 2026 20:36:55 +0900 Subject: [PATCH 23/26] sdl2: add frametime logger, hooked at the eglSwapBuffers fn pointer Backport from Track C (odroid-m1-g29p1): logs one usec timestamp per present to FRAMETIME_LOG (env-gated, zero overhead when unset) for the A/B/C fps benchmark methodology. Hooked at the function-pointer level in SDL_EGL_LoadLibraryOnly() rather than the generic SDL_EGL_SwapBuffers() helper - Wayland/KMSDRM backends call the eglSwapBuffers pointer directly and never go through that helper, so a hook there would never fire. Verified against the pinned SDL2 2.32.10 source (same version A/C use) and confirmed live on Track C. NOTE: this worktree's buildroot submodule isn't checked out locally, so patch application against B's exact SDL2 source wasn't dry-run verified here - watch for a patch-apply failure on B's next sdl2 build if its pinned SDL2 version differs from 2.32.10. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../patches/sdl2/sdl2_frametime_log.patch | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 board/batocera/patches/sdl2/sdl2_frametime_log.patch diff --git a/board/batocera/patches/sdl2/sdl2_frametime_log.patch b/board/batocera/patches/sdl2/sdl2_frametime_log.patch new file mode 100644 index 00000000000..acd4b575c6d --- /dev/null +++ b/board/batocera/patches/sdl2/sdl2_frametime_log.patch @@ -0,0 +1,53 @@ +--- a/src/video/SDL_egl.c ++++ b/src/video/SDL_egl.c +@@ -37,6 +37,28 @@ + #include "SDL_egl_c.h" + #include "SDL_loadso.h" + #include "SDL_hints.h" ++#include ++#include ++ ++/* frametime logger (env FRAMETIME_LOG). Wraps the eglSwapBuffers function ++ * pointer itself instead of the generic SDL_EGL_SwapBuffers() helper: on ++ * this board (and any Wayland/KMSDRM backend), the per-backend SwapWindow ++ * implementations (Wayland_GLES_SwapWindow, KMSDRM_GLES_SwapWindow) call ++ * _this->egl_data->eglSwapBuffers() directly and never go through ++ * SDL_EGL_SwapBuffers() - a hook there is dead code for those backends. ++ * The function pointer is the true single choke point for every backend ++ * (SDL_EGL_SwapBuffers() itself also calls through this same pointer, so ++ * wrapping it here covers that path too without double-counting). */ ++static EGLBoolean (EGLAPIENTRY *FT_real_eglSwapBuffers)(EGLDisplay, EGLSurface); ++static FILE *FT_log; ++static EGLBoolean EGLAPIENTRY FT_log_eglSwapBuffers(EGLDisplay dpy, EGLSurface surf) ++{ ++ struct timespec ts_; ++ clock_gettime(CLOCK_MONOTONIC, &ts_); ++ fprintf(FT_log, "%lld\n", ++ (long long)(ts_.tv_sec * 1000000LL + ts_.tv_nsec / 1000)); ++ return FT_real_eglSwapBuffers(dpy, surf); ++} + + #ifdef EGL_KHR_create_context + /* EGL_OPENGL_ES3_BIT_KHR was added in version 13 of the extension. */ +@@ -479,6 +501,21 @@ + _this->egl_data = NULL; + return -1; + } ++ ++ /* frametime logger: substitute logging wrapper if requested. Guarded by ++ * a pointer-identity check so a second LoadLibraryOnly() call (video ++ * reinit) doesn't double-wrap. */ ++ { ++ const char *ft_path = SDL_getenv("FRAMETIME_LOG"); ++ if (ft_path && !FT_log) { ++ FT_log = fopen(ft_path, "w"); ++ if (FT_log) setvbuf(FT_log, NULL, _IOLBF, 0); ++ } ++ if (FT_log && _this->egl_data->eglSwapBuffers != FT_log_eglSwapBuffers) { ++ FT_real_eglSwapBuffers = _this->egl_data->eglSwapBuffers; ++ _this->egl_data->eglSwapBuffers = FT_log_eglSwapBuffers; ++ } ++ } + return 0; + } + From 001010c73761df96a3864b3ce6e59adce5e0108c Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Tue, 18 Aug 2026 21:37:22 +0900 Subject: [PATCH 24/26] sdl2: exclude emulationstation itself from the frametime logger Same fix as Track C: ES inherits FRAMETIME_LOG so its forked game picks it up, but ES itself also links libSDL2 and does its own swaps, so it was writing into the same log file as the game and corrupting it with interleaved timestamps from two processes. Exclude the frontend by /proc/self/comm so only the actual emulator logs. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../patches/sdl2/sdl2_frametime_log.patch | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/board/batocera/patches/sdl2/sdl2_frametime_log.patch b/board/batocera/patches/sdl2/sdl2_frametime_log.patch index acd4b575c6d..7a0c9accbd8 100644 --- a/board/batocera/patches/sdl2/sdl2_frametime_log.patch +++ b/board/batocera/patches/sdl2/sdl2_frametime_log.patch @@ -1,6 +1,6 @@ --- a/src/video/SDL_egl.c +++ b/src/video/SDL_egl.c -@@ -37,6 +37,28 @@ +@@ -37,6 +37,54 @@ #include "SDL_egl_c.h" #include "SDL_loadso.h" #include "SDL_hints.h" @@ -25,19 +25,46 @@ + fprintf(FT_log, "%lld\n", + (long long)(ts_.tv_sec * 1000000LL + ts_.tv_nsec / 1000)); + return FT_real_eglSwapBuffers(dpy, surf); ++} ++ ++/* FRAMETIME_LOG is set on the *frontend* (emulationstation) process so that ++ * the game it forks/execs inherits it - but emulationstation itself also ++ * links this same libSDL2 and does its own swaps (still-live background ++ * redraws even with a game "in front"), so it would otherwise open and ++ * write into the same log file as the game it launched, corrupting it with ++ * interleaved/out-of-order timestamps from two independent processes ++ * (confirmed live: ES held an open FD on the game's FRAMETIME_LOG path, ++ * producing multi-hundred-second jumps in the CSV). Exclude the frontend ++ * itself by process name so only the actual emulator process logs. */ ++static int FT_skip_self(void) ++{ ++ static int cached = -1; ++ if (cached < 0) { ++ char comm[16] = {0}; ++ FILE *cf = fopen("/proc/self/comm", "r"); ++ if (cf) { ++ if (!fgets(comm, sizeof(comm), cf)) { ++ comm[0] = '\0'; ++ } ++ fclose(cf); ++ } ++ cached = (SDL_strncmp(comm, "emulationstatio", 15) == 0); ++ } ++ return cached; +} #ifdef EGL_KHR_create_context /* EGL_OPENGL_ES3_BIT_KHR was added in version 13 of the extension. */ -@@ -479,6 +501,21 @@ +@@ -479,6 +527,22 @@ _this->egl_data = NULL; return -1; } + + /* frametime logger: substitute logging wrapper if requested. Guarded by + * a pointer-identity check so a second LoadLibraryOnly() call (video -+ * reinit) doesn't double-wrap. */ -+ { ++ * reinit) doesn't double-wrap. Skipped entirely for the emulationstation ++ * frontend itself (see FT_skip_self() above). */ ++ if (!FT_skip_self()) { + const char *ft_path = SDL_getenv("FRAMETIME_LOG"); + if (ft_path && !FT_log) { + FT_log = fopen(ft_path, "w"); From 1fee3228a0c5340aea3aff97db43e24772905555 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Wed, 19 Aug 2026 23:00:08 +0900 Subject: [PATCH 25/26] odroidm1: default to Asia/Seoul timezone + Korean keyboard layout Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- package/batocera/core/batocera-system/batocera.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/batocera/core/batocera-system/batocera.conf b/package/batocera/core/batocera-system/batocera.conf index 3738057e763..54ba78de5e9 100644 --- a/package/batocera/core/batocera-system/batocera.conf +++ b/package/batocera/core/batocera-system/batocera.conf @@ -210,7 +210,7 @@ controllers.gamecon.args=map=1 ## Set the keyboard layout (fr,de,us,es). ## To view all available layouts, i.e. English, via terminal type: ## sed '/! layout/,/^$/!d;/English/!d' < /usr/share/X11/xkb/rules/evdev.lst -#system.kblayout=de +system.kblayout=kr ## Setting a keyboard variant is optional ## To view all variants for German language variants, via terminal type: @@ -219,7 +219,7 @@ controllers.gamecon.args=map=1 ## Set the local time zone ## To view all available time zones, run: ls /usr/share/zoneinfo/ -#system.timezone=Europe/Paris +system.timezone=Asia/Seoul # ------------ G - Updates ------------ # From 5edfab01ff54fc1a03b3da0071935e2dbc216640 Mon Sep 17 00:00:00 2001 From: "Jang,Pyohwan" Date: Thu, 20 Aug 2026 22:23:28 +0900 Subject: [PATCH 26/26] odroidm1: revert global.powermode factory default back to unset ES's Game Settings > Power Mode menu showed "auto" (no saved user override) while highperformance was silently active underneath via the factory default - misleading, since the menu should reflect what's actually applied. Left unset so "auto" in the menu genuinely means auto; the user picks High Performance there explicitly when wanted. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei --- .../rk3568/batocera.conf.Hardkernel_ODROID_M1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 b/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 index 8bd01ef559a..df96eef9599 100644 --- a/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 +++ b/package/batocera/core/batocera-system/sysconfigs/rk3568/batocera.conf.Hardkernel_ODROID_M1 @@ -40,14 +40,14 @@ system.cpu.governor=interactive # knob. See Joplin "ODROID-M1 retro OS 빌드 계획" for the underlying # measurements if per-system defaults are ever wanted again. # -# global.powermode itself, however, was never actually set here - every -# A/B track fps measurement so far ran under whatever batocera-power-mode -# falls back to when global.powermode is unset (`set_default`, EPP/governor -# "default", not "performance"), not the highperformance path the above -# measurements were based on. Setting it here changes only the factory -# default; ES's Game Settings > Power Mode menu still overrides it live -# same as before. -global.powermode=highperformance +# global.powermode itself is intentionally left unset here (reverted after +# briefly trying a "highperformance" factory default) - ES's Game Settings +# > Power Mode menu displayed "auto" (no user override saved) even while +# that factory default was silently active underneath, which is misleading: +# the menu should reflect what's actually applied, not hide a source-level +# override the user never chose. Leaving it unset means "auto" in the menu +# genuinely means auto (batocera-power-mode's set_default fallback), and +# the user picks "High Performance" there explicitly if they want it. # Default core for the fbneo system: dsno/Zansword's Korean-patched # fork (package/batocera/emulators/retroarch/libretro/