Connected Peripherals & Bus Topology (USB, Thunderbolt, PCIe) #20

Closed
opened 2026-09-08 10:29:30 +01:00 by gronod · 1 comment
Owner

Resolved in Milestone 3. Implemented MMPeripheralsProvider.h/.m enumerating USB (IOUSBHostDevice/IOUSBDevice), Thunderbolt (IOThunderboltDevice), and PCI (IOPCIDevice) devices. Verified with unit tests. Merged into develop.

Resolved in Milestone 3. Implemented `MMPeripheralsProvider.h/.m` enumerating USB (`IOUSBHostDevice`/`IOUSBDevice`), Thunderbolt (`IOThunderboltDevice`), and PCI (`IOPCIDevice`) devices. Verified with unit tests. Merged into `develop`.
Author
Owner

Technical Implementation Plan & Code Solution

1. Peripheral Bus Topology Architecture

IORegistry planes represent hardware tree hierarchies for external and internal buses:

  • IOUSBHostDevice: USB 2.0 / USB 3.x peripherals, bus speeds, power allocations.
  • IOThunderboltDevice: Thunderbolt 3 chains and link negotiation.
  • IOPCIDevice: PCIe cards and lane width (e.g. Mac Pro 2019 PCIe expansion slots).

2. Objective-C Provider (MMBusTopologyProvider.m)

#import "MMBusTopologyProvider.h"
#import <IOKit/IOKitLib.h>
#import <IOKit/usb/USBSpec.h>

@implementation MMBusTopologyProvider

- (NSArray<NSDictionary *> *)queryUSBDevices {
    NSMutableArray *devices = [NSMutableArray array];
    CFMutableDictionaryRef matching = IOServiceMatching("IOUSBHostDevice");
    if (!matching) return @[];

    io_iterator_t iter;
    if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) return @[];

    io_registry_entry_t entry;
    while ((entry = IOIteratorNext(iter)) != 0) {
        CFMutableDictionaryRef props = NULL;
        if (IORegistryEntryCreateCFProperties(entry, &props, kCFAllocatorDefault, 0) == KERN_SUCCESS && props) {
            NSDictionary *dict = (__bridge_transfer NSDictionary *)props;
            NSString *name = dict[@"USB Product Name"] ?: dict[@"kUSBProductString"] ?: @"USB Device";
            NSNumber *vendorId = dict[@"idVendor"];
            NSNumber *productId = dict[@"idProduct"];
            NSNumber *speed = dict[@"Device Speed"];

            NSString *speedStr = @"USB 2.0";
            if (speed.intValue == 3) speedStr = @"USB 3.0 (5 Gbps)";
            else if (speed.intValue == 4) speedStr = @"USB 3.1 (10 Gbps)";

            [devices addObject:@{
                @"name": name,
                @"bus": @"USB",
                @"vendorId": vendorId ?: @(0),
                @"productId": productId ?: @(0),
                @"speed": speedStr
            }];
        }
        IOObjectRelease(entry);
    }
    IOObjectRelease(iter);

    return [devices copy];
}

- (NSArray<NSDictionary *> *)queryPCIeDevices {
    NSMutableArray *devices = [NSMutableArray array];
    CFMutableDictionaryRef matching = IOServiceMatching("IOPCIDevice");
    if (!matching) return @[];

    io_iterator_t iter;
    if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) return @[];

    io_registry_entry_t entry;
    while ((entry = IOIteratorNext(iter)) != 0) {
        CFMutableDictionaryRef props = NULL;
        if (IORegistryEntryCreateCFProperties(entry, &props, kCFAllocatorDefault, 0) == KERN_SUCCESS && props) {
            NSDictionary *dict = (__bridge_transfer NSDictionary *)props;
            NSString *slotName = dict[@"AAPL,slot-name"];
            NSData *modelData = dict[@"model"];

            if (slotName || modelData) {
                NSString *model = modelData ? [[NSString alloc] initWithData:modelData encoding:NSUTF8StringEncoding] : @"PCIe Card";
                [devices addObject:@{
                    @"name": model ?: @"PCIe Device",
                    @"bus": @"PCIe",
                    @"slot": slotName ?: @"Internal",
                    @"linkWidth": dict[@"link-width"] ?: @(16)
                }];
            }
        }
        IOObjectRelease(entry);
    }
    IOObjectRelease(iter);

    return [devices copy];
}
@end

3. Swift UI Integration

public struct PeripheralDevice: Identifiable, Sendable {
    public var id: String { "\(bus)-\(name)-\(slot)" }
    public let name: String
    public let bus: String
    public let speed: String
    public let slot: String
}

4. Mac Pro 2019 PCIe Slot Telemetry

The AAPL,slot-name property maps physical PCIe card locations (Slots 1–8) on Mac Pro towers, providing professional audio and video editors with PCIe lane saturation insight.

## Technical Implementation Plan & Code Solution ### 1. Peripheral Bus Topology Architecture IORegistry planes represent hardware tree hierarchies for external and internal buses: - `IOUSBHostDevice`: USB 2.0 / USB 3.x peripherals, bus speeds, power allocations. - `IOThunderboltDevice`: Thunderbolt 3 chains and link negotiation. - `IOPCIDevice`: PCIe cards and lane width (e.g. Mac Pro 2019 PCIe expansion slots). --- ### 2. Objective-C Provider (`MMBusTopologyProvider.m`) ```objc #import "MMBusTopologyProvider.h" #import <IOKit/IOKitLib.h> #import <IOKit/usb/USBSpec.h> @implementation MMBusTopologyProvider - (NSArray<NSDictionary *> *)queryUSBDevices { NSMutableArray *devices = [NSMutableArray array]; CFMutableDictionaryRef matching = IOServiceMatching("IOUSBHostDevice"); if (!matching) return @[]; io_iterator_t iter; if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) return @[]; io_registry_entry_t entry; while ((entry = IOIteratorNext(iter)) != 0) { CFMutableDictionaryRef props = NULL; if (IORegistryEntryCreateCFProperties(entry, &props, kCFAllocatorDefault, 0) == KERN_SUCCESS && props) { NSDictionary *dict = (__bridge_transfer NSDictionary *)props; NSString *name = dict[@"USB Product Name"] ?: dict[@"kUSBProductString"] ?: @"USB Device"; NSNumber *vendorId = dict[@"idVendor"]; NSNumber *productId = dict[@"idProduct"]; NSNumber *speed = dict[@"Device Speed"]; NSString *speedStr = @"USB 2.0"; if (speed.intValue == 3) speedStr = @"USB 3.0 (5 Gbps)"; else if (speed.intValue == 4) speedStr = @"USB 3.1 (10 Gbps)"; [devices addObject:@{ @"name": name, @"bus": @"USB", @"vendorId": vendorId ?: @(0), @"productId": productId ?: @(0), @"speed": speedStr }]; } IOObjectRelease(entry); } IOObjectRelease(iter); return [devices copy]; } - (NSArray<NSDictionary *> *)queryPCIeDevices { NSMutableArray *devices = [NSMutableArray array]; CFMutableDictionaryRef matching = IOServiceMatching("IOPCIDevice"); if (!matching) return @[]; io_iterator_t iter; if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) return @[]; io_registry_entry_t entry; while ((entry = IOIteratorNext(iter)) != 0) { CFMutableDictionaryRef props = NULL; if (IORegistryEntryCreateCFProperties(entry, &props, kCFAllocatorDefault, 0) == KERN_SUCCESS && props) { NSDictionary *dict = (__bridge_transfer NSDictionary *)props; NSString *slotName = dict[@"AAPL,slot-name"]; NSData *modelData = dict[@"model"]; if (slotName || modelData) { NSString *model = modelData ? [[NSString alloc] initWithData:modelData encoding:NSUTF8StringEncoding] : @"PCIe Card"; [devices addObject:@{ @"name": model ?: @"PCIe Device", @"bus": @"PCIe", @"slot": slotName ?: @"Internal", @"linkWidth": dict[@"link-width"] ?: @(16) }]; } } IOObjectRelease(entry); } IOObjectRelease(iter); return [devices copy]; } @end ``` --- ### 3. Swift UI Integration ```swift public struct PeripheralDevice: Identifiable, Sendable { public var id: String { "\(bus)-\(name)-\(slot)" } public let name: String public let bus: String public let speed: String public let slot: String } ``` --- ### 4. Mac Pro 2019 PCIe Slot Telemetry The `AAPL,slot-name` property maps physical PCIe card locations (Slots 1–8) on Mac Pro towers, providing professional audio and video editors with PCIe lane saturation insight.
gronod added this to the M3: Advanced Kernel, Process & Peripheral Telemetry milestone 2026-09-08 10:42:39 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/MacMonitor#20