summaryrefslogtreecommitdiff
path: root/src/net/hosted/wifi_shim.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/hosted/wifi_shim.c')
-rw-r--r--src/net/hosted/wifi_shim.c200
1 files changed, 200 insertions, 0 deletions
diff --git a/src/net/hosted/wifi_shim.c b/src/net/hosted/wifi_shim.c
new file mode 100644
index 0000000..67bdcc6
--- /dev/null
+++ b/src/net/hosted/wifi_shim.c
@@ -0,0 +1,200 @@
+/*
+ * A narrow C surface over ESP-Hosted's Wi-Fi RPC, so Zig never transcribes an IDF struct.
+ *
+ * `rpc_wifi_init` takes a `wifi_init_config_t`, `rpc_wifi_set_config` takes a `wifi_config_t`, and
+ * scanning hands back `wifi_ap_record_t`. Those are large, versioned structs full of bitfields, and
+ * IDF builds them with macros - WIFI_INIT_CONFIG_DEFAULT() alone sets over twenty fields
+ * (esp_wifi.h:316). Writing Zig `extern struct`s to match would be a transcription that compiles
+ * happily and goes wrong on the next IDF release, exactly the mistake that
+ * `esp_hosted_sdio_get_config` taught this project once already.
+ *
+ * So the structs stay on the C side, built by IDF's own macros, and Zig gets plain scalars and byte
+ * buffers. Everything here is a thin forwarder; the interesting code is all in ESP-Hosted's RPC
+ * layer, which this does not duplicate.
+ */
+
+#include <string.h>
+
+#include "esp_wifi_types.h"
+#include "esp_wifi.h"
+#include "rpc_wrap.h"
+
+/* ESP-Hosted's RPC entry points (host/drivers/rpc/wrap/rpc_wrap.c). Declared here rather than
+ * relying on the header, so a signature change is a compile error in this file. */
+int rpc_wifi_init(const wifi_init_config_t *arg);
+int rpc_wifi_set_mode(wifi_mode_t mode);
+int rpc_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf);
+int rpc_wifi_connect(void);
+int rpc_wifi_scan_start(const wifi_scan_config_t *config, bool block);
+int rpc_wifi_scan_get_ap_num(uint16_t *number);
+int rpc_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records);
+int rpc_wifi_start(void);
+int rpc_wifi_get_mac(wifi_interface_t mode, uint8_t mac[6]);
+int rpc_wifi_set_ps(wifi_ps_type_t type);
+
+/* Initialise the coprocessor's Wi-Fi and put it in station mode, started.
+ *
+ * The order is IDF's own and is not negotiable: init, set_mode, start. `esp_wifi_start` is what
+ * actually brings the radio up on the C6; a config set before it is accepted and a connect before
+ * it is not. */
+int hosted_wifi_sta_start(void)
+{
+ /* IDF's WIFI_INIT_CONFIG_DEFAULT() is deliberately NOT used, and this is not a shortcut.
+ *
+ * That macro's first two fields are `.osi_funcs = &g_wifi_osi_funcs` and
+ * `.wpa_crypto_funcs = g_wifi_default_wpa_crypto_funcs` (esp_wifi.h:317-318) - the local Wi-Fi
+ * driver's OS adapter and crypto tables. This chip has no Wi-Fi driver: the C6 does, and it uses
+ * its own. Referencing them here pulls in symbols that cannot exist in this image, which is
+ * exactly the link error that led to this comment.
+ *
+ * They are also provably unnecessary. rpc_req.c:182-215 packs the request field by field, and
+ * every field it packs is a scalar; neither function pointer is ever serialised. So the struct
+ * only has to carry the scalars, and those come from the same Kconfig-derived macros the real
+ * default uses - via the checked-in sdkconfig, so they are this project's configuration and not
+ * a second set of numbers.
+ *
+ * `magic` is load-bearing: the coprocessor validates it (esp_wifi.h's own note says it must
+ * always be WIFI_INIT_CONFIG_MAGIC), so a zeroed struct is rejected. */
+ wifi_init_config_t cfg = { 0 };
+ cfg.static_rx_buf_num = CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM;
+ cfg.dynamic_rx_buf_num = CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM;
+ cfg.tx_buf_type = CONFIG_ESP_WIFI_TX_BUFFER_TYPE;
+ cfg.static_tx_buf_num = WIFI_STATIC_TX_BUFFER_NUM;
+ cfg.dynamic_tx_buf_num = WIFI_DYNAMIC_TX_BUFFER_NUM;
+ cfg.rx_mgmt_buf_type = CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF;
+ cfg.rx_mgmt_buf_num = WIFI_RX_MGMT_BUF_NUM_DEF;
+ cfg.cache_tx_buf_num = WIFI_CACHE_TX_BUFFER_NUM;
+ cfg.csi_enable = WIFI_CSI_ENABLED;
+ cfg.ampdu_rx_enable = WIFI_AMPDU_RX_ENABLED;
+ cfg.ampdu_tx_enable = WIFI_AMPDU_TX_ENABLED;
+ cfg.amsdu_tx_enable = WIFI_AMSDU_TX_ENABLED;
+ cfg.nvs_enable = WIFI_NVS_ENABLED;
+ cfg.nano_enable = WIFI_NANO_FORMAT_ENABLED;
+ cfg.rx_ba_win = WIFI_DEFAULT_RX_BA_WIN;
+ cfg.wifi_task_core_id = WIFI_TASK_CORE_ID;
+ cfg.beacon_max_len = WIFI_SOFTAP_BEACON_MAX_LEN;
+ cfg.mgmt_sbuf_num = WIFI_MGMT_SBUF_NUM;
+ cfg.feature_caps = WIFI_FEATURE_CAPS;
+ cfg.sta_disconnected_pm = WIFI_STA_DISCONNECTED_PM_ENABLED;
+ cfg.espnow_max_encrypt_num = CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM;
+ cfg.tx_hetb_queue_num = WIFI_TX_HETB_QUEUE_NUM;
+ cfg.dump_hesigb_enable = WIFI_DUMP_HESIGB_ENABLED;
+ cfg.magic = WIFI_INIT_CONFIG_MAGIC;
+
+ int err = rpc_wifi_init(&cfg);
+ if (err) {
+ return err;
+ }
+ err = rpc_wifi_set_mode(WIFI_MODE_STA);
+ if (err) {
+ return err;
+ }
+ err = rpc_wifi_start();
+ if (err) {
+ return err;
+ }
+
+ /* Power save OFF, and this is not a performance tweak - it decides whether the board is
+ * reachable at all.
+ *
+ * ESP-IDF's default is WIFI_PS_MIN_MODEM (esp_wifi_types_generic.h:376): the station sleeps and
+ * only wakes for a beacon every DTIM period. A sleeping station misses frames the AP does not
+ * buffer for it, and broadcast ARP is exactly that. The observed symptom on this board was
+ * precise and misleading: DHCP completed - because the host speaks first and the reply arrives
+ * inside the wake window - the board took a real lease, and then it answered no ARP and no ping,
+ * with the frame counter advancing about once per ten seconds. It looked like a broken receive
+ * path rather than a radio that was asleep.
+ *
+ * A device that exists to answer requests cannot sleep between them. WIFI_PS_NONE. */
+ return rpc_wifi_set_ps(WIFI_PS_NONE);
+}
+
+/* The station's MAC. Needed by the IP stack: ARP and Ethernet framing are built around it, and it
+ * belongs to the C6's radio, not to this chip. */
+int hosted_wifi_get_mac(uint8_t out[6])
+{
+ return rpc_wifi_get_mac(WIFI_IF_STA, out);
+}
+
+/* Scan every channel and report how many networks were seen.
+ *
+ * Blocking: the RPC layer waits for the coprocessor to finish, which takes a couple of seconds
+ * across all channels. A scan needs no credentials, which makes it the cheapest end-to-end proof
+ * that the RPC path and the radio both work. */
+int hosted_wifi_scan(uint16_t *found)
+{
+ wifi_scan_config_t scan = { 0 };
+ scan.show_hidden = true;
+ int err = rpc_wifi_scan_start(&scan, true);
+ if (err) {
+ return err;
+ }
+ return rpc_wifi_scan_get_ap_num(found);
+}
+
+/* One scan result, flattened to scalars.
+ *
+ * `ssid_out` must have room for 33 bytes; the SSID is copied NUL-terminated. Returns the number of
+ * records actually written into the caller's view, which is `min(*count, what the slave has)`.
+ */
+int hosted_wifi_scan_record(uint16_t index, char *ssid_out, int8_t *rssi_out,
+ uint8_t *channel_out, uint8_t *authmode_out)
+{
+ /* One record at a time, into a local, so the caller never sees a wifi_ap_record_t. Asking the
+ * slave for a single record by index is not part of the RPC, so this fetches the run up to
+ * `index` and keeps the last - fine for the small numbers a diagnostic prints, and stated here
+ * rather than hidden because it is O(n^2) if someone loops it over hundreds of networks. */
+ static wifi_ap_record_t records[16];
+ uint16_t want = index + 1;
+ if (want > 16) {
+ return -1;
+ }
+ int err = rpc_wifi_scan_get_ap_records(&want, records);
+ if (err) {
+ return err;
+ }
+ if (index >= want) {
+ return -1;
+ }
+
+ const wifi_ap_record_t *r = &records[index];
+ size_t n = strnlen((const char *)r->ssid, 32);
+ memcpy(ssid_out, r->ssid, n);
+ ssid_out[n] = 0;
+ *rssi_out = r->rssi;
+ *channel_out = r->primary;
+ *authmode_out = (uint8_t)r->authmode;
+ return 0;
+}
+
+/* Join a network.
+ *
+ * `ssid` and `psk` are NUL-terminated. The PSK is copied into the request and never stored here;
+ * it arrives from a build option so it is not in the source, and this function keeps no copy after
+ * the RPC returns.
+ *
+ * `threshold.authmode` is deliberately left at 0 (WIFI_AUTH_OPEN) rather than forced to WPA2: it is
+ * a *minimum* acceptable security level, and pinning it too high refuses networks that would
+ * otherwise work while pinning it low refuses nothing. The AP's actual authmode is what gets used.
+ */
+int hosted_wifi_connect(const char *ssid, const char *psk)
+{
+ wifi_config_t conf = { 0 };
+
+ size_t ssid_len = strnlen(ssid, sizeof(conf.sta.ssid) - 1);
+ memcpy(conf.sta.ssid, ssid, ssid_len);
+
+ size_t psk_len = strnlen(psk, sizeof(conf.sta.password) - 1);
+ memcpy(conf.sta.password, psk, psk_len);
+
+ /* Scan all channels and pick the strongest match rather than the first: this network has both a
+ * 2.4 GHz and a 5 GHz radio on the same SSID family, and the C6 is 2.4 GHz only. */
+ conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
+ conf.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
+
+ int err = rpc_wifi_set_config(WIFI_IF_STA, &conf);
+ if (err) {
+ return err;
+ }
+ return rpc_wifi_connect();
+}