// smcstats.c: Print some guessed sensor data.
// For apple m4 and possibly others.
// Does Apple have a utility that will do this?
// Compile: clang -framework IOKit -o smcstats smcstats.c
// Deepseek V4.0 on the assist.
// 20260511 ground/X

#include <IOKit/IOKitLib.h>
#include <stdio.h>
#include <string.h>

typedef struct { char major, minor, build, reserved[1]; UInt16 release; } SMCVersion;
typedef struct { UInt16 version, length; UInt32 cpuPLimit, gpuPLimit, memPLimit; } SMCPLimitData;
typedef struct { UInt32 dataSize, dataType; UInt8 dataAttributes; } SMCKeyInfoData;

typedef struct {
    UInt32            key;
    SMCVersion        vers;
    SMCPLimitData     pLimitData;
    SMCKeyInfoData    keyInfo;
    UInt8             result, status, data8;
    UInt32            data32;
    UInt8             bytes[32];
} SMCParamStruct;

enum { kSMCHandleYPCEvent = 2, kSMCReadKey = 5, kSMCGetKeyInfo = 9 };

static UInt32 s2u(const char *s) {
    UInt32 v = 0;
    for (int i = 0; i < 4; i++) v = (v << 8) | (unsigned char)s[i];
    return v;
}

// Two-step read returning a float – returns 0 on success
static int read_float(io_connect_t conn, const char *key, float *out) {
    SMCParamStruct p = {0};
    size_t z = sizeof(p);

    p.data8 = kSMCGetKeyInfo;
    p.key   = s2u(key);
    if (IOConnectCallStructMethod(conn, kSMCHandleYPCEvent,
                                   &p, sizeof(p), &p, &z) || p.result)
        return -1;
    UInt32 sz = p.keyInfo.dataSize;
    if (sz != 4) return -2;

    SMCKeyInfoData ki = p.keyInfo;

    memset(&p, 0, sizeof(p));
    z = sizeof(p);
    p.data8   = kSMCReadKey;
    p.key     = s2u(key);
    p.keyInfo = ki;
    if (IOConnectCallStructMethod(conn, kSMCHandleYPCEvent,
                                   &p, sizeof(p), &p, &z) || p.result)
        return -3;

    memcpy(out, p.bytes, 4);
    return 0;
}

// Try a list of GPU keys, return the first one that succeeds.
// Writes the actual key name into 'found' (at least 5 bytes).
static int read_gpu_temp(io_connect_t conn, float *value, char *found) {
    const char *candidates[] = {
        "Tg0D", "Tg0C", "Tg0K", "Tg0P", "Tg0H", "Tg0G", "Tg0O",
        NULL
    };
    for (int i = 0; candidates[i]; i++) {
        if (read_float(conn, candidates[i], value) == 0) {
            strcpy(found, candidates[i]);
            return 0;
        }
    }
    return -1;
}

// Mapping of (key, label, unit) – single keys
static const struct {
    const char *key;
    const char *label;
    const char *unit;
    int  integer;   // 1 = print as integer
} sensors[] = {
    // Fan
    { "F0Ac", "Fan speed",             "RPM", 1 },
    { "F0Tg", "Fan target",            "RPM", 1 },
    { "F0Mx", "Fan maximum",           "RPM", 1 },
    // Temperatures
    { "Tp01", "CPU P-core",             "C",   0 },
    { "Tp1S", "CPU P-core max",         "C",   0 },
    { "Te04", "CPU E-core",             "C",   0 },
    { "TCMb", "Memory controller",      "C",   0 },
    { "TVA0", "Ambient / Intake",       "C",   0 },
    { "TPD0", "PMU / Power delivery",   "C",   0 },
    { "TH0a", "Heatsink / Pipe",        "C",   0 },
    { "si00", "NAND flash",             "C",   0 },
    // Power
    { "PD0R", "SoC die power",          "W",   0 },
    { "PHPC", "CPU P-core power",       "W",   0 },
    { "PHPM", "CPU E-core power",       "W",   0 },
    { "PHPS", "GPU power",              "W",   0 },
    { "PPMR", "Memory power",           "W",   0 },
    { "PSTR", "System total power",     "W",   0 },
    // Voltage
    { "Vb0f", "Input voltage (12V)",    "V",   0 },
    { "VP0b", "Core voltage",           "V",   0 },
};

int main(void) {
    io_service_t srv = IOServiceGetMatchingService(kIOMainPortDefault,
                                                   IOServiceMatching("AppleSMC"));
    if (!srv) { fprintf(stderr, "AppleSMC not found\n"); return 1; }
    io_connect_t conn;
    if (IOServiceOpen(srv, mach_task_self(), 0, &conn)) {
        fprintf(stderr, "Cannot open SMC connection\n");
        IOObjectRelease(srv);
        return 1;
    }
    IOObjectRelease(srv);

    int num = sizeof(sensors) / sizeof(sensors[0]);
    for (int i = 0; i < num; i++) {
        float val;
        if (read_float(conn, sensors[i].key, &val) == 0) {
            if (sensors[i].integer || val >= 100.0f)
                printf("%-24s %5.0f %s\n", sensors[i].label, val, sensors[i].unit);
            else
                printf("%-24s %5.1f %s\n", sensors[i].label, val, sensors[i].unit);
        } else {
            printf("%-24s N/A\n", sensors[i].label);
        }
    }

    // GPU (special – try several keys)
    float gpu;
    char key[5];
    if (read_gpu_temp(conn, &gpu, key) == 0) {
        printf("%-24s %5.1f C  (%s)\n", "GPU", gpu, key);
    } else {
        printf("%-24s N/A\n", "GPU");
    }

    IOServiceClose(conn);
    return 0;
}

