A simple fan daemon for OpenPOWER systems
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

/**
* 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;
}