uvc: log configfs snapshot
This commit is contained in:
parent
1dc3bab3aa
commit
dd69d7e378
@ -9,6 +9,11 @@ log() { printf '[lesavka-core] %s\n' "$*"; }
|
||||
|
||||
G=/sys/kernel/config/usb_gadget/lesavka
|
||||
|
||||
if [[ -r /etc/lesavka/uvc.env ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/lesavka/uvc.env
|
||||
fi
|
||||
|
||||
find_udc() {
|
||||
ls /sys/class/udc 2>/dev/null | head -n1 || true
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ const UVC_EVENT_DATA: u32 = V4L2_EVENT_PRIVATE_START + 5;
|
||||
const UVC_STRING_CONTROL_IDX: u8 = 0;
|
||||
const UVC_STRING_STREAMING_IDX: u8 = 1;
|
||||
|
||||
const CONFIGFS_UVC_BASE: &str = "/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0";
|
||||
|
||||
const USB_DIR_IN: u8 = 0x80;
|
||||
|
||||
const UVC_SET_CUR: u8 = 0x01;
|
||||
@ -105,6 +107,7 @@ struct UvcState {
|
||||
default: [u8; STREAM_CTRL_SIZE_MAX],
|
||||
probe: [u8; STREAM_CTRL_SIZE_MAX],
|
||||
commit: [u8; STREAM_CTRL_SIZE_MAX],
|
||||
cfg_snapshot: Option<ConfigfsSnapshot>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@ -120,6 +123,16 @@ struct UvcInterfaces {
|
||||
streaming: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
struct ConfigfsSnapshot {
|
||||
width: u32,
|
||||
height: u32,
|
||||
default_interval: u32,
|
||||
frame_interval: u32,
|
||||
maxpacket: u32,
|
||||
maxburst: u32,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let (dev, cfg) = parse_args()?;
|
||||
let interfaces = load_interfaces();
|
||||
@ -219,10 +232,7 @@ fn main() -> Result<()> {
|
||||
let data = parse_request_data(event_bytes(&ev));
|
||||
data_seen += 1;
|
||||
if debug || data_seen <= 10 {
|
||||
eprintln!(
|
||||
"[lesavka-uvc] data #{data_seen} len={}",
|
||||
data.length
|
||||
);
|
||||
eprintln!("[lesavka-uvc] data #{data_seen} len={}", data.length);
|
||||
}
|
||||
handle_data(
|
||||
fd,
|
||||
@ -302,7 +312,7 @@ impl UvcConfig {
|
||||
);
|
||||
}
|
||||
if let Some(cfg_max) = read_u32_file(
|
||||
"/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0/streaming_maxpacket",
|
||||
&format!("{CONFIGFS_UVC_BASE}/streaming_maxpacket"),
|
||||
) {
|
||||
if max_packet > cfg_max {
|
||||
eprintln!(
|
||||
@ -350,13 +360,18 @@ impl UvcState {
|
||||
default,
|
||||
probe: default,
|
||||
commit: default,
|
||||
cfg_snapshot: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_interfaces() -> UvcInterfaces {
|
||||
let control = env_u8("LESAVKA_UVC_CTRL_INTF")
|
||||
.or_else(|| read_interface("/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0/control/bInterfaceNumber"))
|
||||
.or_else(|| {
|
||||
read_interface(
|
||||
"/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0/control/bInterfaceNumber",
|
||||
)
|
||||
})
|
||||
.unwrap_or(UVC_STRING_CONTROL_IDX);
|
||||
let streaming = env_u8("LESAVKA_UVC_STREAM_INTF")
|
||||
.or_else(|| read_interface("/sys/kernel/config/usb_gadget/lesavka/functions/uvc.usb0/streaming/bInterfaceNumber"))
|
||||
@ -401,9 +416,8 @@ fn subscribe_event(fd: i32, req: libc::c_ulong, event: u32) -> Result<()> {
|
||||
};
|
||||
let rc = unsafe { libc::ioctl(fd, req, &mut sub) };
|
||||
if rc < 0 {
|
||||
return Err(std::io::Error::last_os_error()).with_context(|| {
|
||||
format!("subscribe event {event:#x} (fd={fd})")
|
||||
});
|
||||
return Err(std::io::Error::last_os_error())
|
||||
.with_context(|| format!("subscribe event {event:#x} (fd={fd})"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -420,14 +434,14 @@ fn handle_setup(
|
||||
let selector = (req.w_value >> 8) as u8;
|
||||
let interface_lo = (req.w_index & 0xff) as u8;
|
||||
let interface_hi = (req.w_index >> 8) as u8;
|
||||
let interface_raw = if interface_hi == interfaces.streaming || interface_hi == interfaces.control
|
||||
{
|
||||
interface_hi
|
||||
} else if interface_lo == interfaces.streaming || interface_lo == interfaces.control {
|
||||
interface_lo
|
||||
} else {
|
||||
interface_hi
|
||||
};
|
||||
let interface_raw =
|
||||
if interface_hi == interfaces.streaming || interface_hi == interfaces.control {
|
||||
interface_hi
|
||||
} else if interface_lo == interfaces.streaming || interface_lo == interfaces.control {
|
||||
interface_lo
|
||||
} else {
|
||||
interface_hi
|
||||
};
|
||||
let is_in = (req.b_request_type & USB_DIR_IN) != 0;
|
||||
if matches!(selector, UVC_VS_PROBE_CONTROL | UVC_VS_COMMIT_CONTROL) {
|
||||
maybe_update_ctrl_len(state, req.w_length, debug);
|
||||
@ -435,11 +449,23 @@ fn handle_setup(
|
||||
let interface = map_interface(interface_raw, selector, interfaces, debug);
|
||||
|
||||
if !is_in && req.b_request == UVC_SET_CUR {
|
||||
let len = req.w_length as usize;
|
||||
if interface == interfaces.control {
|
||||
// Accept SET_CUR on VC controls by replying with a zeroed payload.
|
||||
let payload = vec![0u8; len.min(UVC_DATA_SIZE)];
|
||||
let _ = send_response(fd, uvc_send_response, &payload);
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[lesavka-uvc] VC SET_CUR ack len={} iface={} sel={}",
|
||||
req.w_length, interface, selector
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if interface != interfaces.streaming {
|
||||
let _ = send_stall(fd, uvc_send_response);
|
||||
return;
|
||||
}
|
||||
let len = req.w_length as usize;
|
||||
if len > UVC_DATA_SIZE {
|
||||
eprintln!(
|
||||
"[lesavka-uvc] SET_CUR too large len={} (max={}); stalling",
|
||||
@ -568,10 +594,7 @@ fn handle_data(
|
||||
let payload = read_le32(slice, 22);
|
||||
eprintln!(
|
||||
"[lesavka-uvc] data ctrl fmt={} frame={} interval={} payload={}",
|
||||
slice[2],
|
||||
slice[3],
|
||||
interval,
|
||||
payload
|
||||
slice[2], slice[3], interval, payload
|
||||
);
|
||||
}
|
||||
|
||||
@ -586,9 +609,9 @@ fn handle_data(
|
||||
let payload = read_le32(&state.probe, 22);
|
||||
eprintln!(
|
||||
"[lesavka-uvc] probe set interval={} payload={}",
|
||||
interval,
|
||||
payload
|
||||
interval, payload
|
||||
);
|
||||
log_configfs_snapshot(state, "probe");
|
||||
}
|
||||
} else {
|
||||
state.commit = sanitized;
|
||||
@ -597,9 +620,9 @@ fn handle_data(
|
||||
let payload = read_le32(&state.commit, 22);
|
||||
eprintln!(
|
||||
"[lesavka-uvc] commit set interval={} payload={}",
|
||||
interval,
|
||||
payload
|
||||
interval, payload
|
||||
);
|
||||
log_configfs_snapshot(state, "commit");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -624,11 +647,7 @@ fn build_in_response(
|
||||
Some(adjust_length(payload, w_length))
|
||||
}
|
||||
|
||||
fn build_streaming_response(
|
||||
state: &UvcState,
|
||||
selector: u8,
|
||||
request: u8,
|
||||
) -> Option<Vec<u8>> {
|
||||
fn build_streaming_response(state: &UvcState, selector: u8, request: u8) -> Option<Vec<u8>> {
|
||||
let current = match selector {
|
||||
UVC_VS_PROBE_CONTROL => state.probe,
|
||||
UVC_VS_COMMIT_CONTROL => state.commit,
|
||||
@ -636,7 +655,7 @@ fn build_streaming_response(
|
||||
};
|
||||
|
||||
match request {
|
||||
UVC_GET_INFO => Some(vec![0x03]),
|
||||
UVC_GET_INFO => Some(vec![0x03]), // support GET/SET
|
||||
UVC_GET_LEN => Some((state.ctrl_len as u16).to_le_bytes().to_vec()),
|
||||
UVC_GET_CUR => Some(current[..state.ctrl_len].to_vec()),
|
||||
UVC_GET_MIN | UVC_GET_MAX | UVC_GET_DEF | UVC_GET_RES => {
|
||||
@ -648,12 +667,14 @@ fn build_streaming_response(
|
||||
|
||||
fn build_control_response(selector: u8, request: u8) -> Option<Vec<u8>> {
|
||||
match request {
|
||||
UVC_GET_INFO => Some(vec![0x01]),
|
||||
UVC_GET_INFO => Some(vec![0x03]), // indicate both GET/SET supported
|
||||
UVC_GET_LEN => Some(1u16.to_le_bytes().to_vec()),
|
||||
UVC_GET_CUR | UVC_GET_MIN | UVC_GET_MAX | UVC_GET_DEF | UVC_GET_RES => {
|
||||
if selector == UVC_VC_REQUEST_ERROR_CODE_CONTROL {
|
||||
// reset error code to “no error”
|
||||
Some(vec![0x00])
|
||||
} else {
|
||||
// simple 1‑byte placeholder for unhandled VC controls
|
||||
Some(vec![0x00])
|
||||
}
|
||||
}
|
||||
@ -661,10 +682,7 @@ fn build_control_response(selector: u8, request: u8) -> Option<Vec<u8>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_streaming_control(
|
||||
data: &[u8],
|
||||
state: &UvcState,
|
||||
) -> [u8; STREAM_CTRL_SIZE_MAX] {
|
||||
fn sanitize_streaming_control(data: &[u8], state: &UvcState) -> [u8; STREAM_CTRL_SIZE_MAX] {
|
||||
let mut out = state.default;
|
||||
if data.len() >= STREAM_CTRL_SIZE_11 {
|
||||
let format_index = data[2];
|
||||
@ -736,10 +754,10 @@ fn build_streaming_control(cfg: &UvcConfig, ctrl_len: usize) -> [u8; STREAM_CTRL
|
||||
write_le32(&mut buf[22..26], cfg.max_packet);
|
||||
if ctrl_len >= STREAM_CTRL_SIZE_15 {
|
||||
write_le32(&mut buf[26..30], 48_000_000);
|
||||
buf[30] = 0;
|
||||
buf[31] = 0;
|
||||
buf[32] = 0;
|
||||
buf[33] = 0;
|
||||
buf[30] = 0x03; // bmFramingInfo: FID + EOF supported
|
||||
buf[31] = 0x01; // bPreferedVersion
|
||||
buf[32] = 0x01; // bMinVersion
|
||||
buf[33] = 0x01; // bMaxVersion
|
||||
}
|
||||
|
||||
buf
|
||||
@ -795,6 +813,52 @@ fn read_u32_file(path: &str) -> Option<u32> {
|
||||
.and_then(|v| v.trim().parse::<u32>().ok())
|
||||
}
|
||||
|
||||
fn read_u32_first(path: &str) -> Option<u32> {
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|v| v.split_whitespace().next()?.parse::<u32>().ok())
|
||||
}
|
||||
|
||||
fn read_configfs_snapshot() -> Option<ConfigfsSnapshot> {
|
||||
let width = read_u32_file(&format!("{CONFIGFS_UVC_BASE}/streaming/mjpeg/m/720p/wWidth"))?;
|
||||
let height = read_u32_file(&format!("{CONFIGFS_UVC_BASE}/streaming/mjpeg/m/720p/wHeight"))?;
|
||||
let default_interval =
|
||||
read_u32_file(&format!("{CONFIGFS_UVC_BASE}/streaming/mjpeg/m/720p/dwDefaultFrameInterval"))?;
|
||||
let frame_interval =
|
||||
read_u32_first(&format!("{CONFIGFS_UVC_BASE}/streaming/mjpeg/m/720p/dwFrameInterval"))
|
||||
.unwrap_or(0);
|
||||
let maxpacket = read_u32_file(&format!("{CONFIGFS_UVC_BASE}/streaming_maxpacket"))?;
|
||||
let maxburst = read_u32_file(&format!("{CONFIGFS_UVC_BASE}/streaming_maxburst")).unwrap_or(0);
|
||||
Some(ConfigfsSnapshot {
|
||||
width,
|
||||
height,
|
||||
default_interval,
|
||||
frame_interval,
|
||||
maxpacket,
|
||||
maxburst,
|
||||
})
|
||||
}
|
||||
|
||||
fn log_configfs_snapshot(state: &mut UvcState, label: &str) {
|
||||
let Some(current) = read_configfs_snapshot() else {
|
||||
eprintln!("[lesavka-uvc] configfs {label}: unavailable");
|
||||
return;
|
||||
};
|
||||
if state.cfg_snapshot == Some(current) {
|
||||
return;
|
||||
}
|
||||
eprintln!(
|
||||
"[lesavka-uvc] configfs {label}: {}x{} default_interval={} frame_interval={} maxpacket={} maxburst={}",
|
||||
current.width,
|
||||
current.height,
|
||||
current.default_interval,
|
||||
current.frame_interval,
|
||||
current.maxpacket,
|
||||
current.maxburst
|
||||
);
|
||||
state.cfg_snapshot = Some(current);
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -820,7 +884,12 @@ fn write_le32(dst: &mut [u8], val: u32) {
|
||||
}
|
||||
|
||||
fn read_le32(src: &[u8], offset: usize) -> u32 {
|
||||
u32::from_le_bytes([src[offset], src[offset + 1], src[offset + 2], src[offset + 3]])
|
||||
u32::from_le_bytes([
|
||||
src[offset],
|
||||
src[offset + 1],
|
||||
src[offset + 2],
|
||||
src[offset + 3],
|
||||
])
|
||||
}
|
||||
|
||||
fn compute_payload_cap(bulk: bool) -> Option<PayloadCap> {
|
||||
@ -834,10 +903,10 @@ fn compute_payload_cap(bulk: bool) -> Option<PayloadCap> {
|
||||
});
|
||||
}
|
||||
|
||||
let mut periodic = read_fifo_min("/sys/module/dwc2/parameters/g_tx_fifo_size")
|
||||
.map(|v| (v, "dwc2.params"));
|
||||
let mut non_periodic = read_fifo_min("/sys/module/dwc2/parameters/g_np_tx_fifo_size")
|
||||
.map(|v| (v, "dwc2.params"));
|
||||
let mut periodic =
|
||||
read_fifo_min("/sys/module/dwc2/parameters/g_tx_fifo_size").map(|v| (v, "dwc2.params"));
|
||||
let mut non_periodic =
|
||||
read_fifo_min("/sys/module/dwc2/parameters/g_np_tx_fifo_size").map(|v| (v, "dwc2.params"));
|
||||
if periodic.is_none() || non_periodic.is_none() {
|
||||
if let Some((p, np)) = read_debugfs_fifos() {
|
||||
if periodic.is_none() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user