core: fallback when UVC fails

This commit is contained in:
Brad Stein 2026-01-05 14:09:52 -03:00
parent 953f29dec7
commit da6aa6c425

View File

@ -8,6 +8,25 @@ set -euo pipefail
log() { printf '[lesavka-core] %s\n' "$*"; }
cleanup() { echo "" >"$G/UDC" 2>/dev/null || true; }
DISABLE_UAC=${LESAVKA_DISABLE_UAC:-}
DISABLE_UVC=${LESAVKA_DISABLE_UVC:-}
UVC_FALLBACK=${LESAVKA_UVC_FALLBACK:-1}
wait_for_enum() {
local tries=${1:-50} # 50 x 100ms = 5s
UDC_STATE="unknown"
UDC_SPEED="unknown"
for ((i=0; i<tries; i++)); do
UDC_STATE=$(cat "/sys/class/udc/$UDC/state" 2>/dev/null || echo "unknown")
UDC_SPEED=$(cat "/sys/class/udc/$UDC/current_speed" 2>/dev/null || echo "unknown")
if [[ "$UDC_STATE" != "not attached" && "$UDC_STATE" != "unknown" ]]; then
return 0
fi
sleep 0.1
done
return 1
}
exec 2> >(tee -a /tmp/lesavka-core.debug.$(date +%s).log)
set -x
echo "[lesavka-core] running: $0 (sha1sum=$(sha1sum "$0" | cut -d' ' -f1))"
@ -103,114 +122,102 @@ printf '\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00'\
'\x05\x01\x09\x30\x09\x31\x09\x38\x15\x81\x25\x7f\x75\x08\x95\x03\x81\x06'\
'\xc0\xc0' >"$G/functions/hid.usb1/report_desc"
# ---------- UAC2 function - speaker + mic, 2×48kHz stereo ---------
mkdir -p "$G/functions/uac2.usb0"
U="$G/functions/uac2.usb0"
# Playback (speaker)
echo 0x3 >"$U/p_chmask" # L+R
echo 48000 >"$U/p_srate"
echo 2 >"$U/p_ssize" # 16bit
# Capture (microphone)
echo 0x3 >"$U/c_chmask"
echo 48000 >"$U/c_srate"
echo 2 >"$U/c_ssize"
# Optional: allocate a few extra request buffers
echo 32 >"$U/req_number" 2>/dev/null || true
if [[ -z $DISABLE_UAC ]]; then
# ---------- UAC2 function - speaker + mic, 2×48kHz stereo ---------
mkdir -p "$G/functions/uac2.usb0"
U="$G/functions/uac2.usb0"
# Playback (speaker)
echo 0x3 >"$U/p_chmask" # L+R
echo 48000 >"$U/p_srate"
echo 2 >"$U/p_ssize" # 16bit
# Capture (microphone)
echo 0x3 >"$U/c_chmask"
echo 48000 >"$U/c_srate"
echo 2 >"$U/c_ssize"
# Optional: allocate a few extra request buffers
echo 32 >"$U/req_number" 2>/dev/null || true
else
log "🔇 UAC2 disabled (LESAVKA_DISABLE_UAC set)"
fi
# ----------------------- UVC function (usbvideo) ------------------
mkdir -p "$G/functions/uvc.usb0"
F="$G/functions/uvc.usb0"
if [[ -z $DISABLE_UVC ]]; then
# ----------------------- UVC function (usbvideo) ------------------
mkdir -p "$G/functions/uvc.usb0"
F="$G/functions/uvc.usb0"
# ── 1. FORMAT DESCRIPTOR (uncompressed YUY2, 16bpp) ──────────────
mkdir -p "$F/streaming/uncompressed/u"
# GUID = {59555932-0000-0010-8000-00aa00389b71} (“YUY2”) littleendian
printf '\x59\x55\x59\x32\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71' \
>"$F/streaming/uncompressed/u/guidFormat"
echo 16 >"$F/streaming/uncompressed/u/bBitsPerPixel"
# ── 1. FORMAT DESCRIPTOR (uncompressed YUY2, 16 bpp) ──────────────
mkdir -p "$F/streaming/uncompressed/yuyv"
# GUID = {59555932-0000-0010-8000-00aa00389b71} (“YUY2”) little-endian
printf '\x59\x55\x59\x32\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71' \
>"$F/streaming/uncompressed/yuyv/guidFormat"
echo 16 >"$F/streaming/uncompressed/yuyv/bBitsPerPixel"
# ── 2. FRAME DESCRIPTOR (index1 @30fps) ────────────────────────
mkdir -p "$F/streaming/uncompressed/u/f1"
echo 1280 >"$F/streaming/uncompressed/u/f1/wWidth"
echo 720 >"$F/streaming/uncompressed/u/f1/wHeight"
echo 1843200 >"$F/streaming/uncompressed/u/f1/dwMaxVideoFrameBufferSize"
echo 333333 >"$F/streaming/uncompressed/u/f1/dwDefaultFrameInterval" # 30fps
echo 333333 >"$F/streaming/uncompressed/u/f1/dwFrameInterval"
# ── 2. FRAME DESCRIPTOR (720p @ 30 fps) ───────────────────────────
mkdir -p "$F/streaming/uncompressed/yuyv/720p"
echo 1280 >"$F/streaming/uncompressed/yuyv/720p/wWidth"
echo 720 >"$F/streaming/uncompressed/yuyv/720p/wHeight"
echo 1843200 >"$F/streaming/uncompressed/yuyv/720p/dwMaxVideoFrameBufferSize"
echo 333333 >"$F/streaming/uncompressed/yuyv/720p/dwDefaultFrameInterval"
cat <<'EOF' >"$F/streaming/uncompressed/yuyv/720p/dwFrameInterval"
333333
EOF
# ── 3. REQUIRED HEADER LINKS (absolutepaths, no “1”) ──────────────
header_h="$F/streaming/header/h" # convenience variables
fmt_dir="$F/streaming/uncompressed/u"
# ── 3. REQUIRED HEADER LINKS (per UVC gadget docs) ────────────────
mkdir -p "$F/streaming/header/h"
pushd "$F/streaming/header/h" >/dev/null
ln -s ../../uncompressed/yuyv yuyv
popd >/dev/null
mkdir -p "$header_h"
# wait until the kernel has rebuilt the directory and added its attribute
# files (bmInfo is always created by the driver)
for _ in {1..50}; do
[ -e "$header_h/bmInfo" ] && break
sleep 0.010 # max 0.5s total
done
# ABSOLUTE symlink → no relative elements, name is “fmt” (not “1”)
ln -sf "$fmt_dir" "$header_h/fmt"
# echo 1 >"$header_h/bNumFormats"
# echo 0 >"$header_h/bmInfo"
# perspeed class directories (absolute links)
for s in fs hs ss; do
mkdir -p "$F/streaming/class/$s"
ln -sf "$header_h" "$F/streaming/class/$s/h"
done
for s in fs hs ss; do
mkdir -p "$F/streaming/class/$s"
pushd "$F/streaming/class/$s" >/dev/null
ln -s ../../header/h h
popd >/dev/null
done
# ── 4. VideoControl interface ─────────────────────────────────────
set +e # relax errors for configfs quirks
mkdir -p "$F/control/header/h" # real dir mandatory
mkdir -p "$F/control/class" # parent once
mkdir -p "$F/control/class/fs" "$F/control/class/hs" "$F/control/class/ss" 2>/dev/null || true
echo "[lesavka-core] ★ directory tree just before links:"
tree -L 3 "$F/control" | sed 's/^/[lesavka-core] /'
for s in fs hs ss; do
# best-effort: some UDCs reject certain speeds; skip on failure
if mkdir -p "$F/control/class/$s" 2>/dev/null; then
ln -snf "$F/control/header/h" "$F/control/class/$s/h" 2>/dev/null || \
log "⚠️ control/class/$s/h link missing (continuing)"
else
log "⚠️ skipping control/class/$s (mkdir failed)"
fi
done
for s in fs hs ss; do
[ -L "$F/control/class/$s/h" ] || log "⚠️ $s/h link missing (continuing)"
done
echo "[lesavka-core] ★ directory tree just before bind:"
tree -L 3 "$F/control" | sed 's/^/[lesavka-core] /'
for s in fs hs ss; do
[ -L "$F/control/class/$s" ] || log "⚠️ $s link missing (continuing)"
done
set -e # back to strict mode
mkdir -p "$F/control/header/h"
set +e
for s in fs hs ss; do
mkdir -p "$F/control/class/$s" 2>/dev/null || continue
pushd "$F/control/class/$s" >/dev/null
ln -s ../../header/h h 2>/dev/null || true
popd >/dev/null
done
set -e
# optional: hide unsupported controls
echo 0 >"$F/control/terminal/camera/default/bmControls" 2>/dev/null || true
echo 0 >"$F/control/processing/default/bmControls" 2>/dev/null || true
echo 0 >"$F/control/terminal/camera/default/bmControls" 2>/dev/null || true
echo 0 >"$F/control/processing/default/bmControls" 2>/dev/null || true
# friendly label
set +e
mkdir -p "$F/control/header/strings/0x409" 2>/dev/null || log "⚠️ skipping control/header strings (mkdir failed)"
echo "Lesavka UVC" >"$F/control/header/strings/0x409/label" 2>/dev/null || log "⚠️ unable to set UVC label (continuing)"
set -e
mkdir -p "$F/control/header/h/strings/0x409" 2>/dev/null || true
echo "Lesavka UVC" >"$F/control/header/h/strings/0x409/label" 2>/dev/null || true
else
log "📷 UVC disabled (LESAVKA_DISABLE_UVC set)"
fi
# ----------------------- configuration -----------------------------
mkdir -p "$G/configs/c.1/strings/0x409"
echo 500 > "$G/configs/c.1/MaxPower"
# echo "Config 1" > "$G/configs/c.1/strings/0x409/configuration"
echo "Config 1: HID + UAC2" >"$G/configs/c.1/strings/0x409/configuration"
config_label="Config 1: HID"
if [[ -z $DISABLE_UAC ]]; then
config_label+=" + UAC2"
fi
if [[ -z $DISABLE_UVC ]]; then
config_label+=" + UVC"
fi
echo "$config_label" >"$G/configs/c.1/strings/0x409/configuration"
ln -s $G/functions/hid.usb0 $G/configs/c.1/
ln -s $G/functions/hid.usb1 $G/configs/c.1/
ln -s $U $G/configs/c.1/
ln -s $G/functions/uvc.usb0 $G/configs/c.1/
if [[ -z $DISABLE_UAC ]]; then
ln -s $U $G/configs/c.1/
fi
if [[ -z $DISABLE_UVC ]]; then
ln -s $G/functions/uvc.usb0 $G/configs/c.1/
fi
# mkdir -p $G/functions/hid.usb0/os_desc
# mkdir -p $G/functions/hid.usb1/os_desc
@ -233,6 +240,19 @@ ln -s $G/functions/uvc.usb0 $G/configs/c.1/
# 4. Bind gadget
#──────────────────────────────────────────────────
echo "$UDC" >"$G/UDC"
log "🎉 gadget bound on $UDC (hidg0, hidg1, UAC2 L+R, UVC)"
parts="hidg0,hidg1"
[[ -z $DISABLE_UAC ]] && parts+=",UAC2"
[[ -z $DISABLE_UVC ]] && parts+=",UVC"
log "🎉 gadget bound on $UDC ($parts)"
if wait_for_enum 50; then
log "✅ UDC state is '$UDC_STATE' (speed=$UDC_SPEED)"
else
log "⚠️ UDC state is '$UDC_STATE' (speed=$UDC_SPEED). Host not enumerated."
if [[ -z $DISABLE_UVC && "$UVC_FALLBACK" != "0" ]]; then
log "♻️ retrying without UVC (LESAVKA_UVC_FALLBACK=0 to disable)"
exec env LESAVKA_DISABLE_UVC=1 LESAVKA_UVC_FALLBACK=0 "$0"
fi
fi
exit 0