uvc: cap payload to fifo and fix SET_CUR

This commit is contained in:
Brad Stein 2026-01-10 05:27:28 -03:00
parent c7ec6caf4d
commit b6479acf11
2 changed files with 194 additions and 5 deletions

View File

@ -118,6 +118,79 @@ UVC_FPS=${LESAVKA_UVC_FPS:-25}
UVC_DISABLE_IRQ=${LESAVKA_UVC_DISABLE_IRQ:-}
UVC_BULK=${LESAVKA_UVC_BULK:-}
UVC_CODEC=${LESAVKA_UVC_CODEC:-yuyv}
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 }'
}
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)"
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"
UVC_PAYLOAD_SRC="dwc2.g_np_tx_fifo_size"
elif [[ -n $UVC_FIFO_PERIODIC ]]; then
chosen="$UVC_FIFO_PERIODIC"
UVC_PAYLOAD_SRC="dwc2.g_tx_fifo_size"
fi
else
if [[ -n $UVC_FIFO_PERIODIC ]]; then
chosen="$UVC_FIFO_PERIODIC"
UVC_PAYLOAD_SRC="dwc2.g_tx_fifo_size"
elif [[ -n $UVC_FIFO_NP ]]; then
chosen="$UVC_FIFO_NP"
UVC_PAYLOAD_SRC="dwc2.g_np_tx_fifo_size"
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
log "UVC fifo periodic=${UVC_FIFO_PERIODIC:-?} np=${UVC_FIFO_NP:-?} cap=${UVC_PAYLOAD_CAP}B pct=${UVC_PAYLOAD_PCT:-?} src=${UVC_PAYLOAD_SRC:-?}"
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
if [[ -n ${LESAVKA_UVC_MJPEG:-} ]]; then
UVC_CODEC=mjpeg
fi

View File

@ -91,6 +91,14 @@ struct UvcConfig {
frame_size: u32,
}
struct PayloadCap {
limit: u32,
pct: u32,
source: &'static str,
periodic_dw: Option<u32>,
non_periodic_dw: Option<u32>,
}
struct UvcState {
cfg: UvcConfig,
ctrl_len: usize,
@ -103,6 +111,7 @@ struct UvcState {
struct PendingRequest {
interface: u8,
selector: u8,
expected_len: usize,
}
#[derive(Clone, Copy)]
@ -261,6 +270,32 @@ impl UvcConfig {
let interval = env_u32("LESAVKA_UVC_INTERVAL", 0);
let mut max_packet = env_u32("LESAVKA_UVC_MAXPACKET", 1024);
let frame_size = env_u32("LESAVKA_UVC_FRAME_SIZE", width * height * 2);
let bulk = env::var("LESAVKA_UVC_BULK").is_ok();
if let Some(cap) = compute_payload_cap(bulk) {
if max_packet > cap.limit {
eprintln!(
"[lesavka-uvc] payload cap {}B ({}% from {}): clamp max_packet {} -> {} (periodic_dw={:?} non_periodic_dw={:?})",
cap.limit,
cap.pct,
cap.source,
max_packet,
cap.limit,
cap.periodic_dw,
cap.non_periodic_dw
);
max_packet = cap.limit;
} else {
eprintln!(
"[lesavka-uvc] payload cap {}B ({}% from {}): max_packet {} (periodic_dw={:?} non_periodic_dw={:?})",
cap.limit,
cap.pct,
cap.source,
max_packet,
cap.periodic_dw,
cap.non_periodic_dw
);
}
}
if env::var("LESAVKA_UVC_BULK").is_ok() {
max_packet = max_packet.min(512);
} else {
@ -383,9 +418,21 @@ fn handle_setup(
let _ = send_stall(fd, uvc_send_response);
return;
}
*pending = Some(PendingRequest { interface, selector });
let len = req.w_length as usize;
let payload = vec![0u8; len.min(UVC_DATA_SIZE)];
if len > UVC_DATA_SIZE {
eprintln!(
"[lesavka-uvc] SET_CUR too large len={} (max={}); stalling",
len, UVC_DATA_SIZE
);
let _ = send_stall(fd, uvc_send_response);
return;
}
*pending = Some(PendingRequest {
interface,
selector,
expected_len: len,
});
let payload = vec![0u8; len];
let _ = send_response(fd, uvc_send_response, &payload);
if debug {
eprintln!(
@ -477,6 +524,9 @@ fn handle_data(
debug: bool,
) {
let Some(p) = pending.take() else {
if debug {
eprintln!("[lesavka-uvc] DATA with no pending request; ignoring");
}
return;
};
@ -485,6 +535,12 @@ fn handle_data(
}
let len = data.length as usize;
if debug && p.expected_len != 0 && len != p.expected_len {
eprintln!(
"[lesavka-uvc] DATA len mismatch: expected={} got={}",
p.expected_len, len
);
}
let slice = &data.data[..len.min(data.data.len())];
if debug && slice.len() >= STREAM_CTRL_SIZE_11 {
let interval = read_le32(slice, 4);
@ -524,9 +580,6 @@ fn handle_data(
payload
);
}
if std::env::var("LESAVKA_UVC_ACK_AFTER_DATA").is_ok() {
let _ = send_response(fd, uvc_send_response, &[]);
}
}
}
}
@ -711,6 +764,10 @@ fn env_u8(name: &str) -> Option<u8> {
env::var(name).ok().and_then(|v| v.parse::<u8>().ok())
}
fn env_u32_opt(name: &str) -> Option<u32> {
env::var(name).ok().and_then(|v| v.parse::<u32>().ok())
}
fn adjust_length(mut bytes: Vec<u8>, w_length: u16) -> Vec<u8> {
let want = (w_length as usize).min(UVC_DATA_SIZE);
if bytes.len() > want {
@ -739,6 +796,65 @@ fn read_le32(src: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([src[offset], src[offset + 1], src[offset + 2], src[offset + 3]])
}
fn compute_payload_cap(bulk: bool) -> Option<PayloadCap> {
if let Some(limit) = env_u32_opt("LESAVKA_UVC_MAXPAYLOAD_LIMIT") {
return Some(PayloadCap {
limit,
pct: 100,
source: "env",
periodic_dw: None,
non_periodic_dw: None,
});
}
let periodic_dw = read_fifo_min("/sys/module/dwc2/parameters/g_tx_fifo_size");
let non_periodic_dw = read_fifo_min("/sys/module/dwc2/parameters/g_np_tx_fifo_size");
let (fifo_dw, source) = if bulk {
if let Some(np) = non_periodic_dw {
(np, "dwc2.g_np_tx_fifo_size")
} else if let Some(p) = periodic_dw {
(p, "dwc2.g_tx_fifo_size")
} else {
return None;
}
} else if let Some(p) = periodic_dw {
(p, "dwc2.g_tx_fifo_size")
} else if let Some(np) = non_periodic_dw {
(np, "dwc2.g_np_tx_fifo_size")
} else {
return None;
};
let mut pct = env_u32("LESAVKA_UVC_LIMIT_PCT", 95);
if pct == 0 {
pct = 1;
} else if pct > 100 {
pct = 100;
}
let fifo_bytes = fifo_dw.saturating_mul(4);
let limit = fifo_bytes.saturating_mul(pct) / 100;
if limit == 0 {
return None;
}
Some(PayloadCap {
limit,
pct,
source,
periodic_dw,
non_periodic_dw,
})
}
fn read_fifo_min(path: &str) -> Option<u32> {
let raw = std::fs::read_to_string(path).ok()?;
raw.split(|c: char| c == ',' || c.is_whitespace())
.filter_map(|v| v.trim().parse::<u32>().ok())
.filter(|v| *v > 0)
.min()
}
const IOC_NRBITS: u8 = 8;
const IOC_TYPEBITS: u8 = 8;
const IOC_SIZEBITS: u8 = 14;