/* The reference implementation for the timer groups and their watchdogs, which is ESP-IDF's own. * * Thin external-linkage wrappers over `timer_ll.h`, `mwdt_ll.h` and `timg_ll.h`, so Zig can call * IDF's `static inline` functions and the differential harness can run both implementations in one * image on one boot. There is no logic here: anything clever would be a third implementation to * doubt. * * Two things about the watchdog wrappers are deliberate. The write-protect dance is *inside* each * wrapper (`mwdt_ll_write_protect_disable` ... `mwdt_ll_write_protect_enable`) because that is what * IDF's callers do - `mwdt_ll_config_stage` itself will silently do nothing if protection is on - * and because our side does the same thing through `timg.unlock`/`release`. The two sides have to be * the same operation, key register included, or comparing WDTWPROTECT afterwards means nothing. * And nothing here ever calls `mwdt_ll_enable` on group 0: TIMG0 hosts the watchdog the rest of the * system depends on not firing. */ /* IDF's clock and reset LL functions are shadowed by a wrapper macro referencing * `__DECLARE_RCC_ATOMIC_ENV` / `__DECLARE_RCC_RC_ATOMIC_ENV`, identifiers IDF never defines * anywhere: their purpose is to make an unguarded call fail to compile, because the only legal * caller holds a FreeRTOS spinlock. There is no FreeRTOS here and core 1 is held in reset at * power-on, so declaring the names is exactly as safe as the spinlock would be - and it is what * IDF's own bootloader does (bootloader_support/src/bootloader_console.c:53). */ static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); static int __DECLARE_RCC_RC_ATOMIC_ENV __attribute__((unused)); #include "hal/timer_ll.h" #include "hal/mwdt_ll.h" #include "hal/timg_ll.h" #include "soc/timer_group_struct.h" static timg_dev_t *grp(int group) { return TIMER_LL_GET_HW(group); } /* ------------------------------------------------------------------ general purpose timer */ void oracle_timg_set_divider(int group, unsigned timer, unsigned divider) { timer_ll_set_clock_prescale(grp(group), timer, divider); } void oracle_timg_set_direction_up(int group, unsigned timer, int up) { timer_ll_set_count_direction(grp(group), timer, up ? GPTIMER_COUNT_UP : GPTIMER_COUNT_DOWN); } void oracle_timg_set_auto_reload(int group, unsigned timer, int en) { timer_ll_enable_auto_reload(grp(group), timer, en != 0); } void oracle_timg_enable_counter(int group, unsigned timer, int en) { timer_ll_enable_counter(grp(group), timer, en != 0); } void oracle_timg_enable_alarm(int group, unsigned timer, int en) { timer_ll_enable_alarm(grp(group), timer, en != 0); } void oracle_timg_set_alarm_value(int group, unsigned timer, unsigned long long value) { timer_ll_set_alarm_value(grp(group), timer, value); } void oracle_timg_set_reload_value(int group, unsigned timer, unsigned long long value) { timer_ll_set_reload_value(grp(group), timer, value); } unsigned long long oracle_timg_get_reload_value(int group, unsigned timer) { return timer_ll_get_reload_value(grp(group), timer); } void oracle_timg_trigger_soft_reload(int group, unsigned timer) { timer_ll_trigger_soft_reload(grp(group), timer); } /* The latch-then-read sequence: `timer_ll_trigger_soft_capture` writes TxUPDATE and spins until the * hardware clears it, and only then is the TxHI/TxLO pair meaningful. Exposed as one call because * that is how our `timg.read` expresses it, and splitting it would compare halves of a sequence. */ unsigned long long oracle_timg_read_counter(int group, unsigned timer) { timer_ll_trigger_soft_capture(grp(group), timer); return timer_ll_get_counter_value(grp(group), timer); } void oracle_timg_set_clock_source_xtal(int group, unsigned timer) { timer_ll_set_clock_source(group, timer, GPTIMER_CLK_SRC_XTAL); } void oracle_timg_set_clock_source_pll80m(int group, unsigned timer) { timer_ll_set_clock_source(group, timer, GPTIMER_CLK_SRC_PLL_F80M); } void oracle_timg_enable_timer_clock(int group, unsigned timer, int en) { timer_ll_enable_clock(group, timer, en != 0); } void oracle_timg_enable_bus_clock(int group, int en) { timg_ll_enable_bus_clock(group, en != 0); } /* Pulses the group's reset bit and then clears WDT_FLASHBOOT_MOD_EN, which the reset re-arms * (timg_ll.h:51-71). The clearing is the interesting half: leave it out and the board reboots a * moment later with nothing on the console to explain it. */ void oracle_timg_reset_register(int group) { timg_ll_reset_register(group); } /* --------------------------------------------------------------------------------- watchdog */ void oracle_mwdt_set_stage(int group, unsigned stage, unsigned timeout, unsigned action) { mwdt_ll_write_protect_disable(grp(group)); mwdt_ll_config_stage(grp(group), (wdt_stage_t)stage, timeout, (wdt_stage_action_t)action); mwdt_ll_write_protect_enable(grp(group)); } void oracle_mwdt_disable_stage(int group, unsigned stage) { mwdt_ll_write_protect_disable(grp(group)); mwdt_ll_disable_stage(grp(group), stage); mwdt_ll_write_protect_enable(grp(group)); } void oracle_mwdt_set_prescaler(int group, unsigned prescaler) { mwdt_ll_write_protect_disable(grp(group)); mwdt_ll_set_prescaler(grp(group), prescaler); mwdt_ll_write_protect_enable(grp(group)); } void oracle_mwdt_set_cpu_reset_length(int group, unsigned length) { mwdt_ll_write_protect_disable(grp(group)); mwdt_ll_set_cpu_reset_length(grp(group), (wdt_reset_sig_length_t)length); mwdt_ll_write_protect_enable(grp(group)); } void oracle_mwdt_set_sys_reset_length(int group, unsigned length) { mwdt_ll_write_protect_disable(grp(group)); mwdt_ll_set_sys_reset_length(grp(group), (wdt_reset_sig_length_t)length); mwdt_ll_write_protect_enable(grp(group)); } void oracle_mwdt_set_flashboot_en(int group, int en) { mwdt_ll_write_protect_disable(grp(group)); mwdt_ll_set_flashboot_en(grp(group), en != 0); mwdt_ll_write_protect_enable(grp(group)); } void oracle_mwdt_set_enabled(int group, int en) { mwdt_ll_write_protect_disable(grp(group)); if (en) { mwdt_ll_enable(grp(group)); } else { mwdt_ll_disable(grp(group)); } mwdt_ll_write_protect_enable(grp(group)); } void oracle_mwdt_feed(int group) { mwdt_ll_write_protect_disable(grp(group)); mwdt_ll_feed(grp(group)); mwdt_ll_write_protect_enable(grp(group)); } /* The two halves of the protection dance on their own, so a case can check that our key value and * IDF's are the same word rather than only that a guarded sequence ends up locked. */ void oracle_mwdt_write_protect_disable(int group) { mwdt_ll_write_protect_disable(grp(group)); } void oracle_mwdt_write_protect_enable(int group) { mwdt_ll_write_protect_enable(grp(group)); } int oracle_mwdt_is_enabled(int group) { return mwdt_ll_check_if_enabled(grp(group)) ? 1 : 0; }