1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
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();
}
|