Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 15 Apr 2026 04:28:05 +0000
From:      Adrian Chadd <adrian@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Cc:        Abdelkader Boudih <chaos@seuros.com>
Subject:   git: 3e27114a7f96 - main - asmc: add raw SMC key read/write interface
Message-ID:  <69df13d5.3084c.15512e6d@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=3e27114a7f961aac49d75a663a55332375d0bef3

commit 3e27114a7f961aac49d75a663a55332375d0bef3
Author:     Abdelkader Boudih <chaos@seuros.com>
AuthorDate: 2026-04-15 04:20:52 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-04-15 04:21:46 +0000

    asmc: add raw SMC key read/write interface
    
    This patch adds a debugging interface to read and write arbitrary
    Apple SMC keys by name through sysctl, enabling hardware exploration
     and control of undocumented features.
    
    The interface provides four sysctls under dev.asmc.0.raw.*:
      - key - Set the 4-character SMC key name (e.g., "AUPO")
      - value - Read/write key value as a hex string
      - len - Auto-detected key value length (can be overridden)
      - type - Read-only 4-character type string (e.g., "ui8", "flt")
    
    Implementation includes a new asmc_key_getinfo() function using SMC
    command 0x13 to query key metadata. The interface automatically
    detects key lengths and types, uses hex string encoding for
    arbitrary binary values, and is safe for concurrent access via
    CTLFLAG_NEEDGIANT.
    
    This interface was essential for discovering that the AUPO key
    enables Wake-on-LAN from S5 state, and for mapping all 297 SMC keys
    on Mac Mini 5,1.
    
    Reviewed by:    ngie, adrian, markj
    Differential Revision:  https://reviews.freebsd.org/D54441
---
 share/man/man4/asmc.4  |  29 +++++
 sys/dev/asmc/asmc.c    | 287 +++++++++++++++++++++++++++++++++++++++++--------
 sys/dev/asmc/asmcvar.h |  19 ++++
 3 files changed, 288 insertions(+), 47 deletions(-)

diff --git a/share/man/man4/asmc.4 b/share/man/man4/asmc.4
index 9e3550661797..9b42d021e1aa 100644
--- a/share/man/man4/asmc.4
+++ b/share/man/man4/asmc.4
@@ -112,6 +112,35 @@ minimum fan speed, the minimum speed and the maximum speed
 respectively.
 .Pp
 All values are in RPM.
+.Sh RAW SMC KEY ACCESS
+When the kernel is compiled with the
+.Dv ASMC_DEBUG
+option, a set of sysctl nodes is provided under
+.Va dev.asmc.%d.raw
+for reading and writing arbitrary SMC keys by name.
+.Pp
+.Bl -tag -width "dev.asmc.%d.raw.value" -compact
+.It Va dev.asmc.%d.raw.key
+Set the 4-character SMC key name to access (e.g.,\&
+.Dq AUPO ) .
+Setting this automatically queries the key's length and type.
+.It Va dev.asmc.%d.raw.value
+Read or write the key's value as a hex string.
+.It Va dev.asmc.%d.raw.len
+The auto-detected value length in bytes (read-only).
+.It Va dev.asmc.%d.raw.type
+The 4-character SMC type string (e.g.,\&
+.Dq ui8 ,
+.Dq flt )
+(read-only).
+.El
+.Pp
+Example usage:
+.Bd -literal -offset indent
+sysctl dev.asmc.0.raw.key=AUPO
+sysctl dev.asmc.0.raw.value
+sysctl dev.asmc.0.raw.value=01
+.Ed
 .Sh SUDDEN MOTION SENSOR
 The Sudden Motion Sensor (SMS for short) is a device that detects
 laptop movement and notifies the operating system via an interrupt.
diff --git a/sys/dev/asmc/asmc.c b/sys/dev/asmc/asmc.c
index 4a6734e22786..044fd7e85057 100644
--- a/sys/dev/asmc/asmc.c
+++ b/sys/dev/asmc/asmc.c
@@ -123,6 +123,15 @@ 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);
 
+#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
+
 struct asmc_model {
 	const char *smc_model; /* smbios.system.product env var. */
 	const char *smc_desc;  /* driver description */
@@ -828,6 +837,43 @@ asmc_attach(device_t dev)
 		}
 	}
 
+#ifdef ASMC_DEBUG
+	/*
+	 * Raw SMC key access for debugging.
+	 */
+	sc->sc_raw_tree = SYSCTL_ADD_NODE(sysctlctx,
+	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+	    "raw", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Raw SMC key access");
+
+	SYSCTL_ADD_PROC(sysctlctx,
+	    SYSCTL_CHILDREN(sc->sc_raw_tree),
+	    OID_AUTO, "key",
+	    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE,
+	    dev, 0, asmc_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 | CTLFLAG_MPSAFE,
+	    dev, 0, asmc_raw_value_sysctl, "A",
+	    "SMC key value (hex string)");
+
+	SYSCTL_ADD_PROC(sysctlctx,
+	    SYSCTL_CHILDREN(sc->sc_raw_tree),
+	    OID_AUTO, "len",
+	    CTLTYPE_U8 | CTLFLAG_RD | CTLFLAG_MPSAFE,
+	    dev, 0, asmc_raw_len_sysctl, "CU",
+	    "SMC key value length");
+
+	SYSCTL_ADD_PROC(sysctlctx,
+	    SYSCTL_CHILDREN(sc->sc_raw_tree),
+	    OID_AUTO, "type",
+	    CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+	    dev, 0, asmc_raw_type_sysctl, "A",
+	    "SMC key type (4 chars)");
+#endif
+
 	if (model->smc_sms_x == NULL)
 		goto nosms;
 
@@ -1202,10 +1248,10 @@ static int
 asmc_key_dump(device_t dev, int number)
 {
 	struct asmc_softc *sc = device_get_softc(dev);
-	char key[5] = { 0 };
-	char type[7] = { 0 };
+	char key[ASMC_KEYLEN + 1] = { 0 };
+	char type[ASMC_KEYINFO_RESPLEN + 1] = { 0 };
 	uint8_t index[4];
-	uint8_t v[32];
+	uint8_t v[ASMC_MAXVAL];
 	uint8_t maxlen;
 	int i, error = 1, try = 0;
 
@@ -1214,40 +1260,40 @@ asmc_key_dump(device_t dev, int number)
 	index[0] = (number >> 24) & 0xff;
 	index[1] = (number >> 16) & 0xff;
 	index[2] = (number >> 8) & 0xff;
-	index[3] = (number) & 0xff;
+	index[3] = number & 0xff;
 
 begin:
-	if (asmc_command(dev, 0x12))
+	if (asmc_command(dev, ASMC_CMDGETBYINDEX))
 		goto out;
 
-	for (i = 0; i < 4; i++) {
+	for (i = 0; i < ASMC_KEYLEN; i++) {
 		ASMC_DATAPORT_WRITE(sc, index[i]);
-		if (asmc_wait(dev, 0x04))
+		if (asmc_wait(dev, ASMC_STATUS_AWAIT_DATA))
 			goto out;
 	}
 
-	ASMC_DATAPORT_WRITE(sc, 4);
+	ASMC_DATAPORT_WRITE(sc, ASMC_KEYLEN);
 
-	for (i = 0; i < 4; i++) {
-		if (asmc_wait(dev, 0x05))
+	for (i = 0; i < ASMC_KEYLEN; i++) {
+		if (asmc_wait(dev, ASMC_STATUS_DATA_READY))
 			goto out;
 		key[i] = ASMC_DATAPORT_READ(sc);
 	}
 
-	/* get type */
-	if (asmc_command(dev, 0x13))
+	/* Get key info (length + type). */
+	if (asmc_command(dev, ASMC_CMDGETINFO))
 		goto out;
 
-	for (i = 0; i < 4; i++) {
+	for (i = 0; i < ASMC_KEYLEN; i++) {
 		ASMC_DATAPORT_WRITE(sc, key[i]);
-		if (asmc_wait(dev, 0x04))
+		if (asmc_wait(dev, ASMC_STATUS_AWAIT_DATA))
 			goto out;
 	}
 
-	ASMC_DATAPORT_WRITE(sc, 6);
+	ASMC_DATAPORT_WRITE(sc, ASMC_KEYINFO_RESPLEN);
 
-	for (i = 0; i < 6; i++) {
-		if (asmc_wait(dev, 0x05))
+	for (i = 0; i < ASMC_KEYINFO_RESPLEN; i++) {
+		if (asmc_wait(dev, ASMC_STATUS_DATA_READY))
 			goto out;
 		type[i] = ASMC_DATAPORT_READ(sc);
 	}
@@ -1255,41 +1301,188 @@ begin:
 	error = 0;
 out:
 	if (error) {
-		if (++try < 10)
+		if (++try < ASMC_MAXRETRIES)
 			goto begin;
-		device_printf(dev, "%s for key %s failed %d times, giving up\n",
-		    __func__, key, try);
-		mtx_unlock_spin(&sc->sc_mtx);
-	} else {
-		char buf[1024];
-		char buf2[8];
-		mtx_unlock_spin(&sc->sc_mtx);
-		maxlen = type[0];
-		type[0] = ' ';
-		type[5] = 0;
-		if (maxlen > sizeof(v)) {
-			device_printf(dev,
-			    "WARNING: cropping maxlen from %d to %zu\n", maxlen,
-			    sizeof(v));
-			maxlen = sizeof(v);
-		}
-		for (i = 0; i < sizeof(v); i++) {
-			v[i] = 0;
-		}
-		asmc_key_read(dev, key, v, maxlen);
-		snprintf(buf, sizeof(buf),
-		    "key %d is: %s, type %s (len %d), data",
-		    number, key, type, maxlen);
-		for (i = 0; i < maxlen; i++) {
-			snprintf(buf2, sizeof(buf2), " %02x", v[i]);
-			strlcat(buf, buf2, sizeof(buf));
-		}
-		strlcat(buf, " \n", sizeof(buf));
-		device_printf(dev, "%s", buf);
+		device_printf(dev,
+		    "%s for key %d failed %d times, giving up\n",
+		    __func__, number, try);
+	}
+	mtx_unlock_spin(&sc->sc_mtx);
+
+	if (error)
+		return (error);
+
+	maxlen = type[0];
+	type[0] = ' ';
+	type[5] = '\0';
+	if (maxlen > sizeof(v))
+		maxlen = sizeof(v);
+
+	memset(v, 0, sizeof(v));
+	error = asmc_key_read(dev, key, v, maxlen);
+	if (error)
+		return (error);
+
+	device_printf(dev, "key %d: %s, type%s (len %d), data",
+	    number, key, type, maxlen);
+	for (i = 0; i < maxlen; i++)
+		printf(" %02x", v[i]);
+	printf("\n");
+
+	return (0);
+}
+
+/*
+ * 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).
+ */
+static int
+asmc_key_getinfo(device_t dev, const char *key, uint8_t *len, char *type)
+{
+	struct asmc_softc *sc = device_get_softc(dev);
+	uint8_t info[ASMC_KEYINFO_RESPLEN];
+	int i, error = -1, try = 0;
+
+	mtx_lock_spin(&sc->sc_mtx);
+
+begin:
+	if (asmc_command(dev, ASMC_CMDGETINFO))
+		goto out;
+
+	for (i = 0; i < ASMC_KEYLEN; i++) {
+		ASMC_DATAPORT_WRITE(sc, key[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[i] = ASMC_DATAPORT_READ(sc);
+	}
+
+	error = 0;
+out:
+	if (error && ++try < ASMC_MAXRETRIES)
+		goto begin;
+	mtx_unlock_spin(&sc->sc_mtx);
+
+	if (error == 0) {
+		if (len != NULL)
+			*len = info[0];
+		if (type != NULL) {
+			for (i = 0; i < ASMC_TYPELEN; i++)
+				type[i] = info[i + 1];
+			type[ASMC_TYPELEN] = '\0';
+		}
+	}
 	return (error);
 }
+
+/*
+ * Raw SMC key access sysctls - enables reading/writing any SMC key by name
+ * Usage:
+ *   sysctl dev.asmc.0.raw.key=AUPO   # Set key, auto-detects length
+ *   sysctl dev.asmc.0.raw.value      # Read current value (hex bytes)
+ *   sysctl dev.asmc.0.raw.value=01   # Write new value
+ */
+static int
+asmc_raw_key_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	device_t dev = (device_t) arg1;
+	struct asmc_softc *sc = device_get_softc(dev);
+	char newkey[ASMC_KEYLEN + 1];
+	uint8_t keylen;
+	int error;
+
+	strlcpy(newkey, sc->sc_rawkey, sizeof(newkey));
+	error = sysctl_handle_string(oidp, newkey, sizeof(newkey), req);
+	if (error || req->newptr == NULL)
+		return (error);
+
+	if (strlen(newkey) != ASMC_KEYLEN)
+		return (EINVAL);
+
+	/* Get key info to auto-detect length and type */
+	if (asmc_key_getinfo(dev, newkey, &keylen, sc->sc_rawtype) != 0)
+		return (ENOENT);
+
+	if (keylen > ASMC_MAXVAL)
+		keylen = ASMC_MAXVAL;
+
+	strlcpy(sc->sc_rawkey, newkey, sizeof(sc->sc_rawkey));
+	sc->sc_rawlen = keylen;
+	memset(sc->sc_rawval, 0, sizeof(sc->sc_rawval));
+
+	/* Read the key value */
+	asmc_key_read(dev, sc->sc_rawkey, sc->sc_rawval, sc->sc_rawlen);
+
+	return (0);
+}
+
+static int
+asmc_raw_value_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	device_t dev = (device_t) arg1;
+	struct asmc_softc *sc = device_get_softc(dev);
+	char hexbuf[ASMC_MAXVAL * 2 + 1];
+	int error, i;
+
+	/* Refresh from SMC if a key has been selected. */
+	if (sc->sc_rawkey[0] != '\0') {
+		asmc_key_read(dev, sc->sc_rawkey, sc->sc_rawval,
+		    sc->sc_rawlen > 0 ? sc->sc_rawlen : ASMC_MAXVAL);
+	}
+
+	/* Format as hex string */
+	for (i = 0; i < sc->sc_rawlen && i < ASMC_MAXVAL; i++)
+		snprintf(hexbuf + i * 2, 3, "%02x", sc->sc_rawval[i]);
+	hexbuf[i * 2] = '\0';
+
+	error = sysctl_handle_string(oidp, hexbuf, sizeof(hexbuf), req);
+	if (error || req->newptr == NULL)
+		return (error);
+
+	/* Reject writes until a key is selected via raw.key. */
+	if (sc->sc_rawkey[0] == '\0')
+		return (EINVAL);
+
+	memset(sc->sc_rawval, 0, sizeof(sc->sc_rawval));
+	for (i = 0; i < sc->sc_rawlen && hexbuf[i*2] && hexbuf[i*2+1]; i++) {
+		unsigned int val;
+		char tmp[3] = { hexbuf[i*2], hexbuf[i*2+1], 0 };
+		if (sscanf(tmp, "%02x", &val) == 1)
+			sc->sc_rawval[i] = (uint8_t)val;
+	}
+
+	if (asmc_key_write(dev, sc->sc_rawkey, sc->sc_rawval, sc->sc_rawlen) != 0)
+		return (EIO);
+
+	return (0);
+}
+
+static int
+asmc_raw_len_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	device_t dev = (device_t) arg1;
+	struct asmc_softc *sc = device_get_softc(dev);
+
+	return (sysctl_handle_8(oidp, &sc->sc_rawlen, 0, req));
+}
+
+static int
+asmc_raw_type_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	device_t dev = (device_t) arg1;
+	struct asmc_softc *sc = device_get_softc(dev);
+
+	return (sysctl_handle_string(oidp, sc->sc_rawtype,
+	    sizeof(sc->sc_rawtype), req));
+}
 #endif
 
 static int
diff --git a/sys/dev/asmc/asmcvar.h b/sys/dev/asmc/asmcvar.h
index cfc176559ed9..0e8d4e9d4a36 100644
--- a/sys/dev/asmc/asmcvar.h
+++ b/sys/dev/asmc/asmcvar.h
@@ -28,6 +28,9 @@
  */
 
 #define ASMC_MAXFANS	6
+#define ASMC_MAXVAL	32	/* Maximum SMC value size */
+#define ASMC_KEYLEN	4	/* SMC key name length */
+#define ASMC_TYPELEN	4	/* SMC type string length */
 
 struct asmc_softc {
 	device_t 		sc_dev;
@@ -53,6 +56,14 @@ struct asmc_softc {
 	uint8_t			sc_sms_intr_works;
 	struct cdev		*sc_kbd_bkl;
 	uint32_t		sc_kbd_bkl_level;
+#ifdef ASMC_DEBUG
+	/* Raw key access */
+	struct sysctl_oid	*sc_raw_tree;
+	char			sc_rawkey[ASMC_KEYLEN + 1];
+	uint8_t			sc_rawval[ASMC_MAXVAL];
+	uint8_t			sc_rawlen;
+	char			sc_rawtype[ASMC_TYPELEN + 1];
+#endif
 };
 
 /*
@@ -71,6 +82,14 @@ struct asmc_softc {
 	bus_write_1(sc->sc_ioport, 0x04, val)
 #define ASMC_CMDREAD		0x10
 #define ASMC_CMDWRITE		0x11
+#define ASMC_CMDGETBYINDEX	0x12
+#define ASMC_CMDGETINFO		0x13
+
+#define ASMC_STATUS_AWAIT_DATA	0x04
+#define ASMC_STATUS_DATA_READY	0x05
+
+#define ASMC_KEYINFO_RESPLEN	6	/* getinfo: 1 len + 4 type + 1 attr */
+#define ASMC_MAXRETRIES		10
 
 /*
  * Interrupt port.


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69df13d5.3084c.15512e6d>