Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/fbuild-serial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ Centralized serial port I/O manager for the fbuild daemon. All serial access rou
- **messages** -- Serde-tagged WebSocket message types matching the Python protocol
- **preemption** -- `PreemptionTracker` for deploy preemption lifecycle
- **crash_decoder** -- `CrashDecoder` for Xtensa/RISC-V crash dumps, `derive_addr2line_path`
- **ports** -- `DetectedPort`/`PortHealth` enumeration; Windows SetupAPI/CfgMgr32 problem-devnode diagnostics, Linux `sysfs` enrichment (see `sysfs_usb`)
- **sysfs_usb** -- platform-neutral Linux `/sys/bus/usb/devices` topology/health parsing (fixture-testable on any OS; FastLED/fbuild#1091)

See `docs/architecture/serial.md` and `docs/architecture/deploy-preemption.md` for architecture details.
1 change: 1 addition & 0 deletions crates/fbuild-serial/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub mod ports;
pub mod preemption;
pub mod rpc_validate;
pub mod session;
pub mod sysfs_usb;
pub mod usb_recovery;

pub use manager::{PortSessionInfo, SerialClientInfo, SharedSerialManager};
Expand Down
51 changes: 48 additions & 3 deletions crates/fbuild-serial/src/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,16 +146,45 @@ fn health_for_endpoint(observation: PnpObservation, is_usb: bool) -> PortHealth
///
/// Unlike [`serialport::available_ports`], on Windows this includes ports
/// whose devnode status is not "OK" (the Teensy / composite-device case) and
/// preserves that health in the returned record.
/// preserves that health in the returned record. On Linux, ports backed by a
/// USB tty are additionally enriched with [`PortHealth`] from `sysfs` when
/// `sysfs` gives a concrete healthy/problem signal (see
/// [`crate::sysfs_usb`]); anything ambiguous is left at the default
/// `PortHealth::Unknown`, matching current (pre-#1091) behavior. macOS is
/// unchanged (`Unknown`) — see the module doc comment on `sysfs_usb` for why.
pub fn available_ports() -> serialport::Result<Vec<DetectedPort>> {
#[cfg(windows)]
{
imp::available_ports()
}
#[cfg(not(windows))]
{
serialport::available_ports()
.map(|ports| ports.into_iter().map(DetectedPort::unknown).collect())
let mut ports: Vec<DetectedPort> = serialport::available_ports()?
.into_iter()
.map(DetectedPort::unknown)
.collect();
#[cfg(target_os = "linux")]
{
enrich_linux_port_health(&mut ports);
}
Ok(ports)
}
}

/// Overwrite `PortHealth::Unknown` entries with a concrete sysfs-derived
/// signal, for ports whose name is a `/dev/ttyXXX` device. Ports that sysfs
/// has no opinion about (non-USB ttys, ambiguous state) are left untouched.
#[cfg(target_os = "linux")]
fn enrich_linux_port_health(ports: &mut [DetectedPort]) {
let root = crate::sysfs_usb::live_root();
for port in ports.iter_mut() {
let Some(tty_name) = port.info.port_name.strip_prefix("/dev/") else {
continue;
};
let health = crate::sysfs_usb::health_for_tty_from_root(&root, tty_name);
if health != PortHealth::Unknown {
port.health = health;
}
}
}

Expand Down Expand Up @@ -187,6 +216,12 @@ pub struct UsbProblemDevice {
/// Best-effort enumeration of present USB devnodes with a non-zero Windows
/// problem code. This is empty on non-Windows hosts and never makes a port
/// scan fail merely because host diagnostics are unavailable.
///
/// Linux has an equivalent diagnostic (`sysfs`-derived, not Windows PnP
/// problem codes), but the `UsbProblemDevice` shape doesn't fit it honestly
/// (no PnP instance id, no Windows setup class) — see
/// [`present_usb_problem_devices_linux`] instead. macOS has no equivalent
/// implemented yet (IOKit work is out of scope without a macOS host).
pub fn present_usb_problem_devices() -> Vec<UsbProblemDevice> {
#[cfg(windows)]
{
Expand All @@ -198,6 +233,16 @@ pub fn present_usb_problem_devices() -> Vec<UsbProblemDevice> {
}
}

/// Linux sibling of [`present_usb_problem_devices`]: `sysfs`-derived USB
/// devices with a concrete, observed fault (unauthorized, unconfigured, or a
/// CDC interface with no bound driver). Diagnostics only — never used to
/// drive `PortHealth` selection directly (that happens per-tty via
/// [`available_ports`]'s Linux enrichment).
#[cfg(target_os = "linux")]
pub fn present_usb_problem_devices_linux() -> Vec<crate::sysfs_usb::LinuxUsbProblemDevice> {
crate::sysfs_usb::linux_usb_problem_devices_from_root(&crate::sysfs_usb::live_root())
}

#[cfg(test)]
mod health_tests {
use super::*;
Expand Down
Loading
Loading