use core::fmt::Write;
use crate::usb::error::{UsbError, UsbResult};
use crate::usb::host::dwc2::ep0 as dwc2_ep0;
use crate::usb::log::{usb_log_flush_residual, LineBufferedUsbLog};
use crate::usb::setup;
const USB_CLASS_HUB: u8 = 0x09;
const QEMU_USB_HUB_VID: u16 = 0x0409;
const QEMU_USB_HUB_PID: u16 = 0x55aa;
const QEMU_USB_STORAGE_VID: u16 = 0x46f4;
const QEMU_USB_STORAGE_PID: u16 = 0x0001;
const USB_CLASS_MSC: u8 = 0x08;
const USB_CLASS_VIDEO: u8 = 0x0e;
const MAX_USB_ADDR: u8 = 127;
#[derive(Clone, Copy, Debug, Default)]
pub struct TopologyScanExtras {
pub uvc: Option<UvcEnumerated>,
pub msc: Option<MscEnumerated>,
}
#[derive(Clone, Copy, Debug)]
pub struct UvcEnumerated {
pub addr: u8,
pub ep0_mps: u32,
pub vid: u16,
pub pid: u16,
}
#[derive(Clone, Copy, Debug)]
pub struct MscEnumerated {
pub addr: u8,
pub ep0_mps: u32,
pub vid: u16,
pub pid: u16,
pub iface_num: u8,
pub bulk_in_ep: u8,
pub bulk_in_mps: u16,
pub bulk_out_ep: u8,
pub bulk_out_mps: u16,
}
const MAX_HUB_PORTS: u8 = 16;
#[derive(Debug, Clone, Copy)]
struct ScanState {
next_free_addr: u8,
msc_vid: u16,
msc_pid: u16,
msc_ep0_mps: u32,
msc_addr: u32,
have_msc: bool,
extras: TopologyScanExtras,
}
impl ScanState {
const fn new() -> Self {
Self {
next_free_addr: 1,
msc_vid: 0,
msc_pid: 0,
msc_ep0_mps: 8,
msc_addr: 0,
have_msc: false,
extras: TopologyScanExtras {
uvc: None,
msc: None,
},
}
}
fn take_addr(&mut self) -> UsbResult<u8> {
let a = self.next_free_addr;
if a >= MAX_USB_ADDR {
return Err(UsbError::Protocol("usb address space full"));
}
self.next_free_addr = self.next_free_addr.saturating_add(1);
Ok(a)
}
fn note_msc(&mut self, vid: u16, pid: u16, ep0: u32, addr: u32) {
self.msc_vid = vid;
self.msc_pid = pid;
self.msc_ep0_mps = ep0;
self.msc_addr = addr;
self.have_msc = true;
}
}
#[inline]
fn is_hub_device(class: u8, vid: u16, pid: u16) -> bool {
class == USB_CLASS_HUB || (vid == QEMU_USB_HUB_VID && pid == QEMU_USB_HUB_PID)
}
#[inline]
fn is_msc_candidate(iface_class: u8, vid: u16, pid: u16) -> bool {
iface_class == USB_CLASS_MSC || (vid == QEMU_USB_STORAGE_VID && pid == QEMU_USB_STORAGE_PID)
}
fn write_indent<W: Write>(w: &mut W, depth: u8) {
for _ in 0..depth {
let _ = w.write_str(" ");
}
}
fn first_interface_class(dev: u32, ep0_mps: u32) -> UsbResult<u8> {
let mut buf = [0u8; 64];
dwc2_ep0::ep0_control_read(
dev,
setup::get_descriptor_configuration(0, 64),
ep0_mps,
&mut buf,
)?;
let mut i: usize = 0;
while i + 2 <= buf.len() {
let bl = buf[i] as usize;
if bl < 2 {
break;
}
let ty = buf[i + 1];
if ty == 4 && i + 6 <= buf.len() {
return Ok(buf[i + 5]);
}
i = i.saturating_add(bl);
}
Ok(0)
}
fn parse_msc_interface_endpoints(
dev: u32,
ep0_mps: u32,
) -> UsbResult<(u8, u8, u16, u8, u16)> {
let mut hdr = [0u8; 9];
dwc2_ep0::ep0_control_read(
dev,
setup::get_descriptor_configuration(0, 9),
ep0_mps,
&mut hdr,
)?;
if hdr[1] != setup::USB_DT_CONFIGURATION {
return Err(UsbError::Protocol("not a configuration descriptor"));
}
let total = u16::from_le_bytes([hdr[2], hdr[3]]) as usize;
if total < 9 || total > 512 {
return Err(UsbError::Protocol("bad cfg total length"));
}
let mut buf = [0u8; 512];
dwc2_ep0::ep0_control_read(
dev,
setup::get_descriptor_configuration(0, total as u16),
ep0_mps,
&mut buf[..total],
)?;
let mut i: usize = 0;
let mut in_msc_iface = false;
let mut iface_num = 0u8;
let mut bin_ep = 0u8;
let mut bin_mps = 0u16;
let mut bout_ep = 0u8;
let mut bout_mps = 0u16;
while i + 2 <= total {
let bl = buf[i] as usize;
if bl < 2 || i + bl > total {
break;
}
let ty = buf[i + 1];
if ty == 4 && i + 9 <= total {
let cls = buf[i + 5];
let alt = buf[i + 3];
in_msc_iface = cls == USB_CLASS_MSC && alt == 0;
if in_msc_iface {
iface_num = buf[i + 2];
}
} else if ty == 5 && in_msc_iface && i + 7 <= total {
let ep_addr = buf[i + 2];
let attr = buf[i + 3] & 0x03;
let mps = u16::from_le_bytes([buf[i + 4], buf[i + 5]]) & 0x07ff;
if attr == 2 {
if ep_addr & 0x80 != 0 {
if bin_ep == 0 {
bin_ep = ep_addr & 0x7f;
bin_mps = mps;
}
} else if bout_ep == 0 {
bout_ep = ep_addr & 0x7f;
bout_mps = mps;
}
}
}
i = i.saturating_add(bl);
}
Ok((iface_num, bin_ep, bin_mps, bout_ep, bout_mps))
}
struct HubInfo {
nports: u8,
pwr_on_2_pwr_good_ms: u32,
}
fn hub_info(hub_dev: u32, ep0_mps: u32) -> UsbResult<HubInfo> {
let mut buf = [0u8; 64];
dwc2_ep0::ep0_control_read(hub_dev, setup::get_descriptor_hub(64), ep0_mps, &mut buf)?;
if buf[0] < 7 || buf[1] != setup::USB_DT_HUB {
return Err(UsbError::Protocol("invalid hub descriptor"));
}
let nports = buf[2].min(MAX_HUB_PORTS);
let pwr_on = u32::from(buf[5]).saturating_mul(2);
Ok(HubInfo {
nports,
pwr_on_2_pwr_good_ms: pwr_on,
})
}
fn hub_port_status_w0(hub_dev: u32, port: u16, ep0_mps: u32) -> UsbResult<u16> {
let mut buf = [0u8; 4];
dwc2_ep0::ep0_control_read(
hub_dev,
setup::hub_get_port_status(port),
ep0_mps,
&mut buf,
)?;
Ok(u16::from_le_bytes([buf[0], buf[1]]))
}
fn spin_delay_ms(ms: u32) {
let cycles = ms.saturating_mul(250_000);
for _ in 0..cycles {
core::hint::spin_loop();
}
}
fn port_speed_str(status: u16) -> &'static str {
let ls = (status >> 9) & 1;
let hs = (status >> 10) & 1;
match (hs, ls) {
(1, _) => "HS",
(_, 1) => "LS",
_ => "FS",
}
}
fn visit_default_depth(
depth: u8,
parent_hub: u8,
port_on_hub: u8,
st: &mut ScanState,
) -> UsbResult<()> {
let mut w = LineBufferedUsbLog;
let (vid, pid, ep0_mps, dev_class) = dwc2_ep0::get_device_vid_pid_default_addr()?;
write_indent(&mut w, depth);
if parent_hub == 0 && port_on_hub == 0 {
let _ = writeln!(
w,
"[USB] root dev@0 VID={:04x} PID={:04x} dev_class={:02x}",
vid, pid, dev_class
);
} else {
let _ = writeln!(
w,
"[USB] dev@0 (hub {} port {}) VID={:04x} PID={:04x} dev_class={:02x}",
parent_hub, port_on_hub, vid, pid, dev_class
);
}
if is_hub_device(dev_class, vid, pid) {
let hub_addr = st.take_addr()?;
dwc2_ep0::set_usb_address(hub_addr, ep0_mps)?;
dwc2_ep0::usb_post_set_address_delay();
dwc2_ep0::set_configuration(u32::from(hub_addr), 1, ep0_mps)?;
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> Hub enumerated addr={} ep0_mps={}",
hub_addr, ep0_mps
);
let info = hub_info(u32::from(hub_addr), ep0_mps)?;
let nports = info.nports;
let pwr_good_ms = info.pwr_on_2_pwr_good_ms.max(20); write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> Hub descriptor: {} downstream port(s), PwrOn2PwrGood={} ms",
nports, pwr_good_ms
);
for port in 1..=nports {
if let Err(e) = dwc2_ep0::hub_set_port_feature(
u32::from(hub_addr),
u16::from(port),
setup::HUB_PORT_FEATURE_POWER,
ep0_mps,
) {
write_indent(&mut w, depth);
let _ = writeln!(w, "[USB] -> port {} POWER fail: {:?}", port, e);
}
}
spin_delay_ms(pwr_good_ms.saturating_add(100));
for port in 1..=nports {
let status = match hub_port_status_w0(u32::from(hub_addr), u16::from(port), ep0_mps) {
Ok(s) => s,
Err(e) => {
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> port {} GET_PORT_STATUS: {:?}",
port, e
);
continue;
}
};
let conn = status & 1 != 0;
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> port {} wPortStatus={:#06x} {}",
port,
status,
if conn { "CONNECTED" } else { "empty" }
);
if !conn {
continue;
}
let _ = dwc2_ep0::hub_clear_port_feature(
u32::from(hub_addr),
u16::from(port),
setup::HUB_PORT_FEATURE_C_CONNECTION,
ep0_mps,
);
if let Err(e) = dwc2_ep0::hub_set_port_feature(
u32::from(hub_addr),
u16::from(port),
setup::HUB_PORT_FEATURE_RESET,
ep0_mps,
) {
write_indent(&mut w, depth);
let _ = writeln!(w, "[USB] -> port {} RESET fail: {:?}", port, e);
continue;
}
dwc2_ep0::usb_post_hub_port_reset_delay();
let after = match hub_port_status_w0(u32::from(hub_addr), u16::from(port), ep0_mps) {
Ok(s) => s,
Err(e) => {
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> port {} after-reset GET_PORT_STATUS: {:?}",
port, e
);
continue;
}
};
let _ = dwc2_ep0::hub_clear_port_feature(
u32::from(hub_addr),
u16::from(port),
setup::HUB_PORT_FEATURE_C_RESET,
ep0_mps,
);
let enabled = (after >> 1) & 1 != 0;
let speed = port_speed_str(after);
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> port {} after-reset wPortStatus={:#06x} ENABLED={} SPD={}",
port, after, enabled, speed
);
if !enabled {
continue;
}
if speed != "HS" {
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> port {} 设备非 HS({}),HS hub 下 FS/LS 设备需要 split transaction,当前驱动暂不支持,跳过此端口枚举",
port, speed
);
continue;
}
visit_default_depth(depth.saturating_add(1), hub_addr, port, st)?;
}
return Ok(());
}
let fn_addr = st.take_addr()?;
dwc2_ep0::set_usb_address(fn_addr, ep0_mps)?;
dwc2_ep0::usb_post_set_address_delay();
dwc2_ep0::set_configuration(u32::from(fn_addr), 1, ep0_mps)?;
let iface_class = first_interface_class(u32::from(fn_addr), ep0_mps).unwrap_or(0);
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> function addr={} ep0_mps={} first_ifc_class={:02x}",
fn_addr, ep0_mps, iface_class
);
if is_msc_candidate(iface_class, vid, pid) {
st.note_msc(vid, pid, ep0_mps, u32::from(fn_addr));
let (iface_num, bin_ep, bin_mps, bout_ep, bout_mps) =
parse_msc_interface_endpoints(u32::from(fn_addr), ep0_mps).unwrap_or((0, 0, 0, 0, 0));
if st.extras.msc.is_none() {
st.extras.msc = Some(MscEnumerated {
addr: fn_addr,
ep0_mps,
vid,
pid,
iface_num,
bulk_in_ep: bin_ep,
bulk_in_mps: bin_mps,
bulk_out_ep: bout_ep,
bulk_out_mps: bout_mps,
});
}
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> Mass Storage candidate iface={} BulkIN=ep{}({}) BulkOUT=ep{}({})",
iface_num, bin_ep, bin_mps, bout_ep, bout_mps
);
}
if iface_class == USB_CLASS_VIDEO && st.extras.uvc.is_none() {
st.extras.uvc = Some(UvcEnumerated {
addr: fn_addr,
ep0_mps,
vid,
pid,
});
write_indent(&mut w, depth);
let _ = writeln!(
w,
"[USB] -> Video class device (UVC candidate) addr={}",
fn_addr
);
}
Ok(())
}
pub fn enumerate_bus_print_tree() -> UsbResult<(u16, u16, u32, u32)> {
let mut w = LineBufferedUsbLog;
let _ = writeln!(
w,
"[USB] topology: recursive hub scan (QEMU may insert virtual usb-hub on single root port)"
);
let mut st = ScanState::new();
let visit = visit_default_depth(0, 0, 0, &mut st);
let _ = writeln!(w, "[USB] topology: scan finished.");
usb_log_flush_residual();
visit?;
if !st.have_msc {
return Err(UsbError::Protocol("no mass storage device found"));
}
Ok((st.msc_vid, st.msc_pid, st.msc_ep0_mps, st.msc_addr))
}
pub fn enumerate_bus_print_tree_only() -> UsbResult<TopologyScanExtras> {
let mut w = LineBufferedUsbLog;
let _ = writeln!(
w,
"[USB] topology: recursive hub scan (QEMU may insert virtual usb-hub on single root port)"
);
let mut st = ScanState::new();
let visit = visit_default_depth(0, 0, 0, &mut st);
let _ = writeln!(w, "[USB] topology: scan finished.");
usb_log_flush_residual();
visit?;
Ok(st.extras)
}