server: guard gadget cycling
This commit is contained in:
parent
0da8fbb5f7
commit
4e96c271ed
@ -22,6 +22,15 @@ udc_state() {
|
|||||||
cat "/sys/class/udc/$udc/state" 2>/dev/null || echo "unknown"
|
cat "/sys/class/udc/$udc/state" 2>/dev/null || echo "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_attached_state() {
|
||||||
|
case "$1" in
|
||||||
|
configured|addressed|default|suspended)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
detach_gadget() {
|
detach_gadget() {
|
||||||
local udc=""
|
local udc=""
|
||||||
udc="$(find_udc)"
|
udc="$(find_udc)"
|
||||||
@ -177,6 +186,14 @@ fi
|
|||||||
[[ -n $UDC ]] || { log "❌ UDC not present after manual bind"; exit 1; }
|
[[ -n $UDC ]] || { log "❌ UDC not present after manual bind"; exit 1; }
|
||||||
log "✅ UDC detected: $UDC"
|
log "✅ UDC detected: $UDC"
|
||||||
|
|
||||||
|
# Guard against lockups: don't reset gadget while host is attached unless forced.
|
||||||
|
UDC_STATE="$(udc_state "$UDC")"
|
||||||
|
if [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]] && is_attached_state "$UDC_STATE"; then
|
||||||
|
log "🔒 UDC state is '$UDC_STATE' - refusing gadget reset while host attached."
|
||||||
|
log " Set LESAVKA_ALLOW_GADGET_RESET=1 to force."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
#──────────────────────────────────────────────────
|
#──────────────────────────────────────────────────
|
||||||
# 3. (Re‑)create gadget
|
# 3. (Re‑)create gadget
|
||||||
#──────────────────────────────────────────────────
|
#──────────────────────────────────────────────────
|
||||||
|
|||||||
@ -5,6 +5,25 @@ ORIG_USER=${SUDO_USER:-$(id -un)}
|
|||||||
|
|
||||||
REF=${LESAVKA_REF:-master} # fallback
|
REF=${LESAVKA_REF:-master} # fallback
|
||||||
|
|
||||||
|
udc_state() {
|
||||||
|
local udc=""
|
||||||
|
udc=$(ls /sys/class/udc 2>/dev/null | head -n1 || true)
|
||||||
|
if [[ -z $udc ]]; then
|
||||||
|
echo "unknown"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
cat "/sys/class/udc/$udc/state" 2>/dev/null || echo "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_attached_state() {
|
||||||
|
case "$1" in
|
||||||
|
configured|addressed|default|suspended|unknown)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
-r|--ref) REF="$2"; shift 2 ;;
|
-r|--ref) REF="$2"; shift 2 ;;
|
||||||
@ -192,9 +211,16 @@ UNIT
|
|||||||
echo "==> 6c. Systemd units - initialization"
|
echo "==> 6c. Systemd units - initialization"
|
||||||
sudo truncate -s 0 /tmp/lesavka-server.log
|
sudo truncate -s 0 /tmp/lesavka-server.log
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable --now lesavka-core
|
sudo systemctl enable lesavka-core lesavka-uvc lesavka-server
|
||||||
sudo systemctl restart lesavka-core
|
|
||||||
echo "✅ lesavka-core installed and restarted..."
|
UDC_STATE=$(udc_state)
|
||||||
|
if [[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || ! is_attached_state "$UDC_STATE"; then
|
||||||
|
sudo systemctl restart lesavka-core
|
||||||
|
echo "✅ lesavka-core installed and restarted..."
|
||||||
|
else
|
||||||
|
echo "⚠️ UDC state is '$UDC_STATE' - skipping lesavka-core restart."
|
||||||
|
echo " Set LESAVKA_ALLOW_GADGET_RESET=1 to force."
|
||||||
|
fi
|
||||||
|
|
||||||
cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-uvc.service >/dev/null
|
cat <<'UNIT' | sudo tee /etc/systemd/system/lesavka-uvc.service >/dev/null
|
||||||
[Unit]
|
[Unit]
|
||||||
@ -217,10 +243,12 @@ User=root
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
UNIT
|
UNIT
|
||||||
|
|
||||||
sudo systemctl enable --now lesavka-uvc
|
if [[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || ! is_attached_state "$UDC_STATE"; then
|
||||||
sudo systemctl restart lesavka-uvc
|
sudo systemctl restart lesavka-uvc
|
||||||
echo "✅ lesavka-uvc installed and restarted..."
|
echo "✅ lesavka-uvc installed and restarted..."
|
||||||
|
else
|
||||||
|
echo "⚠️ UDC state is '$UDC_STATE' - skipping lesavka-uvc restart."
|
||||||
|
fi
|
||||||
|
|
||||||
sudo systemctl enable --now lesavka-server
|
|
||||||
sudo systemctl restart lesavka-server
|
sudo systemctl restart lesavka-server
|
||||||
echo "✅ lesavka-server installed and restarted..."
|
echo "✅ lesavka-server installed and restarted..."
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// server/src/gadget.rs
|
// server/src/gadget.rs
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::{
|
use std::{
|
||||||
|
env,
|
||||||
fs::{self, OpenOptions},
|
fs::{self, OpenOptions},
|
||||||
io::Write,
|
io::Write,
|
||||||
path::Path,
|
path::Path,
|
||||||
@ -115,6 +116,31 @@ impl UsbGadget {
|
|||||||
let ctrl = Self::find_controller().or_else(|_| {
|
let ctrl = Self::find_controller().or_else(|_| {
|
||||||
Self::probe_platform_udc()?.ok_or_else(|| anyhow::anyhow!("no UDC present"))
|
Self::probe_platform_udc()?.ok_or_else(|| anyhow::anyhow!("no UDC present"))
|
||||||
})?;
|
})?;
|
||||||
|
let force_cycle = env::var("LESAVKA_GADGET_FORCE_CYCLE").is_ok();
|
||||||
|
match Self::state(&ctrl) {
|
||||||
|
Ok(state)
|
||||||
|
if !force_cycle
|
||||||
|
&& matches!(state.as_str(), "configured" | "addressed" | "default" | "suspended") =>
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"🔒 refusing gadget cycle while host attached (state={state}); set LESAVKA_GADGET_FORCE_CYCLE=1 to override"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Ok(state) if !force_cycle && state == "unknown" => {
|
||||||
|
warn!(
|
||||||
|
"🔒 refusing gadget cycle with unknown UDC state; set LESAVKA_GADGET_FORCE_CYCLE=1 to override"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(_) if !force_cycle => {
|
||||||
|
warn!(
|
||||||
|
"🔒 refusing gadget cycle without UDC state; set LESAVKA_GADGET_FORCE_CYCLE=1 to override"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
/* 1 - detach gadget */
|
/* 1 - detach gadget */
|
||||||
info!("🔌 detaching gadget from {ctrl}");
|
info!("🔌 detaching gadget from {ctrl}");
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
//! lesavka-server - **auto-cycle disabled**
|
//! lesavka-server - gadget cycle guarded by env
|
||||||
// server/src/main.rs
|
// server/src/main.rs
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
@ -26,8 +26,6 @@ use lesavka_common::lesavka::{
|
|||||||
use lesavka_server::{audio, gadget::UsbGadget, handshake::HandshakeSvc, video};
|
use lesavka_server::{audio, gadget::UsbGadget, handshake::HandshakeSvc, video};
|
||||||
|
|
||||||
/*──────────────── constants ────────────────*/
|
/*──────────────── constants ────────────────*/
|
||||||
/// **false** = never reset automatically.
|
|
||||||
const AUTO_CYCLE: bool = false;
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
|
|
||||||
@ -90,6 +88,10 @@ fn next_minute() -> SystemTime {
|
|||||||
UNIX_EPOCH + Duration::from_secs(next)
|
UNIX_EPOCH + Duration::from_secs(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allow_gadget_cycle() -> bool {
|
||||||
|
std::env::var("LESAVKA_ALLOW_GADGET_CYCLE").is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
async fn recover_hid_if_needed(
|
async fn recover_hid_if_needed(
|
||||||
err: &std::io::Error,
|
err: &std::io::Error,
|
||||||
gadget: UsbGadget,
|
gadget: UsbGadget,
|
||||||
@ -109,12 +111,19 @@ async fn recover_hid_if_needed(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allow_cycle = allow_gadget_cycle();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
warn!("🔁 HID transport down (errno={code:?}) - cycling gadget");
|
if allow_cycle {
|
||||||
match tokio::task::spawn_blocking(move || gadget.cycle()).await {
|
warn!("🔁 HID transport down (errno={code:?}) - cycling gadget");
|
||||||
Ok(Ok(())) => info!("✅ USB gadget cycle complete (auto-recover)"),
|
match tokio::task::spawn_blocking(move || gadget.cycle()).await {
|
||||||
Ok(Err(e)) => error!("💥 USB gadget cycle failed: {e:#}"),
|
Ok(Ok(())) => info!("✅ USB gadget cycle complete (auto-recover)"),
|
||||||
Err(e) => error!("💥 USB gadget cycle task panicked: {e:#}"),
|
Ok(Err(e)) => error!("💥 USB gadget cycle failed: {e:#}"),
|
||||||
|
Err(e) => error!("💥 USB gadget cycle task panicked: {e:#}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"🔒 HID transport down (errno={code:?}) - gadget cycle disabled; set LESAVKA_ALLOW_GADGET_CYCLE=1 to enable"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = async {
|
if let Err(e) = async {
|
||||||
@ -258,11 +267,11 @@ struct Handler {
|
|||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
async fn new(gadget: UsbGadget) -> anyhow::Result<Self> {
|
async fn new(gadget: UsbGadget) -> anyhow::Result<Self> {
|
||||||
if AUTO_CYCLE {
|
if allow_gadget_cycle() {
|
||||||
info!("🛠️ Initial USB reset…");
|
info!("🛠️ Initial USB reset…");
|
||||||
let _ = gadget.cycle(); // ignore failure - may boot without host
|
let _ = gadget.cycle(); // ignore failure - may boot without host
|
||||||
} else {
|
} else {
|
||||||
info!("🛠️ AUTO_CYCLE disabled - no initial reset");
|
info!("🔒 gadget cycle disabled at startup (set LESAVKA_ALLOW_GADGET_CYCLE=1 to enable)");
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("🛠️ opening HID endpoints …");
|
info!("🛠️ opening HID endpoints …");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user