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 | |
|---|---|---|---|
| b6eff2e748 | |||
| 9c30a92cab | |||
| cbc39b8802 | |||
| 87719600d2 | |||
| baaeb69a19 |
@@ -88,3 +88,6 @@ bootloader/u-boot-r36s-working/extracted_dtb_*.dtb
|
||||
|
||||
# Clone U-Boot build output
|
||||
bootloader/u-boot-clone-build/
|
||||
|
||||
# Others
|
||||
feedback/
|
||||
@@ -153,7 +153,11 @@ Arch-R/
|
||||
|
||||
## Supported Panels
|
||||
|
||||
Arch R supports 18 display panels via DTBO overlays. The default image boots with **Panel 4-V22** (the most common R36S panel).
|
||||
Arch R supports 18 display panels via pre-merged DTBs. Each panel variant has its own DTB file with the correct init sequence and timings applied at build-time.
|
||||
|
||||
There are two separate images: **Original** (for genuine R36S) and **Clone** (for R36S clones like G80CA, K36, etc.). Each image has its own panel list.
|
||||
|
||||
> **Note:** The numerical panel ordering below is available starting from **beta1.2**. Earlier versions may have a different order.
|
||||
|
||||
### Panel Auto-Detection
|
||||
|
||||
@@ -161,66 +165,81 @@ On first boot, Arch R runs a **panel detection wizard** that guides you through
|
||||
|
||||
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.)
|
||||
- Each panel plays a different number of beeps (Panel 0 = 1 beep, Panel 1 = 2 beeps, 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
|
||||
### R36S Original (6 panels)
|
||||
|
||||
| # | 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 |
|
||||
Default: **Panel 4-V22** (~60% of units)
|
||||
|
||||
| Beeps | Panel | DTB | Notes |
|
||||
|-------|-------|-----|-------|
|
||||
| 1 | Panel 0 | kernel-panel0.dtb | Early R36S units |
|
||||
| 2 | Panel 1-V10 | kernel-panel1.dtb | V10 board marking |
|
||||
| 3 | Panel 2-V12 | kernel-panel2.dtb | V12 board marking |
|
||||
| 4 | Panel 3-V20 | kernel-panel3.dtb | V20 board marking |
|
||||
| 5 | **Panel 4-V22** *(default)* | kernel.dtb | Most common |
|
||||
| 6 | Panel 5-V22 Q8 | kernel-panel5.dtb | V22 Q8 variant |
|
||||
|
||||
### R36S Clone (12 panels)
|
||||
|
||||
Default: **Clone 8 ST7703 G80CA**
|
||||
|
||||
| Beeps | Panel | DTB | Notes |
|
||||
|-------|-------|-----|-------|
|
||||
| 1 | Clone 1 | kernel-clone1.dtb | ST7703 |
|
||||
| 2 | Clone 2 | kernel-clone2.dtb | ST7703 |
|
||||
| 3 | Clone 3 | kernel-clone3.dtb | NV3051D |
|
||||
| 4 | Clone 4 | kernel-clone4.dtb | NV3051D |
|
||||
| 5 | Clone 5 | kernel-clone5.dtb | ST7703 |
|
||||
| 6 | Clone 6 | kernel-clone6.dtb | NV3051D |
|
||||
| 7 | Clone 7 | kernel-clone7.dtb | JD9365DA |
|
||||
| 8 | **Clone 8** *(default)* | kernel.dtb | ST7703 (G80CA-MB) |
|
||||
| 9 | Clone 9 | kernel-clone9.dtb | NV3051D |
|
||||
| 10 | Clone 10 | kernel-clone10.dtb | ST7703 variant |
|
||||
| 11 | R36 Max | kernel-r36max.dtb | 720x720 ST7703 |
|
||||
| 12 | RX6S | kernel-rx6s.dtb | 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**
|
||||
**Step 1 -- Mount the BOOT partition on your PC**
|
||||
|
||||
```bash
|
||||
lsblk
|
||||
sudo mount /dev/sdX1 /mnt
|
||||
```
|
||||
|
||||
**Step 2 — Create panel.txt**
|
||||
**Step 2 -- Create panel.txt**
|
||||
|
||||
```bash
|
||||
# Example: Panel 3-V20
|
||||
# Example: Panel 3-V20 (original R36S)
|
||||
echo 'PanelNum=3
|
||||
PanelDTBO=ScreenFiles/Panel 3/mipi-panel.dtbo' | sudo tee /mnt/panel.txt
|
||||
PanelDTB=kernel-panel3.dtb' | 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):
|
||||
For default panels (no custom DTB needed):
|
||||
```bash
|
||||
# Original R36S: Panel 4-V22 (default)
|
||||
echo 'PanelNum=4
|
||||
PanelDTBO=' | sudo tee /mnt/panel.txt
|
||||
PanelDTB=' | sudo tee /mnt/panel.txt
|
||||
|
||||
# Clone R36S: Clone 8 G80CA (default)
|
||||
echo 'PanelNum=C8
|
||||
PanelDTB=' | sudo tee /mnt/panel.txt
|
||||
|
||||
echo 'confirmed' | sudo tee /mnt/panel-confirmed
|
||||
```
|
||||
|
||||
**Step 3 — Unmount and boot**
|
||||
**Step 3 -- Unmount and boot**
|
||||
|
||||
```bash
|
||||
sudo umount /mnt
|
||||
@@ -235,8 +254,8 @@ sync
|
||||
Power On
|
||||
→ U-Boot (idbloader → trust → uboot.img)
|
||||
→ logo.bmp displayed on screen
|
||||
→ boot.ini: load panel.txt → apply DTBO overlay (if non-default panel)
|
||||
→ Kernel 6.6.89 + rk3326-gameconsole-r36s.dtb
|
||||
→ boot.ini: load panel.txt → load pre-merged DTB (kernel-panelN.dtb)
|
||||
→ Kernel 6.6.89 + initramfs splash (0.7s)
|
||||
→ systemd → panel-detect.service (first boot only)
|
||||
→ archr-boot-setup (GPU + governors)
|
||||
→ emulationstation.service → EmulationStation UI
|
||||
|
||||
+54
-1
@@ -1915,6 +1915,59 @@ The panel wizard can only write config for the NEXT boot — no hot-swapping pan
|
||||
|
||||
---
|
||||
|
||||
### 2026-02-27 — Pre-merged Panel DTBs (beta1.1)
|
||||
|
||||
The biggest pain point since beta1 was panel selection. BSP U-Boot's `fdt apply` was silently
|
||||
corrupting DTBs whenever an overlay tried to replace a property with different-sized data — the
|
||||
init-sequence byte arrays vary per panel, so any non-default panel produced a broken DTB and a
|
||||
black screen.
|
||||
|
||||
The fix was surprisingly clean: run `fdtoverlay` at **build time** instead of at boot time.
|
||||
`generate-panel-dtbos.sh` now pre-merges each panel overlay with the kernel DTB, producing
|
||||
`kernel-panel0.dtb` through `kernel-panel5.dtb` (original) and 12 clone variants. The boot
|
||||
script reads `PanelDTB=kernel-panel3.dtb` from `panel.txt` and loads the complete DTB directly —
|
||||
no `fdt apply` at all. Published as v1.0-beta1.1 and pushed to the community for testing.
|
||||
|
||||
---
|
||||
|
||||
### 2026-03-01 — Arch R Flasher & beta1.2
|
||||
|
||||
Two things happened in parallel this week: the Flasher app was born, and the distro itself
|
||||
got some housekeeping.
|
||||
|
||||
**Arch R Flasher — feature-complete.**
|
||||
|
||||
Built the desktop flashing tool (Tauri 2 — Rust backend, vanilla HTML/CSS/JS frontend) that
|
||||
was "Plano C" in case pre-merged DTBs didn't fully work. Turns out having a proper Flasher is
|
||||
valuable regardless — it eliminates the dd-to-SD-card ceremony and lets users select their
|
||||
console type (Original vs Clone) and panel before flashing. The Flasher injects the correct
|
||||
kernel DTB, U-Boot, and panel config into the image at write time. Zero runtime detection needed.
|
||||
|
||||
Feature set:
|
||||
- Console selection (Original: 6 panels, Clone: 12 panels)
|
||||
- Panel picker with defaults marked
|
||||
- In-app image download from GitHub Releases (with progress bar + SHA256 verification)
|
||||
- Local file picker (.img / .xz) with XZ decompression
|
||||
- SD card detection (Linux: sysfs, macOS: diskutil, Windows: PowerShell)
|
||||
- System disk protection (never lists /, /home, /boot disks)
|
||||
- Privileged flash (pkexec on Linux, osascript on macOS, UAC on Windows)
|
||||
- Real-time flash progress via dd monitoring
|
||||
- Post-flash eject (Linux + macOS)
|
||||
- Auto-update via tauri-plugin-updater + GitHub Releases (with minisign signing)
|
||||
- i18n: English, Portuguese, Spanish, Chinese
|
||||
- CI/CD: GitHub Actions builds for Linux (.deb/.rpm/.AppImage), macOS (.dmg), Windows (.msi/.exe)
|
||||
|
||||
The Flasher lives at `archr-linux/archr-flasher` on GitHub. Still ironing out the CI signing
|
||||
(first build caught a Tauri 2 schema issue with `app.title` and 16-bit icon PNGs), but the
|
||||
code is feature-complete.
|
||||
|
||||
**beta1.2 image changes:**
|
||||
|
||||
Published new images with accumulated fixes since beta1.1. Nothing dramatic — mostly polish
|
||||
and small fixes that came out of Flasher development and continued testing.
|
||||
|
||||
---
|
||||
|
||||
## What's Left for v1.0 Stable
|
||||
|
||||
### Critical — Must Work Before Release
|
||||
@@ -1950,7 +2003,7 @@ The panel wizard can only write config for the NEXT boot — no hot-swapping pan
|
||||
| 15 | Bluetooth pairing | Not tested | bluez installed |
|
||||
| 16 | Battery LED indicator | Installed | Python service, needs hardware test |
|
||||
| 17 | Sleep/wake | Not implemented | PMIC sleep pinctrl in DTS |
|
||||
| 18 | OTA updates | Not implemented | Future feature |
|
||||
| 18 | OTA updates | Not implemented | Future feature (Flasher has self-update) |
|
||||
| 19 | Theme customization | Default only | ES-fcamod default theme |
|
||||
| 20 | Headphone detection | Not tested | archr-hotkeys.py ALSA switch |
|
||||
|
||||
|
||||
@@ -213,6 +213,12 @@ if [ "$BUILD_IMAGE" = true ]; then
|
||||
log " BUILDING IMAGE (clone)"
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
"$SCRIPT_DIR/build-image.sh" --variant clone
|
||||
|
||||
log ""
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
log " BUILDING IMAGE (no-panel)"
|
||||
log "═══════════════════════════════════════════════════════════════"
|
||||
"$SCRIPT_DIR/build-image.sh" --variant no-panel
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
+36
-13
@@ -3,11 +3,12 @@
|
||||
#==============================================================================
|
||||
# Arch R - SD Card Image Builder
|
||||
#==============================================================================
|
||||
# Creates a flashable SD card image for R36S (original or clone)
|
||||
# Creates a flashable SD card image for R36S
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./build-image.sh --variant original # R36S original
|
||||
# sudo ./build-image.sh --variant clone # R36S clone (G80CA-MB etc)
|
||||
# sudo ./build-image.sh --variant no-panel # Universal (all panels, for Flasher)
|
||||
# sudo ./build-image.sh # defaults to original
|
||||
#==============================================================================
|
||||
|
||||
@@ -37,13 +38,13 @@ while [[ $# -gt 0 ]]; do
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
error "Unknown option: $1\nUsage: $0 --variant original|clone"
|
||||
error "Unknown option: $1\nUsage: $0 --variant original|clone|no-panel"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$VARIANT" != "original" ] && [ "$VARIANT" != "clone" ]; then
|
||||
error "Invalid variant: $VARIANT (must be 'original' or 'clone')"
|
||||
if [ "$VARIANT" != "original" ] && [ "$VARIANT" != "clone" ] && [ "$VARIANT" != "no-panel" ]; then
|
||||
error "Invalid variant: $VARIANT (must be 'original', 'clone', or 'no-panel')"
|
||||
fi
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
@@ -52,11 +53,12 @@ fi
|
||||
if [ "$VARIANT" = "original" ]; then
|
||||
IMAGE_SUFFIX="R36S"
|
||||
KERNEL_DTB_NAME="rk3326-gameconsole-r36s.dtb"
|
||||
ROOT_DEV="/dev/mmcblk1p2"
|
||||
else
|
||||
elif [ "$VARIANT" = "clone" ]; then
|
||||
IMAGE_SUFFIX="R36S-clone"
|
||||
KERNEL_DTB_NAME="rk3326-gameconsole-r36s-clone-type5.dtb"
|
||||
ROOT_DEV="/dev/mmcblk0p2"
|
||||
elif [ "$VARIANT" = "no-panel" ]; then
|
||||
IMAGE_SUFFIX="R36S-no-panel"
|
||||
KERNEL_DTB_NAME="rk3326-gameconsole-r36s.dtb" # placeholder (Flasher replaces)
|
||||
fi
|
||||
|
||||
# U-Boot binaries per variant:
|
||||
@@ -66,7 +68,12 @@ UBOOT_TYPE=""
|
||||
UBOOT_BSP_DIR="$SCRIPT_DIR/bootloader/u-boot-rk3326/sd_fuse"
|
||||
UBOOT_MAINLINE_DIR="$SCRIPT_DIR/bootloader/u-boot-clone-build"
|
||||
|
||||
if [ "$VARIANT" = "clone" ] && [ -f "$UBOOT_MAINLINE_DIR/uboot.img" ]; then
|
||||
if [ "$VARIANT" = "no-panel" ] && [ -f "$UBOOT_MAINLINE_DIR/uboot.img" ]; then
|
||||
# no-panel: mainline U-Boot works on both original and clone
|
||||
UBOOT_BIN_DIR="$UBOOT_MAINLINE_DIR"
|
||||
UBOOT_TYPE="mainline"
|
||||
log " U-Boot: mainline v2025.10 (no-panel universal)"
|
||||
elif [ "$VARIANT" = "clone" ] && [ -f "$UBOOT_MAINLINE_DIR/uboot.img" ]; then
|
||||
UBOOT_BIN_DIR="$UBOOT_MAINLINE_DIR"
|
||||
UBOOT_TYPE="mainline"
|
||||
log " U-Boot: mainline v2025.10 (clone)"
|
||||
@@ -278,7 +285,23 @@ MERGED_DIR="$OUTPUT_DIR/panels/merged"
|
||||
if [ -d "$MERGED_DIR" ]; then
|
||||
panel_count=0
|
||||
|
||||
if [ "$VARIANT" = "original" ]; then
|
||||
if [ "$VARIANT" = "no-panel" ]; then
|
||||
# Universal: include ALL panel DTBs (original + clone)
|
||||
# Every panel has its own native DTB — no base/inherited DTBs.
|
||||
for dtb in "$MERGED_DIR"/kernel-panel*.dtb "$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
|
||||
# Default panels: copy base DTBs with their panel number names
|
||||
# Panel 4-V22 (original default) → kernel-panel4.dtb
|
||||
[ -f "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s.dtb" ] && \
|
||||
cp "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s.dtb" "$MOUNT_BOOT/kernel-panel4.dtb" && \
|
||||
panel_count=$((panel_count + 1))
|
||||
# Clone 8 G80CA (clone default) → kernel-clone8.dtb
|
||||
[ -f "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s-clone-type5.dtb" ] && \
|
||||
cp "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s-clone-type5.dtb" "$MOUNT_BOOT/kernel-clone8.dtb" && \
|
||||
panel_count=$((panel_count + 1))
|
||||
elif [ "$VARIANT" = "original" ]; then
|
||||
# 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))
|
||||
@@ -338,10 +361,11 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Boot partition: boot script (with root device substituted) ---
|
||||
# --- Boot partition: boot script ---
|
||||
# Root device is auto-detected in boot.ini based on mmcdev (no substitution needed).
|
||||
if [ -f "$SCRIPT_DIR/config/boot.ini" ]; then
|
||||
sed "s|__ROOTDEV__|$ROOT_DEV|" "$SCRIPT_DIR/config/boot.ini" > "$MOUNT_BOOT/boot.ini"
|
||||
log " boot.ini installed (root=$ROOT_DEV)"
|
||||
cp "$SCRIPT_DIR/config/boot.ini" "$MOUNT_BOOT/boot.ini"
|
||||
log " boot.ini installed (root auto-detect)"
|
||||
|
||||
# Mainline U-Boot: also create boot.scr (compiled boot script)
|
||||
# Mainline uses distro boot flow → scans for boot.scr, not boot.ini
|
||||
@@ -483,7 +507,6 @@ IMAGE_SIZE_ACTUAL=$(du -h "$IMAGE_FILE" | cut -f1)
|
||||
log "Image: $IMAGE_FILE"
|
||||
log "Size: $IMAGE_SIZE_ACTUAL"
|
||||
log "Variant: $VARIANT"
|
||||
log "Root: $ROOT_DEV"
|
||||
log ""
|
||||
log "To flash to SD card:"
|
||||
log " sudo dd if=$IMAGE_FILE of=/dev/sdX bs=4M status=progress"
|
||||
|
||||
+48
-9
@@ -123,15 +123,19 @@ log " ✓ Pacman CheckSpace disabled (QEMU chroot compatibility)"
|
||||
|
||||
# Add multiple fallback mirrors (default mirror often has stale/404 packages)
|
||||
cat > "$ROOTFS_DIR/etc/pacman.d/mirrorlist" << 'MIRRORS_EOF'
|
||||
# Arch Linux ARM mirrors - multiple fallbacks for reliability
|
||||
Server = http://de.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://eu.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://dk.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://de3.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://hu.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://mirror.archlinuxarm.org/$arch/$repo
|
||||
# Arch Linux ARM mirrors - all official mirrors, Americas first
|
||||
Server = http://fl.us.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://nj.us.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://ca.us.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://de.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://de4.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://dk.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://hu.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://gr.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://de3.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://tw.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://tw2.mirror.archlinuxarm.org/$arch/$repo
|
||||
Server = http://mirror.archlinuxarm.org/$arch/$repo
|
||||
MIRRORS_EOF
|
||||
log " ✓ Multiple ALARM mirrors configured (fallback for 404s)"
|
||||
|
||||
@@ -368,6 +372,27 @@ FB_EOF
|
||||
|
||||
systemctl enable firstboot
|
||||
|
||||
# Variant sync: copies variant marker from BOOT → /etc/archr/variant
|
||||
# Only runs if /etc/archr/variant doesn't exist yet (no-panel images flashed by Flasher)
|
||||
cat > /etc/systemd/system/archr-variant-sync.service << 'VARSYNC_EOF'
|
||||
[Unit]
|
||||
Description=Sync variant from BOOT partition
|
||||
After=local-fs.target
|
||||
Before=panel-detect.service
|
||||
RequiresMountsFor=/boot
|
||||
ConditionPathExists=!/etc/archr/variant
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/sh -c 'test -f /boot/variant && mkdir -p /etc/archr && cp /boot/variant /etc/archr/variant'
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
VARSYNC_EOF
|
||||
|
||||
systemctl enable archr-variant-sync
|
||||
|
||||
# Panel auto-detect wizard (first boot / X-button reset)
|
||||
cat > /etc/systemd/system/panel-detect.service << 'PANELDET_EOF'
|
||||
[Unit]
|
||||
@@ -521,7 +546,7 @@ dd if=/dev/urandom of=/var/lib/systemd/random-seed bs=512 count=1 2>/dev/null
|
||||
chmod 600 /var/lib/systemd/random-seed
|
||||
|
||||
# Sudoers for perfmax/perfnorm/pmic-poweroff (allow archr to run without password)
|
||||
echo "archr ALL=(ALL) NOPASSWD: /usr/local/bin/perfmax, /usr/local/bin/perfnorm, /usr/local/bin/pmic-poweroff, /usr/bin/ln, /usr/bin/dmesg, /usr/bin/chvt, /usr/bin/cp, /usr/bin/chmod, /bin/bash" > /etc/sudoers.d/archr-perf
|
||||
echo "archr ALL=(ALL) NOPASSWD: /usr/local/bin/perfmax, /usr/local/bin/perfnorm, /usr/local/bin/pmic-poweroff, /usr/local/bin/input-merge, /usr/bin/kill, /usr/bin/ln, /usr/bin/dmesg, /usr/bin/chvt, /usr/bin/cp, /usr/bin/chmod, /bin/bash" > /etc/sudoers.d/archr-perf
|
||||
chmod 440 /etc/sudoers.d/archr-perf
|
||||
|
||||
# Allow archr to use negative nice values (needed for nice -n -19 in ES launch commands)
|
||||
@@ -952,8 +977,22 @@ install -m 755 "$SCRIPT_DIR/scripts/perfmax" "$ROOTFS_DIR/usr/local/bin/perfmax"
|
||||
install -m 755 "$SCRIPT_DIR/scripts/perfnorm" "$ROOTFS_DIR/usr/local/bin/perfnorm"
|
||||
install -m 755 "$SCRIPT_DIR/scripts/pmic-poweroff" "$ROOTFS_DIR/usr/local/bin/pmic-poweroff"
|
||||
install -m 755 "$SCRIPT_DIR/scripts/retroarch-launch.sh" "$ROOTFS_DIR/usr/local/bin/retroarch-launch"
|
||||
# RetroArch joypad autoconfig (gpio-keys buttons + adc-joystick analog sticks)
|
||||
# RetroArch core options (per-core tuning for RK3326, from ROCKNIX)
|
||||
install -m 644 "$SCRIPT_DIR/config/retroarch-core-options.cfg" "$ROOTFS_DIR/home/archr/.config/retroarch/retroarch-core-options.cfg"
|
||||
log " ✓ RetroArch core options installed"
|
||||
# Input merger daemon: combines gpio-keys + adc-joystick into single virtual device
|
||||
# RetroArch needs all inputs on one device (udev driver assigns each to separate port)
|
||||
if command -v aarch64-linux-gnu-gcc &>/dev/null; then
|
||||
aarch64-linux-gnu-gcc -static -O2 -o "$ROOTFS_DIR/usr/local/bin/input-merge" \
|
||||
"$SCRIPT_DIR/scripts/input-merge.c"
|
||||
chmod 755 "$ROOTFS_DIR/usr/local/bin/input-merge"
|
||||
log " ✓ input-merge compiled and installed"
|
||||
else
|
||||
warn "aarch64-linux-gnu-gcc not found — input-merge not compiled"
|
||||
fi
|
||||
# RetroArch joypad autoconfig (merged device + individual fallbacks)
|
||||
mkdir -p "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev"
|
||||
install -m 644 "$SCRIPT_DIR/config/autoconfig/udev/archr-gamepad.cfg" "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev/"
|
||||
install -m 644 "$SCRIPT_DIR/config/autoconfig/udev/gpio-keys.cfg" "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev/"
|
||||
install -m 644 "$SCRIPT_DIR/config/autoconfig/udev/adc-joystick.cfg" "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev/"
|
||||
# systemd shutdown hook — runs pmic-poweroff at the very end of shutdown sequence
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Arch R Gamepad (merged gpio-keys + adc-joystick)
|
||||
# Created by input-merge daemon — combines buttons and analog sticks
|
||||
# into a single virtual device for RetroArch.
|
||||
|
||||
input_driver = "udev"
|
||||
input_device = "Arch R Gamepad"
|
||||
input_device_display_name = "Arch R Gamepad"
|
||||
|
||||
# Face buttons
|
||||
input_b_btn = "0"
|
||||
input_a_btn = "1"
|
||||
input_y_btn = "2"
|
||||
input_x_btn = "3"
|
||||
|
||||
# Shoulders
|
||||
input_l_btn = "4"
|
||||
input_r_btn = "5"
|
||||
input_l2_btn = "6"
|
||||
input_r2_btn = "7"
|
||||
|
||||
# Select / Start / Hotkey
|
||||
input_select_btn = "8"
|
||||
input_start_btn = "9"
|
||||
input_enable_hotkey_btn = "10"
|
||||
|
||||
# Thumbstick clicks
|
||||
input_l3_btn = "11"
|
||||
input_r3_btn = "12"
|
||||
|
||||
# D-Pad
|
||||
input_up_btn = "13"
|
||||
input_down_btn = "14"
|
||||
input_left_btn = "15"
|
||||
input_right_btn = "16"
|
||||
|
||||
# Left analog stick
|
||||
input_l_x_plus_axis = "+0"
|
||||
input_l_x_minus_axis = "-0"
|
||||
input_l_y_plus_axis = "+1"
|
||||
input_l_y_minus_axis = "-1"
|
||||
|
||||
# Right analog stick
|
||||
input_r_x_plus_axis = "+2"
|
||||
input_r_x_minus_axis = "-2"
|
||||
input_r_y_plus_axis = "+3"
|
||||
input_r_y_minus_axis = "-3"
|
||||
|
||||
# Labels
|
||||
input_b_btn_label = "B"
|
||||
input_a_btn_label = "A"
|
||||
input_y_btn_label = "Y"
|
||||
input_x_btn_label = "X"
|
||||
input_l_btn_label = "L1"
|
||||
input_r_btn_label = "R1"
|
||||
input_l2_btn_label = "L2"
|
||||
input_r2_btn_label = "R2"
|
||||
input_select_btn_label = "Select"
|
||||
input_start_btn_label = "Start"
|
||||
input_enable_hotkey_btn_label = "Mode"
|
||||
input_l3_btn_label = "L3"
|
||||
input_r3_btn_label = "R3"
|
||||
input_up_btn_label = "D-Pad Up"
|
||||
input_down_btn_label = "D-Pad Down"
|
||||
input_left_btn_label = "D-Pad Left"
|
||||
input_right_btn_label = "D-Pad Right"
|
||||
input_l_x_plus_axis_label = "LS Right"
|
||||
input_l_x_minus_axis_label = "LS Left"
|
||||
input_l_y_plus_axis_label = "LS Down"
|
||||
input_l_y_minus_axis_label = "LS Up"
|
||||
input_r_x_plus_axis_label = "RS Right"
|
||||
input_r_x_minus_axis_label = "RS Left"
|
||||
input_r_y_plus_axis_label = "RS Down"
|
||||
input_r_y_minus_axis_label = "RS Up"
|
||||
+12
-5
@@ -3,11 +3,10 @@ odroidgoa-uboot-config
|
||||
########################################################################
|
||||
# Arch R Boot Configuration
|
||||
#
|
||||
# Auto-detects SD card MMC device:
|
||||
# Original R36S: eMMC=mmc0, SD=mmc1
|
||||
# Clone (no eMMC): SD=mmc0
|
||||
# Auto-detects SD card MMC device AND root partition:
|
||||
# Original R36S: eMMC=mmc0, SD=mmc1 → root=/dev/mmcblk1p2
|
||||
# Clone (no eMMC): SD=mmc0 → root=/dev/mmcblk0p2
|
||||
#
|
||||
# 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!).
|
||||
@@ -48,10 +47,18 @@ else
|
||||
setenv dtb_name "kernel.dtb"
|
||||
fi
|
||||
|
||||
# --- Auto-detect root device (SD card partition 2) ---
|
||||
# mmcdev already detected above: 1 = original (eMMC+SD), 0 = clone (SD only)
|
||||
if test "${mmcdev}" = "1"; then
|
||||
setenv rootdev "/dev/mmcblk1p2"
|
||||
else
|
||||
setenv rootdev "/dev/mmcblk0p2"
|
||||
fi
|
||||
|
||||
# --- Load DTB and boot (Image already loaded above) ---
|
||||
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"
|
||||
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"
|
||||
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
mupen64plus-alt-map = "False"
|
||||
mupen64plus-aspect = "4:3"
|
||||
mupen64plus-astick-deadzone = "15"
|
||||
mupen64plus-astick-sensitivity = "100"
|
||||
mupen64plus-BackgroundMode = "Stripped"
|
||||
mupen64plus-BilinearMode = "standard"
|
||||
mupen64plus-CorrectTexrectCoords = "Auto"
|
||||
mupen64plus-CountPerOp = "0"
|
||||
mupen64plus-CountPerOpDenomPot = "0"
|
||||
mupen64plus-cpucore = "dynamic_recompiler"
|
||||
mupen64plus-d-cbutton = "C3"
|
||||
mupen64plus-DitheringPattern = "True"
|
||||
mupen64plus-DitheringQuantization = "True"
|
||||
mupen64plus-EnableCopyAuxToRDRAM = "True"
|
||||
mupen64plus-EnableCopyColorToRDRAM = "Async"
|
||||
mupen64plus-EnableCopyDepthToRDRAM = "Software"
|
||||
mupen64plus-EnableEnhancedHighResStorage = "False"
|
||||
mupen64plus-EnableEnhancedTextureStorage = "False"
|
||||
mupen64plus-EnableFBEmulation = "True"
|
||||
mupen64plus-EnableFragmentDepthWrite = "False"
|
||||
mupen64plus-EnableHiResAltCRC = "False"
|
||||
mupen64plus-EnableHWLighting = "True"
|
||||
mupen64plus-EnableInaccurateTextureCoordinates = "False"
|
||||
mupen64plus-EnableLegacyBlending = "False"
|
||||
mupen64plus-EnableLODEmulation = "True"
|
||||
mupen64plus-EnableNativeResFactor = "1"
|
||||
mupen64plus-EnableNativeResTexrects = "Optimized"
|
||||
mupen64plus-EnableOverscan = "Enabled"
|
||||
mupen64plus-EnableTexCoordBounds = "False"
|
||||
mupen64plus-EnableTextureCache = "True"
|
||||
mupen64plus-ForceDisableExtraMem = "False"
|
||||
mupen64plus-FrameDuping = "True"
|
||||
mupen64plus-Framerate = "Original"
|
||||
mupen64plus-FXAA = "0"
|
||||
mupen64plus-GLideN64IniBehaviour = "late"
|
||||
mupen64plus-HybridFilter = "True"
|
||||
mupen64plus-IgnoreTLBExceptions = "False"
|
||||
mupen64plus-l-cbutton = "C2"
|
||||
mupen64plus-MaxHiResTxVramLimit = "0"
|
||||
mupen64plus-MaxTxCacheSize = "8000"
|
||||
mupen64plus-MultiSampling = "0"
|
||||
mupen64plus-OverscanBottom = "0"
|
||||
mupen64plus-OverscanLeft = "0"
|
||||
mupen64plus-OverscanRight = "0"
|
||||
mupen64plus-OverscanTop = "0"
|
||||
mupen64plus-pak1 = "memory"
|
||||
mupen64plus-pak2 = "none"
|
||||
mupen64plus-pak3 = "none"
|
||||
mupen64plus-pak4 = "none"
|
||||
mupen64plus-r-cbutton = "C1"
|
||||
mupen64plus-rdp-plugin = "gliden64"
|
||||
mupen64plus-RDRAMImageDitheringMode = "BlueNoise"
|
||||
mupen64plus-rsp-plugin = "hle"
|
||||
mupen64plus-ThreadedRenderer = "True"
|
||||
mupen64plus-txCacheCompression = "True"
|
||||
mupen64plus-txEnhancementMode = "None"
|
||||
mupen64plus-txFilterIgnoreBG = "True"
|
||||
mupen64plus-txFilterMode = "None"
|
||||
mupen64plus-txHiresEnable = "False"
|
||||
mupen64plus-txHiresFullAlphaChannel = "False"
|
||||
mupen64plus-u-cbutton = "C4"
|
||||
mupen64plus-virefresh = "Auto"
|
||||
pcsx_rearmed_analog_axis_modifier = "circle"
|
||||
pcsx_rearmed_async_cd = "sync"
|
||||
pcsx_rearmed_bios = "auto"
|
||||
pcsx_rearmed_display_internal_fps = "disabled"
|
||||
pcsx_rearmed_dithering = "enabled"
|
||||
pcsx_rearmed_drc = "enabled"
|
||||
pcsx_rearmed_duping_enable = "enabled"
|
||||
pcsx_rearmed_frameskip_type = "disabled"
|
||||
pcsx_rearmed_gteregsunneeded = "disabled"
|
||||
pcsx_rearmed_gunconadjustratiox = "1"
|
||||
pcsx_rearmed_gunconadjustratioy = "1"
|
||||
pcsx_rearmed_gunconadjustx = "0"
|
||||
pcsx_rearmed_gunconadjusty = "0"
|
||||
pcsx_rearmed_idiablofix = "disabled"
|
||||
pcsx_rearmed_input_sensitivity = "1.00"
|
||||
pcsx_rearmed_inuyasha_fix = "disabled"
|
||||
pcsx_rearmed_memcard2 = "disabled"
|
||||
pcsx_rearmed_multitap1 = "auto"
|
||||
pcsx_rearmed_multitap2 = "auto"
|
||||
pcsx_rearmed_negcon_deadzone = "0"
|
||||
pcsx_rearmed_negcon_response = "linear"
|
||||
pcsx_rearmed_neon_enhancement_enable = "disabled"
|
||||
pcsx_rearmed_neon_enhancement_no_main = "disabled"
|
||||
pcsx_rearmed_neon_interlace_enable = "disabled"
|
||||
pcsx_rearmed_nocdaudio = "enabled"
|
||||
pcsx_rearmed_nogteflags = "disabled"
|
||||
pcsx_rearmed_nosmccheck = "disabled"
|
||||
pcsx_rearmed_noxadecoding = "enabled"
|
||||
pcsx_rearmed_pad1type = "standard"
|
||||
pcsx_rearmed_pad2type = "standard"
|
||||
pcsx_rearmed_pad3type = "none"
|
||||
pcsx_rearmed_pad4type = "none"
|
||||
pcsx_rearmed_pad5type = "none"
|
||||
pcsx_rearmed_pad6type = "none"
|
||||
pcsx_rearmed_pad7type = "none"
|
||||
pcsx_rearmed_pad8type = "none"
|
||||
pcsx_rearmed_pe2_fix = "disabled"
|
||||
pcsx_rearmed_psxclock = "57"
|
||||
pcsx_rearmed_region = "auto"
|
||||
pcsx_rearmed_show_bios_bootlogo = "disabled"
|
||||
pcsx_rearmed_show_other_input_settings = "disabled"
|
||||
pcsx_rearmed_spu_interpolation = "simple"
|
||||
pcsx_rearmed_spu_reverb = "enabled"
|
||||
pcsx_rearmed_spuirq = "disabled"
|
||||
pcsx_rearmed_vibration = "enabled"
|
||||
gambatte_gb_colorization = "disabled"
|
||||
gambatte_gb_internal_palette = ""
|
||||
mame2003-plus_analog = "digital"
|
||||
mame2003-plus_art_resolution = "1"
|
||||
mame2003-plus_brightness = "1.0"
|
||||
mame2003-plus_cheat_input_ports = "disabled"
|
||||
mame2003-plus_core_save_subfolder = "enabled"
|
||||
mame2003-plus_core_sys_subfolder = "enabled"
|
||||
mame2003-plus_dcs_speedhack = "enabled"
|
||||
mame2003-plus_deadzone = "20"
|
||||
mame2003-plus_digital_joy_centering = "enabled"
|
||||
mame2003-plus_display_artwork = "enabled"
|
||||
mame2003-plus_display_setup = "disabled"
|
||||
mame2003-plus_four_way_emulation = "disabled"
|
||||
mame2003-plus_frameskip = "0"
|
||||
mame2003-plus_gamma = "1.0"
|
||||
mame2003-plus_input_interface = "retropad"
|
||||
mame2003-plus_machine_timing = "enabled"
|
||||
mame2003-plus_mame_remapping = "enabled"
|
||||
mame2003-plus_mouse_device = "mouse"
|
||||
mame2003-plus_sample_rate = "48000"
|
||||
mame2003-plus_skip_disclaimer = "disabled"
|
||||
mame2003-plus_skip_warnings = "disabled"
|
||||
flycast_internal_resolution = "640x480"
|
||||
flycast_alpha_sorting = "per-strip (fast, least accurate)"
|
||||
flycast_enable_dsp = "disabled"
|
||||
flycast_threaded_rendering = "enabled"
|
||||
flycast_anisotropic_filtering = "off"
|
||||
flycast_synchronous_rendering = "enabled"
|
||||
flycast_enable_rtt = "disabled"
|
||||
flycast_enable_rttb = "disabled"
|
||||
flycast_delay_frame_swapping = "disabled"
|
||||
flycast_div_matching = "auto"
|
||||
flycast_texupscale = "off"
|
||||
flycast_auto_skip_frame = "normal"
|
||||
opera_swi_hle = "enabled"
|
||||
opera_dsp_threaded = "enabled"
|
||||
opera_madam_matrix_engine = "software"
|
||||
puae_deadzone = "25"
|
||||
melonds_screen_layout = "Left/Right"
|
||||
melonds_swapscreen_mode = "Toggle"
|
||||
melonds_threaded_renderer = "enabled"
|
||||
melonds_touch_mode = "Joystick"
|
||||
virtualjaguar_bios = "enabled"
|
||||
virtualjaguar_usefastblitter = "enabled"
|
||||
+17
-8
@@ -1,5 +1,5 @@
|
||||
# Arch R - RetroArch Configuration for R36S
|
||||
# Optimized for 640x480 display
|
||||
# Optimized for 640x480 display, tuned from ROCKNIX RK3326 defaults
|
||||
|
||||
# Video
|
||||
video_driver = "gl"
|
||||
@@ -7,22 +7,22 @@ video_context_driver = ""
|
||||
video_fullscreen = "true"
|
||||
video_windowed_fullscreen = "false"
|
||||
video_vsync = "true"
|
||||
video_max_swapchain_images = "2"
|
||||
video_max_swapchain_images = "3"
|
||||
video_smooth = "false"
|
||||
video_scale_integer = "true"
|
||||
video_scale_integer = "false"
|
||||
video_rotation = "0"
|
||||
video_aspect_ratio_auto = "true"
|
||||
video_fullscreen_x = "640"
|
||||
video_fullscreen_y = "480"
|
||||
|
||||
# Audio — matches dArkOS RG351MP (RK3326) settings
|
||||
# Audio — tuned for RK3326 rk817 codec (48kHz native)
|
||||
# alsathread: non-blocking audio, better for ARM
|
||||
# audio_device empty: uses ALSA default (plug → hw:0,0 from asound.conf)
|
||||
# 48kHz avoids double resampling (RA→44.1k→ALSA→48k)
|
||||
# audio_volume +6dB: software boost to compensate rk817 codec low output level
|
||||
audio_driver = "alsathread"
|
||||
audio_device = ""
|
||||
audio_out_rate = "44100"
|
||||
audio_latency = "128"
|
||||
audio_out_rate = "48000"
|
||||
audio_latency = "64"
|
||||
audio_sync = "true"
|
||||
audio_enable = "true"
|
||||
audio_volume = "6.000000"
|
||||
@@ -31,6 +31,7 @@ audio_mixer_mute_enable = "false"
|
||||
audio_rate_control = "true"
|
||||
audio_rate_control_delta = "0.005000"
|
||||
audio_resampler = "sinc"
|
||||
audio_resampler_quality = "2"
|
||||
audio_max_timing_skew = "0.050000"
|
||||
|
||||
# Microphone — DISABLED (binary built with --disable-microphone, belt-and-suspenders)
|
||||
@@ -41,9 +42,10 @@ input_driver = "udev"
|
||||
input_joypad_driver = "udev"
|
||||
input_autodetect_enable = "true"
|
||||
input_max_users = "1"
|
||||
input_poll_type_behavior = "2"
|
||||
joypad_autoconfig_dir = "/usr/share/retroarch/autoconfig"
|
||||
|
||||
# Menu (rgui — lightweight, better for 640x480 like dArkOS)
|
||||
# Menu (rgui — lightweight, better for 640x480)
|
||||
menu_driver = "rgui"
|
||||
menu_swap_ok_cancel_buttons = "false"
|
||||
|
||||
@@ -55,10 +57,17 @@ libretro_directory = "/usr/lib/libretro"
|
||||
libretro_info_path = "/usr/share/libretro/info"
|
||||
assets_directory = "/usr/share/retroarch/assets"
|
||||
system_directory = "/roms/bios"
|
||||
core_options_path = "/home/archr/.config/retroarch/retroarch-core-options.cfg"
|
||||
|
||||
# Performance
|
||||
video_threaded = "true"
|
||||
video_frame_delay = "0"
|
||||
game_specific_options = "true"
|
||||
auto_overrides_enable = "true"
|
||||
auto_remaps_enable = "true"
|
||||
|
||||
# Save protection (autosave SRAM every 10s, prevents loss on power-off)
|
||||
autosave_interval = "10"
|
||||
|
||||
# Power Saving
|
||||
suspend_screensaver_enable = "false"
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* input-merge — Merge gpio-keys + adc-joystick into single virtual gamepad
|
||||
*
|
||||
* The R36S has buttons (gpio-keys) and analog sticks (adc-joystick) as
|
||||
* separate evdev devices. RetroArch's udev driver assigns each to a
|
||||
* different player port, so analog sticks end up on "player 2" and are
|
||||
* ignored when max_users=1.
|
||||
*
|
||||
* This daemon:
|
||||
* 1. Finds both devices by name in /dev/input/
|
||||
* 2. Grabs them (EVIOCGRAB) so only we read their events
|
||||
* 3. Creates a virtual "Arch R Gamepad" via uinput with all capabilities
|
||||
* 4. Forwards all events from both sources to the virtual device
|
||||
*
|
||||
* Started by retroarch-launch.sh before RetroArch, killed on RA exit.
|
||||
* While grabbed, EmulationStation and other programs won't see the
|
||||
* original devices — that's fine because RA is the only thing running.
|
||||
*
|
||||
* Build: aarch64-linux-gnu-gcc -static -O2 -o input-merge input-merge.c
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/uinput.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DEVICE_NAME "Arch R Gamepad"
|
||||
#define MAX_EVENTS 16
|
||||
#define RETRY_DELAY_US 200000 /* 200ms between retries */
|
||||
#define MAX_RETRIES 50 /* 10 seconds max wait */
|
||||
|
||||
static volatile int running = 1;
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
running = 0;
|
||||
}
|
||||
|
||||
/* Find /dev/input/eventX by device name, return fd or -1 */
|
||||
static int find_device(const char *name)
|
||||
{
|
||||
char path[64];
|
||||
char dev_name[256];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
int fd;
|
||||
snprintf(path, sizeof(path), "/dev/input/event%d", i);
|
||||
fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
continue;
|
||||
|
||||
if (ioctl(fd, EVIOCGNAME(sizeof(dev_name)), dev_name) >= 0) {
|
||||
if (strcmp(dev_name, name) == 0)
|
||||
return fd;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Create uinput virtual device with capabilities from both sources */
|
||||
static int setup_uinput(int gpio_fd, int adc_fd)
|
||||
{
|
||||
int ufd;
|
||||
struct uinput_setup setup;
|
||||
unsigned long bits[(KEY_MAX + 1) / (8 * sizeof(unsigned long)) + 1];
|
||||
int i;
|
||||
|
||||
ufd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
||||
if (ufd < 0) {
|
||||
perror("open /dev/uinput");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Enable EV_KEY + copy key bits from gpio-keys */
|
||||
ioctl(ufd, UI_SET_EVBIT, EV_KEY);
|
||||
memset(bits, 0, sizeof(bits));
|
||||
if (ioctl(gpio_fd, EVIOCGBIT(EV_KEY, sizeof(bits)), bits) >= 0) {
|
||||
for (i = 0; i <= KEY_MAX; i++) {
|
||||
if (bits[i / (8 * sizeof(unsigned long))] &
|
||||
(1UL << (i % (8 * sizeof(unsigned long))))) {
|
||||
ioctl(ufd, UI_SET_KEYBIT, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable EV_ABS + copy abs bits from adc-joystick (with absinfo) */
|
||||
ioctl(ufd, UI_SET_EVBIT, EV_ABS);
|
||||
memset(bits, 0, sizeof(bits));
|
||||
if (ioctl(adc_fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits) >= 0) {
|
||||
for (i = 0; i <= ABS_MAX; i++) {
|
||||
if (bits[i / (8 * sizeof(unsigned long))] &
|
||||
(1UL << (i % (8 * sizeof(unsigned long))))) {
|
||||
struct uinput_abs_setup abs_setup;
|
||||
memset(&abs_setup, 0, sizeof(abs_setup));
|
||||
abs_setup.code = i;
|
||||
if (ioctl(adc_fd, EVIOCGABS(i), &abs_setup.absinfo) >= 0)
|
||||
ioctl(ufd, UI_ABS_SETUP, &abs_setup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Device identity */
|
||||
memset(&setup, 0, sizeof(setup));
|
||||
strncpy(setup.name, DEVICE_NAME, UINPUT_MAX_NAME_SIZE - 1);
|
||||
setup.id.bustype = BUS_VIRTUAL;
|
||||
setup.id.vendor = 0x4152; /* "AR" */
|
||||
setup.id.product = 0x3336; /* "36" */
|
||||
setup.id.version = 1;
|
||||
|
||||
if (ioctl(ufd, UI_DEV_SETUP, &setup) < 0) {
|
||||
perror("UI_DEV_SETUP");
|
||||
close(ufd);
|
||||
return -1;
|
||||
}
|
||||
if (ioctl(ufd, UI_DEV_CREATE) < 0) {
|
||||
perror("UI_DEV_CREATE");
|
||||
close(ufd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ufd;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int gpio_fd = -1, adc_fd = -1, ufd, epfd;
|
||||
int retries = 0;
|
||||
struct epoll_event ev, events[MAX_EVENTS];
|
||||
struct input_event ie;
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
/* Wait for both devices to appear (kernel may still be probing) */
|
||||
while (running && retries < MAX_RETRIES) {
|
||||
if (gpio_fd < 0)
|
||||
gpio_fd = find_device("gpio-keys");
|
||||
if (adc_fd < 0)
|
||||
adc_fd = find_device("adc-joystick");
|
||||
if (gpio_fd >= 0 && adc_fd >= 0)
|
||||
break;
|
||||
usleep(RETRY_DELAY_US);
|
||||
retries++;
|
||||
}
|
||||
|
||||
if (gpio_fd < 0 || adc_fd < 0) {
|
||||
fprintf(stderr, "input-merge: devices not found (gpio=%d adc=%d)\n",
|
||||
gpio_fd, adc_fd);
|
||||
if (gpio_fd >= 0) close(gpio_fd);
|
||||
if (adc_fd >= 0) close(adc_fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Grab both devices (exclusive access) */
|
||||
if (ioctl(gpio_fd, EVIOCGRAB, 1) < 0)
|
||||
perror("EVIOCGRAB gpio-keys");
|
||||
if (ioctl(adc_fd, EVIOCGRAB, 1) < 0)
|
||||
perror("EVIOCGRAB adc-joystick");
|
||||
|
||||
/* Create virtual combined device */
|
||||
ufd = setup_uinput(gpio_fd, adc_fd);
|
||||
if (ufd < 0) {
|
||||
ioctl(gpio_fd, EVIOCGRAB, 0);
|
||||
ioctl(adc_fd, EVIOCGRAB, 0);
|
||||
close(gpio_fd);
|
||||
close(adc_fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Small delay for uinput device to settle */
|
||||
usleep(50000);
|
||||
|
||||
/* Signal ready: write PID file so launcher knows we're up */
|
||||
{
|
||||
int pfd = open("/run/input-merge.pid", O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (pfd >= 0) {
|
||||
char buf[16];
|
||||
int len = snprintf(buf, sizeof(buf), "%d\n", getpid());
|
||||
write(pfd, buf, len);
|
||||
close(pfd);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "input-merge: active (gpio=%d adc=%d -> %s)\n",
|
||||
gpio_fd, adc_fd, DEVICE_NAME);
|
||||
|
||||
/* epoll on both source devices */
|
||||
epfd = epoll_create1(0);
|
||||
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.fd = gpio_fd;
|
||||
epoll_ctl(epfd, EPOLL_CTL_ADD, gpio_fd, &ev);
|
||||
|
||||
ev.data.fd = adc_fd;
|
||||
epoll_ctl(epfd, EPOLL_CTL_ADD, adc_fd, &ev);
|
||||
|
||||
/* Event forwarding loop */
|
||||
while (running) {
|
||||
int n = epoll_wait(epfd, events, MAX_EVENTS, 1000);
|
||||
int i;
|
||||
for (i = 0; i < n; i++) {
|
||||
while (read(events[i].data.fd, &ie, sizeof(ie)) == sizeof(ie)) {
|
||||
write(ufd, &ie, sizeof(ie));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
ioctl(ufd, UI_DEV_DESTROY);
|
||||
close(ufd);
|
||||
ioctl(gpio_fd, EVIOCGRAB, 0);
|
||||
ioctl(adc_fd, EVIOCGRAB, 0);
|
||||
close(gpio_fd);
|
||||
close(adc_fd);
|
||||
close(epfd);
|
||||
unlink("/run/input-merge.pid");
|
||||
|
||||
fprintf(stderr, "input-merge: stopped\n");
|
||||
return 0;
|
||||
}
|
||||
+21
-13
@@ -12,7 +12,7 @@ Flow:
|
||||
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)
|
||||
- Play N beeps (N = position in list: Panel 0=1 beep, Panel 1=2 beeps, etc.)
|
||||
- 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
|
||||
@@ -65,30 +65,32 @@ MAX_CYCLES = 2 # auto-confirm default after this many full cycles
|
||||
# (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
|
||||
# Order: numerical (beep count = position in list, Panel 0 = 1 beep, etc.)
|
||||
|
||||
# R36S Original — 6 panels, default is Panel 4-V22 (~60% of units)
|
||||
# Beeps: Panel 0=1, Panel 1=2, Panel 2=3, Panel 3=4, Panel 4=5, Panel 5=6
|
||||
PANELS_ORIGINAL = [
|
||||
("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"),
|
||||
("3", "kernel-panel3.dtb", "Panel 3-V20"),
|
||||
("4", "", "Panel 4-V22 (Default)"),
|
||||
("5", "kernel-panel5.dtb", "Panel 5-V22 Q8"),
|
||||
]
|
||||
|
||||
# R36S Clone — 12 panels, default is Clone 8 ST7703 (G80CA-MB)
|
||||
# Beeps: Clone 1=1, Clone 2=2, ..., Clone 10=10, R36 Max=11, RX6S=12
|
||||
PANELS_CLONE = [
|
||||
("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)"),
|
||||
("C3", "kernel-clone3.dtb", "Clone 3 (NV3051D)"),
|
||||
("C4", "kernel-clone4.dtb", "Clone 4 (NV3051D)"),
|
||||
("C5", "kernel-clone5.dtb", "Clone 5 (ST7703)"),
|
||||
("C6", "kernel-clone6.dtb", "Clone 6 (NV3051D)"),
|
||||
("C7", "kernel-clone7.dtb", "Clone 7 (JD9365DA)"),
|
||||
("C8", "", "Clone 8 ST7703 G80CA (Default)"),
|
||||
("C9", "kernel-clone9.dtb", "Clone 9 (NV3051D)"),
|
||||
("C10", "kernel-clone10.dtb", "Clone 10 (ST7703)"),
|
||||
("MAX", "kernel-r36max.dtb", "R36 Max (720x720)"),
|
||||
("RX6S", "kernel-rx6s.dtb", "RX6S (NV3051D)"),
|
||||
]
|
||||
@@ -349,8 +351,14 @@ def main():
|
||||
|
||||
# Determine variant and panel list
|
||||
variant = get_variant()
|
||||
|
||||
# no-panel variant: panel is configured by Flasher app, not wizard
|
||||
if variant == "no-panel":
|
||||
log_boot("no-panel variant — wizard disabled (use Flasher app)")
|
||||
sys.exit(0)
|
||||
|
||||
panels = get_panels(variant)
|
||||
default_panel = panels[0] # First in list is always the default
|
||||
default_panel = next((p for p in panels if not p[1]), panels[0]) # Default = empty dtb_name
|
||||
|
||||
print(f"Arch R Panel Detection Wizard starting (variant: {variant})...")
|
||||
print(f" {len(panels)} panels available, default: {default_panel[2]}")
|
||||
@@ -388,8 +396,8 @@ def main():
|
||||
position += f" (cycle {cycle + 1})"
|
||||
write_tty(f"{position} {name}")
|
||||
|
||||
# Audio feedback: N beeps (capped at 5)
|
||||
beep_count = min(idx + 1, 5)
|
||||
# Audio feedback: N beeps (panel number + 1)
|
||||
beep_count = idx + 1
|
||||
play_beeps(beep_count, beep)
|
||||
|
||||
print(f" {position} {name} — waiting...")
|
||||
|
||||
@@ -33,6 +33,21 @@ amixer -q sset 'DAC' 80% 2>/dev/null
|
||||
# Performance governor (archr has NOPASSWD sudo for perfmax/perfnorm)
|
||||
sudo /usr/local/bin/perfmax 2>/dev/null
|
||||
|
||||
# Merge gamepad inputs: gpio-keys (buttons) + adc-joystick (analog sticks)
|
||||
# into a single virtual "Arch R Gamepad" so RetroArch sees one device.
|
||||
# Without this, RetroArch assigns each to a different player port and
|
||||
# analog sticks end up ignored (max_users=1).
|
||||
INPUT_MERGE=/usr/local/bin/input-merge
|
||||
if [ -x "$INPUT_MERGE" ]; then
|
||||
sudo "$INPUT_MERGE" &
|
||||
MERGE_PID=$!
|
||||
# Wait for virtual device to appear
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
[ -f /run/input-merge.pid ] && break
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
|
||||
# Log file — write to HOME dir (archr can write here), copy to /boot after
|
||||
# CRITICAL: /boot is FAT32 mounted as root — archr CANNOT write there directly!
|
||||
LOGFILE="$HOME/retroarch.log"
|
||||
@@ -66,6 +81,12 @@ fi
|
||||
retroarch -L "$@" >> "$LOGFILE" 2>&1
|
||||
ret=$?
|
||||
|
||||
# Stop input merger (ungrab devices so ES can use them again)
|
||||
if [ -n "$MERGE_PID" ]; then
|
||||
sudo kill "$MERGE_PID" 2>/dev/null
|
||||
wait "$MERGE_PID" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Restore ALSA mixer state after RetroArch exits (like dArkOS verifyaudio.sh)
|
||||
# RetroArch may modify mixer controls on exit — restore to known good state
|
||||
amixer -q sset 'Playback Path' SPK 2>/dev/null
|
||||
|
||||
Reference in New Issue
Block a user