diff --git a/main.c b/main.c index 30a8946..8eb557c 100644 --- a/main.c +++ b/main.c @@ -11,6 +11,27 @@ #include #include #include +#include +#include +#include + +/* I have to redefined this because obsdfreqd is compiled with + -Wall and I don't know of a compiler agnostic way to disable + warnings, and I don't want to assume `cc' is clang forever */ +struct ctlnameconst { + const char* ctl_name; /* struct ctlname has this nonconst */ + int ctl_type; +}; +#if __STDC_VERSION__ >= 201112L +/* be polite to maintainers and give them a better compile error */ +# include /* offsetof */ +static_assert(sizeof(struct ctlnameconst) == sizeof(struct ctlname), + "struct ctlname changed, update ctlnameconst"); +static_assert(offsetof(struct ctlnameconst, ctl_name) == offsetof(struct ctlname, ctl_name), + "struct ctlname changed, update ctlnameconst"); +static_assert(offsetof(struct ctlnameconst, ctl_type) == offsetof(struct ctlname, ctl_type), + "struct ctlname changed, update ctlnameconst"); +#endif int hard_min_freq, batt_min, wall_min; int hard_max_freq, batt_max, wall_max; @@ -23,6 +44,12 @@ int temp_max, batt_tmax, wall_tmax; int verbose = 0; int max; +/* sysctl path to the temperature sensor we're tracking */ +int temperature_mib[5]; +/* if false(0), get_temp() will error out */ +int temperature_sensor_found; + +void try_to_find_default_temperature_sensor(void); int get_temp(void); void set_policy(const char*); void quit_gracefully(int signum); @@ -30,21 +57,95 @@ void usage(void); void switch_wall(void); void switch_batt(void); void assign_values_from_param(char*, int*, int*); +void parse_sensor_path(char*); + +/* heuristics to find a good temperature sensor */ +void try_to_find_default_temperature_sensor(void) { + /* pick the first temperature sensor, but... + bonify devices named `cpuN' + TODO and we should probably blacklist devices like: + - GPU temperature sensors + - battery temperature sensors + - UPS temperature sensors + ...just in case the CPU thermometer is not cpu0, or, + for some bizzare reason, there is no CPU thermometer. + + There may be other strange cases, like OpenBSD running + on a Raspberry PI where I can connect an external + temperature sensor and sensor_install that; but I have + a feeling external thermometers would be at the very end */ + int dev = 0; + + temperature_mib[0] = CTL_HW; + temperature_mib[1] = HW_SENSORS; + for(dev = 0; ; ++dev) { + struct sensordev sensordev; + size_t sensordevlen = sizeof(struct sensordev); + memset(&sensordev, 0, sizeof(struct sensordev)); + temperature_mib[2] = dev; + if(-1 == sysctl( + temperature_mib, 3, + &sensordev, &sensordevlen, + NULL, 0) + ) { + /* end of array */ + break; + } + + /* does it have temperature sensors? */ + if(sensordev.maxnumt[SENSOR_TEMP] <= 0) { + /* no */ + continue; + } + + /* potentially use this */ + if(!temperature_sensor_found) { + temperature_sensor_found = 1; + temperature_mib[2] = sensordev.num; + temperature_mib[3] = SENSOR_TEMP; + temperature_mib[4] = 0; + } + + /* is it called cpuSOMETHING? */ + if(strncmp(sensordev.xname, "cpu", 3) == 0) { + /* definitely use this! */ + temperature_sensor_found = 1; + temperature_mib[2] = sensordev.num; + temperature_mib[3] = SENSOR_TEMP; + temperature_mib[4] = 0; + /* stop here, this is our best candidate */ + break; + } + } + + if(verbose) { + if(!temperature_sensor_found) { + fprintf(stderr, "Could not find any temperature sensors.\n"); + } else { + /* we need to get the device name again in order to print it */ + struct sensordev sensordev; + size_t sensordevlen = sizeof(struct sensordev); + memset(&sensordev, 0, sizeof(struct sensordev)); + if(0 == sysctl(temperature_mib, 3, &sensordev, &sensordevlen, NULL, 0)) { + fprintf(stderr, + "Using hw.sensors.%s.temp%d as the temperature sensor\n", + sensordev.xname, + temperature_mib[4]); + } else { + /* panic? */ + err(1, "Failed to read sensordev a second time?"); + } + } + } +} int get_temp() { struct sensor sensor; - int mib[5]; int value = 0; size_t len = sizeof(sensor); - mib[0] = CTL_HW; - mib[1] = HW_SENSORS; - mib[2] = 0; - mib[3] = SENSOR_TEMP; - mib[4] = 0; - - if (sysctl(mib, 5, &sensor, &len, NULL, 0) == -1) - err(1, "sysctl to get temperature"); + if (sysctl(temperature_mib, 5, &sensor, &len, NULL, 0) == -1) + err(1, "failed to read temperature"); // convert from uK to C value = (sensor.value - 273150000) / 1000 / 1000; @@ -62,7 +163,7 @@ void set_policy(const char* policy) { /* restore policy auto upon exit */ void quit_gracefully(int signum) { - printf("Caught signal %d, set auto policy\n", signum); + fprintf(stderr, "Caught signal %d, set auto policy\n", signum); set_policy("auto"); exit(0); } @@ -71,7 +172,7 @@ void usage(void) { fprintf(stderr, "obsdfreqd [-hv] [-d downstepfrequency] [-i inertia] [-m maxfrequency]\n" " [-l minfrequency] [-r threshold] [-s stepfrequency]\n" - " [-t timefreq] [-T maxtemperature]\n"); + " [-t timefreq] [-T maxtemperature [-S sensorpath]]\n"); } /* switch to wall profile */ @@ -125,6 +226,147 @@ void assign_values_from_param(char* parameter, int* charging, int* battery) { *battery = *charging; } +/* parse optarg for a sensor path, sysctl style; + * expecting hw.sensors.*.temp*; + * sets temperature_mid and temperature_sensor_found + * IFF it's a valid path, else sets errno to something + * relevant. + */ +void parse_sensor_path(char* path) { + const struct ctlnameconst ctlnames[] = CTL_NAMES; + const struct ctlnameconst ctlhwnames[] = CTL_HW_NAMES; + const size_t prefix_len = + /*hw*/strlen(ctlnames[CTL_HW].ctl_name) + + /*.*/1 + + /*sensors*/strlen(ctlhwnames[HW_SENSORS].ctl_name) + + /*.*/1; + char* prefix = malloc(prefix_len + 1); + /* these are pointers into optarg */ + char* sensordevname = NULL, *sensortype = NULL, *sensornumts = NULL; + int sensornumt = -1; + const size_t tempslen = strlen(sensor_type_s[SENSOR_TEMP]); + const char* errstr = NULL; + int dev = 0; + + if(prefix == NULL) { + errno = ENOMEM; + return; + } + memset(prefix, 0, prefix_len + 1); + strcpy(prefix, ctlnames[CTL_HW].ctl_name); + strcat(prefix, "."); + strcat(prefix, ctlhwnames[HW_SENSORS].ctl_name); + strcat(prefix, "."); + + /* we only allow hw.sensors...; everything else is irrelevant */ + if(strncmp(path, prefix, prefix_len) != 0) { + if(verbose) + fprintf(stderr, + "Valid temperature sensors start with %s\n" + "Run `sysctl -a' to find sensors", + prefix); + errno = EINVAL; + goto badprefix; + } + + + /* parse sensordevname and tail */ + sensordevname = path + prefix_len; + sensortype = strchr(sensordevname, '.'); + if(!sensortype) { + if(verbose) + fprintf(stderr, + "Valid sensor names are of the form hw.sensors.device.temp42\n" + "for example hw.sensors.cpu0.temp0\n"); + errno = EINVAL; + goto badprefix; + } + *sensortype++ = '\0'; /* save sensordevname, we need to find it later */ + + /* assert tail strats with "temp" */ + if(strncmp(sensortype, sensor_type_s[SENSOR_TEMP], tempslen) != 0) { + if(verbose) + fprintf(stderr, + "%s does not look like a temperature sensor\n", + path); + errno = EINVAL; + goto badprefix; + } + + /* convert remaining string to number = numt */ + sensornumts = sensortype + tempslen; + sensornumt = strtonum(sensornumts, 0, INT_MAX, &errstr); + if(errstr != NULL) { + if(verbose) + fprintf(stderr, + "%s does not look like a temperature sensor\n", + path); + errno = EINVAL; + goto badprefix; + } + + /* the string format looks good, now let's see if we can actually + find the sensor */ + temperature_mib[0] = CTL_HW; + temperature_mib[1] = HW_SENSORS; + /* this is a NULL terminated array of sensordev's */ + for(dev = 0; ; ++dev) { + struct sensordev sensordev; + size_t sensordevlen = sizeof(struct sensordev); + + memset(&sensordev, 0, sizeof(struct sensordev)); + temperature_mib[2] = dev; + if(-1 == sysctl( + temperature_mib, 3, + &sensordev, &sensordevlen, + NULL, 0) + ) { + /* probably end of array; sysctl sets errno to ENOENT */ + /* the man page doesn't say what other errno's it could + set, so let's assume it *is* ENOENT */ + if(verbose) + /* using warn to print errno in case it wasn't ENOENT */ + warn("No such sensor device: %s\n", sensordevname); + goto badprefix; + } + + assert(sensordevlen == sizeof(struct sensordev)); + + if(strcmp(sensordevname, sensordev.xname) == 0) { + /* found it; before we yield success, check if it has + a temperature sensor */ + if(sensornumt >= sensordev.maxnumt[SENSOR_TEMP]) { + /* no dice */ + if(verbose) + fprintf(stderr, + "temp%d is out of range, max is temp%d\n", + sensornumt, + sensordev.maxnumt[SENSOR_TEMP]); + errno = ENOENT; + goto badprefix; + } + temperature_mib[2] = sensordev.num; + break; + } + } + + /* okay, we have it; finish populating mib and claim we're done */ + temperature_mib[3] = SENSOR_TEMP; + temperature_mib[4] = sensornumt; + temperature_sensor_found = 1; + + if(verbose) { + fprintf(stderr, + "Using hw.sensors.%s.temp%d as the temperature sensor\n", + sensordevname, + sensornumt); + } + +badprefix: + free(prefix); +} + + int main(int argc, char *argv[]) { int opt; @@ -165,7 +407,7 @@ int main(int argc, char *argv[]) { err(1, "unveil failed"); unveil(NULL, NULL); - while((opt = getopt(argc, argv, "d:hi:l:m:r:s:t:T:v")) != -1) { + while((opt = getopt(argc, argv, "d:hi:l:m:r:s:S:t:T:v")) != -1) { switch(opt) { case 'd': assign_values_from_param(optarg, &wall_down_step, &batt_down_step); @@ -200,6 +442,11 @@ int main(int argc, char *argv[]) { if(step > 100 || step <= 0) err(1, "step must be positive and up to 100"); break; + case 'S': + parse_sensor_path(optarg); + if(!temperature_sensor_found) + err(1, "invalid temperature sensor"); + break; case 't': assign_values_from_param(optarg, &wall_timefreq, &batt_timefreq); if(wall_timefreq <= 0 || batt_timefreq <= 0) @@ -236,6 +483,18 @@ int main(int argc, char *argv[]) { } } + /* initialize default temp sensor if we need to */ + if(temp_max > 0 && !temperature_sensor_found) { + try_to_find_default_temperature_sensor(); + } + + /* warn if -S was specified, but not -T */ + if(temp_max <= 0 && temperature_sensor_found) { + errno = EINVAL; + warn("you have specified a temperature sensor without setting\ntemperature limits with -T"); + errno = 0; + } + /* avoid weird reading for first delta */ if (sysctl(mib_load, 2, &cpu_previous, &len_cpu, NULL, 0) == -1) err(1, "sysctl"); diff --git a/obsdfreqd.1 b/obsdfreqd.1 index 840a22c..776def1 100644 --- a/obsdfreqd.1 +++ b/obsdfreqd.1 @@ -14,7 +14,9 @@ .Op Fl r Ar threshold .Op Fl s Ar stepfrequency .Op Fl t Ar timefrequency -.Op Fl T Ar maxtemperature +.Oo Fl T Ar maxtemperature +.Op Fl S Ar sensorpath +.Oc .Sh DESCRIPTION .Nm is a program to play with the CPU frequency scheduler by providing @@ -51,6 +53,12 @@ Defines the maximum temperature in Celcius .Nm will allow before lowering the maximum frequency until the temperature cooled down. +.It Fl S Ar sensorpath +In conjunction with +.Sy -T, +.No allows you to specify which sensor to use for temperature readings. If unspecified, +.Nm +will try to guess which temperature sensor to use (usually hw.sensors.cpu0.temp0). .El .Sh EXIT STATUS .Ex