// Small Program to get fan speed on Mac Mini m4.
// Compile with:
// clang -framework IOKit -o getfan getfan.c
// 20260510 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;
}

static int smc_read_float(io_connect_t c, const char *key, float *out) {
    SMCParamStruct p = {0};
    size_t z = sizeof(p);

    // 1. Get key info (size + type)
    p.data8 = kSMCGetKeyInfo;
    p.key   = s2u(key);
    if (IOConnectCallStructMethod(c, kSMCHandleYPCEvent, &p, sizeof(p), &p, &z) || p.result)
        return -1;

    UInt32 size = p.keyInfo.dataSize;
    if (size != 4) return -2;          // expecting flt

    // 2. Read the value, passing size back in
    SMCKeyInfoData ki = p.keyInfo;
    memset(&p, 0, sizeof(p));
    z = sizeof(p);
    p.data8   = kSMCReadKey;
    p.key     = s2u(key);
    p.keyInfo = ki;
    if (IOConnectCallStructMethod(c, kSMCHandleYPCEvent, &p, sizeof(p), &p, &z) || p.result)
        return -3;

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

int main(int argc, char **argv) {
    io_service_t s = IOServiceGetMatchingService(kIOMainPortDefault,
                                                 IOServiceMatching("AppleSMC"));
    if (!s) { fprintf(stderr, "AppleSMC not found\n"); return 1; }
    io_connect_t c;
    if (IOServiceOpen(s, mach_task_self(), 0, &c)) {
        fprintf(stderr, "AppleSMC open failed\n"); return 1;
    }
    IOObjectRelease(s);

    // FNum = number of fans (ui8)
    SMCParamStruct p = {0}; size_t z = sizeof(p);
    p.data8 = kSMCGetKeyInfo; p.key = s2u("FNum");
    IOConnectCallStructMethod(c, kSMCHandleYPCEvent, &p, sizeof(p), &p, &z);
    SMCKeyInfoData ki = p.keyInfo;
    memset(&p, 0, sizeof(p)); z = sizeof(p);
    p.data8 = kSMCReadKey; p.key = s2u("FNum"); p.keyInfo = ki;
    IOConnectCallStructMethod(c, kSMCHandleYPCEvent, &p, sizeof(p), &p, &z);
    int nfans = p.bytes[0];

    for (int i = 0; i < nfans; i++) {
        char k[5];
        snprintf(k, sizeof(k), "F%dAc", i);
        float rpm = 0;
        if (smc_read_float(c, k, &rpm) == 0)
            printf("Fan %d: %.0f RPM\n", i, rpm);
        else
            printf("Fan %d: read failed\n", i);
    }

    IOServiceClose(c);
    return 0;
}

