1 /* $OpenBSD: thermal.c,v 1.6 2019/10/08 13:21:38 cheloha 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
thermal_thread_init(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
thermal_thread_create(void * arg)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
thermal_thread_loop(void * arg)90 thermal_thread_loop(void *arg)
91 {
92 while (thermal_enable) {
93 thermal_manage_fans();
94 tsleep_nsec(&thermal_enable, 0, "thermal", SEC_TO_NSEC(1));
95 }
96 kthread_exit(0);
97 }
98
99 void
thermal_manage_fans(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
thermal_fan_register(struct thermal_fan * fan)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
thermal_sensor_register(struct thermal_temp * sensor)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