You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
4.9 KiB
162 lines
4.9 KiB
/**
|
|
* Copyright 2019 Shawn Anastasio
|
|
*
|
|
* This file is part of op-fan-daemon.
|
|
*
|
|
* op-fan-daemon is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* op-fan-daemon is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with op-fan-daemon. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* Implementation of fan control logic
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fan-daemon.h"
|
|
#include "curve.h"
|
|
#include "util.h"
|
|
|
|
// Number of consecutive times a curve needs to be selected for it to take effect
|
|
#define NEXT_CURVE_COUNT_MIN 20
|
|
|
|
void print_zones(struct controller_state *state) {
|
|
printf("CPU0: ");
|
|
for (size_t i=0; i<state->zones[ZONE_CPU0].sensors.count; i++) {
|
|
printf("%d, ", state->zones[ZONE_CPU0].sensors.data[i]);
|
|
}
|
|
printf("\n");
|
|
|
|
printf("CPU1: ");
|
|
for (size_t i=0; i<state->zones[ZONE_CPU1].sensors.count; i++) {
|
|
printf("%d, ", state->zones[ZONE_CPU1].sensors.data[i]);
|
|
}
|
|
printf("\n");
|
|
|
|
printf("CHASSIS: ");
|
|
for (size_t i=0; i<state->zones[ZONE_CHASSIS].sensors.count; i++) {
|
|
printf("%d, ", state->zones[ZONE_CHASSIS].sensors.data[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
bool read_temp(int sensor_fd, uint32_t *temp_out) {
|
|
char buf[10 + 1 /* enough for a u32 in base 10 */];
|
|
|
|
lseek(sensor_fd, 0, SEEK_SET);
|
|
ssize_t n = read(sensor_fd, buf, sizeof(buf));
|
|
if (n < 0) {
|
|
syslog(LOG_ERR, "Failed to read sensor: %m. Disabling automatic fan control.\n");
|
|
return false;
|
|
}
|
|
buf[n] = '\0';
|
|
|
|
char *endptr;
|
|
long result = strtol(buf, &endptr, 10);
|
|
if (*endptr != '\n' && *endptr != '\0') {
|
|
syslog(LOG_ERR, "Failed to parse sensor data (%s). Disabling automatic fan control.\n", buf);
|
|
return false;
|
|
}
|
|
|
|
*temp_out = result;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Scan for the most appropriate curve point for the given temperature
|
|
*/
|
|
uint32_t find_new_curve_point(const struct zone_data *zone, uint32_t temp) {
|
|
// Check for raise
|
|
if (zone->curve_pos != zone->curve_size - 1) {
|
|
const struct fan_curve_point *next = &zone->curve[zone->curve_pos + 1];
|
|
// Check if temperature is high enough to go to next point
|
|
if (temp > next->temp)
|
|
goto find_new;
|
|
}
|
|
|
|
// Check for drop
|
|
if (zone->curve_pos != 0) {
|
|
const struct fan_curve_point *cur = &zone->curve[zone->curve_pos];
|
|
// Check if temperature dropped below cur - hysteresis
|
|
if (temp < cur->temp - cur->hysteresis)
|
|
goto find_new;
|
|
}
|
|
|
|
// No change
|
|
return zone->curve_pos;
|
|
|
|
find_new: ;
|
|
// Pick new point
|
|
uint32_t new = zone->curve_size - 1;
|
|
for (size_t i=0; i<zone->curve_size; i++) {
|
|
if (temp >= zone->curve[i].temp)
|
|
new = i;
|
|
}
|
|
return new;
|
|
}
|
|
|
|
bool update_zone(struct controller_state *state, enum zone z) {
|
|
// Read all sensors and get the maximum one
|
|
uint32_t max_temp = 0;
|
|
|
|
struct zone_data *zone = &state->zones[z];
|
|
assert(zone->sensors.count > 0);
|
|
for (size_t i=0; i<zone->sensors.count; i++) {
|
|
uint32_t temp;
|
|
int cur_sensor = zone->sensors.data[i];
|
|
if (!read_temp(cur_sensor, &temp))
|
|
return false; // Error occurred, disable fan control
|
|
|
|
if (temp > max_temp)
|
|
max_temp = temp;
|
|
}
|
|
|
|
uint32_t new_point = find_new_curve_point(zone, max_temp);
|
|
if (new_point == zone->curve_pos)
|
|
return true; // no update
|
|
|
|
// Check if the new curve point matches the previous new curve point
|
|
if (new_point != zone->next_curve_pos) {
|
|
// Didn't match, reset counter
|
|
if (zone->next_curve_count != NEXT_CURVE_COUNT_MIN)
|
|
dbg_printf("[Zone %u] flushed staging curve (was going to %u)!\n", z,
|
|
zone->curve[zone->next_curve_pos].speed);
|
|
zone->next_curve_pos = new_point;
|
|
zone->next_curve_count = 1;
|
|
return true;
|
|
} else
|
|
// Matched, increment counter
|
|
zone->next_curve_count++;
|
|
|
|
// If counter isn't high enough, skip update
|
|
if (zone->next_curve_count < NEXT_CURVE_COUNT_MIN)
|
|
return true;
|
|
|
|
// Curve point has been selected for enough cycles, write it
|
|
dbg_printf("Zone %u temperature changed to %u, setting speed to %u\n", z, max_temp,
|
|
zone->curve[zone->next_curve_pos].speed);
|
|
pwm_provider->set_zone_speed(z, zone->curve[zone->next_curve_pos].speed);
|
|
zone->curve_pos = zone->next_curve_pos;
|
|
|
|
return true;
|
|
}
|
|
|