summaryrefslogtreecommitdiff
path: root/src/oracle/timg_ref.c
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 12:40:53 -0300
committerGabriel Schneider <[email protected]>2026-08-25 12:46:51 -0300
commitf5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch)
tree2731a3ed4e51cae09e184e25778eded5fc37d1f5 /src/oracle/timg_ref.c
downloadesp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.tar.gz
esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.zip
zig-p4: pure-Zig ESP32-P4 toolchain
build.zig generates the linker script and drives Zig's own LLD; tools/image.zig turns the ELF into a flashable image and tools/{rom,serial}.zig speak the mask ROM loader over the UART. No CMake, ninja, idf.py, esptool, or external linker. src/soc.zig is a comptime register model over ESP-IDF's own *_reg.h headers; src/hal/ adds peripheral sequences; src/io/ implements std.Io for the chip; src/oracle/ diffs this HAL against ESP-IDF's on the die.
Diffstat (limited to 'src/oracle/timg_ref.c')
-rw-r--r--src/oracle/timg_ref.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/src/oracle/timg_ref.c b/src/oracle/timg_ref.c
new file mode 100644
index 0000000..38bccc8
--- /dev/null
+++ b/src/oracle/timg_ref.c
@@ -0,0 +1,197 @@
+/* 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;
+}