All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marcelo Manzo <marcelomanzo@gmail.com>
To: qemu-devel@nongnu.org
Cc: qemu-arm@nongnu.org, "Peter Maydell" <peter.maydell@linaro.org>,
	"Philippe Mathieu-Daudé" <philmd@mailo.com>,
	"Jason Wang" <jasowangio@gmail.com>,
	"Marcelo Manzo" <marcelomanzo@gmail.com>
Subject: [PATCH 1/2] hw/misc/bcm2835_powermgt: implement a real watchdog timer
Date: Sat, 25 Jul 2026 19:11:41 -0400	[thread overview]
Message-ID: <20260725231142.61663-2-marcelomanzo@gmail.com> (raw)
In-Reply-To: <20260725231142.61663-1-marcelomanzo@gmail.com>

The RSTC register's write-config bits (0x30) being set to the
"full reset" value (0x20) does not mean "reset now" -- it arms the
hardware watchdog so that a reset happens if the WDOG countdown
register is not refreshed before it expires. The previous
implementation treated any such RSTC write as an immediate reset,
regardless of the WDOG value.

This is dormant on older/lighter userspace (nothing in Bullseye's
default boot touches these registers this way), but modern systemd
(observed with Debian 13/Trixie's systemd 257) writes to RSTC as part
of routine early-boot watchdog probing. With the old code, this fires
an immediate reset a few seconds into boot; combined with -no-reboot
this looks exactly like a QEMU crash (clean exit, no panic, no guest
reboot message) with the last log line being the RSTC/WDOG write.

Fix this by actually implementing the watchdog as a QEMUTimer: writes
to RSTC/WDOG (re)compute the timeout from the WDOG register (in units
of 1/65536 s, per the real hardware) and arm a timer for that many
nanoseconds out; only when the timer actually fires do we request a
system reset or shutdown, matching real hardware behavior. Clearing
the write-config bits or the WDOG value disarms the timer, and reset
disarms it too.

Verified against real Raspberry Pi OS images under the patched
raspi4b machine: Bullseye (5.15) and Bookworm (6.12) never exercised
this path either way; Trixie (6.18, systemd 257) no longer crashes at
boot and reaches a working login/SSH state.

Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
 hw/misc/bcm2835_powermgt.c         | 49 +++++++++++++++++++++++-------
 include/hw/misc/bcm2835_powermgt.h |  2 ++
 2 files changed, 40 insertions(+), 11 deletions(-)

diff --git a/hw/misc/bcm2835_powermgt.c b/hw/misc/bcm2835_powermgt.c
index 3ec7abad0e..d90b9a0c3e 100644
--- a/hw/misc/bcm2835_powermgt.c
+++ b/hw/misc/bcm2835_powermgt.c
@@ -19,10 +19,40 @@
 #define PASSWORD_MASK 0xff000000
 
 #define R_RSTC 0x1c
-#define V_RSTC_RESET 0x20
+#define V_RSTC_WRCFG_MASK 0x30
+#define V_RSTC_FULL_RESET 0x20
 #define R_RSTS 0x20
 #define V_RSTS_POWEROFF 0x555 /* Linux uses partition 63 to indicate halt. */
 #define R_WDOG 0x24
+#define V_WDOG_TIME_MASK 0xfffff
+#define WDOG_TICKS_PER_SECOND 65536
+
+static void bcm2835_powermgt_expire(void *opaque)
+{
+    BCM2835PowerMgtState *s = opaque;
+
+    if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) {
+        qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+    } else {
+        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+    }
+}
+
+static void bcm2835_powermgt_update_wdog(BCM2835PowerMgtState *s)
+{
+    uint64_t timeout_ns;
+
+    if ((s->rstc & V_RSTC_WRCFG_MASK) != V_RSTC_FULL_RESET ||
+        s->wdog == 0) {
+        timer_del(s->wdog_timer);
+        return;
+    }
+
+    timeout_ns = muldiv64(s->wdog, NANOSECONDS_PER_SECOND,
+                          WDOG_TICKS_PER_SECOND);
+    timer_mod(s->wdog_timer,
+              qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout_ns);
+}
 
 static uint64_t bcm2835_powermgt_read(void *opaque, hwaddr offset,
                                       unsigned size)
@@ -70,13 +100,7 @@ static void bcm2835_powermgt_write(void *opaque, hwaddr offset,
     switch (offset) {
     case R_RSTC:
         s->rstc = value;
-        if (value & V_RSTC_RESET) {
-            if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) {
-                qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
-            } else {
-                qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
-            }
-        }
+        bcm2835_powermgt_update_wdog(s);
         break;
     case R_RSTS:
         qemu_log_mask(LOG_UNIMP,
@@ -84,9 +108,8 @@ static void bcm2835_powermgt_write(void *opaque, hwaddr offset,
         s->rsts = value;
         break;
     case R_WDOG:
-        qemu_log_mask(LOG_UNIMP,
-                      "bcm2835_powermgt_write: WDOG\n");
-        s->wdog = value;
+        s->wdog = value & V_WDOG_TIME_MASK;
+        bcm2835_powermgt_update_wdog(s);
         break;
 
     default:
@@ -113,6 +136,7 @@ static const VMStateDescription vmstate_bcm2835_powermgt = {
         VMSTATE_UINT32(rstc, BCM2835PowerMgtState),
         VMSTATE_UINT32(rsts, BCM2835PowerMgtState),
         VMSTATE_UINT32(wdog, BCM2835PowerMgtState),
+        VMSTATE_TIMER_PTR(wdog_timer, BCM2835PowerMgtState),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -124,6 +148,8 @@ static void bcm2835_powermgt_init(Object *obj)
     memory_region_init_io(&s->iomem, obj, &bcm2835_powermgt_ops, s,
                           TYPE_BCM2835_POWERMGT, 0x200);
     sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+    s->wdog_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                                 bcm2835_powermgt_expire, s);
 }
 
 static void bcm2835_powermgt_reset(DeviceState *dev)
@@ -134,6 +160,7 @@ static void bcm2835_powermgt_reset(DeviceState *dev)
     s->rstc = 0x00000102;
     s->rsts = 0x00001000;
     s->wdog = 0x00000000;
+    timer_del(s->wdog_timer);
 }
 
 static void bcm2835_powermgt_class_init(ObjectClass *klass, const void *data)
diff --git a/include/hw/misc/bcm2835_powermgt.h b/include/hw/misc/bcm2835_powermgt.h
index fb0740c01e..d1903a9cce 100644
--- a/include/hw/misc/bcm2835_powermgt.h
+++ b/include/hw/misc/bcm2835_powermgt.h
@@ -12,6 +12,7 @@
 #define BCM2835_POWERMGT_H
 
 #include "hw/core/sysbus.h"
+#include "qemu/timer.h"
 #include "qom/object.h"
 
 #define TYPE_BCM2835_POWERMGT "bcm2835-powermgt"
@@ -24,6 +25,7 @@ struct BCM2835PowerMgtState {
     uint32_t rstc;
     uint32_t rsts;
     uint32_t wdog;
+    QEMUTimer *wdog_timer;
 };
 
 #endif
-- 
2.47.1



  reply	other threads:[~2026-07-25 23:12 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-25 23:11 [PATCH 0/2] hw/arm/raspi4b: fix watchdog and GENET bugs hit by modern distro kernels Marcelo Manzo
2026-07-25 23:11 ` Marcelo Manzo [this message]
2026-07-25 23:11 ` [PATCH 2/2] hw/net/bcm2838_genet: fix per-ring DMA status and RX ring selection Marcelo Manzo

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=20260725231142.61663-2-marcelomanzo@gmail.com \
    --to=marcelomanzo@gmail.com \
    --cc=jasowangio@gmail.com \
    --cc=peter.maydell@linaro.org \
    --cc=philmd@mailo.com \
    --cc=qemu-arm@nongnu.org \
    --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.