#!/bin/sh # diskOS dev/recovery: USB CDC-ACM serial console on the device USB-C port. # Host enumerates 0525:a4a7 -> /dev/ttyACM0 -> root shell (raw, no login). # # Robust against the failure that killed the previous attempt: the stock # serial_config.sh binds the UDC exactly once and never retries, so a UDC that # registers (or a host that plugs in) a moment later was missed. This runs a # persistent supervisor: (re)bind whenever a UDC is present, keep a shell alive # on ttyGS0. All steps best-effort + logged to a persistent (rw) partition. LOG=/usr/data/fiio/usbserial.log G=/sys/kernel/config/usb_gadget/serial_demo log() { echo "$(date 2>/dev/null) $*" >> "$LOG" 2>/dev/null; } build_gadget() { [ -d /sys/kernel/config/usb_gadget ] || mount -t configfs none /sys/kernel/config 2>/dev/null [ -d "$G" ] && return 0 mkdir -p "$G" || return 1 echo 0x0525 > "$G/idVendor" echo 0xa4a7 > "$G/idProduct" echo 0x0200 > "$G/bcdUSB" echo 0x2400 > "$G/bcdDevice" echo 0x02 > "$G/bDeviceClass" echo 0x00 > "$G/bDeviceSubClass" echo 0x00 > "$G/bDeviceProtocol" mkdir -p "$G/strings/0x409" echo "INGENIC" > "$G/strings/0x409/manufacturer" echo "diskOS Serial" > "$G/strings/0x409/product" echo "diskos-serial" > "$G/strings/0x409/serialnumber" mkdir -p "$G/configs/c.1/strings/0x409" echo 120 > "$G/configs/c.1/MaxPower" echo 0x80 > "$G/configs/c.1/bmAttributes" echo "serial" > "$G/configs/c.1/strings/0x409/configuration" mkdir -p "$G/functions/acm.0" ln -sf "$G/functions/acm.0" "$G/configs/c.1/" 2>/dev/null log "gadget built" } # Best-effort nudge into peripheral mode. dr_mode=otg already auto-switches to # device when plugged into a host (CC/pin), so this is only a safety net. Only # the unambiguous "device" token is written -- never anything that could select # host mode and break enumeration. force_device_mode() { for f in /sys/devices/platform/*otg*/dwc2_mode \ /sys/devices/platform/*otg*/dwc2_force_dr_mode \ /sys/devices/platform/*/13500000.otg/dwc2_mode; do [ -e "$f" ] || continue echo device > "$f" 2>/dev/null && log "wrote device-mode to $f" done } bound() { [ -n "$(cat "$G/UDC" 2>/dev/null)" ]; } bind_when_ready() { udc=$(ls /sys/class/udc/ 2>/dev/null | head -n1) [ -n "$udc" ] || return 1 echo "$udc" > "$G/UDC" 2>/dev/null && { log "bound UDC=$udc"; return 0; } return 1 } # Delegate the ttyGS0 shell to diskos-debug (the SINGLE owner - fuser-guarded + self-respawning). # Two independent shell respawners on one tty is the classic "answers once then goes silent" wedge, # so this script no longer binds its own shell; it only builds the gadget and asks diskos-debug to # attach the one shell. Idempotent: serial-on is a no-op if a shell already owns ttyGS0. DEBUG=/usr/project/diskos-debug.sh ensure_shell() { [ -c /dev/ttyGS0 ] || return 0 [ -x "$DEBUG" ] && "$DEBUG" serial-on >/dev/null 2>&1 } main() { : > "$LOG" 2>/dev/null log "S99usbserial start" build_gadget force_device_mode while true; do bound || bind_when_ready ensure_shell sleep 2 done } case "$1" in start) main & ;; *) exit 1 ;; esac exit 0