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 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 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 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 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 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 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 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 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