From 138214fd8cce324288716255d177c056c2196272 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 22 Jun 2023 22:09:18 -0600 Subject: [PATCH] debice_probe.py: Add IOUSBDevice parsing --- CHANGELOG.md | 4 ++ resources/device_probe.py | 120 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32a12f6bd..3836f30f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ - Drops ColorSync downgrade configuration option - Resolve app not updating in `/Applications` after an update - Work-around users manually copying app to `/Applications` instead of allowing Root Volume Patcher to create a proper alias +- Backend Changes: + - device_probe.py: + - Add USB device parsing via `IOUSBDevice` class + - Streamline Bluetooth device detection - Increment Binaries: - PatcherSupportPkg 1.1.4 - release diff --git a/resources/device_probe.py b/resources/device_probe.py index d30a4c7d1..f82bd0977 100644 --- a/resources/device_probe.py +++ b/resources/device_probe.py @@ -22,6 +22,94 @@ class CPU: leafs: list[str] +@dataclass +class USBDevice: + vendor_id: int + device_id: int + device_class: int + device_speed: int + product_name: str + vendor_name: Optional[str] = None + + @classmethod + def from_ioregistry(cls, entry: ioreg.io_registry_entry_t): + properties: dict = ioreg.corefoundation_to_native(ioreg.IORegistryEntryCreateCFProperties(entry, None, ioreg.kCFAllocatorDefault, ioreg.kNilOptions)[1]) + + vendor_id = None + device_id = None + device_class = None + device_speed = None + vendor_name = None + product_name = "N/A" + + if "idVendor" in properties: + vendor_id = properties["idVendor"] + if "idProduct" in properties: + device_id = properties["idProduct"] + if "bDeviceClass" in properties: + device_class = properties["bDeviceClass"] + if "kUSBProductString" in properties: + product_name = properties["kUSBProductString"].strip() + if "kUSBVendorString" in properties: + vendor_name = properties["kUSBVendorString"].strip() + if "USBSpeed" in properties: + device_speed = properties["USBSpeed"] + + return cls(vendor_id, device_id, device_class, device_speed, product_name, vendor_name) + + + def detect(self): + self.detect_class() + self.detect_speed() + + + def detect_class(self) -> None: + for device_class in self.ClassCode: + if self.device_class == device_class.value: + self.device_class = device_class + + + def detect_speed(self) -> None: + for speed in self.Speed: + if self.device_speed == speed.value: + self.device_speed = speed + + class Speed(enum.Enum): + LOW_SPEED = 0x01 + FULL_SPEED = 0x02 + HIGH_SPEED = 0x03 + SUPER_SPEED = 0x04 + SUPER_SPEED_PLUS = 0x05 + + + class ClassCode(enum.Enum): + # https://www.usb.org/defined-class-codes + GENERIC = 0x00 + AUDIO = 0x01 + CDC_CONTROL = 0x02 + HID = 0x03 + PHYSICAL = 0x05 + IMAGE = 0x06 + PRINTER = 0x07 + MASS_STORAGE = 0x08 + HUB = 0x09 + CDC_DATA = 0x0A + SMART_CARD = 0x0B + CONTENT_SEC = 0x0D + VIDEO = 0x0E + PERSONAL_HEALTH = 0x0F + AUDIO_VIDEO = 0x10 + BILLBOARD = 0x11 + USB_TYPE_C_BRIDGE = 0x12 + DISPLAY_BDP = 0x13 + I3C = 0x3C + DIAGNOSTIC = 0xDC + WIRELESS = 0xE0 + MISCELLANEOUS = 0xEF + APPLICATION = 0xFE + VENDOR_SPEC = 0xFF + + @dataclass class PCIDevice: VENDOR_ID: ClassVar[int] # Default vendor id, for subclasses. @@ -502,6 +590,7 @@ class Computer: ethernet: Optional[EthernetController] = field(default_factory=list) wifi: Optional[WirelessCard] = None cpu: Optional[CPU] = None + usb_devices: list[USBDevice] = field(default_factory=list) oclp_version: Optional[str] = None opencore_version: Optional[str] = None opencore_path: Optional[str] = None @@ -528,6 +617,7 @@ class Computer: computer.sdxc_controller_probe() computer.ethernet_probe() computer.smbios_probe() + computer.usb_device_probe() computer.cpu_probe() computer.bluetooth_probe() computer.ambient_light_sensor_probe() @@ -536,6 +626,21 @@ class Computer: computer.check_rosetta() return computer + + def usb_device_probe(self): + devices = ioreg.ioiterator_to_list( + ioreg.IOServiceGetMatchingServices( + ioreg.kIOMasterPortDefault, {"IOProviderClass": "IOUSBDevice"}, None + )[1] + ) + for device in devices: + properties = USBDevice.from_ioregistry(device) + if properties: + properties.detect() + self.usb_devices.append(properties) + ioreg.IOObjectRelease(device) + + def gpu_probe(self): # Chain together two iterators: one for class code 00000300, the other for class code 00800300 devices = ioreg.ioiterator_to_list( @@ -760,16 +865,19 @@ class Computer: return leafs def bluetooth_probe(self): - usb_data: str = subprocess.run("system_profiler SPUSBDataType".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode() - if "BRCM20702 Hub" in usb_data: + if not self.usb_devices: + return + + # Ensure we get the "best" bluetooth chipset (if multiple are present) + if any("BRCM20702" in usb_device.product_name for usb_device in self.usb_devices): self.bluetooth_chipset = "BRCM20702 Hub" - elif "BCM20702A0" in usb_data or "BCM2045A0" in usb_data: + elif any("BCM20702A0" in usb_device.product_name or "BCM2045A0" in usb_device.product_name for usb_device in self.usb_devices): self.bluetooth_chipset = "3rd Party Bluetooth 4.0 Hub" - elif "BRCM2070 Hub" in usb_data: + elif any("BRCM2070 Hub" in usb_device.product_name for usb_device in self.usb_devices): self.bluetooth_chipset = "BRCM2070 Hub" - elif "BRCM2046 Hub" in usb_data: + elif any("BRCM2046 Hub" in usb_device.product_name for usb_device in self.usb_devices): self.bluetooth_chipset = "BRCM2046 Hub" - elif "Bluetooth" in usb_data: + elif any("Bluetooth" in usb_device.product_name for usb_device in self.usb_devices): self.bluetooth_chipset = "Generic" def sata_disk_probe(self):