use super::*;
use crate::error::{Error, ErrorKind};
use crate::lsusb::names;
use crate::types::NumericalUnit;
use rusb as libusb;
use usb_ids::{self, FromId};
#[derive(Debug)]
pub(crate) struct LibUsbProfiler;
pub(crate) struct UsbDevice<T: libusb::UsbContext> {
handle: libusb::DeviceHandle<T>,
language: libusb::Language,
vidpid: (u16, u16),
location: DeviceLocation,
timeout: std::time::Duration,
}
impl<T: libusb::UsbContext> std::fmt::Debug for UsbDevice<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"UsbDevice {{ vidpid: {:#04x}:{:#04x}, location: {:?} }}",
self.vidpid.0, self.vidpid.1, self.location
)
}
}
pub fn set_log_level(debug: u8) {
let log_level = match debug {
0 => rusb::LogLevel::None,
1 => rusb::LogLevel::Warning,
2 => rusb::LogLevel::Info,
_ => rusb::LogLevel::Debug,
};
rusb::set_log_level(log_level);
}
impl ControlRequest {
fn get_request_type_in(&self) -> u8 {
libusb::request_type(
libusb::Direction::In,
self.control_type.into(),
self.recipient.into(),
)
}
}
impl From<ControlType> for libusb::RequestType {
fn from(ct: ControlType) -> Self {
match ct {
ControlType::Standard => libusb::RequestType::Standard,
ControlType::Class => libusb::RequestType::Class,
ControlType::Vendor => libusb::RequestType::Vendor,
}
}
}
impl From<Recipient> for libusb::Recipient {
fn from(r: Recipient) -> Self {
match r {
Recipient::Device => libusb::Recipient::Device,
Recipient::Interface => libusb::Recipient::Interface,
Recipient::Endpoint => libusb::Recipient::Endpoint,
Recipient::Other => libusb::Recipient::Other,
}
}
}
impl From<libusb::Error> for Error {
fn from(error: libusb::Error) -> Self {
Error {
kind: ErrorKind::LibUSB,
message: format!(
"Failed to gather system USB data from libusb: Error({})",
&error.to_string()
),
}
}
}
impl From<libusb::Speed> for usb::Speed {
fn from(libusb: libusb::Speed) -> Self {
match libusb {
libusb::Speed::SuperPlus => usb::Speed::SuperSpeedPlus,
libusb::Speed::Super => usb::Speed::SuperSpeed,
libusb::Speed::High => usb::Speed::HighSpeed,
libusb::Speed::Full => usb::Speed::FullSpeed,
libusb::Speed::Low => usb::Speed::LowSpeed,
_ => usb::Speed::Unknown,
}
}
}
impl From<libusb::Direction> for usb::Direction {
fn from(libusb: libusb::Direction) -> Self {
match libusb {
libusb::Direction::Out => usb::Direction::Out,
libusb::Direction::In => usb::Direction::In,
}
}
}
impl From<libusb::TransferType> for usb::TransferType {
fn from(libusb: libusb::TransferType) -> Self {
match libusb {
libusb::TransferType::Control => usb::TransferType::Control,
libusb::TransferType::Isochronous => usb::TransferType::Isochronous,
libusb::TransferType::Bulk => usb::TransferType::Bulk,
libusb::TransferType::Interrupt => usb::TransferType::Interrupt,
}
}
}
impl From<libusb::UsageType> for usb::UsageType {
fn from(libusb: libusb::UsageType) -> Self {
match libusb {
libusb::UsageType::Data => usb::UsageType::Data,
libusb::UsageType::Feedback => usb::UsageType::Feedback,
libusb::UsageType::FeedbackData => usb::UsageType::FeedbackData,
libusb::UsageType::Reserved => usb::UsageType::Reserved,
}
}
}
impl From<libusb::SyncType> for usb::SyncType {
fn from(libusb: libusb::SyncType) -> Self {
match libusb {
libusb::SyncType::NoSync => usb::SyncType::None,
libusb::SyncType::Asynchronous => usb::SyncType::Asynchronous,
libusb::SyncType::Adaptive => usb::SyncType::Adaptive,
libusb::SyncType::Synchronous => usb::SyncType::Synchronous,
}
}
}
impl From<libusb::Version> for usb::Version {
fn from(libusb: libusb::Version) -> Self {
usb::Version(libusb.major(), libusb.minor(), libusb.sub_minor())
}
}
#[allow(unused_variables)]
fn get_sysfs_configuration_string(sysfs_name: &str) -> Option<(u8, String)> {
#[cfg(target_os = "linux")]
match get_sysfs_string(sysfs_name, "bConfigurationValue") {
Some(s) => match s.parse::<u8>() {
Ok(v) => {
get_sysfs_string(sysfs_name, "configuration").map(|s| (v, s))
}
Err(_) => None,
},
None => None,
}
#[cfg(not(target_os = "linux"))]
None
}
impl<T: libusb::UsbContext> UsbOperations for UsbDevice<T> {
fn get_descriptor_string(&self, string_index: u8) -> Option<String> {
if string_index == 0 {
return None;
}
self.handle
.read_string_descriptor(self.language, string_index, self.timeout)
.map(|s| s.trim().trim_end_matches('\0').to_string())
.ok()
}
fn get_control_msg(&self, control_request: ControlRequest) -> Result<Vec<u8>> {
let mut buf = vec![0; control_request.length];
if control_request.claim_interface {
self.handle.claim_interface(control_request.index as u8)?;
}
let n = self
.handle
.read_control(
control_request.get_request_type_in(),
control_request.request,
control_request.value,
control_request.index,
&mut buf,
self.timeout,
)
.map_err(|e| Error {
kind: ErrorKind::LibUSB,
message: format!("Failed to get control message: {e}"),
})?;
if n < control_request.length {
log::warn!(
"Failed to read full control message for {}: {} < {}",
control_request.request,
n,
control_request.length
);
Err(Error {
kind: ErrorKind::LibUSB,
message: "Control message too short".to_string(),
})
} else {
Ok(buf)
}
}
}
impl LibUsbProfiler {
fn build_endpoints<T: libusb::UsbContext>(
&self,
handle: Option<&UsbDevice<T>>,
interface_path: &usb::DevicePath,
interface_desc: &libusb::InterfaceDescriptor,
) -> Vec<usb::Endpoint> {
let mut ret: Vec<usb::Endpoint> = Vec::new();
for endpoint_desc in interface_desc.endpoint_descriptors() {
let extra_desc = handle.and_then(|h| {
endpoint_desc.extra().and_then(|extra| {
self.build_endpoint_descriptor_extra(
h,
(
interface_desc.class_code(),
interface_desc.sub_class_code(),
interface_desc.protocol_code(),
),
interface_desc.interface_number(),
extra.to_vec(),
)
.ok()
.flatten()
})
});
let endpoint_path = usb::EndpointPath::new_with_device_path(
interface_path.to_owned(),
endpoint_desc.number(),
);
ret.push(usb::Endpoint {
address: usb::EndpointAddress {
address: endpoint_desc.address(),
number: endpoint_desc.number(),
direction: usb::Direction::from(endpoint_desc.direction()),
},
transfer_type: usb::TransferType::from(endpoint_desc.transfer_type()),
sync_type: usb::SyncType::from(endpoint_desc.sync_type()),
usage_type: usb::UsageType::from(endpoint_desc.usage_type()),
max_packet_size: endpoint_desc.max_packet_size(),
interval: endpoint_desc.interval(),
length: endpoint_desc.length(),
extra: extra_desc,
internal: InternalData::default(),
endpoint_path: Some(endpoint_path),
});
}
ret
}
fn build_interfaces<T: libusb::UsbContext>(
&self,
handle: Option<&UsbDevice<T>>,
location: &DeviceLocation,
config_desc: &libusb::ConfigDescriptor,
) -> Result<Vec<usb::Interface>> {
let mut ret: Vec<usb::Interface> = Vec::new();
for interface in config_desc.interfaces() {
let sample_path = usb::DevicePath::new_with_port_path(
location.clone().into(),
Some(config_desc.number()),
Some(interface.number()),
None, )
.to_string();
let active_alt = get_sysfs_active_alternate_setting(&sample_path);
for interface_desc in interface.descriptors() {
let device_path = usb::DevicePath::new_with_port_path(
location.clone().into(),
Some(config_desc.number()),
Some(interface_desc.interface_number()),
Some(interface_desc.setting_number()),
);
let path = device_path.to_string();
let mut interface = usb::Interface {
name: get_sysfs_string(&path, "interface").or_else(|| {
interface_desc
.description_string_index()
.and_then(|i| handle.and_then(|h| h.get_descriptor_string(i)))
}),
string_index: interface_desc.description_string_index().unwrap_or(0),
number: interface_desc.interface_number(),
class: usb::BaseClass::from(interface_desc.class_code()),
sub_class: interface_desc.sub_class_code(),
protocol: interface_desc.protocol_code(),
alt_setting: interface_desc.setting_number(),
active: active_alt.is_some_and(|a| a == interface_desc.setting_number()),
driver: get_sysfs_readlink(&path, "driver")
.or_else(|| get_udev_driver_name(&path).ok().flatten()),
syspath: get_syspath(&path).or_else(|| get_udev_syspath(&path).ok().flatten()),
devpaths: None,
mount_paths: None,
path,
length: interface_desc.length(),
endpoints: self.build_endpoints(handle, &device_path, &interface_desc),
extra: handle.and_then(|h| {
self.build_interface_descriptor_extra(
h,
(
interface_desc.class_code(),
interface_desc.sub_class_code(),
interface_desc.protocol_code(),
),
interface_desc.interface_number(),
interface_desc.extra().to_vec(),
)
.ok()
}),
internal: InternalData::default(),
device_path: Some(device_path),
};
interface.devpaths = interface.dev_paths();
interface.mount_paths = interface.block_mount_paths();
ret.push(interface);
}
}
Ok(ret)
}
fn build_configurations<T: libusb::UsbContext>(
&self,
device: &libusb::Device<T>,
handle: Option<&UsbDevice<T>>,
device_desc: &libusb::DeviceDescriptor,
sp_device: &Device,
) -> Result<Vec<usb::Configuration>> {
let cur_config = get_sysfs_configuration_string(&sp_device.sysfs_name());
let mut ret: Vec<usb::Configuration> = Vec::new();
for n in 0..device_desc.num_configurations() {
let config_desc = match device.config_descriptor(n) {
Ok(c) => c,
Err(_) => continue,
};
let mut attributes = Vec::new();
if config_desc.remote_wakeup() {
attributes.push(usb::ConfigAttributes::RemoteWakeup);
}
if config_desc.self_powered() {
attributes.push(usb::ConfigAttributes::SelfPowered);
} else {
attributes.push(usb::ConfigAttributes::BusPowered);
}
let config_name = if let Some((config_num, ref config_name)) = cur_config {
if config_num - 1 == n {
Some(config_name.clone())
} else {
None
}
} else {
None
};
let power_mult = if device_desc.usb_version().0 >= 3 {
4
} else {
1
};
ret.push(usb::Configuration {
name: config_desc
.description_string_index()
.and_then(|i| handle.and_then(|h| h.get_descriptor_string(i)))
.or(config_name)
.unwrap_or(String::new()),
string_index: config_desc.description_string_index().unwrap_or(0),
number: config_desc.number(),
active: cur_config
.as_ref()
.is_some_and(|(c, _)| *c == config_desc.number()),
attributes,
max_power: NumericalUnit {
value: config_desc.max_power() as u32 * power_mult,
unit: String::from("mA"),
description: None,
},
length: config_desc.length(),
total_length: config_desc.total_length(),
num_interfaces: Some(config_desc.num_interfaces()),
interfaces: self.build_interfaces(handle, &sp_device.location_id, &config_desc)?,
extra: handle.and_then(|h| {
self.build_config_descriptor_extra(h, config_desc.extra().to_vec())
.ok()
}),
internal: Default::default(),
});
}
Ok(ret)
}
#[allow(unused_variables)]
fn build_spdevice_extra<T: libusb::UsbContext>(
&self,
device: &libusb::Device<T>,
handle: &UsbDevice<T>,
device_desc: &libusb::DeviceDescriptor,
sp_device: &mut Device,
more_extra: bool,
) -> Result<usb::DeviceExtra> {
sp_device.manufacturer = device_desc
.manufacturer_string_index()
.and_then(|i| handle.get_descriptor_string(i));
if let Some(name) = device_desc
.product_string_index()
.and_then(|i| handle.get_descriptor_string(i))
{
sp_device.name = name;
}
sp_device.serial_num = device_desc
.serial_number_string_index()
.and_then(|i| handle.get_descriptor_string(i));
let sysfs_name = sp_device.sysfs_name();
let mut extra = usb::DeviceExtra {
max_packet_size: device_desc.max_packet_size(),
string_indexes: (
device_desc.product_string_index().unwrap_or(0),
device_desc.manufacturer_string_index().unwrap_or(0),
device_desc.serial_number_string_index().unwrap_or(0),
),
driver: get_sysfs_readlink(&sysfs_name, "driver")
.or_else(|| get_udev_driver_name(&sysfs_name).ok().flatten()),
syspath: get_syspath(&sysfs_name)
.or_else(|| get_udev_syspath(&sysfs_name).ok().flatten()),
vendor: names::vendor(device_desc.vendor_id()).or_else(|| {
usb_ids::Vendor::from_id(device_desc.vendor_id()).map(|v| v.name().to_owned())
}),
product_name: names::product(device_desc.vendor_id(), device_desc.product_id())
.or_else(|| {
usb_ids::Device::from_vid_pid(device_desc.vendor_id(), device_desc.product_id())
.map(|v| v.name().to_owned())
}),
configurations: self.build_configurations(
device,
Some(handle),
device_desc,
sp_device,
)?,
status: None,
debug: None,
binary_object_store: None,
qualifier: None,
hub: None,
negotiated_speed: Some(usb::Speed::from(device.speed())),
};
if more_extra {
extra.status = Self::get_device_status(handle).ok();
extra.debug = Self::get_debug_descriptor(handle).ok();
if device_desc.usb_version() >= rusb::Version::from_bcd(0x0201) {
extra.binary_object_store = Self::get_bos_descriptor(handle).ok();
}
if device_desc.usb_version() >= rusb::Version::from_bcd(0x0200) {
extra.qualifier = Self::get_device_qualifier(handle).ok();
}
if device_desc.class_code() == usb::BaseClass::Hub as u8 {
let has_ssp = if let Some(bos) = &extra.binary_object_store {
bos.capabilities.iter().any(|c| {
matches!(c, usb::descriptors::bos::BosCapability::SuperSpeedPlus(_))
})
} else {
false
};
let bcd = sp_device.bcd_usb.map_or(0x0100, |v| v.into());
extra.hub =
Self::get_hub_descriptor(handle, device_desc.protocol_code(), bcd, has_ssp)
.ok();
}
}
if extra.hub.is_none()
&& (device_desc.class_code() == usb::BaseClass::Hub as u8 || sp_device.is_root_hub())
{
if let Some(ports) = get_sysfs_hub_ports(&sysfs_name) {
extra.hub = Some(usb::HubDescriptor {
num_ports: ports,
..Default::default()
});
}
}
Ok(extra)
}
fn open_device<T: libusb::UsbContext>(
&self,
device: &libusb::Device<T>,
device_desc: &libusb::DeviceDescriptor,
) -> Result<UsbDevice<T>> {
let timeout = std::time::Duration::from_millis(
std::env::var("CYME_USB_TIMEOUT_MS")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(200),
);
let handle = device.open()?;
let language = match handle.read_languages(timeout) {
Ok(l) => {
if l.is_empty() {
return Err(Error {
kind: ErrorKind::LibUSB,
message: format!(
"Languages for {device:?} are empty, will be unable to obtain all data"
),
});
}
l[0]
}
Err(e) => {
return Err(Error {
kind: ErrorKind::LibUSB,
message: format!(
"Could not read languages for {device:?}, will be unable to obtain all data: {e}"
),
});
}
};
Ok(UsbDevice {
handle,
language,
vidpid: (device_desc.vendor_id(), device_desc.product_id()),
location: DeviceLocation {
bus: device.bus_number(),
number: device.address(),
tree_positions: device.port_numbers()?,
},
timeout,
})
}
fn build_spdevice_shallow<T: libusb::UsbContext>(
&self,
device: &libusb::Device<T>,
) -> Result<Device> {
let speed = match usb::Speed::from(device.speed()) {
usb::Speed::Unknown => None,
v => Some(DeviceSpeed::SpeedValue(v)),
};
let device_desc = device.device_descriptor()?;
let mut sp_device = Device {
vendor_id: Some(device_desc.vendor_id()),
product_id: Some(device_desc.product_id()),
device_speed: speed,
location_id: DeviceLocation {
bus: device.bus_number(),
number: device.address(),
tree_positions: device.port_numbers()?,
},
bcd_device: Some(device_desc.device_version().into()),
bcd_usb: Some(device_desc.usb_version().into()),
class: Some(usb::BaseClass::from(device_desc.class_code())),
sub_class: Some(device_desc.sub_class_code()),
protocol: Some(device_desc.protocol_code()),
last_event: Some(Default::default()),
..Default::default()
};
let sysfs_name = sp_device.sysfs_name();
sp_device.name = get_sysfs_string(&sysfs_name, "product")
.or_else(|| names::product(device_desc.vendor_id(), device_desc.product_id()))
.or_else(|| {
usb_ids::Device::from_vid_pid(device_desc.vendor_id(), device_desc.product_id())
.map(|device| device.name().to_owned())
})
.unwrap_or_default();
sp_device.manufacturer = get_sysfs_string(&sysfs_name, "manufacturer").or_else(|| {
names::vendor(device_desc.vendor_id()).or_else(|| {
usb_ids::Vendor::from_id(device_desc.vendor_id())
.map(|vendor| vendor.name().to_owned())
})
});
sp_device.serial_num = get_sysfs_string(&sysfs_name, "serial");
let mut extra = usb::DeviceExtra {
max_packet_size: device_desc.max_packet_size(),
string_indexes: (
device_desc.product_string_index().unwrap_or(0),
device_desc.manufacturer_string_index().unwrap_or(0),
device_desc.serial_number_string_index().unwrap_or(0),
),
driver: get_sysfs_readlink(&sysfs_name, "driver")
.or_else(|| get_udev_driver_name(&sysfs_name).ok().flatten()),
syspath: get_syspath(&sysfs_name)
.or_else(|| get_udev_syspath(&sysfs_name).ok().flatten()),
vendor: names::vendor(device_desc.vendor_id()).or_else(|| {
usb_ids::Vendor::from_id(device_desc.vendor_id()).map(|v| v.name().to_owned())
}),
product_name: names::product(device_desc.vendor_id(), device_desc.product_id())
.or_else(|| {
usb_ids::Device::from_vid_pid(device_desc.vendor_id(), device_desc.product_id())
.map(|v| v.name().to_owned())
}),
configurations: self
.build_configurations(device, None, &device_desc, &sp_device)
.unwrap_or_default(),
status: None,
debug: None,
binary_object_store: None,
qualifier: None,
hub: None,
negotiated_speed: None,
};
if extra.hub.is_none()
&& (device_desc.class_code() == usb::BaseClass::Hub as u8 || sp_device.is_root_hub())
{
if let Some(ports) = get_sysfs_hub_ports(&sp_device.sysfs_name()) {
extra.hub = Some(usb::HubDescriptor {
num_ports: ports,
..Default::default()
});
}
}
sp_device.extra = Some(extra);
Ok(sp_device)
}
fn build_spdevice<T: libusb::UsbContext>(
&self,
device: &libusb::Device<T>,
options: &ProfilerOptions,
) -> Result<Device> {
let mut sp_device = self.build_spdevice_shallow(device)?;
let device_desc = device.device_descriptor()?;
let is_match = options
.filter
.as_ref()
.is_none_or(|f| f.is_potential_match(&sp_device));
if options.depth.includes_extra() && is_match {
if let Ok(handle) = self.open_device(device, &device_desc) {
sp_device.profiler_error = {
match self.build_spdevice_extra(
device,
&handle,
&device_desc,
&mut sp_device,
options.depth.includes_more_extra(),
) {
Ok(extra) => {
sp_device.extra = Some(extra);
None
}
Err(e) => {
Some(format!(
"Failed to get some extra data for {sp_device}, probably requires elevated permissions: {e}"
))
}
}
}
} else {
log::warn!("Failed to open device {device:?} for extra data");
sp_device.profiler_error = Some("Failed to open device for extra data".to_string());
}
}
Ok(sp_device)
}
}
impl<C: libusb::UsbContext> Profiler<UsbDevice<C>> for LibUsbProfiler {
fn get_devices(&mut self, options: &ProfilerOptions) -> Result<Vec<Device>> {
let mut devices = Vec::new();
let raw_devices = libusb::DeviceList::new()?;
let filter_set: Option<std::collections::HashSet<(u8, Vec<u8>)>> =
if let Some(filter) = &options.filter {
let mut set = std::collections::HashSet::new();
for device in raw_devices.iter().filter(|d| d.port_number() != 0) {
if let Ok(sp_device) = self.build_spdevice_shallow(&device) {
if filter.is_potential_match(&sp_device) {
let bus = sp_device.location_id.bus;
let ports = sp_device.location_id.tree_positions.clone();
set.insert((bus, ports.clone()));
if options.tree {
for i in 1..ports.len() {
set.insert((bus, ports[0..i].to_vec()));
}
}
}
}
}
Some(set)
} else {
None
};
for device in raw_devices.iter().filter(|d| d.port_number() != 0) {
let bus = device.bus_number();
let ports = device.port_numbers().unwrap_or_default();
if let Some(set) = &filter_set {
if !set.contains(&(bus, ports.clone())) {
continue;
}
}
let mut device_options = options.clone();
if let Some(filter) = &options.filter {
if let Ok(sp_device) = self.build_spdevice_shallow(&device) {
if !filter.is_potential_match(&sp_device) {
device_options.depth = ProfileDepth::Identity;
}
}
}
match self.build_spdevice(&device, &device_options) {
Ok(sp_device) => {
devices.push(sp_device.to_owned());
let print_stderr =
std::env::var_os("CYME_PRINT_NON_CRITICAL_PROFILER_STDERR").is_some();
sp_device.profiler_error.iter().for_each(|e| {
if print_stderr {
eprintln!("{e}");
} else {
log::warn!("Non-critical error during profile: {e}");
}
});
}
Err(e) => eprintln!("Failed to get data for {device:?}: {e}"),
}
}
Ok(devices)
}
#[cfg(target_os = "linux")]
fn get_root_hubs(&mut self, options: &ProfilerOptions) -> Result<HashMap<u8, Device>> {
let mut ret = HashMap::new();
for device in libusb::DeviceList::new()?
.iter()
.filter(|d| d.port_number() == 0)
{
if let Ok(mut sp_device) = self.build_spdevice(&device, options) {
sp_device.devices = Some(vec![sp_device.clone()]);
ret.insert(sp_device.location_id.bus, sp_device);
}
}
Ok(ret)
}
#[cfg(not(target_os = "linux"))]
fn get_root_hubs(&mut self, _options: &ProfilerOptions) -> Result<HashMap<u8, Device>> {
Ok(HashMap::new())
}
fn get_buses(&mut self, options: &ProfilerOptions) -> Result<HashMap<u8, Bus>> {
<LibUsbProfiler as Profiler<UsbDevice<rusb::Context>>>::get_root_hubs(self, options).map(
|hubs| {
hubs.into_iter()
.filter_map(|(k, d)| Some((k, Bus::try_from(d).ok()?)))
.collect()
},
)
}
}
pub(crate) fn fill_spusb(spusb: &mut SystemProfile, options: &ProfilerOptions) -> Result<()> {
let mut profiler = LibUsbProfiler;
<LibUsbProfiler as Profiler<UsbDevice<rusb::Context>>>::fill_spusb(
&mut profiler,
spusb,
options,
)
}