use super::*;
use std::process::Command;
use core_foundation::{
base::{CFType, TCFType},
data::CFData,
string::CFString,
ConcreteCFType,
};
use io_kit_sys::{
kIOMasterPortDefault, kIORegistryIterateParents, kIORegistryIterateRecursively,
keys::kIOServicePlane, ret::kIOReturnSuccess, IOIteratorNext, IOObjectRelease,
IORegistryEntryGetRegistryEntryID, IORegistryEntrySearchCFProperty,
IOServiceGetMatchingServices, IOServiceNameMatching,
};
pub(crate) struct IoObject(u32);
impl IoObject {
pub unsafe fn new(handle: u32) -> IoObject {
IoObject(handle)
}
pub fn get(&self) -> u32 {
self.0
}
}
impl Drop for IoObject {
fn drop(&mut self) {
unsafe {
IOObjectRelease(self.0);
}
}
}
pub(crate) struct IoService(IoObject);
impl IoService {
pub unsafe fn new(handle: u32) -> IoService {
IoService(IoObject(handle))
}
pub fn get(&self) -> u32 {
self.0 .0
}
}
pub(crate) struct IoServiceIterator(IoObject);
impl IoServiceIterator {
pub unsafe fn new(handle: u32) -> IoServiceIterator {
IoServiceIterator(IoObject::new(handle))
}
}
impl Iterator for IoServiceIterator {
type Item = IoService;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let handle = IOIteratorNext(self.0.get());
if handle != 0 {
Some(IoService::new(handle))
} else {
None
}
}
}
}
pub(crate) struct HostControllerInfo {
pub(crate) name: String,
pub(crate) class_name: String,
pub(crate) io_name: String,
pub(crate) registry_id: u64,
pub(crate) vendor_id: u16,
pub(crate) device_id: u16,
pub(crate) revision_id: u16,
pub(crate) class_code: u32,
pub(crate) subsystem_vendor_id: Option<u16>,
pub(crate) subsystem_id: Option<u16>,
}
impl std::fmt::Debug for HostControllerInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PciControllerInfo")
.field("name", &self.name)
.field("class_name", &self.class_name)
.field("io_name", &self.io_name)
.field("registry_id", &format!("{:08x}", self.registry_id))
.field("vendor_id", &format!("{:04x}", self.vendor_id))
.field("device_id", &format!("{:04x}", self.device_id))
.field("revision_id", &format!("{:04x}", self.revision_id))
.field("class_code", &format!("{:08x}", self.class_code))
.field("subsystem_vendor_id", &self.subsystem_vendor_id)
.field("subsystem_id", &self.subsystem_id)
.finish()
}
}
pub(crate) fn get_registry_id(device: &IoService) -> Option<u64> {
unsafe {
let mut out = 0;
let r = IORegistryEntryGetRegistryEntryID(device.get(), &mut out);
if r == kIOReturnSuccess {
Some(out)
} else {
log::debug!("IORegistryEntryGetRegistryEntryID failed with {r}");
None
}
}
}
fn get_property<T: ConcreteCFType>(device: &IoService, property: &'static str) -> Option<T> {
unsafe {
let cf_property = CFString::from_static_string(property);
let raw = IORegistryEntrySearchCFProperty(
device.get(),
kIOServicePlane as *mut i8,
cf_property.as_CFTypeRef() as *const _,
std::ptr::null(),
kIORegistryIterateRecursively | kIORegistryIterateParents,
);
if raw.is_null() {
log::debug!("Device does not have property `{property}`");
return None;
}
let res = CFType::wrap_under_create_rule(raw).downcast_into();
if res.is_none() {
log::debug!("Failed to convert device property `{property}`");
}
res
}
}
fn get_string_property(device: &IoService, property: &'static str) -> Option<String> {
get_property::<CFString>(device, property).map(|s| s.to_string())
}
fn get_byte_array_property(device: &IoService, property: &'static str) -> Option<Vec<u8>> {
let d = get_property::<CFData>(device, property)?;
Some(d.bytes().to_vec())
}
fn get_ascii_array_property(device: &IoService, property: &'static str) -> Option<String> {
let d = get_property::<CFData>(device, property)?;
Some(
d.bytes()
.iter()
.map(|b| *b as char)
.filter(|c| *c != '\0')
.collect(),
)
}
pub(crate) fn probe_controller(device: IoService) -> Option<HostControllerInfo> {
let registry_id = get_registry_id(&device)?;
log::debug!("Probing controller {registry_id:08x}");
let name = get_ascii_array_property(&device, "name")?;
let class_name = get_string_property(&device, "IOClass")?;
let io_name = get_string_property(&device, "IOName")?;
let vendor_id =
get_byte_array_property(&device, "vendor-id").map(|v| u16::from_le_bytes([v[0], v[1]]))?;
let device_id =
get_byte_array_property(&device, "device-id").map(|v| u16::from_le_bytes([v[0], v[1]]))?;
let revision_id = get_byte_array_property(&device, "revision-id")
.map(|v| u16::from_le_bytes([v[0], v[1]]))?;
let class_code = get_byte_array_property(&device, "class-code")
.map(|v| u32::from_le_bytes([v[0], v[1], v[2], v[3]]))?;
let subsystem_vendor_id = get_byte_array_property(&device, "subsystem-vendor-id")
.map(|v| u16::from_le_bytes([v[0], v[1]]));
let subsystem_id =
get_byte_array_property(&device, "subsystem-id").map(|v| u16::from_le_bytes([v[0], v[1]]));
Some(HostControllerInfo {
name,
class_name,
io_name,
registry_id,
vendor_id,
device_id,
revision_id,
class_code,
subsystem_vendor_id,
subsystem_id,
})
}
pub(crate) fn get_controller(name: &str) -> Result<HostControllerInfo> {
unsafe {
let dictionary = IOServiceNameMatching(name.as_ptr() as *const i8);
if dictionary.is_null() {
return Err(Error::new(ErrorKind::IoKit, "IOServiceMatching failed"));
}
let mut iterator = 0;
let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator);
if r != kIOReturnSuccess {
return Err(Error::new(ErrorKind::IoKit, &r.to_string()));
}
IoServiceIterator::new(iterator)
.next()
.and_then(probe_controller)
.ok_or(Error::new(
ErrorKind::IoKit,
&format!("No controller found for {name}"),
))
}
}
pub fn get_spusb() -> Result<SystemProfile> {
get_spusb_with_options(&ProfilerOptions::default())
}
pub fn get_spusb_with_extra() -> Result<SystemProfile> {
get_spusb_with_options(&ProfilerOptions {
depth: ProfileDepth::Standard,
..Default::default()
})
}
pub fn get_spusb_with_options(options: &ProfilerOptions) -> Result<SystemProfile> {
let output = Command::new("system_profiler")
.args(["-timeout", "5", "-json", "SPUSBDataType"])
.output()?;
if output.status.success() {
let mut sp: SystemProfile =
serde_json::from_str(String::from_utf8(output.stdout)?.as_str()).map_err(|e| {
Error::new(
ErrorKind::Parsing,
&format!("Failed to parse 'system_profiler -json SPUSBDataType'; Error({e})"),
)
})?;
for bus in sp.buses.iter_mut() {
bus.fill_host_controller_from_ids();
}
if options.depth.includes_extra() {
#[cfg(all(feature = "libusb", not(feature = "nusb")))]
{
crate::profiler::libusb::fill_spusb(&mut sp, options)?;
}
#[cfg(feature = "nusb")]
{
crate::profiler::nusb::fill_spusb(&mut sp, options)?;
}
#[cfg(all(not(feature = "libusb"), not(feature = "nusb")))]
{
return Err(Error::new(
ErrorKind::Unsupported,
"nusb or libusb feature is required to do this, install with `cargo install --features nusb/libusb`",
));
}
}
Ok(sp)
} else {
log::error!(
"system_profiler returned non-zero stderr: {:?}, stdout: {:?}",
String::from_utf8(output.stderr)?,
String::from_utf8(output.stdout)?
);
Err(Error::new(
ErrorKind::SystemProfiler,
"system_profiler returned non-zero, use '--force-libusb' to bypass",
))
}
}