2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2009-2011 Nathan Whitehorn
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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
29 #include <sys/param.h>
30 #include <sys/kernel.h>
32 #include <sys/mutex.h>
33 #include <sys/systm.h>
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>
42 #include "powermac_thermal.h"
44 /* A 10 second timer for spinning down fans. */
45 #define FAN_HYSTERESIS_TIMER 10
47 static void fan_management_proc(void);
48 static void pmac_therm_manage_fans(void);
50 static struct proc
*pmac_them_proc
;
51 static int enable_pmac_thermal
= 1;
53 static struct kproc_desc pmac_therm_kp
= {
59 SYSINIT(pmac_therm_setup
, SI_SUB_KTHREAD_IDLE
, SI_ORDER_ANY
, kproc_start
,
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");
69 SLIST_ENTRY(pmac_fan_le
) entries
;
72 struct pmac_therm
*sensor
;
74 #define MAX_CRITICAL_COUNT 6
76 SLIST_ENTRY(pmac_sens_le
) entries
;
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
);
83 fan_management_proc(void)
85 /* Nothing to manage? */
86 if (SLIST_EMPTY(&fans
))
90 pmac_therm_manage_fans();
91 pause("pmac_therm", hz
);
96 pmac_therm_manage_fans(void)
98 struct pmac_sens_le
*sensor
;
99 struct pmac_fan_le
*fan
;
100 int average_excess
, max_excess_zone
, frac_excess
;
102 int nsens
, nsens_zone
;
105 if (!enable_pmac_thermal
)
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
;
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); "
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; "
129 sensor
->sensor
->name
,
130 sensor
->critical_count
);
131 shutdown_nice(RB_POWEROFF
);
134 if (sensor
->critical_count
> 0)
135 sensor
->critical_count
--;
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);
151 if (sensor
->sensor
->zone
== fan
->fan
->zone
) {
152 max_excess_zone
= imax(max_excess_zone
,
156 average_excess
+= frac_excess
;
159 average_excess
/= nsens
;
161 /* If there are no sensors in this zone, use the average */
163 max_excess_zone
= average_excess
;
164 /* No sensors at all? Use default */
166 fan
->fan
->set(fan
->fan
, fan
->fan
->default_rpm
);
171 * Scale the fan linearly in the max temperature in its
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 +
178 if (fan_speed
>= fan
->last_val
) {
179 fan
->timer
= FAN_HYSTERESIS_TIMER
;
180 fan
->last_val
= fan_speed
;
183 if (fan
->timer
== 0) {
184 fan
->last_val
= fan_speed
;
185 fan
->timer
= FAN_HYSTERESIS_TIMER
;
188 fan
->fan
->set(fan
->fan
, fan
->last_val
);
193 pmac_thermal_fan_register(struct pmac_fan
*fan
)
195 struct pmac_fan_le
*list_entry
;
197 list_entry
= malloc(sizeof(struct pmac_fan_le
), M_PMACTHERM
,
199 list_entry
->fan
= fan
;
201 SLIST_INSERT_HEAD(&fans
, list_entry
, entries
);
205 pmac_thermal_sensor_register(struct pmac_therm
*sensor
)
207 struct pmac_sens_le
*list_entry
;
209 list_entry
= malloc(sizeof(struct pmac_sens_le
), M_PMACTHERM
,
211 list_entry
->sensor
= sensor
;
212 list_entry
->last_val
= 0;
213 list_entry
->critical_count
= 0;
215 SLIST_INSERT_HEAD(&sensors
, list_entry
, entries
);