‹ projects

reverb-m1

Native wired VR on Apple silicon: Reverb G1 display bring-up and stereo demo
git clone https://umhau.org/code/reverb-m1.git
Log | Files | Refs

hpmctl.c (7276B)


      1 // hpmctl: talk to the Mac's USB-C port controllers (Apple "Ace" / CD3217, TI TPS6598x family) through
      2 // Apple's AppleHPMLib IOKit plug-in, the same path /usr/bin/hpmdiagnose uses.
      3 //
      4 // Usage: hpmctl <rid> read <reg>          read one controller register (hex), e.g. `hpmctl 1 read 0x58`
      5 //        hpmctl <rid> cmd <4cc>           run a 4-char controller command, e.g. `hpmctl 1 cmd DRST` (USB-PD Data Reset)
      6 //                                         RID 0 is refused: it carries the hub, the headset's USB and the Mac's power.
      7 //
      8 // RID 0 = Port-USB-C@1 (hub, headset USB), RID 1 = Port-USB-C@2 (DP adapter) on this MacBook Air.
      9 // Plug-in function table layout (from dyld_info fixups of AppleHPMLib, macOS 15.6.1):
     10 //   +0x08 QueryInterface  +0x10 AddRef  +0x18 Release  +0x28 iecsRead  +0x30 iecsWrite  +0x38 iecsCommand
     11 //   +0x40 sendVDM  +0x48 receiveVDM  +0x50 receiveVDMAttention
     12 // Build: clang -O2 -framework IOKit -framework CoreFoundation hpmctl.c -o hpmctl
     13 #include <CoreFoundation/CoreFoundation.h>
     14 #include <IOKit/IOCFPlugIn.h>
     15 #include <IOKit/IOKitLib.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 
     20 typedef struct HPMInterface {
     21     IUNKNOWN_C_GUTS;
     22     uint16_t version, revision;
     23     IOReturn (*iecsRead)(void *self, uint64_t route, uint8_t reg, void *buf, uint64_t len, uint32_t flags, uint64_t *outLen);
     24     IOReturn (*iecsWrite)(void *self, uint64_t route, uint8_t reg, const void *buf, uint64_t len, uint32_t flags);
     25     IOReturn (*iecsCommand)(void *self, uint64_t route, uint32_t cmd4cc, uint32_t flags);
     26 } HPMInterface;
     27 
     28 // Plug-in type and HPM interface IDs, from AppleHPMLib's Info.plist and queryInterfaceStatic.
     29 #define kHPMPlugInTypeID CFUUIDGetConstantUUIDWithBytes(NULL, 0x12, 0xA1, 0xDC, 0xCF, 0xCF, 0x7A, 0x47, 0x75, \
     30                                                         0xBE, 0xE5, 0x9C, 0x43, 0x19, 0xF4, 0xCD, 0x2B)
     31 #define kHPMInterfaceID CFUUIDGetConstantUUIDWithBytes(NULL, 0xC1, 0x3A, 0xCD, 0xD9, 0x20, 0x9E, 0x4B, 0x01, \
     32                                                        0xB7, 0xBE, 0xE0, 0x5C, 0xD8, 0x83, 0xC7, 0xB1)
     33 
     34 /// Finds the AppleHPM bus service whose child AppleHPMDevice has the given RID, and opens the plug-in on it.
     35 static HPMInterface **openHPM(int rid, int verifyPort) {
     36     io_iterator_t it;
     37     if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("AppleHPMDevice"), &it) != KERN_SUCCESS) return NULL;
     38     io_service_t dev, found = 0;
     39     while (!found && (dev = IOIteratorNext(it))) {
     40         CFNumberRef n = IORegistryEntryCreateCFProperty(dev, CFSTR("RID"), NULL, 0);
     41         int v = -1;
     42         if (n) { CFNumberGetValue(n, kCFNumberIntType, &v); CFRelease(n); }
     43         if (v == rid) {
     44             int correctPort = !verifyPort;
     45             io_iterator_t children;
     46             if (verifyPort && IORegistryEntryGetChildIterator(dev, kIOServicePlane, &children) == KERN_SUCCESS) {
     47                 io_registry_entry_t child;
     48                 while ((child = IOIteratorNext(children))) {
     49                     io_string_t path;
     50                     if (IORegistryEntryGetPath(child, kIOServicePlane, path) == KERN_SUCCESS) {
     51                         const char *last = strrchr(path, '/');
     52                         if (last && strcmp(last, "/Port-USB-C@2") == 0) correctPort = 1;
     53                     }
     54                     IOObjectRelease(child);
     55                 }
     56                 IOObjectRelease(children);
     57             }
     58             if (correctPort) IORegistryEntryGetParentEntry(dev, kIOServicePlane, &found);
     59             else fprintf(stderr, "RID %d is not verified as Port-USB-C@2; refusing command\n", rid);
     60         }
     61         IOObjectRelease(dev);
     62     }
     63     IOObjectRelease(it);
     64     if (!found) { fprintf(stderr, "no AppleHPMDevice with RID %d\n", rid); return NULL; }
     65 
     66     IOCFPlugInInterface **plugin = NULL;
     67     SInt32 score;
     68     kern_return_t kr = IOCreatePlugInInterfaceForService(found, kHPMPlugInTypeID, kIOCFPlugInInterfaceID, &plugin, &score);
     69     IOObjectRelease(found);
     70     if (kr != KERN_SUCCESS || !plugin) { fprintf(stderr, "IOCreatePlugInInterfaceForService: 0x%x\n", kr); return NULL; }
     71     HPMInterface **hpm = NULL;
     72     HRESULT hr = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kHPMInterfaceID), (LPVOID *)&hpm);
     73     (*plugin)->Release(plugin);
     74     if (hr != S_OK || !hpm) { fprintf(stderr, "QueryInterface: 0x%x\n", (int)hr); return NULL; }
     75     return hpm;
     76 }
     77 
     78 int main(int argc, char **argv) {
     79     if (argc < 3) { fprintf(stderr, "usage: hpmctl <rid> read <reg> | hpmctl <rid> cmd <4cc> | hpmctl 1 recover\n"); return 2; }
     80     char *end;
     81     long ridValue = strtol(argv[1], &end, 10);
     82     if (!*argv[1] || *end || ridValue < 0 || ridValue > 1) { fprintf(stderr, "RID must be 0 or 1\n"); return 2; }
     83     int rid = (int)ridValue;
     84     if (strcmp(argv[2], "recover") == 0) {
     85         if (rid != 1 || argc != 3) { fprintf(stderr, "recovery is restricted to RID 1 (DP adapter)\n"); return 2; }
     86         HPMInterface **hpm = openHPM(rid, 1);
     87         if (!hpm) return 1;
     88         IOReturn r = (*hpm)->iecsCommand(hpm, 0, 0x44525354, 0); // BE numeric 'DRST'
     89         uint8_t data[64] = {0};
     90         uint64_t got = 0;
     91         if (r == kIOReturnSuccess) r = (*hpm)->iecsRead(hpm, 0, 0x09, data, sizeof data, 0, &got);
     92         printf("rid 1 recover DRST: 0x%08x, task result: 0x%02x, bytes: %llu\n", r, data[0], got);
     93         (*hpm)->Release(hpm);
     94         return r == kIOReturnSuccess && got >= 1 && data[0] == 0 ? 0 : 1;
     95     }
     96     if (argc != 4) { fprintf(stderr, "read/cmd require one argument\n"); return 2; }
     97     if (strcmp(argv[2], "cmd") == 0) {
     98         if (rid != 1) { fprintf(stderr, "refusing to send commands to RID 0 (hub + power)\n"); return 2; }
     99         if (strlen(argv[3]) != 4) { fprintf(stderr, "4cc must be 4 characters\n"); return 2; }
    100         // iecsCommand takes a C-style, big-endian numeric 4CC ('DRST' -> 0x44525354).
    101         // AppleHPM byte-swaps it before writing ASCII bytes to CMD1. Passing a little-endian
    102         // number sends a reversed command ("TSRD") and the controller responds "!CMD".
    103         uint32_t cc = (uint32_t)(unsigned char)argv[3][0] << 24 | (uint32_t)(unsigned char)argv[3][1] << 16
    104                     | (uint32_t)(unsigned char)argv[3][2] << 8 | (uint32_t)(unsigned char)argv[3][3];
    105         HPMInterface **hpm = openHPM(rid, 1);
    106         if (!hpm) return 1;
    107         IOReturn r = (*hpm)->iecsCommand(hpm, 0, cc, 0);
    108         printf("rid %d cmd '%s' (0x%08x): 0x%08x\n", rid, argv[3], cc, r);
    109         (*hpm)->Release(hpm);
    110         return r == kIOReturnSuccess ? 0 : 1;
    111     }
    112     if (strcmp(argv[2], "read") != 0) { fprintf(stderr, "unknown op %s\n", argv[2]); return 2; }
    113     uint8_t reg = (uint8_t)strtoul(argv[3], NULL, 0);
    114     HPMInterface **hpm = openHPM(rid, 0);
    115     if (!hpm) return 1;
    116     uint8_t buf[64] = {0};
    117     uint64_t got = 0;
    118     IOReturn r = (*hpm)->iecsRead(hpm, 0, reg, buf, sizeof buf, 0, &got);
    119     if (r != kIOReturnSuccess) { fprintf(stderr, "iecsRead(0x%02x): 0x%08x\n", reg, r); return 1; }
    120     printf("rid %d reg 0x%02x len %llu:", rid, reg, got);
    121     for (uint64_t i = 0; i < got; i++) printf(" %02x", buf[i]);
    122     printf("   \"");
    123     for (uint64_t i = 0; i < got; i++) putchar(buf[i] >= 32 && buf[i] < 127 ? buf[i] : '.');
    124     printf("\"\n");
    125     (*hpm)->Release(hpm);
    126     return 0;
    127 }