The branch stable/15 has been updated by ngie:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=7d2303e4b8275725b12f9816d6a7d81608e6c5b1

commit 7d2303e4b8275725b12f9816d6a7d81608e6c5b1
Author:     Abdelkader Boudih <[email protected]>
AuthorDate: 2026-04-17 02:31:21 +0000
Commit:     Enji Cooper <[email protected]>
CommitDate: 2026-09-04 23:07:00 +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
    
    (cherry picked from commit aae9068404947dd9ffd8522359d0f9dffaa70414)
---
 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 2c60181704e5..d4ae3583a0cf 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;
 };
 
 /*

Reply via email to