#!/usr/bin/env bash # lesavka‑core.sh - one‑shot USB‑gadget bring‑up (Pi‑5 / Arch‑ARM) # Presents: • Boot‑protocol keyboard (hidg0) # • Boot‑protocol mouse (hidg1) # • Stereo UAC2 speaker + microphone set -euo pipefail log() { printf '[lesavka-core] %s\n' "$*"; } #────────────────────────────────────────────────── # 1. Ensure overlay + kernel modules #────────────────────────────────────────────────── CFG=/boot/config.txt grep -q 'dtoverlay=dwc2,dr_mode=peripheral' "$CFG" || echo 'dtoverlay=dwc2,dr_mode=peripheral' >> "$CFG" modprobe dwc2 || { echo "dwc2 not in kernel; abort" >&2; exit 1; } modprobe libcomposite || { echo "libcomposite not in kernel; abort" >&2; exit 1; } modprobe -r uvcvideo 2>/dev/null || true modprobe uvcvideo || { echo "uvcvideo not in kernel; abort" >&2; exit 1; } udevadm control --reload udevadm trigger --subsystem-match=video4linux udevadm settle #────────────────────────────────────────────────── # 2. Wait for UDC device to appear (max 10 s) #────────────────────────────────────────────────── log "⏳ waiting for UDC to register ..." UDC="" for _ in {1..100}; do # 100 × 100ms = 10s UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break sleep 0.1 done if [[ -z $UDC ]]; then log "⚠️ UDC still absent - trying manual bind" 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 node=${node##*/} # strip path echo "$node" >"$drv_root/bind" 2>/dev/null || continue done done # re-check for another 5 s for i in {1..50}; do UDC=$(ls /sys/class/udc 2>/dev/null | head -n1) && [[ -n $UDC ]] && break sleep 0.1 done fi [[ -n $UDC ]] || { log "❌ UDC not present after manual bind"; exit 1; } log "✅ UDC detected: $UDC" #────────────────────────────────────────────────── # 3. (Re‑)create gadget #────────────────────────────────────────────────── mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config G=/sys/kernel/config/usb_gadget/lesavka if [[ -d $G ]]; then echo '' >"$G/UDC" 2>/dev/null || true sleep 0.2 find "$G/configs" -type l -delete 2>/dev/null || true rm -rf "$G" 2>/dev/null || true fi 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" echo "$(cat /proc/sys/kernel/random/uuid)" >"$G/strings/0x409/serialnumber" echo "Lesavka" >"$G/strings/0x409/manufacturer" echo "Lesavka Composite" >"$G/strings/0x409/product" # ----------------------- HID keyboard (usb0) ----------------------- 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" 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" # ----------------------- HID mouse (usb1) -------------------------- mkdir -p "$G/functions/hid.usb1" echo 2 > "$G/functions/hid.usb1/protocol" # Boot mouse echo 1 > "$G/functions/hid.usb1/subclass" echo 4 > "$G/functions/hid.usb1/report_length" 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" # ---------- 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 # ----------------------- UVC function (usb‑video) ------------------ mkdir -p "$G/functions/uvc.usb0" F="$G/functions/uvc.usb0" # ── 1. FORMAT DESCRIPTOR (uncompressed YUY2, 16 bpp) ────────────── mkdir -p "$F/streaming/uncompressed/u" # 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/u/guidFormat" echo 16 >"$F/streaming/uncompressed/u/bBitsPerPixel" # ── 2. FRAME DESCRIPTOR (index 1 @ 30 fps) ──────────────────────── 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" # 30 fps echo 333333 >"$F/streaming/uncompressed/u/f1/dwFrameInterval" # ── 3. REQUIRED HEADER LINKS (absolute‑paths, no “1”) ────────────── header_h="$F/streaming/header/h" # convenience variables fmt_dir="$F/streaming/uncompressed/u" 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.5 s 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" # per‑speed 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 # control‑interface header – share the very same directory mkdir -p "$F/control/header" ln -sf "$header_h" "$F/control/header/h" for s in fs hs ss; do mkdir -p "$F/control/class/$s" ln -sf "$header_h" "$F/control/class/$s/h" done # friendly label mkdir -p "$F/control/header/strings/0x409" echo "Lesavka UVC" >"$F/control/header/strings/0x409/label" # ----------------------- 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" 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/ # mkdir -p $G/functions/hid.usb0/os_desc # mkdir -p $G/functions/hid.usb1/os_desc # mkdir -p $U/os_desc # ---------- optional Microsoft OS descriptors ---------------------- # 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" # ln -s "$G/configs/c.1" "$G/os_desc" # creates $G/os_desc/conf # 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 #────────────────────────────────────────────────── # 4. Bind gadget #────────────────────────────────────────────────── echo "$UDC" >"$G/UDC" log "🎉 gadget bound on $UDC (hidg0, hidg1, UAC2 L+R, UVC)" exit 0