1 /* $NetBSD: apple_smc_fan.c,v 1.6 2022/06/29 15:58:12 mlelstv Exp $ */
2
3 /*
4 * Apple System Management Controller: Fans
5 */
6
7 /*-
8 * Copyright (c) 2013 The NetBSD Foundation, Inc.
9 * All rights reserved.
10 *
11 * This code is derived from software contributed to The NetBSD Foundation
12 * by Taylor R. Campbell.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
24 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
27 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: apple_smc_fan.c,v 1.6 2022/06/29 15:58:12 mlelstv Exp $");
38
39 #include <sys/types.h>
40 #include <sys/param.h>
41 #include <sys/device.h>
42 #include <sys/kmem.h>
43 #include <sys/module.h>
44 #if 0 /* XXX sysctl */
45 #include <sys/sysctl.h>
46 #endif
47 #include <sys/systm.h>
48
49 #include <dev/ic/apple_smc.h>
50
51 #include <dev/sysmon/sysmonvar.h>
52
53 #define APPLE_SMC_NFANS_KEY "FNum"
54
55 static const struct fan_sensor {
56 const char *fs_name;
57 const char *fs_key_suffix;
58 } fan_sensors[] = {
59 { "actual", "Ac" },
60 { "minimum", "Mn" },
61 { "maximum", "Mx" },
62 { "safe", "Sf" },
63 { "target", "Tg" },
64 };
65
66 struct apple_smc_fan_softc {
67 device_t sc_dev;
68 struct apple_smc_tag *sc_smc;
69 struct sysmon_envsys *sc_sme;
70 uint8_t sc_nfans;
71 struct {
72 struct {
73 struct apple_smc_key *sensor_key;
74 struct envsys_data sensor_data;
75 } sensors[__arraycount(fan_sensors)];
76 } *sc_fans;
77
78 #if 0 /* XXX sysctl */
79 struct sysctllog *sc_sysctl_log;
80 const struct sysctlnode *sc_sysctl_node;
81 #endif
82 };
83
84 struct fan_desc {
85 uint8_t fd_type;
86 uint8_t fd_zone;
87 uint8_t fd_location;
88 uint8_t fd_reserved0;
89 char fd_name[12];
90 } __packed;
91
92 static int apple_smc_fan_match(device_t, cfdata_t, void *);
93 static void apple_smc_fan_attach(device_t, device_t, void *);
94 static int apple_smc_fan_detach(device_t, int);
95 static int apple_smc_fan_attach_sensors(struct apple_smc_fan_softc *);
96 static void apple_smc_fan_attach_sensor(struct apple_smc_fan_softc *,
97 uint8_t, const char *, uint8_t);
98 static void apple_smc_fan_refresh(struct sysmon_envsys *,
99 struct envsys_data *);
100 static void apple_smc_fan_release_keys(struct apple_smc_fan_softc *);
101 #if 0 /* XXX sysctl */
102 static int apple_smc_fan_sysctl_setup(struct apple_smc_fan_softc *);
103 static void apple_smc_fan_sysctl_setup_1(struct apple_smc_tag *,
104 uint8_t);
105 #endif
106
107 CFATTACH_DECL_NEW(apple_smc_fan, sizeof(struct apple_smc_fan_softc),
108 apple_smc_fan_match, apple_smc_fan_attach, apple_smc_fan_detach, NULL);
109
110 static int
apple_smc_fan_match(device_t parent,cfdata_t match,void * aux)111 apple_smc_fan_match(device_t parent, cfdata_t match, void *aux)
112 {
113 const struct apple_smc_attach_args *asa = aux;
114 struct apple_smc_key *nfans_key;
115 uint8_t nfans;
116 int rv = 0;
117 int error;
118
119 /* Find how to find how many fans there are. */
120 error = apple_smc_named_key(asa->asa_smc, APPLE_SMC_NFANS_KEY,
121 APPLE_SMC_TYPE_UINT8, &nfans_key);
122 if (error)
123 goto out0;
124
125 /* Find how many fans there are. */
126 error = apple_smc_read_key_1(asa->asa_smc, nfans_key, &nfans);
127 if (error)
128 goto out1;
129
130 /* Attach only if there's at least one fan. */
131 if (nfans > 0)
132 rv = 1;
133
134 out1: apple_smc_release_key(asa->asa_smc, nfans_key);
135 out0: return rv;
136 }
137
138 static void
apple_smc_fan_attach(device_t parent,device_t self,void * aux)139 apple_smc_fan_attach(device_t parent, device_t self, void *aux)
140 {
141 struct apple_smc_fan_softc *sc = device_private(self);
142 const struct apple_smc_attach_args *asa = aux;
143 struct apple_smc_key *nfans_key;
144 int error;
145
146 /* Identify ourselves. */
147 aprint_normal(": Apple SMC fan sensors\n");
148
149 /* Initialize the softc. */
150 sc->sc_dev = self;
151 sc->sc_smc = asa->asa_smc;
152
153 /* Find how to find how many fans there are. */
154 error = apple_smc_named_key(sc->sc_smc, APPLE_SMC_NFANS_KEY,
155 APPLE_SMC_TYPE_UINT8, &nfans_key);
156 if (error)
157 goto out0;
158
159 /* Find how many fans there are. */
160 error = apple_smc_read_key_1(sc->sc_smc, nfans_key, &sc->sc_nfans);
161 if (error)
162 goto out1;
163
164 /*
165 * There should be at least one, but just in case the hardware
166 * changed its mind in the interim...
167 */
168 if (sc->sc_nfans == 0) {
169 aprint_error_dev(self, "no fans\n");
170 goto out1;
171 }
172
173 /*
174 * The number of fans must fit in a single decimal digit for
175 * the names of the fan keys; see the fan_sensor table above.
176 */
177 if (sc->sc_nfans >= 10) {
178 aprint_error_dev(self, "too many fans: %"PRIu8"\n",
179 sc->sc_nfans);
180 sc->sc_nfans = 9;
181 }
182
183 #if 0 /* XXX sysctl */
184 /* Set up the sysctl tree for controlling the fans. */
185 error = apple_smc_fan_sysctl_setup(sc);
186 if (error)
187 goto fail0;
188 #endif
189
190 /* Attach the sensors to sysmon_envsys. */
191 error = apple_smc_fan_attach_sensors(sc);
192 if (error)
193 goto fail1;
194
195 /* Success! */
196 goto out1;
197
198 #if 0
199 fail2:
200 apple_smc_fan_detach_sensors(sc);
201 #endif
202
203 fail1:
204 #if 0 /* XXX sysctl */
205 sysctl_teardown(&sc->sc_sysctl_log);
206 fail0:
207 #endif
208
209 out1: apple_smc_release_key(sc->sc_smc, nfans_key);
210 out0: return;
211 }
212
213 static int
apple_smc_fan_detach(device_t self,int flags)214 apple_smc_fan_detach(device_t self, int flags)
215 {
216 struct apple_smc_fan_softc *sc = device_private(self);
217
218 /* If we registered with sysmon_envsys, unregister. */
219 if (sc->sc_sme != NULL) {
220 sysmon_envsys_unregister(sc->sc_sme);
221
222 KASSERT(sc->sc_fans != NULL);
223 KASSERT(sc->sc_nfans > 0);
224 KASSERT(sc->sc_nfans < 10);
225
226 /* Release the keys and free the memory for fan records. */
227 apple_smc_fan_release_keys(sc);
228 kmem_free(sc->sc_fans,
229 (sizeof(sc->sc_fans[0]) * sc->sc_nfans));
230 sc->sc_fans = NULL;
231 sc->sc_nfans = 0;
232 }
233
234 #if 0 /* XXX sysctl */
235 /* Tear down all the sysctl knobs we set up. */
236 sysctl_teardown(&sc->sc_sysctl_log);
237 #endif
238
239 return 0;
240 }
241
242 static int
apple_smc_fan_attach_sensors(struct apple_smc_fan_softc * sc)243 apple_smc_fan_attach_sensors(struct apple_smc_fan_softc *sc)
244 {
245 uint8_t fan, sensor;
246 char fan_desc_key_name[4 + 1];
247 struct apple_smc_key *fan_desc_key;
248 struct fan_desc fan_desc;
249 char name[sizeof(fan_desc.fd_name) + 1];
250 int error;
251
252 /* Create a sysmon_envsys record, but don't register it yet. */
253 sc->sc_sme = sysmon_envsys_create();
254 sc->sc_sme->sme_name = device_xname(sc->sc_dev);
255 sc->sc_sme->sme_cookie = sc;
256 sc->sc_sme->sme_refresh = apple_smc_fan_refresh;
257
258 /* Create an array of fan sensor records. */
259 CTASSERT(10 <= (SIZE_MAX / sizeof(sc->sc_fans[0])));
260 sc->sc_fans = kmem_zalloc((sizeof(sc->sc_fans[0]) * sc->sc_nfans),
261 KM_SLEEP);
262
263 /* Find all the fans. */
264 for (fan = 0; fan < sc->sc_nfans; fan++) {
265
266 /* Format the name of the key for the fan's description. */
267 (void)snprintf(fan_desc_key_name, sizeof(fan_desc_key_name),
268 "F%"PRIu8"ID", fan);
269 KASSERT(4 == strlen(fan_desc_key_name));
270
271 /* Look up the key for this fan's description. */
272 error = apple_smc_named_key(sc->sc_smc, fan_desc_key_name,
273 APPLE_SMC_TYPE_FANDESC, &fan_desc_key);
274 if (error) {
275 aprint_error_dev(sc->sc_dev,
276 "error identifying fan %"PRIu8": %d\n",
277 fan, error);
278 continue;
279 }
280
281 /* Read the description of this fan. */
282 error = apple_smc_read_key(sc->sc_smc, fan_desc_key, &fan_desc,
283 sizeof(fan_desc));
284 if (error) {
285 aprint_error_dev(sc->sc_dev,
286 "error identifying fan %"PRIu8": %d\n",
287 fan, error);
288 continue;
289 }
290
291 /*
292 * XXX Do more with the fan description...
293 */
294
295 /* Make a null-terminated copy of this fan's description. */
296 (void)memcpy(name, fan_desc.fd_name, sizeof(fan_desc.fd_name));
297 name[sizeof(fan_desc.fd_name)] = '\0';
298
299 /* Attach all the sensors for this fan. */
300 for (sensor = 0; sensor < __arraycount(fan_sensors); sensor++)
301 apple_smc_fan_attach_sensor(sc, fan, name, sensor);
302
303 #if 0 /* XXX sysctl */
304 /* Attach sysctl knobs to control this fan. */
305 apple_smc_fan_sysctl_setup_1(sc, fan);
306 #endif
307 }
308
309 /* Fan sensors are all attached. Register with sysmon_envsys now. */
310 error = sysmon_envsys_register(sc->sc_sme);
311 if (error)
312 goto fail;
313
314 /* Success! */
315 error = 0;
316 goto out;
317
318 fail: sysmon_envsys_destroy(sc->sc_sme);
319 sc->sc_sme = NULL;
320 out: return error;
321 }
322
323 static void
apple_smc_fan_attach_sensor(struct apple_smc_fan_softc * sc,uint8_t fan,const char * name,uint8_t sensor)324 apple_smc_fan_attach_sensor(struct apple_smc_fan_softc *sc, uint8_t fan,
325 const char *name, uint8_t sensor)
326 {
327 char key_name[4 + 1];
328 struct apple_smc_key **keyp;
329 struct envsys_data *edata;
330 int error;
331
332 KASSERT(fan < sc->sc_nfans);
333 KASSERT(sensor < __arraycount(fan_sensors));
334
335 /* Format the name of the key for this fan sensor. */
336 (void)snprintf(key_name, sizeof(key_name), "F%d%s",
337 (int)sensor, fan_sensors[sensor].fs_key_suffix);
338 KASSERT(strlen(key_name) == 4);
339
340 /* Look up the key for this fan sensor. */
341 keyp = &sc->sc_fans[fan].sensors[sensor].sensor_key;
342 error = apple_smc_named_key(sc->sc_smc, key_name, APPLE_SMC_TYPE_FPE2,
343 keyp);
344 if (error)
345 goto fail0;
346
347 /* Initialize the envsys_data record for this fan sensor. */
348 edata = &sc->sc_fans[fan].sensors[sensor].sensor_data;
349 edata->units = ENVSYS_SFANRPM;
350 edata->state = ENVSYS_SINVALID;
351 edata->flags = ENVSYS_FHAS_ENTROPY;
352 (void)snprintf(edata->desc, sizeof(edata->desc), "fan %s %s speed",
353 name, fan_sensors[sensor].fs_name);
354
355 /* Attach this fan sensor to sysmon_envsys. */
356 error = sysmon_envsys_sensor_attach(sc->sc_sme, edata);
357 if (error)
358 goto fail1;
359
360 /* Success! */
361 return;
362
363 fail1: apple_smc_release_key(sc->sc_smc, *keyp);
364 fail0: *keyp = NULL;
365 aprint_error_dev(sc->sc_dev,
366 "failed to attach fan %s %s speed sensor: %d\n",
367 name, fan_sensors[sensor].fs_name, error);
368 }
369
370 static void
apple_smc_fan_refresh(struct sysmon_envsys * sme,struct envsys_data * edata)371 apple_smc_fan_refresh(struct sysmon_envsys *sme, struct envsys_data *edata)
372 {
373 struct apple_smc_fan_softc *sc = sme->sme_cookie;
374 uint8_t fan, sensor;
375 struct apple_smc_key *key;
376 uint16_t rpm;
377 int error;
378
379 /* Sanity-check the sensor number out of paranoia. */
380 CTASSERT(10 <= (SIZE_MAX / __arraycount(fan_sensors)));
381 KASSERT(sc->sc_nfans < 10);
382 if (edata->sensor >= (sc->sc_nfans * __arraycount(fan_sensors))) {
383 aprint_error_dev(sc->sc_dev, "unknown sensor %"PRIu32"\n",
384 edata->sensor);
385 return;
386 }
387
388 /* Pick apart the fan number and its sensor number. */
389 fan = (edata->sensor / __arraycount(fan_sensors));
390 sensor = (edata->sensor % __arraycount(fan_sensors));
391
392 KASSERT(fan < sc->sc_nfans);
393 KASSERT(sensor < __arraycount(fan_sensors));
394 KASSERT(edata == &sc->sc_fans[fan].sensors[sensor].sensor_data);
395
396 /*
397 * If we're refreshing, this sensor got attached, so we ought
398 * to have a sensor key. Grab it.
399 */
400 key = sc->sc_fans[fan].sensors[sensor].sensor_key;
401 KASSERT(key != NULL);
402
403 /* Read the fan sensor value, in rpm. */
404 error = apple_smc_read_key_2(sc->sc_smc, key, &rpm);
405 if (error) {
406 aprint_error_dev(sc->sc_dev,
407 "failed to read fan %d %s speed: %d\n",
408 fan, fan_sensors[sensor].fs_name, error);
409 edata->state = ENVSYS_SINVALID;
410 return;
411 }
412
413 /* Success! */
414 edata->value_cur = rpm;
415 edata->state = ENVSYS_SVALID;
416 }
417
418 static void
apple_smc_fan_release_keys(struct apple_smc_fan_softc * sc)419 apple_smc_fan_release_keys(struct apple_smc_fan_softc *sc)
420 {
421 uint8_t fan, sensor;
422
423 for (fan = 0; fan < sc->sc_nfans; fan++) {
424 for (sensor = 0;
425 sensor < __arraycount(fan_sensors);
426 sensor++) {
427 struct apple_smc_key **const keyp =
428 &sc->sc_fans[fan].sensors[sensor].sensor_key;
429 if (*keyp != NULL) {
430 apple_smc_release_key(sc->sc_smc, *keyp);
431 *keyp = NULL;
432 }
433 }
434 }
435 }
436
437 #if 0 /* XXX sysctl */
438 static int
439 apple_smc_fan_sysctl_setup(struct apple_smc_fan_softc *sc)
440 {
441 ...
442 }
443
444 static void
445 apple_smc_fan_sysctl_setup_1(struct apple_smc_fan_softc *sc, uint8_t fan)
446 {
447 }
448 #endif
449
450 MODULE(MODULE_CLASS_DRIVER, apple_smc_fan, "apple_smc,sysmon_envsys");
451
452 #ifdef _MODULE
453 #include "ioconf.c"
454 #endif
455
456 static int
apple_smc_fan_modcmd(modcmd_t cmd,void * arg __unused)457 apple_smc_fan_modcmd(modcmd_t cmd, void *arg __unused)
458 {
459 #ifdef _MODULE
460 int error;
461 #endif
462
463 switch (cmd) {
464 case MODULE_CMD_INIT:
465 #ifdef _MODULE
466 error = config_init_component(cfdriver_ioconf_apple_smc_fan,
467 cfattach_ioconf_apple_smc_fan,
468 cfdata_ioconf_apple_smc_fan);
469 if (error)
470 return error;
471 #endif
472 return 0;
473
474 case MODULE_CMD_FINI:
475 #ifdef _MODULE
476 error = config_fini_component(cfdriver_ioconf_apple_smc_fan,
477 cfattach_ioconf_apple_smc_fan,
478 cfdata_ioconf_apple_smc_fan);
479 if (error)
480 return error;
481 #endif
482 return 0;
483
484 default:
485 return ENOTTY;
486 }
487 }
488