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