@samitouri / QOSamiQemu / commits / 21fcfb6046

hw/misc/bcm2835_powermgt: implement a real watchdog timer

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. This is a migration compatibility break for the raspi boards. Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com> [PMM: bump vmstate version IDs, note migration break in commit msg] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

Marcelo Manzo committed Aug 11, 2026 at 20:14 UTC 21fcfb6046082a06b82c17688634a1b769ee63a0
2 files changed +42 -13
hw/misc/bcm2835_powermgt.c
+40 -13
@@ -19,10 +19,40 @@
19 #define PASSWORD_MASK 0xff000000
20
21 #define R_RSTC 0x1c
22 -#define V_RSTC_RESET 0x20
22 +#define V_RSTC_WRCFG_MASK 0x30
23 +#define V_RSTC_FULL_RESET 0x20
24 #define R_RSTS 0x20
25 #define V_RSTS_POWEROFF 0x555 /* Linux uses partition 63 to indicate halt. */
26 #define R_WDOG 0x24
27 +#define V_WDOG_TIME_MASK 0xfffff
28 +#define WDOG_TICKS_PER_SECOND 65536
29 +
30 +static void bcm2835_powermgt_expire(void *opaque)
31 +{
32 + BCM2835PowerMgtState *s = opaque;
33 +
34 + if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) {
35 + qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
36 + } else {
37 + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
38 + }
39 +}
40 +
41 +static void bcm2835_powermgt_update_wdog(BCM2835PowerMgtState *s)
42 +{
43 + uint64_t timeout_ns;
44 +
45 + if ((s->rstc & V_RSTC_WRCFG_MASK) != V_RSTC_FULL_RESET ||
46 + s->wdog == 0) {
47 + timer_del(s->wdog_timer);
48 + return;
49 + }
50 +
51 + timeout_ns = muldiv64(s->wdog, NANOSECONDS_PER_SECOND,
52 + WDOG_TICKS_PER_SECOND);
53 + timer_mod(s->wdog_timer,
54 + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout_ns);
55 +}
56
57 static uint64_t bcm2835_powermgt_read(void *opaque, hwaddr offset,
58 unsigned size)
@@ -70,13 +100,7 @@ static void bcm2835_powermgt_write(void *opaque, hwaddr offset,
100 switch (offset) {
101 case R_RSTC:
102 s->rstc = value;
73 - if (value & V_RSTC_RESET) {
74 - if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) {
75 - qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
76 - } else {
77 - qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
78 - }
79 - }
103 + bcm2835_powermgt_update_wdog(s);
104 break;
105 case R_RSTS:
106 qemu_log_mask(LOG_UNIMP,
@@ -84,9 +108,8 @@ static void bcm2835_powermgt_write(void *opaque, hwaddr offset,
108 s->rsts = value;
109 break;
110 case R_WDOG:
87 - qemu_log_mask(LOG_UNIMP,
88 - "bcm2835_powermgt_write: WDOG\n");
89 - s->wdog = value;
111 + s->wdog = value & V_WDOG_TIME_MASK;
112 + bcm2835_powermgt_update_wdog(s);
113 break;
114
115 default:
@@ -107,12 +130,13 @@ static const MemoryRegionOps bcm2835_powermgt_ops = {
130
131 static const VMStateDescription vmstate_bcm2835_powermgt = {
132 .name = TYPE_BCM2835_POWERMGT,
110 - .version_id = 1,
111 - .minimum_version_id = 1,
133 + .version_id = 2,
134 + .minimum_version_id = 2,
135 .fields = (const VMStateField[]) {
136 VMSTATE_UINT32(rstc, BCM2835PowerMgtState),
137 VMSTATE_UINT32(rsts, BCM2835PowerMgtState),
138 VMSTATE_UINT32(wdog, BCM2835PowerMgtState),
139 + VMSTATE_TIMER_PTR(wdog_timer, BCM2835PowerMgtState),
140 VMSTATE_END_OF_LIST()
141 }
142 };
@@ -124,6 +148,8 @@ static void bcm2835_powermgt_init(Object *obj)
148 memory_region_init_io(&s->iomem, obj, &bcm2835_powermgt_ops, s,
149 TYPE_BCM2835_POWERMGT, 0x200);
150 sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
151 + s->wdog_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
152 + bcm2835_powermgt_expire, s);
153 }
154
155 static void bcm2835_powermgt_reset(DeviceState *dev)
@@ -134,6 +160,7 @@ static void bcm2835_powermgt_reset(DeviceState *dev)
160 s->rstc = 0x00000102;
161 s->rsts = 0x00001000;
162 s->wdog = 0x00000000;
163 + timer_del(s->wdog_timer);
164 }
165
166 static void bcm2835_powermgt_class_init(ObjectClass *klass, const void *data)
include/hw/misc/bcm2835_powermgt.h
+2
@@ -12,6 +12,7 @@
12 #define BCM2835_POWERMGT_H
13
14 #include "hw/core/sysbus.h"
15 +#include "qemu/timer.h"
16 #include "qom/object.h"
17
18 #define TYPE_BCM2835_POWERMGT "bcm2835-powermgt"
@@ -24,6 +25,7 @@ struct BCM2835PowerMgtState {
25 uint32_t rstc;
26 uint32_t rsts;
27 uint32_t wdog;
28 + QEMUTimer *wdog_timer;
29 };
30
31 #endif