|
25 | 25 | /* ADR-060: Access the global NVS config for MAC filter and channel override. */ |
26 | 26 | extern nvs_config_t g_nvs_config; |
27 | 27 |
|
| 28 | +/* Defensive fix (#232, #375, #385, #386, #390): capture node_id at init-time |
| 29 | + * into a module-local static. Using the global g_nvs_config.node_id directly |
| 30 | + * at every callback is vulnerable to any memory corruption that clobbers the |
| 31 | + * struct (which users have reported reverting node_id to the Kconfig default |
| 32 | + * of 1). The local copy is set once at csi_collector_init() and then used |
| 33 | + * exclusively by csi_serialize_frame(). */ |
| 34 | +static uint8_t s_node_id = 1; |
| 35 | + |
28 | 36 | /* ADR-057: Build-time guard — fail early if CSI is not enabled in sdkconfig. |
29 | 37 | * Without this, the firmware compiles but crashes at runtime with: |
30 | 38 | * "E (xxxx) wifi:CSI not enabled in menuconfig!" |
@@ -117,8 +125,9 @@ size_t csi_serialize_frame(const wifi_csi_info_t *info, uint8_t *buf, size_t buf |
117 | 125 | uint32_t magic = CSI_MAGIC; |
118 | 126 | memcpy(&buf[0], &magic, 4); |
119 | 127 |
|
120 | | - /* Node ID (from NVS runtime config, not compile-time Kconfig) */ |
121 | | - buf[4] = g_nvs_config.node_id; |
| 128 | + /* Node ID (captured at init into s_node_id to survive memory corruption |
| 129 | + * that could clobber g_nvs_config.node_id - see #232/#375/#385/#390). */ |
| 130 | + buf[4] = s_node_id; |
122 | 131 |
|
123 | 132 | /* Number of antennas */ |
124 | 133 | buf[5] = n_antennas; |
@@ -215,6 +224,13 @@ static void wifi_promiscuous_cb(void *buf, wifi_promiscuous_pkt_type_t type) |
215 | 224 |
|
216 | 225 | void csi_collector_init(void) |
217 | 226 | { |
| 227 | + /* Capture node_id into module-local static at init time. After this point |
| 228 | + * csi_serialize_frame() uses s_node_id exclusively, isolating the UDP |
| 229 | + * frame node_id field from any memory corruption of g_nvs_config. */ |
| 230 | + s_node_id = g_nvs_config.node_id; |
| 231 | + ESP_LOGI(TAG, "Captured node_id=%u at init (defensive copy for #232/#375/#385/#390)", |
| 232 | + (unsigned)s_node_id); |
| 233 | + |
218 | 234 | /* ADR-060: Determine the CSI channel. |
219 | 235 | * Priority: 1) NVS override (--channel), 2) connected AP channel, 3) Kconfig default. */ |
220 | 236 | uint8_t csi_channel = (uint8_t)CONFIG_CSI_WIFI_CHANNEL; |
@@ -272,8 +288,24 @@ void csi_collector_init(void) |
272 | 288 | g_nvs_config.filter_mac[4], g_nvs_config.filter_mac[5]); |
273 | 289 | } |
274 | 290 |
|
275 | | - ESP_LOGI(TAG, "CSI collection initialized (node_id=%d, channel=%u)", |
276 | | - g_nvs_config.node_id, (unsigned)csi_channel); |
| 291 | + ESP_LOGI(TAG, "CSI collection initialized (node_id=%u, channel=%u)", |
| 292 | + (unsigned)s_node_id, (unsigned)csi_channel); |
| 293 | + |
| 294 | + /* Clobber-detection canary: if g_nvs_config.node_id no longer matches the |
| 295 | + * value we captured, something corrupted the struct between nvs_config_load |
| 296 | + * and here. This is the historic #232/#375 symptom. */ |
| 297 | + if (g_nvs_config.node_id != s_node_id) { |
| 298 | + ESP_LOGW(TAG, "node_id clobber detected: captured=%u but g_nvs_config=%u " |
| 299 | + "(frames will use captured value %u). Please report to #390.", |
| 300 | + (unsigned)s_node_id, (unsigned)g_nvs_config.node_id, |
| 301 | + (unsigned)s_node_id); |
| 302 | + } |
| 303 | +} |
| 304 | + |
| 305 | +/* Accessor for other modules that need the authoritative runtime node_id. */ |
| 306 | +uint8_t csi_collector_get_node_id(void) |
| 307 | +{ |
| 308 | + return s_node_id; |
277 | 309 | } |
278 | 310 |
|
279 | 311 | /* ---- ADR-029: Channel hopping ---- */ |
|
0 commit comments