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.
 
 
 
 

195 lines
5.1 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/>.
*/
/**
* PWM operations for Talos II machines
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "fan-daemon.h"
static const char fan_controller_path[] =
"/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a440.i2c-bus/i2c-12/12-0052/hwmon/";
struct controller {
char dir_path[REASONABLE_PATH_LEN];
DIR *dir; // sysfs directory containing pwm control files
};
static struct controller controller;
// Maps zones to PWM outputs
static int8_t zone_map[][5] = {
[ZONE_CPU0] = {6, -1},
[ZONE_CPU1] = {5, -1},
[ZONE_CHASSIS] = {1, 2, 3, 4, -1}
};
static bool open_controller(const char *path, struct controller *controller_out) {
bool ret = false;
char pathbuf[REASONABLE_PATH_LEN];
DIR *controller = opendir(path);
if (!controller) {
syslog(LOG_ERR, "Unable to open Talos fan controller base dir!\n");
goto out;
}
// Walk fs to get to hwmonN directory
DIR *hwmonN = NULL;
struct dirent *child;
while ((child = readdir(controller))) {
if (child->d_name[0] == '.')
continue; // Skip hidden, ".", ".."
if (!strncmp("hwmon", child->d_name, 5)) {
snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, child->d_name);
hwmonN = opendir(pathbuf);
break;
}
}
if (!hwmonN) {
syslog(LOG_ERR, "Unable to open Talos fan controller hwmon directory!\n");
goto out_controller;
}
// Successfully opened hwmon
controller_out->dir = hwmonN;
strncpy(controller_out->dir_path, pathbuf, sizeof(controller_out->dir_path) - 1);
controller_out->dir_path[sizeof(controller_out->dir_path) - 1] = '\0';
ret = true;
out_controller:
closedir(controller);
out:
return ret;
}
static bool enable_all_pwm(struct controller *controller) {
char pathbuf[REASONABLE_PATH_LEN + 256 /* d_name */];
ssize_t n;
// Write 1 to all pwmX_enable nodes
struct dirent *child;
rewinddir(controller->dir);
while ((child = readdir(controller->dir))) {
if (child->d_name[0] == '.')
continue; // Skip hidden, ".", ".."
if (!strstr(child->d_name, "_enable"))
continue;
snprintf(pathbuf, sizeof(pathbuf), "%s/%s", controller->dir_path, child->d_name);
int fd = open(pathbuf, O_WRONLY);
if (fd < 0) {
syslog(LOG_ERR, "Unable to set open pwmX_enable: %m.\n");
return false;
}
const char manual_mode[] = "1";
if ((n = write(fd, manual_mode, 1)) < 0) {
syslog(LOG_ERR, "Unable to set manual PWM mode: %m.\n");
close(fd);
return false;
}
close(fd);
}
return true;
}
static void set_pwm(struct controller *controller, int8_t pwm, uint8_t speed) {
char buf[REASONABLE_PATH_LEN + 4 /* pwm */ + 4 /* digit */];
snprintf(buf, sizeof(buf), "%s/pwm%d", controller->dir_path, pwm);
int fd = open(buf, O_WRONLY);
if (fd < 0)
goto fail;
ssize_t n = snprintf(buf, sizeof(buf), "%d", speed);
if (write(fd, buf, n) < 0)
goto fail_open;
close(fd);
return;
fail_open:
close(fd);
fail:
syslog(LOG_ERR, "Unable to set PWM speed: %m.\n");
}
static bool talos_probe(void) {
char buf[256];
ssize_t n;
// Read device-tree to determine if we're on a Talos
int fd = open("/proc/device-tree/compatible", O_RDONLY);
if (fd < 0)
goto fail;
if ((n = read(fd, buf, sizeof(buf) - 1)) < 0)
goto fail_open;
close(fd);
buf[n] = '\0';
if (!strstr(buf, "rcs,talos-bmc"))
return false; // Not a Talos
// We're on a Talos, perform early initalization
if (!open_controller(fan_controller_path, &controller))
goto fail_open;
if (!enable_all_pwm(&controller))
goto fail_open;
syslog(LOG_INFO, "Successfully autodetected Talos platform.\n");
return true;
fail_open:
close(fd);
fail:
syslog(LOG_ERR, "talos_probe failed: %m.\n");
return false;
}
static void talos_set_zone_speed(enum zone zone, uint8_t speed) {
int8_t *fans = zone_map[zone];
assert(fans);
for (int8_t *cur = fans; *cur != -1; cur++) {
set_pwm(&controller, *cur, speed);
}
}
DECLARE_PWM_OPS(talos) = {
.probe = talos_probe,
.set_zone_speed = talos_set_zone_speed
};