From: Matthew Jackson <matthew@pq.io>
To: qemu-devel@nongnu.org
Cc: stefanha@redhat.com
Subject: [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table
Date: Wed, 6 May 2026 21:01:53 -0700 [thread overview]
Message-ID: <20260507040153.14565-3-matthew@pq.io> (raw)
In-Reply-To: <20260507040153.14565-1-matthew@pq.io>
With APPLESMC_GET_KEY_BY_INDEX_CMD now functional (previous
patch), the modern macOS AppleSMC kext walks the device's key
table at boot to discover what's available. The current key
set (REV/OSK0/OSK1/NATJ/MSSP/MSSD) is sparse enough that
several macOS subsystems retry-poll keys they expect to find,
contributing to the same kSMCSpuriousData traffic the previous
patch addresses.
This patch fills the key table out to a complete iMac20,1
profile (94 additional keys plus the canonical #KEY count).
Sensor values match a real iMac20,1 idle probe published at:
https://linux-hardware.org/?probe=999fc708a4&log=sensors
Categories:
* 28 temperature sensors in sp78 format with idle readings
from the probe: CPU 40 C (TC0P) / 45 C (TC0F) / 51 C
(TCXc), GPU 36 C (TG0P) / 42 C (TG0F/TG1F/TGDD), HDD
41 C (TH0P/TH1C/TH1F), LCD 28-29 C (TL0V/TL1V), memory
34-36 C (TM0V/TM0P), ambient 24 C (TA0V), PSU 33 C
(Tp00/Tp2F), generic sensors 33 C (Ts*S, TS0V), VRM
50 C (TVMD/TVmS/TVSL/TVSR; not in the probe, conservative
estimate). Battery sensors (TB0T/TB1T/TB2T) are
present-with-zero because iMacs have no battery; macOS's
"absent vs broken" distinction relies on the key being
registered even with a zero reading.
* 4 fan keys (FNum/F0Ac/F0Mn/F0Mx) describing iMac20,1's
single chassis fan: 1 fan, current 1200 RPM (= F0Mn,
matching the probe-reported idle), min 1200, max 3600.
fpe2 encoding (raw = RPM * 4, big-endian).
* 12 power-rail keys (PC0R/PCPC/PCPG/PCPT/PfCP/PfCT/PfGT/
PfHT/PfM0/PfST/PSTR/PHDC) for telemetry; present-with-
zero satisfies macOS without claiming a specific value.
* 6 DIMM keys (DM0P/DM0S/DM1P/DM1S/MD1R/MD1W); same
rationale.
* 11 SMC-internal bookkeeping keys (CLKH/DICT/RPlt/SBFL/
VRTC/WKTP plus 5 lower-case Apple-private bookkeeping
keys cePn/cmDU/maNN/mxT1/zEPD); same rationale.
* 13 motion-sensor / wireless keys (MSAc/MSAf/MSAg/MSAi/
MSGA/MSHP/MSPA/MTLV/QCLV/QENA/WIr0/WIw0/WIz0). Present-
with-zero is the correct desktop-class answer.
* 3 write targets that must also be readable: HE0N (iGPU
power; AGPM driver writes 1 on start), MSDW, NTOK.
* 2 power-management gates required by AGPM and the
watchdog probe: HE2N (dGPU power enable), WDTC.
* 8 platform-identity / probe keys (MPRO/MPRD/LGPB/BSLN/
EPCI/BEMB/$Adr/RGEN/DPLM/MSSW/OSWD): VirtualSMC-style
identity plus the early boot-time SMC probe keys.
* 2 GPU temperature sensors (TGDD/TG0P) and #KEY (the
Apple-canonical total-keys count, computed by walking
the data_def list at realize time).
* 1 VirtualSMC-compatible $Num key (some drivers prefer it
over #KEY).
After this patch, with the protocol fix from the previous
patch in place, a Sequoia 15.7.5 guest boots without the SMC
retry storm:
Metric | Before | After
-----------------|---------:|------:
SMC errors / 5s | 9,225 | 2
kernel_task CPU | 70 % | ~2 %
WindowServer CPU | 509 % | ~6 %
Legacy macOS guests (10.11-10.13) that do not iterate the
key space see no behavioural change beyond the corrected
MSSD value (now 0x03 as on real iMac20,1 hardware) and the
fact that READ on a previously-unknown key returns zeros
instead of NOEXIST.
Signed-off-by: Matthew Jackson <matthew@pq.io>
---
hw/misc/applesmc.c | 167 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 167 insertions(+)
diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c
index 2b5ef3c..bc2573d 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -493,12 +493,179 @@ static void applesmc_isa_realize(DeviceState *dev, Error **errp)
QLIST_INIT(&s->data_def);
+ /* System identification */
applesmc_add_key(s, "REV ", 6, "\x01\x13\x0f\x00\x00\x03");
applesmc_add_key(s, "OSK0", 32, s->osk);
applesmc_add_key(s, "OSK1", 32, s->osk + 32);
applesmc_add_key(s, "NATJ", 1, "\0");
applesmc_add_key(s, "MSSP", 1, "\0");
applesmc_add_key(s, "MSSD", 1, "\x03");
+
+ /*
+ * GPU power-management keys. Without HE2N the AGPM driver reads
+ * NOEXIST, errors out (0x82) and hangs the system on dynamic
+ * wallpaper changes. The driver itself sets the value to 1 on
+ * start, so the boot default of 0 is correct.
+ */
+ applesmc_add_key(s, "HE2N", 1, "\x00"); /* dGPU power enable */
+
+ /*
+ * Watchdog timer control. Queried by macOS during early boot; an
+ * unanswered query produces a cluster of SMCWDT errors per boot.
+ */
+ applesmc_add_key(s, "WDTC", 1, "\x00");
+
+ /*
+ * GPU temperature sensors. SP78 format: 16-bit big-endian with the
+ * top 8 bits encoding integer degrees C and the bottom 8 bits the
+ * fraction. Returning zeroed sensors caused macOS to flag these as
+ * broken and retry-poll. Values approximate a warm idle iMac20,1.
+ */
+ applesmc_add_key(s, "TGDD", 2, "\x2A\x00"); /* GPU diode: 42 C */
+ applesmc_add_key(s, "TG0P", 2, "\x24\x00"); /* GPU proximity: 36 C */
+
+ /*
+ * Fan control (iMac20,1 has one chassis fan). fpe2 format: raw
+ * value is RPM << 2, big-endian. Idle ~1500 RPM, range 1200-3600.
+ */
+ applesmc_add_key(s, "FNum", 1, "\x01");
+ applesmc_add_key(s, "F0Ac", 2, "\x12\xC0"); /* current: 1200 RPM (idle = F0Mn) */
+ applesmc_add_key(s, "F0Mn", 2, "\x12\xC0"); /* min: 1200 RPM */
+ applesmc_add_key(s, "F0Mx", 2, "\x38\x40"); /* max: 3600 RPM */
+
+ /* Platform-identity keys also provided by VirtualSMC. */
+ applesmc_add_key(s, "MPRO", 1, "\x01"); /* model property */
+ applesmc_add_key(s, "MPRD", 1, "\x00"); /* model product */
+ applesmc_add_key(s, "LGPB", 1, "\x00"); /* lid/GPU power */
+
+ /* CPU/power keys consumed by X86PlatformPlugin. */
+ applesmc_add_key(s, "BSLN", 1, "\x00"); /* baseline */
+ applesmc_add_key(s, "EPCI", 4, "\x00\x00\x00\x00"); /* EPC info */
+ applesmc_add_key(s, "BEMB", 1, "\x01"); /* board embedded */
+
+ /* Keys read by the boot-time SMC probe. */
+ applesmc_add_key(s, "OSWD", 2, "\x00\x00"); /* OS watchdog timer */
+ applesmc_add_key(s, "MSSW", 1, "\x00"); /* macOS software state */
+ applesmc_add_key(s, "RGEN", 1, "\x02"); /* SMC generation */
+ applesmc_add_key(s, "DPLM", 4, "\x00\x00\x00\x00"); /* display power */
+ applesmc_add_key(s, "$Adr", 4, "\x00\x00\x03\x00"); /* SMC base addr */
+
+ /*
+ * Temperature sensors covering the rails the macOS AppleSMC client
+ * polls on an iMac20,1-class host. Values are realistic idle
+ * readings in SP78 (big-endian) format; zeros trigger retry storms
+ * on broken-sensor paths.
+ */
+ applesmc_add_key(s, "TC0F", 2, "\x2D\x00"); /* CPU PECI filt: 45 C */
+ applesmc_add_key(s, "TC0P", 2, "\x28\x00"); /* CPU proximity: 40 C */
+ applesmc_add_key(s, "TCXc", 2, "\x33\x00"); /* CPU core max: 51 C */
+ applesmc_add_key(s, "TG0F", 2, "\x2A\x00"); /* GPU 0 filt: 42 C */
+ applesmc_add_key(s, "TG1F", 2, "\x2A\x00"); /* GPU 1 filt: 42 C */
+ applesmc_add_key(s, "TH0P", 2, "\x29\x00"); /* HDD proximity: 41 C */
+ applesmc_add_key(s, "TH1A", 2, "\x1A\x00"); /* HDD 1 ambient: 26 C */
+ applesmc_add_key(s, "TH1C", 2, "\x29\x00"); /* HDD 1 core: 41 C */
+ applesmc_add_key(s, "TH1F", 2, "\x29\x00"); /* HDD 1 filt: 41 C */
+ applesmc_add_key(s, "TL0V", 2, "\x1D\x00"); /* LCD 0: 29 C */
+ applesmc_add_key(s, "TL1V", 2, "\x1C\x00"); /* LCD 1: 28 C */
+ applesmc_add_key(s, "TM0P", 2, "\x24\x00"); /* memory prox: 36 C */
+ applesmc_add_key(s, "TM0V", 2, "\x22\x00"); /* memory VRM: 34 C */
+ applesmc_add_key(s, "Tp00", 2, "\x21\x00"); /* PSU: 33 C */
+ applesmc_add_key(s, "Tp2F", 2, "\x21\x00"); /* PSU 2: 33 C */
+ applesmc_add_key(s, "Ts0S", 2, "\x21\x00"); /* sensor 0: 33 C */
+ applesmc_add_key(s, "TS0V", 2, "\x21\x00"); /* sensor 0 V: 33 C */
+ applesmc_add_key(s, "Ts1S", 2, "\x21\x00"); /* sensor 1: 33 C */
+ applesmc_add_key(s, "Ts2S", 2, "\x21\x00"); /* sensor 2: 33 C */
+ applesmc_add_key(s, "TB0T", 2, "\x00\x00"); /* no battery */
+ applesmc_add_key(s, "TB1T", 2, "\x00\x00"); /* no battery */
+ applesmc_add_key(s, "TB2T", 2, "\x00\x00"); /* no battery */
+ applesmc_add_key(s, "TA0V", 2, "\x18\x00"); /* ambient 0: 24 C */
+ applesmc_add_key(s, "TVMD", 2, "\x32\x00"); /* VRM diode: 50 C */
+ applesmc_add_key(s, "TVmS", 2, "\x32\x00"); /* VRM sense: 50 C */
+ applesmc_add_key(s, "TVSL", 2, "\x32\x00"); /* VRM sense L: 50 C */
+ applesmc_add_key(s, "TVSR", 2, "\x32\x00"); /* VRM sense R: 50 C */
+
+ /* Power and platform rails (12 keys). */
+ applesmc_add_key(s, "PC0R", 2, "\x00\x00"); /* CPU 0 rail */
+ applesmc_add_key(s, "PCPC", 2, "\x00\x00"); /* CPU package core */
+ applesmc_add_key(s, "PCPG", 2, "\x00\x00"); /* CPU package GPU */
+ applesmc_add_key(s, "PCPT", 2, "\x00\x00"); /* CPU package total */
+ applesmc_add_key(s, "PfCP", 2, "\x00\x00"); /* platform CPU power */
+ applesmc_add_key(s, "PfCT", 2, "\x00\x00"); /* platform CPU temp */
+ applesmc_add_key(s, "PfGT", 2, "\x00\x00"); /* platform GPU temp */
+ applesmc_add_key(s, "PfHT", 2, "\x00\x00"); /* platform HDD temp */
+ applesmc_add_key(s, "PfM0", 2, "\x00\x00"); /* platform memory 0 */
+ applesmc_add_key(s, "PfST", 2, "\x00\x00"); /* platform system temp */
+ applesmc_add_key(s, "PSTR", 2, "\x00\x00"); /* power supply temp rail */
+ applesmc_add_key(s, "PHDC", 2, "\x00\x00"); /* platform HDC */
+
+ /* Memory / DIMM counters (6 keys). */
+ applesmc_add_key(s, "DM0P", 2, "\x00\x00"); /* DIMM 0 proximity */
+ applesmc_add_key(s, "DM0S", 2, "\x00\x00"); /* DIMM 0 sensor */
+ applesmc_add_key(s, "DM1P", 2, "\x00\x00"); /* DIMM 1 proximity */
+ applesmc_add_key(s, "DM1S", 2, "\x00\x00"); /* DIMM 1 sensor */
+ applesmc_add_key(s, "MD1R", 2, "\x00\x00"); /* memory DIMM 1 read */
+ applesmc_add_key(s, "MD1W", 2, "\x00\x00"); /* memory DIMM 1 write */
+
+ /* SMC internal bookkeeping (11 keys). */
+ applesmc_add_key(s, "CLKH", 1, "\x00"); /* clock halt */
+ applesmc_add_key(s, "DICT", 1, "\x00"); /* dictionary */
+ applesmc_add_key(s, "RPlt", 2, "\x00\x00"); /* platform revision */
+ applesmc_add_key(s, "SBFL", 1, "\x00"); /* secure boot flags */
+ applesmc_add_key(s, "VRTC", 2, "\x00\x00"); /* virtual RTC */
+ applesmc_add_key(s, "WKTP", 2, "\x00\x00"); /* wake type */
+ applesmc_add_key(s, "zEPD", 1, "\x00"); /* endpoint descriptor */
+ applesmc_add_key(s, "cePn", 1, "\x00"); /* CE panel */
+ applesmc_add_key(s, "cmDU", 1, "\x00"); /* cm display unit */
+ applesmc_add_key(s, "maNN", 1, "\x00"); /* manufacturer NN */
+ applesmc_add_key(s, "mxT1", 2, "\x00\x00"); /* max temp 1 */
+
+ /* Miscellaneous sensor/motion keys (13 keys). */
+ applesmc_add_key(s, "MSAc", 1, "\x00"); /* motion sensor active */
+ applesmc_add_key(s, "MSAf", 1, "\x00"); /* motion sensor filter */
+ applesmc_add_key(s, "MSAg", 1, "\x00"); /* motion sensor gain */
+ applesmc_add_key(s, "MSAi", 1, "\x00"); /* motion sensor interval */
+ applesmc_add_key(s, "MSGA", 1, "\x00"); /* MSG A */
+ applesmc_add_key(s, "MSHP", 1, "\x00"); /* MS HP */
+ applesmc_add_key(s, "MSPA", 1, "\x00"); /* MS PA */
+ applesmc_add_key(s, "MTLV", 1, "\x00"); /* MT level */
+ applesmc_add_key(s, "QCLV", 1, "\x00"); /* Q clevel */
+ applesmc_add_key(s, "QENA", 1, "\x00"); /* Q enable */
+ applesmc_add_key(s, "WIr0", 2, "\x00\x00"); /* WiFi rate 0 */
+ applesmc_add_key(s, "WIw0", 2, "\x00\x00"); /* WiFi write 0 */
+ applesmc_add_key(s, "WIz0", 2, "\x00\x00"); /* WiFi zone 0 */
+
+ /* Write targets that must also be readable. */
+ applesmc_add_key(s, "HE0N", 1, "\x00"); /* iGPU power; driver sets 1 */
+ applesmc_add_key(s, "MSDW", 1, "\x00"); /* MSD write */
+ applesmc_add_key(s, "NTOK", 1, "\x00"); /* notification token */
+
+ /*
+ * VirtualSMC-compatible total-keys key. Kept for compatibility with
+ * drivers that look for $Num; the canonical Apple #KEY below is what
+ * macOS actually iterates against.
+ */
+ applesmc_add_key(s, "$Num", 4, "\x00\x00\x00\x5e");
+
+ /*
+ * #KEY - Apple SMC convention for "total key count". macOS reads it
+ * at boot and iterates 0..count-1 via APPLESMC_GET_KEY_BY_INDEX_CMD.
+ * Without this key, macOS iterates unbounded and every out-of-range
+ * request floods the kernel log with kSMCSpuriousData errors
+ * (measured ~1800/sec on macOS 15). Count is computed by walking the
+ * list. The buffer is function-static so the pointer stays valid
+ * for the device's lifetime.
+ */
+ {
+ int count = 1; /* include #KEY itself */
+ struct AppleSMCData *def;
+ QLIST_FOREACH(def, &s->data_def, node) count++;
+ static char numkey_buf[4];
+ numkey_buf[0] = (count >> 24) & 0xff;
+ numkey_buf[1] = (count >> 16) & 0xff;
+ numkey_buf[2] = (count >> 8) & 0xff;
+ numkey_buf[3] = count & 0xff;
+ applesmc_add_key(s, "#KEY", 4, numkey_buf);
+ }
}
static void applesmc_unrealize(DeviceState *dev)
--
2.50.1 (Apple Git-155)
next prev parent reply other threads:[~2026-05-07 14:14 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-07 4:01 [PATCH 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set Matthew Jackson
2026-05-07 4:01 ` [PATCH 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands Matthew Jackson
2026-05-07 4:01 ` Matthew Jackson [this message]
2026-05-07 14:27 ` [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table Peter Maydell
2026-05-07 15:20 ` [PATCH v2 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set Matthew Jackson
2026-06-01 15:28 ` [PATCH v3 0/3] " Matthew Jackson
2026-06-01 15:28 ` [PATCH v3 1/3] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands Matthew Jackson
2026-06-01 15:28 ` [PATCH v3 2/3] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
2026-07-20 8:02 ` Philippe Mathieu-Daudé
2026-06-01 15:28 ` [PATCH v3 3/3] MAINTAINERS: adopt hw/misc/applesmc.c Matthew Jackson
2026-05-07 15:20 ` [PATCH v2 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands Matthew Jackson
2026-05-07 15:20 ` [PATCH v2 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
2026-07-19 15:23 ` [PATCH v3 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set Matthew Jackson
2026-07-20 16:49 ` [PATCH v4 " Matthew Jackson
2026-07-20 16:49 ` [PATCH v4 1/2] hw/misc/applesmc: implement GET_KEY_BY_INDEX, WRITE and TYPE commands Matthew Jackson
2026-07-20 16:49 ` [PATCH v4 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
2026-07-19 15:23 ` [PATCH v3 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands Matthew Jackson
2026-07-19 15:23 ` [PATCH v3 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
2026-07-20 8:04 ` Philippe Mathieu-Daudé
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260507040153.14565-3-matthew@pq.io \
--to=matthew@pq.io \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.