From 42579d648413013b7e0b138aa4e6513280b00261 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 12 May 2026 14:39:09 -0300 Subject: [PATCH] install: detect live uac descriptor drift --- scripts/install/server.sh | 41 ++++++++++++++++++- .../install/server_install_script_contract.rs | 16 ++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/scripts/install/server.sh b/scripts/install/server.sh index ae333f8..888e79b 100755 --- a/scripts/install/server.sh +++ b/scripts/install/server.sh @@ -560,6 +560,33 @@ live_uvc_descriptor_matches_request() { [[ "$(cat "$frame_root/dwDefaultFrameInterval" 2>/dev/null || true)" == "${LESAVKA_UVC_INTERVAL:-333333}" ]] } +live_uac_descriptor_matches_request() { + if [[ -n ${LESAVKA_DISABLE_UAC:-} ]]; then + [[ ! -d /sys/kernel/config/usb_gadget/lesavka/functions/uac2.usb0 ]] + return $? + fi + + local function_root=/sys/kernel/config/usb_gadget/lesavka/functions/uac2.usb0 + [[ -d "$function_root" ]] || return 1 + + local expected_mixer=0 + if [[ ${LESAVKA_UAC_HOST_MIXER_CONTROLS:-0} == 1 ]]; then + expected_mixer=1 + fi + + for attr in p_volume_present p_mute_present c_volume_present c_mute_present; do + [[ -r "$function_root/$attr" ]] || return 1 + [[ "$(cat "$function_root/$attr" 2>/dev/null || true)" == "$expected_mixer" ]] || return 1 + done + [[ "$(cat "$function_root/p_chmask" 2>/dev/null || true)" == "3" ]] || return 1 + [[ "$(cat "$function_root/c_chmask" 2>/dev/null || true)" == "3" ]] || return 1 + [[ "$(cat "$function_root/p_srate" 2>/dev/null || true)" == "48000" ]] || return 1 + [[ "$(cat "$function_root/c_srate" 2>/dev/null || true)" == "48000" ]] || return 1 + [[ "$(cat "$function_root/p_ssize" 2>/dev/null || true)" == "2" ]] || return 1 + [[ "$(cat "$function_root/c_ssize" 2>/dev/null || true)" == "2" ]] || return 1 + [[ "$(cat "$function_root/c_sync" 2>/dev/null || true)" == "adaptive" ]] +} + udc_state() { local udc="" udc=$(ls /sys/class/udc 2>/dev/null | head -n1 || true) @@ -1547,13 +1574,17 @@ LIVE_UVC_DESCRIPTOR_MISMATCH=0 if [[ "$HOST_GADGET_PROTECTED" == "1" ]] && uvc_gadget_present && ! live_uvc_descriptor_matches_request; then LIVE_UVC_DESCRIPTOR_MISMATCH=1 fi +LIVE_UAC_DESCRIPTOR_MISMATCH=0 +if [[ "$HOST_GADGET_PROTECTED" == "1" ]] && ! live_uac_descriptor_matches_request; then + LIVE_UAC_DESCRIPTOR_MISMATCH=1 +fi LIVE_UVC_FUNCTION_MISSING=0 if [[ -z ${LESAVKA_DISABLE_UVC:-} ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]] && ! uvc_gadget_present; then LIVE_UVC_FUNCTION_MISSING=1 fi ATTACHED_UVC_CHANGE_DEFERRED=0 if [[ "$HOST_GADGET_PROTECTED" == "1" ]] \ - && { [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" == "1" ]] || [[ "$LIVE_UVC_FUNCTION_MISSING" == "1" ]]; } \ + && { [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" == "1" ]] || [[ "$LIVE_UAC_DESCRIPTOR_MISMATCH" == "1" ]] || [[ "$LIVE_UVC_FUNCTION_MISSING" == "1" ]]; } \ && { [[ "$EXPLICIT_GADGET_REBUILD" != "1" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; }; then ATTACHED_UVC_CHANGE_DEFERRED=1 fi @@ -1561,12 +1592,13 @@ ATTACHED_UVC_RESTART_DEFERRED=0 if [[ "$HOST_GADGET_PROTECTED" == "1" ]] \ && [[ "$UVC_ENV_CHANGED" == "1" ]] \ && [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" != "1" ]] \ + && [[ "$LIVE_UAC_DESCRIPTOR_MISMATCH" != "1" ]] \ && [[ "$LIVE_UVC_FUNCTION_MISSING" != "1" ]] \ && { [[ "$EXPLICIT_GADGET_REBUILD" != "1" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]; }; then ATTACHED_UVC_RESTART_DEFERRED=1 fi if [[ "$ATTACHED_UVC_CHANGE_DEFERRED" == "1" ]]; then - echo "⚠️ UVC runtime settings or live descriptors differ while the host is attached." >&2 + echo "⚠️ UVC/UAC runtime settings or live descriptors differ while the host is attached." >&2 echo " The installer will not restart lesavka-core or lesavka-uvc because that can wedge the Pi USB controller." >&2 echo " To apply descriptor-changing UVC settings, use a maintenance window with LESAVKA_ALLOW_GADGET_RESET=1 LESAVKA_FORCE_GADGET_REBUILD=1." >&2 if lesavka_server_has_active_clients && [[ "${LESAVKA_INSTALL_RESTART_WITH_CLIENTS:-0}" != "1" ]]; then @@ -1672,6 +1704,11 @@ if [[ "$LIVE_UVC_DESCRIPTOR_MISMATCH" == "1" ]] && [[ "$HOST_GADGET_PROTECTED" = GADGET_REBUILD_REASON="live UVC descriptor does not match requested codec ${INSTALL_UVC_CODEC}" echo "⚠️ live UVC descriptor does not match requested codec ${INSTALL_UVC_CODEC}; forcing a gadget rebuild so descriptors and runtime agree." fi +if [[ "$LIVE_UAC_DESCRIPTOR_MISMATCH" == "1" ]] && [[ "$HOST_GADGET_PROTECTED" == "1" ]]; then + FORCE_GADGET_REBUILD=1 + GADGET_REBUILD_REASON="live UAC descriptor does not match requested host mixer/default audio settings" + echo "⚠️ live UAC descriptor does not match requested host mixer/default audio settings; forcing a gadget rebuild so descriptors and runtime agree." +fi if [[ -n ${LESAVKA_FORCE_GADGET_REBUILD:-} ]]; then FORCE_GADGET_REBUILD=1 EXPLICIT_GADGET_REBUILD=1 diff --git a/tests/installer/scripts/install/server_install_script_contract.rs b/tests/installer/scripts/install/server_install_script_contract.rs index 240ae94..fcd0519 100644 --- a/tests/installer/scripts/install/server_install_script_contract.rs +++ b/tests/installer/scripts/install/server_install_script_contract.rs @@ -295,6 +295,22 @@ fn server_install_pins_hdmi_camera_and_display_defaults() { SERVER_INSTALL.contains("LIVE_UVC_DESCRIPTOR_MISMATCH"), "installer should catch partial previous runs where env already changed but live descriptors did not" ); + assert!( + SERVER_INSTALL.contains("live_uac_descriptor_matches_request") + && SERVER_INSTALL.contains("LIVE_UAC_DESCRIPTOR_MISMATCH") + && SERVER_INSTALL.contains("p_volume_present") + && SERVER_INSTALL.contains("c_volume_present") + && SERVER_INSTALL.contains("LESAVKA_UAC_HOST_MIXER_CONTROLS:-0"), + "installer should catch live UAC descriptor drift, including stale host mixer controls" + ); + assert!( + SERVER_INSTALL.contains( + "UVC/UAC runtime settings or live descriptors differ while the host is attached" + ) && SERVER_INSTALL.contains( + "live UAC descriptor does not match requested host mixer/default audio settings" + ), + "installer should explain when UAC descriptor drift requires a maintenance-window gadget rebuild" + ); assert!( SERVER_INSTALL.contains("LIVE_UVC_FUNCTION_MISSING"), "installer should not rebuild a missing attached UVC function without explicit maintenance-window force"