diff --git a/Cargo.lock b/Cargo.lock index 9f76250..995009a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.22.31" +version = "0.22.32" dependencies = [ "anyhow", "async-stream", @@ -1686,7 +1686,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.22.31" +version = "0.22.32" dependencies = [ "anyhow", "base64", @@ -1698,7 +1698,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.22.31" +version = "0.22.32" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index bd842ff..f5d8038 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.22.31" +version = "0.22.32" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index fb2fb02..ca34060 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.22.31" +version = "0.22.32" edition = "2024" build = "build.rs" diff --git a/docs/operational-env.md b/docs/operational-env.md index 32f7e08..0e5bb12 100644 --- a/docs/operational-env.md +++ b/docs/operational-env.md @@ -313,7 +313,7 @@ from `LESAVKA_CLIENT_PKI_SSH_SOURCE` over SSH. Runtime clients require the insta | `LESAVKA_UVC_APP_MAX_BYTES` | server UVC appsrc memory guard; defaults to `4194304` queued bytes | | `LESAVKA_UVC_APP_MAX_TIME_NS` | server UVC appsrc memory guard; defaults to `200000000` ns of queued media | | `LESAVKA_UVC_BLOCKING` | server hardware/device override | -| `LESAVKA_UVC_BULK` | UVC transfer-mode override; defaults to `1` so patched kernels prefer reliable bulk transfer over lossy isochronous MJPEG. Set `0` to force classic isochronous descriptors | +| `LESAVKA_UVC_BULK` | UVC transfer-mode override; defaults to `1` so patched kernels prefer reliable bulk transfer over lossy isochronous MJPEG. If the live kernel does not expose `streaming_bulk`, Lesavka falls back to isochronous descriptors without the 512-byte bulk clamp. Set `0` to force classic isochronous descriptors | | `LESAVKA_UVC_BUFFER_COUNT` | UVC helper freshness override; number of queued gadget output buffers, defaults to `2` for live-call freshness | | `LESAVKA_UVC_BY_PATH_ROOT` | server hardware/device override | | `LESAVKA_UVC_CODEC` | server hardware/device override | diff --git a/scripts/daemon/lesavka-core.sh b/scripts/daemon/lesavka-core.sh index dd16980..8b01c30 100755 --- a/scripts/daemon/lesavka-core.sh +++ b/scripts/daemon/lesavka-core.sh @@ -181,8 +181,10 @@ flag_enabled() { esac } if flag_enabled "${LESAVKA_UVC_BULK:-1}"; then + UVC_BULK_REQUESTED=1 UVC_BULK=1 else + UVC_BULK_REQUESTED= UVC_BULK= fi UVC_CODEC=${LESAVKA_UVC_CODEC:-mjpeg} @@ -294,18 +296,20 @@ compute_uvc_payload_cap() { UVC_PAYLOAD_CAP=$((bytes * pct / 100)) } -compute_uvc_payload_cap -if [[ -n $UVC_PAYLOAD_CAP && $UVC_PAYLOAD_CAP -gt 0 ]]; then - 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:-?}" - if ((UVC_MAXPACKET > UVC_PAYLOAD_CAP)); then - log "clamping UVC maxpacket $UVC_MAXPACKET -> $UVC_PAYLOAD_CAP" - UVC_MAXPACKET=$UVC_PAYLOAD_CAP +apply_uvc_payload_limits() { + compute_uvc_payload_cap + if [[ -n $UVC_PAYLOAD_CAP && $UVC_PAYLOAD_CAP -gt 0 ]]; then + 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:-?}" + if ((UVC_MAXPACKET > UVC_PAYLOAD_CAP)); then + log "clamping UVC maxpacket $UVC_MAXPACKET -> $UVC_PAYLOAD_CAP" + UVC_MAXPACKET=$UVC_PAYLOAD_CAP + fi fi -fi -if [[ -n $UVC_BULK && $UVC_MAXPACKET -gt 512 ]]; then - log "clamping UVC maxpacket $UVC_MAXPACKET -> 512 (bulk)" - UVC_MAXPACKET=512 -fi + if [[ -n $UVC_BULK && $UVC_MAXPACKET -gt 512 ]]; then + log "clamping UVC maxpacket $UVC_MAXPACKET -> 512 (bulk)" + UVC_MAXPACKET=512 + fi +} if [[ -n ${LESAVKA_UVC_MJPEG:-} ]]; then UVC_CODEC=mjpeg fi @@ -588,6 +592,19 @@ if [[ -z $DISABLE_UVC ]]; then # ----------------------- UVC function (usb‑video) ------------------ mkdir -p "$G/functions/uvc.usb0" F="$G/functions/uvc.usb0" + if [[ -n $UVC_BULK_REQUESTED ]]; then + if [[ -e "$F/streaming_bulk" ]]; then + UVC_BULK=1 + else + # Some kernels do not expose the patched bulk-transfer knob. Falling + # back to isochronous must also avoid the 512-byte bulk packet clamp; + # otherwise MJPEG frames are much more likely to arrive truncated. + log "UVC bulk requested but this kernel lacks streaming_bulk; using isochronous descriptors" + UVC_BULK= + UVC_MAXPACKET=${LESAVKA_UVC_MAXPACKET:-1024} + fi + fi + apply_uvc_payload_limits echo "$UVC_STREAMING_INTERVAL" >"$F/streaming_interval" echo "$UVC_MAXPACKET" >"$F/streaming_maxpacket" echo "$UVC_MAXBURST" >"$F/streaming_maxburst" diff --git a/server/Cargo.toml b/server/Cargo.toml index 77d90fa..f2a2768 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,7 +10,7 @@ bench = false [package] name = "lesavka_server" -version = "0.22.31" +version = "0.22.32" edition = "2024" autobins = false diff --git a/server/src/bin/lesavka-uvc.real.inc b/server/src/bin/lesavka-uvc.real.inc index aa632bc..df3ecca 100644 --- a/server/src/bin/lesavka-uvc.real.inc +++ b/server/src/bin/lesavka-uvc.real.inc @@ -669,7 +669,17 @@ fn uvc_frame_size_guard_enabled() -> bool { } fn uvc_bulk_transfer_enabled() -> bool { - env_flag_enabled("LESAVKA_UVC_BULK", true) + if !env_flag_enabled("LESAVKA_UVC_BULK", true) { + return false; + } + let base = std::path::Path::new(CONFIGFS_UVC_BASE); + if base.exists() && !base.join("streaming_bulk").exists() { + eprintln!( + "[lesavka-uvc] UVC bulk requested but live configfs has no streaming_bulk; using isochronous payload sizing" + ); + return false; + } + true } fn uvc_frame_size_for_active_mode(width: u32, height: u32, fps: u32) -> u32 { diff --git a/server/src/bin/lesavka_uvc/coverage_model.rs b/server/src/bin/lesavka_uvc/coverage_model.rs index 61c99d7..1581090 100644 --- a/server/src/bin/lesavka_uvc/coverage_model.rs +++ b/server/src/bin/lesavka_uvc/coverage_model.rs @@ -19,6 +19,8 @@ const UVC_DATA_SIZE: usize = 60; const UVC_STRING_CONTROL_IDX: u8 = 0; #[cfg(coverage)] const UVC_STRING_STREAMING_IDX: u8 = 1; +#[cfg(coverage)] +const CONFIGFS_UVC_BASE: &str = "/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0"; #[cfg(coverage)] const USB_DIR_IN: u8 = 0x80; diff --git a/server/src/bin/lesavka_uvc/coverage_startup.rs b/server/src/bin/lesavka_uvc/coverage_startup.rs index 3462cae..a4fe670 100644 --- a/server/src/bin/lesavka_uvc/coverage_startup.rs +++ b/server/src/bin/lesavka_uvc/coverage_startup.rs @@ -222,7 +222,14 @@ fn uvc_frame_size_guard_enabled() -> bool { #[cfg(coverage)] fn uvc_bulk_transfer_enabled() -> bool { - env_flag_enabled("LESAVKA_UVC_BULK", true) + if !env_flag_enabled("LESAVKA_UVC_BULK", true) { + return false; + } + let base = std::path::Path::new(CONFIGFS_UVC_BASE); + if base.exists() && !base.join("streaming_bulk").exists() { + return false; + } + true } #[cfg(coverage)] diff --git a/tests/contract/scripts/daemon/server_core_script_contract.rs b/tests/contract/scripts/daemon/server_core_script_contract.rs index 1fa463e..a473090 100644 --- a/tests/contract/scripts/daemon/server_core_script_contract.rs +++ b/tests/contract/scripts/daemon/server_core_script_contract.rs @@ -97,6 +97,9 @@ fn core_script_keeps_uvc_output_on_supported_mjpeg_descriptor() { "UVC codec '$UVC_CODEC' is not supported by the MJPEG UVC helper; using mjpeg", "UVC_CODEC=mjpeg", "flag_enabled \"${LESAVKA_UVC_BULK:-1}\"", + "UVC bulk requested but this kernel lacks streaming_bulk; using isochronous descriptors", + "apply_uvc_payload_limits", + "UVC_MAXPACKET=${LESAVKA_UVC_MAXPACKET:-1024}", "uvc_mjpeg_frame_size_for_fps()", "UVC_MJPEG_BUDGET_BYTES_PER_SEC=${LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC:-10000000}", "UVC_FRAME_SIZE=\"$(uvc_mjpeg_frame_size_for_fps \"$UVC_FPS\")\"", diff --git a/tests/contract/server/uvc/server_uvc_binary_contract.rs b/tests/contract/server/uvc/server_uvc_binary_contract.rs index 2db3462..59befe2 100644 --- a/tests/contract/server/uvc/server_uvc_binary_contract.rs +++ b/tests/contract/server/uvc/server_uvc_binary_contract.rs @@ -341,6 +341,14 @@ mod uvc_binary { }); } + #[test] + fn uvc_bulk_mode_is_tied_to_live_configfs_support() { + let source = include_str!("../../../../server/src/bin/lesavka-uvc.real.inc"); + + assert!(source.contains("base.exists() && !base.join(\"streaming_bulk\").exists()")); + assert!(source.contains("using isochronous payload sizing")); + } + #[test] #[serial] fn uvc_stats_snapshot_can_be_disabled_or_written() {