summaryrefslogtreecommitdiff
path: root/src/net/all.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/all.zig')
-rw-r--r--src/net/all.zig156
1 files changed, 156 insertions, 0 deletions
diff --git a/src/net/all.zig b/src/net/all.zig
new file mode 100644
index 0000000..3fc6dba
--- /dev/null
+++ b/src/net/all.zig
@@ -0,0 +1,156 @@
+//! Everything the radio path needs, in one translation unit.
+//!
+//! This file exists for the same reason src/appdesc.zig is a separate object: the files it names
+//! define `export`ed C symbols that ESP-Hosted's C calls, and nothing in the application source
+//! mentions them. An ordinary `@import` would be analysed lazily under ReleaseSmall, the exports
+//! would never be emitted, and the link would fail with a list of missing `_h_*` symbols that looks
+//! like the port table was never written.
+//!
+//! Referencing each import in a `comptime` block forces analysis, which forces the exports.
+//!
+//! The layers, bottom up:
+//!
+//! src/hal/sdmmc.zig the P4's SDMMC peripheral as an SDIO host
+//! src/io/p4.zig std.Io for this chip - the cooperative runtime everything above uses
+//! src/net/libc.zig the libc symbols ESP-Hosted's C reaches for
+//! src/net/port.zig `g_h`, the table ESP-Hosted reaches the machine through
+//! src/net/hosted_glue.zig logging, event bases, and loud stubs for layers not yet run
+//! src/net/ip.zig IPv4/ARP/ICMP/UDP/DHCP/TCP/HTTP, replacing lwIP
+//! src/net/link.zig ESP-Hosted's station channel, bridged to that stack
+//!
+//! ESP-Hosted's transport C sits on top of `port.zig` and is compiled by `hostedC` in build.zig.
+
+const std = @import("std");
+
+pub const libc = @import("libc.zig");
+pub const glue = @import("hosted_glue.zig");
+pub const port = @import("port.zig");
+pub const heap = @import("heap.zig");
+pub const os = @import("hosted_os.zig");
+pub const ip = @import("ip.zig");
+/// The station data path. Separate from `init` on purpose: bringing the transport up and putting an
+/// IP stack on the station interface are two decisions, and an application may want the first
+/// without the second (examples/radio.zig does). Call `link.open()` once `hosted_wifi_sta_start`
+/// has returned.
+pub const link = @import("link.zig");
+/// The std.Io implementation. A module rather than a path: Zig confines a module's imports to its
+/// own root directory, so src/net/ cannot reach ../io/ by file. build.zig wires it as `io`.
+pub const runtime = @import("io");
+
+comptime {
+ _ = libc;
+ _ = glue;
+ _ = port;
+ _ = heap;
+ _ = os;
+ _ = ip;
+ _ = link;
+ _ = runtime;
+}
+
+/// ESP-Hosted's transport entry point, from
+/// host/drivers/transport/transport_drv.h:131. Returns an `esp_err_t`, so 0 is success. The
+/// callback fires once the slave has answered and the transport has reached its "active" state,
+/// which is the moment the radio becomes usable.
+extern fn setup_transport(up_cb: ?*const fn () callconv(.c) void) c_int;
+
+/// Populate the transport configuration from Kconfig - SDIO slot, bus width, clock, the pin map and
+/// the C6 reset pin. From host/api/src/esp_hosted_transport_config.c:25.
+///
+/// This is not optional and skipping it does not fail loudly. `esp_hosted_sdio_get_config` hands
+/// back a pointer to static storage which starts out all zeroes, so a transport started without
+/// this reads slot 0, width 0, 0 kHz, every pin GPIO0 and queue sizes of zero. The first run of
+/// examples/radio.zig did exactly that: the only hint was ESP-Hosted warning "provided sdio tx queue
+/// size is zero! Setting to 20", and then nothing ever came up. ESP-Hosted's own esp_hosted_init
+/// calls this at esp_hosted_api.c:151; this file calls the same function for the same reason.
+extern fn esp_hosted_set_default_config() c_int;
+
+/// True if a configuration has already been set, so `init` can be called twice without clobbering
+/// a configuration an application deliberately overrode.
+extern fn esp_hosted_is_config_valid() bool;
+
+/// Attempt the connection to the coprocessor: release its reset, bring the SDIO card up, and run
+/// the capability handshake. From host/drivers/transport/transport_drv.h:133.
+///
+/// `setup_transport` does NOT do this - it only calls transport_drv_init (bus and threads) and
+/// stores the up-callback (transport_drv.c:170-177). ESP-Hosted's own API splits the two the same
+/// way: esp_hosted_init calls setup_transport, and esp_hosted_connect_to_slave calls this
+/// (esp_hosted_api.c:184). Calling only the first is a transport that exists and never speaks; that
+/// is exactly what the third run of examples/radio.zig showed - threads created, nothing allocated,
+/// no SDIO traffic, and silence for ten seconds.
+extern fn transport_drv_reconfigure() c_int;
+
+/// Bring the RPC layer up and register its event callbacks. From
+/// host/drivers/rpc/wrap/rpc_wrap.h:45,50.
+///
+/// Separate from the transport on purpose: the transport is the pipe, RPC is the language spoken
+/// over it. esp_hosted_init calls setup_transport and then these two (esp_hosted_api.c:154-156).
+/// Skipping them leaves a transport that is genuinely up and a control path that answers every
+/// request with "RPC not initialized or transport down, failing fast" - which is what the first
+/// Wi-Fi call on this board printed.
+///
+/// These must run BEFORE `transport_drv_reconfigure`, and the reason is a single line in
+/// ESP-Hosted: `rpc_core_init` ends with `set_rpc_lib_state(RPC_LIB_STATE_INIT)`
+/// (rpc_core.c:1164), and the *only* thing that ever raises that state to READY is `rpc_start`,
+/// called from `transport_delayed_init` (transport_drv.c:802) on the transport's own RX thread the
+/// moment the slave's INIT event is parsed. Call `rpc_init` after the transport is up and
+/// `rpc_core_init` stamps INIT over the READY that already happened, with no second writer: both
+/// `rpc_rx_thread` and `rpc_tx_thread` then sit in `if (!is_rpc_lib_ready()) _h_sleep(1)`
+/// (rpc_core.c:482-485, :543-547) forever. `rpc_send_req` still succeeds - it only enqueues
+/// (rpc_core.c:1019) - so every synchronous request is accepted, never transmitted, and returns
+/// "Timeout waiting for Resp" ten seconds later. That is exactly the Req_WifiInit failure.
+extern fn rpc_init() c_int;
+extern fn rpc_register_event_callbacks() c_int;
+
+/// Set once the C reports the transport up. Read through `isUp`.
+var transport_up: bool = false;
+
+fn onTransportUp() callconv(.c) void {
+ transport_up = true;
+}
+
+/// True once the C6 has answered and ESP-Hosted's transport has reached its active state.
+pub fn isUp() bool {
+ return transport_up;
+}
+
+pub const Error = error{ ConfigFailed, TransportSetupFailed, SlaveConnectFailed, RpcInitFailed };
+
+/// Bring the radio path up, in the one order that works.
+///
+/// Each step depends on the one before it:
+/// 1. the libc allocator must exist before ESP-Hosted allocates anything, and its very first act
+/// is to allocate,
+/// 2. the port table must be installed before the transport starts, because the transport reaches
+/// the SDIO bus, the clock and its own threads through that table,
+/// 3. the transport must be *set up* - bus, queues, threads - before RPC, because `rpc_core_init`
+/// opens a serial endpoint on it,
+/// 4. RPC must be initialised before the coprocessor is spoken to, because the handshake's own
+/// `rpc_start` is what takes the RPC lib from INIT to READY and `rpc_core_init` would
+/// overwrite it. See `rpc_init` above,
+/// 5. only then is the C6 reset released and the capability handshake run, through our driver.
+///
+/// Blocks for the handshake: step 5 is `transport_drv_reconfigure`, which polls the slave every
+/// 200 ms (transport_drv.c:219-234). It runs on whatever task calls this, so that task must not be
+/// the one printing progress.
+pub fn init(io_impl: std.Io, gpa: std.mem.Allocator) Error!void {
+ libc.install(gpa);
+ port.install(io_impl, gpa);
+ // The configuration must exist before the transport reads it. Only set defaults if an
+ // application has not already provided its own, which is the order esp_hosted_init uses.
+ if (!esp_hosted_is_config_valid()) {
+ if (esp_hosted_set_default_config() != 0) return error.ConfigFailed;
+ }
+ if (setup_transport(&onTransportUp) != 0) return error.TransportSetupFailed;
+
+ // The control path, before the pipe is opened. This is ESP-Hosted's own order
+ // (esp_hosted_api.c:154-156 init, then esp_hosted_api.c:184 connect) and it is load-bearing,
+ // not cosmetic: see the comment on `rpc_init` above. With RPC initialised first, `rpc_start`
+ // from the handshake is the last writer of the RPC lib state, and the reader and writer threads
+ // leave their not-ready loop for good.
+ if (rpc_init() != 0) return error.RpcInitFailed;
+ if (rpc_register_event_callbacks() != 0) return error.RpcInitFailed;
+
+ // And now actually talk to the coprocessor. Blocks until the slave answers.
+ if (transport_drv_reconfigure() != 0) return error.SlaveConnectFailed;
+}