1 /* $OpenBSD: thermal.c,v 1.5 2016/07/29 04:41:53 jsg Exp $ */ 2 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/systm.h> 31 #include <sys/kernel.h> 32 33 #include <sys/malloc.h> 34 #include <sys/reboot.h> 35 #include <sys/sensors.h> 36 #include <sys/kthread.h> 37 38 #include <macppc/dev/thermal.h> 39 40 /* A 10 second timer for spinning down fans. */ 41 #define FAN_HYSTERESIS_TIMER 10 42 43 void thermal_thread_init(void); 44 void thermal_thread_create(void *); 45 void thermal_thread_loop(void *); 46 void thermal_manage_fans(void); 47 48 int thermal_enable = 0; 49 50 struct thermal_fan_le { 51 struct thermal_fan *fan; 52 int last_val; 53 int timer; 54 SLIST_ENTRY(thermal_fan_le) entries; 55 }; 56 struct thermal_sens_le { 57 struct thermal_temp *sensor; 58 int last_val; 59 #define MAX_CRITICAL_COUNT 6 60 int critical_count; 61 SLIST_ENTRY(thermal_sens_le) entries; 62 }; 63 64 SLIST_HEAD(thermal_fans, thermal_fan_le) fans = 65 SLIST_HEAD_INITIALIZER(fans); 66 SLIST_HEAD(thermal_sensors, thermal_sens_le) sensors = 67 SLIST_HEAD_INITIALIZER(sensors); 68 69 void 70 thermal_thread_init(void) 71 { 72 if (thermal_enable) 73 return; /* we're already running */ 74 thermal_enable = 1; 75 76 kthread_create_deferred(thermal_thread_create, &thermal_enable); 77 } 78 79 void 80 thermal_thread_create(void *arg) 81 { 82 if (kthread_create(thermal_thread_loop, &thermal_enable, NULL, 83 "thermal")) { 84 printf("thermal kernel thread can't be created!\n"); 85 thermal_enable = 0; 86 } 87 } 88 89 void 90 thermal_thread_loop(void *arg) 91 { 92 while (thermal_enable) { 93 thermal_manage_fans(); 94 tsleep(&thermal_enable, 0, "thermal", hz); 95 } 96 kthread_exit(0); 97 } 98 99 void 100 thermal_manage_fans(void) 101 { 102 struct thermal_sens_le *sensor; 103 struct thermal_fan_le *fan; 104 int64_t average_excess, max_excess_zone, frac_excess; 105 int fan_speed; 106 int nsens, nsens_zone; 107 int temp; 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 (%lld.%lld C); " 119 "count=%d\n", 120 sensor->sensor->name, 121 (sensor->last_val - ZERO_C_TO_MUK)/1000000, 122 (sensor->last_val - ZERO_C_TO_MUK)%1000000, 123 (sensor->sensor->max_temp - ZERO_C_TO_MUK)/1000000, 124 (sensor->sensor->max_temp - ZERO_C_TO_MUK)%1000000, 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 reboot(RB_HALT | RB_POWERDOWN | RB_TIMEBAD); 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 = ulmin(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 = ulmax(max_excess_zone, 154 frac_excess); 155 nsens_zone++; 156 } 157 average_excess += frac_excess; 158 nsens++; 159 } 160 161 /* No sensors at all? Use default */ 162 if (nsens == 0) { 163 fan->fan->set(fan->fan, fan->fan->default_rpm); 164 continue; 165 } 166 167 average_excess /= nsens; 168 169 /* If there are no sensors in this zone, use the average */ 170 if (nsens_zone == 0) 171 max_excess_zone = average_excess; 172 173 /* 174 * Scale the fan linearly in the max temperature in its 175 * thermal zone. 176 */ 177 max_excess_zone = ulmin(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 thermal_fan_register(struct thermal_fan *fan) 197 { 198 struct thermal_fan_le *list_entry; 199 200 thermal_thread_init(); /* first caller inits our thread */ 201 202 list_entry = malloc(sizeof(struct thermal_fan_le), M_DEVBUF, 203 M_ZERO | M_WAITOK); 204 list_entry->fan = fan; 205 206 SLIST_INSERT_HEAD(&fans, list_entry, entries); 207 } 208 209 void 210 thermal_sensor_register(struct thermal_temp *sensor) 211 { 212 struct thermal_sens_le *list_entry; 213 214 thermal_thread_init(); /* first caller inits our thread */ 215 216 list_entry = malloc(sizeof(struct thermal_sens_le), M_DEVBUF, 217 M_ZERO | M_WAITOK); 218 list_entry->sensor = sensor; 219 list_entry->last_val = 0; 220 list_entry->critical_count = 0; 221 222 SLIST_INSERT_HEAD(&sensors, list_entry, entries); 223 } 224