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