From ee4aa02e59475e6cc6a25a306b2f21fd17bb6a14 Mon Sep 17 00:00:00 2001 From: Brendan Fletcher Date: Fri, 12 Jul 2024 15:17:56 -0400 Subject: [PATCH] Major watchdog timer rewrite based on hardware research --- core/misc.c | 197 ++++++++++++++++++++++++++++++++++++++++++---------- core/misc.h | 19 +++-- 2 files changed, 176 insertions(+), 40 deletions(-) diff --git a/core/misc.c b/core/misc.c index 4dab8322b..1989cb453 100644 --- a/core/misc.c +++ b/core/misc.c @@ -17,21 +17,104 @@ cxxx_state_t cxxx; /* Global CXXX state */ fxxx_state_t fxxx; /* Global FXXX state */ static inline uint32_t watchdog_counter(void) { - return sched_ticks_remaining(SCHED_WATCHDOG) - 1; + if (watchdog.mode != WATCHDOG_COUNTER || !sched_active(SCHED_WATCHDOG)) { + return watchdog.count; + } + return sched_ticks_remaining(SCHED_WATCHDOG); } -static void watchdog_event(enum sched_item_id id) { - assert(watchdog.control & 1); +static inline uint8_t watchdog_pulse_counter(void) { + if (watchdog.mode != WATCHDOG_PULSE || !sched_active(SCHED_WATCHDOG)) { + return watchdog.pulseCount; + } + return sched_ticks_remaining(SCHED_WATCHDOG) - 1; +} - watchdog.status = 1; - if (watchdog.control & 2) { +static void watchdog_pulse(uint8_t signals) { + if (signals & 2) { cpu_crash("[CEmu] Reset triggered by watchdog timer.\n"); } - if (watchdog.control & 4) { + if (signals & 4) { gui_console_printf("[CEmu] Watchdog NMI triggered.\n"); cpu_nmi(); } - sched_repeat(id, (uint64_t)watchdog.load + 1); +} + +static inline enum clock_id watchdog_clock(void) { + return watchdog.control & 0x10 ? CLOCK_32K : CLOCK_CPU; +} + +static void watchdog_repeat_counter(enum sched_item_id id) { + assert(watchdog.count != 0); + if (watchdog.control & 1) { + if (watchdog_clock() == CLOCK_32K) { + sched_repeat(id, 0); /* re-activate to event cycle */ + sched_set_item_clock(id, CLOCK_32K); + } + sched_repeat(id, watchdog.count); + } + watchdog.mode = WATCHDOG_COUNTER; +} + +static void watchdog_repeat_expired(enum sched_item_id id) { + assert(watchdog.count == 0); + sched_repeat(id, 1); + watchdog.mode = WATCHDOG_EXPIRED; +} + +static void watchdog_event(enum sched_item_id id) { + switch (watchdog.mode) { + case WATCHDOG_COUNTER: /* May be CLOCK_CPU or CLOCK_32K */ + watchdog.count = 0; + if (watchdog_clock() != CLOCK_CPU) { + sched_repeat(id, 0); /* re-activate to event cycle */ + sched_set_item_clock(id, CLOCK_CPU); + } + watchdog_repeat_expired(id); + break; + + case WATCHDOG_PULSE: /* Always CLOCK_CPU */ + watchdog.pulseCount = watchdog.pulseLoad; + watchdog.blockPulseReload = false; + if (unlikely(watchdog.pendingReload)) { + watchdog.pendingReload = false; + sched_repeat(id, 1); + watchdog.mode = WATCHDOG_RELOAD; + } else if (likely(watchdog.count != 0)) { + watchdog_repeat_counter(id); + } else { + watchdog_repeat_expired(id); + } + break; + + case WATCHDOG_RELOAD: /* Always CLOCK_CPU */ + watchdog.count = watchdog.load; + if (likely(watchdog.count != 0)) { + watchdog_repeat_counter(id); + break; + } + fallthrough; + case WATCHDOG_EXPIRED: /* Always CLOCK_CPU */ + watchdog.count = watchdog.load; + if (watchdog.control & 1) { + watchdog_pulse(watchdog.control); + if (!watchdog.blockStatus) { + watchdog.status = 1; + } + } + watchdog.blockStatus = false; + if (watchdog.pulseCount == 0) { + watchdog.blockPulseReload = true; + sched_repeat(id, 1); + } else if (watchdog.control & 1) { + sched_repeat(id, watchdog.pulseCount + 1); + } + watchdog.mode = WATCHDOG_PULSE; + break; + + default: + unreachable(); + } } /* Watchdog read routine */ @@ -43,23 +126,19 @@ static uint8_t watchdog_read(const uint16_t pio, bool peek) { switch (index) { case 0x000: case 0x001: case 0x002: case 0x003: - if (watchdog.control & 1) { - value = read8(watchdog_counter(), bit_offset); - } else { - value = read8(watchdog.count, bit_offset); - } + value = read8(watchdog_counter(), bit_offset); break; case 0x004: case 0x005: case 0x006: case 0x007: value = read8(watchdog.load, bit_offset); break; case 0x00C: - value = read8(watchdog.control, bit_offset); + value = watchdog.control; break; case 0x010: - value = read8(watchdog.status, bit_offset); + value = watchdog.status; break; case 0x018: - value = read8(watchdog.length, bit_offset); + value = watchdog_pulse_counter(); break; case 0x01C: case 0x01D: case 0x01E: case 0x01F: value = read8(watchdog.revision, bit_offset); @@ -83,38 +162,84 @@ static void watchdog_write(const uint16_t pio, const uint8_t byte, bool poke) { switch (index) { case 0x004: case 0x005: case 0x006: case 0x007: write8(watchdog.load, bit_offset, byte); + if (watchdog.mode == WATCHDOG_PULSE && watchdog.count == 0) { + watchdog.count = watchdog.load; + } break; - case 0x008: case 0x009: - write8(watchdog.restart, bit_offset, byte); - if (watchdog.restart == 0x5AB9) { - if (watchdog.control & 1) { - sched_set(SCHED_WATCHDOG, (uint64_t)watchdog.load + 1); + case 0x008: + if (byte == 0xB9) { + if (watchdog.mode == WATCHDOG_COUNTER && sched_active(SCHED_WATCHDOG)) { + watchdog.count = sched_ticks_remaining(SCHED_WATCHDOG); + sched_set_item_clock(SCHED_WATCHDOG, CLOCK_CPU); + sched_set(SCHED_WATCHDOG, 2); + watchdog.mode = WATCHDOG_RELOAD; + } else if (watchdog.mode == WATCHDOG_PULSE && sched_active(SCHED_WATCHDOG) && sched_ticks_remaining(SCHED_WATCHDOG) == 1) { + watchdog.pendingReload = true; } else { watchdog.count = watchdog.load; + if (watchdog.mode == WATCHDOG_COUNTER && unlikely(watchdog.count == 0)) { + assert(!(watchdog.control & 1)); + sched_set_item_clock(SCHED_WATCHDOG, CLOCK_CPU); + if (unlikely(watchdog.pulseCount == 0)) { + watchdog.blockPulseReload = true; + sched_set(SCHED_WATCHDOG, 1); + } + watchdog.mode = WATCHDOG_PULSE; + } } - watchdog.restart = 0; } break; case 0x00C: old = watchdog.control; - write8(watchdog.control, bit_offset, byte); - if (watchdog.control & 16) { - sched_set_item_clock(SCHED_WATCHDOG, CLOCK_32K); - } else { - sched_set_item_clock(SCHED_WATCHDOG, CLOCK_CPU); + watchdog.control = byte; + if (watchdog.mode == WATCHDOG_COUNTER) { + sched_set_item_clock(SCHED_WATCHDOG, watchdog_clock()); + } else if (watchdog.mode == WATCHDOG_PULSE) { + if (byte & old & 1) { + watchdog_pulse(byte & ~old); + } } - if ((watchdog.control ^ old) & 1) { - if (watchdog.control & 1) { - sched_set(SCHED_WATCHDOG, (uint64_t)watchdog.count + 1); + if ((byte ^ old) & 1) { + if (byte & 1) { + if (!sched_active(SCHED_WATCHDOG)) { + if (watchdog.mode == WATCHDOG_COUNTER) { + assert(watchdog.count != 0); + sched_set(SCHED_WATCHDOG, watchdog.count); + } else if (watchdog.mode == WATCHDOG_PULSE) { + watchdog_pulse(byte); + sched_set(SCHED_WATCHDOG, watchdog.pulseCount + 1); + } + } } else { - watchdog.count = watchdog_counter(); - sched_clear(SCHED_WATCHDOG); + assert(sched_active(SCHED_WATCHDOG)); + if (watchdog.mode == WATCHDOG_COUNTER) { + watchdog.count = sched_ticks_remaining(SCHED_WATCHDOG); + sched_clear(SCHED_WATCHDOG); + } else if (watchdog.mode == WATCHDOG_PULSE) { + watchdog.pulseCount = sched_ticks_remaining(SCHED_WATCHDOG) - 1; + if (watchdog.pulseCount != 0) { + sched_clear(SCHED_WATCHDOG); + } + } } } break; - case 0x014: - if (byte & 1) { - watchdog.status = 0; + case 0x014: case 0x015: case 0x016: case 0x017: + watchdog.status = 0; + if (unlikely(watchdog.mode == WATCHDOG_EXPIRED)) { + assert(sched_ticks_remaining(SCHED_WATCHDOG) == 1); + watchdog.blockStatus = true; + } + break; + case 0x018: + watchdog.pulseLoad = byte; + fallthrough; + case 0x019: case 0x01A: case 0x01B: + if (!unlikely(watchdog.blockPulseReload)) { + watchdog.pulseCount = watchdog.pulseLoad; + if (watchdog.mode == WATCHDOG_PULSE && (watchdog.pulseCount == 0 || sched_active(SCHED_WATCHDOG))) { + sched_set(SCHED_WATCHDOG, watchdog.pulseCount + 2); + } } break; default: @@ -133,8 +258,8 @@ void watchdog_reset() { sched_init_event(SCHED_WATCHDOG, CLOCK_CPU, watchdog_event); watchdog.revision = 0x00010602; - watchdog.load = 0x03EF1480; /* (66MHz) */ - watchdog.count = 0x03EF1480; + watchdog.count = watchdog.load = 0x03EF1480; /* (66MHz) */ + watchdog.pulseCount = watchdog.pulseLoad = 0xFF; gui_console_printf("[CEmu] Watchdog timer reset.\n"); } diff --git a/core/misc.h b/core/misc.h index 0c569bb5d..ef1d55153 100644 --- a/core/misc.h +++ b/core/misc.h @@ -11,14 +11,25 @@ extern "C" { #include #include +typedef enum watchdog_mode { + WATCHDOG_COUNTER, + WATCHDOG_PULSE, + WATCHDOG_EXPIRED, + WATCHDOG_RELOAD +} watchdog_mode_t; + typedef struct watchdog_state { uint32_t count; uint32_t load; - uint16_t restart; - uint32_t control; - uint32_t status; - uint32_t length; uint32_t revision; + uint8_t control; + uint8_t status; + uint8_t pulseCount; + uint8_t pulseLoad; + uint8_t mode; + bool blockStatus; + bool blockPulseReload; + bool pendingReload; } watchdog_state_t; typedef struct protected_state {