Skip to content

Commit

Permalink
Major watchdog timer rewrite based on hardware research
Browse files Browse the repository at this point in the history
  • Loading branch information
calc84maniac committed Jul 12, 2024
1 parent 4cb42e2 commit ee4aa02
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 40 deletions.
197 changes: 161 additions & 36 deletions core/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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);
Expand All @@ -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:
Expand All @@ -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");
}
Expand Down
19 changes: 15 additions & 4 deletions core/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,25 @@ extern "C" {
#include <stdint.h>
#include <stdbool.h>

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 {
Expand Down

0 comments on commit ee4aa02

Please sign in to comment.