use serde::Serialize;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum ProductType {
#[default]
Undefined,
Hub,
Peripheral,
PassiveCable,
ActiveCable,
Ama,
Vpd,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum CableSpeed {
Usb20,
Usb32Gen1,
Usb32Gen2,
Usb4Gen3,
Usb4Gen4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum CableCurrent {
UsbDefault,
ThreeAmp,
FiveAmp,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct IdHeaderVdo {
pub usb_comm_capable_as_host: bool,
pub usb_comm_capable_as_device: bool,
pub modal_operation: bool,
pub ufp_product_type: Option<ProductType>,
pub dfp_product_type: Option<ProductType>,
pub vendor_id: u16,
}
#[derive(Debug, Clone, Copy)]
pub struct CableVdo {
pub speed: CableSpeed,
pub current_rating: CableCurrent,
pub vbus_through_cable: bool,
pub max_vbus_volts: u32,
pub is_active: bool,
pub max_watts: u32,
}
pub fn product_type_label(t: ProductType) -> &'static str {
match t {
ProductType::Hub => "USB Hub",
ProductType::Peripheral => "USB Peripheral",
ProductType::PassiveCable => "Passive Cable",
ProductType::ActiveCable => "Active Cable",
ProductType::Ama => "Alternate Mode Adapter",
ProductType::Vpd => "VCONN-Powered Device",
ProductType::Other => "Other",
ProductType::Undefined => "Unknown",
}
}
pub fn cable_speed_label(s: CableSpeed) -> &'static str {
match s {
CableSpeed::Usb20 => "USB 2.0",
CableSpeed::Usb32Gen1 => "USB 3.2 Gen 1 (5 Gbps)",
CableSpeed::Usb32Gen2 => "USB 3.2 Gen 2 (10 Gbps)",
CableSpeed::Usb4Gen3 => "USB4 Gen 3 (20/40 Gbps)",
CableSpeed::Usb4Gen4 => "USB4 Gen 4 (40/80 Gbps)",
}
}
pub fn cable_speed_max_gbps(s: CableSpeed) -> u32 {
match s {
CableSpeed::Usb20 => 0,
CableSpeed::Usb32Gen1 => 5,
CableSpeed::Usb32Gen2 => 10,
CableSpeed::Usb4Gen3 => 40,
CableSpeed::Usb4Gen4 => 80,
}
}
pub fn cable_current_label(c: CableCurrent) -> &'static str {
match c {
CableCurrent::UsbDefault => "USB Default",
CableCurrent::ThreeAmp => "3A",
CableCurrent::FiveAmp => "5A",
}
}
pub fn cable_current_max_amps(c: CableCurrent) -> f64 {
match c {
CableCurrent::UsbDefault => 0.9,
CableCurrent::ThreeAmp => 3.0,
CableCurrent::FiveAmp => 5.0,
}
}
fn ufp_from_bits(bits: u32) -> Option<ProductType> {
match bits {
1 => Some(ProductType::Hub),
2 => Some(ProductType::Peripheral),
3 => Some(ProductType::PassiveCable),
4 => Some(ProductType::ActiveCable),
5 => Some(ProductType::Ama),
6 => Some(ProductType::Vpd),
_ => None,
}
}
fn dfp_from_bits(bits: u32) -> Option<ProductType> {
match bits {
1 => Some(ProductType::Hub),
2 => Some(ProductType::Peripheral),
_ => None,
}
}
pub fn decode_id_header(vdo: u32) -> IdHeaderVdo {
IdHeaderVdo {
usb_comm_capable_as_host: (vdo >> 31) & 1 != 0,
usb_comm_capable_as_device: (vdo >> 30) & 1 != 0,
modal_operation: (vdo >> 26) & 1 != 0,
ufp_product_type: ufp_from_bits((vdo >> 27) & 0x7),
dfp_product_type: dfp_from_bits((vdo >> 23) & 0x7),
vendor_id: (vdo & 0xFFFF) as u16,
}
}
pub fn decode_cable_vdo(vdo: u32, is_active: bool) -> CableVdo {
let speed = match vdo & 0x7 {
1 => CableSpeed::Usb32Gen1,
2 => CableSpeed::Usb32Gen2,
3 => CableSpeed::Usb4Gen3,
4 => CableSpeed::Usb4Gen4,
_ => CableSpeed::Usb20,
};
let current_rating = match (vdo >> 5) & 0x3 {
1 => CableCurrent::ThreeAmp,
2 => CableCurrent::FiveAmp,
_ => CableCurrent::UsbDefault,
};
let max_vbus_volts = match (vdo >> 9) & 0x3 {
1 => 30,
2 => 40,
3 => 50,
_ => 20,
};
let amps = cable_current_max_amps(current_rating);
let max_watts = (max_vbus_volts as f64 * amps) as u32;
CableVdo {
speed,
current_rating,
vbus_through_cable: (vdo >> 4) & 1 != 0,
max_vbus_volts,
is_active,
max_watts,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn id_header_vendor_low16() {
let h = decode_id_header(0x12345678);
assert_eq!(h.vendor_id, 0x5678);
}
#[test]
fn id_header_active_cable_ufp() {
let h = decode_id_header(4 << 27);
assert_eq!(h.ufp_product_type, Some(ProductType::ActiveCable));
}
#[test]
fn id_header_dfp_field() {
let h = decode_id_header(2 << 23);
assert_eq!(h.dfp_product_type, Some(ProductType::Peripheral));
}
#[test]
fn id_header_flag_bits() {
let h = decode_id_header((1u32 << 31) | (1 << 30) | (1 << 26));
assert!(h.usb_comm_capable_as_host);
assert!(h.usb_comm_capable_as_device);
assert!(h.modal_operation);
}
#[test]
fn id_header_unknown_product_type_is_none() {
let h = decode_id_header(7 << 27);
assert_eq!(h.ufp_product_type, None);
}
#[test]
fn cable_vdo_decodes_speed_current_voltage() {
let v = decode_cable_vdo(2 | (1 << 5), false);
assert_eq!(v.speed, CableSpeed::Usb32Gen2);
assert_eq!(v.current_rating, CableCurrent::ThreeAmp);
assert_eq!(v.max_vbus_volts, 20);
assert_eq!(v.max_watts, 60);
assert!(!v.is_active);
}
#[test]
fn cable_vdo_5a_50v_marks_250w() {
let v = decode_cable_vdo((2 << 5) | (3 << 9), true);
assert_eq!(v.current_rating, CableCurrent::FiveAmp);
assert_eq!(v.max_vbus_volts, 50);
assert_eq!(v.max_watts, 250);
assert!(v.is_active);
}
#[test]
fn cable_vdo_unknown_speed_falls_to_usb20() {
let v = decode_cable_vdo(7, false);
assert_eq!(v.speed, CableSpeed::Usb20);
}
#[test]
fn cable_vdo_vbus_through_bit() {
let v = decode_cable_vdo(1 << 4, false);
assert!(v.vbus_through_cable);
let v = decode_cable_vdo(0, false);
assert!(!v.vbus_through_cable);
}
#[test]
fn label_helpers() {
assert_eq!(product_type_label(ProductType::Hub), "USB Hub");
assert_eq!(
cable_speed_label(CableSpeed::Usb4Gen4),
"USB4 Gen 4 (40/80 Gbps)"
);
assert_eq!(cable_speed_max_gbps(CableSpeed::Usb32Gen1), 5);
assert_eq!(cable_speed_max_gbps(CableSpeed::Usb20), 0);
assert_eq!(cable_current_label(CableCurrent::FiveAmp), "5A");
assert!((cable_current_max_amps(CableCurrent::ThreeAmp) - 3.0).abs() < 1e-9);
}
}