git: aae906840494 - main - asmc: add automatic voltage/current/power/ambient sensor detection
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 17 Apr 2026 02:40:14 UTC
The branch main has been updated by adrian:
URL: https://cgit.FreeBSD.org/src/commit/?id=aae9068404947dd9ffd8522359d0f9dffaa70414
commit aae9068404947dd9ffd8522359d0f9dffaa70414
Author: Abdelkader Boudih <chaos@seuros.com>
AuthorDate: 2026-04-17 02:31:21 +0000
Commit: Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-04-17 02:31:37 +0000
asmc: add automatic voltage/current/power/ambient sensor detection
Apple SMCs contain numerous undocumented voltage, current, power,
and ambient light sensors. This change adds automatic detection
and registration of these sensors as sysctls.
New sysctl trees:
dev.asmc.0.voltage.* - Voltage sensors (millivolts)
dev.asmc.0.current.* - Current sensors (milliamps)
dev.asmc.0.power.* - Power sensors (milliwatts)
dev.asmc.0.ambient.* - Ambient light sensors
Implementation:
- Scans all SMC keys at attach time via asmc_key_dump_by_index()
- Identifies sensors by key prefix patterns:
- Voltage: VC*, VD*, VG*, VP*, VI*
- Current: I{C,D,G,M,N,O,H,P,B,A,L}*
- Power: P{C,D,N,S,T,H,F,Z,z}*
- Light: ALV*, ALS*
- Dynamically creates sysctls for detected sensors
- Supports 8 fixed-point SMC data types:
- sp78, sp87, sp4b, sp5a, sp69, sp96, sp2d, ui16
- Auto-converts all values to milli-units (mV, mA, mW)
On Mac Mini 5,1, detects:
- 7 voltage sensors
- 18 current sensors
- 27 power sensors
- 2 ambient light sensors
Enables power consumption monitoring, voltage rail debugging,
and ambient light detection without hardcoding model-specific
sensor lists.
Tested on:
- Mac Mini 5,1 (2011) running FreeBSD 15.0-RELEASE
- 54 sensors auto-detected and exposed via sysctl
- All sensor types verified with multimeter readings
- Fixed-point conversions validated against known values
- Memory management tested (malloc/free on detach)
Reviewed by: adrian
Differential Revision: https://reviews.freebsd.org/D55807
---
sys/dev/asmc/asmc.c | 437 ++++++++++++++++++++++++++++++++++++++++++++++++-
sys/dev/asmc/asmcvar.h | 10 ++
2 files changed, 445 insertions(+), 2 deletions(-)
diff --git a/sys/dev/asmc/asmc.c b/sys/dev/asmc/asmc.c
index 7cd5181605a8..0a701e6fd663 100644
--- a/sys/dev/asmc/asmc.c
+++ b/sys/dev/asmc/asmc.c
@@ -123,15 +123,22 @@ static int asmc_mbp_sysctl_light_control(SYSCTL_HANDLER_ARGS);
static int asmc_mbp_sysctl_light_left_10byte(SYSCTL_HANDLER_ARGS);
static int asmc_wol_sysctl(SYSCTL_HANDLER_ARGS);
+static int asmc_key_getinfo(device_t, const char *, uint8_t *, char *);
+
#ifdef ASMC_DEBUG
/* Raw key access */
-static int asmc_key_getinfo(device_t, const char *, uint8_t *, char *);
static int asmc_raw_key_sysctl(SYSCTL_HANDLER_ARGS);
static int asmc_raw_value_sysctl(SYSCTL_HANDLER_ARGS);
static int asmc_raw_len_sysctl(SYSCTL_HANDLER_ARGS);
static int asmc_raw_type_sysctl(SYSCTL_HANDLER_ARGS);
#endif
+/* Voltage/Current/Power/Light sensor support */
+static int asmc_sensor_read(device_t, const char *, int *);
+static int asmc_sensor_sysctl(SYSCTL_HANDLER_ARGS);
+static int asmc_detect_sensors(device_t);
+static int asmc_key_dump_by_index(device_t, int, char *, char *, uint8_t *);
+
struct asmc_model {
const char *smc_model; /* smbios.system.product env var. */
const char *smc_desc; /* driver description */
@@ -963,6 +970,16 @@ asmc_detach(device_t dev)
if (sc->sc_kbd_bkl != NULL)
backlight_destroy(sc->sc_kbd_bkl);
+ /* Free sensor key arrays */
+ for (int i = 0; i < sc->sc_voltage_count; i++)
+ free(sc->sc_voltage_sensors[i], M_DEVBUF);
+ for (int i = 0; i < sc->sc_current_count; i++)
+ free(sc->sc_current_sensors[i], M_DEVBUF);
+ for (int i = 0; i < sc->sc_power_count; i++)
+ free(sc->sc_power_sensors[i], M_DEVBUF);
+ for (int i = 0; i < sc->sc_light_count; i++)
+ free(sc->sc_light_sensors[i], M_DEVBUF);
+
if (sc->sc_sms_tq) {
taskqueue_drain(sc->sc_sms_tq, &sc->sc_sms_task);
taskqueue_free(sc->sc_sms_tq);
@@ -1134,6 +1151,12 @@ nosms:
sc->sc_nkeys = 0;
}
+ /*
+ * Auto-detect and register voltage/current/power/ambient sensors.
+ * Scans SMC keys and creates sysctls for detected sensors.
+ */
+ asmc_detect_sensors(dev);
+
out_err:
#ifdef ASMC_DEBUG
asmc_dumpall(dev);
@@ -1337,10 +1360,10 @@ out:
return (0);
}
+#endif /* ASMC_DEBUG */
/*
* Get key info (length and type) from SMC using command 0x13.
- * Returns 0 on success, -1 on failure.
* If len is non-NULL, stores the key's value length.
* If type is non-NULL, stores the 4-char type string (must be at least 5 bytes).
*/
@@ -1389,6 +1412,7 @@ out:
return (error);
}
+#ifdef ASMC_DEBUG
/*
* Raw SMC key access sysctls - enables reading/writing any SMC key by name
* Usage:
@@ -1491,6 +1515,415 @@ asmc_raw_type_sysctl(SYSCTL_HANDLER_ARGS)
}
#endif
+/*
+ * Convert signed fixed-point SMC values to milli-units.
+ * Format "spXY" means signed with X integer bits and Y fraction bits.
+ */
+static int
+asmc_sp78_to_milli(const uint8_t *buf)
+{
+ int16_t val = (int16_t)be16dec(buf);
+
+ return ((int)val * 1000) / 256;
+}
+
+static int
+asmc_sp87_to_milli(const uint8_t *buf)
+{
+ int16_t val = (int16_t)be16dec(buf);
+
+ return ((int)val * 1000) / 128;
+}
+
+static int
+asmc_sp4b_to_milli(const uint8_t *buf)
+{
+ int16_t val = (int16_t)be16dec(buf);
+
+ return ((int)val * 1000) / 2048;
+}
+
+static int
+asmc_sp5a_to_milli(const uint8_t *buf)
+{
+ int16_t val = (int16_t)be16dec(buf);
+
+ return ((int)val * 1000) / 1024;
+}
+
+static int
+asmc_sp69_to_milli(const uint8_t *buf)
+{
+ int16_t val = (int16_t)be16dec(buf);
+
+ return ((int)val * 1000) / 512;
+}
+
+static int
+asmc_sp96_to_milli(const uint8_t *buf)
+{
+ int16_t val = (int16_t)be16dec(buf);
+
+ return ((int)val * 1000) / 64;
+}
+
+static int
+asmc_sp2d_to_milli(const uint8_t *buf)
+{
+ int16_t val = (int16_t)be16dec(buf);
+
+ return ((int)val * 1000) / 8192;
+}
+
+static bool
+asmc_sensor_type_supported(const char *type)
+{
+
+ return (strncmp(type, "sp78", 4) == 0 ||
+ strncmp(type, "sp87", 4) == 0 ||
+ strncmp(type, "sp4b", 4) == 0 ||
+ strncmp(type, "sp5a", 4) == 0 ||
+ strncmp(type, "sp69", 4) == 0 ||
+ strncmp(type, "sp96", 4) == 0 ||
+ strncmp(type, "sp2d", 4) == 0 ||
+ strncmp(type, "ui16", 4) == 0);
+}
+
+/*
+ * Generic sensor value reader with automatic type conversion.
+ * Reads an SMC key, detects its type, and converts to millivalue.
+ */
+static int
+asmc_sensor_read(device_t dev, const char *key, int *millivalue)
+{
+ uint8_t buf[2];
+ char type[ASMC_TYPELEN + 1];
+ uint8_t len;
+ int error;
+
+ error = asmc_key_getinfo(dev, key, &len, type);
+ if (error != 0)
+ return (error);
+
+ if (len != 2) {
+ if (bootverbose)
+ device_printf(dev,
+ "%s: key %s unexpected length %d\n",
+ __func__, key, len);
+ return (ENXIO);
+ }
+
+ error = asmc_key_read(dev, key, buf, sizeof(buf));
+ if (error != 0)
+ return (error);
+
+ if (strncmp(type, "sp78", 4) == 0) {
+ *millivalue = asmc_sp78_to_milli(buf);
+ } else if (strncmp(type, "sp87", 4) == 0) {
+ *millivalue = asmc_sp87_to_milli(buf);
+ } else if (strncmp(type, "sp4b", 4) == 0) {
+ *millivalue = asmc_sp4b_to_milli(buf);
+ } else if (strncmp(type, "sp5a", 4) == 0) {
+ *millivalue = asmc_sp5a_to_milli(buf);
+ } else if (strncmp(type, "sp69", 4) == 0) {
+ *millivalue = asmc_sp69_to_milli(buf);
+ } else if (strncmp(type, "sp96", 4) == 0) {
+ *millivalue = asmc_sp96_to_milli(buf);
+ } else if (strncmp(type, "sp2d", 4) == 0) {
+ *millivalue = asmc_sp2d_to_milli(buf);
+ } else if (strncmp(type, "ui16", 4) == 0) {
+ *millivalue = be16dec(buf);
+ } else {
+ if (bootverbose)
+ device_printf(dev,
+ "%s: unknown type '%s' for key %s\n",
+ __func__, type, key);
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+/*
+ * Generic sensor sysctl handler for voltage/current/power/light sensors.
+ * arg2 encodes: sensor_type (high byte) | sensor_index (low byte)
+ * Sensor types: 'V'=voltage, 'I'=current, 'P'=power, 'L'=light
+ */
+static int
+asmc_sensor_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ device_t dev = (device_t) arg1;
+ struct asmc_softc *sc = device_get_softc(dev);
+ int error, val;
+ int sensor_type = (arg2 >> 8) & 0xFF;
+ int sensor_idx = arg2 & 0xFF;
+ const char *key = NULL;
+
+ /* Select sensor based on type and index */
+ switch (sensor_type) {
+ case 'V': /* Voltage */
+ if (sensor_idx < sc->sc_voltage_count)
+ key = sc->sc_voltage_sensors[sensor_idx];
+ break;
+ case 'I': /* Current */
+ if (sensor_idx < sc->sc_current_count)
+ key = sc->sc_current_sensors[sensor_idx];
+ break;
+ case 'P': /* Power */
+ if (sensor_idx < sc->sc_power_count)
+ key = sc->sc_power_sensors[sensor_idx];
+ break;
+ case 'L': /* Light */
+ if (sensor_idx < sc->sc_light_count)
+ key = sc->sc_light_sensors[sensor_idx];
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if (key == NULL)
+ return (ENOENT);
+
+ error = asmc_sensor_read(dev, key, &val);
+ if (error != 0)
+ return (error);
+
+ return (sysctl_handle_int(oidp, &val, 0, req));
+}
+
+/*
+ * Detect and register voltage/current/power/ambient sensors.
+ * Scans all SMC keys and identifies sensor keys by prefix.
+ * Returns 0 on success, -1 on error.
+ */
+static int
+asmc_detect_sensors(device_t dev)
+{
+ struct asmc_softc *sc = device_get_softc(dev);
+ struct sysctl_ctx_list *sysctlctx;
+ struct sysctl_oid *tree_node;
+ char key[ASMC_KEYLEN + 1];
+ char type[ASMC_TYPELEN + 1];
+ uint8_t len;
+ unsigned int nkeys;
+ unsigned int i;
+ int error;
+ char *sensor_key;
+
+ /* Initialize counts */
+ sc->sc_voltage_count = 0;
+ sc->sc_current_count = 0;
+ sc->sc_power_count = 0;
+ sc->sc_light_count = 0;
+
+ if (sc->sc_nkeys == 0)
+ return (0);
+ nkeys = sc->sc_nkeys;
+
+ /* Scan all keys for voltage/current/power/ambient light sensors */
+ for (i = 0; i < nkeys; i++) {
+ /* Get key name by index */
+ error = asmc_key_dump_by_index(dev, i, key, type, &len);
+ if (error != 0)
+ continue;
+ if (!asmc_sensor_type_supported(type))
+ continue;
+
+ /* Voltage sensors (VC*, VD*, VG*, VP*, VI*) */
+ if (key[0] == 'V' && (key[1] == 'C' || key[1] == 'D' ||
+ key[1] == 'G' || key[1] == 'P' || key[1] == 'I') &&
+ len == 2) {
+ if (sc->sc_voltage_count >= ASMC_MAX_SENSORS)
+ continue;
+ sensor_key = malloc(ASMC_KEYLEN + 1,
+ M_DEVBUF, M_WAITOK);
+ memcpy(sensor_key, key, ASMC_KEYLEN + 1);
+ sc->sc_voltage_sensors[sc->sc_voltage_count++] =
+ sensor_key;
+ } else if (key[0] == 'I' && (key[1] == 'C' ||
+ key[1] == 'D' || key[1] == 'G' || key[1] == 'M' ||
+ key[1] == 'N' || key[1] == 'O' || key[1] == 'H' ||
+ key[1] == 'P' || key[1] == 'B' || key[1] == 'A' ||
+ key[1] == 'L') && len == 2) {
+ /* Current sensors */
+ if (sc->sc_current_count >= ASMC_MAX_SENSORS)
+ continue;
+ sensor_key = malloc(ASMC_KEYLEN + 1,
+ M_DEVBUF, M_WAITOK);
+ memcpy(sensor_key, key, ASMC_KEYLEN + 1);
+ sc->sc_current_sensors[sc->sc_current_count++] =
+ sensor_key;
+ } else if (key[0] == 'P' && (key[1] == 'C' ||
+ key[1] == 'D' || key[1] == 'N' || key[1] == 'S' ||
+ key[1] == 'T' || key[1] == 'H' || key[1] == 'F' ||
+ key[1] == 'Z' || key[1] == 'z') && len == 2) {
+ /* Power sensors */
+ if (sc->sc_power_count >= ASMC_MAX_SENSORS)
+ continue;
+ sensor_key = malloc(ASMC_KEYLEN + 1,
+ M_DEVBUF, M_WAITOK);
+ memcpy(sensor_key, key, ASMC_KEYLEN + 1);
+ sc->sc_power_sensors[sc->sc_power_count++] =
+ sensor_key;
+ } else if (key[0] == 'A' && key[1] == 'L' &&
+ (key[2] == 'V' || key[2] == 'S') && len == 2) {
+ /* Ambient light sensors */
+ if (sc->sc_light_count >= ASMC_MAX_SENSORS)
+ continue;
+ sensor_key = malloc(ASMC_KEYLEN + 1,
+ M_DEVBUF, M_WAITOK);
+ memcpy(sensor_key, key, ASMC_KEYLEN + 1);
+ sc->sc_light_sensors[sc->sc_light_count++] =
+ sensor_key;
+ }
+ }
+
+ if (bootverbose)
+ device_printf(dev,
+ "detected %d voltage, %d current, "
+ "%d power, %d light sensors\n",
+ sc->sc_voltage_count, sc->sc_current_count,
+ sc->sc_power_count, sc->sc_light_count);
+
+ /* Register sysctls for detected sensors */
+ sysctlctx = device_get_sysctl_ctx(dev);
+
+ /* Voltage sensors */
+ if (sc->sc_voltage_count > 0) {
+ tree_node = SYSCTL_ADD_NODE(sysctlctx,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "voltage", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Voltage sensors (millivolts)");
+
+ for (i = 0; i < sc->sc_voltage_count; i++) {
+ SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(tree_node),
+ OID_AUTO, sc->sc_voltage_sensors[i],
+ CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ dev, ('V' << 8) | i, asmc_sensor_sysctl, "I",
+ "Voltage sensor (millivolts)");
+ }
+ }
+
+ /* Current sensors */
+ if (sc->sc_current_count > 0) {
+ tree_node = SYSCTL_ADD_NODE(sysctlctx,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "current", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Current sensors (milliamps)");
+
+ for (i = 0; i < sc->sc_current_count; i++) {
+ SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(tree_node),
+ OID_AUTO, sc->sc_current_sensors[i],
+ CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ dev, ('I' << 8) | i, asmc_sensor_sysctl, "I",
+ "Current sensor (milliamps)");
+ }
+ }
+
+ /* Power sensors */
+ if (sc->sc_power_count > 0) {
+ tree_node = SYSCTL_ADD_NODE(sysctlctx,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "power", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Power sensors (milliwatts)");
+
+ for (i = 0; i < sc->sc_power_count; i++) {
+ SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(tree_node),
+ OID_AUTO, sc->sc_power_sensors[i],
+ CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ dev, ('P' << 8) | i, asmc_sensor_sysctl, "I",
+ "Power sensor (milliwatts)");
+ }
+ }
+
+ /* Ambient light sensors */
+ if (sc->sc_light_count > 0) {
+ tree_node = SYSCTL_ADD_NODE(sysctlctx,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "ambient", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Ambient light sensors");
+
+ for (i = 0; i < sc->sc_light_count; i++) {
+ SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(tree_node),
+ OID_AUTO, sc->sc_light_sensors[i],
+ CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ dev, ('L' << 8) | i, asmc_sensor_sysctl, "I",
+ "Light sensor value");
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Helper function to get key info by index (for sensor detection).
+ */
+static int
+asmc_key_dump_by_index(device_t dev, int index, char *key_out,
+ char *type_out, uint8_t *len_out)
+{
+ struct asmc_softc *sc = device_get_softc(dev);
+ uint8_t index_buf[ASMC_KEYLEN];
+ uint8_t key_buf[ASMC_KEYLEN];
+ uint8_t info_buf[ASMC_KEYINFO_RESPLEN];
+ int error = ENXIO, try = 0;
+ int i;
+
+ mtx_lock_spin(&sc->sc_mtx);
+
+ index_buf[0] = (index >> 24) & 0xff;
+ index_buf[1] = (index >> 16) & 0xff;
+ index_buf[2] = (index >> 8) & 0xff;
+ index_buf[3] = index & 0xff;
+
+begin:
+ if (asmc_command(dev, ASMC_CMDGETBYINDEX))
+ goto out;
+
+ for (i = 0; i < ASMC_KEYLEN; i++) {
+ ASMC_DATAPORT_WRITE(sc, index_buf[i]);
+ if (asmc_wait(dev, ASMC_STATUS_AWAIT_DATA))
+ goto out;
+ }
+
+ ASMC_DATAPORT_WRITE(sc, ASMC_KEYLEN);
+
+ for (i = 0; i < ASMC_KEYLEN; i++) {
+ if (asmc_wait(dev, ASMC_STATUS_DATA_READY))
+ goto out;
+ key_buf[i] = ASMC_DATAPORT_READ(sc);
+ }
+
+ if (asmc_command(dev, ASMC_CMDGETINFO))
+ goto out;
+
+ for (i = 0; i < ASMC_KEYLEN; i++) {
+ ASMC_DATAPORT_WRITE(sc, key_buf[i]);
+ if (asmc_wait(dev, ASMC_STATUS_AWAIT_DATA))
+ goto out;
+ }
+
+ ASMC_DATAPORT_WRITE(sc, ASMC_KEYINFO_RESPLEN);
+
+ for (i = 0; i < ASMC_KEYINFO_RESPLEN; i++) {
+ if (asmc_wait(dev, ASMC_STATUS_DATA_READY))
+ goto out;
+ info_buf[i] = ASMC_DATAPORT_READ(sc);
+ }
+
+ memcpy(key_out, key_buf, ASMC_KEYLEN);
+ key_out[ASMC_KEYLEN] = '\0';
+ *len_out = info_buf[0];
+ memcpy(type_out, &info_buf[1], ASMC_TYPELEN);
+ type_out[ASMC_TYPELEN] = '\0';
+ error = 0;
+
+out:
+ if (error) {
+ if (++try < ASMC_MAXRETRIES)
+ goto begin;
+ }
+
+ mtx_unlock_spin(&sc->sc_mtx);
+ return (error);
+}
+
static int
asmc_key_write(device_t dev, const char *key, uint8_t *buf, uint8_t len)
{
diff --git a/sys/dev/asmc/asmcvar.h b/sys/dev/asmc/asmcvar.h
index 00857fe5fea8..43f679f3fef0 100644
--- a/sys/dev/asmc/asmcvar.h
+++ b/sys/dev/asmc/asmcvar.h
@@ -31,6 +31,7 @@
#define ASMC_MAXVAL 32 /* Maximum SMC value size */
#define ASMC_KEYLEN 4 /* SMC key name length */
#define ASMC_TYPELEN 4 /* SMC type string length */
+#define ASMC_MAX_SENSORS 64 /* Max sensors per type */
struct asmc_softc {
device_t sc_dev;
@@ -64,6 +65,15 @@ struct asmc_softc {
uint8_t sc_rawlen;
char sc_rawtype[ASMC_TYPELEN + 1];
#endif
+ /* Voltage/Current/Power/Light sensors */
+ char *sc_voltage_sensors[ASMC_MAX_SENSORS];
+ int sc_voltage_count;
+ char *sc_current_sensors[ASMC_MAX_SENSORS];
+ int sc_current_count;
+ char *sc_power_sensors[ASMC_MAX_SENSORS];
+ int sc_power_count;
+ char *sc_light_sensors[ASMC_MAX_SENSORS];
+ int sc_light_count;
};
/*