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/param.h> 30 #include <sys/kernel.h> 31 #include <sys/lock.h> 32 #include <sys/mutex.h> 33 #include <sys/systm.h> 34 35 #include <sys/types.h> 36 #include <sys/kthread.h> 37 #include <sys/malloc.h> 38 #include <sys/reboot.h> 39 #include <sys/sysctl.h> 40 #include <sys/queue.h> 41 42 #include "powermac_thermal.h" 43 44 /* A 10 second timer for spinning down fans. */ 45 #define FAN_HYSTERESIS_TIMER 10 46 47 static void fan_management_proc(void); 48 static void pmac_therm_manage_fans(void); 49 50 static struct proc *pmac_them_proc; 51 static int enable_pmac_thermal = 1; 52 53 static struct kproc_desc pmac_therm_kp = { 54 "pmac_thermal", 55 fan_management_proc, 56 &pmac_them_proc 57 }; 58 59 SYSINIT(pmac_therm_setup, SI_SUB_KTHREAD_IDLE, SI_ORDER_ANY, kproc_start, 60 &pmac_therm_kp); 61 SYSCTL_INT(_machdep, OID_AUTO, manage_fans, CTLFLAG_RWTUN, 62 &enable_pmac_thermal, 1, "Enable automatic fan management"); 63 static MALLOC_DEFINE(M_PMACTHERM, "pmactherm", "Powermac Thermal Management"); 64 65 struct pmac_fan_le { 66 struct pmac_fan *fan; 67 int last_val; 68 int timer; 69 SLIST_ENTRY(pmac_fan_le) entries; 70 }; 71 struct pmac_sens_le { 72 struct pmac_therm *sensor; 73 int last_val; 74 #define MAX_CRITICAL_COUNT 6 75 int critical_count; 76 SLIST_ENTRY(pmac_sens_le) entries; 77 }; 78 static SLIST_HEAD(pmac_fans, pmac_fan_le) fans = SLIST_HEAD_INITIALIZER(fans); 79 static SLIST_HEAD(pmac_sensors, pmac_sens_le) sensors = 80 SLIST_HEAD_INITIALIZER(sensors); 81 82 static void 83 fan_management_proc(void) 84 { 85 /* Nothing to manage? */ 86 if (SLIST_EMPTY(&fans)) 87 kproc_exit(0); 88 89 while (1) { 90 pmac_therm_manage_fans(); 91 pause("pmac_therm", hz); 92 } 93 } 94 95 static void 96 pmac_therm_manage_fans(void) 97 { 98 struct pmac_sens_le *sensor; 99 struct pmac_fan_le *fan; 100 int average_excess, max_excess_zone, frac_excess; 101 int fan_speed; 102 int nsens, nsens_zone; 103 int temp; 104 105 if (!enable_pmac_thermal) 106 return; 107 108 /* Read all the sensors */ 109 SLIST_FOREACH(sensor, &sensors, entries) { 110 temp = sensor->sensor->read(sensor->sensor); 111 if (temp > 0) /* Use the previous temp in case of error */ 112 sensor->last_val = temp; 113 114 if (sensor->last_val > sensor->sensor->max_temp) { 115 sensor->critical_count++; 116 printf("WARNING: Current temperature (%s: %d.%d C) " 117 "exceeds critical temperature (%d.%d C); " 118 "count=%d\n", 119 sensor->sensor->name, 120 (sensor->last_val - ZERO_C_TO_K) / 10, 121 (sensor->last_val - ZERO_C_TO_K) % 10, 122 (sensor->sensor->max_temp - ZERO_C_TO_K) / 10, 123 (sensor->sensor->max_temp - ZERO_C_TO_K) % 10, 124 sensor->critical_count); 125 if (sensor->critical_count >= MAX_CRITICAL_COUNT) { 126 printf("WARNING: %s temperature exceeded " 127 "critical temperature %d times in a row; " 128 "shutting down!\n", 129 sensor->sensor->name, 130 sensor->critical_count); 131 shutdown_nice(RB_POWEROFF); 132 } 133 } else { 134 if (sensor->critical_count > 0) 135 sensor->critical_count--; 136 } 137 } 138 139 /* Set all the fans */ 140 SLIST_FOREACH(fan, &fans, entries) { 141 nsens = nsens_zone = 0; 142 average_excess = max_excess_zone = 0; 143 SLIST_FOREACH(sensor, &sensors, entries) { 144 temp = imin(sensor->last_val, 145 sensor->sensor->max_temp); 146 frac_excess = (temp - 147 sensor->sensor->target_temp)*100 / 148 (sensor->sensor->max_temp - temp + 1); 149 if (frac_excess < 0) 150 frac_excess = 0; 151 if (sensor->sensor->zone == fan->fan->zone) { 152 max_excess_zone = imax(max_excess_zone, 153 frac_excess); 154 nsens_zone++; 155 } 156 average_excess += frac_excess; 157 nsens++; 158 } 159 average_excess /= nsens; 160 161 /* If there are no sensors in this zone, use the average */ 162 if (nsens_zone == 0) 163 max_excess_zone = average_excess; 164 /* No sensors at all? Use default */ 165 if (nsens == 0) { 166 fan->fan->set(fan->fan, fan->fan->default_rpm); 167 continue; 168 } 169 170 /* 171 * Scale the fan linearly in the max temperature in its 172 * thermal zone. 173 */ 174 max_excess_zone = imin(max_excess_zone, 100); 175 fan_speed = max_excess_zone * 176 (fan->fan->max_rpm - fan->fan->min_rpm)/100 + 177 fan->fan->min_rpm; 178 if (fan_speed >= fan->last_val) { 179 fan->timer = FAN_HYSTERESIS_TIMER; 180 fan->last_val = fan_speed; 181 } else { 182 fan->timer--; 183 if (fan->timer == 0) { 184 fan->last_val = fan_speed; 185 fan->timer = FAN_HYSTERESIS_TIMER; 186 } 187 } 188 fan->fan->set(fan->fan, fan->last_val); 189 } 190 } 191 192 void 193 pmac_thermal_fan_register(struct pmac_fan *fan) 194 { 195 struct pmac_fan_le *list_entry; 196 197 list_entry = malloc(sizeof(struct pmac_fan_le), M_PMACTHERM, 198 M_ZERO | M_WAITOK); 199 list_entry->fan = fan; 200 201 SLIST_INSERT_HEAD(&fans, list_entry, entries); 202 } 203 204 void 205 pmac_thermal_sensor_register(struct pmac_therm *sensor) 206 { 207 struct pmac_sens_le *list_entry; 208 209 list_entry = malloc(sizeof(struct pmac_sens_le), M_PMACTHERM, 210 M_ZERO | M_WAITOK); 211 list_entry->sensor = sensor; 212 list_entry->last_val = 0; 213 list_entry->critical_count = 0; 214 215 SLIST_INSERT_HEAD(&sensors, list_entry, entries); 216 } 217