:p
atchew
Login
The QEMU applesmc device implements just enough of the Apple SMC PMIO protocol to satisfy the OSK boot check on older macOS versions. On modern macOS guests (x86 10.14+, all of the 15.x series) the real AppleSMC kext enumerates the SMC key space at boot via APPLESMC_GET_KEY_BY_INDEX_CMD (0x12). The current device only acknowledges APPLESMC_READ_CMD (0x10) at the command port; every other command falls through to the default arm of the switch and sets ST_1E_BAD_CMD. The macOS driver interprets the resulting 0x82 reply as "spurious data" and enters a retry loop that floods the kernel log with kSMCSpuriousData (0x81) / kSMCKeyNotFound errors at roughly 1800 events per second, pegging kernel_task at ~70% CPU and WindowServer at ~509% CPU. This reproduces reliably on any recent macOS 15 guest booted with -device isa-applesmc,osk=<valid-OSK>. This two-patch series fixes the protocol-level bug and rounds out the SMC key table to a complete iMac20,1 profile. Patch 1: protocol-level fix - Accept WRITE_CMD, GET_KEY_BY_INDEX_CMD, GET_KEY_TYPE_CMD at the command port (in addition to READ_CMD). - Implement the indexed-iteration walker (returns real key names from s->data_def, or APPLESMC_ST_1E_BAD_INDEX 0xb8 once the index is past the end so the guest stops iterating). - Implement GET_KEY_TYPE returning a 6-byte type/size/attr response matching VirtualSMC's kern_pmio.cpp behaviour. - Accept and log WRITE_CMD silently. - Replace the unknown-key NOEXIST (0x84) reply with a zeroed payload of the requested length, logged at LOG_UNIMP. - Route the BAD_CMD path through qemu_log_mask(LOG_GUEST_ERROR). - Fix MSSD initialiser typo ("\0x3" -> "\x03"). The original literal was three bytes ('\0', 'x', '3') truncated to one ('\0') by the size argument, so MSSD has been silently returning 0 since the device was introduced; the corrected value matches what a real iMac20,1 SMC reports. Patch 2: populate the key table - Add 94 keys covering the categories macOS queries on a Sequoia 15.7.5 guest: 28 temperature sensors (sp78), 4 fan keys (fpe2), 12 power-rail keys, 6 DIMM keys, 11 SMC-internal bookkeeping, 13 motion-sensor / wireless, 3 write targets (HE0N/MSDW/NTOK), 2 power-management gates (HE2N/WDTC), 8 platform-identity / probe keys, plus the Apple-canonical #KEY total-count. - Sensor values match a real iMac20,1 idle probe published at https://linux-hardware.org/?probe=999fc708a4&log=sensors: CPU 40-51 C, GPU 36-42 C, fan at 1200 RPM (= F0Mn idle), etc. Measured impact (macOS 15.7.5 guest, iMac20,1 profile): Metric | Before | After -----------------|---------:|------: SMC errors / 5s | 9,225 | 2 kernel_task CPU | 70 % | ~2 % WindowServer CPU | 509 % | ~6 % A note for review on the zero-valued keys in patch 2: the 26 keys covering DIMM / SMC bookkeeping / motion-sensor / wireless rails are registered with present-with-zero values rather than omitted. macOS distinguishes "absent" (NOEXIST reply, retry-poll) from "broken" (present, value 0, accepted-and-ignored). Registering these keys present-with-zero stops the retry-poll behaviour without asserting any specific value. If the maintainer prefers a tighter scope for this series I am happy to drop any subset and follow up; the present-with-zero approach was driven by which keys macOS observed querying during boot. Backwards compatibility: legacy macOS guests (10.11-10.13) which do not iterate the key space via GET_KEY_BY_INDEX boot unchanged. The original six keys (REV/OSK0/OSK1/NATJ/MSSP/MSSD) are still present and respond with the same values, modulo the MSSD typo fix in patch 1 which corrects MSSD to the value a real iMac20,1 SMC reports. Tested against current master (post v11.0.0). Builds clean on gcc-13 / clang-17 with --enable-werror. Sequoia 15.7.5 guest boots to login screen with the SMC retry storm absent from the kernel log; smoke test recipe in TESTING.md alongside the series. Matthew Jackson (2): hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands hw/misc/applesmc: populate Apple SMC key table hw/misc/applesmc.c | 344 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 337 insertions(+), 7 deletions(-) -- 2.50.1 (Apple Git-155)
The applesmc device implements just enough of the Apple SMC PMIO protocol to satisfy the OSK boot check on older macOS versions. On modern macOS guests (x86 10.14+, all 15.x), the AppleSMC kext enumerates the SMC key space at boot via APPLESMC_GET_KEY_BY_INDEX_CMD (0x12). The current device only acknowledges APPLESMC_READ_CMD (0x10) at the command port; every other command falls through to the default arm of the switch and sets ST_1E_BAD_CMD. The macOS driver interprets the resulting 0x82 reply as "spurious data" and enters a retry loop that floods the kernel log with kSMCSpuriousData (0x81) / kSMCKeyNotFound errors at roughly 1800 events per second, pegging kernel_task at ~70% CPU and WindowServer at ~509% CPU. This reproduces reliably on any recent macOS 15 guest booted with -device isa-applesmc,osk=<valid-OSK>. This patch: * Accepts APPLESMC_WRITE_CMD, APPLESMC_GET_KEY_BY_INDEX_CMD, and APPLESMC_GET_KEY_TYPE_CMD at the command port (in addition to the existing READ_CMD path). * Implements GET_KEY_BY_INDEX by walking s->data_def and returning the 4-byte ASCII key name at the requested index; returns APPLESMC_ST_1E_BAD_INDEX (0xb8) once the index is past the end of the list so the guest stops iterating. * Implements GET_KEY_TYPE by looking up the key in s->data_def and returning a 6-byte response (type[4] + size[1] + attr[1]) matching VirtualSMC's kern_pmio.cpp behaviour. * Implements WRITE by accepting the key name, length, and payload and logging at LOG_UNIMP. macOS writes SMC keys during normal power management; silent acceptance avoids BAD_CMD on every write. * Replaces the unknown-key NOEXIST (0x84) reply on READ with a zeroed payload of the requested length, logged at LOG_UNIMP. Early-boot probes hit hundreds of undocumented keys per second; NOEXIST triggers retry storms while a zeroed payload satisfies the probe semantics without asserting a particular value. * Routes the BAD_CMD path through qemu_log_mask (LOG_GUEST_ERROR) instead of the smc_debug printf. * Fixes a typo in the MSSD key initialiser ("\0x3" -> "\x03"). The original literal was three bytes ('\\0', 'x', '3') truncated to one ('\\0') by the size argument, so MSSD has been silently returning 0 since introduction; the corrected value matches what a real iMac20,1 SMC reports. Reported-by: macOS guests booted with -device isa-applesmc since 10.14. Signed-off-by: Matthew Jackson <matthew@pq.io> --- hw/misc/applesmc.c | 177 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 170 insertions(+), 7 deletions(-) diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/applesmc.c +++ b/hw/misc/applesmc.c @@ -XXX,XX +XXX,XX @@ #include "hw/core/qdev-properties.h" #include "ui/console.h" #include "qemu/error-report.h" +#include "qemu/log.h" #include "qemu/module.h" #include "qemu/timer.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val, smc_debug("CMD received: 0x%02x\n", (uint8_t)val); switch (val) { case APPLESMC_READ_CMD: - /* did last command run through OK? */ + case APPLESMC_WRITE_CMD: + case APPLESMC_GET_KEY_BY_INDEX_CMD: + case APPLESMC_GET_KEY_TYPE_CMD: + /* + * Accept all standard SMC commands. Pre-existing code only handled + * READ_CMD; macOS boots hang if WRITE/TYPE/GET_KEY_BY_INDEX commands + * return BAD_CMD during early AppleSMC driver init. + */ if (status == APPLESMC_ST_CMD_DONE || status == APPLESMC_ST_NEW_CMD) { s->cmd = val; s->status = APPLESMC_ST_NEW_CMD | APPLESMC_ST_ACK; @@ -XXX,XX +XXX,XX @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val, } break; default: - smc_debug("UNEXPECTED CMD 0x%02x\n", (uint8_t)val); + qemu_log_mask(LOG_GUEST_ERROR, + "applesmc: unexpected CMD 0x%02x\n", (uint8_t)val); s->status = APPLESMC_ST_NEW_CMD; s->status_1e = APPLESMC_ST_1E_BAD_CMD; } @@ -XXX,XX +XXX,XX @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val, s->data_len = d->len; s->data_pos = 0; s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; - s->status_1e = APPLESMC_ST_CMD_DONE; /* clear on valid key */ + s->status_1e = APPLESMC_ST_CMD_DONE; } else { - smc_debug("READ_CMD: key '%c%c%c%c' not found!\n", - s->key[0], s->key[1], s->key[2], s->key[3]); + /* + * Return zeros for unknown keys instead of NOEXIST. Early + * macOS boot probes many undocumented keys; responding + * NOEXIST triggers retry storms. A zeroed payload satisfies + * the probe without asserting a particular value. + */ + qemu_log_mask(LOG_UNIMP, + "applesmc: READ unknown key '%c%c%c%c' len=%d\n", + s->key[0], s->key[1], s->key[2], s->key[3], + (uint8_t)val); + memset(s->data, 0, APPLESMC_MAX_DATA_LENGTH); + s->data_len = (uint8_t)val; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; + } + } + s->read_pos++; + break; + case APPLESMC_WRITE_CMD: + /* + * Accept writes silently. macOS writes SMC keys during power + * management, fan control, etc. Log at LOG_UNIMP for visibility + * without treating the write as an error. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 4) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 4) { + s->data_len = (uint8_t)val; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK; + } else { + if (s->data_pos < s->data_len) { + s->data[s->data_pos] = (uint8_t)val; + s->data_pos++; + if (s->data_pos == s->data_len) { + qemu_log_mask(LOG_UNIMP, + "applesmc: WRITE key '%c%c%c%c' len=%d\n", + s->key[0], s->key[1], s->key[2], s->key[3], + s->data_len); + s->status = APPLESMC_ST_CMD_DONE; + s->status_1e = APPLESMC_ST_CMD_DONE; + } else { + s->status = APPLESMC_ST_ACK; + } + } + } + s->read_pos++; + break; + case APPLESMC_GET_KEY_TYPE_CMD: + /* + * Return key type info. Protocol (matches VirtualSMC): + * - Receive 4 bytes of key name. + * - After the 4th byte, immediately set DATA_READY with response. + * - Response is 6 bytes: type[4] + size[1] + attr[1]. + * Unlike READ_CMD there is no length byte between key name and + * response. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 3) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 3) { + /* 4th and final key byte. Unlike READ_CMD which has a 5th byte + * for data length, GET_KEY_TYPE responds immediately after the + * 4-byte key name (matching VirtualSMC kern_pmio.cpp behavior). */ + s->key[3] = val; + d = applesmc_find_key(s); + if (d != NULL) { + switch (d->len) { + case 1: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '8'; s->data[3] = ' '; + break; + case 2: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '1'; s->data[3] = '6'; + break; + case 4: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '3'; s->data[3] = '2'; + break; + default: + s->data[0] = 'c'; s->data[1] = 'h'; + s->data[2] = '8'; s->data[3] = '*'; + break; + } + s->data[4] = d->len; + s->data[5] = 0xD0; + } else { + qemu_log_mask(LOG_UNIMP, + "applesmc: GET_KEY_TYPE unknown '%c%c%c%c'\n", + s->key[0], s->key[1], s->key[2], s->key[3]); + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '8'; s->data[3] = ' '; + s->data[4] = 1; + s->data[5] = 0xD0; + } + s->data_len = 6; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; + } + s->read_pos++; + break; + case APPLESMC_GET_KEY_BY_INDEX_CMD: + /* + * Return key name by index. macOS sends a 4-byte big-endian index + * and expects the 4-byte ASCII key name at that position. The + * previous implementation returned 4 zero bytes, which macOS + * treated as kSMCSpuriousData (0x81) and retried indefinitely, + * flooding the kernel log at ~1800 errors/sec. Walk the keys list + * to return the actual key name, or APPLESMC_ST_1E_BAD_INDEX + * (0xb8) once the index is past the end of the list so the guest + * stops iterating. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 3) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 3) { + s->key[3] = val; + uint32_t idx = ((uint8_t)s->key[0] << 24) + | ((uint8_t)s->key[1] << 16) + | ((uint8_t)s->key[2] << 8) + | (uint8_t)s->key[3]; + struct AppleSMCData *def; + uint32_t i = 0; + bool found = false; + QLIST_FOREACH(def, &s->data_def, node) { + if (i == idx) { + memcpy(s->data, def->key, 4); + s->data_len = 4; + s->data_pos = 0; + found = true; + break; + } + i++; + } + if (!found) { + s->data_len = 0; + s->status_1e = APPLESMC_ST_1E_BAD_INDEX; s->status = APPLESMC_ST_CMD_DONE; - s->status_1e = APPLESMC_ST_1E_NOEXIST; + s->read_pos++; + break; } + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; } s->read_pos++; break; default: + qemu_log_mask(LOG_GUEST_ERROR, + "applesmc: unhandled data for cmd 0x%02x\n", s->cmd); s->status = APPLESMC_ST_CMD_DONE; s->status_1e = APPLESMC_ST_1E_STILL_BAD_CMD; } @@ -XXX,XX +XXX,XX @@ static void applesmc_isa_realize(DeviceState *dev, Error **errp) } QLIST_INIT(&s->data_def); + 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, "\0x3"); + applesmc_add_key(s, "MSSD", 1, "\x03"); } static void applesmc_unrealize(DeviceState *dev) -- 2.50.1 (Apple Git-155)
The applesmc device implements just enough of the Apple SMC PMIO protocol to satisfy the OSK boot check on older macOS versions. On modern macOS guests (x86 10.14+, all 15.x), the AppleSMC kext enumerates the SMC key space at boot via APPLESMC_GET_KEY_BY_INDEX_CMD (0x12). The current device only acknowledges APPLESMC_READ_CMD (0x10) at the command port; every other command falls through to the default arm of the switch and sets ST_1E_BAD_CMD. The macOS driver interprets the resulting 0x82 reply as "spurious data" and enters a retry loop that floods the kernel log with kSMCSpuriousData (0x81) / kSMCKeyNotFound errors at roughly 1800 events per second, pegging kernel_task at ~70% CPU and WindowServer at ~509% CPU. This reproduces reliably on any recent macOS 15 guest booted with -device isa-applesmc,osk=<valid-OSK>. This patch: * Accepts APPLESMC_WRITE_CMD, APPLESMC_GET_KEY_BY_INDEX_CMD, and APPLESMC_GET_KEY_TYPE_CMD at the command port (in addition to the existing READ_CMD path). * Implements GET_KEY_BY_INDEX by walking s->data_def and returning the 4-byte ASCII key name at the requested index; returns APPLESMC_ST_1E_BAD_INDEX (0xb8) once the index is past the end of the list so the guest stops iterating. * Implements GET_KEY_TYPE by looking up the key in s->data_def and returning a 6-byte response (type[4] + size[1] + attr[1]) matching VirtualSMC's kern_pmio.cpp behaviour. * Implements WRITE by accepting the key name, length, and payload and logging at LOG_UNIMP. macOS writes SMC keys during normal power management; silent acceptance avoids BAD_CMD on every write. * Replaces the unknown-key NOEXIST (0x84) reply on READ with a zeroed payload of the requested length, logged at LOG_UNIMP. Early-boot probes hit hundreds of undocumented keys per second; NOEXIST triggers retry storms while a zeroed payload satisfies the probe semantics without asserting a particular value. * Routes the BAD_CMD path through qemu_log_mask (LOG_GUEST_ERROR) instead of the smc_debug printf. * Fixes a typo in the MSSD key initialiser ("\0x3" -> "\x03"). The original literal was three bytes ('\\0', 'x', '3') truncated to one ('\\0') by the size argument, so MSSD has been silently returning 0 since introduction; the corrected value matches what a real iMac20,1 SMC reports. Reported-by: macOS guests booted with -device isa-applesmc since 10.14. Signed-off-by: Matthew Jackson <matthew@pq.io> --- hw/misc/applesmc.c | 177 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 170 insertions(+), 7 deletions(-) diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/applesmc.c +++ b/hw/misc/applesmc.c @@ -XXX,XX +XXX,XX @@ #include "hw/core/qdev-properties.h" #include "ui/console.h" #include "qemu/error-report.h" +#include "qemu/log.h" #include "qemu/module.h" #include "qemu/timer.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val, smc_debug("CMD received: 0x%02x\n", (uint8_t)val); switch (val) { case APPLESMC_READ_CMD: - /* did last command run through OK? */ + case APPLESMC_WRITE_CMD: + case APPLESMC_GET_KEY_BY_INDEX_CMD: + case APPLESMC_GET_KEY_TYPE_CMD: + /* + * Accept all standard SMC commands. Pre-existing code only handled + * READ_CMD; macOS boots hang if WRITE/TYPE/GET_KEY_BY_INDEX commands + * return BAD_CMD during early AppleSMC driver init. + */ if (status == APPLESMC_ST_CMD_DONE || status == APPLESMC_ST_NEW_CMD) { s->cmd = val; s->status = APPLESMC_ST_NEW_CMD | APPLESMC_ST_ACK; @@ -XXX,XX +XXX,XX @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val, } break; default: - smc_debug("UNEXPECTED CMD 0x%02x\n", (uint8_t)val); + qemu_log_mask(LOG_GUEST_ERROR, + "applesmc: unexpected CMD 0x%02x\n", (uint8_t)val); s->status = APPLESMC_ST_NEW_CMD; s->status_1e = APPLESMC_ST_1E_BAD_CMD; } @@ -XXX,XX +XXX,XX @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val, s->data_len = d->len; s->data_pos = 0; s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; - s->status_1e = APPLESMC_ST_CMD_DONE; /* clear on valid key */ + s->status_1e = APPLESMC_ST_CMD_DONE; } else { - smc_debug("READ_CMD: key '%c%c%c%c' not found!\n", - s->key[0], s->key[1], s->key[2], s->key[3]); + /* + * Return zeros for unknown keys instead of NOEXIST. Early + * macOS boot probes many undocumented keys; responding + * NOEXIST triggers retry storms. A zeroed payload satisfies + * the probe without asserting a particular value. + */ + qemu_log_mask(LOG_UNIMP, + "applesmc: READ unknown key '%c%c%c%c' len=%d\n", + s->key[0], s->key[1], s->key[2], s->key[3], + (uint8_t)val); + memset(s->data, 0, APPLESMC_MAX_DATA_LENGTH); + s->data_len = (uint8_t)val; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; + } + } + s->read_pos++; + break; + case APPLESMC_WRITE_CMD: + /* + * Accept writes silently. macOS writes SMC keys during power + * management, fan control, etc. Log at LOG_UNIMP for visibility + * without treating the write as an error. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 4) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 4) { + s->data_len = (uint8_t)val; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK; + } else { + if (s->data_pos < s->data_len) { + s->data[s->data_pos] = (uint8_t)val; + s->data_pos++; + if (s->data_pos == s->data_len) { + qemu_log_mask(LOG_UNIMP, + "applesmc: WRITE key '%c%c%c%c' len=%d\n", + s->key[0], s->key[1], s->key[2], s->key[3], + s->data_len); + s->status = APPLESMC_ST_CMD_DONE; + s->status_1e = APPLESMC_ST_CMD_DONE; + } else { + s->status = APPLESMC_ST_ACK; + } + } + } + s->read_pos++; + break; + case APPLESMC_GET_KEY_TYPE_CMD: + /* + * Return key type info. Protocol (matches VirtualSMC): + * - Receive 4 bytes of key name. + * - After the 4th byte, immediately set DATA_READY with response. + * - Response is 6 bytes: type[4] + size[1] + attr[1]. + * Unlike READ_CMD there is no length byte between key name and + * response. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 3) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 3) { + /* 4th and final key byte. Unlike READ_CMD which has a 5th byte + * for data length, GET_KEY_TYPE responds immediately after the + * 4-byte key name (matching VirtualSMC kern_pmio.cpp behavior). */ + s->key[3] = val; + d = applesmc_find_key(s); + if (d != NULL) { + switch (d->len) { + case 1: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '8'; s->data[3] = ' '; + break; + case 2: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '1'; s->data[3] = '6'; + break; + case 4: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '3'; s->data[3] = '2'; + break; + default: + s->data[0] = 'c'; s->data[1] = 'h'; + s->data[2] = '8'; s->data[3] = '*'; + break; + } + s->data[4] = d->len; + s->data[5] = 0xD0; + } else { + qemu_log_mask(LOG_UNIMP, + "applesmc: GET_KEY_TYPE unknown '%c%c%c%c'\n", + s->key[0], s->key[1], s->key[2], s->key[3]); + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '8'; s->data[3] = ' '; + s->data[4] = 1; + s->data[5] = 0xD0; + } + s->data_len = 6; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; + } + s->read_pos++; + break; + case APPLESMC_GET_KEY_BY_INDEX_CMD: + /* + * Return key name by index. macOS sends a 4-byte big-endian index + * and expects the 4-byte ASCII key name at that position. The + * previous implementation returned 4 zero bytes, which macOS + * treated as kSMCSpuriousData (0x81) and retried indefinitely, + * flooding the kernel log at ~1800 errors/sec. Walk the keys list + * to return the actual key name, or APPLESMC_ST_1E_BAD_INDEX + * (0xb8) once the index is past the end of the list so the guest + * stops iterating. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 3) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 3) { + s->key[3] = val; + uint32_t idx = ((uint8_t)s->key[0] << 24) + | ((uint8_t)s->key[1] << 16) + | ((uint8_t)s->key[2] << 8) + | (uint8_t)s->key[3]; + struct AppleSMCData *def; + uint32_t i = 0; + bool found = false; + QLIST_FOREACH(def, &s->data_def, node) { + if (i == idx) { + memcpy(s->data, def->key, 4); + s->data_len = 4; + s->data_pos = 0; + found = true; + break; + } + i++; + } + if (!found) { + s->data_len = 0; + s->status_1e = APPLESMC_ST_1E_BAD_INDEX; s->status = APPLESMC_ST_CMD_DONE; - s->status_1e = APPLESMC_ST_1E_NOEXIST; + s->read_pos++; + break; } + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; } s->read_pos++; break; default: + qemu_log_mask(LOG_GUEST_ERROR, + "applesmc: unhandled data for cmd 0x%02x\n", s->cmd); s->status = APPLESMC_ST_CMD_DONE; s->status_1e = APPLESMC_ST_1E_STILL_BAD_CMD; } @@ -XXX,XX +XXX,XX @@ static void applesmc_isa_realize(DeviceState *dev, Error **errp) } QLIST_INIT(&s->data_def); + 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, "\0x3"); + applesmc_add_key(s, "MSSD", 1, "\x03"); } static void applesmc_unrealize(DeviceState *dev) -- 2.50.1 (Apple Git-155)
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 | 169 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/applesmc.c +++ b/hw/misc/applesmc.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "hw/isa/isa.h" -#include "hw/core/qdev-properties.h" +#include "hw/qdev-properties.h" #include "ui/console.h" #include "qemu/error-report.h" #include "qemu/log.h" @@ -XXX,XX +XXX,XX @@ 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; + static char numkey_buf[4]; + + QLIST_FOREACH(def, &s->data_def, node) { + count++; + } + stl_be_p(numkey_buf, count); + applesmc_add_key(s, "#KEY", 4, numkey_buf); + } } static void applesmc_unrealize(DeviceState *dev) -- 2.50.1 (Apple Git-155)
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 XXXXXXX..XXXXXXX 100644 --- a/hw/misc/applesmc.c +++ b/hw/misc/applesmc.c @@ -XXX,XX +XXX,XX @@ 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)
v1: https://lore.kernel.org/qemu-devel/20260507040153.14565-1-matthew@pq.io/ v2: https://lore.kernel.org/qemu-devel/20260507152030.48753-1-matthew@pq.io/ This is a rebase/respin of v2, which had no outstanding review comments after Peter's two style nits were addressed. Resending on current master with checkpatch cleared, plus a new patch 3/3 that gives the file a MAINTAINERS entry. Changes in v3: * New patch 3/3: add a MAINTAINERS entry for hw/misc/applesmc.c. The file currently has no entry, so get_maintainer.pl resolves only the open list and these patches had no designated reviewer. This is easy to drop if a maintainer would rather route it elsewhere; patches 1-2 do not depend on it. * Rebased on current master. hw/misc/applesmc.c is byte-identical upstream to the v2 base (blob fd96f5f), so the rebase is a clean no-op with no functional change to patches 1-2 from v2. * checkpatch cleanups in patches 1-2, no functional change: - patch 1/2: reformat the GET_KEY_TYPE 4th-byte block comment to the leading-/* and trailing-*/-on-their-own-line house style. - patch 2/2: shorten the F0Ac fan-key comment to stay under the 80-column limit. Both were pre-existing checkpatch warnings; neither was raised in the v1/v2 review. The series is now checkpatch-clean. Changes in v2 (carried into v3, all in patch 2/2, #KEY block): * Add braces around the QLIST_FOREACH() count loop body (qemu coding style: loops always need braces, even single-line). Reported-by: Peter Maydell <peter.maydell@linaro.org> * Replace the manual 4-byte big-endian byte-shift packing of `count` into `numkey_buf` with a single stl_be_p() call. Reported-by: Peter Maydell <peter.maydell@linaro.org> Patch 1/2 is otherwise unchanged since v1. On Peter's v2 remark that applesmc_add_key()'s "pointer must remain valid forever" contract is awkward: agreed it's not ideal, but the fix (e.g. converting the key table to a glib hashtable that copies values) is orthogonal to this protocol bug fix and isn't reported as noticeable in profiles. Happy to send that conversion as a separate follow-up series if the maintainers think it's worth doing. Original v1 cover letter follows below. --- The QEMU applesmc device implements just enough of the Apple SMC PMIO protocol to satisfy the OSK boot check on older macOS versions. On modern macOS guests (x86 10.14+, all of the 15.x series) the real AppleSMC kext enumerates the SMC key space at boot via APPLESMC_GET_KEY_BY_INDEX_CMD (0x12). The current device only acknowledges APPLESMC_READ_CMD (0x10) at the command port; every other command falls through to the default arm of the switch and sets ST_1E_BAD_CMD. The macOS driver interprets the resulting 0x82 reply as "spurious data" and enters a retry loop that floods the kernel log with kSMCSpuriousData (0x81) / kSMCKeyNotFound errors at roughly 1800 events per second, pegging kernel_task at ~70% CPU and WindowServer at ~509% CPU. This reproduces reliably on any recent macOS 15 guest booted with -device isa-applesmc,osk=<valid-OSK>. This series fixes the protocol-level bug and rounds out the SMC key table to a complete iMac20,1 profile. Patch 1: protocol-level fix - Accept WRITE_CMD, GET_KEY_BY_INDEX_CMD, GET_KEY_TYPE_CMD at the command port (in addition to READ_CMD). - Implement the indexed-iteration walker (returns real key names from s->data_def, or APPLESMC_ST_1E_BAD_INDEX 0xb8 once the index is past the end so the guest stops iterating). - Implement GET_KEY_TYPE returning a 6-byte type/size/attr response matching VirtualSMC's kern_pmio.cpp behaviour. - Accept and log WRITE_CMD silently. - Replace the unknown-key NOEXIST (0x84) reply with a zeroed payload of the requested length, logged at LOG_UNIMP. - Route the BAD_CMD path through qemu_log_mask(LOG_GUEST_ERROR). - Fix MSSD initialiser typo ("\0x3" -> "\x03"). The original literal was three bytes ('\0', 'x', '3') truncated to one ('\0') by the size argument, so MSSD has been silently returning 0 since the device was introduced; the corrected value matches what a real iMac20,1 SMC reports. Patch 2: populate the key table - Add 94 keys covering the categories macOS queries on a Sequoia 15.7.5 guest: 28 temperature sensors (sp78), 4 fan keys (fpe2), 12 power-rail keys, 6 DIMM keys, 11 SMC-internal bookkeeping, 13 motion-sensor / wireless, 3 write targets (HE0N/MSDW/NTOK), 2 power-management gates (HE2N/WDTC), 8 platform-identity / probe keys, plus the Apple-canonical #KEY total-count. - Sensor values match a real iMac20,1 idle probe published at https://linux-hardware.org/?probe=999fc708a4&log=sensors: CPU 40-51 C, GPU 36-42 C, fan at 1200 RPM (= F0Mn idle), etc. Patch 3: MAINTAINERS entry for the device (see "Changes in v3"). Measured impact (macOS 15.7.5 guest, iMac20,1 profile): Metric | Before | After -----------------|---------:|------: SMC errors / 5s | 9,225 | 2 kernel_task CPU | 70 % | ~2 % WindowServer CPU | 509 % | ~6 % A note for review on the zero-valued keys in patch 2: the 26 keys covering DIMM / SMC bookkeeping / motion-sensor / wireless rails are registered with present-with-zero values rather than omitted. macOS distinguishes "absent" (NOEXIST reply, retry-poll) from "broken" (present, value 0, accepted-and-ignored). Registering these keys present-with-zero stops the retry-poll behaviour without asserting any specific value. If the maintainer prefers a tighter scope for this series I am happy to drop any subset and follow up; the present-with-zero approach was driven by which keys macOS observed querying during boot. Backwards compatibility: legacy macOS guests (10.11-10.13) which do not iterate the key space via GET_KEY_BY_INDEX boot unchanged. The original six keys (REV/OSK0/OSK1/NATJ/MSSP/MSSD) are still present and respond with the same values, modulo the MSSD typo fix in patch 1 which corrects MSSD to the value a real iMac20,1 SMC reports. Tested against current master. Builds clean on gcc-13 / clang-17 with --enable-werror. Sequoia 15.7.5 guest boots to login screen with the SMC retry storm absent from the kernel log. Matthew Jackson (3): hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands hw/misc/applesmc: populate Apple SMC key table MAINTAINERS: adopt hw/misc/applesmc.c MAINTAINERS | 5 + hw/misc/applesmc.c | 348 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 345 insertions(+), 8 deletions(-) -- 2.50.1 (Apple Git-155)
The applesmc device implements just enough of the Apple SMC PMIO protocol to satisfy the OSK boot check on older macOS versions. On modern macOS guests (x86 10.14+, all 15.x), the AppleSMC kext enumerates the SMC key space at boot via APPLESMC_GET_KEY_BY_INDEX_CMD (0x12). The current device only acknowledges APPLESMC_READ_CMD (0x10) at the command port; every other command falls through to the default arm of the switch and sets ST_1E_BAD_CMD. The macOS driver interprets the resulting 0x82 reply as "spurious data" and enters a retry loop that floods the kernel log with kSMCSpuriousData (0x81) / kSMCKeyNotFound errors at roughly 1800 events per second, pegging kernel_task at ~70% CPU and WindowServer at ~509% CPU. This reproduces reliably on any recent macOS 15 guest booted with -device isa-applesmc,osk=<valid-OSK>. This patch: * Accepts APPLESMC_WRITE_CMD, APPLESMC_GET_KEY_BY_INDEX_CMD, and APPLESMC_GET_KEY_TYPE_CMD at the command port (in addition to the existing READ_CMD path). * Implements GET_KEY_BY_INDEX by walking s->data_def and returning the 4-byte ASCII key name at the requested index; returns APPLESMC_ST_1E_BAD_INDEX (0xb8) once the index is past the end of the list so the guest stops iterating. * Implements GET_KEY_TYPE by looking up the key in s->data_def and returning a 6-byte response (type[4] + size[1] + attr[1]) matching VirtualSMC's kern_pmio.cpp behaviour. * Implements WRITE by accepting the key name, length, and payload and logging at LOG_UNIMP. macOS writes SMC keys during normal power management; silent acceptance avoids BAD_CMD on every write. * Replaces the unknown-key NOEXIST (0x84) reply on READ with a zeroed payload of the requested length, logged at LOG_UNIMP. Early-boot probes hit hundreds of undocumented keys per second; NOEXIST triggers retry storms while a zeroed payload satisfies the probe semantics without asserting a particular value. * Routes the BAD_CMD path through qemu_log_mask (LOG_GUEST_ERROR) instead of the smc_debug printf. * Fixes a typo in the MSSD key initialiser ("\0x3" -> "\x03"). The original literal was three bytes ('\\0', 'x', '3') truncated to one ('\\0') by the size argument, so MSSD has been silently returning 0 since introduction; the corrected value matches what a real iMac20,1 SMC reports. Reported-by: macOS guests booted with -device isa-applesmc since 10.14. Signed-off-by: Matthew Jackson <matthew@pq.io> --- hw/misc/applesmc.c | 179 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 172 insertions(+), 7 deletions(-) diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/applesmc.c +++ b/hw/misc/applesmc.c @@ -XXX,XX +XXX,XX @@ #include "hw/core/qdev-properties.h" #include "ui/console.h" #include "qemu/error-report.h" +#include "qemu/log.h" #include "qemu/module.h" #include "qemu/timer.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val, smc_debug("CMD received: 0x%02x\n", (uint8_t)val); switch (val) { case APPLESMC_READ_CMD: - /* did last command run through OK? */ + case APPLESMC_WRITE_CMD: + case APPLESMC_GET_KEY_BY_INDEX_CMD: + case APPLESMC_GET_KEY_TYPE_CMD: + /* + * Accept all standard SMC commands. Pre-existing code only handled + * READ_CMD; macOS boots hang if WRITE/TYPE/GET_KEY_BY_INDEX commands + * return BAD_CMD during early AppleSMC driver init. + */ if (status == APPLESMC_ST_CMD_DONE || status == APPLESMC_ST_NEW_CMD) { s->cmd = val; s->status = APPLESMC_ST_NEW_CMD | APPLESMC_ST_ACK; @@ -XXX,XX +XXX,XX @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val, } break; default: - smc_debug("UNEXPECTED CMD 0x%02x\n", (uint8_t)val); + qemu_log_mask(LOG_GUEST_ERROR, + "applesmc: unexpected CMD 0x%02x\n", (uint8_t)val); s->status = APPLESMC_ST_NEW_CMD; s->status_1e = APPLESMC_ST_1E_BAD_CMD; } @@ -XXX,XX +XXX,XX @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val, s->data_len = d->len; s->data_pos = 0; s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; - s->status_1e = APPLESMC_ST_CMD_DONE; /* clear on valid key */ + s->status_1e = APPLESMC_ST_CMD_DONE; } else { - smc_debug("READ_CMD: key '%c%c%c%c' not found!\n", - s->key[0], s->key[1], s->key[2], s->key[3]); + /* + * Return zeros for unknown keys instead of NOEXIST. Early + * macOS boot probes many undocumented keys; responding + * NOEXIST triggers retry storms. A zeroed payload satisfies + * the probe without asserting a particular value. + */ + qemu_log_mask(LOG_UNIMP, + "applesmc: READ unknown key '%c%c%c%c' len=%d\n", + s->key[0], s->key[1], s->key[2], s->key[3], + (uint8_t)val); + memset(s->data, 0, APPLESMC_MAX_DATA_LENGTH); + s->data_len = (uint8_t)val; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; + } + } + s->read_pos++; + break; + case APPLESMC_WRITE_CMD: + /* + * Accept writes silently. macOS writes SMC keys during power + * management, fan control, etc. Log at LOG_UNIMP for visibility + * without treating the write as an error. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 4) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 4) { + s->data_len = (uint8_t)val; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK; + } else { + if (s->data_pos < s->data_len) { + s->data[s->data_pos] = (uint8_t)val; + s->data_pos++; + if (s->data_pos == s->data_len) { + qemu_log_mask(LOG_UNIMP, + "applesmc: WRITE key '%c%c%c%c' len=%d\n", + s->key[0], s->key[1], s->key[2], s->key[3], + s->data_len); + s->status = APPLESMC_ST_CMD_DONE; + s->status_1e = APPLESMC_ST_CMD_DONE; + } else { + s->status = APPLESMC_ST_ACK; + } + } + } + s->read_pos++; + break; + case APPLESMC_GET_KEY_TYPE_CMD: + /* + * Return key type info. Protocol (matches VirtualSMC): + * - Receive 4 bytes of key name. + * - After the 4th byte, immediately set DATA_READY with response. + * - Response is 6 bytes: type[4] + size[1] + attr[1]. + * Unlike READ_CMD there is no length byte between key name and + * response. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 3) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 3) { + /* + * 4th and final key byte. Unlike READ_CMD which has a 5th byte + * for data length, GET_KEY_TYPE responds immediately after the + * 4-byte key name (matching VirtualSMC kern_pmio.cpp behavior). + */ + s->key[3] = val; + d = applesmc_find_key(s); + if (d != NULL) { + switch (d->len) { + case 1: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '8'; s->data[3] = ' '; + break; + case 2: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '1'; s->data[3] = '6'; + break; + case 4: + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '3'; s->data[3] = '2'; + break; + default: + s->data[0] = 'c'; s->data[1] = 'h'; + s->data[2] = '8'; s->data[3] = '*'; + break; + } + s->data[4] = d->len; + s->data[5] = 0xD0; + } else { + qemu_log_mask(LOG_UNIMP, + "applesmc: GET_KEY_TYPE unknown '%c%c%c%c'\n", + s->key[0], s->key[1], s->key[2], s->key[3]); + s->data[0] = 'u'; s->data[1] = 'i'; + s->data[2] = '8'; s->data[3] = ' '; + s->data[4] = 1; + s->data[5] = 0xD0; + } + s->data_len = 6; + s->data_pos = 0; + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; + } + s->read_pos++; + break; + case APPLESMC_GET_KEY_BY_INDEX_CMD: + /* + * Return key name by index. macOS sends a 4-byte big-endian index + * and expects the 4-byte ASCII key name at that position. The + * previous implementation returned 4 zero bytes, which macOS + * treated as kSMCSpuriousData (0x81) and retried indefinitely, + * flooding the kernel log at ~1800 errors/sec. Walk the keys list + * to return the actual key name, or APPLESMC_ST_1E_BAD_INDEX + * (0xb8) once the index is past the end of the list so the guest + * stops iterating. + */ + if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) { + break; + } + if (s->read_pos < 3) { + s->key[s->read_pos] = val; + s->status = APPLESMC_ST_ACK; + } else if (s->read_pos == 3) { + s->key[3] = val; + uint32_t idx = ((uint8_t)s->key[0] << 24) + | ((uint8_t)s->key[1] << 16) + | ((uint8_t)s->key[2] << 8) + | (uint8_t)s->key[3]; + struct AppleSMCData *def; + uint32_t i = 0; + bool found = false; + QLIST_FOREACH(def, &s->data_def, node) { + if (i == idx) { + memcpy(s->data, def->key, 4); + s->data_len = 4; + s->data_pos = 0; + found = true; + break; + } + i++; + } + if (!found) { + s->data_len = 0; + s->status_1e = APPLESMC_ST_1E_BAD_INDEX; s->status = APPLESMC_ST_CMD_DONE; - s->status_1e = APPLESMC_ST_1E_NOEXIST; + s->read_pos++; + break; } + s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY; + s->status_1e = APPLESMC_ST_CMD_DONE; } s->read_pos++; break; default: + qemu_log_mask(LOG_GUEST_ERROR, + "applesmc: unhandled data for cmd 0x%02x\n", s->cmd); s->status = APPLESMC_ST_CMD_DONE; s->status_1e = APPLESMC_ST_1E_STILL_BAD_CMD; } @@ -XXX,XX +XXX,XX @@ static void applesmc_isa_realize(DeviceState *dev, Error **errp) } QLIST_INIT(&s->data_def); + 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, "\0x3"); + applesmc_add_key(s, "MSSD", 1, "\x03"); } static void applesmc_unrealize(DeviceState *dev) -- 2.50.1 (Apple Git-155)
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 | 169 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/applesmc.c +++ b/hw/misc/applesmc.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "hw/isa/isa.h" -#include "hw/core/qdev-properties.h" +#include "hw/qdev-properties.h" #include "ui/console.h" #include "qemu/error-report.h" #include "qemu/log.h" @@ -XXX,XX +XXX,XX @@ 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"); /* 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; + static char numkey_buf[4]; + + QLIST_FOREACH(def, &s->data_def, node) { + count++; + } + stl_be_p(numkey_buf, count); + applesmc_add_key(s, "#KEY", 4, numkey_buf); + } } static void applesmc_unrealize(DeviceState *dev) -- 2.50.1 (Apple Git-155)
hw/misc/applesmc.c has no MAINTAINERS entry, so patches to it are not routed to anyone and get_maintainer.pl only resolves the open list. Add an "Apple SMC" entry (Odd Fixes) so future changes have a designated recipient. Signed-off-by: Matthew Jackson <matthew@pq.io> --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/net/can/xlnx-* F: include/hw/net/xlnx-* F: tests/qtest/xlnx-can*-test* +Apple SMC +M: Matthew Jackson <matthew@pq.io> +S: Odd Fixes +F: hw/misc/applesmc.c + EDU M: Jiri Slaby <jslaby@suse.cz> S: Maintained -- 2.50.1 (Apple Git-155)