fix(uvc): keep control helper off stream queue
This commit is contained in:
parent
5eb984ce08
commit
ce8f855db2
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.14.43"
|
version = "0.14.44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1676,7 +1676,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.43"
|
version = "0.14.44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1688,7 +1688,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.43"
|
version = "0.14.44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.14.43"
|
version = "0.14.44"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.43"
|
version = "0.14.44"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ LESAVKA_UVC_WIDTH=${LESAVKA_UVC_WIDTH:-640}
|
|||||||
LESAVKA_UVC_HEIGHT=${LESAVKA_UVC_HEIGHT:-480}
|
LESAVKA_UVC_HEIGHT=${LESAVKA_UVC_HEIGHT:-480}
|
||||||
LESAVKA_UVC_CODEC=${INSTALL_UVC_CODEC}
|
LESAVKA_UVC_CODEC=${INSTALL_UVC_CODEC}
|
||||||
LESAVKA_UVC_BLOCKING=${LESAVKA_UVC_BLOCKING:-1}
|
LESAVKA_UVC_BLOCKING=${LESAVKA_UVC_BLOCKING:-1}
|
||||||
|
LESAVKA_UVC_CONTROL_READ_ONLY=${LESAVKA_UVC_CONTROL_READ_ONLY:-1}
|
||||||
LESAVKA_UVC_MAXBURST=${LESAVKA_UVC_MAXBURST:-0}
|
LESAVKA_UVC_MAXBURST=${LESAVKA_UVC_MAXBURST:-0}
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.43"
|
version = "0.14.44"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::{File, OpenOptions};
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@ -135,6 +135,7 @@ struct ConfigfsSnapshot {
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let (dev, cfg) = parse_args()?;
|
let (dev, cfg) = parse_args()?;
|
||||||
|
let _singleton = acquire_singleton_lock()?;
|
||||||
let interfaces = load_interfaces();
|
let interfaces = load_interfaces();
|
||||||
eprintln!("[lesavka-uvc] starting (dev={dev})");
|
eprintln!("[lesavka-uvc] starting (dev={dev})");
|
||||||
eprintln!(
|
eprintln!(
|
||||||
@ -425,15 +426,20 @@ fn read_interface(path: &str) -> Option<u8> {
|
|||||||
|
|
||||||
fn open_with_retry(path: &str) -> Result<std::fs::File> {
|
fn open_with_retry(path: &str) -> Result<std::fs::File> {
|
||||||
let nonblock = env::var("LESAVKA_UVC_BLOCKING").is_err();
|
let nonblock = env::var("LESAVKA_UVC_BLOCKING").is_err();
|
||||||
|
let read_only = uvc_control_read_only();
|
||||||
for attempt in 1..=200 {
|
for attempt in 1..=200 {
|
||||||
let mut opts = OpenOptions::new();
|
let mut opts = OpenOptions::new();
|
||||||
opts.read(true).write(true);
|
opts.read(true);
|
||||||
|
if !read_only {
|
||||||
|
opts.write(true);
|
||||||
|
}
|
||||||
if nonblock {
|
if nonblock {
|
||||||
opts.custom_flags(libc::O_NONBLOCK);
|
opts.custom_flags(libc::O_NONBLOCK);
|
||||||
}
|
}
|
||||||
match opts.open(path) {
|
match opts.open(path) {
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
eprintln!("[lesavka-uvc] opened {path} (attempt {attempt})");
|
let mode = if read_only { "ro" } else { "rw" };
|
||||||
|
eprintln!("[lesavka-uvc] opened {path} mode={mode} (attempt {attempt})");
|
||||||
return Ok(f);
|
return Ok(f);
|
||||||
}
|
}
|
||||||
Err(err) if err.raw_os_error() == Some(libc::ENOENT) => {
|
Err(err) if err.raw_os_error() == Some(libc::ENOENT) => {
|
||||||
@ -445,6 +451,42 @@ fn open_with_retry(path: &str) -> Result<std::fs::File> {
|
|||||||
Err(anyhow::anyhow!("timeout opening {path}"))
|
Err(anyhow::anyhow!("timeout opening {path}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn uvc_control_read_only() -> bool {
|
||||||
|
env::var("LESAVKA_UVC_CONTROL_READ_ONLY")
|
||||||
|
.ok()
|
||||||
|
.map(|value| {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
!(trimmed.eq_ignore_ascii_case("0")
|
||||||
|
|| trimmed.eq_ignore_ascii_case("false")
|
||||||
|
|| trimmed.eq_ignore_ascii_case("no")
|
||||||
|
|| trimmed.eq_ignore_ascii_case("off"))
|
||||||
|
})
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acquire_singleton_lock() -> Result<File> {
|
||||||
|
let path =
|
||||||
|
env::var("LESAVKA_UVC_LOCK_PATH").unwrap_or_else(|_| "/run/lesavka-uvc.lock".to_string());
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)
|
||||||
|
.with_context(|| format!("open singleton lock {path}"))?;
|
||||||
|
let rc = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
|
||||||
|
if rc < 0 {
|
||||||
|
let err = std::io::Error::last_os_error();
|
||||||
|
match err.raw_os_error() {
|
||||||
|
Some(code) if code == libc::EWOULDBLOCK || code == libc::EAGAIN => {
|
||||||
|
eprintln!("[lesavka-uvc] another helper already owns {path}; exiting");
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
_ => return Err(err).with_context(|| format!("lock singleton {path}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
fn subscribe_event(fd: i32, req: libc::c_ulong, event: u32) -> Result<()> {
|
fn subscribe_event(fd: i32, req: libc::c_ulong, event: u32) -> Result<()> {
|
||||||
let mut sub = V4l2EventSubscription {
|
let mut sub = V4l2EventSubscription {
|
||||||
type_: event,
|
type_: event,
|
||||||
@ -951,14 +993,15 @@ fn compute_payload_cap(bulk: bool) -> Option<PayloadCap> {
|
|||||||
let mut non_periodic =
|
let mut non_periodic =
|
||||||
read_fifo_min("/sys/module/dwc2/parameters/g_np_tx_fifo_size").map(|v| (v, "dwc2.params"));
|
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 (periodic.is_none() || non_periodic.is_none())
|
||||||
&& let Some((p, np)) = read_debugfs_fifos() {
|
&& let Some((p, np)) = read_debugfs_fifos()
|
||||||
if periodic.is_none() {
|
{
|
||||||
periodic = p.map(|v| (v, "debugfs.params"));
|
if periodic.is_none() {
|
||||||
}
|
periodic = p.map(|v| (v, "debugfs.params"));
|
||||||
if non_periodic.is_none() {
|
|
||||||
non_periodic = np.map(|v| (v, "debugfs.params"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if non_periodic.is_none() {
|
||||||
|
non_periodic = np.map(|v| (v, "debugfs.params"));
|
||||||
|
}
|
||||||
|
}
|
||||||
let periodic_dw = periodic.map(|(v, _)| v);
|
let periodic_dw = periodic.map(|(v, _)| v);
|
||||||
let non_periodic_dw = non_periodic.map(|(v, _)| v);
|
let non_periodic_dw = non_periodic.map(|(v, _)| v);
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
|
|||||||
"LESAVKA_UVC_WIDTH=",
|
"LESAVKA_UVC_WIDTH=",
|
||||||
"LESAVKA_UVC_HEIGHT=",
|
"LESAVKA_UVC_HEIGHT=",
|
||||||
"LESAVKA_UVC_CODEC=",
|
"LESAVKA_UVC_CODEC=",
|
||||||
|
"LESAVKA_UVC_CONTROL_READ_ONLY=",
|
||||||
] {
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
SERVER_INSTALL.contains(expected),
|
SERVER_INSTALL.contains(expected),
|
||||||
@ -52,6 +53,7 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
|
|||||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_INTERVAL:-500000}"));
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_INTERVAL:-500000}"));
|
||||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_WIDTH:-640}"));
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_WIDTH:-640}"));
|
||||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEIGHT:-480}"));
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEIGHT:-480}"));
|
||||||
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_CONTROL_READ_ONLY:-1}"));
|
||||||
assert!(
|
assert!(
|
||||||
!SERVER_INSTALL.contains("LESAVKA_UVC_CODEC=${LESAVKA_UVC_CODEC:-mjpeg}"),
|
!SERVER_INSTALL.contains("LESAVKA_UVC_CODEC=${LESAVKA_UVC_CODEC:-mjpeg}"),
|
||||||
"install script should not let ambient LESAVKA_UVC_CODEC leak into persisted defaults"
|
"install script should not let ambient LESAVKA_UVC_CODEC leak into persisted defaults"
|
||||||
|
|||||||
@ -311,6 +311,22 @@ mod uvc_binary {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn uvc_control_open_mode_defaults_read_only_with_escape_hatch() {
|
||||||
|
with_var("LESAVKA_UVC_CONTROL_READ_ONLY", None::<&str>, || {
|
||||||
|
assert!(uvc_control_read_only());
|
||||||
|
});
|
||||||
|
for disabled in ["0", "false", "no", "off"] {
|
||||||
|
with_var("LESAVKA_UVC_CONTROL_READ_ONLY", Some(disabled), || {
|
||||||
|
assert!(!uvc_control_read_only());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
with_var("LESAVKA_UVC_CONTROL_READ_ONLY", Some("1"), || {
|
||||||
|
assert!(uvc_control_read_only());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interface_helpers_and_configfs_snapshot_are_stable_without_sysfs() {
|
fn interface_helpers_and_configfs_snapshot_are_stable_without_sysfs() {
|
||||||
let tmp = NamedTempFile::new().expect("tmp");
|
let tmp = NamedTempFile::new().expect("tmp");
|
||||||
|
|||||||
@ -73,9 +73,11 @@ fn uvc_binary_applies_env_config_and_fails_fast_on_non_v4l2_node() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let fake_device = NamedTempFile::new().expect("temp device");
|
let fake_device = NamedTempFile::new().expect("temp device");
|
||||||
|
let lock = NamedTempFile::new().expect("temp lock");
|
||||||
let child = Command::new(Path::new(&bin))
|
let child = Command::new(Path::new(&bin))
|
||||||
.arg("--device")
|
.arg("--device")
|
||||||
.arg(fake_device.path())
|
.arg(fake_device.path())
|
||||||
|
.env("LESAVKA_UVC_LOCK_PATH", lock.path())
|
||||||
.env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "256")
|
.env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "256")
|
||||||
.env("LESAVKA_UVC_MAXPACKET", "4096")
|
.env("LESAVKA_UVC_MAXPACKET", "4096")
|
||||||
.env("LESAVKA_UVC_BULK", "1")
|
.env("LESAVKA_UVC_BULK", "1")
|
||||||
@ -98,8 +100,10 @@ fn uvc_binary_accepts_positional_device_argument() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let fake_device = NamedTempFile::new().expect("temp device");
|
let fake_device = NamedTempFile::new().expect("temp device");
|
||||||
|
let lock = NamedTempFile::new().expect("temp lock");
|
||||||
let child = Command::new(Path::new(&bin))
|
let child = Command::new(Path::new(&bin))
|
||||||
.arg(fake_device.path())
|
.arg(fake_device.path())
|
||||||
|
.env("LESAVKA_UVC_LOCK_PATH", lock.path())
|
||||||
.env_remove("LESAVKA_UVC_BULK")
|
.env_remove("LESAVKA_UVC_BULK")
|
||||||
.env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "2048")
|
.env("LESAVKA_UVC_MAXPAYLOAD_LIMIT", "2048")
|
||||||
.env("LESAVKA_UVC_MAXPACKET", "1024")
|
.env("LESAVKA_UVC_MAXPACKET", "1024")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user