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
85 changes: 32 additions & 53 deletions crates/fbuild-cli/src/cli/port_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ fn render_usb_port_with_kernel_class(
"{name:<10}{vid:04X}:{pid:04X} {descriptor}{serial_field}{interface_field}",
);
let info = fbuild_core::usb::resolve(vid, pid);
let friendly_product = friendly_product_name(vid, pid, &info.product, product);
let friendly_product = friendly_product_name(pid, &info.product, product);
let _ = writeln!(
out,
" └─ {} / {} cdc={}",
Expand All @@ -244,30 +244,24 @@ fn cdc_label(kernel_class: Option<fbuild_serial::port_class::PortKernelClass>) -
/// Pick the most "friendly" product label for the resolver row.
///
/// Preference order:
/// 1. Resolver's product if it's a real name (tier-2 overlay hit) —
/// i.e. *not* the synthetic `Device 0xPPPP` placeholder.
/// 2. Small inline supplement table for common embedded CDC-ACM PIDs
/// that FastLED/boards' canonical `vidpid` table doesn't yet
/// cover (e.g. ESP32-S3 builtin USB-CDC at 303A:1001). Migrate
/// these upstream as the canonical DB picks them up.
/// 3. The OS-supplied descriptor when it carries chip-specific
/// detail (e.g. macOS / Linux often expose "CP2102 USB to UART
/// Bridge Controller") — skip if it's the generic "USB Serial
/// Device" Windows fallback.
/// 4. Synthetic `Device 0xPPPP` placeholder (tier-3 fallback).
fn friendly_product_name(
vid: u16,
pid: u16,
resolved_product: &str,
os_descriptor: Option<&str>,
) -> String {
/// 1. Resolver's product if it's a real name — i.e. *not* the synthetic
/// `Device 0xPPPP` placeholder. This comes from the embedded
/// FastLED/boards VID:PID archive (offline) or the online overlay.
/// 2. The OS-supplied descriptor when it carries chip-specific detail
/// (e.g. macOS / Linux often expose "CP2102 USB to UART Bridge
/// Controller") — skip if it's the generic "USB Serial Device"
/// Windows fallback.
/// 3. Synthetic `Device 0xPPPP` placeholder.
///
/// There is intentionally NO hardcoded per-PID product table here: friendly
/// product names are owned by the FastLED/boards VID:PID data and embedded at
/// build time (FastLED/fbuild#722, #959). A missing name is a data gap to fix
/// on the boards `other` branch, not in fbuild source.
fn friendly_product_name(pid: u16, resolved_product: &str, os_descriptor: Option<&str>) -> String {
let synthetic = format!("Device 0x{pid:04X}");
if resolved_product != synthetic {
return resolved_product.to_string();
}
if let Some(name) = friendly_supplement(vid, pid) {
return name.to_string();
}
if let Some(d) = os_descriptor {
let trimmed = d.trim();
if !trimmed.is_empty() && !is_generic_descriptor(trimmed) {
Expand All @@ -288,30 +282,6 @@ fn is_generic_descriptor(d: &str) -> bool {
)
}

/// Small inline supplement for common embedded VID:PIDs that the
/// canonical online overlay may not carry in offline/test paths. Keep it
/// short — anything that lands into the embedded product map should be
/// removed here.
const FRIENDLY_PRODUCTS: &[(u16, u16, &str)] = &[
// Espressif Systems (VID 0x303A) — ESP32 series USB-CDC ACM.
(0x303A, 0x1001, "ESP32-S3 USB-CDC"),
(0x303A, 0x1002, "ESP32-C3 USB-CDC"),
(0x303A, 0x4001, "ESP32-S2 USB-CDC"),
(0x303A, 0x0002, "ESP32-S2 ROM-DL"),
(0x303A, 0x0003, "ESP32-S3 ROM-DL"),
(0x303A, 0x1000, "ESP32-S2 USB-CDC"),
// NXP Semiconductors (VID 0x1FC9) — LPC-Link2 / MCU-Link CMSIS-DAP.
(0x1FC9, 0x0132, "LPC-Link2 CMSIS-DAP"),
(0x1FC9, 0x0143, "MCU-Link CMSIS-DAP"),
];

fn friendly_supplement(vid: u16, pid: u16) -> Option<&'static str> {
FRIENDLY_PRODUCTS
.iter()
.find(|&&(v, p, _)| v == vid && p == pid)
.map(|&(_, _, name)| name)
}

fn render_non_usb(out: &mut String, name: &str, kind: &str) {
use std::fmt::Write as _;
let _ = writeln!(out, "{name:<10}[{kind}]");
Expand Down Expand Up @@ -649,10 +619,10 @@ mod tests {
}

#[test]
fn esp32_s3_cdc_pid_gets_friendly_supplement() {
// In the tier-1/offline path the embedded archive carries vendor
// names only; the inline supplement keeps this common PID friendly
// when the online product overlay is not installed.
fn common_esp32_cdc_pid_resolves_from_embedded_archive() {
// The common ESP32 USB-Serial-JTAG PID (303A:1001) resolves to a real
// product name from the embedded FastLED/boards archive — no hardcoded
// supplement table, and NOT the synthetic placeholder. FastLED/fbuild#722.
let ports = vec![usb_port(
"COM25",
0x303A,
Expand All @@ -662,11 +632,20 @@ mod tests {
)];
let out = render_scan(&ports);
assert!(
out.contains("ESP32-S3 USB-CDC"),
"expected friendly supplement product, got: {out}"
out.to_lowercase().contains("espressif"),
"expected Espressif vendor from the archive, got: {out}"
);
// A real archive product name, not the synthetic placeholder or the
// generic Windows descriptor.
assert!(!out.contains("Device 0x1001"), "leaked placeholder: {out}");
let resolver_row = out
.lines()
.find(|l| l.contains("└─"))
.expect("resolver row");
assert!(
!resolver_row.contains("USB Serial Device"),
"generic descriptor leaked into resolver row: {resolver_row}"
);
// And we do NOT fall through to the synthetic placeholder.
assert!(!out.contains("Device 0x1001"));
}

#[test]
Expand Down
Binary file modified crates/fbuild-core/data/usb-vids.proto.zstd
Binary file not shown.
11 changes: 7 additions & 4 deletions crates/fbuild-core/src/usb/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,23 @@ mod tests {
// embedded FastLED/boards VID:PID archive — NOT a hardcoded table in
// fbuild. Pin the round-trip for the PIDs a Teensy exposes as serial
// ports. FastLED/fbuild#962.
for (pid, expect) in [(0x0483u16, "serial"), (0x0489, "midi")] {
for pid in [0x0483u16, 0x0489] {
let info = try_resolve(0x16C0, pid).expect("Teensy PID in embedded archive");
assert!(
info.vendor.to_lowercase().contains("pjrc")
|| info.vendor.to_lowercase().contains("teensy"),
"16C0:{pid:04X} vendor should be PJRC/Teensy, got {:?}",
info.vendor
);
// Product name is archive-derived (a board or USB-mode label) and
// may change as the FastLED/boards data is refreshed — assert it is
// a real Teensy name, NOT the synthetic `Device 0xPPPP` placeholder.
assert!(
info.product.to_lowercase().contains("teensy")
&& info.product.to_lowercase().contains(expect),
"16C0:{pid:04X} product should name the Teensy {expect} mode, got {:?}",
info.product.to_lowercase().contains("teensy"),
"16C0:{pid:04X} product should name Teensy, got {:?}",
info.product
);
assert_ne!(info.product, format!("Device 0x{pid:04X}"));
}
}

Expand Down
Loading