mirror of
https://github.com/Keyitdev/asus-fan-control-ec.git
synced 2026-09-24 02:32:08 +00:00
First commit: Publish project as open source.
This commit is contained in:
+396
@@ -0,0 +1,396 @@
|
||||
#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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef AFC_COMMANDS_H
|
||||
#define AFC_COMMANDS_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
int cmd_version(args_t *a);
|
||||
int cmd_fan_info(args_t *a);
|
||||
int cmd_fan_speed(args_t *a);
|
||||
int cmd_temps_info(args_t *a);
|
||||
int cmd_set(args_t *a);
|
||||
int cmd_setp(args_t *a);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef AFC_COMMON_H
|
||||
#define AFC_COMMON_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define DATA_PORT 0x25C
|
||||
#define CMD_PORT 0x25D
|
||||
#define PREAMBLE 0xFF
|
||||
#define CMD_VERSION 0xBB
|
||||
#define CMD_TABLE 0xDD
|
||||
#define TBL_READ 0x02
|
||||
#define TBL_WRITE 0x82
|
||||
#define REG_FAN_COUNT 0x30
|
||||
#define REG_TEST_MODE 0x31
|
||||
#define REG_FAN_INDEX 0x32
|
||||
#define REG_RPM_LO 0x33
|
||||
#define REG_RPM_HI 0x34
|
||||
#define REG_PWM_DUTY 0x35
|
||||
#define ERM2 0xFEDD8B00UL
|
||||
#define ERAM 0xFEDD8300UL
|
||||
#define PAGE_MASK (~0xFFFUL)
|
||||
#define CTMP (ERAM + 0x58)
|
||||
#define CLOT (ERAM + 0x01)
|
||||
#define ECMJ (ERAM + 0xE4)
|
||||
#define ECMN (ERAM + 0xE5)
|
||||
#define TACH_OFFSET 0x7C
|
||||
#define APP_NAME "asus-fan-control-ec"
|
||||
#define APP_VERSION "v1.0.0"
|
||||
#define APP_URL "https://github.com/Keyitdev/asus-fan-control-ec"
|
||||
#define APP_KOFI "https://ko-fi.com/keyitdev"
|
||||
#define APP_LICENSE "GPLv3+"
|
||||
#define APP_COPYRIGHT "Copyright (C) 2026 Keyitdev."
|
||||
#define DEFAULT_CONFIG "/etc/asus-fan-curve.conf"
|
||||
#define PANIC_TEMP 96
|
||||
#define HYSTERESIS 3
|
||||
#define INTERVAL 3.0
|
||||
#define OBF 0x01
|
||||
#define IBF 0x02
|
||||
#define POLL_LIMIT 1000
|
||||
#define POLL_DELAY 0.0001
|
||||
#define RETRIES 2
|
||||
#define IO_GAP 0.02
|
||||
#define TOL_PCT 5
|
||||
#define TOL_MIN 3
|
||||
#define READ_RETRIES 3
|
||||
#define TACH_TOL 64
|
||||
#define RELEASE_ARG (-1)
|
||||
#define CURVE_UNSET (-2)
|
||||
#define MAX_FANS 2
|
||||
#define MAX_POINTS 64
|
||||
|
||||
enum { ORDER_MODE_FIRST, ORDER_DUTY_FIRST };
|
||||
enum { SENSOR_CPU, SENSOR_BOARD, SENSOR_MAX };
|
||||
enum { APPLY_FAILED, APPLY_CLEAN, APPLY_RETRIED };
|
||||
|
||||
typedef struct {
|
||||
unsigned cmd_port;
|
||||
unsigned data_port;
|
||||
double gap;
|
||||
bool verbose;
|
||||
int index;
|
||||
int retries;
|
||||
bool no_verify;
|
||||
int order;
|
||||
int duty;
|
||||
int percent;
|
||||
const char *config;
|
||||
double interval;
|
||||
int hysteresis;
|
||||
int panic_temp;
|
||||
int sensor;
|
||||
bool once;
|
||||
bool dry_run;
|
||||
bool silent;
|
||||
} args_t;
|
||||
|
||||
#endif
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "curve.h"
|
||||
|
||||
#include "ec.h"
|
||||
#include "fan.h"
|
||||
#include "healthy.h"
|
||||
#include "mmio.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
static volatile sig_atomic_t stop_flag = 0;
|
||||
|
||||
static int point_cmp(const void *a, const void *b)
|
||||
{
|
||||
const point_t *p = a, *q = b;
|
||||
|
||||
if (p->temp != q->temp)
|
||||
return p->temp < q->temp ? -1 : 1;
|
||||
if (p->percent != q->percent)
|
||||
return p->percent < q->percent ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_curve(const char *path, point_t *points)
|
||||
{
|
||||
FILE *f = fopen(path, "r");
|
||||
char line[256];
|
||||
int n = 0, num = 0, i;
|
||||
|
||||
if (!f)
|
||||
die("Config not found: %s.", path);
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
char *hash, *sep, *end, *rest;
|
||||
long percent, temp;
|
||||
|
||||
num++;
|
||||
hash = strchr(line, '#');
|
||||
if (hash)
|
||||
*hash = '\0';
|
||||
rest = line;
|
||||
while (*rest && isspace((unsigned char)*rest))
|
||||
rest++;
|
||||
end = rest + strlen(rest);
|
||||
while (end > rest && isspace((unsigned char)end[-1]))
|
||||
*--end = '\0';
|
||||
if (!*rest)
|
||||
continue;
|
||||
sep = strpbrk(rest, ",;");
|
||||
if (!sep || strpbrk(sep + 1, ",;")) {
|
||||
fclose(f);
|
||||
die("%s:%d: expected 'percent,temp'.", path, num);
|
||||
}
|
||||
*sep = '\0';
|
||||
errno = 0;
|
||||
percent = strtol(rest, &end, 10);
|
||||
while (end && *end && isspace((unsigned char)*end))
|
||||
end++;
|
||||
if (errno || end == rest || *end) {
|
||||
fclose(f);
|
||||
die("%s:%d: not a pair of integers.", path, num);
|
||||
}
|
||||
rest = sep + 1;
|
||||
while (*rest && isspace((unsigned char)*rest))
|
||||
rest++;
|
||||
errno = 0;
|
||||
temp = strtol(rest, &end, 10);
|
||||
while (end && *end && isspace((unsigned char)*end))
|
||||
end++;
|
||||
if (errno || end == rest || *end) {
|
||||
fclose(f);
|
||||
die("%s:%d not a pair of integers", path, num);
|
||||
}
|
||||
if ((percent < 0 && percent != RELEASE_ARG) || percent > 100) {
|
||||
fclose(f);
|
||||
die("%s:%d: percent must be 0..100, or -1 for EC control.", path, num);
|
||||
}
|
||||
if (temp < 0 || temp > 120) {
|
||||
fclose(f);
|
||||
die("%s:%d: temperature out of range.", path, num);
|
||||
}
|
||||
if (n == MAX_POINTS) {
|
||||
fclose(f);
|
||||
die("%s has too many points.", path);
|
||||
}
|
||||
points[n].temp = (int)temp;
|
||||
points[n].percent = (int)percent;
|
||||
n++;
|
||||
}
|
||||
fclose(f);
|
||||
if (!n)
|
||||
die("%s has no usable lines.", path);
|
||||
qsort(points, (size_t)n, sizeof points[0], point_cmp);
|
||||
for (i = 1; i < n; i++)
|
||||
if (points[i].temp == points[i - 1].temp)
|
||||
die("%s has duplicate temperatures.", path);
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char *percent_text(char *buf, size_t size, int percent)
|
||||
{
|
||||
if (percent == RELEASE_ARG)
|
||||
snprintf(buf, size, "EC (auto)");
|
||||
else
|
||||
snprintf(buf, size, "%d%%", percent);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void describe_curve(const point_t *points, int n)
|
||||
{
|
||||
char text[16];
|
||||
int i;
|
||||
|
||||
if (points[0].temp > 0)
|
||||
printf(" below %d C -> 0%%\n", points[0].temp);
|
||||
for (i = 0; i < n; i++) {
|
||||
percent_text(text, sizeof text, points[i].percent);
|
||||
if (i + 1 < n)
|
||||
printf(" %d-%d C -> %s\n", points[i].temp, points[i + 1].temp, text);
|
||||
else
|
||||
printf(" %d C and above -> %s\n", points[i].temp, text);
|
||||
}
|
||||
}
|
||||
|
||||
static int curve_band(const point_t *points, int n, int temp, int current,
|
||||
int hysteresis)
|
||||
{
|
||||
int band = -1, keep = -1, i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (temp >= points[i].temp)
|
||||
band = i;
|
||||
if (temp + hysteresis >= points[i].temp)
|
||||
keep = i;
|
||||
}
|
||||
if (current == CURVE_UNSET || band >= current)
|
||||
return band;
|
||||
return imax(band, imin(current, keep));
|
||||
}
|
||||
|
||||
static int band_percent(const point_t *points, int band)
|
||||
{
|
||||
return band < 0 ? 0 : points[band].percent;
|
||||
}
|
||||
|
||||
static int apply_percent(hy_t *h, int count, int percent, args_t *a)
|
||||
{
|
||||
int duty = percent_to_duty(percent);
|
||||
int marks[MAX_FANS];
|
||||
int attempt, r, idx;
|
||||
bool retried = false;
|
||||
|
||||
for (attempt = 1; attempt <= a->retries; attempt++) {
|
||||
bool all_seen = false, agreed = true;
|
||||
|
||||
for (idx = 0; idx < count; idx++)
|
||||
apply_duty(h, idx, duty, a->order);
|
||||
for (r = 1; r <= READ_RETRIES; r++) {
|
||||
all_seen = true;
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
int rb, md;
|
||||
marks[idx] = readback(h, idx, &rb, &md) ? -1 : rb;
|
||||
if (marks[idx] < 0)
|
||||
all_seen = false;
|
||||
}
|
||||
if (all_seen)
|
||||
break;
|
||||
retried = true;
|
||||
if (r < READ_RETRIES)
|
||||
printf(" Readback failed, retrying read %d/%d.\n", r + 1,
|
||||
READ_RETRIES);
|
||||
}
|
||||
if (all_seen) {
|
||||
for (idx = 1; idx < count; idx++)
|
||||
if (marks[idx] != marks[0])
|
||||
agreed = false;
|
||||
if (agreed && duty_close(marks[0], duty))
|
||||
return retried ? APPLY_RETRIED : APPLY_CLEAN;
|
||||
}
|
||||
if (attempt < a->retries) {
|
||||
retried = true;
|
||||
printf(" Setting not confirmed, retrying write %d/%d.\n",
|
||||
attempt + 1, a->retries);
|
||||
}
|
||||
}
|
||||
return APPLY_FAILED;
|
||||
}
|
||||
|
||||
static void on_stop(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
stop_flag = 1;
|
||||
}
|
||||
|
||||
int cmd_curve(args_t *a)
|
||||
{
|
||||
point_t points[MAX_POINTS];
|
||||
ec_t ec;
|
||||
hy_t h;
|
||||
mmio_t mm;
|
||||
int n, count, current = CURVE_UNSET, band = CURVE_UNSET;
|
||||
struct sigaction sa;
|
||||
|
||||
n = load_curve(a->config, points);
|
||||
printf("Curve loaded from %s.\n", a->config);
|
||||
describe_curve(points, n);
|
||||
printf("sensor=%s interval=%.1fs hysteresis=%dC panic=%dC\n",
|
||||
sensor_name(a->sensor),
|
||||
a->interval, a->hysteresis, a->panic_temp);
|
||||
if (a->dry_run)
|
||||
return 0;
|
||||
|
||||
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 run.");
|
||||
}
|
||||
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sa_handler = on_stop;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
sigaction(SIGHUP, &sa, NULL);
|
||||
|
||||
for (;;) {
|
||||
int cpu, board, temp, want, next, fans[2], ticks, i;
|
||||
bool panic;
|
||||
const char *status, *mode;
|
||||
char clock[16], target[8];
|
||||
time_t now;
|
||||
|
||||
mmio_temps(&mm, &cpu, &board);
|
||||
temp = sensor_pick(a->sensor, cpu, board);
|
||||
next = curve_band(points, n, temp, band, a->hysteresis);
|
||||
want = band_percent(points, next);
|
||||
panic = temp >= a->panic_temp;
|
||||
if (panic)
|
||||
want = 100;
|
||||
if (want != current) {
|
||||
int applied = want == RELEASE_ARG ? release_to_ec(&h, count)
|
||||
: apply_percent(&h, count, want, a);
|
||||
if (applied != APPLY_FAILED) {
|
||||
current = want;
|
||||
band = next;
|
||||
}
|
||||
status = applied == APPLY_FAILED ? "FAILED"
|
||||
: applied == APPLY_RETRIED ? "retried" : "applied";
|
||||
} else {
|
||||
band = next;
|
||||
status = "hold";
|
||||
}
|
||||
mode = current == RELEASE_ARG ? "EC (auto)"
|
||||
: current == CURVE_UNSET ? "unknown" : "manual";
|
||||
if (want == RELEASE_ARG)
|
||||
snprintf(target, sizeof target, "%4s", "--");
|
||||
else
|
||||
snprintf(target, sizeof target, "%3d%%", want);
|
||||
mmio_fans(&mm, fans);
|
||||
now = time(NULL);
|
||||
strftime(clock, sizeof clock, "%H:%M:%S", localtime(&now));
|
||||
if (!a->silent) {
|
||||
printf("%s temp=%dC target=%s %-9s %-7s fan0=%5d fan1=%5d%s\n", clock,
|
||||
temp, target, mode, status, fans[0], fans[1],
|
||||
panic ? " PANIC" : "");
|
||||
fflush(stdout);
|
||||
}
|
||||
if (a->once || stop_flag)
|
||||
break;
|
||||
ticks = (int)(a->interval * 10);
|
||||
for (i = 0; i < ticks && !stop_flag; i++)
|
||||
nsleep(0.1);
|
||||
if (stop_flag)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!a->once) {
|
||||
hand_back(&h, count);
|
||||
printf("Handed fan control back to the EC.\n");
|
||||
}
|
||||
mmio_close(&mm);
|
||||
ec_close(&ec);
|
||||
return 0;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#ifndef AFC_CURVE_H
|
||||
#define AFC_CURVE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
typedef struct {
|
||||
int temp;
|
||||
int percent;
|
||||
} point_t;
|
||||
|
||||
int cmd_curve(args_t *a);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,163 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "ec.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
#include <sys/io.h>
|
||||
#define HAVE_PORT_IO 1
|
||||
#else
|
||||
#define HAVE_PORT_IO 0
|
||||
#endif
|
||||
|
||||
char ec_error[64] = "";
|
||||
|
||||
int ec_open(ec_t *e, unsigned cmd_port, unsigned data_port, bool verbose)
|
||||
{
|
||||
e->cmd_port = cmd_port;
|
||||
e->data_port = data_port;
|
||||
e->verbose = verbose;
|
||||
e->fd = -1;
|
||||
e->direct = false;
|
||||
#if HAVE_PORT_IO
|
||||
if (cmd_port < 0x400 && data_port < 0x400 &&
|
||||
ioperm(cmd_port, 1, 1) == 0 && ioperm(data_port, 1, 1) == 0) {
|
||||
e->direct = true;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
e->fd = open("/dev/port", O_RDWR);
|
||||
if (e->fd < 0)
|
||||
die("Cannot open /dev/port: %s.", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ec_close(ec_t *e)
|
||||
{
|
||||
if (e->fd >= 0)
|
||||
close(e->fd);
|
||||
e->fd = -1;
|
||||
}
|
||||
|
||||
static uint8_t ec_in8(ec_t *e, unsigned port)
|
||||
{
|
||||
uint8_t v;
|
||||
|
||||
#if HAVE_PORT_IO
|
||||
if (e->direct)
|
||||
return inb((unsigned short)port);
|
||||
#endif
|
||||
if (pread(e->fd, &v, 1, (off_t)port) != 1)
|
||||
die("Cannot read port 0x%03X: %s.", port, strerror(errno));
|
||||
return v;
|
||||
}
|
||||
|
||||
static void ec_out8(ec_t *e, unsigned port, uint8_t val)
|
||||
{
|
||||
if (e->verbose)
|
||||
printf(" out 0x%03X <- %02X\n", port, (unsigned)val);
|
||||
#if HAVE_PORT_IO
|
||||
if (e->direct) {
|
||||
outb(val, (unsigned short)port);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (pwrite(e->fd, &val, 1, (off_t)port) != 1)
|
||||
die("Cannot write port 0x%03X: %s.", port, strerror(errno));
|
||||
}
|
||||
|
||||
static uint8_t ec_status(ec_t *e)
|
||||
{
|
||||
return ec_in8(e, e->cmd_port);
|
||||
}
|
||||
|
||||
static int ec_timeout(const char *what)
|
||||
{
|
||||
snprintf(ec_error, sizeof ec_error, "%s", what);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ec_drain(ec_t *e)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < POLL_LIMIT; i++) {
|
||||
if (!(ec_status(e) & OBF))
|
||||
return 0;
|
||||
ec_in8(e, e->data_port);
|
||||
nsleep(POLL_DELAY);
|
||||
}
|
||||
return ec_timeout("obf never cleared");
|
||||
}
|
||||
|
||||
static int ec_wait_ibf(ec_t *e)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < POLL_LIMIT; i++) {
|
||||
if (!(ec_status(e) & IBF))
|
||||
return 0;
|
||||
nsleep(POLL_DELAY);
|
||||
}
|
||||
return ec_timeout("ibf never cleared");
|
||||
}
|
||||
|
||||
static int ec_wait_obf(ec_t *e)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < POLL_LIMIT; i++) {
|
||||
if (ec_status(e) & OBF)
|
||||
return 0;
|
||||
nsleep(POLL_DELAY);
|
||||
}
|
||||
return ec_timeout("obf never set");
|
||||
}
|
||||
|
||||
static int ec_once(ec_t *e, uint8_t cmd, const uint8_t *payload, size_t n,
|
||||
bool want_result, uint8_t *out)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (ec_drain(e) || ec_wait_ibf(e))
|
||||
return -1;
|
||||
ec_out8(e, e->cmd_port, PREAMBLE);
|
||||
if (ec_wait_ibf(e))
|
||||
return -1;
|
||||
ec_out8(e, e->cmd_port, cmd);
|
||||
for (i = 0; i < n; i++) {
|
||||
if (ec_wait_ibf(e))
|
||||
return -1;
|
||||
ec_out8(e, e->data_port, payload[i]);
|
||||
}
|
||||
if (ec_wait_ibf(e))
|
||||
return -1;
|
||||
if (!want_result)
|
||||
return 0;
|
||||
if (ec_wait_obf(e))
|
||||
return -1;
|
||||
if (out)
|
||||
*out = ec_in8(e, e->data_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ec_xact(ec_t *e, uint8_t cmd, const uint8_t *payload, size_t n,
|
||||
bool want_result, uint8_t *out)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (n > 8)
|
||||
die("Payload limit is 8 bytes.");
|
||||
for (i = 0; i < RETRIES; i++)
|
||||
if (ec_once(e, cmd, payload, n, want_result, out) == 0)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef AFC_EC_H
|
||||
#define AFC_EC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
bool direct;
|
||||
unsigned cmd_port;
|
||||
unsigned data_port;
|
||||
bool verbose;
|
||||
} ec_t;
|
||||
|
||||
extern char ec_error[64];
|
||||
|
||||
int ec_open(ec_t *e, unsigned cmd_port, unsigned data_port, bool verbose);
|
||||
void ec_close(ec_t *e);
|
||||
int ec_xact(ec_t *e, uint8_t cmd, const uint8_t *payload, size_t n,
|
||||
bool want_result, uint8_t *out);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,109 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "fan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
int percent_to_duty(int percent)
|
||||
{
|
||||
return (int)(percent * 255 / 100.0 + 0.5);
|
||||
}
|
||||
|
||||
int duty_to_percent(int duty)
|
||||
{
|
||||
return (int)(duty * 100 / 255.0 + 0.5);
|
||||
}
|
||||
|
||||
bool duty_close(int readback_value, int want)
|
||||
{
|
||||
int slack = want * TOL_PCT / 100;
|
||||
|
||||
if (slack < TOL_MIN)
|
||||
slack = TOL_MIN;
|
||||
return abs(readback_value - want) <= slack;
|
||||
}
|
||||
|
||||
const char *mode_name(int mode)
|
||||
{
|
||||
if (mode < 0)
|
||||
return "unreadable";
|
||||
return mode ? "Manual" : "EC automatic";
|
||||
}
|
||||
|
||||
int fan_count_or_default(hy_t *h)
|
||||
{
|
||||
uint8_t n;
|
||||
|
||||
if (hy_read(h, REG_FAN_COUNT, &n))
|
||||
return 0;
|
||||
return (n >= 1 && n <= MAX_FANS) ? n : 0;
|
||||
}
|
||||
|
||||
int apply_duty(hy_t *h, int index, int duty, int order)
|
||||
{
|
||||
if (hy_select(h, index))
|
||||
return -1;
|
||||
if (order == ORDER_MODE_FIRST) {
|
||||
if (hy_write(h, REG_TEST_MODE, 1))
|
||||
return -1;
|
||||
if (hy_select(h, index))
|
||||
return -1;
|
||||
if (hy_write(h, REG_PWM_DUTY, (uint8_t)duty))
|
||||
return -1;
|
||||
} else {
|
||||
if (hy_write(h, REG_PWM_DUTY, (uint8_t)duty))
|
||||
return -1;
|
||||
if (hy_write(h, REG_TEST_MODE, 1))
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readback(hy_t *h, int index, int *duty, int *mode)
|
||||
{
|
||||
uint8_t d, m;
|
||||
|
||||
if (hy_select(h, index) || hy_read(h, REG_PWM_DUTY, &d) ||
|
||||
hy_read(h, REG_TEST_MODE, &m))
|
||||
return -1;
|
||||
*duty = d;
|
||||
*mode = m;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hand_back(hy_t *h, int count)
|
||||
{
|
||||
int idx;
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
if (hy_select(h, idx))
|
||||
continue;
|
||||
hy_write(h, REG_TEST_MODE, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int still_manual(hy_t *h, int count, int *left)
|
||||
{
|
||||
int idx, nleft = 0;
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
int rb, md;
|
||||
if (readback(h, idx, &rb, &md) == 0 && md)
|
||||
left[nleft++] = idx;
|
||||
}
|
||||
return nleft;
|
||||
}
|
||||
|
||||
int release_to_ec(hy_t *h, int count)
|
||||
{
|
||||
int left[MAX_FANS];
|
||||
|
||||
hand_back(h, count);
|
||||
if (!still_manual(h, count, left))
|
||||
return APPLY_CLEAN;
|
||||
hand_back(h, count);
|
||||
return still_manual(h, count, left) ? APPLY_FAILED : APPLY_RETRIED;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef AFC_FAN_H
|
||||
#define AFC_FAN_H
|
||||
|
||||
#include "healthy.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
int percent_to_duty(int percent);
|
||||
int duty_to_percent(int duty);
|
||||
bool duty_close(int readback_value, int want);
|
||||
const char *mode_name(int mode);
|
||||
int fan_count_or_default(hy_t *h);
|
||||
int apply_duty(hy_t *h, int index, int duty, int order);
|
||||
int readback(hy_t *h, int index, int *duty, int *mode);
|
||||
void hand_back(hy_t *h, int count);
|
||||
int still_manual(hy_t *h, int count, int *left);
|
||||
int release_to_ec(hy_t *h, int count);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "healthy.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "util.h"
|
||||
|
||||
int hy_version(hy_t *h, uint8_t *out)
|
||||
{
|
||||
uint8_t payload[1] = { 0x50 };
|
||||
|
||||
return ec_xact(h->ec, CMD_VERSION, payload, 1, true, out);
|
||||
}
|
||||
|
||||
int hy_read(hy_t *h, uint8_t reg, uint8_t *out)
|
||||
{
|
||||
uint8_t payload[3] = { TBL_READ, reg, 0x00 };
|
||||
|
||||
return ec_xact(h->ec, CMD_TABLE, payload, 3, true, out);
|
||||
}
|
||||
|
||||
int hy_write(hy_t *h, uint8_t reg, uint8_t value)
|
||||
{
|
||||
uint8_t payload[3] = { TBL_WRITE, reg, value };
|
||||
|
||||
if (ec_xact(h->ec, CMD_TABLE, payload, 3, false, NULL))
|
||||
return -1;
|
||||
nsleep(h->gap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hy_select(hy_t *h, int index)
|
||||
{
|
||||
return hy_write(h, REG_FAN_INDEX, (uint8_t)index);
|
||||
}
|
||||
|
||||
int hy_rpm(hy_t *h, int *out)
|
||||
{
|
||||
uint8_t hi, lo;
|
||||
|
||||
if (hy_read(h, REG_RPM_HI, &hi) || hy_read(h, REG_RPM_LO, &lo))
|
||||
return -1;
|
||||
*out = (hi << 8) | lo;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef AFC_HEALTHY_H
|
||||
#define AFC_HEALTHY_H
|
||||
|
||||
#include "ec.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
ec_t *ec;
|
||||
double gap;
|
||||
} hy_t;
|
||||
|
||||
int hy_version(hy_t *h, uint8_t *out);
|
||||
int hy_read(hy_t *h, uint8_t reg, uint8_t *out);
|
||||
int hy_write(hy_t *h, uint8_t reg, uint8_t value);
|
||||
int hy_select(hy_t *h, int index);
|
||||
int hy_rpm(hy_t *h, int *out);
|
||||
|
||||
#endif
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "commands.h"
|
||||
#include "common.h"
|
||||
#include "curve.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void need_root(void)
|
||||
{
|
||||
if (geteuid() != 0)
|
||||
die("Root privileges required.");
|
||||
}
|
||||
|
||||
static const char INT_MSG[] =
|
||||
"\nInterrupted. Fans keep the last setting, use 'set -1' to hand control "
|
||||
"back to the EC.\n";
|
||||
|
||||
static void on_interrupt(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
if (write(STDOUT_FILENO, INT_MSG, sizeof INT_MSG - 1) < 0) {
|
||||
}
|
||||
_exit(130);
|
||||
}
|
||||
|
||||
static void usage_text(FILE *out)
|
||||
{
|
||||
fprintf(out,
|
||||
"usage: " APP_NAME " [global options] <command> [options]\n"
|
||||
"\n"
|
||||
"global options:\n"
|
||||
" --cmd-port N --data-port N --gap SECONDS --verbose\n"
|
||||
"\n"
|
||||
"commands:\n"
|
||||
" set DUTY [--fan N] [--retries N] [--no-verify]\n"
|
||||
" [--order mode-first|duty-first]\n"
|
||||
" duty 0-255, or -1 to hand control back to the EC\n"
|
||||
" -1 always covers every fan, 0x31 is global\n"
|
||||
" setp PERCENT [same options as set], or -1 for the same release\n"
|
||||
" curve [--config PATH] [--interval S] [--hysteresis C]\n"
|
||||
" [--panic-temp C] [--sensor cpu|board|max] [--retries N]\n"
|
||||
" [--order ORDER] [--once] [--dry-run] [--silent]\n"
|
||||
" config lines are 'percent,temp'; percent -1 hands that\n"
|
||||
" band back to the EC\n"
|
||||
" fan-speed\n"
|
||||
" fan-info\n"
|
||||
" temps-info\n"
|
||||
" help\n"
|
||||
" version\n");
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
usage_text(stderr);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
static int cmd_help(void)
|
||||
{
|
||||
usage_text(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *need_value(int argc, char **argv, int *i)
|
||||
{
|
||||
if (*i + 1 >= argc)
|
||||
die("%s requires a value.", argv[*i]);
|
||||
return argv[++(*i)];
|
||||
}
|
||||
|
||||
static long int_auto(const char *s, const char *what)
|
||||
{
|
||||
char *end;
|
||||
long v;
|
||||
|
||||
errno = 0;
|
||||
v = strtol(s, &end, 0);
|
||||
if (errno || end == s || *end)
|
||||
die("Invalid %s: %s.", what, s);
|
||||
return v;
|
||||
}
|
||||
|
||||
static double dbl_arg(const char *s, const char *what)
|
||||
{
|
||||
char *end;
|
||||
double v;
|
||||
|
||||
errno = 0;
|
||||
v = strtod(s, &end);
|
||||
if (errno || end == s || *end)
|
||||
die("invalid %s: %s", what, s);
|
||||
return v;
|
||||
}
|
||||
|
||||
static int pick(const char *value, const char *what, const char *const *names,
|
||||
int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
if (strcmp(value, names[i]) == 0)
|
||||
return i;
|
||||
die("Invalid %s: %s.", what, value);
|
||||
}
|
||||
|
||||
static bool global_option(args_t *a, int argc, char **argv, int *i)
|
||||
{
|
||||
const char *arg = argv[*i];
|
||||
|
||||
if (strcmp(arg, "--cmd-port") == 0)
|
||||
a->cmd_port = (unsigned)int_auto(need_value(argc, argv, i), "--cmd-port");
|
||||
else if (strcmp(arg, "--data-port") == 0)
|
||||
a->data_port = (unsigned)int_auto(need_value(argc, argv, i), "--data-port");
|
||||
else if (strcmp(arg, "--gap") == 0)
|
||||
a->gap = dbl_arg(need_value(argc, argv, i), "--gap");
|
||||
else if (strcmp(arg, "--verbose") == 0)
|
||||
a->verbose = true;
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
static const char *const orders[] = { "mode-first", "duty-first" };
|
||||
static const char *const sensors[] = { "cpu", "board", "max" };
|
||||
args_t a;
|
||||
struct sigaction sa;
|
||||
const char *cmd = NULL;
|
||||
bool have_duty = false;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < argc; i++)
|
||||
if (strcmp(argv[i], "help") == 0 || strcmp(argv[i], "--help") == 0 ||
|
||||
strcmp(argv[i], "-h") == 0)
|
||||
return cmd_help();
|
||||
|
||||
memset(&a, 0, sizeof a);
|
||||
a.cmd_port = CMD_PORT;
|
||||
a.data_port = DATA_PORT;
|
||||
a.gap = IO_GAP;
|
||||
a.index = -1;
|
||||
a.retries = 3;
|
||||
a.order = ORDER_MODE_FIRST;
|
||||
a.config = DEFAULT_CONFIG;
|
||||
a.interval = INTERVAL;
|
||||
a.hysteresis = HYSTERESIS;
|
||||
a.panic_temp = PANIC_TEMP;
|
||||
a.sensor = SENSOR_MAX;
|
||||
|
||||
i = 1;
|
||||
while (i < argc && argv[i][0] == '-' && argv[i][1]) {
|
||||
if (!global_option(&a, argc, argv, &i))
|
||||
usage();
|
||||
i++;
|
||||
}
|
||||
if (i >= argc)
|
||||
usage();
|
||||
cmd = argv[i++];
|
||||
if (strcmp(cmd, "version") != 0)
|
||||
need_root();
|
||||
|
||||
for (; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
||||
if (global_option(&a, argc, argv, &i))
|
||||
continue;
|
||||
if (arg[0] != '-' || isdigit((unsigned char)arg[1])) {
|
||||
if (strcmp(cmd, "set") == 0 && !have_duty) {
|
||||
a.duty = (int)int_auto(arg, "duty");
|
||||
have_duty = true;
|
||||
} else if (strcmp(cmd, "setp") == 0 && !have_duty) {
|
||||
a.percent = (int)int_auto(arg, "percent");
|
||||
have_duty = true;
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strcmp(arg, "--fan") == 0)
|
||||
a.index = (int)int_auto(need_value(argc, argv, &i), "--fan");
|
||||
else if (strcmp(arg, "--retries") == 0)
|
||||
a.retries = (int)int_auto(need_value(argc, argv, &i), "--retries");
|
||||
else if (strcmp(arg, "--no-verify") == 0)
|
||||
a.no_verify = true;
|
||||
else if (strcmp(arg, "--order") == 0)
|
||||
a.order = pick(need_value(argc, argv, &i), "--order", orders, 2);
|
||||
else if (strcmp(arg, "--sensor") == 0)
|
||||
a.sensor = pick(need_value(argc, argv, &i), "--sensor", sensors, 3);
|
||||
else if (strcmp(arg, "--config") == 0)
|
||||
a.config = need_value(argc, argv, &i);
|
||||
else if (strcmp(arg, "--interval") == 0)
|
||||
a.interval = dbl_arg(need_value(argc, argv, &i), "--interval");
|
||||
else if (strcmp(arg, "--hysteresis") == 0)
|
||||
a.hysteresis = (int)int_auto(need_value(argc, argv, &i), "--hysteresis");
|
||||
else if (strcmp(arg, "--panic-temp") == 0)
|
||||
a.panic_temp = (int)int_auto(need_value(argc, argv, &i), "--panic-temp");
|
||||
else if (strcmp(arg, "--once") == 0)
|
||||
a.once = true;
|
||||
else if (strcmp(arg, "--dry-run") == 0)
|
||||
a.dry_run = true;
|
||||
else if (strcmp(arg, "--silent") == 0)
|
||||
a.silent = true;
|
||||
else {
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "set") == 0 && !have_duty)
|
||||
die("set requires a duty value.");
|
||||
if (strcmp(cmd, "setp") == 0 && !have_duty)
|
||||
die("setp requires a percent value.");
|
||||
if (a.index >= MAX_FANS)
|
||||
die("--fan must be 0..%d.", MAX_FANS - 1);
|
||||
if (a.retries < 1)
|
||||
die("--retries must be at least 1.");
|
||||
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sa_handler = on_interrupt;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
|
||||
if (strcmp(cmd, "version") == 0)
|
||||
return cmd_version(&a);
|
||||
if (strcmp(cmd, "fan-info") == 0)
|
||||
return cmd_fan_info(&a);
|
||||
if (strcmp(cmd, "temps-info") == 0)
|
||||
return cmd_temps_info(&a);
|
||||
if (strcmp(cmd, "fan-speed") == 0)
|
||||
return cmd_fan_speed(&a);
|
||||
if (strcmp(cmd, "set") == 0)
|
||||
return cmd_set(&a);
|
||||
if (strcmp(cmd, "setp") == 0)
|
||||
return cmd_setp(&a);
|
||||
if (strcmp(cmd, "curve") == 0)
|
||||
return cmd_curve(&a);
|
||||
usage();
|
||||
return 2;
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
#define _GNU_SOURCE
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#include "mmio.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void mmio_open(mmio_t *m, unsigned long base)
|
||||
{
|
||||
void *p;
|
||||
|
||||
m->fd = open("/dev/mem", O_RDONLY | O_SYNC);
|
||||
if (m->fd < 0)
|
||||
die("Cannot open /dev/mem: %s.", strerror(errno));
|
||||
m->page = base & PAGE_MASK;
|
||||
m->off = base - m->page;
|
||||
p = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, m->fd, (off_t)m->page);
|
||||
if (p == MAP_FAILED)
|
||||
die("Cannot map 0x%lX: %s.", m->page, strerror(errno));
|
||||
m->map = p;
|
||||
}
|
||||
|
||||
void mmio_close(mmio_t *m)
|
||||
{
|
||||
if (m->map)
|
||||
munmap((void *)m->map, 0x1000);
|
||||
if (m->fd >= 0)
|
||||
close(m->fd);
|
||||
m->map = NULL;
|
||||
m->fd = -1;
|
||||
}
|
||||
|
||||
int mmio_be16(mmio_t *m, unsigned long offset)
|
||||
{
|
||||
unsigned long i = m->off + offset;
|
||||
|
||||
return (m->map[i] << 8) | m->map[i + 1];
|
||||
}
|
||||
|
||||
void mmio_fans(mmio_t *m, int fans[2])
|
||||
{
|
||||
fans[0] = mmio_be16(m, TACH_OFFSET);
|
||||
fans[1] = mmio_be16(m, TACH_OFFSET + 2);
|
||||
}
|
||||
|
||||
int mmio_at(mmio_t *m, unsigned long address)
|
||||
{
|
||||
return m->map[address - m->page];
|
||||
}
|
||||
|
||||
void mmio_temps(mmio_t *m, int *cpu, int *board)
|
||||
{
|
||||
*cpu = mmio_at(m, CTMP);
|
||||
*board = mmio_at(m, CLOT);
|
||||
}
|
||||
|
||||
const char *sensor_name(int sensor)
|
||||
{
|
||||
switch (sensor) {
|
||||
case SENSOR_CPU:
|
||||
return "cpu";
|
||||
case SENSOR_BOARD:
|
||||
return "board";
|
||||
default:
|
||||
return "max";
|
||||
}
|
||||
}
|
||||
|
||||
int sensor_pick(int sensor, int cpu, int board)
|
||||
{
|
||||
switch (sensor) {
|
||||
case SENSOR_CPU:
|
||||
return cpu;
|
||||
case SENSOR_BOARD:
|
||||
return board;
|
||||
default:
|
||||
return imax(cpu, board);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#ifndef AFC_MMIO_H
|
||||
#define AFC_MMIO_H
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
volatile unsigned char *map;
|
||||
unsigned long page;
|
||||
unsigned long off;
|
||||
} mmio_t;
|
||||
|
||||
void mmio_open(mmio_t *m, unsigned long base);
|
||||
void mmio_close(mmio_t *m);
|
||||
int mmio_be16(mmio_t *m, unsigned long offset);
|
||||
int mmio_at(mmio_t *m, unsigned long address);
|
||||
void mmio_fans(mmio_t *m, int fans[2]);
|
||||
void mmio_temps(mmio_t *m, int *cpu, int *board);
|
||||
const char *sensor_name(int sensor);
|
||||
int sensor_pick(int sensor, int cpu, int board);
|
||||
|
||||
#endif
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
void die(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void nsleep(double seconds)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if (seconds <= 0.0)
|
||||
return;
|
||||
ts.tv_sec = (time_t)seconds;
|
||||
ts.tv_nsec = (long)((seconds - (double)ts.tv_sec) * 1e9);
|
||||
if (ts.tv_nsec < 0)
|
||||
ts.tv_nsec = 0;
|
||||
if (ts.tv_nsec > 999999999L)
|
||||
ts.tv_nsec = 999999999L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
bool read_line_file(const char *path, char *buf, size_t size)
|
||||
{
|
||||
FILE *f = fopen(path, "r");
|
||||
size_t len;
|
||||
|
||||
if (!f)
|
||||
return false;
|
||||
if (!fgets(buf, (int)size, f)) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
fclose(f);
|
||||
len = strlen(buf);
|
||||
while (len && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
|
||||
buf[--len] = '\0';
|
||||
return len > 0;
|
||||
}
|
||||
|
||||
void join_ints(char *buf, size_t size, const int *v, int n)
|
||||
{
|
||||
int i;
|
||||
size_t used = 0;
|
||||
|
||||
buf[0] = '\0';
|
||||
for (i = 0; i < n && used + 8 < size; i++)
|
||||
used += (size_t)snprintf(buf + used, size - used, "%s%d", i ? " " : "", v[i]);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#ifndef AFC_UTIL_H
|
||||
#define AFC_UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void die(const char *fmt, ...) __attribute__((noreturn, format(printf, 1, 2)));
|
||||
void nsleep(double seconds);
|
||||
bool read_line_file(const char *path, char *buf, size_t size);
|
||||
void join_ints(char *buf, size_t size, const int *v, int n);
|
||||
|
||||
static inline int imin(int a, int b) { return a < b ? a : b; }
|
||||
static inline int imax(int a, int b) { return a > b ? a : b; }
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user