This diff adds fan management support for the macppc smu(4), ported from FreeBSD. Some test reports would be welcome by putting load on your system and watch the fans scale (hopefully). On my G5 it works fine so far.
Later on more I2C temp. sensors could be added like, maxds(4) and lmtemp(4). Index: sys/arch/macppc/conf/files.macppc =================================================================== RCS file: /cvs/src/sys/arch/macppc/conf/files.macppc,v retrieving revision 1.86 diff -u -p -u -p -r1.86 files.macppc --- sys/arch/macppc/conf/files.macppc 5 Mar 2016 17:41:55 -0000 1.86 +++ sys/arch/macppc/conf/files.macppc 19 May 2016 19:22:36 -0000 @@ -29,6 +29,7 @@ include "dev/mii/files.mii" # MAC generic # file arch/macppc/dev/dbdma.c +file arch/macppc/dev/thermal.c # # Openfirmware support Index: sys/arch/macppc/dev/thermal.c =================================================================== RCS file: sys/arch/macppc/dev/thermal.c diff -N sys/arch/macppc/dev/thermal.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/arch/macppc/dev/thermal.c 19 May 2016 19:22:36 -0000 @@ -0,0 +1,221 @@ +/* $ OpenBSD $ */ + +/*- + * Copyright (c) 2009-2011 Nathan Whitehorn + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> + +#include <sys/malloc.h> +#include <sys/reboot.h> +#include <sys/sensors.h> +#include <sys/kthread.h> + +#include <macppc/dev/thermal.h> + +/* A 10 second timer for spinning down fans. */ +#define FAN_HYSTERESIS_TIMER 10 + +void thermal_thread_init(void); +void thermal_thread_create(void *); +void thermal_thread_loop(void *); +void thermal_manage_fans(void); + +int enable_thermal = 0; + +struct thermal_fan_le { + struct thermal_fan *fan; + int last_val; + int timer; + SLIST_ENTRY(thermal_fan_le) entries; +}; +struct thermal_sens_le { + struct thermal_temp *sensor; + int last_val; +#define MAX_CRITICAL_COUNT 6 + int critical_count; + SLIST_ENTRY(thermal_sens_le) entries; +}; + +SLIST_HEAD(thermal_fans, thermal_fan_le) fans = + SLIST_HEAD_INITIALIZER(fans); +SLIST_HEAD(thermal_sensors, thermal_sens_le) sensors = + SLIST_HEAD_INITIALIZER(sensors); + +void +thermal_thread_init(void) +{ + kthread_create_deferred(thermal_thread_create, &enable_thermal); +} + +void +thermal_thread_create(void *arg) +{ + if (enable_thermal) + return; /* we're already running */ + + enable_thermal = 1; + if (kthread_create(thermal_thread_loop, &enable_thermal, NULL, + "thermal")) { + printf("thermal kernel thread can't be created!\n"); + enable_thermal = 0; + } +} + +void +thermal_thread_loop(void *arg) +{ + while (enable_thermal) { + thermal_manage_fans(); + tsleep(&enable_thermal, 0, "thermal", hz); + } + kthread_exit(0); +} + +void +thermal_manage_fans(void) +{ + struct thermal_sens_le *sensor; + struct thermal_fan_le *fan; + int average_excess, max_excess_zone, frac_excess; + int fan_speed; + int nsens, nsens_zone; + int temp; + + /* Read all the sensors */ + SLIST_FOREACH(sensor, &sensors, entries) { + temp = sensor->sensor->read(sensor->sensor); + if (temp > 0) /* Use the previous temp in case of error */ + sensor->last_val = temp; + + if (sensor->last_val > sensor->sensor->max_temp) { + sensor->critical_count++; + printf("WARNING: Current temperature (%s: %d.%d C) " + "exceeds critical temperature (%d.%d C); " + "count=%d\n", + sensor->sensor->name, + (sensor->last_val - ZERO_C_TO_MUK)/1000000, + (sensor->last_val - ZERO_C_TO_MUK)%1000000, + (sensor->sensor->max_temp - ZERO_C_TO_MUK)/1000000, + (sensor->sensor->max_temp - ZERO_C_TO_MUK)%1000000, + sensor->critical_count); + if (sensor->critical_count >= MAX_CRITICAL_COUNT) { + printf("WARNING: %s temperature exceeded " + "critical temperature %d times in a row; " + "shutting down!\n", + sensor->sensor->name, + sensor->critical_count); + boot(RB_POWERDOWN); + } + } else { + if (sensor->critical_count > 0) + sensor->critical_count--; + } + } + + /* Set all the fans */ + SLIST_FOREACH(fan, &fans, entries) { + nsens = nsens_zone = 0; + average_excess = max_excess_zone = 0; + SLIST_FOREACH(sensor, &sensors, entries) { + temp = imin(sensor->last_val, + sensor->sensor->max_temp); + frac_excess = (temp - + sensor->sensor->target_temp)*100 / + (sensor->sensor->max_temp - temp + 1); + if (frac_excess < 0) + frac_excess = 0; + if (sensor->sensor->zone == fan->fan->zone) { + max_excess_zone = imax(max_excess_zone, + frac_excess); + nsens_zone++; + } + average_excess += frac_excess; + nsens++; + } + average_excess /= nsens; + + /* If there are no sensors in this zone, use the average */ + if (nsens_zone == 0) + max_excess_zone = average_excess; + /* No sensors at all? Use default */ + if (nsens == 0) { + fan->fan->set(fan->fan, fan->fan->default_rpm); + continue; + } + + /* + * Scale the fan linearly in the max temperature in its + * thermal zone. + */ + max_excess_zone = imin(max_excess_zone, 100); + fan_speed = max_excess_zone * + (fan->fan->max_rpm - fan->fan->min_rpm)/100 + + fan->fan->min_rpm; + if (fan_speed >= fan->last_val) { + fan->timer = FAN_HYSTERESIS_TIMER; + fan->last_val = fan_speed; + } else { + fan->timer--; + if (fan->timer == 0) { + fan->last_val = fan_speed; + fan->timer = FAN_HYSTERESIS_TIMER; + } + } + fan->fan->set(fan->fan, fan->last_val); + } +} + +void +thermal_fan_register(struct thermal_fan *fan) +{ + struct thermal_fan_le *list_entry; + + thermal_thread_init(); /* first caller inits our thread */ + + list_entry = malloc(sizeof(struct thermal_fan_le), M_DEVBUF, + M_ZERO | M_WAITOK); + list_entry->fan = fan; + + SLIST_INSERT_HEAD(&fans, list_entry, entries); +} + +void +thermal_sensor_register(struct thermal_temp *sensor) +{ + struct thermal_sens_le *list_entry; + + thermal_thread_init(); /* first caller inits our thread */ + + list_entry = malloc(sizeof(struct thermal_sens_le), M_DEVBUF, + M_ZERO | M_WAITOK); + list_entry->sensor = sensor; + list_entry->last_val = 0; + list_entry->critical_count = 0; + + SLIST_INSERT_HEAD(&sensors, list_entry, entries); +} Index: sys/arch/macppc/dev/thermal.h =================================================================== RCS file: sys/arch/macppc/dev/thermal.h diff -N sys/arch/macppc/dev/thermal.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/arch/macppc/dev/thermal.h 19 May 2016 19:22:36 -0000 @@ -0,0 +1,51 @@ +/* $OpenBSD$ */ + +/*- + * Copyright (c) 2009-2011 Nathan Whitehorn + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define ZERO_C_TO_MUK 273150000 + +struct thermal_fan { + int min_rpm, max_rpm, default_rpm; + + char name[32]; + int zone; + + int (*read)(struct thermal_fan *); + int (*set)(struct thermal_fan *, int value); +}; + +struct thermal_temp { + int target_temp, max_temp; /* muK */ + + char name[32]; + int zone; + + int (*read)(struct thermal_temp *); +}; + +void thermal_fan_register(struct thermal_fan *); +void thermal_sensor_register(struct thermal_temp *); Index: sys/arch/macppc/dev/smu.c =================================================================== RCS file: /cvs/src/sys/arch/macppc/dev/smu.c,v retrieving revision 1.31 diff -u -p -u -p -r1.31 smu.c --- sys/arch/macppc/dev/smu.c 14 May 2016 21:22:17 -0000 1.31 +++ sys/arch/macppc/dev/smu.c 19 May 2016 19:25:24 -0000 @@ -32,14 +32,20 @@ #include <dev/ofw/openfirm.h> #include <macppc/dev/maci2cvar.h> +#include <macppc/dev/thermal.h> #include <macppc/pci/macobio.h> int smu_match(struct device *, void *, void *); void smu_attach(struct device *, struct device *, void *); +/* Target and Max. temperature in muK. */ +#define TEMP_TRG 38 * 1000000 + 273150000 +#define TEMP_MAX 80 * 1000000 + 273150000 + #define SMU_MAXFANS 8 struct smu_fan { + struct thermal_fan fan; u_int8_t reg; u_int16_t min_rpm; u_int16_t max_rpm; @@ -53,6 +59,7 @@ struct smu_fan { #define SMU_MAXSENSORS 4 struct smu_sensor { + struct thermal_temp therm; u_int8_t reg; struct ksensor sensor; }; @@ -152,9 +159,13 @@ int smu_fan_read_rpm(struct smu_softc *, int smu_fan_read_pwm(struct smu_softc *, struct smu_fan *, u_int16_t *, u_int16_t *); int smu_fan_refresh(struct smu_softc *, struct smu_fan *); -int smu_sensor_refresh(struct smu_softc *, struct smu_sensor *); +int smu_sensor_refresh(struct smu_softc *, struct smu_sensor *, int); void smu_refresh_sensors(void *); +int smu_fan_set_rpm_thermal(struct smu_fan *, int); +int smu_fan_set_pwm_thermal(struct smu_fan *, int); +int smu_sensor_refresh_thermal(struct smu_sensor *); + int smu_i2c_acquire_bus(void *, int); void smu_i2c_release_bus(void *, int); int smu_i2c_exec(void *, i2c_op_t, i2c_addr_t, @@ -301,6 +312,15 @@ smu_attach(struct device *parent, struct /* Start running fans at their "unmanaged" speed. */ smu_fan_set_rpm(sc, fan, fan->unmanaged_rpm); + /* Register fan at thermal management framework. */ + fan->fan.min_rpm = fan->min_rpm; + fan->fan.max_rpm = fan->max_rpm; + fan->fan.default_rpm = fan->unmanaged_rpm; + strlcpy(fan->fan.name, loc, sizeof fan->fan.name); + OF_getprop(node, "zone", &fan->fan.zone, sizeof fan->fan.zone); + fan->fan.set = (int (*)(struct thermal_fan *, int)) + smu_fan_set_rpm_thermal; + thermal_fan_register(&fan->fan); #ifndef SMALL_KERNEL sensor_attach(&sc->sc_sensordev, &fan->sensor); #endif @@ -348,6 +368,15 @@ smu_attach(struct device *parent, struct /* Start running fans at their "unmanaged" speed. */ smu_fan_set_pwm(sc, fan, fan->unmanaged_pwm); + /* Register fan at thermal management framework. */ + fan->fan.min_rpm = fan->min_pwm; + fan->fan.max_rpm = fan->max_pwm; + fan->fan.default_rpm = fan->unmanaged_pwm; + strlcpy(fan->fan.name, loc, sizeof fan->fan.name); + OF_getprop(node, "zone", &fan->fan.zone, sizeof fan->fan.zone); + fan->fan.set = (int (*)(struct thermal_fan *, int)) + smu_fan_set_pwm_thermal; + thermal_fan_register(&fan->fan); #ifndef SMALL_KERNEL sensor_attach(&sc->sc_sensordev, &fan->sensor); #endif @@ -397,6 +426,19 @@ smu_attach(struct device *parent, struct strlcpy(loc, "Unknown", sizeof loc); strlcpy(sensor->sensor.desc, loc, sizeof sensor->sensor.desc); + /* Register temp. sensor at thermal management framework. */ + if (sensor->sensor.type == SENSOR_TEMP) { + sensor->therm.target_temp = TEMP_TRG; + sensor->therm.max_temp = TEMP_MAX; + strlcpy(sensor->therm.name, loc, + sizeof sensor->therm.name); + OF_getprop(node, "zone", &sensor->therm.zone, + sizeof sensor->therm.zone); + sensor->therm.read = (int (*) + (struct thermal_temp *))smu_sensor_refresh_thermal; + thermal_sensor_register(&sensor->therm); + } + sensor_attach(&sc->sc_sensordev, &sensor->sensor); } @@ -700,7 +742,8 @@ smu_fan_refresh(struct smu_softc *sc, st } int -smu_sensor_refresh(struct smu_softc *sc, struct smu_sensor *sensor) +smu_sensor_refresh(struct smu_softc *sc, struct smu_sensor *sensor, + int update_sysctl) { struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; int64_t value; @@ -761,9 +804,11 @@ smu_sensor_refresh(struct smu_softc *sc, default: break; } - sensor->sensor.value = value; - sensor->sensor.flags = 0; - return (0); + if (update_sysctl) { + sensor->sensor.value = value; + sensor->sensor.flags = 0; + } + return (value); } void @@ -774,10 +819,50 @@ smu_refresh_sensors(void *arg) rw_enter_write(&sc->sc_lock); for (i = 0; i < sc->sc_num_sensors; i++) - smu_sensor_refresh(sc, &sc->sc_sensors[i]); + smu_sensor_refresh(sc, &sc->sc_sensors[i], 1); for (i = 0; i < sc->sc_num_fans; i++) smu_fan_refresh(sc, &sc->sc_fans[i]); rw_exit_write(&sc->sc_lock); +} + +/* + * Wrapper functions for the thermal management framework. + */ +int +smu_fan_set_rpm_thermal(struct smu_fan *fan, int rpm) +{ + struct smu_softc *sc = smu_cd.cd_devs[0]; + + rw_enter_write(&sc->sc_lock); + (void)smu_fan_set_rpm(sc, fan, rpm); + rw_exit_write(&sc->sc_lock); + + return (0); +} + +int +smu_fan_set_pwm_thermal(struct smu_fan *fan, int pwm) +{ + struct smu_softc *sc = smu_cd.cd_devs[0]; + + rw_enter_write(&sc->sc_lock); + (void)smu_fan_set_pwm(sc, fan, pwm); + rw_exit_write(&sc->sc_lock); + + return (0); +} + +int +smu_sensor_refresh_thermal(struct smu_sensor *sensor) +{ + struct smu_softc *sc = smu_cd.cd_devs[0]; + int value; + + rw_enter_write(&sc->sc_lock); + value = smu_sensor_refresh(sc, sensor, 0); + rw_exit_write(&sc->sc_lock); + + return (value); } int