summaryrefslogtreecommitdiff
path: root/src/oracle/uart_ref.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/oracle/uart_ref.c')
-rw-r--r--src/oracle/uart_ref.c163
1 files changed, 163 insertions, 0 deletions
diff --git a/src/oracle/uart_ref.c b/src/oracle/uart_ref.c
new file mode 100644
index 0000000..6476fd4
--- /dev/null
+++ b/src/oracle/uart_ref.c
@@ -0,0 +1,163 @@
+/* UART's reference implementation: ESP-IDF's own `uart_ll.h`, given external linkage.
+ *
+ * No logic here. Anything clever in this file would be a third implementation to doubt, and the
+ * whole point of the differential is that one side is unmodified IDF.
+ *
+ * Two IDF-specific notes:
+ *
+ * - Several of these LL functions are shadowed by a macro that references
+ * `__DECLARE_RCC_ATOMIC_ENV`, an identifier IDF never defines anywhere, so that an unguarded call
+ * fails to compile: HP_SYS_CLKRST's PERI_CLK_CTRL and SOC_CLK_CTRL registers are shared with
+ * unrelated peripherals and the only legal caller holds a spinlock. There is no FreeRTOS here and
+ * core 1 is held in reset at power-on, so declaring the name is as safe as the spinlock would be,
+ * and it is what IDF's own bootloader does (bootloader_console.c:53).
+ * - `uart_ll_set_sclk` and `uart_ll_set_baudrate` are *function-like macros* wrapping
+ * `uart_ll_set_sclk` / `_uart_ll_set_baudrate` (uart_ll.h:477-481, 578-586). Calling the
+ * underscored inner function directly would skip the very guard above, so these wrappers call
+ * through the macro. For `set_sclk` the macro and the function share a name, which works only
+ * because the macro is defined after the function.
+ */
+
+static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused));
+
+#include "hal/uart_ll.h"
+#include "soc/uart_struct.h"
+
+/* Nothing here touches UART0. Resetting UART0 clears UART_CLKDIV, and UART0 is this board's
+ * console: the output turns to garbage mid-character and the board takes a watchdog reset with
+ * nothing readable left to say why. Measured on this board. The suite drives UART1. */
+static inline uart_dev_t *dev(unsigned num)
+{
+ return UART_LL_GET_HW(num);
+}
+
+/* ---------------------------------------------------------------------- clocks, reset, baud */
+
+/* Only the operations uart_cases.zig actually pairs are wrapped. A wrapper with no case behind it
+ * is unreachable code that looks like coverage - and for `uart_ll_reset_register` in particular it
+ * would be a loaded gun, since the harness restores this block through its own reset-bit path and
+ * calling that function with num = 0 kills the console. */
+
+/* The whole point of the UART suite: IDF's baud-rate arithmetic, which picks an integer pre-divider
+ * and then a 12-bit divider with a 4-bit fraction, and commits through REG_UPDATE. Returns false on
+ * the rates it cannot represent - which is a real outcome, not an error path, since a 12-bit divider
+ * cannot reach every baud from every source clock. uart_ll.h:532-588. */
+int oracle_uart_set_baudrate(unsigned num, unsigned baud, unsigned sclk_freq)
+{
+ return _uart_ll_set_baudrate(UART_LL_GET_HW(num), baud, sclk_freq) ? 1 : 0;
+}
+
+void oracle_uart_enable_bus_clock(unsigned num, int enable)
+{
+ uart_ll_enable_bus_clock((uart_port_t)num, enable != 0);
+}
+
+void oracle_uart_sclk_enable(unsigned num)
+{
+ uart_ll_sclk_enable(dev(num));
+}
+
+
+/* `sel` is the soc_module_clk_t value, not the raw field encoding: UART_SCLK_XTAL,
+ * UART_SCLK_RTC, UART_SCLK_PLL_F80M. The mapping from those to the 2-bit field is the part of
+ * uart_ll_set_sclk under test. */
+/* The clock source, named on the C side rather than passed as a number.
+ *
+ * `UART_SCLK_XTAL` is an enumerator of `soc_module_clk_t`, not a small ordinal: clk_tree_defs.h:280
+ * defines it as SOC_MOD_CLK_XTAL, whose value is whatever position it happens to occupy in a
+ * chip-wide enum. Passing 0 from Zig - which is what a first version did - lands on an unmatched
+ * case in `_uart_ll_set_sclk`, whose default is HAL_ASSERT(false); compiled at assertion level 0
+ * that is `__builtin_unreachable()`, so the select is left at a value with no clock behind it and
+ * the very next `uart_ll_update` spins forever on a commit that cannot land. The harness reached the
+ * first UART case and went silent, which looks exactly like a crash.
+ *
+ * Keeping the enumerator on this side of the boundary removes the class of bug entirely. */
+void oracle_uart_set_sclk_xtal(unsigned num)
+{
+ uart_ll_set_sclk(UART_LL_GET_HW(num), UART_SCLK_XTAL);
+}
+
+void oracle_uart_set_sclk_pll(unsigned num)
+{
+ uart_ll_set_sclk(UART_LL_GET_HW(num), UART_SCLK_PLL_F80M);
+}
+
+void oracle_uart_set_sclk(unsigned num, unsigned sel)
+{
+ uart_ll_set_sclk(dev(num), (soc_module_clk_t)sel);
+}
+
+
+/* -------------------------------------------------------------------------------- data format */
+
+void oracle_uart_set_data_bit_num(unsigned num, unsigned bits)
+{
+ uart_ll_set_data_bit_num(dev(num), (uart_word_length_t)bits);
+}
+
+void oracle_uart_set_stop_bits(unsigned num, unsigned stop)
+{
+ uart_ll_set_stop_bits(dev(num), (uart_stop_bits_t)stop);
+}
+
+void oracle_uart_set_parity(unsigned num, unsigned parity)
+{
+ uart_ll_set_parity(dev(num), (uart_parity_t)parity);
+}
+
+/* ---------------------------------------------------------------------------------------- FIFO */
+
+void oracle_uart_txfifo_rst(unsigned num)
+{
+ uart_ll_txfifo_rst(dev(num));
+}
+
+void oracle_uart_rxfifo_rst(unsigned num)
+{
+ uart_ll_rxfifo_rst(dev(num));
+}
+
+
+/* ------------------------------------------------------------------------- loopback and update */
+
+void oracle_uart_set_loop_back(unsigned num, int enable)
+{
+ uart_ll_set_loop_back(dev(num), enable != 0);
+}
+
+void oracle_uart_update(unsigned num)
+{
+ uart_ll_update(dev(num));
+}
+
+/* ---------------------------------------------------------------------------------- pin routing */
+
+/* IDF routes UART pins through esp_rom_gpio_connect_*_signal / gpio_ll, not through uart_ll, so the
+ * reference for pin routing is the GPIO LL - the same functions gpio_ref.c wraps, called with this
+ * UART's signal indices. Kept here rather than in gpio_ref.c because the *signal index* is the part
+ * under test, and it belongs to the UART. */
+#include "hal/gpio_ll.h"
+#include "soc/gpio_struct.h"
+/* The signal indices themselves, which are not in any *_ll.h - and are the whole point of these two
+ * wrappers: the Zig side reads the same macros through translate-c. */
+#include "soc/gpio_sig_map.h"
+
+void oracle_uart_route_tx(unsigned num, unsigned pin)
+{
+ const unsigned sig[5] = {
+ UART0_TXD_PAD_OUT_IDX, UART1_TXD_PAD_OUT_IDX, UART2_TXD_PAD_OUT_IDX,
+ UART3_TXD_PAD_OUT_IDX, UART4_TXD_PAD_OUT_IDX,
+ };
+ gpio_ll_set_output_signal_matrix_source(&GPIO, pin, sig[num], false);
+ gpio_ll_set_output_enable_ctrl(&GPIO, pin, true, false);
+}
+
+void oracle_uart_route_rx(unsigned num, unsigned pin)
+{
+ const unsigned sig[5] = {
+ UART0_RXD_PAD_IN_IDX, UART1_RXD_PAD_IN_IDX, UART2_RXD_PAD_IN_IDX,
+ UART3_RXD_PAD_IN_IDX, UART4_RXD_PAD_IN_IDX,
+ };
+ gpio_ll_input_enable(&GPIO, pin);
+ gpio_ll_set_input_signal_matrix_source(&GPIO, sig[num], pin, false);
+}