mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-07-12 18:19:42 -07:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4294ab47b | |||
| 70765e96bd | |||
| 2ccf5383ac | |||
| 476b05cd9a | |||
| a9029b419f |
+17
@@ -71,3 +71,20 @@ restore-mesa26-gbm.sh
|
||||
|
||||
# Website (separate project)
|
||||
website/
|
||||
|
||||
# Environment secrets
|
||||
.env
|
||||
|
||||
# Bootloader source trees (cloned/downloaded during build)
|
||||
bootloader/rkbin/
|
||||
bootloader/u-boot-mainline/
|
||||
bootloader/u-boot-rk3326-clone/
|
||||
|
||||
# Pre-built U-Boot backups (working binaries, not part of build pipeline)
|
||||
bootloader/u-boot-clone-working/
|
||||
|
||||
# Extracted DTBs (debugging artifacts)
|
||||
bootloader/u-boot-r36s-working/extracted_dtb_*.dtb
|
||||
|
||||
# Clone U-Boot build output
|
||||
bootloader/u-boot-clone-build/
|
||||
|
||||
@@ -153,11 +153,81 @@ Arch-R/
|
||||
|
||||
## Supported Panels
|
||||
|
||||
Arch R supports 18 display panels via DTBO overlays:
|
||||
Arch R supports 18 display panels via DTBO overlays. The default image boots with **Panel 4-V22** (the most common R36S panel).
|
||||
|
||||
**Original R36S:** Panel 0, Panel 1 (V10), Panel 2 (V12), Panel 3 (V20), Panel 4 (V22, default), Panel 5 (V22 Q8)
|
||||
### Panel Auto-Detection
|
||||
|
||||
**Clones:** Clone Panel 1-10 (ST7703, NV3051D variants), R36 Max, RX6S
|
||||
On first boot, Arch R runs a **panel detection wizard** that guides you through selecting your display panel:
|
||||
|
||||
1. **If your screen works** (you see text on screen): press **A** to confirm the current panel
|
||||
2. **If your screen is black** (wrong panel): listen for audio beeps and use buttons to navigate:
|
||||
- Each panel plays a different number of beeps (1 beep = first panel, 2 beeps = second, etc.)
|
||||
- Press **B** to skip to the next panel
|
||||
- Press **A** to confirm (you'll hear 3 rapid beeps)
|
||||
- The system reboots with the correct panel applied
|
||||
|
||||
Your selection is saved permanently. To reset it later, **hold X during boot**.
|
||||
|
||||
### Panel List
|
||||
|
||||
| # | Panel | Notes |
|
||||
|---|-------|-------|
|
||||
| 1 | **Panel 4-V22** *(default)* | Most common (~60% of R36S units) |
|
||||
| 2 | Panel 3-V20 | V20 board marking |
|
||||
| 3 | Panel 5-V22 Q8 | V22 Q8 variant |
|
||||
| 4 | Clone Panel 8 | ST7703 (G80CA-MB) |
|
||||
| 5 | Panel 0 | Early R36S units |
|
||||
| 6 | Panel 1-V10 | V10 board marking |
|
||||
| 7 | Panel 2-V12 | V12 board marking |
|
||||
| 8 | Clone Panel 1 | ST7703 |
|
||||
| 9 | Clone Panel 3 | NV3051D |
|
||||
| 10 | Clone Panel 7 | JD9365DA |
|
||||
| 11 | Clone Panel 9 | NV3051D |
|
||||
| 12 | Clone Panel 10 | ST7703 variant |
|
||||
| 13 | Clone Panel 2 | ST7703 |
|
||||
| 14 | Clone Panel 4 | NV3051D |
|
||||
| 15 | Clone Panel 5 | ST7703 |
|
||||
| 16 | Clone Panel 6 | NV3051D |
|
||||
| 17 | R36 Max | 720x720 ST7703 |
|
||||
| 18 | RX6S | NV3051D variant |
|
||||
|
||||
### Manual Panel Selection
|
||||
|
||||
If you prefer to set the panel manually (e.g., you know which panel you have from ArkOS/dArkOS):
|
||||
|
||||
**Step 1 — Mount the BOOT partition on your PC**
|
||||
|
||||
```bash
|
||||
lsblk
|
||||
sudo mount /dev/sdX1 /mnt
|
||||
```
|
||||
|
||||
**Step 2 — Create panel.txt**
|
||||
|
||||
```bash
|
||||
# Example: Panel 3-V20
|
||||
echo 'PanelNum=3
|
||||
PanelDTBO=ScreenFiles/Panel 3/mipi-panel.dtbo' | sudo tee /mnt/panel.txt
|
||||
|
||||
# Mark as confirmed (skip wizard on next boot)
|
||||
echo 'confirmed' | sudo tee /mnt/panel-confirmed
|
||||
```
|
||||
|
||||
For default Panel 4-V22 (no overlay needed):
|
||||
```bash
|
||||
echo 'PanelNum=4
|
||||
PanelDTBO=' | sudo tee /mnt/panel.txt
|
||||
echo 'confirmed' | sudo tee /mnt/panel-confirmed
|
||||
```
|
||||
|
||||
**Step 3 — Unmount and boot**
|
||||
|
||||
```bash
|
||||
sudo umount /mnt
|
||||
sync
|
||||
```
|
||||
|
||||
**To reset panel selection:** hold **X** during boot, or delete `panel-confirmed` from the BOOT partition.
|
||||
|
||||
## Boot Flow
|
||||
|
||||
@@ -165,11 +235,12 @@ Arch R supports 18 display panels via DTBO overlays:
|
||||
Power On
|
||||
→ U-Boot (idbloader → trust → uboot.img)
|
||||
→ logo.bmp displayed on screen
|
||||
→ extlinux/extlinux.conf loaded (primary)
|
||||
→ boot.ini: load panel.txt → apply DTBO overlay (if non-default panel)
|
||||
→ Kernel 6.6.89 + rk3326-gameconsole-r36s.dtb
|
||||
→ systemd → archr-boot-setup (GPU + governors)
|
||||
→ systemd → panel-detect.service (first boot only)
|
||||
→ archr-boot-setup (GPU + governors)
|
||||
→ emulationstation.service → EmulationStation UI
|
||||
≈ 19 seconds total
|
||||
≈ 19 seconds total (+ wizard on first boot)
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
+745
-152
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+30
-4
@@ -44,6 +44,7 @@ usage() {
|
||||
echo "Options:"
|
||||
echo " --kernel Build kernel only"
|
||||
echo " --rootfs Build rootfs only"
|
||||
echo " --uboot Build U-Boot only"
|
||||
echo " --image Build image only"
|
||||
echo " --all Build everything (default)"
|
||||
echo " --clean Clean all build artifacts"
|
||||
@@ -53,6 +54,7 @@ usage() {
|
||||
|
||||
BUILD_KERNEL=false
|
||||
BUILD_ROOTFS=false
|
||||
BUILD_UBOOT=false
|
||||
BUILD_IMAGE=false
|
||||
CLEAN=false
|
||||
|
||||
@@ -60,14 +62,16 @@ CLEAN=false
|
||||
if [ $# -eq 0 ]; then
|
||||
BUILD_KERNEL=true
|
||||
BUILD_ROOTFS=true
|
||||
BUILD_UBOOT=true
|
||||
BUILD_IMAGE=true
|
||||
else
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--kernel) BUILD_KERNEL=true; shift ;;
|
||||
--rootfs) BUILD_ROOTFS=true; shift ;;
|
||||
--uboot) BUILD_UBOOT=true; shift ;;
|
||||
--image) BUILD_IMAGE=true; shift ;;
|
||||
--all) BUILD_KERNEL=true; BUILD_ROOTFS=true; BUILD_IMAGE=true; shift ;;
|
||||
--all) BUILD_KERNEL=true; BUILD_ROOTFS=true; BUILD_UBOOT=true; BUILD_IMAGE=true; shift ;;
|
||||
--clean) CLEAN=true; shift ;;
|
||||
--help) usage; exit 0 ;;
|
||||
*) error "Unknown option: $1" ;;
|
||||
@@ -180,13 +184,35 @@ log "═════════════════════════
|
||||
chmod +x "$SCRIPT_DIR/scripts/generate-panel-dtbos.sh"
|
||||
"$SCRIPT_DIR/scripts/generate-panel-dtbos.sh"
|
||||
|
||||
# Build custom U-Boot (optional — requires arm-linux-gnueabihf toolchain)
|
||||
if [ "$BUILD_UBOOT" = true ] && [ -f "$SCRIPT_DIR/build-uboot.sh" ]; then
|
||||
if command -v aarch64-linux-gnu-gcc &>/dev/null; then
|
||||
log ""
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
log " BUILDING U-BOOT"
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
chmod +x "$SCRIPT_DIR/build-uboot.sh"
|
||||
"$SCRIPT_DIR/build-uboot.sh"
|
||||
else
|
||||
warn "Skipping U-Boot build: aarch64-linux-gnu-gcc not found"
|
||||
warn "Install: sudo apt install gcc-aarch64-linux-gnu"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$BUILD_IMAGE" = true ]; then
|
||||
chmod +x "$SCRIPT_DIR/build-image.sh"
|
||||
|
||||
log ""
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
log " BUILDING IMAGE"
|
||||
log " BUILDING IMAGE (original)"
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
chmod +x "$SCRIPT_DIR/build-image.sh"
|
||||
"$SCRIPT_DIR/build-image.sh"
|
||||
"$SCRIPT_DIR/build-image.sh" --variant original
|
||||
|
||||
log ""
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
log " BUILDING IMAGE (clone)"
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
"$SCRIPT_DIR/build-image.sh" --variant clone
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
+282
-110
File diff suppressed because it is too large
Load Diff
+35
-5
@@ -31,8 +31,9 @@ KERNEL_REPO="https://github.com/rockchip-linux/kernel.git"
|
||||
DEFCONFIG="rockchip_linux_defconfig"
|
||||
CONFIG_FRAGMENT="$SCRIPT_DIR/config/archr-6.6-r36s.config"
|
||||
|
||||
# DTS target
|
||||
# DTS targets
|
||||
R36S_DTB="rk3326-gameconsole-r36s"
|
||||
CLONE_DTB="rk3326-gameconsole-r36s-clone-type5"
|
||||
|
||||
# Paths
|
||||
KERNEL_SRC="${KERNEL_SRC:-/home/dgateles/Documentos/Projetos/kernel}"
|
||||
@@ -93,6 +94,14 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy clone DTS (if available)
|
||||
CLONE_DTS_SRC="$SCRIPT_DIR/kernel/dts/${CLONE_DTB}.dts"
|
||||
CLONE_DTS_DEST="$KERNEL_SRC/arch/arm64/boot/dts/rockchip/${CLONE_DTB}.dts"
|
||||
if [ -f "$CLONE_DTS_SRC" ]; then
|
||||
cp "$CLONE_DTS_SRC" "$CLONE_DTS_DEST"
|
||||
log " DTS (clone): copied from repo"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Step 2: Configure Kernel
|
||||
#------------------------------------------------------------------------------
|
||||
@@ -164,6 +173,17 @@ fi
|
||||
|
||||
log " DTB built: ${R36S_DTB}.dtb"
|
||||
|
||||
# Build clone DTB (if DTS was copied)
|
||||
if [ -f "$CLONE_DTS_DEST" ]; then
|
||||
make -C "$KERNEL_SRC" -j$JOBS ARCH=$ARCH CROSS_COMPILE=$CROSS_COMPILE \
|
||||
"rockchip/${CLONE_DTB}.dtb" 2>&1 | tail -5
|
||||
if [ -f "$KERNEL_SRC/arch/arm64/boot/dts/rockchip/${CLONE_DTB}.dtb" ]; then
|
||||
log " DTB built (clone): ${CLONE_DTB}.dtb"
|
||||
else
|
||||
warn "Clone DTB build failed: ${CLONE_DTB}.dtb"
|
||||
fi
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Step 5: Build Kernel Modules
|
||||
#------------------------------------------------------------------------------
|
||||
@@ -192,6 +212,12 @@ log " Copied: Image"
|
||||
cp "$KERNEL_SRC/arch/arm64/boot/dts/rockchip/${R36S_DTB}.dtb" "$BOOT_DIR/"
|
||||
log " Copied: ${R36S_DTB}.dtb"
|
||||
|
||||
# Copy clone DTB (if built)
|
||||
if [ -f "$KERNEL_SRC/arch/arm64/boot/dts/rockchip/${CLONE_DTB}.dtb" ]; then
|
||||
cp "$KERNEL_SRC/arch/arm64/boot/dts/rockchip/${CLONE_DTB}.dtb" "$BOOT_DIR/"
|
||||
log " Copied: ${CLONE_DTB}.dtb"
|
||||
fi
|
||||
|
||||
# Install modules (use pipefail to catch errors masked by tail)
|
||||
set -o pipefail
|
||||
make -C "$KERNEL_SRC" ARCH=$ARCH CROSS_COMPILE=$CROSS_COMPILE \
|
||||
@@ -231,9 +257,13 @@ MODULES_SIZE=$(du -sh "$MODULES_DIR" 2>/dev/null | cut -f1 || echo "N/A")
|
||||
|
||||
log "Kernel: $KERNEL_FULL"
|
||||
log ""
|
||||
CLONE_DTB_SIZE="N/A"
|
||||
[ -f "$BOOT_DIR/${CLONE_DTB}.dtb" ] && CLONE_DTB_SIZE=$(du -h "$BOOT_DIR/${CLONE_DTB}.dtb" | cut -f1)
|
||||
|
||||
log "Artifacts:"
|
||||
log " Image: $BOOT_DIR/Image ($IMAGE_SIZE)"
|
||||
log " DTB: $BOOT_DIR/${R36S_DTB}.dtb ($DTB_SIZE)"
|
||||
log " Modules: $MODULES_DIR/ ($MODULES_SIZE)"
|
||||
log " Image: $BOOT_DIR/Image ($IMAGE_SIZE)"
|
||||
log " DTB: $BOOT_DIR/${R36S_DTB}.dtb ($DTB_SIZE)"
|
||||
log " DTB clone: $BOOT_DIR/${CLONE_DTB}.dtb ($CLONE_DTB_SIZE)"
|
||||
log " Modules: $MODULES_DIR/ ($MODULES_SIZE)"
|
||||
log ""
|
||||
log "Kernel 6.6 ready for R36S!"
|
||||
log "Kernel 6.6 ready for R36S (original + clone)!"
|
||||
|
||||
+34
-3
@@ -353,6 +353,7 @@ fi
|
||||
cat > /etc/systemd/system/firstboot.service << 'FB_EOF'
|
||||
[Unit]
|
||||
Description=Arch R First Boot Setup
|
||||
After=local-fs.target
|
||||
Before=emulationstation.service
|
||||
ConditionPathExists=!/var/lib/archr/.first-boot-done
|
||||
|
||||
@@ -367,6 +368,25 @@ FB_EOF
|
||||
|
||||
systemctl enable firstboot
|
||||
|
||||
# Panel auto-detect wizard (first boot / X-button reset)
|
||||
cat > /etc/systemd/system/panel-detect.service << 'PANELDET_EOF'
|
||||
[Unit]
|
||||
Description=Arch R Panel Detection Wizard
|
||||
After=local-fs.target
|
||||
Before=emulationstation.service
|
||||
RequiresMountsFor=/boot
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/panel-detect.py
|
||||
TimeoutStartSec=600
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
PANELDET_EOF
|
||||
|
||||
systemctl enable panel-detect
|
||||
|
||||
# EmulationStation launch — PRIMARY: systemd service (created by build-emulationstation.sh)
|
||||
# FALLBACK: autologin + .bash_profile (if ES service not installed)
|
||||
# The emulationstation.service Conflicts with getty@tty1, so only one runs.
|
||||
@@ -403,9 +423,12 @@ fi
|
||||
PROFILE_EOF
|
||||
chown archr:archr /home/archr/.bash_profile
|
||||
|
||||
# Disable old custom splash if it exists
|
||||
# Boot splash: handled by initramfs (archr-init with embedded BMP).
|
||||
# Initramfs displays splash at ~0.7s after kernel start, before systemd.
|
||||
# No systemd service needed — splash persists until ES takes DRM master.
|
||||
systemctl disable splash 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/splash.service
|
||||
systemctl disable archr-splash 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/splash.service /etc/systemd/system/archr-splash.service
|
||||
|
||||
# Battery LED warning service
|
||||
cat > /etc/systemd/system/battery-led.service << 'BATT_EOF'
|
||||
@@ -483,7 +506,8 @@ TIMING_EOF
|
||||
|
||||
systemctl enable boot-timing
|
||||
|
||||
# Delete initramfs (not used — extlinux.conf has no INITRD line)
|
||||
# Delete stock ALARM initramfs and kernel (we use our own kernel + initramfs)
|
||||
# Our initramfs.img is built by build-image.sh and placed on BOOT partition
|
||||
rm -f /boot/initramfs-linux.img /boot/Image /boot/Image.gz 2>/dev/null
|
||||
|
||||
# Clean kernel modules: remove media tuners (154 modules, 13MB — useless for gaming)
|
||||
@@ -968,6 +992,13 @@ log " ✓ Distro version installed (text.plymouth)"
|
||||
install -m 755 "$SCRIPT_DIR/scripts/first-boot.sh" "$ROOTFS_DIR/usr/local/bin/first-boot.sh"
|
||||
log " ✓ First boot script installed"
|
||||
|
||||
# Boot splash: no binary needed in rootfs — splash is embedded in initramfs /init
|
||||
# (built by build-image.sh: archr-init.c + embedded splash BMP → initramfs.img)
|
||||
|
||||
# Panel detection wizard
|
||||
install -m 755 "$SCRIPT_DIR/scripts/panel-detect.py" "$ROOTFS_DIR/usr/local/bin/panel-detect.py"
|
||||
log " ✓ Panel detection wizard installed"
|
||||
|
||||
# RetroArch config (install to user's config dir where retroarch expects it)
|
||||
mkdir -p "$ROOTFS_DIR/home/archr/.config/retroarch"
|
||||
cp "$SCRIPT_DIR/config/retroarch.cfg" "$ROOTFS_DIR/home/archr/.config/retroarch/retroarch.cfg"
|
||||
|
||||
Executable
+174
@@ -0,0 +1,174 @@
|
||||
#!/bin/bash
|
||||
#==============================================================================
|
||||
# Arch R - Build Mainline U-Boot for R36S Clones
|
||||
#==============================================================================
|
||||
# Uses mainline U-Boot v2025.10 + ROCKNIX patches
|
||||
# Produces: idbloader.img, uboot.img, trust.img (Rockchip format)
|
||||
#
|
||||
# Usage:
|
||||
# ./build-uboot-clone.sh [--uart5]
|
||||
#
|
||||
# --uart5: Build for K36 clones that use UART5 instead of UART2
|
||||
#==============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
UBOOT_SRC="$SCRIPT_DIR/bootloader/u-boot-mainline"
|
||||
RKBIN_DIR="$SCRIPT_DIR/bootloader/rkbin"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/bootloader/u-boot-clone-build"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[UBOOT-CLONE]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[UBOOT-CLONE] WARNING:${NC} $1"; }
|
||||
error() { echo -e "${RED}[UBOOT-CLONE] ERROR:${NC} $1"; exit 1; }
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Parse arguments
|
||||
#------------------------------------------------------------------------------
|
||||
UART5=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--uart5) UART5=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Verify prerequisites
|
||||
#------------------------------------------------------------------------------
|
||||
CROSS_COMPILE="aarch64-linux-gnu-"
|
||||
|
||||
if ! command -v ${CROSS_COMPILE}gcc &>/dev/null; then
|
||||
error "Cross-compiler not found. Install: sudo apt install gcc-aarch64-linux-gnu"
|
||||
fi
|
||||
|
||||
if ! python3 -c "import elftools" 2>/dev/null; then
|
||||
error "pyelftools not found. Install: pip3 install pyelftools"
|
||||
fi
|
||||
|
||||
if ! command -v swig &>/dev/null; then
|
||||
error "swig not found. Install: sudo apt install swig"
|
||||
fi
|
||||
|
||||
[ -d "$UBOOT_SRC" ] || error "Mainline U-Boot source not found: $UBOOT_SRC"
|
||||
[ -d "$RKBIN_DIR" ] || error "rkbin not found: $RKBIN_DIR"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Rockchip firmware binaries
|
||||
#------------------------------------------------------------------------------
|
||||
PKG_DDR_BIN="$RKBIN_DIR/bin/rk33/rk3326_ddr_333MHz_v2.11.bin"
|
||||
PKG_MINILOADER="$RKBIN_DIR/bin/rk33/rk3326_miniloader_v1.40.bin"
|
||||
PKG_BL31="$RKBIN_DIR/bin/rk33/rk3326_bl31_v1.34.elf"
|
||||
|
||||
[ -f "$PKG_DDR_BIN" ] || error "DDR binary not found: $PKG_DDR_BIN"
|
||||
[ -f "$PKG_MINILOADER" ] || error "Miniloader not found: $PKG_MINILOADER"
|
||||
[ -f "$PKG_BL31" ] || error "BL31 not found: $PKG_BL31"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# UART5 DDR binary (for K36 clones)
|
||||
#------------------------------------------------------------------------------
|
||||
if [ "$UART5" -eq 1 ]; then
|
||||
log "Building UART5 variant for K36 clones"
|
||||
PKG_DDR_BIN_UART5="$RKBIN_DIR/rk3326_ddr_uart5.bin"
|
||||
|
||||
if [ ! -f "$PKG_DDR_BIN_UART5" ]; then
|
||||
log "Creating UART5-tuned DDR binary..."
|
||||
DDRBIN_TOOL="$RKBIN_DIR/tools/ddrbin_tool"
|
||||
if [ ! -x "$DDRBIN_TOOL" ]; then
|
||||
error "ddrbin_tool not found or not executable: $DDRBIN_TOOL"
|
||||
fi
|
||||
cp "$PKG_DDR_BIN" "$PKG_DDR_BIN_UART5"
|
||||
"$DDRBIN_TOOL" rk3326 -g "$RKBIN_DIR/rk3326_ddr_uart5.txt" "$PKG_DDR_BIN_UART5"
|
||||
sed -i 's|uart id=.*$|uart id=5|' "$RKBIN_DIR/rk3326_ddr_uart5.txt"
|
||||
"$DDRBIN_TOOL" rk3326 "$RKBIN_DIR/rk3326_ddr_uart5.txt" "$PKG_DDR_BIN_UART5" >/dev/null
|
||||
log "UART5 DDR binary created"
|
||||
fi
|
||||
PKG_DDR_BIN="$PKG_DDR_BIN_UART5"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Build U-Boot
|
||||
#------------------------------------------------------------------------------
|
||||
JOBS=$(nproc)
|
||||
DEFCONFIG="rk3326-handheld_defconfig"
|
||||
|
||||
log "=== Building Mainline U-Boot v2025.10 for R36S Clone ==="
|
||||
log "Cross-compiler: $(${CROSS_COMPILE}gcc --version | head -1)"
|
||||
log "Defconfig: $DEFCONFIG"
|
||||
log "Jobs: $JOBS"
|
||||
[ "$UART5" -eq 1 ] && log "UART: UART5 (0xFF178000)" || log "UART: UART2 (0xFF160000)"
|
||||
|
||||
cd "$UBOOT_SRC"
|
||||
|
||||
log "Step 1: Clean..."
|
||||
make CROSS_COMPILE="$CROSS_COMPILE" ARCH=arm mrproper 2>&1 | tail -1
|
||||
|
||||
log "Step 2: Configure..."
|
||||
make CROSS_COMPILE="$CROSS_COMPILE" ARCH=arm "$DEFCONFIG" 2>&1 | tail -1
|
||||
|
||||
# UART5 variant: override config
|
||||
if [ "$UART5" -eq 1 ]; then
|
||||
./scripts/config --set-val CONFIG_DEBUG_UART_BASE 0xFF178000
|
||||
./scripts/config --set-str CONFIG_DEVICE_TREE_INCLUDES "rk3326-odroid-go2-emmc.dtsi rk3326-odroid-go2-uart5.dtsi"
|
||||
log "UART5 config applied"
|
||||
fi
|
||||
|
||||
log "Step 3: Compile..."
|
||||
make CROSS_COMPILE="$CROSS_COMPILE" ARCH=arm -j"$JOBS" u-boot-dtb.bin 2>&1 | tail -20
|
||||
|
||||
[ -f "u-boot-dtb.bin" ] || error "Build failed: u-boot-dtb.bin not found"
|
||||
log "u-boot-dtb.bin built: $(du -h u-boot-dtb.bin | cut -f1)"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Package: idbloader.img
|
||||
#------------------------------------------------------------------------------
|
||||
log "Step 4: Creating idbloader.img..."
|
||||
"$UBOOT_SRC/tools/mkimage" -n px30 -T rksd -d "$PKG_DDR_BIN" -C bzip2 idbloader.img
|
||||
cat "$PKG_MINILOADER" >> idbloader.img
|
||||
log "idbloader.img: $(du -h idbloader.img | cut -f1)"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Package: uboot.img
|
||||
#------------------------------------------------------------------------------
|
||||
log "Step 5: Creating uboot.img..."
|
||||
"$RKBIN_DIR/tools/loaderimage" --pack --uboot u-boot-dtb.bin uboot.img 0x00200000 2>&1 | tail -3
|
||||
log "uboot.img: $(du -h uboot.img | cut -f1)"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Package: trust.img
|
||||
#------------------------------------------------------------------------------
|
||||
log "Step 6: Creating trust.img..."
|
||||
cat >trust.ini <<EOF
|
||||
[BL30_OPTION]
|
||||
SEC=0
|
||||
[BL31_OPTION]
|
||||
SEC=1
|
||||
PATH=${PKG_BL31}
|
||||
ADDR=0x00010000
|
||||
[BL32_OPTION]
|
||||
SEC=0
|
||||
[BL33_OPTION]
|
||||
SEC=0
|
||||
[OUTPUT]
|
||||
PATH=trust.img
|
||||
EOF
|
||||
"$RKBIN_DIR/tools/trust_merger" --verbose trust.ini 2>&1 | tail -5
|
||||
log "trust.img: $(du -h trust.img | cut -f1)"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Copy to output directory
|
||||
#------------------------------------------------------------------------------
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
cp idbloader.img uboot.img trust.img "$OUTPUT_DIR/"
|
||||
|
||||
log ""
|
||||
log "=== Build Complete ==="
|
||||
log "Output: $OUTPUT_DIR/"
|
||||
ls -lh "$OUTPUT_DIR/"*.img
|
||||
log ""
|
||||
log "Flash with:"
|
||||
log " ./flash-uboot-clone.sh /dev/sdX"
|
||||
+65
-35
@@ -1,9 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
#==============================================================================
|
||||
# Arch R - Build U-Boot for R36S
|
||||
# Arch R - U-Boot Build Script for R36S
|
||||
#==============================================================================
|
||||
# Builds U-Boot bootloader for RK3326 (R36S/RG351MP compatible)
|
||||
# Builds custom U-Boot with R36S original/clone auto-detection via eMMC probe.
|
||||
# Cross-compiles for aarch64 (CONFIG_ARM64=y in defconfig).
|
||||
#
|
||||
# Custom modifications:
|
||||
# - cmd/hwrev.c: R36S detection via eMMC probe (original vs clone)
|
||||
# - r36s-uboot.dts: U-Boot display DTB for original (Panel 4-V22 NV3052C)
|
||||
# - r36s-clone-uboot.dts: U-Boot display DTB for clone (Type5 NV3052C)
|
||||
#
|
||||
# Prerequisites:
|
||||
# sudo apt install gcc-aarch64-linux-gnu
|
||||
#==============================================================================
|
||||
|
||||
set -e
|
||||
@@ -29,7 +38,10 @@ UBOOT_REPO="https://github.com/christianhaitian/RG351MP-u-boot"
|
||||
UBOOT_SRC="$UBOOT_DIR/u-boot-rk3326"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/output/bootloader"
|
||||
|
||||
log "=== Arch R - Build U-Boot ==="
|
||||
# U-Boot for RK3326 is aarch64 (CONFIG_ARM64=y in defconfig)
|
||||
CROSS_COMPILE="aarch64-linux-gnu-"
|
||||
|
||||
log "=== Arch R - U-Boot Builder (Custom R36S) ==="
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Step 1: Clone U-Boot
|
||||
@@ -42,59 +54,77 @@ mkdir -p "$UBOOT_DIR"
|
||||
if [ ! -d "$UBOOT_SRC" ]; then
|
||||
log " Cloning U-Boot for RK3326..."
|
||||
git clone --depth 1 "$UBOOT_REPO" "$UBOOT_SRC"
|
||||
log " ✓ U-Boot cloned"
|
||||
log " Cloned"
|
||||
else
|
||||
log " ✓ U-Boot source exists"
|
||||
log " Source exists"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Step 2: Verify toolchain
|
||||
#------------------------------------------------------------------------------
|
||||
log ""
|
||||
log "Step 2: Checking toolchain..."
|
||||
|
||||
if ! command -v ${CROSS_COMPILE}gcc &>/dev/null; then
|
||||
error "aarch64 toolchain not found!\n Install: sudo apt install gcc-aarch64-linux-gnu"
|
||||
fi
|
||||
|
||||
log " $(${CROSS_COMPILE}gcc --version | head -1)"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Step 3: Build U-Boot
|
||||
#------------------------------------------------------------------------------
|
||||
log ""
|
||||
log "Step 3: Building U-Boot..."
|
||||
|
||||
cd "$UBOOT_SRC"
|
||||
|
||||
# GCC 13+ adds stricter warnings that U-Boot 2017.09 doesn't pass.
|
||||
# Disable specific -Werror flags that break with modern GCC:
|
||||
# address-of-packed-member: disk/part_efi.c (packed struct pointer)
|
||||
# enum-int-mismatch: common/command.c (enum command_ret_t vs int)
|
||||
# maybe-uninitialized: common/edid.c (hdmi_len variable)
|
||||
GCC_COMPAT_FLAGS="-Wno-error=address-of-packed-member -Wno-error=enum-int-mismatch -Wno-error=maybe-uninitialized"
|
||||
|
||||
make CROSS_COMPILE=$CROSS_COMPILE odroidgoa_defconfig
|
||||
make CROSS_COMPILE=$CROSS_COMPILE KCFLAGS="$GCC_COMPAT_FLAGS" -j$(nproc)
|
||||
|
||||
log " Built"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Step 2: Build U-Boot
|
||||
# Step 4: Copy artifacts
|
||||
#------------------------------------------------------------------------------
|
||||
log ""
|
||||
log "Step 2: Building U-Boot..."
|
||||
|
||||
# Set up cross-compiler (use Linaro if available, otherwise system)
|
||||
if [ -d "/opt/toolchains/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu" ]; then
|
||||
export PATH=/opt/toolchains/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin:$PATH
|
||||
fi
|
||||
|
||||
export ARCH=arm64
|
||||
export CROSS_COMPILE=aarch64-linux-gnu-
|
||||
|
||||
# Build for OdroidGoA (compatible with R36S/RG351MP)
|
||||
./make.sh odroidgoa 2>&1 | tail -20 || true
|
||||
|
||||
log " ✓ U-Boot built"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Step 3: Copy artifacts
|
||||
#------------------------------------------------------------------------------
|
||||
log ""
|
||||
log "Step 3: Copying bootloader files..."
|
||||
log "Step 4: Copying bootloader files..."
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Copy the bootloader images
|
||||
if [ -d "sd_fuse" ]; then
|
||||
if [ -d "sd_fuse" ] && [ -f "sd_fuse/idbloader.img" ]; then
|
||||
cp sd_fuse/idbloader.img "$OUTPUT_DIR/"
|
||||
cp sd_fuse/uboot.img "$OUTPUT_DIR/"
|
||||
cp sd_fuse/trust.img "$OUTPUT_DIR/"
|
||||
log " ✓ Bootloader files copied"
|
||||
log " Bootloader binaries copied"
|
||||
else
|
||||
error "sd_fuse directory not found - U-Boot build may have failed"
|
||||
error "sd_fuse directory not found — U-Boot build may have failed"
|
||||
fi
|
||||
|
||||
# Copy R36S U-Boot DTBs
|
||||
for dtb in r36s-uboot.dtb r36s-clone-uboot.dtb; do
|
||||
if [ -f "arch/arm/dts/$dtb" ]; then
|
||||
cp "arch/arm/dts/$dtb" "$OUTPUT_DIR/"
|
||||
log " DTB copied: $dtb"
|
||||
else
|
||||
warn "DTB not found: $dtb"
|
||||
fi
|
||||
done
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Summary
|
||||
#------------------------------------------------------------------------------
|
||||
log ""
|
||||
log "=== U-Boot Build Complete ==="
|
||||
log ""
|
||||
log "Bootloader files:"
|
||||
ls -la "$OUTPUT_DIR/"
|
||||
log "Bootloader: $OUTPUT_DIR/"
|
||||
ls -lh "$OUTPUT_DIR/"
|
||||
log ""
|
||||
log "These will be automatically installed by build-image.sh"
|
||||
log ""
|
||||
log "✓ U-Boot ready for R36S!"
|
||||
log "These will be installed by build-image.sh"
|
||||
|
||||
+57
-16
@@ -1,24 +1,65 @@
|
||||
ODROIDGOA-UBOOT-CONFIG
|
||||
odroidgoa-uboot-config
|
||||
|
||||
########################################################################
|
||||
# Arch R Boot Configuration for R36S — Kernel 6.6 + Systemd
|
||||
# Default panel: Panel 4-V22 (hardcoded in base DTB, no overlay needed)
|
||||
# Primary boot: extlinux/extlinux.conf (U-Boot tries it first)
|
||||
# This boot.ini is a fallback if extlinux is missing.
|
||||
# Arch R Boot Configuration
|
||||
#
|
||||
# Auto-detects SD card MMC device:
|
||||
# Original R36S: eMMC=mmc0, SD=mmc1
|
||||
# Clone (no eMMC): SD=mmc0
|
||||
#
|
||||
# Root device (__ROOTDEV__) substituted by build-image.sh per variant.
|
||||
# Panel config from panel.txt (managed by panel-detect.py).
|
||||
# Panel reset handled in Linux (panel-detect.py) -- no GPIO check here
|
||||
# because X button GPIO differs between boards (modular!).
|
||||
########################################################################
|
||||
|
||||
# Memory addresses — kernel at 0x02008000 (matches working R36S firmware)
|
||||
setenv dtb_loadaddr "0x01f00000"
|
||||
setenv loadaddr "0x02008000"
|
||||
# --- Turn on LED immediately (feedback: device is booting) ---
|
||||
gpio set b5
|
||||
|
||||
# Load kernel Image
|
||||
if load mmc 1:1 ${loadaddr} Image
|
||||
then
|
||||
# Load DTB
|
||||
if load mmc 1:1 ${dtb_loadaddr} rk3326-gameconsole-r36s.dtb
|
||||
then
|
||||
# Boot without initrd — use device path directly
|
||||
setenv bootargs "root=/dev/mmcblk1p2 rootwait rw console=ttyFIQ0 loglevel=0 quiet vt.global_cursor_default=0 consoleblank=0 printk.devkmsg=off fsck.mode=skip"
|
||||
setenv dtbo_loadaddr "0x01e00000"
|
||||
setenv dtb_loadaddr "0x01f00000"
|
||||
setenv loadaddr "0x02000000"
|
||||
|
||||
# --- Auto-detect MMC device (loads kernel Image into memory) ---
|
||||
setenv mmcdev 1
|
||||
if load mmc 1:1 ${loadaddr} Image; then
|
||||
setenv mmcdev 1
|
||||
elif load mmc 0:1 ${loadaddr} Image; then
|
||||
setenv mmcdev 0
|
||||
else
|
||||
sleep 15
|
||||
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}
|
||||
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
|
||||
|
||||
setenv bootargs "root=__ROOTDEV__ rootwait rw console=ttyFIQ0 loglevel=0 quiet vt.global_cursor_default=0 consoleblank=0 printk.devkmsg=off fsck.mode=skip"
|
||||
|
||||
# Load initramfs (splash appears immediately after kernel boot, before systemd)
|
||||
setenv initrd_loadaddr "0x04000000"
|
||||
if load mmc ${mmcdev}:1 ${initrd_loadaddr} initramfs.img; then
|
||||
booti ${loadaddr} ${initrd_loadaddr}:${filesize} ${dtb_loadaddr}
|
||||
else
|
||||
booti ${loadaddr} - ${dtb_loadaddr}
|
||||
fi
|
||||
fi
|
||||
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
#==============================================================================
|
||||
# Arch R - Flash Mainline U-Boot to Clone SD Card
|
||||
#==============================================================================
|
||||
# Flashes mainline U-Boot binaries to an existing Arch R SD card for clones.
|
||||
# Does NOT touch partitions or rootfs — only U-Boot area.
|
||||
#
|
||||
# Also creates a minimal boot.scr for mainline U-Boot (distro boot).
|
||||
#
|
||||
# Usage:
|
||||
# ./flash-uboot-clone.sh /dev/sdX
|
||||
#==============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[FLASH-CLONE]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[FLASH-CLONE] WARNING:${NC} $1"; }
|
||||
error() { echo -e "${RED}[FLASH-CLONE] ERROR:${NC} $1"; exit 1; }
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Parse arguments
|
||||
#------------------------------------------------------------------------------
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 /dev/sdX"
|
||||
echo ""
|
||||
echo "Available devices:"
|
||||
lsblk -d -o NAME,SIZE,MODEL,TRAN | grep -E "sd|mmcblk" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEVICE="$1"
|
||||
|
||||
if [ ! -b "$DEVICE" ]; then
|
||||
error "Device not found: $DEVICE"
|
||||
fi
|
||||
|
||||
# Safety check
|
||||
if mount | grep -q "^${DEVICE}"; then
|
||||
warn "Device $DEVICE has mounted partitions:"
|
||||
mount | grep "^${DEVICE}" || true
|
||||
echo ""
|
||||
read -p "Continue anyway? (y/N) " -n 1 -r
|
||||
echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Verify binaries
|
||||
#------------------------------------------------------------------------------
|
||||
BUILD_DIR="$SCRIPT_DIR/bootloader/u-boot-clone-build"
|
||||
|
||||
for f in "$BUILD_DIR/idbloader.img" "$BUILD_DIR/uboot.img" "$BUILD_DIR/trust.img"; do
|
||||
[ -f "$f" ] || error "Missing: $f (run build-uboot-clone.sh first)"
|
||||
done
|
||||
|
||||
log "Device: $DEVICE"
|
||||
log "Binaries: $BUILD_DIR"
|
||||
log ""
|
||||
log "Will flash:"
|
||||
log " idbloader.img → sector 64 ($(du -h "$BUILD_DIR/idbloader.img" | cut -f1))"
|
||||
log " uboot.img → sector 16384 ($(du -h "$BUILD_DIR/uboot.img" | cut -f1))"
|
||||
log " trust.img → sector 24576 ($(du -h "$BUILD_DIR/trust.img" | cut -f1))"
|
||||
log ""
|
||||
|
||||
read -p "Flash to $DEVICE? (y/N) " -n 1 -r
|
||||
echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Flash U-Boot binaries (raw sectors)
|
||||
#------------------------------------------------------------------------------
|
||||
log "Flashing idbloader.img..."
|
||||
pkexec dd if="$BUILD_DIR/idbloader.img" of="$DEVICE" bs=512 seek=64 conv=sync,noerror,notrunc 2>/dev/null
|
||||
|
||||
log "Flashing uboot.img..."
|
||||
pkexec dd if="$BUILD_DIR/uboot.img" of="$DEVICE" bs=512 seek=16384 conv=sync,noerror,notrunc 2>/dev/null
|
||||
|
||||
log "Flashing trust.img..."
|
||||
pkexec dd if="$BUILD_DIR/trust.img" of="$DEVICE" bs=512 seek=24576 conv=sync,noerror,notrunc 2>/dev/null
|
||||
|
||||
log "U-Boot binaries flashed"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Create boot.scr on BOOT partition
|
||||
#------------------------------------------------------------------------------
|
||||
BOOT_PART="${DEVICE}1"
|
||||
if [ ! -b "$BOOT_PART" ]; then
|
||||
BOOT_PART="${DEVICE}p1"
|
||||
fi
|
||||
|
||||
if [ ! -b "$BOOT_PART" ]; then
|
||||
warn "BOOT partition not found — boot.scr NOT created"
|
||||
else
|
||||
MOUNT_TMP=$(mktemp -d)
|
||||
pkexec mount "$BOOT_PART" "$MOUNT_TMP"
|
||||
|
||||
# Create boot.scr from boot.ini (strip BSP-specific commands)
|
||||
MKIMAGE="$SCRIPT_DIR/bootloader/u-boot-mainline/tools/mkimage"
|
||||
if [ -x "$MKIMAGE" ] && [ -f "$MOUNT_TMP/boot.ini" ]; then
|
||||
log "Creating boot.scr from boot.ini..."
|
||||
TMP_SCR=$(mktemp)
|
||||
# Strip 'odroidgoa-uboot-config' (BSP command, not in mainline)
|
||||
grep -v '^odroidgoa-uboot-config' "$MOUNT_TMP/boot.ini" > "$TMP_SCR"
|
||||
"$MKIMAGE" -T script -d "$TMP_SCR" "$MOUNT_TMP/boot.scr" 2>/dev/null && \
|
||||
log "boot.scr created" || \
|
||||
warn "boot.scr creation failed"
|
||||
rm -f "$TMP_SCR"
|
||||
fi
|
||||
|
||||
log "BOOT partition contents:"
|
||||
ls "$MOUNT_TMP/" | head -20
|
||||
|
||||
pkexec umount "$MOUNT_TMP"
|
||||
rmdir "$MOUNT_TMP"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Done
|
||||
#------------------------------------------------------------------------------
|
||||
sync
|
||||
log ""
|
||||
log "=== Mainline U-Boot flashed to $DEVICE ==="
|
||||
log ""
|
||||
log "This is MAINLINE U-Boot (not BSP). Key differences:"
|
||||
log " - Uses go2.c board detection (not hwrev.c)"
|
||||
log " - Boots via boot.scr / extlinux.conf (not boot.ini directly)"
|
||||
log " - Display: mainline DRM (may not show boot logo)"
|
||||
log ""
|
||||
log "If boot fails, restore working clone U-Boot:"
|
||||
log " pkexec dd if=bootloader/u-boot-clone-working/uboot.img of=$DEVICE bs=512 seek=16384 conv=notrunc"
|
||||
log " pkexec dd if=bootloader/u-boot-clone-working/idbloader.img of=$DEVICE bs=512 seek=64 conv=notrunc"
|
||||
log " pkexec dd if=bootloader/u-boot-clone-working/trust.img of=$DEVICE bs=512 seek=24576 conv=notrunc"
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/bin/bash
|
||||
|
||||
#==============================================================================
|
||||
# Arch R - Flash Custom U-Boot to SD Card
|
||||
#==============================================================================
|
||||
# Flashes custom U-Boot binaries + display DTBs to an existing Arch R SD card.
|
||||
# Does NOT touch partitions or rootfs — only U-Boot area + BOOT partition DTBs.
|
||||
#
|
||||
# Usage:
|
||||
# ./flash-uboot.sh /dev/sdX
|
||||
#==============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[FLASH]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[FLASH] WARNING:${NC} $1"; }
|
||||
error() { echo -e "${RED}[FLASH] ERROR:${NC} $1"; exit 1; }
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Parse arguments
|
||||
#------------------------------------------------------------------------------
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 /dev/sdX"
|
||||
echo ""
|
||||
echo "Available devices:"
|
||||
lsblk -d -o NAME,SIZE,MODEL,TRAN | grep -E "sd|mmcblk" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEVICE="$1"
|
||||
|
||||
if [ ! -b "$DEVICE" ]; then
|
||||
error "Device not found: $DEVICE"
|
||||
fi
|
||||
|
||||
# Safety: refuse whole-disk devices that look like system disks
|
||||
if mount | grep -q "^${DEVICE}"; then
|
||||
warn "Device $DEVICE has mounted partitions:"
|
||||
mount | grep "^${DEVICE}" || true
|
||||
echo ""
|
||||
read -p "Continue anyway? (y/N) " -n 1 -r
|
||||
echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Verify binaries
|
||||
#------------------------------------------------------------------------------
|
||||
SDFUSE="$SCRIPT_DIR/bootloader/u-boot-rk3326/sd_fuse"
|
||||
DTS_DIR="$SCRIPT_DIR/bootloader/u-boot-rk3326/arch/arm/dts"
|
||||
|
||||
for f in "$SDFUSE/idbloader.img" "$SDFUSE/uboot.img" "$SDFUSE/trust.img"; do
|
||||
[ -f "$f" ] || error "Missing: $f"
|
||||
done
|
||||
|
||||
for f in "$DTS_DIR/r36s-uboot.dtb" "$DTS_DIR/r36-uboot.dtb"; do
|
||||
[ -f "$f" ] || error "Missing: $f"
|
||||
done
|
||||
|
||||
log "Device: $DEVICE"
|
||||
log "Binaries: $SDFUSE"
|
||||
log ""
|
||||
log "Will flash:"
|
||||
log " idbloader.img → sector 64 ($(du -h "$SDFUSE/idbloader.img" | cut -f1))"
|
||||
log " uboot.img → sector 16384 ($(du -h "$SDFUSE/uboot.img" | cut -f1))"
|
||||
log " trust.img → sector 24576 ($(du -h "$SDFUSE/trust.img" | cut -f1))"
|
||||
log " r36s-uboot.dtb + r36-uboot.dtb → BOOT partition"
|
||||
log ""
|
||||
|
||||
read -p "Flash to $DEVICE? (y/N) " -n 1 -r
|
||||
echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Flash U-Boot binaries (raw sectors)
|
||||
#------------------------------------------------------------------------------
|
||||
log "Flashing idbloader.img..."
|
||||
pkexec dd if="$SDFUSE/idbloader.img" of="$DEVICE" bs=512 seek=64 conv=sync,noerror,notrunc 2>/dev/null
|
||||
|
||||
log "Flashing uboot.img..."
|
||||
pkexec dd if="$SDFUSE/uboot.img" of="$DEVICE" bs=512 seek=16384 conv=sync,noerror,notrunc 2>/dev/null
|
||||
|
||||
log "Flashing trust.img..."
|
||||
pkexec dd if="$SDFUSE/trust.img" of="$DEVICE" bs=512 seek=24576 conv=sync,noerror,notrunc 2>/dev/null
|
||||
|
||||
log "U-Boot binaries flashed"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Copy display DTBs to BOOT partition
|
||||
#------------------------------------------------------------------------------
|
||||
BOOT_PART="${DEVICE}1"
|
||||
if [ ! -b "$BOOT_PART" ]; then
|
||||
# Try mmcblk style (p1)
|
||||
BOOT_PART="${DEVICE}p1"
|
||||
fi
|
||||
|
||||
if [ ! -b "$BOOT_PART" ]; then
|
||||
warn "BOOT partition not found ($BOOT_PART) — display DTBs NOT copied"
|
||||
warn "Copy manually: r36s-uboot.dtb + r36-uboot.dtb to BOOT partition"
|
||||
else
|
||||
MOUNT_TMP=$(mktemp -d)
|
||||
pkexec mount "$BOOT_PART" "$MOUNT_TMP"
|
||||
|
||||
pkexec cp "$DTS_DIR/r36s-uboot.dtb" "$MOUNT_TMP/"
|
||||
pkexec cp "$DTS_DIR/r36-uboot.dtb" "$MOUNT_TMP/"
|
||||
|
||||
# Verify
|
||||
log "BOOT partition contents:"
|
||||
ls -la "$MOUNT_TMP/"*.dtb 2>/dev/null || true
|
||||
|
||||
pkexec umount "$MOUNT_TMP"
|
||||
rmdir "$MOUNT_TMP"
|
||||
|
||||
log "Display DTBs copied to BOOT partition"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Done
|
||||
#------------------------------------------------------------------------------
|
||||
sync
|
||||
log ""
|
||||
log "=== Custom U-Boot flashed to $DEVICE ==="
|
||||
log ""
|
||||
log "Test on R36S original → should detect eMMC → r36s-uboot.dtb → logo"
|
||||
log "Test on R36S clone → no eMMC → r36-uboot.dtb → logo"
|
||||
log ""
|
||||
log "If boot fails (red LED blink), restore working U-Boot:"
|
||||
log " Original: bootloader/u-boot-r36s-working/"
|
||||
log " Clone: bootloader/u-boot-clone-working/"
|
||||
File diff suppressed because it is too large
Load Diff
+26
-12
@@ -25,9 +25,12 @@ except ImportError:
|
||||
sys.exit(1)
|
||||
|
||||
# Volume step (percentage per key press)
|
||||
VOL_STEP = 2
|
||||
VOL_STEP = 5
|
||||
# Brightness step (percentage per key press)
|
||||
BRIGHT_STEP = 3
|
||||
BRIGHT_STEP = 5
|
||||
# Minimum interval between volume/brightness actions (seconds)
|
||||
# adc-keys autorepeat fires at ~30Hz — throttle to ~3 events/sec
|
||||
VOL_THROTTLE = 0.3
|
||||
# Minimum brightness percentage (prevent black screen)
|
||||
BRIGHT_MIN = 5
|
||||
# Brightness persistence file
|
||||
@@ -177,6 +180,7 @@ def brightness_up():
|
||||
def brightness_down():
|
||||
if get_brightness_pct() <= BRIGHT_MIN:
|
||||
return
|
||||
log(f"BRIGHT- brightnessctl s {BRIGHT_STEP}%-")
|
||||
run_cmd(f"brightnessctl -q s {BRIGHT_STEP}%-")
|
||||
# Clamp: if we went below minimum, set to minimum
|
||||
if get_brightness_pct() < BRIGHT_MIN:
|
||||
@@ -203,7 +207,7 @@ def find_devices():
|
||||
name = dev.name.lower()
|
||||
caps = dev.capabilities()
|
||||
|
||||
if 'gpio-keys' in name:
|
||||
if 'gpio-keys' in name or 'adc-keys' in name:
|
||||
# Distinguish vol device from gamepad by checking for KEY_VOLUMEUP
|
||||
key_caps = caps.get(ecodes.EV_KEY, [])
|
||||
if ecodes.KEY_VOLUMEUP in key_caps:
|
||||
@@ -253,6 +257,8 @@ def main():
|
||||
|
||||
# Track MODE button state for brightness hotkey combo
|
||||
mode_held = False
|
||||
# Throttle: last time a volume/brightness action was executed
|
||||
last_vol_action = 0.0
|
||||
|
||||
print("Hotkey daemon ready.")
|
||||
# Clear previous log on fresh start
|
||||
@@ -298,18 +304,26 @@ def main():
|
||||
elif val == 0:
|
||||
mode_held = False
|
||||
|
||||
# Volume keys from gpio-keys-vol (grabbed)
|
||||
# Volume keys (grabbed): accept press + repeat,
|
||||
# but throttle to max ~3 events/sec (300ms interval).
|
||||
# adc-keys autorepeat fires at ~30Hz natively.
|
||||
elif key == ecodes.KEY_VOLUMEUP and val in (1, 2):
|
||||
if mode_held:
|
||||
brightness_up()
|
||||
else:
|
||||
volume_up()
|
||||
now = time.monotonic()
|
||||
if now - last_vol_action >= VOL_THROTTLE:
|
||||
last_vol_action = now
|
||||
if mode_held:
|
||||
brightness_up()
|
||||
else:
|
||||
volume_up()
|
||||
|
||||
elif key == ecodes.KEY_VOLUMEDOWN and val in (1, 2):
|
||||
if mode_held:
|
||||
brightness_down()
|
||||
else:
|
||||
volume_down()
|
||||
now = time.monotonic()
|
||||
if now - last_vol_action >= VOL_THROTTLE:
|
||||
last_vol_action = now
|
||||
if mode_held:
|
||||
brightness_down()
|
||||
else:
|
||||
volume_down()
|
||||
|
||||
# Headphone jack switch
|
||||
elif event.type == ecodes.EV_SW:
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* archr-init — initramfs /init for Arch R
|
||||
*
|
||||
* Shows splash on /dev/fb0 IMMEDIATELY after kernel boot (before systemd).
|
||||
* Splash image is EMBEDDED in the binary (no file I/O needed).
|
||||
* Then mounts the real root filesystem and switch_root to /sbin/init.
|
||||
*
|
||||
* Initramfs contents:
|
||||
* /init → this binary (static aarch64, includes splash data)
|
||||
* /dev/ → empty (devtmpfs mounted here)
|
||||
* /proc/ → empty (proc mounted temporarily)
|
||||
* /newroot/ → empty (real root mounted here)
|
||||
*
|
||||
* Build:
|
||||
* xxd -i splash.bmp > splash_data.h
|
||||
* aarch64-linux-gnu-gcc -static -O2 -o archr-init archr-init.c
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/fb.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Embedded splash image (generated by xxd -i splash.bmp) */
|
||||
#include "splash_data.h"
|
||||
|
||||
/* ---- Logging (dmesg + in-memory buffer, flushed to file after root mount) ---- */
|
||||
|
||||
static int kmsg_fd = -1;
|
||||
#define LOG_BUF_SIZE 4096
|
||||
static char log_buf[LOG_BUF_SIZE];
|
||||
static int log_pos = 0;
|
||||
|
||||
/* Simple snprintf — avoid pulling in full stdio */
|
||||
static int fmt_int(char *buf, int val)
|
||||
{
|
||||
if (val < 0) { *buf++ = '-'; val = -val; return 1 + fmt_int(buf, val); }
|
||||
if (val >= 10) { int n = fmt_int(buf, val / 10); return n + fmt_int(buf + n, val % 10); }
|
||||
*buf = '0' + val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void klog(const char *msg)
|
||||
{
|
||||
/* Save msg pointer before we iterate */
|
||||
const char *saved_msg = msg;
|
||||
|
||||
/* Write to dmesg */
|
||||
if (kmsg_fd < 0)
|
||||
kmsg_fd = open("/dev/kmsg", O_WRONLY);
|
||||
if (kmsg_fd >= 0) {
|
||||
char buf[256];
|
||||
int i = 0;
|
||||
const char *prefix = "archr-init: ";
|
||||
while (*prefix) buf[i++] = *prefix++;
|
||||
while (*msg && i < 250) buf[i++] = *msg++;
|
||||
buf[i++] = '\n';
|
||||
write(kmsg_fd, buf, i);
|
||||
}
|
||||
|
||||
/* Buffer for file log (using saved pointer) */
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
if (log_pos < LOG_BUF_SIZE - 200) {
|
||||
log_pos += fmt_int(log_buf + log_pos, (int)ts.tv_sec);
|
||||
log_buf[log_pos++] = '.';
|
||||
int ms = (int)(ts.tv_nsec / 1000000);
|
||||
if (ms < 100) log_buf[log_pos++] = '0';
|
||||
if (ms < 10) log_buf[log_pos++] = '0';
|
||||
log_pos += fmt_int(log_buf + log_pos, ms);
|
||||
log_buf[log_pos++] = ' ';
|
||||
while (*saved_msg && log_pos < LOG_BUF_SIZE - 2)
|
||||
log_buf[log_pos++] = *saved_msg++;
|
||||
log_buf[log_pos++] = '\n';
|
||||
}
|
||||
}
|
||||
|
||||
static void klog_num(const char *prefix, int val)
|
||||
{
|
||||
char buf[128];
|
||||
int i = 0;
|
||||
while (*prefix && i < 100) buf[i++] = *prefix++;
|
||||
i += fmt_int(buf + i, val);
|
||||
buf[i] = 0;
|
||||
klog(buf);
|
||||
}
|
||||
|
||||
static void flush_log(const char *path)
|
||||
{
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd >= 0) {
|
||||
write(fd, log_buf, log_pos);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- BMP parsing (from embedded data) ---- */
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint16_t type;
|
||||
uint32_t size;
|
||||
uint16_t reserved1;
|
||||
uint16_t reserved2;
|
||||
uint32_t offset;
|
||||
} BMPFileHeader;
|
||||
|
||||
typedef struct {
|
||||
uint32_t size;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint16_t planes;
|
||||
uint16_t bpp;
|
||||
uint32_t compression;
|
||||
uint32_t image_size;
|
||||
int32_t x_ppm;
|
||||
int32_t y_ppm;
|
||||
uint32_t colors_used;
|
||||
uint32_t colors_important;
|
||||
} BMPInfoHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
/* ---- Splash display (writes embedded BMP to /dev/fb0) ---- */
|
||||
|
||||
static void show_splash(void)
|
||||
{
|
||||
/* Validate embedded BMP data */
|
||||
/* splash_data.h defines: unsigned char splash_bmp[] and unsigned int splash_bmp_len */
|
||||
extern unsigned char splash_bmp[];
|
||||
extern unsigned int splash_bmp_len;
|
||||
|
||||
if (splash_bmp_len < sizeof(BMPFileHeader) + sizeof(BMPInfoHeader)) {
|
||||
klog("splash: embedded data too small");
|
||||
return;
|
||||
}
|
||||
|
||||
BMPFileHeader *fh = (BMPFileHeader *)splash_bmp;
|
||||
BMPInfoHeader *ih = (BMPInfoHeader *)(splash_bmp + sizeof(BMPFileHeader));
|
||||
|
||||
if (fh->type != 0x4D42 || (ih->bpp != 24 && ih->bpp != 32)) {
|
||||
klog("splash: invalid BMP header");
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t bmp_w = ih->width;
|
||||
int32_t bmp_h = ih->height > 0 ? ih->height : -ih->height;
|
||||
int bottom_up = ih->height > 0;
|
||||
uint32_t src_bpp = ih->bpp / 8;
|
||||
uint32_t src_row_padded = (bmp_w * src_bpp + 3) & ~3;
|
||||
uint8_t *pixel_data = splash_bmp + fh->offset;
|
||||
|
||||
klog("splash: BMP parsed from embedded data");
|
||||
|
||||
/* Wait for fb0 (DRM probe might still be finishing) */
|
||||
int fb_fd = -1;
|
||||
int retries;
|
||||
for (retries = 0; retries < 30; retries++) {
|
||||
fb_fd = open("/dev/fb0", O_RDWR);
|
||||
if (fb_fd >= 0) break;
|
||||
usleep(100000); /* 100ms, max 3s */
|
||||
}
|
||||
if (fb_fd < 0) {
|
||||
klog("splash: fb0 NOT FOUND after 3s");
|
||||
return;
|
||||
}
|
||||
klog_num("splash: fb0 opened, retries=", retries);
|
||||
|
||||
struct fb_var_screeninfo vinfo;
|
||||
struct fb_fix_screeninfo finfo;
|
||||
|
||||
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) < 0 ||
|
||||
ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo) < 0) {
|
||||
klog("splash: ioctl failed");
|
||||
close(fb_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t fb_w = vinfo.xres;
|
||||
uint32_t fb_h = vinfo.yres;
|
||||
uint32_t fb_bpp_bytes = vinfo.bits_per_pixel / 8;
|
||||
uint32_t stride = finfo.line_length;
|
||||
uint32_t fb_size = stride * fb_h;
|
||||
|
||||
klog_num("splash: fb width=", (int)fb_w);
|
||||
klog_num("splash: fb height=", (int)fb_h);
|
||||
klog_num("splash: fb bpp=", (int)vinfo.bits_per_pixel);
|
||||
|
||||
/* Write directly to fb0 — row by row to avoid large malloc */
|
||||
/* First: clear the framebuffer (black) */
|
||||
{
|
||||
uint8_t zero[2560]; /* enough for 640px * 4bpp */
|
||||
memset(zero, 0, sizeof(zero));
|
||||
lseek(fb_fd, 0, SEEK_SET);
|
||||
for (uint32_t y = 0; y < fb_h; y++)
|
||||
write(fb_fd, zero, stride < sizeof(zero) ? stride : sizeof(zero));
|
||||
}
|
||||
|
||||
/* Then: write BMP pixels */
|
||||
uint32_t copy_w = (uint32_t)bmp_w < fb_w ? (uint32_t)bmp_w : fb_w;
|
||||
uint32_t copy_h = (uint32_t)bmp_h < fb_h ? (uint32_t)bmp_h : fb_h;
|
||||
|
||||
uint8_t row_buf[2560]; /* enough for 640px * 4bpp */
|
||||
|
||||
for (uint32_t y = 0; y < copy_h; y++) {
|
||||
uint32_t src_y = bottom_up ? (bmp_h - 1 - y) : y;
|
||||
uint8_t *src_row = pixel_data + src_y * src_row_padded;
|
||||
|
||||
memset(row_buf, 0, stride < sizeof(row_buf) ? stride : sizeof(row_buf));
|
||||
|
||||
for (uint32_t x = 0; x < copy_w; x++) {
|
||||
uint8_t b = src_row[x * src_bpp + 0];
|
||||
uint8_t g = src_row[x * src_bpp + 1];
|
||||
uint8_t r = src_row[x * src_bpp + 2];
|
||||
|
||||
if (fb_bpp_bytes == 4) {
|
||||
row_buf[x * 4 + 0] = b;
|
||||
row_buf[x * 4 + 1] = g;
|
||||
row_buf[x * 4 + 2] = r;
|
||||
row_buf[x * 4 + 3] = 0xFF;
|
||||
} else if (fb_bpp_bytes == 2) {
|
||||
uint16_t c = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
|
||||
row_buf[x * 2 + 0] = c & 0xFF;
|
||||
row_buf[x * 2 + 1] = (c >> 8) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
lseek(fb_fd, y * stride, SEEK_SET);
|
||||
write(fb_fd, row_buf, stride < sizeof(row_buf) ? stride : sizeof(row_buf));
|
||||
}
|
||||
|
||||
klog("splash: written to fb0");
|
||||
close(fb_fd);
|
||||
}
|
||||
|
||||
/* ---- Main: initramfs /init ---- */
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/* 1. Mount devtmpfs on /dev (kernel creates device nodes here) */
|
||||
mkdir("/dev", 0755);
|
||||
mount("devtmpfs", "/dev", "devtmpfs", 0, NULL);
|
||||
|
||||
klog("=== INITRAMFS STARTED ===");
|
||||
|
||||
/* 2. Show splash from embedded data — no file I/O needed */
|
||||
show_splash();
|
||||
|
||||
/* 3. Parse root= and rw from kernel cmdline */
|
||||
mkdir("/proc", 0755);
|
||||
mount("proc", "/proc", "proc", 0, NULL);
|
||||
|
||||
char cmdline[4096];
|
||||
memset(cmdline, 0, sizeof(cmdline));
|
||||
int fd = open("/proc/cmdline", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
read(fd, cmdline, sizeof(cmdline) - 1);
|
||||
close(fd);
|
||||
}
|
||||
umount("/proc");
|
||||
|
||||
/* Extract root= device path */
|
||||
char rootdev[256] = "/dev/mmcblk1p2"; /* default: original R36S */
|
||||
char *p = strstr(cmdline, "root=");
|
||||
if (p) {
|
||||
p += 5;
|
||||
int i = 0;
|
||||
while (*p && *p != ' ' && i < 255)
|
||||
rootdev[i++] = *p++;
|
||||
rootdev[i] = 0;
|
||||
}
|
||||
|
||||
/* Check rw flag */
|
||||
unsigned long mflags = MS_NOATIME;
|
||||
if (strstr(cmdline, " rw"))
|
||||
mflags |= 0; /* rw is default without MS_RDONLY */
|
||||
else
|
||||
mflags |= MS_RDONLY;
|
||||
|
||||
klog(rootdev);
|
||||
|
||||
/* 4. Mount real root (retry — MMC might still be probing) */
|
||||
mkdir("/newroot", 0755);
|
||||
int mounted = 0;
|
||||
int mount_retries;
|
||||
for (mount_retries = 0; mount_retries < 50; mount_retries++) {
|
||||
if (mount(rootdev, "/newroot", "ext4", mflags, NULL) == 0) {
|
||||
mounted = 1;
|
||||
break;
|
||||
}
|
||||
usleep(100000); /* 100ms, max 5s total */
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
klog("FATAL: could not mount root!");
|
||||
for (;;) sleep(60);
|
||||
}
|
||||
|
||||
klog_num("root mounted, retries=", mount_retries);
|
||||
|
||||
/* 5. Write diagnostic log to root filesystem */
|
||||
klog("switch_root");
|
||||
flush_log("/newroot/var/log/archr-init.log");
|
||||
|
||||
/* 6. Move /dev to new root */
|
||||
mount("/dev", "/newroot/dev", NULL, MS_MOVE, NULL);
|
||||
|
||||
/* 7. switch_root: pivot to real rootfs and exec systemd */
|
||||
if (kmsg_fd >= 0) close(kmsg_fd);
|
||||
|
||||
chdir("/newroot");
|
||||
mount(".", "/", NULL, MS_MOVE, NULL);
|
||||
chroot(".");
|
||||
chdir("/");
|
||||
|
||||
execl("/sbin/init", "/sbin/init", NULL);
|
||||
|
||||
/* Should never reach here */
|
||||
return 1;
|
||||
}
|
||||
@@ -50,8 +50,8 @@ _bt "after_amixer"
|
||||
|
||||
# === BACKGROUND: Everything else runs parallel with ES SDL init (~2s window) ===
|
||||
(
|
||||
# Framebuffer blank (hides login text, ES takes over via KMSDRM anyway)
|
||||
dd if=/dev/zero of=/dev/fb0 bs=614400 count=1 2>/dev/null
|
||||
# NOTE: fb0 blank removed — archr-splash.service writes splash to fb0,
|
||||
# which persists until ES takes DRM master via KMSDRM.
|
||||
|
||||
# Permissions + governors (single sudo)
|
||||
sudo sh -c '/usr/local/bin/perfmax; chmod 666 /dev/tty1 /dev/dri/* /sys/class/backlight/backlight/brightness' 2>/dev/null
|
||||
|
||||
+83
-44
@@ -11,12 +11,17 @@
|
||||
#==============================================================================
|
||||
|
||||
FIRST_BOOT_FLAG="/var/lib/archr/.first-boot-done"
|
||||
LOG_FILE="/var/log/first-boot.log"
|
||||
|
||||
# Log all output for debugging
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
if [ -f "$FIRST_BOOT_FLAG" ]; then
|
||||
echo "First boot already completed, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "=== Arch R First Boot Setup ==="
|
||||
echo "=== Arch R First Boot Setup === ($(date))"
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Detect SD card device
|
||||
@@ -32,29 +37,49 @@ echo "SD card: $ROOT_DISK"
|
||||
# Create ROMS partition (partition 3) if it doesn't exist
|
||||
#------------------------------------------------------------------------------
|
||||
ROMS_PART="${ROOT_DISK}p3"
|
||||
ROMS_OK=false
|
||||
|
||||
if ! lsblk "$ROMS_PART" &>/dev/null; then
|
||||
echo "Creating ROMS partition..."
|
||||
|
||||
# Get the end of the last partition (in sectors)
|
||||
LAST_END=$(sfdisk -l "$ROOT_DISK" 2>/dev/null | awk '/^\/dev/ {end=$3} END {print end+1}')
|
||||
DISK_SECTORS=$(sfdisk -l "$ROOT_DISK" 2>/dev/null | awk '/sectors$/ {print $7; exit}')
|
||||
# Parse partition info using sfdisk dump format (reliable, no column alignment issues)
|
||||
LAST_INFO=$(sfdisk -d "$ROOT_DISK" 2>/dev/null | grep '^/dev' | tail -1)
|
||||
PART_START=$(echo "$LAST_INFO" | grep -o 'start=[[:space:]]*[0-9]*' | grep -o '[0-9]*')
|
||||
PART_SIZE=$(echo "$LAST_INFO" | grep -o 'size=[[:space:]]*[0-9]*' | grep -o '[0-9]*')
|
||||
|
||||
if [ -n "$LAST_END" ] && [ -n "$DISK_SECTORS" ] && [ "$LAST_END" -lt "$DISK_SECTORS" ]; then
|
||||
echo "${LAST_END},+,0c" | sfdisk --append "$ROOT_DISK" 2>/dev/null || \
|
||||
echo ",,0c" | sfdisk --append "$ROOT_DISK" 2>/dev/null || true
|
||||
if [ -n "$PART_START" ] && [ -n "$PART_SIZE" ]; then
|
||||
LAST_END=$((PART_START + PART_SIZE))
|
||||
DISK_SECTORS=$(blockdev --getsz "$ROOT_DISK" 2>/dev/null)
|
||||
|
||||
echo " Last partition ends at sector $LAST_END, disk has $DISK_SECTORS sectors"
|
||||
|
||||
if [ -n "$DISK_SECTORS" ] && [ "$LAST_END" -lt "$DISK_SECTORS" ]; then
|
||||
echo "${LAST_END},+,0c" | sfdisk --force --append "$ROOT_DISK" 2>&1 || \
|
||||
echo ",,0c" | sfdisk --force --append "$ROOT_DISK" 2>&1 || \
|
||||
echo " ERROR: sfdisk --append failed"
|
||||
else
|
||||
echo " ERROR: No free space on disk (last=$LAST_END, total=$DISK_SECTORS)"
|
||||
fi
|
||||
else
|
||||
echo ",,0c" | sfdisk --append "$ROOT_DISK" 2>/dev/null || true
|
||||
echo " WARNING: Could not parse partition table, trying generic append"
|
||||
echo ",,0c" | sfdisk --force --append "$ROOT_DISK" 2>&1 || \
|
||||
echo " ERROR: sfdisk --append failed"
|
||||
fi
|
||||
|
||||
partprobe "$ROOT_DISK"
|
||||
sleep 3
|
||||
# Tell kernel about the new partition (try multiple methods)
|
||||
partprobe "$ROOT_DISK" 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
# Retry partprobe if device not yet visible
|
||||
if ! lsblk "$ROMS_PART" &>/dev/null; then
|
||||
# partprobe failed — try partx (more reliable for adding single partitions)
|
||||
partx -a "$ROOT_DISK" 2>/dev/null
|
||||
sleep 2
|
||||
partprobe "$ROOT_DISK"
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
if ! lsblk "$ROMS_PART" &>/dev/null; then
|
||||
# Last resort: blockdev re-read
|
||||
blockdev --rereadpt "$ROOT_DISK" 2>/dev/null
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
if lsblk "$ROMS_PART" &>/dev/null; then
|
||||
@@ -62,15 +87,18 @@ if ! lsblk "$ROMS_PART" &>/dev/null; then
|
||||
mkfs.vfat -F 32 -n ROMS "$ROMS_PART"
|
||||
echo " ROMS partition created!"
|
||||
else
|
||||
echo " WARNING: Failed to create ROMS partition"
|
||||
echo " You can create it manually: echo ',,0c' | sudo sfdisk --append $ROOT_DISK"
|
||||
echo " WARNING: Partition created in table but kernel can't see it yet"
|
||||
echo " ROMS will be available after reboot"
|
||||
fi
|
||||
else
|
||||
echo " ROMS partition already exists"
|
||||
# Ensure it has a filesystem
|
||||
if ! blkid "$ROMS_PART" | grep -q vfat; then
|
||||
# Ensure it has a FAT32 filesystem with the ROMS label
|
||||
if ! blkid "$ROMS_PART" | grep -q 'TYPE="vfat"'; then
|
||||
echo " Formatting existing partition as FAT32..."
|
||||
mkfs.vfat -F 32 -n ROMS "$ROMS_PART"
|
||||
elif ! blkid "$ROMS_PART" | grep -q 'LABEL="ROMS"'; then
|
||||
echo " Setting label to ROMS..."
|
||||
fatlabel "$ROMS_PART" ROMS 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -82,30 +110,39 @@ echo "Setting up ROM directories..."
|
||||
mkdir -p /roms
|
||||
|
||||
if lsblk "$ROMS_PART" &>/dev/null; then
|
||||
mount "$ROMS_PART" /roms 2>/dev/null || true
|
||||
if ! mountpoint -q /roms; then
|
||||
mount "$ROMS_PART" /roms 2>&1 || echo " WARNING: Could not mount $ROMS_PART"
|
||||
fi
|
||||
fi
|
||||
|
||||
SYSTEMS=(
|
||||
"nes" "snes" "gb" "gbc" "gba" "nds"
|
||||
"megadrive" "mastersystem" "gamegear" "genesis" "segacd" "sega32x"
|
||||
"n64" "psx" "psp"
|
||||
"dreamcast" "saturn"
|
||||
"arcade" "mame" "fbneo" "neogeo"
|
||||
"atari2600" "atari7800" "atarilynx"
|
||||
"pcengine" "pcenginecd" "supergrafx"
|
||||
"wonderswan" "wonderswancolor"
|
||||
"ngp" "ngpc"
|
||||
"virtualboy"
|
||||
"scummvm" "dos"
|
||||
"ports"
|
||||
"bios"
|
||||
)
|
||||
if mountpoint -q /roms; then
|
||||
ROMS_OK=true
|
||||
|
||||
for sys in "${SYSTEMS[@]}"; do
|
||||
mkdir -p "/roms/$sys"
|
||||
done
|
||||
SYSTEMS=(
|
||||
"nes" "snes" "gb" "gbc" "gba" "nds"
|
||||
"megadrive" "mastersystem" "gamegear" "genesis" "segacd" "sega32x"
|
||||
"n64" "psx" "psp"
|
||||
"dreamcast" "saturn"
|
||||
"arcade" "mame" "fbneo" "neogeo"
|
||||
"atari2600" "atari7800" "atarilynx"
|
||||
"pcengine" "pcenginecd" "supergrafx"
|
||||
"wonderswan" "wonderswancolor"
|
||||
"ngp" "ngpc"
|
||||
"virtualboy"
|
||||
"scummvm" "dos"
|
||||
"ports"
|
||||
"bios"
|
||||
)
|
||||
|
||||
echo " ROM directories created"
|
||||
for sys in "${SYSTEMS[@]}"; do
|
||||
mkdir -p "/roms/$sys"
|
||||
done
|
||||
|
||||
mkdir -p /roms/saves /roms/states
|
||||
echo " ROM directories created"
|
||||
else
|
||||
echo " WARNING: /roms not mounted — directories will be created on next boot"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Generate SSH host keys
|
||||
@@ -141,10 +178,9 @@ if [ ! -f "$RA_DIR/retroarch.cfg" ] && [ -f /etc/archr/retroarch.cfg ]; then
|
||||
cp /etc/archr/retroarch.cfg "$RA_DIR/retroarch.cfg"
|
||||
fi
|
||||
|
||||
# Set savefile/savestate directories to per-system on ROMS partition
|
||||
# Set savefile/savestate directories to ROMS partition
|
||||
sed -i "s|^savefile_directory =.*|savefile_directory = \"/roms/saves\"|" "$RA_DIR/retroarch.cfg" 2>/dev/null || true
|
||||
sed -i "s|^savestate_directory =.*|savestate_directory = \"/roms/states\"|" "$RA_DIR/retroarch.cfg" 2>/dev/null || true
|
||||
mkdir -p /roms/saves /roms/states
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Configure EmulationStation
|
||||
@@ -162,9 +198,12 @@ fi
|
||||
chown -R archr:archr /home/archr
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Mark first boot complete
|
||||
# Mark first boot complete (only if ROMS partition is working)
|
||||
#------------------------------------------------------------------------------
|
||||
mkdir -p "$(dirname "$FIRST_BOOT_FLAG")"
|
||||
touch "$FIRST_BOOT_FLAG"
|
||||
|
||||
echo "=== First Boot Setup Complete ==="
|
||||
if [ "$ROMS_OK" = true ]; then
|
||||
mkdir -p "$(dirname "$FIRST_BOOT_FLAG")"
|
||||
touch "$FIRST_BOOT_FLAG"
|
||||
echo "=== First Boot Setup Complete ==="
|
||||
else
|
||||
echo "=== First Boot Setup Partial — ROMS partition will retry on next boot ==="
|
||||
fi
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# - R36S clones (Clone Panel 1-10, R36 Max, RX6S) from compiled DTBs
|
||||
# in kernel/dts/R36S-Clones-DTB/
|
||||
#
|
||||
# These DTBOs are applied by U-Boot via PanCho panel selection system.
|
||||
# These DTBOs are applied by U-Boot via boot.ini (panel auto-detect system).
|
||||
# Base DTB has Panel 4-V22 hardcoded as default (works without overlay).
|
||||
#==============================================================================
|
||||
|
||||
@@ -475,7 +475,7 @@ for panel_name in "${CLONE_ORDER[@]}"; do
|
||||
done
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Create ScreenFiles directory structure (for PanCho compatibility)
|
||||
# Create ScreenFiles directory structure (for boot.ini panel auto-detect)
|
||||
#------------------------------------------------------------------------------
|
||||
log ""
|
||||
log "Creating ScreenFiles directory structure..."
|
||||
|
||||
Executable
+453
@@ -0,0 +1,453 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
Arch R - Panel Detection Wizard
|
||||
|
||||
Runs on first boot (or after X-button reset) to let the user select
|
||||
their display panel. Provides audio feedback (beeps) for blind selection
|
||||
and visual feedback on tty1 for users who already have the correct panel.
|
||||
|
||||
Flow:
|
||||
1. Check /boot/panel-confirmed — if valid (>1 byte), exit immediately
|
||||
2. Read variant from /etc/archr/variant (original or clone)
|
||||
3. Initialize audio (speaker, 60% volume)
|
||||
4. Cycle through panels (most common first):
|
||||
- Show panel name on tty1
|
||||
- Play N beeps (N = position in list, capped at 5)
|
||||
- Wait 15s for input: A=confirm, B/DPAD_DOWN=next
|
||||
5. On confirm: write panel.txt + panel-confirmed → sync → reboot
|
||||
6. After 2 full cycles without confirm: auto-confirm default
|
||||
|
||||
Panel selection is persistent:
|
||||
- panel.txt: U-Boot reads PanelDTBO variable (overlay path)
|
||||
- panel-confirmed: marker file (>1 byte = confirmed, ≤1 byte = reset)
|
||||
- Hold X during boot to reset (U-Boot overwrites panel-confirmed with 1 byte)
|
||||
"""
|
||||
|
||||
import math
|
||||
import os
|
||||
import select
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import evdev
|
||||
from evdev import ecodes
|
||||
except ImportError:
|
||||
print("ERROR: python-evdev not installed")
|
||||
sys.exit(1)
|
||||
|
||||
# --- Paths ---
|
||||
BOOT_DIR = Path("/boot")
|
||||
PANEL_TXT = BOOT_DIR / "panel.txt"
|
||||
PANEL_CONFIRMED = BOOT_DIR / "panel-confirmed"
|
||||
VARIANT_FILE = Path("/etc/archr/variant")
|
||||
|
||||
# --- Buttons (from rk3326-odroid-go.dtsi gpio-keys) ---
|
||||
BTN_A = ecodes.BTN_EAST # 305 — A button (confirm)
|
||||
BTN_B = ecodes.BTN_SOUTH # 304 — B button (next)
|
||||
BTN_X = ecodes.BTN_NORTH # 307 — X button (reset panel selection)
|
||||
BTN_DOWN = ecodes.BTN_DPAD_DOWN # 545 — D-pad down (next, alternative)
|
||||
|
||||
# --- Audio ---
|
||||
BEEP_FREQ = 880 # Hz
|
||||
BEEP_DURATION = 0.12 # seconds
|
||||
BEEP_GAP = 0.15 # seconds between beeps
|
||||
SAMPLE_RATE = 44100
|
||||
|
||||
# --- Timing ---
|
||||
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)
|
||||
# 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"),
|
||||
]
|
||||
|
||||
# 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)"),
|
||||
]
|
||||
|
||||
|
||||
def get_variant():
|
||||
"""Read variant from /etc/archr/variant (written by build-image.sh)."""
|
||||
try:
|
||||
return VARIANT_FILE.read_text().strip()
|
||||
except FileNotFoundError:
|
||||
# Fallback: detect via eMMC presence
|
||||
if os.path.exists('/sys/block/mmcblk1'):
|
||||
return "original"
|
||||
return "clone"
|
||||
|
||||
|
||||
def get_panels(variant):
|
||||
"""Return panel list for this variant."""
|
||||
if variant == "clone":
|
||||
return PANELS_CLONE
|
||||
return PANELS_ORIGINAL
|
||||
|
||||
|
||||
def is_confirmed():
|
||||
"""Check if panel selection is already confirmed."""
|
||||
if not PANEL_CONFIRMED.exists():
|
||||
return False
|
||||
return PANEL_CONFIRMED.stat().st_size > 1
|
||||
|
||||
|
||||
def generate_beep_wav(freq=BEEP_FREQ, duration=BEEP_DURATION):
|
||||
"""Generate a short sine wave beep as WAV data (16-bit mono PCM)."""
|
||||
n = int(SAMPLE_RATE * duration)
|
||||
samples = b''.join(
|
||||
struct.pack('<h', int(16384 * math.sin(2 * math.pi * freq * i / SAMPLE_RATE)))
|
||||
for i in range(n)
|
||||
)
|
||||
hdr = struct.pack(
|
||||
'<4sI4s4sIHHIIHH4sI',
|
||||
b'RIFF', 36 + len(samples), b'WAVE',
|
||||
b'fmt ', 16, 1, 1, SAMPLE_RATE, SAMPLE_RATE * 2, 2, 16,
|
||||
b'data', len(samples)
|
||||
)
|
||||
return hdr + samples
|
||||
|
||||
|
||||
def play_beeps(count, wav_data):
|
||||
"""Play N beeps with gaps between them."""
|
||||
for i in range(count):
|
||||
try:
|
||||
subprocess.run(
|
||||
["aplay", "-q", "-"],
|
||||
input=wav_data, timeout=2,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if i < count - 1:
|
||||
time.sleep(BEEP_GAP)
|
||||
|
||||
|
||||
def play_confirm_sound(wav_data):
|
||||
"""Play 3 rapid high beeps for confirmation feedback."""
|
||||
for _ in range(3):
|
||||
try:
|
||||
subprocess.run(
|
||||
["aplay", "-q", "-"],
|
||||
input=wav_data, timeout=2,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.08)
|
||||
|
||||
|
||||
def write_tty(msg):
|
||||
"""Write message to tty1 (visible only if current panel is correct)."""
|
||||
try:
|
||||
with open("/dev/tty1", "w") as tty:
|
||||
tty.write("\033[2J\033[H") # clear screen
|
||||
tty.write("\033[1;37m") # bright white
|
||||
tty.write("=" * 42 + "\n")
|
||||
tty.write(" Arch R - Panel Detection\n")
|
||||
tty.write("=" * 42 + "\n\n")
|
||||
tty.write(f" {msg}\n\n")
|
||||
tty.write(" A = Confirm this panel\n")
|
||||
tty.write(" B = Try next panel\n\n")
|
||||
tty.write(" (auto-advances in 15s)\n")
|
||||
tty.write("\033[0m")
|
||||
tty.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def find_gamepad():
|
||||
"""Find gpio-keys gamepad input device."""
|
||||
for path in evdev.list_devices():
|
||||
try:
|
||||
dev = evdev.InputDevice(path)
|
||||
caps = dev.capabilities().get(ecodes.EV_KEY, [])
|
||||
if BTN_A in caps and BTN_B in caps:
|
||||
return dev
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def is_x_held(dev):
|
||||
"""Check if X button is currently held down (for panel reset).
|
||||
|
||||
Works on any board — uses evdev (kernel input), not raw GPIO.
|
||||
Original: X = GPIO1_PA7, Clone: X = GPIO3_PC2, but evdev
|
||||
abstracts this to BTN_NORTH regardless of hardware.
|
||||
"""
|
||||
try:
|
||||
active = dev.active_keys()
|
||||
return BTN_X in active
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def reset_panel():
|
||||
"""Reset panel selection (triggered by holding X during boot)."""
|
||||
log_boot("X held — resetting panel selection")
|
||||
# Truncate panel-confirmed to trigger wizard
|
||||
try:
|
||||
fd = os.open(str(PANEL_CONFIRMED), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
|
||||
os.write(fd, b'\x00')
|
||||
os.fsync(fd)
|
||||
os.close(fd)
|
||||
dir_fd = os.open(str(PANEL_CONFIRMED.parent), os.O_RDONLY)
|
||||
os.fsync(dir_fd)
|
||||
os.close(dir_fd)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def wait_for_button(dev, timeout):
|
||||
"""Wait for A or B button press. Returns 'A', 'B', or None (timeout)."""
|
||||
# Drain pending events
|
||||
while dev.read_one():
|
||||
pass
|
||||
|
||||
deadline = time.monotonic() + timeout
|
||||
while True:
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
return None
|
||||
|
||||
r, _, _ = select.select([dev], [], [], min(remaining, 0.5))
|
||||
if not r:
|
||||
continue
|
||||
|
||||
for event in dev.read():
|
||||
if event.type != ecodes.EV_KEY or event.value != 1:
|
||||
continue
|
||||
if event.code == BTN_A:
|
||||
return 'A'
|
||||
if event.code in (BTN_B, BTN_DOWN):
|
||||
return 'B'
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def fsync_write(path, data):
|
||||
"""Write data to file with explicit fsync (critical for FAT32 on SD card).
|
||||
|
||||
Path.write_text() doesn't fsync — data stays in page cache.
|
||||
If power is cut before writeback completes, the file is lost.
|
||||
"""
|
||||
fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
|
||||
os.write(fd, data.encode())
|
||||
os.fsync(fd)
|
||||
os.close(fd)
|
||||
# Also fsync the directory to persist the directory entry itself
|
||||
dir_fd = os.open(str(path.parent), os.O_RDONLY)
|
||||
os.fsync(dir_fd)
|
||||
os.close(dir_fd)
|
||||
|
||||
|
||||
def write_panel_config(panel_num, dtbo_path):
|
||||
"""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"
|
||||
else:
|
||||
content += "PanelDTBO=\n"
|
||||
fsync_write(PANEL_TXT, content)
|
||||
|
||||
|
||||
def confirm_panel():
|
||||
"""Write panel-confirmed marker (content > 1 byte = confirmed)."""
|
||||
fsync_write(PANEL_CONFIRMED, "confirmed\n")
|
||||
|
||||
|
||||
def init_audio():
|
||||
"""Initialize audio output for panel detection beeps."""
|
||||
try:
|
||||
subprocess.run(
|
||||
["amixer", "-q", "sset", "Playback Path", "SPK"],
|
||||
timeout=3, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
||||
)
|
||||
subprocess.run(
|
||||
["amixer", "-q", "sset", "DAC", "60%"],
|
||||
timeout=3, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def wait_for_boot_mount():
|
||||
"""Wait for /boot to be mounted (FAT32 partition with panel files)."""
|
||||
for _ in range(30):
|
||||
if os.path.ismount("/boot"):
|
||||
return True
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
|
||||
def log_boot(msg):
|
||||
"""Log to /boot for persistent debugging (survives tmpfs /var/log)."""
|
||||
try:
|
||||
with open("/boot/panel-detect.log", "a") as f:
|
||||
f.write(f"{time.strftime('%H:%M:%S')} {msg}\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
# Ensure /boot is mounted before checking panel-confirmed
|
||||
if not os.path.ismount("/boot"):
|
||||
wait_for_boot_mount()
|
||||
|
||||
log_boot(f"start: /boot mounted={os.path.ismount('/boot')}")
|
||||
log_boot(f" panel-confirmed exists={PANEL_CONFIRMED.exists()}")
|
||||
if PANEL_CONFIRMED.exists():
|
||||
log_boot(f" panel-confirmed size={PANEL_CONFIRMED.stat().st_size}")
|
||||
|
||||
# Find gamepad input device (needed for X-button check AND wizard)
|
||||
gamepad = None
|
||||
for _ in range(10):
|
||||
gamepad = find_gamepad()
|
||||
if gamepad:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
# Check if X is held — reset panel selection (works on any board via evdev)
|
||||
if gamepad and is_x_held(gamepad):
|
||||
reset_panel()
|
||||
log_boot("panel reset by X button")
|
||||
|
||||
# Quick exit if panel already confirmed
|
||||
if is_confirmed():
|
||||
log_boot("confirmed — exiting")
|
||||
sys.exit(0)
|
||||
|
||||
log_boot("NOT confirmed — starting wizard")
|
||||
|
||||
# Determine variant and panel list
|
||||
variant = get_variant()
|
||||
panels = get_panels(variant)
|
||||
default_panel = panels[0] # First in list is always the default
|
||||
|
||||
print(f"Arch R Panel Detection Wizard starting (variant: {variant})...")
|
||||
print(f" {len(panels)} panels available, default: {default_panel[2]}")
|
||||
|
||||
# Initialize audio
|
||||
init_audio()
|
||||
|
||||
# Generate beep WAVs
|
||||
beep = generate_beep_wav()
|
||||
intro_beep = generate_beep_wav(freq=660, duration=0.3)
|
||||
confirm_beep = generate_beep_wav(freq=1100, duration=0.1)
|
||||
|
||||
if not gamepad:
|
||||
print("WARNING: Gamepad not found — auto-confirming default panel")
|
||||
write_panel_config(default_panel[0], default_panel[1])
|
||||
confirm_panel()
|
||||
subprocess.run(["sync"])
|
||||
sys.exit(0)
|
||||
|
||||
print(f" Gamepad: {gamepad.name} ({gamepad.path})")
|
||||
|
||||
# Intro: 2 long beeps to signal wizard is running
|
||||
play_beeps(2, intro_beep)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Panel selection loop
|
||||
for cycle in range(MAX_CYCLES):
|
||||
for idx, (panel_num, dtbo_path, name) in enumerate(panels):
|
||||
# Write panel config (ready for confirm)
|
||||
write_panel_config(panel_num, dtbo_path)
|
||||
|
||||
# Visual feedback on tty1
|
||||
position = f"[{idx + 1}/{len(panels)}]"
|
||||
if cycle > 0:
|
||||
position += f" (cycle {cycle + 1})"
|
||||
write_tty(f"{position} {name}")
|
||||
|
||||
# Audio feedback: N beeps (capped at 5)
|
||||
beep_count = min(idx + 1, 5)
|
||||
play_beeps(beep_count, beep)
|
||||
|
||||
print(f" {position} {name} — waiting...")
|
||||
|
||||
# Wait for button input
|
||||
result = wait_for_button(gamepad, WAIT_PER_PANEL)
|
||||
|
||||
if result == 'A':
|
||||
print(f" CONFIRMED: {name}")
|
||||
play_confirm_sound(confirm_beep)
|
||||
confirm_panel()
|
||||
subprocess.run(["sync"])
|
||||
if dtbo_path:
|
||||
# Non-default panel: overlay applied by U-Boot on next reset
|
||||
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
|
||||
write_tty(f"Confirmed: {name}")
|
||||
print(f" Default panel — continuing boot")
|
||||
sys.exit(0)
|
||||
|
||||
elif result == 'B':
|
||||
print(f" NEXT (B pressed)")
|
||||
continue
|
||||
|
||||
else:
|
||||
print(f" TIMEOUT — advancing")
|
||||
continue
|
||||
|
||||
# After MAX_CYCLES without confirmation: auto-confirm default
|
||||
print(f" No selection made — auto-confirming default {default_panel[2]}")
|
||||
write_panel_config(default_panel[0], default_panel[1])
|
||||
write_tty(f"Auto-confirmed: {default_panel[2]}")
|
||||
play_confirm_sound(generate_beep_wav(freq=440, duration=0.2))
|
||||
confirm_panel()
|
||||
subprocess.run(["sync"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user