Index | Thread | Search

From:
Bobby Johnson <bobby@plexuscomp.com>
Subject:
Apple firmware change battery capacity
To:
tech@openbsd.org
Date:
Wed, 4 Nov 2026 21:33:36 -0700

Download raw body.

Thread
  • Bobby Johnson:

    Apple firmware change battery capacity

After updating MacOS on my M1 macbook air, not to MacOS27, it was a minor update for Macos 15, my battery sensor became erratic.  apm reports wild numbers, often above 100%.

Found the behavior and cause documented here - https://github.com/torvalds/linux/commit/f7b253a6e217f71d754d70c525e9b4c1dcbd4414.  Apple changed the byte order for the remaining battery capacity sensor(B0RM).  The diff below checks for a teltale sign of the new firmware and reverses the order in that case.  This fixed the apm output on my laptop.  Claude helped. 


Index: sys/arch/arm64/dev/aplsmc.c
===================================================================
RCS file: /cvs/src/sys/arch/arm64/dev/aplsmc.c,v
diff -u -p -u -p -r1.34 aplsmc.c
--- sys/arch/arm64/dev/aplsmc.c	13 Oct 2026 12:30:12 -0000	1.34
+++ sys/arch/arm64/dev/aplsmc.c	5 Nov 2026 00:30:04 -0000
@@ -114,6 +114,7 @@ struct aplsmc_sensor {
 
 #define APLSMC_BE		(1 << 0)
 #define APLSMC_HIDDEN		(1 << 1)
+#define APLSMC_BE_OLDFW		(1 << 2)	/* BE on older firmware only */
 
 #define APLSMC_MAX_SENSORS	19
 
@@ -143,6 +144,7 @@ struct aplsmc_softc {
 	struct ksensordev	sc_sensordev;
 	int			sc_nbatteries;
 	uint32_t		sc_suspend_pstr;
+	int			sc_newfw;	/* 1-byte BCF0, LE B0RM */
 };
 
 #define CH0I_DISCHARGE		(1 << 0)
@@ -156,7 +158,7 @@ struct aplsmc_softc *aplsmc_sc;
 struct aplsmc_sensor aplsmc_sensors[] = {
 	{ "ACDI", "ui16", SENSOR_INDICATOR, 1, "power supply" },
 	{ "B0RM", "ui16", SENSOR_AMPHOUR, 1000, "remaining battery capacity",
-	  APLSMC_BE },
+	  APLSMC_BE_OLDFW },
 	{ "B0FC", "ui16", SENSOR_AMPHOUR, 1000, "last full battery capacity" },
 	{ "B0DC", "ui16", SENSOR_AMPHOUR, 1000, "battery design capacity" },
 	{ "B0AV", "ui16", SENSOR_VOLTS_DC, 1000, "battery" },
@@ -198,6 +200,8 @@ int	aplsmc_send_cmd(struct aplsmc_softc 
 int	aplsmc_wait_cmd(struct aplsmc_softc *sc);
 int	aplsmc_read_key(struct aplsmc_softc *, uint32_t, void *, size_t);
 int	aplsmc_write_key(struct aplsmc_softc *, uint32_t, void *, size_t);
+int	aplsmc_get_key_info(struct aplsmc_softc *, uint32_t,
+	    struct smc_key_info *);
 int64_t aplsmc_convert_flt(uint32_t, int);
 void	aplsmc_refresh_sensors(void *);
 int	aplsmc_apminfo(struct apm_power_info *);
@@ -231,6 +235,7 @@ aplsmc_attach(struct device *parent, str
 	uint8_t ntap;
 	int error, node;
 #ifndef SMALL_KERNEL
+	struct smc_key_info info;
 	int i;
 #endif
 
@@ -322,18 +327,20 @@ aplsmc_attach(struct device *parent, str
 
 #ifndef SMALL_KERNEL
 
-	for (i = 0; i < nitems(aplsmc_sensors); i++) {
-		struct smc_key_info info;
+	/*
+	 * Newer SMC firmware changed the BCF0 key from 4 bytes to 1
+	 * byte and at the same time switched B0RM from big-endian to
+	 * little-endian.  Use the size of BCF0 to tell them apart.
+	 */
+	if (aplsmc_get_key_info(sc, SMC_KEY("BCF0"), &info) == 0 &&
+	    info.size == 1)
+		sc->sc_newfw = 1;
 
-		aplsmc_send_cmd(sc, SMC_GET_KEY_INFO,
-		    SMC_KEY(aplsmc_sensors[i].key), 0);
-		error = aplsmc_wait_cmd(sc);
-		if (error || SMC_ERROR(sc->sc_data) != SMC_OK)
+	for (i = 0; i < nitems(aplsmc_sensors); i++) {
+		if (aplsmc_get_key_info(sc, SMC_KEY(aplsmc_sensors[i].key),
+		    &info) != 0)
 			continue;
 
-		bus_space_read_region_1(sc->sc_iot, sc->sc_sram_ioh, 0,
-		    (uint8_t *)&info, sizeof(info));
-
 		/* Skip if the key type doesn't match. */
 		if (memcmp(aplsmc_sensors[i].key_type, info.type,
 		    sizeof(info.type)) != 0)
@@ -581,6 +588,24 @@ aplsmc_write_key(struct aplsmc_softc *sc
 
 #ifndef SMALL_KERNEL
 
+int
+aplsmc_get_key_info(struct aplsmc_softc *sc, uint32_t key,
+    struct smc_key_info *info)
+{
+	int error;
+
+	aplsmc_send_cmd(sc, SMC_GET_KEY_INFO, key, 0);
+	error = aplsmc_wait_cmd(sc);
+	if (error)
+		return error;
+	if (SMC_ERROR(sc->sc_data) != SMC_OK)
+		return EINVAL;
+
+	bus_space_read_region_1(sc->sc_iot, sc->sc_sram_ioh, 0,
+	    (uint8_t *)info, sizeof(*info));
+	return 0;
+}
+
 int64_t
 aplsmc_convert_flt(uint32_t flt, int scale)
 {
@@ -624,7 +649,9 @@ aplsmc_refresh_sensors(void *arg)
 			uint16_t ui16;
 
 			error = aplsmc_read_key(sc, key, &ui16, sizeof(ui16));
-			if (sensor->flags & APLSMC_BE)
+			if ((sensor->flags & APLSMC_BE) ||
+			    ((sensor->flags & APLSMC_BE_OLDFW) &&
+			    !sc->sc_newfw))
 				ui16 = betoh16(ui16);
 			value = (int64_t)ui16 * sensor->scale;
 		} else if (strcmp(sensor->key_type, "flt ") == 0) {