2025-06-01 21:28:09 -05:00
|
|
|
|
#!/usr/bin/env bash
|
2025-06-30 15:45:37 -05:00
|
|
|
|
# lesavka‑core.sh - one‑shot USB‑gadget bring‑up (Pi‑5 / Arch‑ARM)
|
2025-06-29 23:24:20 -05:00
|
|
|
|
# Presents: • Boot‑protocol keyboard (hidg0)
|
|
|
|
|
|
# • Boot‑protocol mouse (hidg1)
|
|
|
|
|
|
# • Stereo UAC2 speaker + microphone
|
2025-06-01 21:28:09 -05:00
|
|
|
|
set -euo pipefail
|
2025-06-06 20:25:26 -05:00
|
|
|
|
|
2025-06-29 23:36:01 -05:00
|
|
|
|
log() { printf '[lesavka-core] %s\n' "$*"; }
|
2026-01-07 04:47:39 -03:00
|
|
|
|
|
|
|
|
|
|
G=/sys/kernel/config/usb_gadget/lesavka
|
|
|
|
|
|
|
|
|
|
|
|
find_udc() {
|
|
|
|
|
|
ls /sys/class/udc 2>/dev/null | head -n1 || true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-08 00:20:12 -03:00
|
|
|
|
udc_state() {
|
|
|
|
|
|
local udc="$1"
|
|
|
|
|
|
if [[ -z $udc ]]; then
|
|
|
|
|
|
echo "unknown"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
cat "/sys/class/udc/$udc/state" 2>/dev/null || echo "unknown"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-08 23:58:19 -03:00
|
|
|
|
is_attached_state() {
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
configured|addressed|default|suspended)
|
|
|
|
|
|
return 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
return 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 04:47:39 -03:00
|
|
|
|
detach_gadget() {
|
|
|
|
|
|
local udc=""
|
|
|
|
|
|
udc="$(find_udc)"
|
2026-01-08 00:20:12 -03:00
|
|
|
|
local state
|
|
|
|
|
|
state="$(udc_state "$udc")"
|
|
|
|
|
|
case "$state" in
|
|
|
|
|
|
configured|addressed|default|suspended)
|
|
|
|
|
|
log "detach skipped (state=$state)"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
2026-01-07 04:47:39 -03:00
|
|
|
|
if [[ -n $udc && -w /sys/class/udc/$udc/soft_connect ]]; then
|
|
|
|
|
|
echo 0 >"/sys/class/udc/$udc/soft_connect" 2>/dev/null || true
|
|
|
|
|
|
fi
|
2026-01-08 00:20:12 -03:00
|
|
|
|
if [[ -n ${LESAVKA_DETACH_CLEAR_UDC:-} && -e $G/UDC ]]; then
|
2026-01-07 04:47:39 -03:00
|
|
|
|
echo "" >"$G/UDC" 2>/dev/null || true
|
|
|
|
|
|
fi
|
2026-01-08 00:20:12 -03:00
|
|
|
|
if [[ -n $udc ]]; then
|
2026-01-07 04:47:39 -03:00
|
|
|
|
log "detached (state=${state:-unknown})"
|
|
|
|
|
|
else
|
2026-01-08 00:20:12 -03:00
|
|
|
|
log "detached (no UDC)"
|
2026-01-07 04:47:39 -03:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
attach_gadget() {
|
|
|
|
|
|
if [[ ! -d $G ]]; then
|
|
|
|
|
|
log "gadget path missing; need full setup"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
local udc=""
|
|
|
|
|
|
udc="$(find_udc)"
|
|
|
|
|
|
if [[ -z $udc ]]; then
|
|
|
|
|
|
log "UDC not found; need full setup"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
2026-01-08 00:20:12 -03:00
|
|
|
|
if [[ -n $udc && -w /sys/class/udc/$udc/soft_connect ]]; then
|
|
|
|
|
|
echo 1 >"/sys/class/udc/$udc/soft_connect" 2>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -n ${LESAVKA_ATTACH_WRITE_UDC:-} && -e $G/UDC ]]; then
|
2026-01-07 04:47:39 -03:00
|
|
|
|
echo "$udc" >"$G/UDC" 2>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
log "attached to $udc"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case "${1:-}" in
|
|
|
|
|
|
--detach)
|
|
|
|
|
|
detach_gadget
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
;;
|
2026-01-08 00:20:12 -03:00
|
|
|
|
--detach-hard)
|
|
|
|
|
|
LESAVKA_DETACH_CLEAR_UDC=1 detach_gadget
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
;;
|
2026-01-07 04:47:39 -03:00
|
|
|
|
--attach)
|
|
|
|
|
|
if attach_gadget; then
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
;;
|
|
|
|
|
|
--help|-h)
|
2026-01-08 00:20:12 -03:00
|
|
|
|
echo "Usage: $0 [--attach|--detach|--detach-hard]"
|
2026-01-07 04:47:39 -03:00
|
|
|
|
exit 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
2026-01-09 17:55:25 -03:00
|
|
|
|
cleanup() {
|
|
|
|
|
|
if [[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; then
|
|
|
|
|
|
LESAVKA_DETACH_CLEAR_UDC=1 detach_gadget
|
|
|
|
|
|
else
|
|
|
|
|
|
detach_gadget
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
2025-06-29 23:36:01 -05:00
|
|
|
|
|
2026-01-05 14:09:52 -03:00
|
|
|
|
DISABLE_UAC=${LESAVKA_DISABLE_UAC:-}
|
|
|
|
|
|
DISABLE_UVC=${LESAVKA_DISABLE_UVC:-}
|
2026-01-09 22:50:09 -03:00
|
|
|
|
ALLOW_RESET=${LESAVKA_ALLOW_GADGET_RESET:-}
|
2026-01-05 14:09:52 -03:00
|
|
|
|
UVC_FALLBACK=${LESAVKA_UVC_FALLBACK:-1}
|
2026-01-06 09:36:55 -03:00
|
|
|
|
UVC_STREAMING_INTERVAL=${LESAVKA_UVC_STREAMING_INTERVAL:-1}
|
|
|
|
|
|
UVC_MAXPACKET=${LESAVKA_UVC_MAXPACKET:-1024}
|
|
|
|
|
|
UVC_MAXBURST=${LESAVKA_UVC_MAXBURST:-1}
|
|
|
|
|
|
UVC_INTERVAL=${LESAVKA_UVC_INTERVAL:-}
|
|
|
|
|
|
UVC_WIDTH=${LESAVKA_UVC_WIDTH:-1280}
|
|
|
|
|
|
UVC_HEIGHT=${LESAVKA_UVC_HEIGHT:-720}
|
2026-01-06 10:05:21 -03:00
|
|
|
|
UVC_FPS=${LESAVKA_UVC_FPS:-25}
|
2026-01-06 09:36:55 -03:00
|
|
|
|
UVC_DISABLE_IRQ=${LESAVKA_UVC_DISABLE_IRQ:-}
|
|
|
|
|
|
UVC_BULK=${LESAVKA_UVC_BULK:-}
|
2026-01-06 21:06:20 -03:00
|
|
|
|
UVC_CODEC=${LESAVKA_UVC_CODEC:-yuyv}
|
2026-01-10 05:27:28 -03:00
|
|
|
|
|
|
|
|
|
|
uvc_fifo_min() {
|
|
|
|
|
|
local path="$1"
|
|
|
|
|
|
local raw=""
|
|
|
|
|
|
raw="$(cat "$path" 2>/dev/null || true)"
|
|
|
|
|
|
if [[ -z $raw ]]; then
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
echo "$raw" | tr ', ' '\n' | awk 'NF{print $1}' | awk '
|
|
|
|
|
|
$1 > 0 { if (min == "" || $1 < min) min = $1 }
|
|
|
|
|
|
END { if (min != "") print min }'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 13:08:38 -03:00
|
|
|
|
uvc_fifo_min_debugfs() {
|
|
|
|
|
|
local path="$1"
|
|
|
|
|
|
awk -F': ' '/^g_tx_fifo_size\\[/{print $2}' "$path" 2>/dev/null | awk '
|
|
|
|
|
|
$1 > 0 { if (min == "" || $1 < min) min = $1 }
|
|
|
|
|
|
END { if (min != "") print min }'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uvc_fifo_np_debugfs() {
|
|
|
|
|
|
local path="$1"
|
|
|
|
|
|
awk -F': ' '/^g_np_tx_fifo_size/{print $2; exit}' "$path" 2>/dev/null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 05:27:28 -03:00
|
|
|
|
compute_uvc_payload_cap() {
|
|
|
|
|
|
UVC_PAYLOAD_CAP=""
|
|
|
|
|
|
UVC_PAYLOAD_SRC=""
|
|
|
|
|
|
UVC_PAYLOAD_PCT=""
|
|
|
|
|
|
UVC_FIFO_PERIODIC="$(uvc_fifo_min /sys/module/dwc2/parameters/g_tx_fifo_size)"
|
|
|
|
|
|
UVC_FIFO_NP="$(uvc_fifo_min /sys/module/dwc2/parameters/g_np_tx_fifo_size)"
|
2026-01-10 13:08:38 -03:00
|
|
|
|
UVC_FIFO_SRC_PERIODIC=""
|
|
|
|
|
|
UVC_FIFO_SRC_NP=""
|
|
|
|
|
|
if [[ -n $UVC_FIFO_PERIODIC ]]; then
|
|
|
|
|
|
UVC_FIFO_SRC_PERIODIC="dwc2.params"
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -n $UVC_FIFO_NP ]]; then
|
|
|
|
|
|
UVC_FIFO_SRC_NP="dwc2.params"
|
|
|
|
|
|
fi
|
|
|
|
|
|
UVC_UDC="$(ls /sys/class/udc 2>/dev/null | head -n1 || true)"
|
|
|
|
|
|
if [[ -n $UVC_UDC ]]; then
|
|
|
|
|
|
local params="/sys/kernel/debug/usb/$UVC_UDC/params"
|
|
|
|
|
|
if [[ -r $params ]]; then
|
|
|
|
|
|
if [[ -z $UVC_FIFO_PERIODIC ]]; then
|
|
|
|
|
|
UVC_FIFO_PERIODIC="$(uvc_fifo_min_debugfs "$params")"
|
|
|
|
|
|
if [[ -n $UVC_FIFO_PERIODIC ]]; then
|
|
|
|
|
|
UVC_FIFO_SRC_PERIODIC="debugfs.params"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -z $UVC_FIFO_NP ]]; then
|
|
|
|
|
|
UVC_FIFO_NP="$(uvc_fifo_np_debugfs "$params")"
|
|
|
|
|
|
if [[ -n $UVC_FIFO_NP ]]; then
|
|
|
|
|
|
UVC_FIFO_SRC_NP="debugfs.params"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
2026-01-10 05:27:28 -03:00
|
|
|
|
|
|
|
|
|
|
if [[ -n ${LESAVKA_UVC_MAXPAYLOAD_LIMIT:-} ]]; then
|
|
|
|
|
|
UVC_PAYLOAD_CAP="${LESAVKA_UVC_MAXPAYLOAD_LIMIT}"
|
|
|
|
|
|
UVC_PAYLOAD_SRC="env"
|
|
|
|
|
|
UVC_PAYLOAD_PCT=100
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
local chosen=""
|
|
|
|
|
|
if [[ -n $UVC_BULK ]]; then
|
|
|
|
|
|
if [[ -n $UVC_FIFO_NP ]]; then
|
|
|
|
|
|
chosen="$UVC_FIFO_NP"
|
2026-01-10 13:08:38 -03:00
|
|
|
|
UVC_PAYLOAD_SRC="${UVC_FIFO_SRC_NP:-dwc2.g_np_tx_fifo_size}"
|
2026-01-10 05:27:28 -03:00
|
|
|
|
elif [[ -n $UVC_FIFO_PERIODIC ]]; then
|
|
|
|
|
|
chosen="$UVC_FIFO_PERIODIC"
|
2026-01-10 13:08:38 -03:00
|
|
|
|
UVC_PAYLOAD_SRC="${UVC_FIFO_SRC_PERIODIC:-dwc2.g_tx_fifo_size}"
|
2026-01-10 05:27:28 -03:00
|
|
|
|
fi
|
|
|
|
|
|
else
|
|
|
|
|
|
if [[ -n $UVC_FIFO_PERIODIC ]]; then
|
|
|
|
|
|
chosen="$UVC_FIFO_PERIODIC"
|
2026-01-10 13:08:38 -03:00
|
|
|
|
UVC_PAYLOAD_SRC="${UVC_FIFO_SRC_PERIODIC:-dwc2.g_tx_fifo_size}"
|
2026-01-10 05:27:28 -03:00
|
|
|
|
elif [[ -n $UVC_FIFO_NP ]]; then
|
|
|
|
|
|
chosen="$UVC_FIFO_NP"
|
2026-01-10 13:08:38 -03:00
|
|
|
|
UVC_PAYLOAD_SRC="${UVC_FIFO_SRC_NP:-dwc2.g_np_tx_fifo_size}"
|
2026-01-10 05:27:28 -03:00
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -z $chosen ]]; then
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
local pct=${LESAVKA_UVC_LIMIT_PCT:-95}
|
|
|
|
|
|
if ((pct < 1)); then
|
|
|
|
|
|
pct=1
|
|
|
|
|
|
elif ((pct > 100)); then
|
|
|
|
|
|
pct=100
|
|
|
|
|
|
fi
|
|
|
|
|
|
UVC_PAYLOAD_PCT=$pct
|
|
|
|
|
|
local bytes=$((chosen * 4))
|
|
|
|
|
|
UVC_PAYLOAD_CAP=$((bytes * pct / 100))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
compute_uvc_payload_cap
|
|
|
|
|
|
if [[ -n $UVC_PAYLOAD_CAP && $UVC_PAYLOAD_CAP -gt 0 ]]; then
|
2026-01-10 13:08:38 -03:00
|
|
|
|
log "UVC fifo periodic=${UVC_FIFO_PERIODIC:-?} np=${UVC_FIFO_NP:-?} cap=${UVC_PAYLOAD_CAP}B pct=${UVC_PAYLOAD_PCT:-?} src=${UVC_PAYLOAD_SRC:-?} udc=${UVC_UDC:-?}"
|
2026-01-10 05:27:28 -03:00
|
|
|
|
if ((UVC_MAXPACKET > UVC_PAYLOAD_CAP)); then
|
|
|
|
|
|
log "clamping UVC maxpacket $UVC_MAXPACKET -> $UVC_PAYLOAD_CAP"
|
|
|
|
|
|
UVC_MAXPACKET=$UVC_PAYLOAD_CAP
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -n $UVC_BULK && $UVC_MAXPACKET -gt 512 ]]; then
|
|
|
|
|
|
log "clamping UVC maxpacket $UVC_MAXPACKET -> 512 (bulk)"
|
|
|
|
|
|
UVC_MAXPACKET=512
|
|
|
|
|
|
fi
|
2026-01-06 21:06:20 -03:00
|
|
|
|
if [[ -n ${LESAVKA_UVC_MJPEG:-} ]]; then
|
|
|
|
|
|
UVC_CODEC=mjpeg
|
|
|
|
|
|
fi
|
2026-01-06 09:36:55 -03:00
|
|
|
|
MAX_SPEED=${LESAVKA_MAX_SPEED:-high-speed}
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -z $UVC_INTERVAL ]]; then
|
|
|
|
|
|
UVC_INTERVAL=$((10000000 / UVC_FPS))
|
|
|
|
|
|
fi
|
2026-01-06 21:06:20 -03:00
|
|
|
|
UVC_FRAME_SIZE=${LESAVKA_UVC_FRAME_SIZE:-$((UVC_WIDTH * UVC_HEIGHT * 2))}
|
2026-01-05 14:09:52 -03:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-05 17:08:48 -05:00
|
|
|
|
exec 2> >(tee -a /tmp/lesavka-core.debug.$(date +%s).log)
|
|
|
|
|
|
set -x
|
|
|
|
|
|
echo "[lesavka-core] running: $0 (sha1sum=$(sha1sum "$0" | cut -d' ' -f1))"
|
|
|
|
|
|
|
2025-06-29 23:24:20 -05:00
|
|
|
|
#──────────────────────────────────────────────────
|
|
|
|
|
|
# 1. Ensure overlay + kernel modules
|
|
|
|
|
|
#──────────────────────────────────────────────────
|
2025-06-06 20:45:09 -05:00
|
|
|
|
CFG=/boot/config.txt
|
|
|
|
|
|
grep -q 'dtoverlay=dwc2,dr_mode=peripheral' "$CFG" || echo 'dtoverlay=dwc2,dr_mode=peripheral' >> "$CFG"
|
2025-06-06 20:25:26 -05:00
|
|
|
|
|
2025-06-06 20:55:01 -05:00
|
|
|
|
modprobe dwc2 || { echo "dwc2 not in kernel; abort" >&2; exit 1; }
|
2025-06-06 00:41:32 -05:00
|
|
|
|
modprobe libcomposite || { echo "libcomposite not in kernel; abort" >&2; exit 1; }
|
2025-06-25 22:48:45 -05:00
|
|
|
|
|
2026-01-09 22:50:09 -03:00
|
|
|
|
if [[ -n ${LESAVKA_RELOAD_UVCVIDEO:-} ]]; then
|
|
|
|
|
|
modprobe -r uvcvideo 2>/dev/null || true
|
|
|
|
|
|
fi
|
2025-06-23 19:44:03 -05:00
|
|
|
|
modprobe uvcvideo || { echo "uvcvideo not in kernel; abort" >&2; exit 1; }
|
2025-06-25 22:48:45 -05:00
|
|
|
|
|
2025-06-23 19:44:03 -05:00
|
|
|
|
udevadm control --reload
|
|
|
|
|
|
udevadm trigger --subsystem-match=video4linux
|
2026-01-06 21:41:32 -03:00
|
|
|
|
udevadm settle --timeout=5 || log "⚠️ udevadm settle timed out"
|
2025-06-06 20:45:09 -05:00
|
|
|
|
|
2025-06-29 23:24:20 -05:00
|
|
|
|
#──────────────────────────────────────────────────
|
|
|
|
|
|
# 2. Wait for UDC device to appear (max 10 s)
|
|
|
|
|
|
#──────────────────────────────────────────────────
|
2025-06-29 23:36:01 -05:00
|
|
|
|
log "⏳ waiting for UDC to register ..."
|
2025-06-25 22:48:45 -05:00
|
|
|
|
UDC=""
|
2025-06-25 23:06:16 -05:00
|
|
|
|
for _ in {1..100}; do # 100 × 100ms = 10s
|
|
|
|
|
|
UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break
|
2025-06-25 22:48:45 -05:00
|
|
|
|
sleep 0.1
|
|
|
|
|
|
done
|
2025-06-25 23:06:16 -05:00
|
|
|
|
|
|
|
|
|
|
if [[ -z $UDC ]]; then
|
2025-06-29 23:36:01 -05:00
|
|
|
|
log "⚠️ UDC still absent - trying manual bind"
|
2025-06-25 23:06:16 -05:00
|
|
|
|
for drv in dwc2 dwc3; do
|
|
|
|
|
|
drv_root="/sys/bus/platform/drivers/$drv"
|
|
|
|
|
|
[[ -d $drv_root ]] || continue
|
|
|
|
|
|
for node in /sys/bus/platform/devices/*usb*; do
|
2025-06-29 23:36:01 -05:00
|
|
|
|
node=${node##*/} # strip path
|
2025-06-25 23:06:16 -05:00
|
|
|
|
echo "$node" >"$drv_root/bind" 2>/dev/null || continue
|
|
|
|
|
|
done
|
|
|
|
|
|
done
|
2025-06-28 15:45:35 -05:00
|
|
|
|
# re-check for another 5 s
|
2025-06-25 23:06:16 -05:00
|
|
|
|
for i in {1..50}; do
|
|
|
|
|
|
UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break
|
|
|
|
|
|
sleep 0.1
|
|
|
|
|
|
done
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2025-06-29 23:36:01 -05:00
|
|
|
|
[[ -n $UDC ]] || { log "❌ UDC not present after manual bind"; exit 1; }
|
|
|
|
|
|
log "✅ UDC detected: $UDC"
|
2025-06-25 22:48:45 -05:00
|
|
|
|
|
2026-01-09 22:50:09 -03:00
|
|
|
|
# If a gadget is already configured, avoid tearing it down unless forced.
|
|
|
|
|
|
if [[ -d $G && -z $ALLOW_RESET ]]; then
|
|
|
|
|
|
if [[ -s $G/UDC || -d $G/configs/c.1 ]]; then
|
|
|
|
|
|
log "🔒 gadget already configured; skipping reset."
|
|
|
|
|
|
log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force rebuild."
|
|
|
|
|
|
attach_gadget || true
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-01-09 17:55:25 -03:00
|
|
|
|
# Guard against lockups: if the gadget is already bound, don't reset unless forced.
|
|
|
|
|
|
BOUND_UDC=""
|
|
|
|
|
|
if [[ -r $G/UDC ]]; then
|
|
|
|
|
|
BOUND_UDC=$(cat "$G/UDC" 2>/dev/null || true)
|
|
|
|
|
|
fi
|
2026-01-09 22:50:09 -03:00
|
|
|
|
if [[ -n $BOUND_UDC && -z $ALLOW_RESET ]]; then
|
2026-01-09 17:55:25 -03:00
|
|
|
|
log "🔒 gadget already bound to '$BOUND_UDC' - refusing reset."
|
|
|
|
|
|
log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force."
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-01-08 23:58:19 -03:00
|
|
|
|
# Guard against lockups: don't reset gadget while host is attached unless forced.
|
|
|
|
|
|
UDC_STATE="$(udc_state "$UDC")"
|
2026-01-09 22:50:09 -03:00
|
|
|
|
if [[ -z $ALLOW_RESET ]] && is_attached_state "$UDC_STATE"; then
|
2026-01-08 23:58:19 -03:00
|
|
|
|
log "🔒 UDC state is '$UDC_STATE' - refusing gadget reset while host attached."
|
|
|
|
|
|
log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force."
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2025-06-29 23:24:20 -05:00
|
|
|
|
#──────────────────────────────────────────────────
|
|
|
|
|
|
# 3. (Re‑)create gadget
|
|
|
|
|
|
#──────────────────────────────────────────────────
|
2025-06-06 20:25:26 -05:00
|
|
|
|
mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config
|
2025-06-01 21:28:09 -05:00
|
|
|
|
|
2025-06-02 20:18:19 -05:00
|
|
|
|
if [[ -d $G ]]; then
|
2025-06-06 22:10:59 -05:00
|
|
|
|
echo '' >"$G/UDC" 2>/dev/null || true
|
|
|
|
|
|
sleep 0.2
|
2026-01-06 09:36:55 -03:00
|
|
|
|
# configfs doesn't allow unlinking attribute files; remove links then rmdir.
|
|
|
|
|
|
find "$G" -type l -delete 2>/dev/null || true
|
|
|
|
|
|
for dir in "$G/functions" "$G/configs" "$G/strings" "$G/os_desc" "$G/webusb"; do
|
|
|
|
|
|
[[ -d $dir ]] || continue
|
|
|
|
|
|
find "$dir" -mindepth 1 -depth -type d -exec rmdir {} \; 2>/dev/null || true
|
|
|
|
|
|
done
|
2025-06-02 20:18:19 -05:00
|
|
|
|
fi
|
2025-06-01 21:28:09 -05:00
|
|
|
|
|
|
|
|
|
|
mkdir -p "$G"
|
|
|
|
|
|
echo 0x1d6b >"$G/idVendor" # Linux Foundation
|
|
|
|
|
|
echo 0x0104 >"$G/idProduct" # Multifunction Composite Gadget
|
|
|
|
|
|
echo 0x0200 >"$G/bcdUSB"
|
2026-01-06 09:36:55 -03:00
|
|
|
|
echo "$MAX_SPEED" >"$G/max_speed"
|
2025-06-01 21:28:09 -05:00
|
|
|
|
|
|
|
|
|
|
mkdir -p "$G/strings/0x409"
|
2025-06-06 20:25:26 -05:00
|
|
|
|
echo "$(cat /proc/sys/kernel/random/uuid)" >"$G/strings/0x409/serialnumber"
|
2025-06-23 07:18:26 -05:00
|
|
|
|
echo "Lesavka" >"$G/strings/0x409/manufacturer"
|
|
|
|
|
|
echo "Lesavka Composite" >"$G/strings/0x409/product"
|
2025-06-01 21:28:09 -05:00
|
|
|
|
|
2025-06-15 20:19:27 -05:00
|
|
|
|
# ----------------------- HID keyboard (usb0) -----------------------
|
2025-06-01 21:28:09 -05:00
|
|
|
|
mkdir -p "$G/functions/hid.usb0"
|
|
|
|
|
|
echo 1 >"$G/functions/hid.usb0/protocol"
|
|
|
|
|
|
echo 1 >"$G/functions/hid.usb0/subclass"
|
|
|
|
|
|
echo 8 >"$G/functions/hid.usb0/report_length"
|
2025-06-15 20:19:27 -05:00
|
|
|
|
printf '\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01'\
|
|
|
|
|
|
'\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x95\x05\x75\x01\x05'\
|
|
|
|
|
|
'\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x01\x95\x06\x75\x08'\
|
|
|
|
|
|
'\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' \
|
|
|
|
|
|
>"$G/functions/hid.usb0/report_desc"
|
2025-06-01 21:28:09 -05:00
|
|
|
|
|
2025-06-15 20:19:27 -05:00
|
|
|
|
# ----------------------- HID mouse (usb1) --------------------------
|
2025-06-11 22:01:16 -05:00
|
|
|
|
mkdir -p "$G/functions/hid.usb1"
|
2025-06-19 01:02:10 -05:00
|
|
|
|
echo 2 > "$G/functions/hid.usb1/protocol" # Boot mouse
|
|
|
|
|
|
echo 1 > "$G/functions/hid.usb1/subclass"
|
2025-06-11 22:01:16 -05:00
|
|
|
|
echo 4 > "$G/functions/hid.usb1/report_length"
|
2025-06-18 23:46:32 -05:00
|
|
|
|
printf '\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00'\
|
|
|
|
|
|
'\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01\x95\x03\x75\x01\x81\x02'\
|
|
|
|
|
|
'\x95\x01\x75\x05\x81\x03'\
|
|
|
|
|
|
'\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"
|
2025-06-11 22:01:16 -05:00
|
|
|
|
|
2026-01-05 14:09:52 -03:00
|
|
|
|
if [[ -z $DISABLE_UAC ]]; then
|
|
|
|
|
|
# ---------- UAC2 function - speaker + mic, 2×48 kHz 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" # 16 bit
|
|
|
|
|
|
# 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
|
2025-07-04 15:01:06 -05:00
|
|
|
|
|
2026-01-05 14:09:52 -03:00
|
|
|
|
if [[ -z $DISABLE_UVC ]]; then
|
|
|
|
|
|
# ----------------------- UVC function (usb‑video) ------------------
|
|
|
|
|
|
mkdir -p "$G/functions/uvc.usb0"
|
|
|
|
|
|
F="$G/functions/uvc.usb0"
|
2026-01-06 09:36:55 -03:00
|
|
|
|
echo "$UVC_STREAMING_INTERVAL" >"$F/streaming_interval"
|
|
|
|
|
|
echo "$UVC_MAXPACKET" >"$F/streaming_maxpacket"
|
|
|
|
|
|
echo "$UVC_MAXBURST" >"$F/streaming_maxburst"
|
|
|
|
|
|
if [[ -n $UVC_BULK ]]; then
|
|
|
|
|
|
echo 1 >"$F/streaming_bulk" 2>/dev/null || true
|
|
|
|
|
|
fi
|
2026-01-05 14:09:52 -03:00
|
|
|
|
|
2026-01-06 21:06:20 -03:00
|
|
|
|
# ── 1. FORMAT DESCRIPTOR ──────────────────────────────────────────
|
|
|
|
|
|
if [[ "$UVC_CODEC" == "mjpeg" ]]; then
|
|
|
|
|
|
mkdir -p "$F/streaming/mjpeg/m"
|
2026-01-06 21:43:50 -03:00
|
|
|
|
echo 1 >"$F/streaming/mjpeg/m/bDefaultFrameIndex" 2>/dev/null || true
|
|
|
|
|
|
echo 0 >"$F/streaming/mjpeg/m/bmaControls" 2>/dev/null || true
|
2026-01-06 21:06:20 -03:00
|
|
|
|
|
|
|
|
|
|
mkdir -p "$F/streaming/mjpeg/m/720p"
|
|
|
|
|
|
echo 0 >"$F/streaming/mjpeg/m/720p/bmCapabilities"
|
|
|
|
|
|
echo "$UVC_WIDTH" >"$F/streaming/mjpeg/m/720p/wWidth"
|
|
|
|
|
|
echo "$UVC_HEIGHT" >"$F/streaming/mjpeg/m/720p/wHeight"
|
|
|
|
|
|
echo "$UVC_FRAME_SIZE" >"$F/streaming/mjpeg/m/720p/dwMaxVideoFrameBufferSize"
|
|
|
|
|
|
echo "$UVC_INTERVAL" >"$F/streaming/mjpeg/m/720p/dwDefaultFrameInterval"
|
|
|
|
|
|
cat <<EOF >"$F/streaming/mjpeg/m/720p/dwFrameInterval"
|
|
|
|
|
|
${UVC_INTERVAL}
|
|
|
|
|
|
$((UVC_INTERVAL * 2))
|
|
|
|
|
|
EOF
|
|
|
|
|
|
else
|
|
|
|
|
|
# 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"
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$F/streaming/uncompressed/yuyv/480p"
|
|
|
|
|
|
echo "$UVC_WIDTH" >"$F/streaming/uncompressed/yuyv/480p/wWidth"
|
|
|
|
|
|
echo "$UVC_HEIGHT" >"$F/streaming/uncompressed/yuyv/480p/wHeight"
|
|
|
|
|
|
echo "$UVC_FRAME_SIZE" >"$F/streaming/uncompressed/yuyv/480p/dwMaxVideoFrameBufferSize"
|
|
|
|
|
|
echo "$UVC_INTERVAL" >"$F/streaming/uncompressed/yuyv/480p/dwDefaultFrameInterval"
|
|
|
|
|
|
cat <<EOF >"$F/streaming/uncompressed/yuyv/480p/dwFrameInterval"
|
2026-01-06 09:36:55 -03:00
|
|
|
|
${UVC_INTERVAL}
|
|
|
|
|
|
$((UVC_INTERVAL * 2))
|
2026-01-05 14:09:52 -03:00
|
|
|
|
EOF
|
2026-01-06 21:06:20 -03:00
|
|
|
|
fi
|
2026-01-05 14:09:52 -03:00
|
|
|
|
|
|
|
|
|
|
# ── 3. REQUIRED HEADER LINKS (per UVC gadget docs) ────────────────
|
|
|
|
|
|
mkdir -p "$F/streaming/header/h"
|
|
|
|
|
|
pushd "$F/streaming/header/h" >/dev/null
|
2026-01-06 21:06:20 -03:00
|
|
|
|
if [[ "$UVC_CODEC" == "mjpeg" ]]; then
|
|
|
|
|
|
ln -s ../../mjpeg/m mjpeg
|
|
|
|
|
|
else
|
|
|
|
|
|
ln -s ../../uncompressed/yuyv yuyv
|
|
|
|
|
|
fi
|
2026-01-05 14:09:52 -03:00
|
|
|
|
popd >/dev/null
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2025-07-04 14:01:06 -05:00
|
|
|
|
|
2025-07-05 17:08:48 -05:00
|
|
|
|
# ── 4. Video‑Control interface ─────────────────────────────────────
|
2026-01-05 14:09:52 -03:00
|
|
|
|
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
|
2025-07-04 13:26:32 -05:00
|
|
|
|
|
2026-01-06 09:36:55 -03:00
|
|
|
|
if [[ -n $UVC_DISABLE_IRQ ]]; then
|
|
|
|
|
|
echo 0 >"$F/control/enable_interrupt_ep" 2>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2025-07-05 17:08:48 -05:00
|
|
|
|
# optional: hide unsupported controls
|
2026-01-05 14:09:52 -03:00
|
|
|
|
echo 0 >"$F/control/terminal/camera/default/bmControls" 2>/dev/null || true
|
|
|
|
|
|
echo 0 >"$F/control/processing/default/bmControls" 2>/dev/null || true
|
2025-07-05 12:45:08 -05:00
|
|
|
|
|
2025-07-04 15:11:39 -05:00
|
|
|
|
# friendly label
|
2026-01-05 14:09:52 -03:00
|
|
|
|
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
|
2025-07-03 08:19:59 -05:00
|
|
|
|
|
2025-06-15 20:19:27 -05:00
|
|
|
|
# ----------------------- configuration -----------------------------
|
2025-06-06 23:03:17 -05:00
|
|
|
|
mkdir -p "$G/configs/c.1/strings/0x409"
|
|
|
|
|
|
echo 500 > "$G/configs/c.1/MaxPower"
|
2025-06-29 23:24:20 -05:00
|
|
|
|
# echo "Config 1" > "$G/configs/c.1/strings/0x409/configuration"
|
2026-01-05 14:09:52 -03:00
|
|
|
|
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"
|
2025-06-06 20:55:01 -05:00
|
|
|
|
|
2025-06-29 23:29:00 -05:00
|
|
|
|
ln -s $G/functions/hid.usb0 $G/configs/c.1/
|
|
|
|
|
|
ln -s $G/functions/hid.usb1 $G/configs/c.1/
|
2026-01-05 14:09:52 -03:00
|
|
|
|
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
|
2025-06-01 21:28:09 -05:00
|
|
|
|
|
2025-06-30 20:00:35 -05:00
|
|
|
|
# mkdir -p $G/functions/hid.usb0/os_desc
|
|
|
|
|
|
# mkdir -p $G/functions/hid.usb1/os_desc
|
|
|
|
|
|
# mkdir -p $U/os_desc
|
2025-06-30 19:46:44 -05:00
|
|
|
|
|
2025-06-30 19:56:33 -05:00
|
|
|
|
# ---------- optional Microsoft OS descriptors ----------------------
|
2025-06-30 20:03:52 -05:00
|
|
|
|
# if [ -e "$G/os_desc/use" ]; then
|
|
|
|
|
|
# echo 1 >"$G/os_desc/use"
|
|
|
|
|
|
# echo 0xcd >"$G/os_desc/b_vendor_code"
|
|
|
|
|
|
# echo "MSFT100" >"$G/os_desc/qw_sign"
|
2025-06-30 19:56:33 -05:00
|
|
|
|
|
2025-06-30 20:03:52 -05:00
|
|
|
|
# ln -s "$G/configs/c.1" "$G/os_desc" # creates $G/os_desc/conf
|
2025-06-30 19:56:33 -05:00
|
|
|
|
|
2025-06-30 20:03:52 -05:00
|
|
|
|
# echo "Lesavka Keyboard" >"$G/functions/hid.usb0/os_desc/interface"
|
|
|
|
|
|
# echo "Lesavka Mouse" >"$G/functions/hid.usb1/os_desc/interface"
|
|
|
|
|
|
# echo "Lesavka Mic+Spkr" >"$U/os_desc/interface"
|
|
|
|
|
|
# fi
|
2025-06-30 19:35:38 -05:00
|
|
|
|
|
2025-06-29 23:24:20 -05:00
|
|
|
|
#──────────────────────────────────────────────────
|
|
|
|
|
|
# 4. Bind gadget
|
|
|
|
|
|
#──────────────────────────────────────────────────
|
|
|
|
|
|
echo "$UDC" >"$G/UDC"
|
2026-01-05 14:09:52 -03:00
|
|
|
|
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
|
2025-06-29 23:24:20 -05:00
|
|
|
|
|
|
|
|
|
|
exit 0
|