lesavka/scripts/daemon/lesavka-core.sh

205 lines
8.7 KiB
Bash
Raw Normal View History

2025-06-01 21:28:09 -05:00
#!/usr/bin/env bash
2025-06-30 15:45:37 -05:00
# lesavkacore.sh - oneshot USBgadget bringup (Pi5 / ArchARM)
2025-06-29 23:24:20 -05:00
# Presents: • Bootprotocol keyboard (hidg0)
# • Bootprotocol 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' "$*"; }
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
modprobe -r uvcvideo 2>/dev/null || true
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
udevadm settle
2025-06-06 20:45:09 -05:00
2025-06-29 23:24:20 -05:00
#──────────────────────────────────────────────────
# 2. Wait for UDC device to appear (max 10s)
#──────────────────────────────────────────────────
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 5s
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
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-23 00:26:02 -05:00
G=/sys/kernel/config/usb_gadget/lesavka
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
2025-06-06 20:55:01 -05:00
find "$G/configs" -type l -delete 2>/dev/null || true
2025-06-15 17:39:39 -05:00
rm -rf "$G" 2>/dev/null || true
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"
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) --------------------------
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"
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-30 15:45:37 -05:00
# ---------- UAC2 function - speaker + mic, 2×48kHz stereo ---------
2025-06-29 22:57:54 -05:00
mkdir -p "$G/functions/uac2.usb0"
2025-06-29 23:24:20 -05:00
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
2025-06-06 20:25:26 -05:00
2025-07-03 08:19:59 -05:00
# ----------------------- UVC function (usbvideo) ------------------
mkdir -p "$G/functions/uvc.usb0"
2025-07-04 11:06:08 -05:00
F="$G/functions/uvc.usb0"
2025-07-04 11:41:27 -05:00
# ── 1. FORMAT DESCRIPTOR (uncompressed YUY2, 16bpp) ──────────────
2025-07-04 11:19:35 -05:00
mkdir -p "$F/streaming/uncompressed/u"
2025-07-04 11:41:27 -05:00
# 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"
2025-07-04 11:19:35 -05:00
2025-07-04 11:41:27 -05:00
# ── 2. FRAME DESCRIPTOR (index1 @30fps) ────────────────────────
2025-07-04 11:19:35 -05:00
mkdir -p "$F/streaming/uncompressed/u/f1"
2025-07-04 11:41:27 -05:00
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"
2025-07-04 11:19:35 -05:00
2025-07-04 13:09:07 -05:00
# ── 3. REQUIRED HEADER LINKS ──────────────────────────────────────
2025-07-04 14:27:31 -05:00
# NOTE: do *not* try to delete the kernelmade “u1” placeholder the
# easiest way is to wipe the whole gadget at the top of the script (you
# already do that) and recreate everything cleanly.
# 3a Streaminginterface header directory
2025-07-04 14:10:21 -05:00
mkdir -p "$F/streaming/header/h"
2025-07-04 14:01:06 -05:00
2025-07-04 14:27:31 -05:00
# 3b Perspeed “class” directories *must* exist before the links
2025-07-04 14:01:06 -05:00
for s in fs hs ss; do
mkdir -p "$F/streaming/class/$s"
2025-07-04 14:27:31 -05:00
ln -sf ../../header/h "$F/streaming/class/$s/h"
2025-07-04 14:01:06 -05:00
done
2025-07-04 14:27:31 -05:00
# 3c Link format #1 (our YUY2 descriptor) into the header.
# Path is *relative* to header/h, hence ../../uncompressed/u
ln -sf ../../uncompressed/u "$F/streaming/header/h/1"
echo 1 >"$F/streaming/header/h/bNumFormats" # exactly one format
echo 0 >"$F/streaming/header/h/bmInfo"
# 3d VideoCONTROL side needs the very same header
2025-07-04 14:01:06 -05:00
mkdir -p "$F/control/header/h"
for s in fs hs ss; do
mkdir -p "$F/control/class/$s"
2025-07-04 14:27:31 -05:00
ln -sf ../../header/h "$F/control/class/$s/h"
2025-07-04 14:01:06 -05:00
done
2025-07-04 13:26:32 -05:00
2025-07-04 14:27:31 -05:00
# Friendly string for entity (unchanged)
mkdir -p "$F/control/header/strings/0x409"
echo "Lesavka UVC" >"$F/control/header/strings/0x409/label"
2025-07-04 13:26:32 -05:00
# Friendly string for the camera entity
2025-07-04 11:06:08 -05:00
mkdir -p "$F/control/header/strings/0x409"
echo "Lesavka UVC" >"$F/control/header/strings/0x409/label"
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"
echo "Config 1: HID + UAC2" >"$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/
2025-07-03 08:19:59 -05:00
ln -s $U $G/configs/c.1/
ln -s $G/functions/uvc.usb0 $G/configs/c.1/
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"
2025-07-03 09:24:57 -05:00
log "🎉 gadget bound on $UDC (hidg0, hidg1, UAC2 L+R, UVC)"
2025-06-29 23:24:20 -05:00
exit 0