/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2026 Abdelkader Boudih * * $FreeBSD: head/sys/dev/asmc/asmc.c$ * * Ported from FreeBSD's asmc(4) driver. * * DragonFlyBSD port: adapted for kmalloc/kfree, lockmgr 2-arg form, * taskqueue_start_threads ncpu arg, sys/bus_resource.h, acpica paths. * Added MMIO (T2) backend support. */ /* * Driver for Apple's System Management Controller (SMC). * Supports ISA I/O port (Intel Macs) and MMIO (T2 Macs). * * Inspired by the Linux applesmc driver. * * This file: probe/attach/detach, ACPI device methods, init, * capability/sensor detection, module glue (DRIVER_MODULE, MODULE_DEPEND). */ #include "smc.h" #include #include #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("APPLE_SMC") static int apple_smc_probe(device_t); static int apple_smc_attach(device_t); static int apple_smc_detach(device_t); static int apple_smc_suspend(device_t); static int apple_smc_resume(device_t); static int apple_smc_init(device_t); static void apple_smc_detect_capabilities(device_t); static int apple_smc_detect_sensors(device_t); static void apple_smc_check_prochot(device_t); static void apple_smc_fan_override_headless(device_t); static const struct { const char *key; const char *desc; } apple_smc_temp_descs[] = { /* Ambient / airflow */ { "TA0P", "Ambient" }, { "TA0S", "PCIe Slot 1 Ambient" }, { "TA0p", "Ambient Air" }, { "TA0V", "Ambient" }, { "TA1P", "Ambient 2" }, { "TA1S", "PCIe Slot 1 PCB" }, { "TA1p", "Ambient Air 2" }, { "TA2P", "Ambient 3" }, { "TA2S", "PCIe Slot 2 Ambient" }, { "TA3S", "PCIe Slot 2 PCB" }, { "TALP", "Ambient Light Proximity" }, { "TaLC", "Airflow Left" }, { "TaRC", "Airflow Right" }, { "Ta0P", "Airflow Proximity" }, /* Battery / enclosure */ { "TB0T", "Enclosure Bottom" }, { "TB1T", "Battery 1" }, { "TB2T", "Battery 2" }, { "TB3T", "Battery 3" }, { "TBXT", "Battery" }, { "Tb0P", "BLC Proximity" }, /* CPU */ { "TC0C", "CPU Core 1" }, { "TC0D", "CPU Die" }, { "TC0E", "CPU 1" }, { "TC0F", "CPU 2" }, { "TC0G", "CPU Package GPU" }, { "TC0H", "CPU Heatsink" }, { "TC0h", "CPU Heatsink" }, { "TC0J", "CPU" }, { "TC0P", "CPU Proximity" }, { "TC0c", "CPU Core 1 PECI" }, { "TC0d", "CPU Die PECI" }, { "TC0p", "CPU Proximity" }, { "TC1C", "CPU Core 2" }, { "TC1c", "CPU Core 2 PECI" }, { "TC1P", "CPU Proximity 2" }, { "TC2C", "CPU Core 3" }, { "TC2c", "CPU Core 3 PECI" }, { "TC2P", "CPU Proximity 3" }, { "TC3C", "CPU Core 4" }, { "TC3c", "CPU Core 4 PECI" }, { "TC3P", "CPU Proximity 4" }, { "TC4C", "CPU Core 5" }, { "TC5C", "CPU Core 6" }, { "TC6C", "CPU Core 7" }, { "TC7C", "CPU Core 8" }, { "TC8C", "CPU Core 9" }, { "TCGC", "PECI GPU" }, { "TCGc", "PECI GPU" }, { "TCHP", "Charger Proximity" }, { "TCSA", "PECI SA" }, { "TCSC", "PECI SA" }, { "TCSc", "PECI SA" }, { "TCTD", "CPU DTS" }, { "TCXC", "PECI CPU" }, { "TCXc", "PECI CPU" }, { "TCPG", "CPU Package GPU" }, { "TCXR", "CPU PECI DTS" }, /* CPU dual-socket (Mac Pro) */ { "TCAG", "CPU A Package" }, { "TCAH", "CPU A Heatsink" }, { "TCBG", "CPU B Package" }, { "TCBH", "CPU B Heatsink" }, /* GPU */ { "TG0C", "GPU Core" }, { "TG0D", "GPU Diode" }, { "TG0H", "GPU Heatsink" }, { "TG0M", "GPU Memory" }, { "TG0P", "GPU Proximity" }, { "TG0T", "GPU Diode" }, { "TG0V", "GPU" }, { "TG0d", "GPU Die" }, { "TG0h", "GPU Heatsink" }, { "TG0p", "GPU Proximity" }, { "TGTV", "GPU" }, { "TG1D", "GPU 2 Diode" }, { "TG1H", "GPU 2 Heatsink" }, { "TG1P", "GPU 2 Proximity" }, { "TG1d", "GPU 2 Die" }, { "TGVP", "GPU Memory Proximity" }, /* Storage */ { "TH0A", "SSD A" }, { "TH0B", "SSD B" }, { "TH0C", "SSD C" }, { "TH0F", "SSD" }, { "TH0O", "HDD" }, { "TH0P", "HDD Proximity" }, { "TH0R", "SSD" }, { "TH0V", "SSD" }, { "TH0a", "SSD A" }, { "TH0b", "SSD B" }, { "TH0c", "SSD C" }, { "TH1O", "HDD 2" }, { "TH1P", "HDD Bay 2" }, { "TH2P", "HDD Bay 3" }, { "TH3P", "HDD Bay 4" }, { "Th0H", "Heatpipe 1" }, { "Th0N", "SSD" }, { "Th1H", "Heatpipe 2" }, { "Th2H", "Heatpipe 3" }, /* Thunderbolt */ { "THSP", "Thunderbolt Proximity" }, { "TI0P", "Thunderbolt 1" }, { "TI0p", "Thunderbolt 1" }, { "TI1P", "Thunderbolt 2" }, { "TI1p", "Thunderbolt 2" }, { "TTLD", "Thunderbolt Left" }, { "TTRD", "Thunderbolt Right" }, { "Te0T", "Thunderbolt Diode" }, { "Te0t", "Thunderbolt Diode" }, /* LCD */ { "TL0P", "LCD Proximity" }, { "TL0V", "LCD" }, { "TL0p", "LCD Proximity" }, { "TL1P", "LCD Panel 1" }, { "TL1V", "LCD 1" }, { "TL1p", "LCD Panel 1" }, { "TL1v", "LCD 1" }, { "TL2V", "LCD 2" }, { "TLAV", "LCD" }, { "TLBV", "LCD" }, { "TLCV", "LCD" }, /* Memory */ { "TM0P", "Memory Proximity" }, { "TM0S", "Memory Slot 1" }, { "TM0p", "Memory Proximity" }, { "TM1P", "Memory Riser A 2" }, { "TM1S", "Memory Slot 2" }, { "Tm0P", "Memory Proximity" }, { "Tm0p", "Memory Proximity" }, { "Tm1P", "Memory Proximity 2" }, { "TMBS", "Memory Bank" }, { "TMCD", "Memory DIMM" }, /* Northbridge / MCH */ { "TN0C", "Northbridge Core" }, { "TN0D", "Northbridge Diode" }, { "TN0H", "MCH Heatsink" }, { "TN0P", "Northbridge Proximity" }, { "TN1D", "MCH Die 2" }, { "TN1P", "Northbridge Proximity 2" }, /* PCH */ { "TP0P", "PCH Proximity" }, { "TP0p", "PCH Proximity" }, { "TPCD", "PCH Die" }, { "TPCd", "PCH Die" }, /* Optical drive */ { "TO0P", "Optical Drive" }, { "TO0p", "Optical Drive" }, /* Power supply */ { "Tp0C", "Power Supply" }, { "Tp0P", "Power Supply Proximity" }, { "Tp1C", "Power Supply 2" }, { "Tp1P", "Power Supply Component" }, { "Tp1p", "Power Supply Component" }, { "Tp2P", "Power Supply 2" }, { "Tp2h", "Power Supply 2" }, { "Tp2H", "Power Supply 2" }, { "Tp3P", "Power Supply 3 Inlet" }, { "Tp3h", "Power Supply 3" }, { "Tp3H", "Power Supply 3" }, { "Tp4P", "Power Supply 4" }, { "Tp5P", "Power Supply 5" }, /* Palm rest / trackpad */ { "Ts0P", "Palm Rest" }, { "Ts0S", "Memory Proximity" }, { "Ts1P", "Palm Rest 2" }, { "Ts1S", "Palm Rest 2" }, /* Wireless */ { "TW0P", "Wireless Proximity" }, { "TW0p", "Wireless Proximity" }, { "TBLR", "Bluetooth" }, /* Camera */ { "TS2P", "Camera Proximity" }, { "TS2V", "Camera" }, { "TS2p", "Camera Proximity" }, /* Expansion */ { "TS0C", "Expansion Slots" }, { "TS0P", "Expansion Proximity" }, { "TS0V", "Expansion" }, { "TS0p", "Expansion Proximity" }, /* Air vent */ { "TV0P", "Air Vent" }, /* VRM */ { "Tv0S", "VRM 1" }, { "Tv1S", "VRM 2" }, /* Misc */ { "TTF0", "Fan" }, { "TMLB", "Logic Board" }, }; static const char * apple_smc_temp_desc(const char *key) { unsigned int i; for (i = 0; i < nitems(apple_smc_temp_descs); i++) { if (strcmp(apple_smc_temp_descs[i].key, key) == 0) return (apple_smc_temp_descs[i].desc); } return ("Temperature"); } static device_method_t apple_smc_methods[] = { DEVMETHOD(device_probe, apple_smc_probe), DEVMETHOD(device_attach, apple_smc_attach), DEVMETHOD(device_detach, apple_smc_detach), DEVMETHOD(device_suspend, apple_smc_suspend), DEVMETHOD(device_resume, apple_smc_resume), DEVMETHOD_END }; static driver_t apple_smc_driver = { "apple_smc", apple_smc_methods, sizeof(struct apple_smc_softc), .gpri = KOBJ_GPRI_ACPI }; static devclass_t apple_smc_devclass; static char *apple_smc_ids[] = { "APP0001", NULL }; DRIVER_MODULE(apple_smc, acpi, apple_smc_driver, apple_smc_devclass, NULL, NULL); MODULE_DEPEND(apple_smc, acpi, 1, 1, 1); MODULE_VERSION(apple_smc, 1); static int apple_smc_probe(device_t dev) { char *product; char buf[64]; if (acpi_disabled("apple_smc")) return (ENXIO); if (ACPI_ID_PROBE(device_get_parent(dev), dev, apple_smc_ids) == NULL) return (ENXIO); /* Singleton: only unit 0. */ if (device_get_unit(dev) != 0) return (ENXIO); product = kgetenv("smbios.system.product"); ksnprintf(buf, sizeof(buf), "Apple %s", product ? product : "SMC"); device_set_desc_copy(dev, buf); if (product) kfreeenv(product); return (0); } static int apple_smc_attach(device_t dev) { int i, j; int ret; char name[2]; struct apple_smc_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *sysctlctx; struct sysctl_oid *sysctlnode; /* Lock must be initialized before any backend probe */ lockinit(&sc->sc_lock, "apple_smc", 0, LK_CANRECURSE); /* Try MMIO first */ sc->sc_rid_mem = 0; sc->sc_iomem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_rid_mem, RF_ACTIVE); if (sc->sc_iomem != NULL) { if (apple_smc_try_enable_mmio(dev) == 0) { sc->sc_is_mmio = 1; device_printf(dev, "using MMIO backend%s\n", sc->sc_is_t2 ? " (T2)" : ""); } else { bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid_mem, sc->sc_iomem); sc->sc_iomem = NULL; } } if (!sc->sc_is_mmio) { sc->sc_ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->sc_rid_port, RF_ACTIVE); if (sc->sc_ioport == NULL) { device_printf(dev, "unable to allocate IO port\n"); ret = ENOMEM; goto err; } } sysctlctx = device_get_sysctl_ctx(dev); sysctlnode = device_get_sysctl_tree(dev); ret = apple_smc_init(dev); if (ret != 0) { device_printf(dev, "SMC not responding\n"); goto err; } /* Read Tj,max from MSR 0x1A2 bits[23:16] (Intel Sandy Bridge+). */ sc->sc_tj_max = 100; /* safe default */ { uint64_t msr; if (rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &msr) == 0) { int tj = (int)((msr >> 16) & 0xff); if (tj > 0) sc->sc_tj_max = tj; } } apple_smc_detect_capabilities(dev); apple_smc_detect_sensors(dev); /* Thermal governor defaults (millidegrees). */ sc->sc_thermal_interval = 5; sc->sc_thermal_temp_low = 55000; sc->sc_thermal_temp_high = 80000; sc->sc_thermal_hysteresis = 3000; sc->sc_thermal_emergency = 90000; sc->sc_thermal_enabled = 0; apple_smc_check_prochot(dev); /* fan tree */ sc->sc_fan_tree[0] = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "fan", CTLFLAG_RD, 0, "Fan Root Tree"); { /* * Per-fan sysctl table. arg2_key encodes the key index * for apple_smc_mb_sysctl_fanrw in bits [15:8]; the fan * number is OR'd into bits [7:0] at registration time. * Entries with arg2_key == -1 pass fan index directly. */ static const struct { const char *name; int type; int arg2_key; int (*handler)(SYSCTL_HANDLER_ARGS); const char *desc; int need_safespeed; } fan_sysctls[] = { { "id", CTLTYPE_STRING | CTLFLAG_RD, -1, apple_smc_mb_sysctl_fanid, "Fan ID", 0 }, { "speed", CTLTYPE_INT | CTLFLAG_RD, -1, apple_smc_mb_sysctl_fanspeed, "Fan speed in RPM", 0 }, { "safespeed", CTLTYPE_INT | CTLFLAG_RD, -1, apple_smc_mb_sysctl_fansafespeed, "Fan safe speed in RPM", 1 }, { "minspeed", CTLTYPE_INT | CTLFLAG_RW, 0 << 8, apple_smc_mb_sysctl_fanrw, "Fan minimum speed in RPM", 0 }, { "maxspeed", CTLTYPE_INT | CTLFLAG_RW, 1 << 8, apple_smc_mb_sysctl_fanrw, "Fan maximum speed in RPM", 0 }, { "targetspeed", CTLTYPE_INT | CTLFLAG_RW, 2 << 8, apple_smc_mb_sysctl_fanrw, "Fan target speed in RPM", 0 }, { "manual", CTLTYPE_INT | CTLFLAG_RW, -1, apple_smc_mb_sysctl_fanmanual, "Fan manual mode (0=auto, 1=manual)", 0 }, }; unsigned int fi; for (i = 1; i <= sc->sc_nfan; i++) { j = i - 1; name[0] = '0' + j; name[1] = 0; sc->sc_fan_tree[i] = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[0]), OID_AUTO, name, CTLFLAG_RD, 0, "Fan Subtree"); for (fi = 0; fi < nitems(fan_sysctls); fi++) { if (fan_sysctls[fi].need_safespeed && !sc->sc_has_safespeed) continue; SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[i]), OID_AUTO, fan_sysctls[fi].name, fan_sysctls[fi].type, dev, fan_sysctls[fi].arg2_key == -1 ? j : (fan_sysctls[fi].arg2_key | j), fan_sysctls[fi].handler, "I", fan_sysctls[fi].desc); } } } /* temp tree */ sc->sc_temp_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "temp", CTLFLAG_RD, 0, "Temperature sensors"); for (i = 0; i < sc->sc_temp_count; i++) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_temp_tree), OID_AUTO, sc->sc_temp_sensors[i], CTLTYPE_INT | CTLFLAG_RD, dev, i, apple_smc_temp_sysctl, "I", apple_smc_temp_desc(sc->sc_temp_sensors[i])); } /* DTS (distance-to-Tj,max) sensors, decoded as absolute temps */ if (sc->sc_dts_count > 0) { struct sysctl_oid *dts_tree; dts_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sc->sc_temp_tree), OID_AUTO, "dts", CTLFLAG_RD, 0, "CPU DTS temperature sensors"); for (i = 0; i < sc->sc_dts_count; i++) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(dts_tree), OID_AUTO, sc->sc_dts_sensors[i], CTLTYPE_INT | CTLFLAG_RD, dev, i, apple_smc_dts_sysctl, "I", apple_smc_temp_desc(sc->sc_dts_sensors[i])); } } /* light tree */ if (sc->sc_has_light) { sc->sc_light_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "light", CTLFLAG_RD, 0, "Keyboard backlight sensors"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_light_tree), OID_AUTO, "left", CTLTYPE_INT | CTLFLAG_RD, dev, 0, sc->sc_light_len == ASMC_LIGHT_LONGLEN ? apple_smc_mbp_sysctl_light_left_10byte : apple_smc_mbp_sysctl_light, "I", "Keyboard backlight left sensor"); if (sc->sc_light_len != ASMC_LIGHT_LONGLEN && apple_smc_key_getinfo(dev, ASMC_KEY_LIGHTRIGHT, NULL, NULL) == 0) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_light_tree), OID_AUTO, "right", CTLTYPE_INT | CTLFLAG_RD, dev, 1, apple_smc_mbp_sysctl_light, "I", "Keyboard backlight right sensor"); } SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_light_tree), OID_AUTO, "control", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, dev, 0, apple_smc_mbp_sysctl_light_control, "I", "Keyboard backlight brightness control"); } sc->sc_raw_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "raw", CTLFLAG_RD, 0, "Raw SMC key access"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_raw_tree), OID_AUTO, "key", CTLTYPE_STRING | CTLFLAG_RW, dev, 0, apple_smc_raw_key_sysctl, "A", "SMC key name (4 chars)"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_raw_tree), OID_AUTO, "value", CTLTYPE_STRING | CTLFLAG_RW, dev, 0, apple_smc_raw_value_sysctl, "A", "SMC key value (hex)"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_raw_tree), OID_AUTO, "len", CTLTYPE_INT | CTLFLAG_RD, dev, 0, apple_smc_raw_len_sysctl, "I", "SMC key value length"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_raw_tree), OID_AUTO, "type", CTLTYPE_STRING | CTLFLAG_RD, dev, 0, apple_smc_raw_type_sysctl, "A", "SMC key type (4 chars)"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_raw_tree), OID_AUTO, "index", CTLTYPE_INT | CTLFLAG_RW, dev, 0, apple_smc_raw_index_sysctl, "I", "Look up SMC key by index (0..nkeys-1)"); /* battery charge limit (T2) */ if (sc->sc_is_t2 && apple_smc_key_getinfo(dev, ASMC_KEY_BCLM, NULL, NULL) == 0) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "battery_charge_limit", CTLTYPE_INT | CTLFLAG_RW, dev, 0, apple_smc_bclm_sysctl, "I", "Battery charge limit (0-100)"); } /* system state subtree */ { struct sysctl_oid *sys_tree; sys_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "system", CTLFLAG_RD, 0, "System state and board identity"); if (apple_smc_key_getinfo(dev, ASMC_KEY_MSSD, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "shutdown_cause", CTLTYPE_STRING | CTLFLAG_RD, dev, 0, apple_smc_cause_sysctl, "A", "Last shutdown cause (MSSD)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_MSSP, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "sleep_cause", CTLTYPE_STRING | CTLFLAG_RD, dev, 1, apple_smc_cause_sysctl, "A", "Last sleep cause (MSSP)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_MSAL, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "thermal_status", CTLTYPE_STRING | CTLFLAG_RD, dev, 0, apple_smc_msal_sysctl, "A", "Thermal subsystem status (MSAL)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_CLKT, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "time_of_day", CTLTYPE_UINT | CTLFLAG_RD, dev, 0, apple_smc_clkt_sysctl, "IU", "Seconds since midnight (CLKT)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_CLWK, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "wake_time", CTLTYPE_UINT | CTLFLAG_RW, dev, 0, apple_smc_clwk_sysctl, "IU", "Scheduled wake time: seconds since midnight (0=disabled)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_MSPS, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "power_state", CTLTYPE_UINT | CTLFLAG_RD, dev, 0, apple_smc_msps_sysctl, "IU", "SMC power state index (MSPS)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_RPLT, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "board_id", CTLTYPE_STRING | CTLFLAG_RD, dev, 0, apple_smc_rplt_sysctl, "A", "Apple board codename (RPlt)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_RGEN, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "chip_gen", CTLTYPE_UINT | CTLFLAG_RD, dev, 0, apple_smc_rgen_sysctl, "IU", "Apple chip generation (RGEN; 3=T2)"); if (sc->sc_prochot_override) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "prochot_override", CTLTYPE_INT | CTLFLAG_RW, dev, 0, apple_smc_prochot_override_sysctl, "I", "BD_PROCHOT override (1=CPU ignores SMC PROCHOT#)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_G3AO, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "g3_poweron_time", CTLTYPE_UINT | CTLFLAG_RW, dev, 0, apple_smc_g3ao_sysctl, "IU", "G3 scheduled power-on: Unix timestamp (0=disabled)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_G3WD, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "g3_watchdog", CTLTYPE_INT | CTLFLAG_RW, dev, (intptr_t)ASMC_KEY_G3WD, apple_smc_flag_sysctl, "I", "G3 watchdog flag (paired with g3_poweron_time)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_AUWT, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "ac_wake_timer", CTLTYPE_UINT | CTLFLAG_RW, dev, 0, apple_smc_auwt_sysctl, "IU", "AC wake timer: delay (seconds) before AUPO power-on"); if (apple_smc_key_getinfo(dev, ASMC_KEY_DPBR, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "display_brightness_raw", CTLTYPE_UINT | CTLFLAG_RD, dev, 0, apple_smc_dpbr_sysctl, "IU", "Display brightness raw SMC value (DPBR)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_ENV0, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "chassis_state", CTLTYPE_UINT | CTLFLAG_RD, dev, 0, apple_smc_env0_sysctl, "IU", "Chassis/environment state byte (ENV0)"); if (apple_smc_key_getinfo(dev, ASMC_KEY_STFD, NULL, NULL) == 0) SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sys_tree), OID_AUTO, "sw_thermal_throttle_disable", CTLTYPE_INT | CTLFLAG_RW, dev, (intptr_t)ASMC_KEY_STFD, apple_smc_flag_sysctl, "I", "Software thermal throttle disable flag (STFD)"); } /* Thermal governor -- only when PROCHOT is overridden and fans exist. */ if (sc->sc_prochot_override && sc->sc_fan_manual_mask != 0) { struct sysctl_oid *th_tree; th_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "thermal", CTLFLAG_RW, 0, "Thermal fan governor"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(th_tree), OID_AUTO, "enabled", CTLTYPE_INT | CTLFLAG_RW, dev, 0, apple_smc_thermal_enabled_sysctl, "I", "Enable thermal fan curve (0=off, 1=on)"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(th_tree), OID_AUTO, "interval", CTLTYPE_INT | CTLFLAG_RW, &sc->sc_thermal_interval, 0, apple_smc_thermal_int_sysctl, "I", "Poll interval in seconds"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(th_tree), OID_AUTO, "temp_low", CTLTYPE_INT | CTLFLAG_RW, &sc->sc_thermal_temp_low, 0, apple_smc_thermal_int_sysctl, "I", "Temperature (mC) below which fans run at minimum"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(th_tree), OID_AUTO, "temp_high", CTLTYPE_INT | CTLFLAG_RW, &sc->sc_thermal_temp_high, 0, apple_smc_thermal_int_sysctl, "I", "Temperature (mC) above which fans run at maximum"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(th_tree), OID_AUTO, "hysteresis", CTLTYPE_INT | CTLFLAG_RW, &sc->sc_thermal_hysteresis, 0, apple_smc_thermal_int_sysctl, "I", "Temperature deadband (mC) to prevent oscillation"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(th_tree), OID_AUTO, "emergency", CTLTYPE_INT | CTLFLAG_RW, &sc->sc_thermal_emergency, 0, apple_smc_thermal_int_sysctl, "I", "Emergency temperature (mC)"); sc->sc_thermal_enabled = 1; if (apple_smc_thermal_start(dev) != 0) { device_printf(dev, "thermal: failed to start governor, " "fans to max for safety\n"); apple_smc_thermal_fans_to_max(dev); } else { device_printf(dev, "thermal: governor started " "(low=%dC high=%dC emergency=%dC interval=%ds)\n", sc->sc_thermal_temp_low / 1000, sc->sc_thermal_temp_high / 1000, sc->sc_thermal_emergency / 1000, sc->sc_thermal_interval); } } if (!sc->sc_has_sms) goto nosms; apple_smc_sms_init(dev); sc->sc_sms_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sms", CTLFLAG_RD, 0, "Sudden Motion Sensor"); { static const char *sms_names[] = { "x", "y", "z" }; static const char *sms_descs[] = { "Sudden Motion Sensor X value", "Sudden Motion Sensor Y value", "Sudden Motion Sensor Z value", }; unsigned int si; for (si = 0; si < nitems(sms_names); si++) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_sms_tree), OID_AUTO, sms_names[si], CTLTYPE_INT | CTLFLAG_RD, dev, si, apple_smc_mb_sysctl_sms, "I", sms_descs[si]); } } sc->sc_sms_tq = NULL; TASK_INIT(&sc->sc_sms_task, 0, apple_smc_sms_task, sc); sc->sc_sms_tq = taskqueue_create("apple_smc_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->sc_sms_tq); taskqueue_start_threads(&sc->sc_sms_tq, 1, TDPRI_INT_HIGH, -1, "%s sms taskq", device_get_nameunit(dev)); sc->sc_rid_irq = 0; sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_rid_irq, RF_ACTIVE); if (sc->sc_irq == NULL) { device_printf(dev, "unable to allocate IRQ resource\n"); ret = ENXIO; goto err; } ret = bus_setup_intr(dev, sc->sc_irq, INTR_MPSAFE, apple_smc_sms_intr, dev, &sc->sc_cookie, NULL); if (ret) { device_printf(dev, "unable to setup SMS IRQ\n"); goto err; } nosms: return (0); err: apple_smc_detach(dev); return (ret); } static void apple_smc_free_sensors(char **sensors, int count) { int i; for (i = 0; i < count; i++) kfree(sensors[i], M_DEVBUF); } static int apple_smc_detach(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); apple_smc_thermal_stop(dev); /* Fail-hot: fans to max, restore BD_PROCHOT. */ if (sc->sc_prochot_override) { apple_smc_thermal_fans_to_max(dev); apple_smc_restore_prochot(dev); } apple_smc_free_sensors(sc->sc_temp_sensors, sc->sc_temp_count); apple_smc_free_sensors(sc->sc_dts_sensors, sc->sc_dts_count); apple_smc_free_sensors(sc->sc_voltage_sensors, sc->sc_voltage_count); apple_smc_free_sensors(sc->sc_current_sensors, sc->sc_current_count); apple_smc_free_sensors(sc->sc_power_sensors, sc->sc_power_count); apple_smc_free_sensors(sc->sc_light_sensors, sc->sc_light_count); /* * Detach order matters: tear down the IRQ handler first so no new * tasks can be enqueued, then drain any in-flight task, then free * the taskqueue. Reversing drain and teardown would allow the ISR * to re-enqueue while we are draining. */ if (sc->sc_cookie) { bus_teardown_intr(dev, sc->sc_irq, sc->sc_cookie); sc->sc_cookie = NULL; } if (sc->sc_irq) { bus_release_resource(dev, SYS_RES_IRQ, sc->sc_rid_irq, sc->sc_irq); sc->sc_irq = NULL; } if (sc->sc_sms_tq) { taskqueue_drain(sc->sc_sms_tq, &sc->sc_sms_task); taskqueue_free(sc->sc_sms_tq); sc->sc_sms_tq = NULL; } if (sc->sc_ioport) { bus_release_resource(dev, SYS_RES_IOPORT, sc->sc_rid_port, sc->sc_ioport); sc->sc_ioport = NULL; } if (sc->sc_iomem) { bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid_mem, sc->sc_iomem); sc->sc_iomem = NULL; } lockuninit(&sc->sc_lock); return (0); } static int apple_smc_suspend(device_t dev) { apple_smc_thermal_stop(dev); return (0); } static int apple_smc_resume(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); uint8_t buf[2]; unsigned int lc; int error; /* Restore keyboard backlight level from before sleep. */ lc = apple_smc_get_light_control(); buf[0] = (uint8_t)lc; buf[1] = 0x00; error = apple_smc_key_write(dev, ASMC_KEY_LIGHTVALUE, buf, sizeof(buf)); if (error != 0) device_printf(dev, "resume: failed to restore backlight: %d\n", error); /* Re-initialize the Sudden Motion Sensor after resume. */ if (sc->sc_has_sms) apple_smc_sms_init(dev); /* MSR resets on S3 resume; re-apply override. */ if (sc->sc_prochot_override) { uint64_t msr; if (rdmsr_safe(MSR_IA32_POWER_CTL, &msr) == 0 && (msr & IA32_POWER_CTL_BD_PROCHOT) != 0) { msr &= ~(uint64_t)IA32_POWER_CTL_BD_PROCHOT; wrmsr_safe(MSR_IA32_POWER_CTL, msr); } /* SMC resets fan mode on resume. */ apple_smc_fan_override_headless(dev); } /* Restart thermal governor. */ if (sc->sc_thermal_enabled) apple_smc_thermal_start(dev); return (0); } static int apple_smc_init(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *sysctlctx; uint8_t buf[6]; int error; sysctlctx = device_get_sysctl_ctx(dev); error = apple_smc_key_read(dev, ASMC_KEY_REV, buf, 6); if (error != 0) { if (sc->sc_is_t2) { error = apple_smc_key_read(dev, ASMC_NKEYS, buf, 4); if (error != 0) goto out; device_printf(dev, "T2 SMC: %d keys\n", be32dec(buf)); } else { goto out; } } else { device_printf(dev, "SMC revision: %x.%x%x\n", buf[0], buf[1], buf[2]); } if (apple_smc_key_read(dev, ASMC_KEY_AUPO, buf, 1) == 0) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "always_up", CTLTYPE_INT | CTLFLAG_RW, dev, (intptr_t)ASMC_KEY_AUPO, apple_smc_flag_sysctl, "I", "Auto power-on after any shutdown"); } sc->sc_nfan = apple_smc_fan_count(dev); if (sc->sc_nfan < 0) sc->sc_nfan = 0; if (sc->sc_nfan > ASMC_MAXFANS) { device_printf(dev, "clamping fan count to %d\n", ASMC_MAXFANS); sc->sc_nfan = ASMC_MAXFANS; } if (apple_smc_key_read(dev, ASMC_NKEYS, buf, 4) == 0) sc->sc_nkeys = be32dec(buf); out: return (error); } static void apple_smc_detect_capabilities(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); uint8_t len; char type[ASMC_TYPELEN + 1]; /* * SMS detection issues 10 separate key_getinfo calls. Each call * acquires the lock and performs a full SMC round-trip, so this * runs about 10x slower than a single lookup. Acceptable at attach * time; not on a hot path. */ sc->sc_has_sms = (apple_smc_key_getinfo(dev, ASMC_KEY_SMS, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_X, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_Y, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_Z, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_LOW, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_HIGH, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_LOW_INT, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_HIGH_INT, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_SMS_FLAG, &len, type) == 0 && apple_smc_key_getinfo(dev, ASMC_KEY_INTOK, &len, type) == 0); if (apple_smc_key_getinfo(dev, ASMC_KEY_LIGHTLEFT, &len, type) == 0 && (len == ASMC_LIGHT_SHORTLEN || len == ASMC_LIGHT_LONGLEN) && apple_smc_key_getinfo(dev, ASMC_KEY_LIGHTVALUE, NULL, NULL) == 0) { sc->sc_has_light = 1; sc->sc_light_len = len; } sc->sc_has_safespeed = (apple_smc_key_getinfo(dev, ASMC_KEY_FANSAFESPEED0, &len, type) == 0); sc->sc_has_alsl = (apple_smc_key_getinfo(dev, ASMC_KEY_LIGHTSRC, &len, type) == 0); } /* * Reassert manual mode for override fans. */ int apple_smc_fan_reassert_manual(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); int error = 0; if (sc->sc_fan_manual_mask == 0) return (0); if (sc->sc_is_t2) { int i; for (i = 0; i < sc->sc_nfan; i++) { if (sc->sc_fan_manual_mask & (1 << i)) { uint8_t v = 1; char fmkey[5]; ksnprintf(fmkey, sizeof(fmkey), ASMC_KEY_FANMANUAL_T2, i); error = apple_smc_key_write(dev, fmkey, &v, 1); if (error) break; } } } else { uint8_t buf[2]; uint16_t val; error = apple_smc_key_read(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf)); if (error == 0) { val = (buf[0] << 8) | buf[1]; val |= sc->sc_fan_manual_mask; buf[0] = val >> 8; buf[1] = val & 0xff; error = apple_smc_key_write(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf)); } } return (error); } /* * Release manual mode for override fans. */ void apple_smc_fan_release_manual(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); if (sc->sc_fan_manual_mask == 0) return; if (sc->sc_is_t2) { int i; for (i = 0; i < sc->sc_nfan; i++) { if (sc->sc_fan_manual_mask & (1 << i)) { uint8_t v = 0; char fmkey[5]; ksnprintf(fmkey, sizeof(fmkey), ASMC_KEY_FANMANUAL_T2, i); apple_smc_key_write(dev, fmkey, &v, 1); } } } else { uint8_t buf[2]; uint16_t val; /* Restore pre-override FS! state. */ if (apple_smc_key_read(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf)) == 0) { val = (buf[0] << 8) | buf[1]; val &= ~sc->sc_fan_manual_mask; val |= sc->sc_fan_manual_saved; buf[0] = val >> 8; buf[1] = val & 0xff; apple_smc_key_write(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf)); } } sc->sc_fan_manual_mask = 0; } /* * Override fans in SMC failsafe mode. */ static void apple_smc_fan_override_headless(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); int i, cur, maxspd, minspd; uint8_t fsbuf[2]; /* Save pre-override FS! mask. */ if (!sc->sc_is_t2 && apple_smc_key_read(dev, ASMC_KEY_FANMANUAL, fsbuf, sizeof(fsbuf)) == 0) sc->sc_fan_manual_saved = (fsbuf[0] << 8) | fsbuf[1]; else sc->sc_fan_manual_saved = 0; for (i = 0; i < sc->sc_nfan; i++) { cur = apple_smc_fan_getvalue(dev, ASMC_KEY_FANSPEED, i); maxspd = apple_smc_fan_getvalue(dev, ASMC_KEY_FANMAXSPEED, i); minspd = apple_smc_fan_getvalue(dev, ASMC_KEY_FANMINSPEED, i); if (cur <= 0 || maxspd <= 0 || minspd <= 0 || maxspd < minspd) continue; /* Below 90% of max -- not in failsafe. */ if (cur < maxspd * 90 / 100) continue; sc->sc_fan_manual_mask |= (1 << i); device_printf(dev, "fan %d: failsafe detected (%d/%d RPM), " "override to %d RPM\n", i, cur, maxspd, minspd); } /* Write mask, then set targets to min RPM. */ if (sc->sc_fan_manual_mask != 0) { if (apple_smc_fan_reassert_manual(dev) != 0) { device_printf(dev, "WARNING: failed to set fan manual mode\n"); return; } for (i = 0; i < sc->sc_nfan; i++) { if (!(sc->sc_fan_manual_mask & (1 << i))) continue; minspd = apple_smc_fan_getvalue(dev, ASMC_KEY_FANMINSPEED, i); if (minspd > 0) apple_smc_fan_setvalue(dev, ASMC_KEY_FANTARGETSPEED, i, minspd); } } } /* * Return true if PROCHOT# is currently asserted. */ bool apple_smc_prochot_asserted(device_t dev) { uint8_t buf[2]; if (apple_smc_key_read(dev, ASMC_KEY_SPHT, buf, sizeof(buf)) != 0) return (0); return (buf[0] == 0x01 && buf[1] == 0x01); } /* * Restore BD_PROCHOT and release fan manual mode. */ void apple_smc_restore_prochot(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); uint64_t msr; int msr_ok = 0; if (!sc->sc_prochot_override) return; if (rdmsr_safe(MSR_IA32_POWER_CTL, &msr) == 0) { msr |= IA32_POWER_CTL_BD_PROCHOT; if (wrmsr_safe(MSR_IA32_POWER_CTL, msr) == 0) msr_ok = 1; else device_printf(dev, "WARNING: failed to restore BD_PROCHOT\n"); } /* Only release fans if MSR write succeeded. */ if (msr_ok) { apple_smc_fan_release_manual(dev); sc->sc_prochot_override = 0; device_printf(dev, "restored BD_PROCHOT and SMC fan control\n"); } } /* * Return true if LCD temperature sensor is disconnected. */ static bool apple_smc_lcd_disconnected(device_t dev) { uint8_t buf[2]; if (apple_smc_key_read(dev, "TL0P", buf, sizeof(buf)) != 0) return (false); return (buf[0] == 0x82 && buf[1] == 0x00); } /* * Return true if battery is absent or flagged bad. */ static bool apple_smc_battery_dead(device_t dev) { uint8_t buf[1]; if (apple_smc_key_read(dev, ASMC_KEY_BATP, buf, 1) == 0 && buf[0] == 0) return (true); if (apple_smc_key_read(dev, ASMC_KEY_BBAD, buf, 1) == 0 && buf[0] == 1) return (true); return (false); } static void apple_smc_check_prochot(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); uint64_t msr; const char *reason = NULL; sc->sc_prochot_override = 0; if (!apple_smc_prochot_asserted(dev)) return; /* Only override for known-benign causes. */ if (apple_smc_lcd_disconnected(dev)) reason = "headless"; else if (apple_smc_battery_dead(dev)) reason = "no battery"; if (reason == NULL) { device_printf(dev, "PROCHOT# asserted but no known benign cause, " "not overriding (possible real thermal event)\n"); return; } if (rdmsr_safe(MSR_IA32_POWER_CTL, &msr) != 0) return; if ((msr & IA32_POWER_CTL_BD_PROCHOT) == 0) return; /* already cleared by someone else */ msr &= ~(uint64_t)IA32_POWER_CTL_BD_PROCHOT; if (wrmsr_safe(MSR_IA32_POWER_CTL, msr) != 0) { device_printf(dev, "WARNING: PROCHOT active but MSR 0x1FC write failed\n"); return; } sc->sc_prochot_override = 1; device_printf(dev, "PROCHOT# asserted by SMC (%s), " "disabled BD_PROCHOT via MSR 0x1FC\n", reason); /* Override fans pinned to max by SMC failsafe. */ apple_smc_fan_override_headless(dev); } static void apple_smc_register_sensor_tree(struct sysctl_ctx_list *ctx, device_t dev, const char *name, const char *desc, int sensor_char, char **sensors, int count) { struct sysctl_oid *tree; int i; if (count <= 0) return; tree = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, name, CTLFLAG_RD, 0, desc); for (i = 0; i < count; i++) { SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, sensors[i], CTLTYPE_INT | CTLFLAG_RD, dev, (sensor_char << 8) | i, apple_smc_sensor_sysctl, "I", desc); } } static int apple_smc_detect_sensors(device_t dev) { struct apple_smc_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *sysctlctx; char key[ASMC_KEYLEN + 1]; char type[ASMC_TYPELEN + 1]; uint8_t len; unsigned int start, end, i; int error; char *sensor_key; if (sc->sc_nkeys == 0) return (0); /* Temperature: T..U range, sp78 only. * Probe-read each key: values consistently below -10000 millideg are * Intel DTS (distance-to-Tj,max) readings, not absolute temperatures. * Values at or below -120000 millideg are "not connected" sentinels * (Apple encodes these as 0x8000/0x8100/0x8200 in sp78) and are * dropped entirely. DTS sensors are stored separately and decoded in * the sysctl handler as: actual = sc_tj_max*1000 + dts_millideg. */ error = apple_smc_key_search(dev, "T\0\0\0", &start); if (error == 0) error = apple_smc_key_search(dev, "U\0\0\0", &end); if (error == 0) { for (i = start; i < end; i++) { int probe; if (apple_smc_key_dump_by_index(dev, i, key, type, &len)) continue; if (len != 2 || strncmp(type, "sp78", 4) != 0) continue; /* Probe read to classify the sensor. */ if (apple_smc_sensor_read(dev, key, &probe) != 0) continue; /* Disconnected sentinel: drop. */ if (probe <= -120000) continue; sensor_key = kmalloc(ASMC_KEYLEN + 1, M_DEVBUF, M_WAITOK); memcpy(sensor_key, key, ASMC_KEYLEN + 1); if (probe < -10000) { /* DTS sensor. */ if (sc->sc_dts_count < ASMC_TEMP_MAX) sc->sc_dts_sensors[sc->sc_dts_count++] = sensor_key; else kfree(sensor_key, M_DEVBUF); } else { /* Absolute temperature sensor. */ if (sc->sc_temp_count < ASMC_TEMP_MAX) sc->sc_temp_sensors[sc->sc_temp_count++] = sensor_key; else kfree(sensor_key, M_DEVBUF); } } } /* Voltage: V..W */ error = apple_smc_key_search(dev, "V\0\0\0", &start); if (error == 0) error = apple_smc_key_search(dev, "W\0\0\0", &end); if (error == 0) apple_smc_scan_sensor_range(dev, start, end, 'V', &sc->sc_voltage_count, sc->sc_voltage_sensors, ASMC_MAX_SENSORS); /* Current: I..J */ error = apple_smc_key_search(dev, "I\0\0\0", &start); if (error == 0) error = apple_smc_key_search(dev, "J\0\0\0", &end); if (error == 0) apple_smc_scan_sensor_range(dev, start, end, 'I', &sc->sc_current_count, sc->sc_current_sensors, ASMC_MAX_SENSORS); /* Power: P..Q */ error = apple_smc_key_search(dev, "P\0\0\0", &start); if (error == 0) error = apple_smc_key_search(dev, "Q\0\0\0", &end); if (error == 0) apple_smc_scan_sensor_range(dev, start, end, 'P', &sc->sc_power_count, sc->sc_power_sensors, ASMC_MAX_SENSORS); /* Ambient light: ALV/ALS keys in A..B */ error = apple_smc_key_search(dev, "A\0\0\0", &start); if (error == 0) error = apple_smc_key_search(dev, "B\0\0\0", &end); if (error == 0) { for (i = start; i < end; i++) { if (apple_smc_key_dump_by_index(dev, i, key, type, &len)) continue; if (key[0] != 'A' || key[1] != 'L' || (key[2] != 'V' && key[2] != 'S') || len != 2) continue; if (!apple_smc_sensor_type_supported(type)) continue; if (sc->sc_light_count >= ASMC_MAX_SENSORS) break; sensor_key = kmalloc(ASMC_KEYLEN + 1, M_DEVBUF, M_WAITOK); memcpy(sensor_key, key, ASMC_KEYLEN + 1); sc->sc_light_sensors[sc->sc_light_count++] = sensor_key; } } sysctlctx = device_get_sysctl_ctx(dev); apple_smc_register_sensor_tree(sysctlctx, dev, "voltage", "Voltage sensors (millivolts)", 'V', sc->sc_voltage_sensors, sc->sc_voltage_count); apple_smc_register_sensor_tree(sysctlctx, dev, "current", "Current sensors (milliamps)", 'I', sc->sc_current_sensors, sc->sc_current_count); apple_smc_register_sensor_tree(sysctlctx, dev, "power", "Power sensors (milliwatts)", 'P', sc->sc_power_sensors, sc->sc_power_count); apple_smc_register_sensor_tree(sysctlctx, dev, "ambient", "Ambient light sensors", 'L', sc->sc_light_sensors, sc->sc_light_count); return (0); }