From cb0ae059c94e8e80240be73cad43de602e9248dc Mon Sep 17 00:00:00 2001 From: zackees Date: Tue, 25 Aug 2026 19:30:49 -0700 Subject: [PATCH] fix(rp): reacquire healthy same-name CDC after UF2 --- crates/fbuild-deploy/src/rp2040.rs | 63 +++++++++++++++++++++-- crates/fbuild-deploy/src/rp2040_target.rs | 45 ++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/crates/fbuild-deploy/src/rp2040.rs b/crates/fbuild-deploy/src/rp2040.rs index 44169e63..65cf0684 100644 --- a/crates/fbuild-deploy/src/rp2040.rs +++ b/crates/fbuild-deploy/src/rp2040.rs @@ -27,7 +27,8 @@ mod target; mod topology; use mount::try_mount_rom_device; use target::{ - describe_unhealthy, resolve_requested_runtime_target, select_cdc_candidate, serial_selector, + describe_unhealthy, preflash_eligible_port_names, resolve_requested_runtime_target, + select_cdc_candidate, serial_selector, }; const UF2_MAGIC_START0: u32 = 0x0A32_4655; @@ -1828,8 +1829,7 @@ impl Deployer for Rp2040Deployer { "RP2040 serial snapshot task failed: {error}" )) })??; - let ports_before: BTreeSet = - current_ports.iter().map(|port| port.name.clone()).collect(); + let ports_before = preflash_eligible_port_names(¤t_ports); let explicit_volume = selector.and_then(explicit_uf2_volume); if selector.is_some_and(|value| value.to_ascii_lowercase().starts_with("uf2=")) && explicit_volume.is_none() @@ -2878,6 +2878,63 @@ mod tests { Some(root.path().to_path_buf()) ); assert!(resolve_requested_runtime_target(&selector, &[]).is_err()); + + let before = preflash_eligible_port_names(&[cdc_candidate( + "COM18", + Some("2DCB876B587EA334"), + fbuild_serial::ports::PortHealth::Phantom { + problem_code: None, + status: None, + }, + )]); + let recovered = select_cdc_candidate( + None, + None, + &before, + &[cdc_candidate( + "COM18", + Some("2DCB876B587EA334"), + fbuild_serial::ports::PortHealth::HealthyPresent, + )], + ) + .unwrap() + .expect("same-name healthy CDC must be recovered after explicit UF2 deploy"); + assert_eq!(recovered.name, "COM18"); + } + + #[test] + fn stale_com_selector_uses_unique_bootsel_and_reacquires_same_name() { + let stale = cdc_candidate( + "COM18", + Some("2DCB876B587EA334"), + fbuild_serial::ports::PortHealth::Phantom { + problem_code: None, + status: None, + }, + ); + let can_attribute = resolve_requested_runtime_target("COM18", std::slice::from_ref(&stale)) + .ok() + .is_some(); + let (volume, volumes_before) = + pretouch_volume_policy(vec![PathBuf::from("H:\\")], can_attribute).unwrap(); + + assert_eq!(volume, Some(PathBuf::from("H:\\"))); + assert!(volumes_before.is_empty()); + + let before = preflash_eligible_port_names(&[stale]); + let recovered = select_cdc_candidate( + None, + None, + &before, + &[cdc_candidate( + "COM18", + Some("2DCB876B587EA334"), + fbuild_serial::ports::PortHealth::HealthyPresent, + )], + ) + .unwrap() + .expect("stale COM selector must converge through unique BOOTSEL recovery"); + assert_eq!(recovered.name, "COM18"); } #[test] diff --git a/crates/fbuild-deploy/src/rp2040_target.rs b/crates/fbuild-deploy/src/rp2040_target.rs index 45b91477..2078f502 100644 --- a/crates/fbuild-deploy/src/rp2040_target.rs +++ b/crates/fbuild-deploy/src/rp2040_target.rs @@ -42,6 +42,18 @@ pub(super) fn describe_unhealthy(port: &PicoCdcPort) -> String { ) } +/// Snapshot only CDC names that were eligible before the flash. A stale +/// Windows devnode may later return healthy under the same COM name; treating +/// that historical phantom name as already present would hide the recovered +/// endpoint from post-flash discovery. +pub(super) fn preflash_eligible_port_names(candidates: &[PicoCdcPort]) -> BTreeSet { + candidates + .iter() + .filter(|candidate| !candidate.health.is_known_unhealthy()) + .map(|candidate| candidate.name.clone()) + .collect() +} + pub(super) fn resolve_requested_runtime_target( selector: &str, candidates: &[PicoCdcPort], @@ -177,6 +189,39 @@ mod tests { ); } + #[test] + fn phantom_name_can_return_as_a_healthy_post_flash_endpoint() { + let before = preflash_eligible_port_names(&[ + cdc_with_health( + "COM18", + Some("PICO-1"), + PortHealth::Phantom { + problem_code: None, + status: None, + }, + ), + cdc_with_health("COM27", Some("PICO-2"), PortHealth::HealthyPresent), + ]); + + assert_eq!(before, BTreeSet::from(["COM27".to_string()])); + assert_eq!( + selected_name( + select_cdc_candidate( + None, + None, + &before, + &[cdc_with_health( + "COM18", + Some("PICO-1"), + PortHealth::HealthyPresent, + )], + ) + .unwrap() + ), + Some("COM18".to_string()) + ); + } + #[test] fn usb_serial_selector_survives_port_renumbering() { let before = BTreeSet::from(["COM7".to_string()]);