From a1efd4fc016f7609cd26c09773188bf366d2380f Mon Sep 17 00:00:00 2001 From: Douglas Teles Date: Sat, 28 Feb 2026 00:14:54 -0300 Subject: [PATCH] Pre-merged panel DTBs: eliminate U-Boot fdt apply bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users reported that non-default panel overlays caused broken display, audio, and controls. Root cause: BSP U-Boot's fdt apply has bugs with property replacement of different-sized data, corrupting the DTB. Fix: apply overlays at build-time using fdtoverlay (verified working) instead of at boot-time. Each panel now gets a pre-merged kernel-*.dtb. U-Boot simply loads the right DTB by name — no fdt apply needed. - generate-panel-dtbos.sh: add fdtoverlay pre-merge step - build-image.sh: copy pre-merged DTBs instead of ScreenFiles - boot.ini: load PanelDTB by name, remove fdt addr/resize/apply - panel-detect.py: write DTB filename instead of DTBO overlay path --- build-image.sh | 43 +++++------ config/boot.ini | 36 +++++---- scripts/generate-panel-dtbos.sh | 129 ++++++++++++++++++++------------ scripts/panel-detect.py | 79 ++++++++----------- 4 files changed, 148 insertions(+), 139 deletions(-) diff --git a/build-image.sh b/build-image.sh index c8322d4be9..91c2cf3831 100755 --- a/build-image.sh +++ b/build-image.sh @@ -266,40 +266,33 @@ cp "$ROOTFS_DIR/boot/Image" "$MOUNT_BOOT/" cp "$ROOTFS_DIR/boot/$KERNEL_DTB_NAME" "$MOUNT_BOOT/kernel.dtb" log " kernel.dtb <- $KERNEL_DTB_NAME" -# --- Boot partition: Panel DTBO overlays (variant-specific) --- -PANELS_DIR="$OUTPUT_DIR/panels/ScreenFiles" -if [ -d "$PANELS_DIR" ]; then - mkdir -p "$MOUNT_BOOT/ScreenFiles" +# --- Generate pre-merged panel DTBs (overlay applied at build-time) --- +# Runs generate-panel-dtbos.sh to create DTBOs + pre-merged DTBs in one step. +log " Generating panel DTBs..." +"$SCRIPT_DIR/scripts/generate-panel-dtbos.sh" + +# --- Boot partition: Pre-merged panel DTBs --- +# Each panel gets its own kernel-*.dtb with the overlay already merged. +# No fdt apply needed at boot — U-Boot just loads the right DTB by name. +MERGED_DIR="$OUTPUT_DIR/panels/merged" +if [ -d "$MERGED_DIR" ]; then panel_count=0 - # Copy variant-specific panels (explicit names — NO glob with spaces) if [ "$VARIANT" = "original" ]; then - # R36S original: Panel 0 through Panel 5 - for i in 0 1 2 3 4 5; do - if [ -d "$PANELS_DIR/Panel $i" ]; then - cp -r "$PANELS_DIR/Panel $i" "$MOUNT_BOOT/ScreenFiles/" - panel_count=$((panel_count + 1)) - fi + # R36S original: kernel-panel0.dtb through kernel-panel5.dtb + for dtb in "$MERGED_DIR"/kernel-panel*.dtb; do + [ -f "$dtb" ] && cp "$dtb" "$MOUNT_BOOT/" && panel_count=$((panel_count + 1)) done else - # R36S clone: Clone Panel 1 through Clone Panel 10 + extras - for i in 1 2 3 4 5 6 7 8 9 10; do - if [ -d "$PANELS_DIR/Clone Panel $i" ]; then - cp -r "$PANELS_DIR/Clone Panel $i" "$MOUNT_BOOT/ScreenFiles/" - panel_count=$((panel_count + 1)) - fi - done - for extra in "R36 Max" "RX6S"; do - if [ -d "$PANELS_DIR/$extra" ]; then - cp -r "$PANELS_DIR/$extra" "$MOUNT_BOOT/ScreenFiles/" - panel_count=$((panel_count + 1)) - fi + # R36S clone: kernel-clone*.dtb + kernel-r36max.dtb + kernel-rx6s.dtb + for dtb in "$MERGED_DIR"/kernel-clone*.dtb "$MERGED_DIR"/kernel-r36max.dtb "$MERGED_DIR"/kernel-rx6s.dtb; do + [ -f "$dtb" ] && cp "$dtb" "$MOUNT_BOOT/" && panel_count=$((panel_count + 1)) done fi - log " ScreenFiles: ${panel_count} panel overlays ($VARIANT)" + log " Panel DTBs: ${panel_count} pre-merged ($VARIANT)" else - warn "Panel DTBOs not found! Run scripts/generate-panel-dtbos.sh first" + warn "Pre-merged panel DTBs not found! Run scripts/generate-panel-dtbos.sh first" fi # --- Boot partition: U-Boot display DTB --- diff --git a/config/boot.ini b/config/boot.ini index 49e579b86e..6098c19d1d 100644 --- a/config/boot.ini +++ b/config/boot.ini @@ -16,7 +16,7 @@ odroidgoa-uboot-config # --- Turn on LED immediately (feedback: device is booting) --- gpio set b5 -setenv dtbo_loadaddr "0x01e00000" +setenv tmp_loadaddr "0x01e00000" setenv dtb_loadaddr "0x01f00000" setenv loadaddr "0x02000000" @@ -31,27 +31,25 @@ else poweroff fi -# --- Panel overlay (read panel.txt written by panel-detect.py) --- -setenv PanelDTBO "" -mw.b ${dtbo_loadaddr} 0x00 0x400 -if load mmc ${mmcdev}:1 ${dtbo_loadaddr} panel.txt; then - env import -t ${dtbo_loadaddr} ${filesize} +# --- Panel DTB selection (read panel.txt written by panel-detect.py) --- +# Pre-merged DTBs: overlay already applied at build-time (no fdt apply needed). +# panel.txt contains PanelDTB=kernel-panelN.dtb (or empty for default). +setenv PanelDTB "" +mw.b ${tmp_loadaddr} 0x00 0x400 +if load mmc ${mmcdev}:1 ${tmp_loadaddr} panel.txt; then + env import -t ${tmp_loadaddr} ${filesize} +fi + +# Select DTB: pre-merged panel DTB if set, otherwise default kernel.dtb +if test -n "${PanelDTB}"; then + setenv dtb_name "${PanelDTB}" + echo "Panel DTB: ${PanelDTB}" +else + setenv dtb_name "kernel.dtb" fi # --- Load DTB and boot (Image already loaded above) --- -if load mmc ${mmcdev}:1 ${dtb_loadaddr} kernel.dtb; then - - # Apply panel DTBO overlay if PanelDTBO is set (non-default panel) - if test -n "${PanelDTBO}"; then - if load mmc ${mmcdev}:1 ${dtbo_loadaddr} "${PanelDTBO}"; then - fdt addr ${dtb_loadaddr} - fdt resize 8192 - fdt apply ${dtbo_loadaddr} - echo "Panel overlay: ${PanelDTBO}" - else - echo "WARN: overlay not found: ${PanelDTBO}" - fi - fi +if load mmc ${mmcdev}:1 ${dtb_loadaddr} ${dtb_name}; then setenv bootargs "root=__ROOTDEV__ rootwait rw console=ttyFIQ0 loglevel=0 quiet vt.global_cursor_default=0 consoleblank=0 printk.devkmsg=off fsck.mode=skip" diff --git a/scripts/generate-panel-dtbos.sh b/scripts/generate-panel-dtbos.sh index 842837b885..e69f937ac9 100755 --- a/scripts/generate-panel-dtbos.sh +++ b/scripts/generate-panel-dtbos.sh @@ -475,64 +475,99 @@ for panel_name in "${CLONE_ORDER[@]}"; do done #------------------------------------------------------------------------------ -# Create ScreenFiles directory structure (for boot.ini panel auto-detect) +# Pre-merge DTBs (overlay applied at build-time, not boot-time) +# +# This eliminates U-Boot's fdt apply at boot, which has known bugs in BSP +# U-Boot (property replacement with different-sized data can corrupt the DTB). +# Instead, fdtoverlay runs on the build host where it's verified to work. #------------------------------------------------------------------------------ log "" -log "Creating ScreenFiles directory structure..." +log "=== Pre-merging Panel DTBs ===" -SCREENFILES_DIR="$OUTPUT_DIR/ScreenFiles" -rm -rf "$SCREENFILES_DIR" +MERGED_DIR="$OUTPUT_DIR/merged" +rm -rf "$MERGED_DIR" +mkdir -p "$MERGED_DIR" + +# Check for fdtoverlay +if ! command -v fdtoverlay &>/dev/null; then + error "fdtoverlay not found. Install with: sudo apt install device-tree-compiler" +fi + +# Determine base DTBs (passed via --base-dtb-original and --base-dtb-clone, or auto-detect) +BASE_DTB_ORIGINAL="${BASE_DTB_ORIGINAL:-$PROJECT_DIR/output/boot/rk3326-gameconsole-r36s.dtb}" +BASE_DTB_CLONE="${BASE_DTB_CLONE:-$PROJECT_DIR/output/boot/rk3326-gameconsole-r36s-clone-type5.dtb}" + +MERGED_COUNT=0 # R36S original panels (Panel 0-5) -for panel_num in 0 1 2 3 4 5; do - dtbo_file="$OUTPUT_DIR/panel${panel_num}.dtbo" - if [ -f "$dtbo_file" ]; then - target_dir="$SCREENFILES_DIR/Panel ${panel_num}" - mkdir -p "$target_dir" - cp "$dtbo_file" "$target_dir/mipi-panel.dtbo" - log " ScreenFiles/Panel ${panel_num}/mipi-panel.dtbo" - fi -done +if [ -f "$BASE_DTB_ORIGINAL" ]; then + log "Base DTB (original): $BASE_DTB_ORIGINAL" + for panel_num in 0 1 2 3 4 5; do + dtbo_file="$OUTPUT_DIR/panel${panel_num}.dtbo" + merged_file="$MERGED_DIR/kernel-panel${panel_num}.dtb" + if [ -f "$dtbo_file" ]; then + if fdtoverlay -i "$BASE_DTB_ORIGINAL" -o "$merged_file" "$dtbo_file" 2>/dev/null; then + log " kernel-panel${panel_num}.dtb ($(stat -c%s "$merged_file") bytes)" + MERGED_COUNT=$((MERGED_COUNT + 1)) + else + warn " Failed to merge panel${panel_num}.dtbo" + fi + fi + done +else + warn "Base DTB (original) not found: $BASE_DTB_ORIGINAL" +fi # Clone panels -for panel_name in "${CLONE_ORDER[@]}"; do - safe_name=$(echo "$panel_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]') - dtbo_file="$OUTPUT_DIR/${safe_name}.dtbo" - if [ -f "$dtbo_file" ]; then - target_dir="$SCREENFILES_DIR/${panel_name}" - mkdir -p "$target_dir" - cp "$dtbo_file" "$target_dir/mipi-panel.dtbo" - log " ScreenFiles/${panel_name}/mipi-panel.dtbo" - fi -done +if [ -f "$BASE_DTB_CLONE" ]; then + log "Base DTB (clone): $BASE_DTB_CLONE" + + # Map: panel_name -> merged DTB filename + declare -A CLONE_MERGED_NAME=( + ["Clone Panel 1"]="kernel-clone1.dtb" + ["Clone Panel 2"]="kernel-clone2.dtb" + ["Clone Panel 3"]="kernel-clone3.dtb" + ["Clone Panel 4"]="kernel-clone4.dtb" + ["Clone Panel 5"]="kernel-clone5.dtb" + ["Clone Panel 6"]="kernel-clone6.dtb" + ["Clone Panel 7"]="kernel-clone7.dtb" + ["Clone Panel 8"]="kernel-clone8.dtb" + ["Clone Panel 9"]="kernel-clone9.dtb" + ["Clone Panel 10"]="kernel-clone10.dtb" + ["R36 Max"]="kernel-r36max.dtb" + ["RX6S"]="kernel-rx6s.dtb" + ) + + for panel_name in "${CLONE_ORDER[@]}"; do + safe_name=$(echo "$panel_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]') + dtbo_file="$OUTPUT_DIR/${safe_name}.dtbo" + merged_name="${CLONE_MERGED_NAME[$panel_name]}" + merged_file="$MERGED_DIR/$merged_name" + if [ -f "$dtbo_file" ]; then + if fdtoverlay -i "$BASE_DTB_CLONE" -o "$merged_file" "$dtbo_file" 2>/dev/null; then + log " $merged_name ($(stat -c%s "$merged_file") bytes)" + MERGED_COUNT=$((MERGED_COUNT + 1)) + else + warn " Failed to merge ${safe_name}.dtbo" + fi + fi + done +else + warn "Base DTB (clone) not found: $BASE_DTB_CLONE" +fi #------------------------------------------------------------------------------ # Summary #------------------------------------------------------------------------------ log "" -log "=== Panel DTBO Generation Complete ===" -log "Generated: ${GENERATED} panels" -[ $FAILED -gt 0 ] && warn "Failed: ${FAILED} panels" +log "=== Panel Generation Complete ===" +log "DTBOs generated: ${GENERATED}" +log "Pre-merged DTBs: ${MERGED_COUNT}" +[ $FAILED -gt 0 ] && warn "Failed: ${FAILED}" log "" -log "Structure:" -log " output/panels/ScreenFiles/" -log " ├── Panel 0/mipi-panel.dtbo (R36S original)" -log " ├── Panel 1/mipi-panel.dtbo (R36S original)" -log " ├── Panel 2/mipi-panel.dtbo (R36S original)" -log " ├── Panel 3/mipi-panel.dtbo (R36S original)" -log " ├── Panel 4/mipi-panel.dtbo (R36S Panel4-V22, default)" -log " ├── Panel 5/mipi-panel.dtbo (R36S original)" -log " ├── Clone Panel 1/mipi-panel.dtbo (ST7703)" -log " ├── Clone Panel 2/mipi-panel.dtbo (ST7703)" -log " ├── Clone Panel 3/mipi-panel.dtbo (NV3051D)" -log " ├── Clone Panel 4/mipi-panel.dtbo (NV3051D)" -log " ├── Clone Panel 5/mipi-panel.dtbo (ST7703)" -log " ├── Clone Panel 6/mipi-panel.dtbo (NV3051D)" -log " ├── Clone Panel 7/mipi-panel.dtbo (JD9365DA)" -log " ├── Clone Panel 8/mipi-panel.dtbo (ST7703)" -log " ├── Clone Panel 9/mipi-panel.dtbo (NV3051D)" -log " ├── Clone Panel 10/mipi-panel.dtbo (ST7703 variant)" -log " ├── R36 Max/mipi-panel.dtbo (ST7703 720x720)" -log " └── RX6S/mipi-panel.dtbo (NV3051D)" +log "Output: $MERGED_DIR/" +ls -1 "$MERGED_DIR"/*.dtb 2>/dev/null | while read f; do + log " $(basename "$f")" +done log "" -log "Copy to boot partition: cp -r output/panels/ScreenFiles /boot/" +log "These pre-merged DTBs go on BOOT partition. boot.ini loads the selected one." diff --git a/scripts/panel-detect.py b/scripts/panel-detect.py index 37a14cef5f..93e83d84b6 100755 --- a/scripts/panel-detect.py +++ b/scripts/panel-detect.py @@ -18,7 +18,7 @@ Flow: 6. After 2 full cycles without confirm: auto-confirm default Panel selection is persistent: - - panel.txt: U-Boot reads PanelDTBO variable (overlay path) + - panel.txt: U-Boot reads PanelDTB variable (pre-merged DTB name) - panel-confirmed: marker file (>1 byte = confirmed, ≤1 byte = reset) - Hold X during boot to reset (U-Boot overwrites panel-confirmed with 1 byte) """ @@ -62,52 +62,35 @@ WAIT_PER_PANEL = 15 # seconds to wait for input per panel MAX_CYCLES = 2 # auto-confirm default after this many full cycles # --- Panel definitions per variant --- -# (panel_num, dtbo_path, friendly_name) -# Empty dtbo_path = default panel (hardcoded in base DTB, no overlay needed) +# (panel_num, dtb_name, friendly_name) +# Empty dtb_name = default panel (hardcoded in base DTB kernel.dtb, no overlay) +# Non-empty dtb_name = pre-merged DTB with overlay applied at build-time # Order: most common first # R36S Original — 6 panels, default is Panel 4-V22 (~60% of units) PANELS_ORIGINAL = [ - ("4", "", - "Panel 4-V22 (Default)"), - ("3", "ScreenFiles/Panel 3/mipi-panel.dtbo", - "Panel 3-V20"), - ("5", "ScreenFiles/Panel 5/mipi-panel.dtbo", - "Panel 5-V22 Q8"), - ("0", "ScreenFiles/Panel 0/mipi-panel.dtbo", - "Panel 0"), - ("1", "ScreenFiles/Panel 1/mipi-panel.dtbo", - "Panel 1-V10"), - ("2", "ScreenFiles/Panel 2/mipi-panel.dtbo", - "Panel 2-V12"), + ("4", "", "Panel 4-V22 (Default)"), + ("3", "kernel-panel3.dtb", "Panel 3-V20"), + ("5", "kernel-panel5.dtb", "Panel 5-V22 Q8"), + ("0", "kernel-panel0.dtb", "Panel 0"), + ("1", "kernel-panel1.dtb", "Panel 1-V10"), + ("2", "kernel-panel2.dtb", "Panel 2-V12"), ] # R36S Clone — 12 panels, default is Clone 8 ST7703 (G80CA-MB) PANELS_CLONE = [ - ("C8", "", - "Clone 8 ST7703 G80CA (Default)"), - ("C1", "ScreenFiles/Clone Panel 1/mipi-panel.dtbo", - "Clone 1 (ST7703)"), - ("C3", "ScreenFiles/Clone Panel 3/mipi-panel.dtbo", - "Clone 3 (NV3051D)"), - ("C7", "ScreenFiles/Clone Panel 7/mipi-panel.dtbo", - "Clone 7 (JD9365DA)"), - ("C9", "ScreenFiles/Clone Panel 9/mipi-panel.dtbo", - "Clone 9 (NV3051D)"), - ("C10", "ScreenFiles/Clone Panel 10/mipi-panel.dtbo", - "Clone 10 (ST7703)"), - ("C2", "ScreenFiles/Clone Panel 2/mipi-panel.dtbo", - "Clone 2 (ST7703)"), - ("C4", "ScreenFiles/Clone Panel 4/mipi-panel.dtbo", - "Clone 4 (NV3051D)"), - ("C5", "ScreenFiles/Clone Panel 5/mipi-panel.dtbo", - "Clone 5 (ST7703)"), - ("C6", "ScreenFiles/Clone Panel 6/mipi-panel.dtbo", - "Clone 6 (NV3051D)"), - ("MAX", "ScreenFiles/R36 Max/mipi-panel.dtbo", - "R36 Max (720x720)"), - ("RX6S", "ScreenFiles/RX6S/mipi-panel.dtbo", - "RX6S (NV3051D)"), + ("C8", "", "Clone 8 ST7703 G80CA (Default)"), + ("C1", "kernel-clone1.dtb", "Clone 1 (ST7703)"), + ("C3", "kernel-clone3.dtb", "Clone 3 (NV3051D)"), + ("C7", "kernel-clone7.dtb", "Clone 7 (JD9365DA)"), + ("C9", "kernel-clone9.dtb", "Clone 9 (NV3051D)"), + ("C10", "kernel-clone10.dtb", "Clone 10 (ST7703)"), + ("C2", "kernel-clone2.dtb", "Clone 2 (ST7703)"), + ("C4", "kernel-clone4.dtb", "Clone 4 (NV3051D)"), + ("C5", "kernel-clone5.dtb", "Clone 5 (ST7703)"), + ("C6", "kernel-clone6.dtb", "Clone 6 (NV3051D)"), + ("MAX", "kernel-r36max.dtb", "R36 Max (720x720)"), + ("RX6S", "kernel-rx6s.dtb", "RX6S (NV3051D)"), ] @@ -286,13 +269,13 @@ def fsync_write(path, data): os.close(dir_fd) -def write_panel_config(panel_num, dtbo_path): +def write_panel_config(panel_num, dtb_name): """Write panel.txt for U-Boot to load on next boot.""" content = f"PanelNum={panel_num}\n" - if dtbo_path: - content += f"PanelDTBO={dtbo_path}\n" + if dtb_name: + content += f"PanelDTB={dtb_name}\n" else: - content += "PanelDTBO=\n" + content += "PanelDTB=\n" fsync_write(PANEL_TXT, content) @@ -395,9 +378,9 @@ def main(): # Panel selection loop for cycle in range(MAX_CYCLES): - for idx, (panel_num, dtbo_path, name) in enumerate(panels): + for idx, (panel_num, dtb_name, name) in enumerate(panels): # Write panel config (ready for confirm) - write_panel_config(panel_num, dtbo_path) + write_panel_config(panel_num, dtb_name) # Visual feedback on tty1 position = f"[{idx + 1}/{len(panels)}]" @@ -419,15 +402,15 @@ def main(): play_confirm_sound(confirm_beep) confirm_panel() subprocess.run(["sync"]) - if dtbo_path: - # Non-default panel: overlay applied by U-Boot on next reset + if dtb_name: + # Non-default panel: pre-merged DTB loaded on next boot write_tty(f"Confirmed: {name}\n\n Press RESET to apply.") print(f" Non-default panel — waiting for RESET") # Hold here until user presses RESET (no timeout) while True: time.sleep(60) else: - # Default panel: no overlay needed, continue booting + # Default panel: kernel.dtb used, continue booting write_tty(f"Confirmed: {name}") print(f" Default panel — continuing boot") sys.exit(0)