All of lore.kernel.org
 help / color / mirror / Atom feed
From: Daniel Golle <daniel@makrotopia.org>
To: "Matthew Jackson" <matthew@pq.io>,
	"Alexander Graf" <agraf@csgraf.de>,
	"Philippe Mathieu-Daudé" <philmd@oss.qualcomm.com>,
	"Pierrick Bouvier" <pierrick.bouvier@oss.qualcomm.com>
Cc: "Paolo Bonzini" <pbonzini@redhat.com>,
	"Cédric Le Goater" <clg@redhat.com>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	Kane-Chen-AS <kane_chen@aspeedtech.com>,
	"jack wang" <163wangjack@gmail.com>,
	qemu-devel@nongnu.org
Subject: [PATCH 2/2] hw/misc/applesmc: Emulate the SMC shutdown watchdog
Date: Wed, 19 Aug 2026 18:36:58 +0100	[thread overview]
Message-ID: <6888e300e29ce09f2c754641c6108da170357dbd.1787150842.git.daniel@makrotopia.org> (raw)
In-Reply-To: <cover.1787150842.git.daniel@makrotopia.org>

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


  parent reply	other threads:[~2026-08-19 17:37 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-07  4:01 [PATCH 0/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX iteration and populate Apple SMC key set Matthew Jackson
2026-05-07  4:01 ` [PATCH 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands Matthew Jackson
2026-05-07  4:01 ` [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       ` Daniel Golle [this message]
2026-07-19 15:23 ` [PATCH v3 1/2] hw/misc/applesmc: fix GET_KEY_BY_INDEX to return real keys, accept WRITE/TYPE commands Matthew Jackson
2026-07-19 15:23 ` [PATCH v3 2/2] hw/misc/applesmc: populate Apple SMC key table Matthew Jackson
2026-07-20  8:04   ` Philippe Mathieu-Daudé

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6888e300e29ce09f2c754641c6108da170357dbd.1787150842.git.daniel@makrotopia.org \
    --to=daniel@makrotopia.org \
    --cc=163wangjack@gmail.com \
    --cc=agraf@csgraf.de \
    --cc=clg@redhat.com \
    --cc=kane_chen@aspeedtech.com \
    --cc=matthew@pq.io \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=philmd@oss.qualcomm.com \
    --cc=pierrick.bouvier@oss.qualcomm.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.