// smc-rails.c: read SMC power keys (flt, watts) from user space, no sudo. // Build: clang -O2 -Wall -o smc-rails smc-rails.c -framework IOKit -framework CoreFoundation // Run: ./smc-rails N MS KEY... e.g. ./smc-rails 10 1000 PSTR PP0b PP4b PP1b PP7b PR0b PR7b // Tested on macOS 27.0 (Mac17,8, M5 Pro). SMC keys are undocumented and differ per model. // --- SMC helpers: the 80-byte AppleSMC struct, selector 2 --- #include #include #include typedef struct { uint32_t key; uint8_t vers[6]; uint8_t vers_pad[2]; uint8_t p_limit[16]; uint32_t data_size; uint32_t data_type; uint8_t data_attributes; uint8_t info_pad[3]; uint8_t result; uint8_t status; uint8_t data8; uint8_t cmd_pad; uint32_t data32; uint8_t bytes[32]; } SmcKeyData; _Static_assert(sizeof(SmcKeyData) == 80, "SMC struct must be 80 bytes"); static io_connect_t g_conn; static uint32_t fourcc(const char *s) { return ((uint32_t)(uint8_t)s[0] << 24) | ((uint32_t)(uint8_t)s[1] << 16) | ((uint32_t)(uint8_t)s[2] << 8) | (uint8_t)s[3]; } static void unfourcc(uint32_t k, char *o) { o[0] = (char)(k >> 24); o[1] = (char)(k >> 16); o[2] = (char)(k >> 8); o[3] = (char)k; o[4] = 0; } static int call(SmcKeyData *in, SmcKeyData *out) { size_t sz = sizeof *out; memset(out, 0, sizeof *out); kern_return_t kr = IOConnectCallStructMethod(g_conn, 2, in, sizeof *in, out, &sz); return kr == KERN_SUCCESS && out->result == 0; } static int read_key(uint32_t key, uint32_t *type, uint32_t *size, uint8_t *bytes) { SmcKeyData in = {0}, out; in.key = key; in.data8 = 9; // READ_KEYINFO if (!call(&in, &out)) return 0; *type = out.data_type; *size = out.data_size; if (*size == 0 || *size > 32) return 0; in.data8 = 5; // READ_BYTES in.data_size = out.data_size; in.data_type = out.data_type; if (!call(&in, &out)) return 0; memcpy(bytes, out.bytes, 32); return 1; } static int decode(uint32_t type, uint32_t size, const uint8_t *b, double *v) { char t[5]; unfourcc(type, t); if (!strcmp(t, "flt ") && size == 4) { float f; memcpy(&f, b, 4); *v = f; return 1; } if (!strcmp(t, "ui8 ")) { *v = b[0]; return 1; } if (!strcmp(t, "ui16")) { *v = (b[0] << 8) | b[1]; return 1; } if (!strcmp(t, "ui32")) { *v = (double)(((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3]); return 1; } if (!strcmp(t, "si8 ")) { *v = (int8_t)b[0]; return 1; } if (!strcmp(t, "si16")) { *v = (int16_t)((b[0] << 8) | b[1]); return 1; } if (!strcmp(t, "sp78")) { *v = (int16_t)((b[0] << 8) | b[1]) / 256.0; return 1; } if (!strcmp(t, "fpe2")) { *v = ((b[0] << 8) | b[1]) / 4.0; return 1; } if (!strcmp(t, "ioft") && size == 8) { uint64_t u; memcpy(&u, b, 8); *v = u / 65536.0; return 1; } if (!strcmp(t, "flag")) { *v = b[0]; return 1; } return 0; } static int open_smc(void) { io_iterator_t it = 0; if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("AppleSMC"), &it) != KERN_SUCCESS) return 0; io_service_t s; int ok = 0; while ((s = IOIteratorNext(it))) { io_name_t nm; IORegistryEntryGetName(s, nm); io_connect_t c = 0; if (IOServiceOpen(s, mach_task_self(), 0, &c) == KERN_SUCCESS && c) { if (!ok || !strcmp(nm, "AppleSMCKeysEndpoint")) { if (ok) IOServiceClose(g_conn); g_conn = c; ok = 1; } else IOServiceClose(c); } IOObjectRelease(s); } IOObjectRelease(it); return ok; } #include #include int main(int argc, char **argv) { if (argc < 4) { fprintf(stderr, "usage: smc-rails N MS KEY...\n"); return 2; } if (!open_smc()) { fprintf(stderr, "cannot open AppleSMC\n"); return 1; } int n = atoi(argv[1]), ms = atoi(argv[2]); for (int r = 0; r < n; r++) { for (int i = 3; i < argc; i++) { uint32_t type, size; uint8_t b[32]; double v; if (strlen(argv[i]) == 4 && read_key(fourcc(argv[i]), &type, &size, b) && decode(type, size, b, &v)) printf("%s=%.3f ", argv[i], v); else printf("%s=- ", argv[i]); } printf("\n"); fflush(stdout); if (r + 1 < n) usleep((useconds_t)ms * 1000); } IOServiceClose(g_conn); return 0; }