All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set
@ 2026-05-07  4:01 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
                   ` (7 more replies)
  0 siblings, 8 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-05-07  4:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha

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)



^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands
  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 ` Matthew Jackson
  2026-05-07  4:01 ` [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-05-07  4:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha

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 fd96f5f..2b5ef3c 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -35,6 +35,7 @@
 #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"
@@ -126,7 +127,14 @@ 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;
@@ -137,7 +145,8 @@ 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;
     }
@@ -179,17 +188,170 @@ 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;
     }
@@ -330,12 +492,13 @@ 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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table
  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
  2026-05-07 14:27   ` 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
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 24+ messages in thread
From: Matthew Jackson @ 2026-05-07  4:01 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha

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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table
  2026-05-07  4:01 ` [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
@ 2026-05-07 14:27   ` Peter Maydell
  0 siblings, 0 replies; 24+ messages in thread
From: Peter Maydell @ 2026-05-07 14:27 UTC (permalink / raw)
  To: Matthew Jackson; +Cc: qemu-devel, stefanha

On Thu, 7 May 2026 at 15:14, Matthew Jackson <matthew@pq.io> wrote:
>
> 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.

> +    /*
> +     * #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.
> +     */

The applesmc_add_key() API's "pointer passed in must remain
valid forever" is a bit awkward, but I suppose for a single
key that's not "fixed data" it's fine.

Does the guest query this stuff often enough that the "loop
through all keys" in applesmc_find_key() is at all noticeable
in a profile? I'm guessing not, but if we did find we wanted
to replace that with a glib hashtable then we could take the
opportunity to make the memory lifetime requirement here a bit
less bug-prone.

> +    {
> +        int count = 1;  /* include #KEY itself */
> +        struct AppleSMCData *def;
> +        QLIST_FOREACH(def, &s->data_def, node) count++;

Loops always need {}, even single line bodies.

> +        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;

This is
   stl_be_p(numkey_buf, count);

> +        applesmc_add_key(s, "#KEY", 4, numkey_buf);
> +    }
>  }


thanks
-- PMM


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v2 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set
  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 ` [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
@ 2026-05-07 15:20 ` Matthew Jackson
  2026-06-01 15:28   ` [PATCH v3 0/3] " 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
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 24+ messages in thread
From: Matthew Jackson @ 2026-05-07 15:20 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha, peter.maydell

v1: https://lore.kernel.org/qemu-devel/20260507040153.14565-1-matthew@pq.io/

Changes in v2 (all in patch 2/2, applesmc_isa_realize() #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 unchanged from v1.

Peter also noted that the static-buffer "must remain valid forever"
contract that applesmc_add_key() requires is awkward — fair, but
fixing it (e.g. switching the key table to a glib hashtable that
copies values) is independent from this series and not reported as
performance-noticeable in profiles. Happy to follow up with a
hashtable conversion as a separate 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 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 | 346 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 338 insertions(+), 8 deletions(-)

-- 
2.50.1 (Apple Git-155)



^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v2 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands
  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
                   ` (2 preceding siblings ...)
  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-05-07 15:20 ` Matthew Jackson
  2026-05-07 15:20 ` [PATCH v2 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-05-07 15:20 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha, peter.maydell

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 fd96f5f..2b5ef3c 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -35,6 +35,7 @@
 #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"
@@ -126,7 +127,14 @@ 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;
@@ -137,7 +145,8 @@ 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;
     }
@@ -179,17 +188,170 @@ 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;
     }
@@ -330,12 +492,13 @@ 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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v2 2/2] hw/misc/applesmc: populate Apple SMC key table
  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
                   ` (3 preceding siblings ...)
  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 ` 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
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-05-07 15:20 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha, peter.maydell

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 2b5ef3c..d11fb32 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -32,7 +32,7 @@
 
 #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"
@@ -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;
+        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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 0/3] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set
  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   ` 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
                       ` (2 more replies)
  0 siblings, 3 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-06-01 15:28 UTC (permalink / raw)
  To: qemu-devel; +Cc: philmd, peter.maydell, pbonzini, stefanha, thuth

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)


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/3] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands
  2026-06-01 15:28   ` [PATCH v3 0/3] " Matthew Jackson
@ 2026-06-01 15:28     ` Matthew Jackson
  2026-06-01 15:28     ` [PATCH v3 2/3] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
  2026-06-01 15:28     ` [PATCH v3 3/3] MAINTAINERS: adopt hw/misc/applesmc.c Matthew Jackson
  2 siblings, 0 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-06-01 15:28 UTC (permalink / raw)
  To: qemu-devel; +Cc: philmd, peter.maydell, pbonzini, stefanha, thuth

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 fd96f5f245..263b2251b0 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -35,6 +35,7 @@
 #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"
@@ -126,7 +127,14 @@ 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;
@@ -137,7 +145,8 @@ 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;
     }
@@ -179,17 +188,172 @@ 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;
     }
@@ -330,12 +494,13 @@ 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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 2/3] hw/misc/applesmc: populate Apple SMC key table
  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     ` 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
  2 siblings, 1 reply; 24+ messages in thread
From: Matthew Jackson @ 2026-06-01 15:28 UTC (permalink / raw)
  To: qemu-devel; +Cc: philmd, peter.maydell, pbonzini, stefanha, thuth

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 263b2251b0..38062a07b5 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -32,7 +32,7 @@
 
 #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"
@@ -495,12 +495,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");  /* 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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 3/3] MAINTAINERS: adopt hw/misc/applesmc.c
  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-06-01 15:28     ` Matthew Jackson
  2 siblings, 0 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-06-01 15:28 UTC (permalink / raw)
  To: qemu-devel; +Cc: philmd, peter.maydell, pbonzini, stefanha, thuth

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 7752917d8c..4155e1d93d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2106,6 +2106,11 @@ 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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set
  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
                   ` (4 preceding siblings ...)
  2026-05-07 15:20 ` [PATCH v2 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
@ 2026-07-19 15:23 ` Matthew Jackson
  2026-07-20 16:49   ` [PATCH v4 " 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
  7 siblings, 1 reply; 24+ messages in thread
From: Matthew Jackson @ 2026-07-19 15:23 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha, peter.maydell

v3: rebased onto current master — no code changes. hw/misc/applesmc.c is
unchanged upstream since v2 (2026-05-07), so the series applies as-is.
Resending after the review thread went quiet.

v1: https://lore.kernel.org/qemu-devel/20260507040153.14565-1-matthew@pq.io/

Changes in v2 (all in patch 2/2, applesmc_isa_realize() #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 unchanged from v1.

Peter also noted that the static-buffer "must remain valid forever"
contract that applesmc_add_key() requires is awkward — fair, but
fixing it (e.g. switching the key table to a glib hashtable that
copies values) is independent from this series and not reported as
performance-noticeable in profiles. Happy to follow up with a
hashtable conversion as a separate 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 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 | 346 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 338 insertions(+), 8 deletions(-)

-- 
2.50.1 (Apple Git-155)



^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands
  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
                   ` (5 preceding siblings ...)
  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-19 15:23 ` Matthew Jackson
  2026-07-19 15:23 ` [PATCH v3 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
  7 siblings, 0 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-07-19 15:23 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha, peter.maydell

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 fd96f5f..2b5ef3c 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -35,6 +35,7 @@
 #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"
@@ -126,7 +127,14 @@ 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;
@@ -137,7 +145,8 @@ 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;
     }
@@ -179,17 +188,170 @@ 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;
     }
@@ -330,12 +492,13 @@ 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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 2/2] hw/misc/applesmc: populate Apple SMC key table
  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
                   ` (6 preceding siblings ...)
  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 ` Matthew Jackson
  2026-07-20  8:04   ` Philippe Mathieu-Daudé
  7 siblings, 1 reply; 24+ messages in thread
From: Matthew Jackson @ 2026-07-19 15:23 UTC (permalink / raw)
  To: qemu-devel; +Cc: stefanha, peter.maydell

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 2b5ef3c..d11fb32 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -32,7 +32,7 @@
 
 #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"
@@ -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;
+        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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH v3 2/3] hw/misc/applesmc: populate Apple SMC key table
  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é
  0 siblings, 0 replies; 24+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-07-20  8:02 UTC (permalink / raw)
  To: Matthew Jackson, qemu-devel
  Cc: philmd, peter.maydell, pbonzini, stefanha, thuth

On 1/6/26 17:28, Matthew Jackson wrote:
> 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 263b2251b0..38062a07b5 100644
> --- a/hw/misc/applesmc.c
> +++ b/hw/misc/applesmc.c
> @@ -32,7 +32,7 @@
>   
>   #include "qemu/osdep.h"
>   #include "hw/isa/isa.h"
> -#include "hw/core/qdev-properties.h"
> +#include "hw/qdev-properties.h"

Invalid change?



^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v3 2/2] hw/misc/applesmc: populate Apple SMC key table
  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é
  0 siblings, 0 replies; 24+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-07-20  8:04 UTC (permalink / raw)
  To: Matthew Jackson, qemu-devel; +Cc: stefanha, peter.maydell

On 19/7/26 17:23, Matthew Jackson wrote:
> 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 2b5ef3c..d11fb32 100644
> --- a/hw/misc/applesmc.c
> +++ b/hw/misc/applesmc.c
> @@ -32,7 +32,7 @@
>   
>   #include "qemu/osdep.h"
>   #include "hw/isa/isa.h"
> -#include "hw/core/qdev-properties.h"
> +#include "hw/qdev-properties.h"

Something seems wrong in your tree.


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v4 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set
  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   ` 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
                       ` (2 more replies)
  0 siblings, 3 replies; 24+ messages in thread
From: Matthew Jackson @ 2026-07-20 16:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: kraxel, Peter Maydell, Philippe Mathieu-Daudé, qemu-trivial

The applesmc device only answers APPLESMC_READ_CMD at its command
port. Modern macOS guests (x86 10.14+, all 15.x) also issue WRITE,
TYPE and GET_KEY_BY_INDEX, and enumerate the SMC key space at boot.
The unanswered commands and the near-empty key table drive the
AppleSMC kext into a kSMCSpuriousData retry storm (~1800 errors/sec,
kernel_task ~70% CPU, WindowServer ~509% CPU on macOS 15).

Patch 1 implements the missing commands. Patch 2 populates the key
table with a realistic iMac20,1 profile and the canonical #KEY count,
so the guest's boot-time enumeration terminates.

v4:
  - Regenerated against current master. Drop a stray hunk in patch 2/2
    that reverted the qdev-properties.h include move (commit 78d45220b4);
    the include is now left untouched. Reported-by Philippe
    Mathieu-Daudé. No functional change.
  - No other changes from v3.

v3: https://lore.kernel.org/qemu-devel/20260719152343.69536-1-matthew@pq.io/
v2: addressed Peter Maydell's style review (loop braces, stl_be_p()).
v1: https://lore.kernel.org/qemu-devel/20260507040153.14565-1-matthew@pq.io/

Matthew Jackson (2):
  hw/misc/applesmc: implement GET_KEY_BY_INDEX, WRITE and 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)



^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v4 1/2] hw/misc/applesmc: implement GET_KEY_BY_INDEX, WRITE and TYPE commands
  2026-07-20 16:49   ` [PATCH v4 " Matthew Jackson
@ 2026-07-20 16:49     ` Matthew Jackson
  2026-08-19 17:35       ` Daniel Golle
  2026-07-20 16:49     ` [PATCH v4 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
  2026-08-19 17:36     ` [PATCH 0/2] hw/misc/applesmc: SMC shutdown watchdog and trace events Daniel Golle
  2 siblings, 1 reply; 24+ messages in thread
From: Matthew Jackson @ 2026-07-20 16:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: kraxel, Peter Maydell, Philippe Mathieu-Daudé, qemu-trivial

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 silently returned 0 since
    introduction; the corrected value matches what a real iMac20,1 SMC
    reports.

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 fd96f5f245..2b5ef3c6e0 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -35,6 +35,7 @@
 #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"
@@ -126,7 +127,14 @@ 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;
@@ -137,7 +145,8 @@ 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;
     }
@@ -179,17 +188,170 @@ 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;
     }
@@ -330,12 +492,13 @@ 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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v4 2/2] hw/misc/applesmc: populate Apple SMC key table
  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     ` Matthew Jackson
  2026-08-19 17:36       ` Daniel Golle
  2026-08-19 17:36     ` [PATCH 0/2] hw/misc/applesmc: SMC shutdown watchdog and trace events Daniel Golle
  2 siblings, 1 reply; 24+ messages in thread
From: Matthew Jackson @ 2026-07-20 16:49 UTC (permalink / raw)
  To: qemu-devel
  Cc: kraxel, Peter Maydell, Philippe Mathieu-Daudé, qemu-trivial

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 2b5ef3c6e0..d2f292f10e 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 */
+    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)



^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH v4 1/2] hw/misc/applesmc: implement GET_KEY_BY_INDEX, WRITE and TYPE commands
  2026-07-20 16:49     ` [PATCH v4 1/2] hw/misc/applesmc: implement GET_KEY_BY_INDEX, WRITE and TYPE commands Matthew Jackson
@ 2026-08-19 17:35       ` Daniel Golle
  0 siblings, 0 replies; 24+ messages in thread
From: Daniel Golle @ 2026-08-19 17:35 UTC (permalink / raw)
  To: Matthew Jackson
  Cc: Alexander Graf, Philippe Mathieu-Daudé, Pierrick Bouvier,
	Paolo Bonzini, Cédric Le Goater, Peter Maydell, Kane-Chen-AS,
	jack wang, qemu-devel

I implemented the same two commands independently and ended up at the same
place, so I have read this one closely and tested it against a macOS 26
(Tahoe) guest: with it applied the guest issues cmd 0x11/0x13 during boot
and both are accepted (no more kSMCBadCommand), the boot-time key writes
(NTOK, MSDW, QENA, HE0N) succeed, and get-key-type is answered for the keys
it probes.

Reviewed-by: Daniel Golle <daniel@makrotopia.org>
Tested-by: Daniel Golle <daniel@makrotopia.org>

One minor observation on get-key-type:
Inferring the type from the value length reports REV as "ch8*" (six
bytes) rather than its documented "{rev" fourcc. The guest I traced
accepts it and boots either way, so this is cosmetic -- just noting it
in case you want to give the handful of typed keys (REV, the
"flag"/"{fds" style keys) an explicit type later.


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v4 2/2] hw/misc/applesmc: populate Apple SMC key table
  2026-07-20 16:49     ` [PATCH v4 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
@ 2026-08-19 17:36       ` Daniel Golle
  0 siblings, 0 replies; 24+ messages in thread
From: Daniel Golle @ 2026-08-19 17:36 UTC (permalink / raw)
  To: Matthew Jackson
  Cc: Alexander Graf, Philippe Mathieu-Daudé, Pierrick Bouvier,
	Paolo Bonzini, Cédric Le Goater, Peter Maydell, Kane-Chen-AS,
	jack wang, qemu-devel

I cross-checked this table against the keys a macOS 26 (Tahoe) guest
actually reads and probes during boot (I have a small set of applesmc trace
events for exactly this); the guest reads them without complaint and boots
and shuts down cleanly with the table in place.

Reviewed-by: Daniel Golle <daniel@makrotopia.org>
Tested-by: Daniel Golle <daniel@makrotopia.org>


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH 0/2] hw/misc/applesmc: SMC shutdown watchdog and trace events
  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-08-19 17:36     ` Daniel Golle
  2026-08-19 17:36       ` [PATCH 1/2] hw/misc/applesmc: Add " Daniel Golle
  2026-08-19 17:36       ` [PATCH 2/2] hw/misc/applesmc: Emulate the SMC shutdown watchdog Daniel Golle
  2 siblings, 2 replies; 24+ messages in thread
From: Daniel Golle @ 2026-08-19 17:36 UTC (permalink / raw)
  To: Matthew Jackson, Alexander Graf, Philippe Mathieu-Daudé,
	Pierrick Bouvier
  Cc: Paolo Bonzini, Cédric Le Goater, Peter Maydell, Kane-Chen-AS,
	jack wang, qemu-devel

These two patches are the part of my earlier AppleSMC series [1] that is not
already covered by Matthew Jackson's Apple SMC v4 series [2], rebased on top
of it.

Matthew's series is the more complete of the two -- besides the write and
get-key-type commands (which we implemented independently) it also handles
get-key-by-index and populates a full key table -- so I have dropped the
overlapping patches from mine and kept only the two bits it does not cover:

  1/2  trace events for the command dispatch and each handler, so the
       SMC command/key conversation can be watched with -trace 'applesmc*'.
       This is how the exact set of commands and keys a macOS guest relies
       on was found in the first place.

  2/2  the SMC shutdown watchdog: the "NATi"/"NATJ"/"OSWD" keys that patch
       1/2 of Matthew's series accepts and discards are the watchdog macOS
       arms to force the machine down. Model them as a QEMUTimer that a
       write arms, refreshes or disarms.

Both apply on top of Matthew's v4 [2]; they touch only hw/misc/applesmc.c
and hw/misc/trace-events and do not conflict with it.

Tested by booting a macOS 26 (Tahoe) guest on Matthew's v4 plus these two
patches. It boots and shuts down cleanly; every SMC command the guest issues
is accepted (0 rejected out of 77 in one boot+shutdown), the boot-time key
writes (NTOK, MSDW, QENA, HE0N) succeed, get-key-type is answered for the 60+
keys the guest probes, and on shutdown the guest's "OSWD" write is handled as
a watchdog disarm (trace_applesmc_wdt_disarm). The guest arms the watchdog
only to disarm it on the way down; the arm-and-expiry path is the documented
behaviour for a guest that does arm it.

[1] https://patchew.org/QEMU/cover.1787114994.git.daniel@makrotopia.org/
[2] https://patchew.org/QEMU/20260720164930.68383-1-matthew@pq.io/

Daniel Golle (2):
  hw/misc/applesmc: Add trace events
  hw/misc/applesmc: Emulate the SMC shutdown watchdog

 hw/misc/applesmc.c   | 80 ++++++++++++++++++++++++++++++++++++++++++++
 hw/misc/trace-events | 13 +++++++
 2 files changed, 93 insertions(+)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH 1/2] hw/misc/applesmc: Add trace events
  2026-08-19 17:36     ` [PATCH 0/2] hw/misc/applesmc: SMC shutdown watchdog and trace events Daniel Golle
@ 2026-08-19 17:36       ` Daniel Golle
  2026-08-19 17:36       ` [PATCH 2/2] hw/misc/applesmc: Emulate the SMC shutdown watchdog Daniel Golle
  1 sibling, 0 replies; 24+ messages in thread
From: Daniel Golle @ 2026-08-19 17:36 UTC (permalink / raw)
  To: Matthew Jackson, Alexander Graf, Philippe Mathieu-Daudé,
	Pierrick Bouvier
  Cc: Paolo Bonzini, Cédric Le Goater, Peter Maydell, Kane-Chen-AS,
	jack wang, qemu-devel

Add trace events for the command dispatch and for the read, write,
get-key-type and get-key-by-index handlers, so the sequence of commands
and keys a guest issues can be observed with

  -trace 'applesmc*'

This is how the exact set of commands and keys a macOS guest relies on
was determined, and it stays useful for debugging future guests and key
probes.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 hw/misc/applesmc.c   | 14 ++++++++++++++
 hw/misc/trace-events | 10 ++++++++++
 2 files changed, 24 insertions(+)

diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c
index d2f292f10e..4a82a36a2a 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -40,6 +40,7 @@
 #include "qemu/timer.h"
 #include "qom/object.h"
 #include "hw/acpi/acpi_aml_interface.h"
+#include "trace.h"
 
 /* #define DEBUG_SMC */
 
@@ -138,6 +139,7 @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val,
         if (status == APPLESMC_ST_CMD_DONE || status == APPLESMC_ST_NEW_CMD) {
             s->cmd = val;
             s->status = APPLESMC_ST_NEW_CMD | APPLESMC_ST_ACK;
+            trace_applesmc_cmd(val);
         } else {
             smc_debug("ERROR: previous command interrupted!\n");
             s->status = APPLESMC_ST_NEW_CMD;
@@ -147,6 +149,7 @@ static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val,
     default:
         qemu_log_mask(LOG_GUEST_ERROR,
                       "applesmc: unexpected CMD 0x%02x\n", (uint8_t)val);
+        trace_applesmc_cmd_bad(val);
         s->status = APPLESMC_ST_NEW_CMD;
         s->status_1e = APPLESMC_ST_1E_BAD_CMD;
     }
@@ -189,6 +192,8 @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
                 s->data_pos = 0;
                 s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY;
                 s->status_1e = APPLESMC_ST_CMD_DONE;
+                trace_applesmc_read(s->key[0], s->key[1], s->key[2], s->key[3],
+                                    d->len);
             } else {
                 /*
                  * Return zeros for unknown keys instead of NOEXIST. Early
@@ -200,6 +205,8 @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
                               "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);
+                trace_applesmc_read_unknown(s->key[0], s->key[1],
+                                            s->key[2], s->key[3]);
                 memset(s->data, 0, APPLESMC_MAX_DATA_LENGTH);
                 s->data_len = (uint8_t)val;
                 s->data_pos = 0;
@@ -234,6 +241,8 @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
                                   "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);
+                    trace_applesmc_write(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 {
@@ -298,6 +307,8 @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
             s->data_pos = 0;
             s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY;
             s->status_1e = APPLESMC_ST_CMD_DONE;
+            trace_applesmc_key_type(s->key[0], s->key[1], s->key[2],
+                                    s->key[3], s->data[4]);
         }
         s->read_pos++;
         break;
@@ -341,9 +352,12 @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
                 s->data_len = 0;
                 s->status_1e = APPLESMC_ST_1E_BAD_INDEX;
                 s->status = APPLESMC_ST_CMD_DONE;
+                trace_applesmc_key_by_index_end(idx);
                 s->read_pos++;
                 break;
             }
+            trace_applesmc_key_by_index(idx, def->key[0], def->key[1],
+                                        def->key[2], def->key[3]);
             s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY;
             s->status_1e = APPLESMC_ST_CMD_DONE;
         }
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b3ef..5b823182c9 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -37,6 +37,16 @@ allwinner_sid_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%"
 allwinner_sramc_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64
 allwinner_sramc_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64
 
+# applesmc.c
+applesmc_cmd(uint8_t cmd) "cmd 0x%02x"
+applesmc_cmd_bad(uint8_t cmd) "unsupported cmd 0x%02x"
+applesmc_read(char k0, char k1, char k2, char k3, uint8_t len) "READ '%c%c%c%c' len %u"
+applesmc_read_unknown(char k0, char k1, char k2, char k3) "READ unknown key '%c%c%c%c'"
+applesmc_write(char k0, char k1, char k2, char k3, uint8_t len) "WRITE '%c%c%c%c' len %u"
+applesmc_key_type(char k0, char k1, char k2, char k3, uint8_t len) "GET_KEY_TYPE '%c%c%c%c' size %u"
+applesmc_key_by_index(uint32_t idx, char k0, char k1, char k2, char k3) "GET_KEY_BY_INDEX %u -> '%c%c%c%c'"
+applesmc_key_by_index_end(uint32_t idx) "GET_KEY_BY_INDEX %u past end"
+
 # avr_power.c
 avr_power_read(uint8_t value) "power_reduc read value:%u"
 avr_power_write(uint8_t value) "power_reduc write value:%u"
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 2/2] hw/misc/applesmc: Emulate the SMC shutdown watchdog
  2026-08-19 17:36     ` [PATCH 0/2] hw/misc/applesmc: SMC shutdown watchdog and trace events Daniel Golle
  2026-08-19 17:36       ` [PATCH 1/2] hw/misc/applesmc: Add " Daniel Golle
@ 2026-08-19 17:36       ` Daniel Golle
  1 sibling, 0 replies; 24+ messages in thread
From: Daniel Golle @ 2026-08-19 17:36 UTC (permalink / raw)
  To: Matthew Jackson, Alexander Graf, Philippe Mathieu-Daudé,
	Pierrick Bouvier
  Cc: Paolo Bonzini, Cédric Le Goater, Peter Maydell, Kane-Chen-AS,
	jack wang, qemu-devel

The "NATi", "NATJ" and "OSWD" keys drive the SMC shutdown watchdog: a
guest arms a countdown and the SMC forces the machine down if the guest
stops refreshing it. The previous patch lets writes to these keys
succeed; model them as the watchdog they are instead of discarding the
value, so a guest that arms the watchdog gets the reset or power-off it
asked for.

NATi sets the timeout in seconds, NATJ selects the job and arms it (0
disarms, 1 powers the machine down, 2 restarts it), and OSWD is a
one-shot shutdown timer. A write arms, refreshes or disarms a
QEMU_CLOCK_VIRTUAL timer whose expiry raises a guest shutdown or reset
request.

A traced macOS guest writes OSWD with a zero countdown to disarm on its
way down and does not otherwise arm the watchdog; the arm-and-expiry
path is the documented behaviour for a guest that does arm it.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 hw/misc/applesmc.c   | 66 ++++++++++++++++++++++++++++++++++++++++++++
 hw/misc/trace-events |  3 ++
 2 files changed, 69 insertions(+)

diff --git a/hw/misc/applesmc.c b/hw/misc/applesmc.c
index 4a82a36a2a..e329b2e1e6 100644
--- a/hw/misc/applesmc.c
+++ b/hw/misc/applesmc.c
@@ -40,6 +40,7 @@
 #include "qemu/timer.h"
 #include "qom/object.h"
 #include "hw/acpi/acpi_aml_interface.h"
+#include "system/runstate.h"
 #include "trace.h"
 
 /* #define DEBUG_SMC */
@@ -81,6 +82,12 @@ enum {
     APPLESMC_ST_1E_BAD_INDEX         = 0xb8,
 };
 
+enum {
+    APPLESMC_WDT_DISARM              = 0,
+    APPLESMC_WDT_SHUTDOWN            = 1,
+    APPLESMC_WDT_RESTART             = 2,
+};
+
 #ifdef DEBUG_SMC
 #define smc_debug(...) fprintf(stderr, "AppleSMC: " __VA_ARGS__)
 #else
@@ -117,6 +124,9 @@ struct AppleSMCState {
     uint8_t data[255];
     char *osk;
     QLIST_HEAD(, AppleSMCData) data_def;
+    QEMUTimer *wdt_timer;
+    uint16_t wdt_timeout;
+    uint8_t wdt_job;
 };
 
 static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val,
@@ -169,6 +179,53 @@ static const struct AppleSMCData *applesmc_find_key(AppleSMCState *s)
     return NULL;
 }
 
+static void applesmc_wdt_expired(void *opaque)
+{
+    AppleSMCState *s = opaque;
+
+    trace_applesmc_wdt_expired(s->wdt_job);
+    if (s->wdt_job == APPLESMC_WDT_RESTART) {
+        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+    } else {
+        qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+    }
+}
+
+static void applesmc_wdt_update(AppleSMCState *s)
+{
+    if (s->wdt_job == APPLESMC_WDT_DISARM || s->wdt_timeout == 0) {
+        timer_del(s->wdt_timer);
+        trace_applesmc_wdt_disarm();
+        return;
+    }
+    timer_mod(s->wdt_timer,
+              qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+              (uint64_t)s->wdt_timeout * NANOSECONDS_PER_SECOND);
+    trace_applesmc_wdt_arm(s->wdt_job, s->wdt_timeout);
+}
+
+/*
+ * The "NATi"/"NATJ"/"OSWD" keys are the SMC shutdown watchdog macOS uses to
+ * force the machine down if userspace stops petting it: NATi sets the
+ * timeout in seconds, NATJ selects and arms the job (0 disarm, 1 shutdown,
+ * 2 restart), and OSWD is a one-shot shutdown timer. A write to one of them
+ * arms, refreshes or disarms the timer rather than being silently discarded.
+ */
+static void applesmc_wdt_write_key(AppleSMCState *s)
+{
+    if (!memcmp(s->key, "NATi", 4) && s->data_len >= 2) {
+        s->wdt_timeout = (s->data[0] << 8) | s->data[1];
+    } else if (!memcmp(s->key, "NATJ", 4) && s->data_len >= 1) {
+        s->wdt_job = s->data[0];
+        applesmc_wdt_update(s);
+    } else if (!memcmp(s->key, "OSWD", 4) && s->data_len >= 2) {
+        s->wdt_timeout = (s->data[0] << 8) | s->data[1];
+        s->wdt_job = s->wdt_timeout ? APPLESMC_WDT_SHUTDOWN
+                                    : APPLESMC_WDT_DISARM;
+        applesmc_wdt_update(s);
+    }
+}
+
 static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
                                    unsigned size)
 {
@@ -243,6 +300,7 @@ static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
                                   s->data_len);
                     trace_applesmc_write(s->key[0], s->key[1], s->key[2],
                                          s->key[3], s->data_len);
+                    applesmc_wdt_write_key(s);
                     s->status = APPLESMC_ST_CMD_DONE;
                     s->status_1e = APPLESMC_ST_CMD_DONE;
                 } else {
@@ -449,6 +507,9 @@ static void qdev_applesmc_isa_reset(DeviceState *dev)
     s->status = 0x00;
     s->status_1e = 0x00;
     s->last_ret = 0x00;
+    s->wdt_job = APPLESMC_WDT_DISARM;
+    s->wdt_timeout = 0;
+    timer_del(s->wdt_timer);
 }
 
 static const MemoryRegionOps applesmc_data_io_ops = {
@@ -485,6 +546,8 @@ static void applesmc_isa_realize(DeviceState *dev, Error **errp)
 {
     AppleSMCState *s = APPLE_SMC(dev);
 
+    s->wdt_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, applesmc_wdt_expired, s);
+
     memory_region_init_io(&s->io_data, OBJECT(s), &applesmc_data_io_ops, s,
                           "applesmc-data", 1);
     isa_register_ioport(&s->parent_obj, &s->io_data,
@@ -511,6 +574,7 @@ static void applesmc_isa_realize(DeviceState *dev, Error **errp)
     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, "NATi", 2, "\0\0");
     applesmc_add_key(s, "NATJ", 1, "\0");
     applesmc_add_key(s, "MSSP", 1, "\0");
     applesmc_add_key(s, "MSSD", 1, "\x03");
@@ -687,6 +751,8 @@ static void applesmc_unrealize(DeviceState *dev)
     AppleSMCState *s = APPLE_SMC(dev);
     struct AppleSMCData *d, *next;
 
+    timer_free(s->wdt_timer);
+
     /* Remove existing entries */
     QLIST_FOREACH_SAFE(d, &s->data_def, node, next) {
         QLIST_REMOVE(d, node);
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 5b823182c9..a62d357213 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -46,6 +46,9 @@ applesmc_write(char k0, char k1, char k2, char k3, uint8_t len) "WRITE '%c%c%c%c
 applesmc_key_type(char k0, char k1, char k2, char k3, uint8_t len) "GET_KEY_TYPE '%c%c%c%c' size %u"
 applesmc_key_by_index(uint32_t idx, char k0, char k1, char k2, char k3) "GET_KEY_BY_INDEX %u -> '%c%c%c%c'"
 applesmc_key_by_index_end(uint32_t idx) "GET_KEY_BY_INDEX %u past end"
+applesmc_wdt_arm(uint8_t job, uint16_t seconds) "arm job %u timeout %us"
+applesmc_wdt_disarm(void) "disarm"
+applesmc_wdt_expired(uint8_t job) "expired job %u"
 
 # avr_power.c
 avr_power_read(uint8_t value) "power_reduc read value:%u"
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-08-19 17:37 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
2026-05-07 14:27   ` 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-08-19 17:35       ` Daniel Golle
2026-07-20 16:49     ` [PATCH v4 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
2026-08-19 17:36       ` Daniel Golle
2026-08-19 17:36     ` [PATCH 0/2] hw/misc/applesmc: SMC shutdown watchdog and trace events Daniel Golle
2026-08-19 17:36       ` [PATCH 1/2] hw/misc/applesmc: Add " Daniel Golle
2026-08-19 17:36       ` [PATCH 2/2] hw/misc/applesmc: Emulate the SMC shutdown watchdog Daniel Golle
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é

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.