summaryrefslogtreecommitdiff
path: root/src/net/hosted/abi_assert.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/net/hosted/abi_assert.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/net/hosted/abi_assert.c')
-rw-r--r--src/net/hosted/abi_assert.c78
1 files changed, 78 insertions, 0 deletions
diff --git a/src/net/hosted/abi_assert.c b/src/net/hosted/abi_assert.c
new file mode 100644
index 0000000..8815e89
--- /dev/null
+++ b/src/net/hosted/abi_assert.c
@@ -0,0 +1,78 @@
+/*
+ * The one ABI check that stands between this build and a silent hang.
+ *
+ * `hosted_osi_funcs_t` (host/esp_hosted_os_abstraction.h) is the function-pointer table ESP-Hosted
+ * reaches everything through - memory, sync, threads, GPIO, SDIO. src/net/port.zig defines it in
+ * Zig. Zig can assert its own layout; it cannot assert C's. This file asserts C's, so the two are
+ * checked against each other at build time.
+ *
+ * Why this is not paranoia. Four of the table's entries are guarded:
+ *
+ * #ifdef H_USE_MEMPOOL <- host/esp_hosted_os_abstraction.h:64-69
+ * void *(*_h_get_mempool)(...);
+ * ...
+ * #endif
+ *
+ * `#ifdef`, not `#if`. H_USE_MEMPOOL is defined by
+ * host/port/esp/freertos/include/port_esp_hosted_host_config.h:127-131 - to 1 or to 0, but always
+ * DEFINED. So the four pointers are present in any translation unit that saw that header, and
+ * absent in any that did not, and every entry after them shifts by four pointers.
+ *
+ * That is reachable, not theoretical: host/esp_hosted.h:14 and
+ * host/drivers/transport/transport_util.h:10 both include esp_hosted_os_abstraction.h as their
+ * FIRST include, so a TU reaching the struct through either of those - before any port header -
+ * gets the short layout. Under IDF's CMake the ordering happens to work out. Under our flags it
+ * would be luck.
+ *
+ * Measured with our exact flags, both ways:
+ *
+ * sizeof _h_config_gpio _h_event_post
+ * without the force-include 268 132 264
+ * with the force-include 284 148 280
+ *
+ * Sixteen bytes. A TU with the short layout calling _h_config_gpio jumps through a mempool
+ * pointer instead - which is a jump to the wrong function, on a board with no debugger, and the
+ * symptom would look exactly like the SDIO bus failing to come up.
+ *
+ * build.zig therefore force-includes port_esp_hosted_host_config.h into every ESP-Hosted
+ * translation unit, and compiles this file to assert that it worked. The numbers below are the
+ * long (correct) layout.
+ */
+
+#include "esp_hosted_os_abstraction.h"
+#include <stddef.h>
+
+/* The guard must be visible here, or this file is asserting the wrong layout and proving nothing. */
+#ifndef H_USE_MEMPOOL
+#error "H_USE_MEMPOOL is not visible: the force-include of port_esp_hosted_host_config.h is missing."
+#endif
+
+_Static_assert(
+ sizeof(hosted_osi_funcs_t) == 284,
+ "hosted_osi_funcs_t is not the 284-byte layout. Either the force-include of "
+ "port_esp_hosted_host_config.h was lost (short layout, 268), or ESP-Hosted changed the table. "
+ "Compare against the struct in src/net/port.zig before touching this number.");
+
+/* Two offsets, chosen because they sit on either side of the mempool block: the first entry after
+ * it, and one near the end. If the block appears or disappears, both move. */
+_Static_assert(
+ offsetof(hosted_osi_funcs_t, _h_config_gpio) == 148,
+ "_h_config_gpio moved. It is the first entry after the #ifdef H_USE_MEMPOOL block, so this is "
+ "what the short layout breaks first: 132 instead of 148.");
+
+_Static_assert(
+ offsetof(hosted_osi_funcs_t, _h_event_post) == 280,
+ "_h_event_post moved. Together with the _h_config_gpio assertion this pins both ends of the "
+ "table.");
+
+/* Field count, checked through the size. src/net/port.zig asserts its Zig struct has 71 fields;
+ * every entry is a pointer, so 71 * 4 must be the size on this 32-bit target. A size check alone
+ * would not catch a field deleted in one place and duplicated in another - the length survives and
+ * the two sides silently disagree about which pointer is which. */
+_Static_assert(
+ sizeof(hosted_osi_funcs_t) == 71 * sizeof(void (*)(void)),
+ "hosted_osi_funcs_t is not 71 function pointers. Compare field by field against the struct in "
+ "src/net/port.zig - a count mismatch means one side has an entry the other does not, and every "
+ "entry after it calls the wrong function.");
+
+const int esp_hosted_abi_assertions_hold = 1;