fix(sync): skip soft connect on dwc2

This commit is contained in:
Brad Stein 2026-04-28 04:30:48 -03:00
parent 7002ba514a
commit b65bdfb259
9 changed files with 105 additions and 13 deletions

6
Cargo.lock generated
View File

@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lesavka_client"
version = "0.14.38"
version = "0.14.39"
dependencies = [
"anyhow",
"async-stream",
@ -1676,7 +1676,7 @@ dependencies = [
[[package]]
name = "lesavka_common"
version = "0.14.38"
version = "0.14.39"
dependencies = [
"anyhow",
"base64",
@ -1688,7 +1688,7 @@ dependencies = [
[[package]]
name = "lesavka_server"
version = "0.14.38"
version = "0.14.39"
dependencies = [
"anyhow",
"base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.14.38"
version = "0.14.39"
edition = "2024"
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.14.38"
version = "0.14.39"
edition = "2024"
build = "build.rs"

View File

@ -27,6 +27,25 @@ udc_state() {
cat "/sys/class/udc/$udc/state" 2>/dev/null || echo "unknown"
}
udc_driver() {
local udc="$1"
local driver=""
driver="$(readlink -f "/sys/bus/platform/devices/$udc/driver" 2>/dev/null || true)"
[[ -n $driver ]] || return 0
basename "$driver"
}
udc_supports_soft_connect() {
local udc="$1"
[[ -n $udc ]] || return 1
[[ -w /sys/class/udc/$udc/soft_connect ]] || return 1
[[ ${LESAVKA_FORCE_SOFT_CONNECT:-0} == 1 ]] && return 0
local driver=""
driver="$(udc_driver "$udc")"
[[ $driver == "dwc2" ]] && return 1
return 0
}
is_attached_state() {
case "$1" in
configured|addressed|default|suspended)
@ -47,7 +66,7 @@ detach_gadget() {
return 0
;;
esac
if [[ -n $udc && -w /sys/class/udc/$udc/soft_connect ]]; then
if udc_supports_soft_connect "$udc"; then
echo 0 >"/sys/class/udc/$udc/soft_connect" 2>/dev/null || true
fi
if [[ -n ${LESAVKA_DETACH_CLEAR_UDC:-} && -e $G/UDC ]]; then
@ -71,7 +90,7 @@ attach_gadget() {
log "UDC not found; need full setup"
return 1
fi
if [[ -n $udc && -w /sys/class/udc/$udc/soft_connect ]]; then
if udc_supports_soft_connect "$udc"; then
echo 1 >"/sys/class/udc/$udc/soft_connect" 2>/dev/null || true
fi
if [[ -n ${LESAVKA_ATTACH_WRITE_UDC:-} && -e $G/UDC ]]; then

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.14.38"
version = "0.14.39"
edition = "2024"
autobins = false

View File

@ -91,8 +91,10 @@ impl UsbGadget {
/* 1 - detach gadget */
info!("🔌 detaching gadget from {ctrl}");
// a) drop pull-ups (if the controller offers the switch)
let sc = format!("{}/class/udc/{ctrl}/soft_connect", Self::sysfs_root());
let _ = Self::write_attr(&sc, "0"); // ignore errors - not all HW has it
let sc = Self::soft_connect_path(&ctrl);
if let Some(sc) = sc.as_deref() {
let _ = Self::write_attr(sc, "0"); // ignore errors - not all HW has it
}
// b) clear the UDC attribute; the kernel may transiently answer EBUSY
for attempt in 1..=10 {
@ -124,9 +126,9 @@ impl UsbGadget {
/* 4 - re-attach + pull-up */
info!("🔌 re-attaching gadget to {ctrl}");
Self::write_attr(self.udc_file, &ctrl)?;
if Path::new(&sc).exists() {
if let Some(sc) = sc.as_deref() {
// try to set the pull-up; ignore if the kernel rejects it
match Self::write_attr(&sc, "1") {
match Self::write_attr(sc, "1") {
Err(err) => {
// only swallow specific errno values
if let Some(io) = err.downcast_ref::<std::io::Error>() {

View File

@ -124,4 +124,27 @@ impl UsbGadget {
Ok(None)
}
fn platform_driver(ctrl: &str) -> Option<String> {
fs::read_link(format!(
"{}/bus/platform/devices/{ctrl}/driver",
Self::sysfs_root()
))
.ok()
.and_then(|path| path.file_name().map(|name| name.to_string_lossy().into_owned()))
}
fn soft_connect_path(ctrl: &str) -> Option<String> {
let path = format!("{}/class/udc/{ctrl}/soft_connect", Self::sysfs_root());
if !Path::new(&path).exists() {
return None;
}
if env::var("LESAVKA_FORCE_SOFT_CONNECT").ok().as_deref() == Some("1") {
return Some(path);
}
if Self::platform_driver(ctrl).as_deref() == Some("dwc2") {
return None;
}
Some(path)
}
}

View File

@ -23,3 +23,18 @@ fn core_script_rebuilds_incomplete_bound_gadgets() {
);
}
}
#[test]
fn core_script_skips_soft_connect_for_dwc2() {
for expected in [
"udc_driver()",
"udc_supports_soft_connect()",
"[[ $driver == \"dwc2\" ]] && return 1",
"if udc_supports_soft_connect \"$udc\"; then",
] {
assert!(
CORE_SCRIPT.contains(expected),
"lesavka-core soft_connect guard missing: {expected}"
);
}
}

View File

@ -11,7 +11,7 @@ mod gadget_include_contract {
include!(env!("LESAVKA_SERVER_GADGET_SRC"));
use serial_test::serial;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::fs::{PermissionsExt, symlink};
use temp_env::with_var;
use tempfile::{NamedTempFile, tempdir};
@ -47,6 +47,12 @@ mod gadget_include_contract {
&base.join("sys/bus/platform/drivers/dwc2/bind"),
"placeholder\n",
);
let driver_target = base.join("sys/bus/platform/drivers/dwc2");
let driver_link = base.join(format!("sys/bus/platform/devices/{ctrl}/driver"));
if let Some(parent) = driver_link.parent() {
std::fs::create_dir_all(parent).expect("create driver link parent");
}
symlink(&driver_target, &driver_link).expect("link controller driver");
write_file(
&base.join(format!("cfg/{gadget_name}/UDC")),
&format!("{ctrl}\n"),
@ -181,6 +187,33 @@ mod gadget_include_contract {
let _ = UsbGadget::probe_platform_udc();
}
#[test]
#[serial]
fn soft_connect_path_skips_dwc2_platform_driver() {
let dir = tempdir().expect("tempdir");
let ctrl = "fake-ctrl.usb";
build_fake_tree(dir.path(), ctrl, "lesavka-test", "not attached");
with_fake_roots(&dir.path().join("sys"), &dir.path().join("cfg"), || {
assert!(UsbGadget::soft_connect_path(ctrl).is_none());
});
}
#[test]
#[serial]
fn soft_connect_path_can_be_forced_for_dwc2() {
let dir = tempdir().expect("tempdir");
let ctrl = "fake-ctrl.usb";
build_fake_tree(dir.path(), ctrl, "lesavka-test", "not attached");
with_fake_roots(&dir.path().join("sys"), &dir.path().join("cfg"), || {
with_var("LESAVKA_FORCE_SOFT_CONNECT", Some("1"), || {
let path = UsbGadget::soft_connect_path(ctrl).expect("forced soft_connect path");
assert!(path.ends_with("/soft_connect"));
});
});
}
#[test]
fn find_controller_returns_name_or_error_without_panicking() {
let result = UsbGadget::find_controller();