1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2009-2011 Nathan Whitehorn 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/param.h> 33 #include <sys/kernel.h> 34 #include <sys/lock.h> 35 #include <sys/mutex.h> 36 #include <sys/systm.h> 37 38 #include <sys/types.h> 39 #include <sys/kthread.h> 40 #include <sys/malloc.h> 41 #include <sys/reboot.h> 42 #include <sys/sysctl.h> 43 #include <sys/queue.h> 44 45 #include "powermac_thermal.h" 46 47 /* A 10 second timer for spinning down fans. */ 48 #define FAN_HYSTERESIS_TIMER 10 49 50 static void fan_management_proc(void); 51 static void pmac_therm_manage_fans(void); 52 53 static struct proc *pmac_them_proc; 54 static int enable_pmac_thermal = 1; 55 56 static struct kproc_desc pmac_therm_kp = { 57 "pmac_thermal", 58 fan_management_proc, 59 &pmac_them_proc 60 }; 61 62 SYSINIT(pmac_therm_setup, SI_SUB_KTHREAD_IDLE, SI_ORDER_ANY, kproc_start, 63 &pmac_therm_kp); 64 SYSCTL_INT(_machdep, OID_AUTO, manage_fans, CTLFLAG_RWTUN, 65 &enable_pmac_thermal, 1, "Enable automatic fan management"); 66 static MALLOC_DEFINE(M_PMACTHERM, "pmactherm", "Powermac Thermal Management"); 67 68 struct pmac_fan_le { 69 struct pmac_fan *fan; 70 int last_val; 71 int timer; 72 SLIST_ENTRY(pmac_fan_le) entries; 73 }; 74 struct pmac_sens_le { 75 struct pmac_therm *sensor; 76 int last_val; 77 #define MAX_CRITICAL_COUNT 6 78 int critical_count; 79 SLIST_ENTRY(pmac_sens_le) entries; 80 }; 81 static SLIST_HEAD(pmac_fans, pmac_fan_le) fans = SLIST_HEAD_INITIALIZER(fans); 82 static SLIST_HEAD(pmac_sensors, pmac_sens_le) sensors = 83 SLIST_HEAD_INITIALIZER(sensors); 84 85 static void 86 fan_management_proc(void) 87 { 88 /* Nothing to manage? */ 89 if (SLIST_EMPTY(&fans)) 90 kproc_exit(0); 91 92 while (1) { 93 pmac_therm_manage_fans(); 94 pause("pmac_therm", hz); 95 } 96 } 97 98 static void 99 pmac_therm_manage_fans(void) 100 { 101 struct pmac_sens_le *sensor; 102 struct pmac_fan_le *fan; 103 int average_excess, max_excess_zone, frac_excess; 104 int fan_speed; 105 int nsens, nsens_zone; 106 int temp; 107 108 if (!enable_pmac_thermal) 109 return; 110 111 /* Read all the sensors */ 112 SLIST_FOREACH(sensor, &sensors, entries) { 113 temp = sensor->sensor->read(sensor->sensor); 114 if (temp > 0) /* Use the previous temp in case of error */ 115 sensor->last_val = temp; 116 117 if (sensor->last_val > sensor->sensor->max_temp) { 118 sensor->critical_count++; 119 printf("WARNING: Current temperature (%s: %d.%d C) " 120 "exceeds critical temperature (%d.%d C); " 121 "count=%d\n", 122 sensor->sensor->name, 123 (sensor->last_val - ZERO_C_TO_K) / 10, 124 (sensor->last_val - ZERO_C_TO_K) % 10, 125 (sensor->sensor->max_temp - ZERO_C_TO_K) / 10, 126 (sensor->sensor->max_temp - ZERO_C_TO_K) % 10, 127 sensor->critical_count); 128 if (sensor->critical_count >= MAX_CRITICAL_COUNT) { 129 printf("WARNING: %s temperature exceeded " 130 "critical temperature %d times in a row; " 131 "shutting down!\n", 132 sensor->sensor->name, 133 sensor->critical_count); 134 shutdown_nice(RB_POWEROFF); 135 } 136 } else { 137 if (sensor->critical_count > 0) 138 sensor->critical_count--; 139 } 140 } 141 142 /* Set all the fans */ 143 SLIST_FOREACH(fan, &fans, entries) { 144 nsens = nsens_zone = 0; 145 average_excess = max_excess_zone = 0; 146 SLIST_FOREACH(sensor, &sensors, entries) { 147 temp = imin(sensor->last_val, 148 sensor->sensor->max_temp); 149 frac_excess = (temp - 150 sensor->sensor->target_temp)*100 / 151 (sensor->sensor->max_temp - temp + 1); 152 if (frac_excess < 0) 153 frac_excess = 0; 154 if (sensor->sensor->zone == fan->fan->zone) { 155 max_excess_zone = imax(max_excess_zone, 156 frac_excess); 157 nsens_zone++; 158 } 159 average_excess += frac_excess; 160 nsens++; 161 } 162 average_excess /= nsens; 163 164 /* If there are no sensors in this zone, use the average */ 165 if (nsens_zone == 0) 166 max_excess_zone = average_excess; 167 /* No sensors at all? Use default */ 168 if (nsens == 0) { 169 fan->fan->set(fan->fan, fan->fan->default_rpm); 170 continue; 171 } 172 173 /* 174 * Scale the fan linearly in the max temperature in its 175 * thermal zone. 176 */ 177 max_excess_zone = imin(max_excess_zone, 100); 178 fan_speed = max_excess_zone * 179 (fan->fan->max_rpm - fan->fan->min_rpm)/100 + 180 fan->fan->min_rpm; 181 if (fan_speed >= fan->last_val) { 182 fan->timer = FAN_HYSTERESIS_TIMER; 183 fan->last_val = fan_speed; 184 } else { 185 fan->timer--; 186 if (fan->timer == 0) { 187 fan->last_val = fan_speed; 188 fan->timer = FAN_HYSTERESIS_TIMER; 189 } 190 } 191 fan->fan->set(fan->fan, fan->last_val); 192 } 193 } 194 195 void 196 pmac_thermal_fan_register(struct pmac_fan *fan) 197 { 198 struct pmac_fan_le *list_entry; 199 200 list_entry = malloc(sizeof(struct pmac_fan_le), M_PMACTHERM, 201 M_ZERO | M_WAITOK); 202 list_entry->fan = fan; 203 204 SLIST_INSERT_HEAD(&fans, list_entry, entries); 205 } 206 207 void 208 pmac_thermal_sensor_register(struct pmac_therm *sensor) 209 { 210 struct pmac_sens_le *list_entry; 211 212 list_entry = malloc(sizeof(struct pmac_sens_le), M_PMACTHERM, 213 M_ZERO | M_WAITOK); 214 list_entry->sensor = sensor; 215 list_entry->last_val = 0; 216 list_entry->critical_count = 0; 217 218 SLIST_INSERT_HEAD(&sensors, list_entry, entries); 219 } 220