#define _GNU_SOURCE #include "commands.h" #include "common.h" #include "ec.h" #include "fan.h" #include "healthy.h" #include "mmio.h" #include "util.h" #include #include #include #include #include #include static void print_model(void) { char vendor[128], product[128]; if (read_line_file("/sys/class/dmi/id/sys_vendor", vendor, sizeof vendor) && read_line_file("/sys/class/dmi/id/product_name", product, sizeof product)) printf("Laptop model: %s %s (DMI)\n", vendor, product); else if (read_line_file("/sys/class/dmi/id/product_name", product, sizeof product)) printf("Laptop model: %s (DMI)\n", product); else printf("Laptop model: unreadable (/sys/class/dmi/id)\n"); } static void print_identity(hy_t *h, mmio_t *m) { uint8_t version; int mj, mn; print_model(); mj = mmio_at(m, ECMJ); mn = mmio_at(m, ECMN); printf("Embedded controller version: %d.%d (0x%08lX = %02X %02X)\n", mj, mn, ECMJ, (unsigned)mj, (unsigned)mn); if (hy_version(h, &version)) printf("Healthy table version: unreadable (0xBB 50)\n"); else printf("Healthy table version: %d (0xBB 50)\n", version); } static void print_project(void) { printf("\n"); printf("%s version: %s\n", APP_NAME, APP_VERSION); printf("Source code: %s\n", APP_URL); printf("Support the project: %s\n", APP_KOFI); printf("License: %s\n", APP_LICENSE); printf("%s\n", APP_COPYRIGHT); } int cmd_version(args_t *a) { ec_t ec; hy_t h; mmio_t mm; if (geteuid() == 0) { ec_open(&ec, a->cmd_port, a->data_port, a->verbose); h.ec = &ec; h.gap = a->gap; mmio_open(&mm, ERM2); print_identity(&h, &mm); mmio_close(&mm); ec_close(&ec); } else { print_model(); printf("Embedded controller version: root required\n"); printf("Healthy table version: root required\n"); } print_project(); return 0; } int cmd_fan_info(args_t *a) { ec_t ec; hy_t h; mmio_t mm; int duty[MAX_FANS], mode[MAX_FANS], rpm[MAX_FANS]; int hi[MAX_FANS], lo[MAX_FANS], ref[MAX_FANS]; int count, idx, agree = 0, checked = 0, spinning = 0, rc = 0; bool uniform = true; ec_open(&ec, a->cmd_port, a->data_port, a->verbose); h.ec = &ec; h.gap = a->gap; mmio_open(&mm, ERM2); count = fan_count_or_default(&h); if (count < 1 || count > MAX_FANS) { printf("Fan count: unreadable (0x30 out of range)\n"); rc = 1; goto out; } printf("Fan count: %d (0x30=%d)\n", count, count); for (idx = 0; idx < count; idx++) { uint8_t d = 0, m = 0, rh = 0, rl = 0; duty[idx] = -1; mode[idx] = -1; rpm[idx] = -1; ref[idx] = -1; hi[idx] = 0; lo[idx] = 0; if (hy_select(&h, idx)) continue; if (!hy_read(&h, REG_PWM_DUTY, &d)) duty[idx] = d; if (!hy_read(&h, REG_TEST_MODE, &m)) mode[idx] = m; if (!hy_read(&h, REG_RPM_HI, &rh) && !hy_read(&h, REG_RPM_LO, &rl)) { hi[idx] = rh; lo[idx] = rl; rpm[idx] = (rh << 8) | rl; } if (idx < 2) ref[idx] = mmio_be16(&mm, TACH_OFFSET + 2 * (unsigned long)idx); if (ref[idx] > 0) spinning++; } for (idx = 1; idx < count; idx++) if (mode[idx] != mode[0]) uniform = false; if (!uniform) printf("Fan control: differs between fans (see below)\n"); else if (mode[0] < 0) printf("Fan control: unreadable (0x31)\n"); else printf("Fan control: %s (0x31=%d)\n", mode_name(mode[0]), mode[0]); for (idx = 0; idx < count; idx++) { unsigned long addr = ERM2 + TACH_OFFSET + 2 * (unsigned long)idx; int expect = ref[idx]; printf("\nfan%d:\n", idx); if (duty[idx] < 0) printf(" PWM duty: unreadable (0x35)\n"); else printf(" PWM duty: %d (%d%%) (0x35=%d)\n", duty[idx], duty_to_percent(duty[idx]), duty[idx]); if (!uniform) printf(" Fan control: %s (0x31=%d)\n", mode_name(mode[idx]), mode[idx]); if (rpm[idx] < 0) printf(" Fan speed reg: unreadable (0x34/0x33)\n"); else printf(" Fan speed reg: %d rpm (0x34/0x33 = %02X %02X)\n", rpm[idx], (unsigned)hi[idx], (unsigned)lo[idx]); if (expect < 0) printf(" Fan speed mmio: no aperture slot\n"); else printf(" Fan speed mmio: %d rpm (0x%08lX)\n", expect, addr); if (rpm[idx] < 0 || expect < 0) { printf(" Verdict: UNKNOWN\n"); continue; } checked++; if (abs(rpm[idx] - expect) <= TACH_TOL) { agree++; printf(" Verdict: MATCH\n"); } else { printf(" Verdict: MISMATCH\n"); } } printf("\n"); if (!spinning) { printf("Result: both tachometers read zero, spin the fans up and retry.\n"); rc = 1; } else if (checked == count && agree == count) { printf("Result: VALIDATED against the MMIO aperture.\n"); printf("If the program works on your device and it is not listed\nin the tested devices section on github, please open an issue.\n"); } else { printf("Result: NOT VALIDATED, do not write anything.\n"); rc = 1; } out: mmio_close(&mm); ec_close(&ec); return rc; } int cmd_temps_info(args_t *a) { mmio_t mm; int cpu, board; (void)a; mmio_open(&mm, ERAM); cpu = mmio_at(&mm, CTMP); board = mmio_at(&mm, CLOT); mmio_close(&mm); printf("CPU: %d C (0x%08lX = %02X, ERAM+0x58 CTMP)\n", cpu, CTMP, (unsigned)cpu); printf("Board: %d C (0x%08lX = %02X, ERAM+0x01 CLOT)\n", board, CLOT, (unsigned)board); printf("Max: %d C (higher of the two)\n", imax(cpu, board)); return 0; } int cmd_fan_speed(args_t *a) { ec_t ec; hy_t h; uint8_t mode; int n, idx; ec_open(&ec, a->cmd_port, a->data_port, a->verbose); h.ec = &ec; h.gap = a->gap; n = fan_count_or_default(&h); if (n < 1 || n > MAX_FANS) n = 2; if (hy_read(&h, REG_TEST_MODE, &mode)) printf("Fan control: unreadable (0x31)\n"); else printf("Fan control: %s (0x31=%d)\n", mode_name(mode), mode); for (idx = 0; idx < n; idx++) { int rpm; if (hy_select(&h, idx) || hy_rpm(&h, &rpm)) printf("fan %d: speed unreadable\n", idx); else printf("fan %d: %d rpm\n", idx, rpm); } ec_close(&ec); return 0; } static int verify(hy_t *h, int duty, const int *pending, int npending, int *failed) { int i, nfailed = 0; for (i = 0; i < npending; i++) { int idx = pending[i], rb, md; bool ok; printf("fan %d:\n", idx); if (readback(h, idx, &rb, &md)) { printf(" Set: FAILED, readback error: %s\n", ec_error); printf(" PWM duty: unreadable (0x35)\n"); ok = false; } else { ok = duty_close(rb, duty); if (ok) printf(" Set: OK\n"); else printf(" Set: FAILED, wanted duty %d\n", duty); printf(" PWM duty: %d (%d%%) (0x35=%d)\n", rb, duty_to_percent(rb), rb); } if (!ok) failed[nfailed++] = idx; } return nfailed; } static int drive(hy_t *h, int duty, const int *targets, int ntargets, args_t *a, int *left) { int pending[MAX_FANS], npending = ntargets; int failed[MAX_FANS], nfailed; int attempt, i; char list[64]; memcpy(pending, targets, sizeof(int) * (size_t)ntargets); for (attempt = 1; attempt <= a->retries; attempt++) { join_ints(list, sizeof list, pending, npending); printf("Attempt %d/%d, fans %s, duty %d.\n", attempt, a->retries, list, duty); for (i = 0; i < npending; i++) if (apply_duty(h, pending[i], duty, a->order)) printf(" fan %d FAIL, write failed: %s.\n", pending[i], ec_error); if (a->no_verify) return 0; nfailed = verify(h, duty, pending, npending, failed); if (!nfailed) return 0; memcpy(pending, failed, sizeof(int) * (size_t)nfailed); npending = nfailed; } memcpy(left, pending, sizeof(int) * (size_t)npending); return npending; } static int run_set(args_t *a, int duty) { ec_t ec; hy_t h; mmio_t mm; int count, targets[MAX_FANS], ntargets, left[MAX_FANS], nleft, fans[2], i; char list[64]; if (duty < 0 || duty > 255) die("Duty must be 0..255, or -1 to hand control back to the EC."); ec_open(&ec, a->cmd_port, a->data_port, a->verbose); h.ec = &ec; h.gap = a->gap; mmio_open(&mm, ERM2); count = fan_count_or_default(&h); if (count < 1 || count > MAX_FANS) { mmio_close(&mm); ec_close(&ec); die("Fan count from 0x30 is implausible, refusing to write."); } mmio_fans(&mm, fans); printf("Fan count: %d, speeds before: %d/%d rpm.\n", count, fans[0], fans[1]); if (a->index < 0) { ntargets = count; for (i = 0; i < count; i++) targets[i] = i; } else { ntargets = 1; targets[0] = a->index; } nleft = drive(&h, duty, targets, ntargets, a, left); mmio_fans(&mm, fans); printf("Fan count: %d, speeds after: %d/%d rpm.\n", count, fans[0], fans[1]); mmio_close(&mm); ec_close(&ec); if (nleft) { join_ints(list, sizeof list, left, nleft); printf("\n"); printf("Fans %s did not take the setting after %d attempts.\n", list, a->retries); return 1; } return 0; } static int run_release(args_t *a) { ec_t ec; hy_t h; int left[MAX_FANS]; int n, nleft; char list[64]; ec_open(&ec, a->cmd_port, a->data_port, a->verbose); h.ec = &ec; h.gap = a->gap; n = fan_count_or_default(&h); if (n < 1 || n > MAX_FANS) n = 2; hand_back(&h, n); nleft = still_manual(&h, n, left); if (nleft) { join_ints(list, sizeof list, left, nleft); printf("Fans %s still in manual, retrying.\n", list); hand_back(&h, n); nleft = still_manual(&h, n, left); } ec_close(&ec); if (nleft) { join_ints(list, sizeof list, left, nleft); printf("Fans %s are still in manual, the EC did not take the release.\n", list); return 1; } printf("Handed fan control back to the EC.\n"); return 0; } int cmd_set(args_t *a) { if (a->duty == RELEASE_ARG) return run_release(a); return run_set(a, a->duty); } int cmd_setp(args_t *a) { int duty; if (a->percent == RELEASE_ARG) return run_release(a); if (a->percent < 0 || a->percent > 100) die("Percent must be 0..100, or -1 to hand control back to the EC."); duty = percent_to_duty(a->percent); printf("%d%% maps to duty %d.\n", a->percent, duty); return run_set(a, duty); }