Files
desklock/firmware/components/esp_hosted/slave/main/slave_control.c
T
jpmschweitzerandClaude Fable 5 cb5826e02b
Test, Build and Push / test-gateway (push) Successful in 12s
Test, Build and Push / release (push) Skipped
Test, Build and Push / build-gateway (push) Skipped
Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
Root cause (verified against our exact IDF tree, not the community guess):
the "258" in "sdio_write_task: Failed to send data: 258" is NOT a timeout
(that is 263). 258 = 0x102 = ESP_ERR_INVALID_ARG. On the ESP32-P4, block-
mode CMD53 writes require the SOURCE buffer to be 64-byte (cache-line)
aligned; the IDF sdmmc driver rejects a misaligned source with INVALID_ARG
BEFORE any bus activity. esp_hosts write loop then declares "Unrecoverable
host sdio state" and reboots the whole P4. The audio TX payload is not
64-aligned, so streaming mic audio wedged on the very FIRST frame (which is
exactly what we saw: listening -> instant Failed to send -> reboot).

This also explains why buffer/queue/clock/retry tuning all did nothing: the
write never reached the bus. And why our symptom was instant, not after
~100 writes (the community block-mode-desync theory) — it is the first
misaligned buffer, every time.

Fix: vendored esp_hosted 2.12.11 as an editable local component (overrides
the registry copy) and bounce a misaligned TX payload through one aligned
DMA scratch buffer in hosted_sdio_write_block (port_esp_hosted_host_sdio.c).
TX is serialized by the bus lock so a single static bounce buffer is safe;
freed in hosted_sdio_deinit. Host-only change — no C6 reflash.

VERIFIED ON HARDWARE (autonomous self-test): 40s of continuous mic-audio
upstream streaming — the traffic that previously wedged on the first frame
— ran clean, zero timeouts, zero reboots. A guarded SDIO_TX_SELFTEST harness
is kept (compiled out) for future SDIO stress testing.

Credit: root cause + patch designed via multi-agent investigation; the
precise 258=INVALID_ARG decode (correcting the upstream community timeout
assumption) came from checking our actual esp_err.h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:50:27 +02:00

1908 lines
54 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_app_desc.h"
#ifdef CONFIG_ESP_HOSTED_CP_WIFI
#include "slave_wifi_std.h"
#endif
#if H_WIFI_ENTERPRISE_SUPPORT
#include "slave_wifi_enterprise.h"
#endif
#ifdef CONFIG_ESP_HOSTED_NETWORK_SPLIT_ENABLED
#include "slave_network_split.h"
#endif
#include "esp_image_format.h"
#include "esp_partition.h"
#include "slave_control.h"
#include "esp_hosted_rpc.pb-c.h"
#include "esp_ota_ops.h"
#include "esp_mac.h"
#include "esp_hosted_rpc.h"
#include "esp_hosted_transport.h"
#include "esp_hosted_bitmasks.h"
#include "slave_config.h"
#include "esp_hosted_log.h"
#include "slave_bt.h"
#include "esp_hosted_coprocessor_fw_ver.h"
#include "slave_gpio_expander.h"
#include "slave_ext_coex.h"
#if CONFIG_ESP_HOSTED_OT_RCP_ENABLED
#include "slave_openthread.h"
#endif
#define IFACE_MAC_SIZE 8 // 6 for MAC-48, 8 for EIU-64, 2 for EFUSE_EXT
#define TIMEOUT_IN_MIN (60*TIMEOUT_IN_SEC)
#define TIMEOUT_IN_HOUR (60*TIMEOUT_IN_MIN)
#define RESTART_TIMEOUT (2*TIMEOUT_IN_SEC)
#define MIN_HEARTBEAT_INTERVAL (1)
#define MAX_HEARTBEAT_INTERVAL (24*60*60)
enum {
OTA_NOT_STARTED,
OTA_IN_PROGRESS,
OTA_FAILED,
OTA_COMPLETED,
OTA_ACTIVATED,
};
#ifdef CONFIG_ESP_HOSTED_MEM_MONITOR
// structures for mem monitor event
typedef struct {
uint32_t internal_mem_dma;
uint32_t internal_mem_8bit;
uint32_t external_mem_dma;
uint32_t external_mem_8bit;
} mem_monitor_params_t;
typedef struct {
uint32_t total_free_heap_size;
uint32_t min_free_heap_size;
mem_monitor_params_t free_size;
mem_monitor_params_t largest_free_block;
} mem_monitor_event_t;
// static variables for mem monitor
static TimerHandle_t mem_monitor_timer_handle = NULL;
static mem_monitor_params_t mem_monitor_params = { 0 };
static bool mem_monitor_report_always = false;
static uint32_t mem_monitor_interval_sec = 0;
#endif
uint8_t ota_status = OTA_NOT_STARTED;
typedef struct esp_rpc_cmd {
int req_num;
esp_err_t (*command_handler)(Rpc *req,
Rpc *resp, void *priv_data);
} esp_rpc_req_t;
static const char* TAG = "slave_rpc";
static TimerHandle_t handle_heartbeat_task;
static uint32_t hb_num;
static esp_ota_handle_t handle;
const esp_partition_t* update_partition = NULL;
static bool first_ota_write = false;
#if H_OTA_CHECK_IMAGE_VALIDITY
#define OTA_IMAGE_HEADER_SIZE (sizeof(esp_image_header_t) + \
sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t))
static const esp_app_desc_t *esp_hosted_get_app_desc_from_ota_img(const void *data_buf)
{
return (const esp_app_desc_t *)((const uint8_t *)data_buf +
sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t));
}
#endif
extern esp_err_t wlan_sta_rx_callback(void *buffer, uint16_t len, void *eb);
extern esp_err_t wlan_ap_rx_callback(void *buffer, uint16_t len, void *eb);
extern volatile uint8_t station_connected;
extern volatile uint8_t softap_started;
#ifdef CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER
/* Array of callback slots (empty slot has callback = NULL, msg_id = -1 is invalid sentinel) */
static struct {
uint32_t msg_id;
void (*callback)(uint32_t msg_id_recvd, const uint8_t *data_recvd, size_t data_len_recvd, void *local_context);
void *local_context;
} custom_msg_callbacks[CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS] = {
[0 ... (CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS - 1)] = {
.msg_id = (uint32_t)-1,
.callback = NULL
}
};
static SemaphoreHandle_t custom_callbacks_mutex = NULL;
#endif
/* OTA end timer callback */
void vTimerCallback( TimerHandle_t xTimer )
{
xTimerDelete(xTimer, 0);
#ifdef CONFIG_ESP_HOSTED_CP_WIFI
esp_unregister_shutdown_handler((shutdown_handler_t)esp_wifi_stop);
#endif // CONFIG_ESP_HOSTED_CP_WIFI
esp_restart();
}
/* Function OTA begin */
static esp_err_t req_ota_begin_handler (Rpc *req,
Rpc *resp, void *priv_data)
{
esp_err_t ret = ESP_OK;
RpcRespOTABegin *resp_payload = NULL;
if (!req || !resp) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_FAIL;
}
ESP_LOGI(TAG, "OTA update started");
resp_payload = (RpcRespOTABegin *)
calloc(1,sizeof(RpcRespOTABegin));
if (!resp_payload) {
ESP_LOGE(TAG,"Failed to allocate memory");
return ESP_ERR_NO_MEM;
}
rpc__resp__otabegin__init(resp_payload);
resp->payload_case = RPC__PAYLOAD_RESP_OTA_BEGIN;
resp->resp_ota_begin = resp_payload;
/* Identify next OTA partition */
update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL) {
ESP_LOGE(TAG, "Failed to get next update partition");
ret = -1;
goto err;
}
ESP_LOGI(TAG, "Prepare partition for OTA\n");
ret = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &handle);
if (ret) {
ESP_LOGE(TAG, "OTA begin failed[%d]", ret);
goto err;
}
ota_status = OTA_IN_PROGRESS;
first_ota_write = true;
resp_payload->resp = SUCCESS;
return ESP_OK;
err:
resp_payload->resp = ret;
return ESP_OK;
}
/* Function OTA write */
static esp_err_t req_ota_write_handler (Rpc *req,
Rpc *resp, void *priv_data)
{
esp_err_t ret = ESP_OK;
RpcRespOTAWrite *resp_payload = NULL;
if (!req || !resp) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_FAIL;
}
resp_payload = (RpcRespOTAWrite *)calloc(1,sizeof(RpcRespOTAWrite));
if (!resp_payload) {
ESP_LOGE(TAG,"Failed to allocate memory");
return ESP_ERR_NO_MEM;
}
rpc__resp__otawrite__init(resp_payload);
resp->payload_case = RPC__PAYLOAD_RESP_OTA_WRITE;
resp->resp_ota_write = resp_payload;
// Check image validity before writing if it's the first chunk
if (first_ota_write) {
ESP_LOGI(TAG, "Flashing image\n");
first_ota_write = false;
#if H_OTA_CHECK_IMAGE_VALIDITY
// sanity check: first write should contain enough data to query app header
if (req->req_ota_write->ota_data.len < OTA_IMAGE_HEADER_SIZE) {
ESP_LOGE(TAG, "First OTA write is too small to contain app header");
resp_payload->resp = ESP_ERR_INVALID_SIZE;
return ESP_OK;
}
// do additional OTA image checking
// - SPI FLASH mode of incoming OTA is compatible with current image
const esp_image_header_t *img_hdr = (const esp_image_header_t *)req->req_ota_write->ota_data.data;
const esp_app_desc_t *app_desc = esp_hosted_get_app_desc_from_ota_img(req->req_ota_write->ota_data.data);
esp_err_t validity_ret = esp_ota_check_image_validity(update_partition->type, img_hdr, app_desc);
if (validity_ret != ESP_OK) {
ESP_LOGE(TAG, "OTA image validity check failed with error: %s", esp_err_to_name(validity_ret));
resp_payload->resp = validity_ret;
return ESP_OK;
}
#else
ESP_LOGW(TAG, "esp_ota_check_image_validity() not available in this IDF version, skipping validation");
resp_payload->resp = ESP_OK;
#endif
}
ret = esp_ota_write( handle, (const void *)req->req_ota_write->ota_data.data,
req->req_ota_write->ota_data.len);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "OTA write failed with return code 0x%x",ret);
resp_payload->resp = ret;
return ESP_OK;
}
resp_payload->resp = SUCCESS;
return ESP_OK;
}
/* Function OTA end */
static esp_err_t req_ota_end_handler (Rpc *req,
Rpc *resp, void *priv_data)
{
esp_err_t ret = ESP_OK;
RpcRespOTAEnd *resp_payload = NULL;
if (!req || !resp) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_FAIL;
}
resp_payload = (RpcRespOTAEnd *)calloc(1,sizeof(RpcRespOTAEnd));
if (!resp_payload) {
ESP_LOGE(TAG,"Failed to allocate memory");
return ESP_ERR_NO_MEM;
}
rpc__resp__otaend__init(resp_payload);
resp->payload_case = RPC__PAYLOAD_RESP_OTA_END;
resp->resp_ota_end = resp_payload;
ret = esp_ota_end(handle);
if (ret != ESP_OK) {
if (ret == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
} else {
ESP_LOGE(TAG, "OTA update failed in end (%s)!", esp_err_to_name(ret));
}
ota_status = OTA_FAILED;
goto err;
}
ESP_LOGI(TAG, "**** OTA updated successful, ready for activation ****");
ota_status = OTA_COMPLETED;
resp_payload->resp = SUCCESS;
return ESP_OK;
err:
resp_payload->resp = ret;
return ESP_OK;
}
/* Function OTA activate */
static esp_err_t req_ota_activate_handler (Rpc *req,
Rpc *resp, void *priv_data)
{
esp_err_t ret = ESP_OK;
RpcRespOTAActivate *resp_payload = NULL;
TimerHandle_t xTimer = NULL;
if (!req || !resp) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_FAIL;
}
resp_payload = (RpcRespOTAActivate *)calloc(1,sizeof(RpcRespOTAActivate));
if (!resp_payload) {
ESP_LOGE(TAG,"Failed to allocate memory");
return ESP_ERR_NO_MEM;
}
rpc__resp__otaactivate__init(resp_payload);
resp->payload_case = RPC__PAYLOAD_RESP_OTA_ACTIVATE;
resp->resp_ota_activate = resp_payload;
ret = ESP_OK;
switch (ota_status) {
case OTA_COMPLETED:
break;
case OTA_IN_PROGRESS:
ESP_LOGW(TAG, "OTA in progress");
goto err;
break;
case OTA_NOT_STARTED:
ESP_LOGW(TAG, "OTA not started");
goto err;
break;
case OTA_FAILED:
ESP_LOGW(TAG, "OTA failed");
goto err;
break;
default:
ESP_LOGW(TAG, "OTA status unknown");
goto err;
break;
}
/* set OTA partition for next boot */
ret = esp_ota_set_boot_partition(update_partition);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(ret));
goto err;
}
ota_status = OTA_ACTIVATED;
/* Create timer to reboot system and activate OTA */
xTimer = xTimerCreate("OTAActivateTimer", RESTART_TIMEOUT , pdFALSE, 0, vTimerCallback);
if (xTimer == NULL) {
ESP_LOGE(TAG, "Failed to create timer to restart system");
ret = -1;
goto err;
}
ret = xTimerStart(xTimer, 0);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to start timer to restart system");
ret = -2;
goto err;
}
ESP_LOGE(TAG, "**** OTA activation initiated, ESP32 will reboot in 2 sec ****");
resp_payload->resp = SUCCESS;
return ESP_OK;
err:
resp_payload->resp = ret;
return ESP_OK;
}
static void heartbeat_timer_cb(TimerHandle_t xTimer)
{
send_event_to_host(RPC_ID__Event_Heartbeat);
hb_num++;
}
static void stop_heartbeat(void)
{
if (handle_heartbeat_task &&
xTimerIsTimerActive(handle_heartbeat_task)) {
ESP_LOGI(TAG, "Stopping HB timer");
xTimerStop(handle_heartbeat_task, portMAX_DELAY);
xTimerDelete(handle_heartbeat_task, portMAX_DELAY);
handle_heartbeat_task = NULL;
}
hb_num = 0;
}
static esp_err_t start_heartbeat(int duration)
{
esp_err_t ret = ESP_OK;
handle_heartbeat_task = xTimerCreate("HB_Timer",
duration*TIMEOUT_IN_SEC, pdTRUE, 0, heartbeat_timer_cb);
if (handle_heartbeat_task == NULL) {
ESP_LOGE(TAG, "Failed to Heartbeat");
return ESP_FAIL;
}
ret = xTimerStart(handle_heartbeat_task, 0);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to start Heartbeat");
return ESP_FAIL;
}
ESP_LOGI(TAG, "HB timer started for %u sec\n", duration);
return ESP_OK;
}
static esp_err_t configure_heartbeat(bool enable, int hb_duration)
{
esp_err_t ret = ESP_OK;
int duration = hb_duration ;
if (!enable) {
ESP_LOGI(TAG, "Stop Heartbeat");
stop_heartbeat();
} else {
if ((duration < MIN_HEARTBEAT_INTERVAL) ||
(duration > MAX_HEARTBEAT_INTERVAL)) {
return ESP_ERR_INVALID_ARG;
}
stop_heartbeat();
ret = start_heartbeat(duration);
}
return ret;
}
/* Function to config heartbeat */
static esp_err_t req_config_heartbeat(Rpc *req,
Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespConfigHeartbeat,
resp_config_heartbeat,
RpcReqConfigHeartbeat,
req_config_heartbeat,
rpc__resp__config_heartbeat__init);
RPC_RET_FAIL_IF(configure_heartbeat(req_payload->enable, req_payload->duration));
return ESP_OK;
}
static esp_err_t req_get_coprocessor_fw_version(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE_SIMPLE(RpcRespGetCoprocessorFwVersion, resp_get_coprocessor_fwversion,
RpcReqGetCoprocessorFwVersion, req_get_coprocessor_fwversion,
rpc__resp__get_coprocessor_fw_version__init);
resp_payload->major1 = PROJECT_VERSION_MAJOR_1;
resp_payload->minor1 = PROJECT_VERSION_MINOR_1;
resp_payload->patch1 = PROJECT_VERSION_PATCH_1;
resp_payload->revision = -1;
resp_payload->prerelease = -1;
resp_payload->build = -1;
resp_payload->chip_id = CONFIG_IDF_FIRMWARE_CHIP_ID;
RPC_RESP_COPY_STR(resp_payload->idf_target, CONFIG_IDF_TARGET, strlen(CONFIG_IDF_TARGET));
resp_payload->resp = ESP_OK;
return ESP_OK;
}
static esp_err_t req_iface_mac_addr_len_get(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespIfaceMacAddrLenGet, resp_iface_mac_addr_len_get,
RpcReqIfaceMacAddrLenGet, req_iface_mac_addr_len_get,
rpc__resp__iface_mac_addr_len_get__init);
size_t len = esp_mac_addr_len_get(req_payload->type);
resp_payload->type = req_payload->type;
resp_payload->len = len;
return ESP_OK;
}
static esp_err_t req_iface_mac_addr_set_get(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespIfaceMacAddrSetGet, resp_iface_mac_addr_set_get,
RpcReqIfaceMacAddrSetGet, req_iface_mac_addr_set_get,
rpc__resp__iface_mac_addr_set_get__init);
// copy the incoming request to the outgoing response
resp_payload->set = req_payload->set;
resp_payload->type = req_payload->type;
// get the expected len based on the type
size_t len = esp_mac_addr_len_get(req_payload->type);
if (req_payload->set) {
// set the interface mac address
if (req_payload->mac.len) {
if (req_payload->mac.len == len) {
RPC_RET_FAIL_IF(esp_iface_mac_addr_set(req_payload->mac.data, req_payload->type));
// copy the mac address that was set in the response
RPC_RESP_COPY_BYTES_SRC_UNCHECKED(resp_payload->mac, req_payload->mac.data, len);
} else {
ESP_LOGE(TAG, "expected mac length %" PRIu32 ", but got %" PRIu32, (uint32_t)len, (uint32_t)req_payload->mac.len);
resp_payload->resp = ESP_ERR_INVALID_ARG;
}
} else {
// no mac data provided
ESP_LOGE(TAG, "error: set iface mac address without mac data");
resp_payload->resp = ESP_ERR_INVALID_ARG;
}
} else {
// get the interface mac address
uint8_t iface_mac[IFACE_MAC_SIZE] = {0};
RPC_RET_FAIL_IF(esp_read_mac(iface_mac, req_payload->type));
RPC_RESP_COPY_BYTES_SRC_UNCHECKED(resp_payload->mac, iface_mac, len);
}
return ESP_OK;
}
static esp_err_t req_feature_control(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespFeatureControl, resp_feature_control,
RpcReqFeatureControl, req_feature_control,
rpc__resp__feature_control__init);
/* Echo request into response */
resp_payload->feature = req_payload->feature;
resp_payload->command = req_payload->command;
resp_payload->option = req_payload->option;
switch (req_payload->feature) {
#ifdef CONFIG_ESP_HOSTED_CP_BT
case RPC_FEATURE__Feature_Bluetooth:
switch (req_payload->command) {
case RPC_FEATURE_COMMAND__Feature_Command_BT_Init:
RPC_RET_FAIL_IF(init_bluetooth());
break;
case RPC_FEATURE_COMMAND__Feature_Command_BT_Deinit: {
bool mem_release =
(req_payload->option ==
RPC_FEATURE_OPTION__Feature_Option_BT_Deinit_Release_Memory);
RPC_RET_FAIL_IF(deinit_bluetooth(mem_release));
break;
}
case RPC_FEATURE_COMMAND__Feature_Command_BT_Enable:
RPC_RET_FAIL_IF(enable_bluetooth());
break;
case RPC_FEATURE_COMMAND__Feature_Command_BT_Disable:
RPC_RET_FAIL_IF(disable_bluetooth());
break;
default:
ESP_LOGE(TAG, "error: invalid Bluetooth Feature Control");
resp_payload->resp = ESP_ERR_INVALID_ARG;
break;
}
break;
#endif /* CONFIG_ESP_HOSTED_CP_BT */
#if CONFIG_ESP_HOSTED_OT_RCP_ENABLED
case RPC_FEATURE__Feature_Openthread_Rcp:
switch(req_payload->command) {
case RPC_FEATURE_COMMAND__Feature_Command_Init:
RPC_RET_FAIL_IF(slave_openthread_init());
break;
case RPC_FEATURE_COMMAND__Feature_Command_Deinit:
RPC_RET_FAIL_IF(slave_openthread_deinit());
break;
case RPC_FEATURE_COMMAND__Feature_Command_Enable:
RPC_RET_FAIL_IF(slave_openthread_start());
break;
case RPC_FEATURE_COMMAND__Feature_Command_Disable:
RPC_RET_FAIL_IF(slave_openthread_stop());
break;
case RPC_FEATURE_COMMAND__Feature_Command_Query:
{
slave_openthread_state_t state = slave_openthread_get_state();
switch (req_payload->option) {
case RPC_FEATURE_OPTION__Feature_Option_Query_Configured:
resp_payload->resp = ESP_OK;
break;
case RPC_FEATURE_OPTION__Feature_Option_Query_Inited:
resp_payload->resp = slave_openthread_state_check(state, SLAVE_OT_STATE_INITED);
break;
case RPC_FEATURE_OPTION__Feature_Option_Query_Enabled:
resp_payload->resp = slave_openthread_state_check(state, SLAVE_OT_STATE_ENABLED);
break;
case RPC_FEATURE_OPTION__Feature_Option_Query_Ready:
resp_payload->resp = slave_openthread_state_check(state, SLAVE_OT_STATE_READY);
break;
default:
ESP_LOGE(TAG, "error: invalid Feature Query Option");
break;
}
}
break;
default:
ESP_LOGE(TAG, "error: invalid OpenThread Feature Control");
resp_payload->resp = ESP_ERR_INVALID_ARG;
break;
}
break;
#endif
default:
/* Covers:
* - BT feature when BT is disabled
* - Any unsupported / unknown feature
*/
ESP_LOGE(TAG, "error: invalid Feature Control");
resp_payload->resp = ESP_ERR_INVALID_ARG;
break;
}
return ESP_OK;
}
static esp_err_t req_app_get_desc(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE_SIMPLE(RpcRespAppGetDesc, resp_app_get_desc,
RpcReqAppGetDesc, req_app_get_desc,
rpc__resp__app_get_desc__init);
RPC_ALLOC_ELEMENT(EspAppDesc, resp_payload->app_desc, esp_app_desc__init);
EspAppDesc * p_c = resp_payload->app_desc;
const esp_app_desc_t *app_desc = esp_app_get_description();
if (app_desc) {
// copy basic info: project name, version, IDF version
RPC_RESP_COPY_STR(p_c->project_name, app_desc->project_name, sizeof(app_desc->project_name));
RPC_RESP_COPY_STR(p_c->version, app_desc->version, sizeof(app_desc->version));
RPC_RESP_COPY_STR(p_c->idf_ver, app_desc->idf_ver, sizeof(app_desc->idf_ver));
#if H_ALLOW_FULL_APP_DESC
// copy full info
p_c->magic_word = app_desc->magic_word;
p_c->secure_version = app_desc->secure_version;
RPC_RESP_COPY_STR(p_c->time, app_desc->time, sizeof(app_desc->time));
RPC_RESP_COPY_STR(p_c->date, app_desc->date, sizeof(app_desc->date));
RPC_RESP_COPY_BYTES(p_c->app_elf_sha256, app_desc->app_elf_sha256, sizeof(app_desc->app_elf_sha256));
#if H_GOT_EFUSE_BLK_REV_FULL_APP_DESC
p_c->min_efuse_blk_rev_full = app_desc->min_efuse_blk_rev_full;
p_c->max_efuse_blk_rev_full = app_desc->max_efuse_blk_rev_full;
#endif
#if H_GOT_MMU_PAGE_SIZE_FULL_APP_DESC
p_c->mmu_page_size = app_desc->mmu_page_size;
#endif
#endif
} else {
resp_payload->resp = ESP_FAIL;
}
err:
return ESP_OK;
}
#ifdef CONFIG_ESP_HOSTED_MEM_MONITOR
void mem_monitor_timer_cb(TimerHandle_t xTimer)
{
bool threshold_exceeded = false;
mem_monitor_params_t current_mem_params = { 0 };
// get current params
current_mem_params.internal_mem_dma = heap_caps_get_free_size(MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
current_mem_params.internal_mem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
current_mem_params.external_mem_dma = heap_caps_get_free_size(MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM);
current_mem_params.external_mem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
// are current params lower than threshold
#if CONFIG_SPIRAM
if ((current_mem_params.internal_mem_dma < mem_monitor_params.internal_mem_dma) ||
(current_mem_params.internal_mem_8bit < mem_monitor_params.internal_mem_8bit) ||
(current_mem_params.external_mem_dma < mem_monitor_params.external_mem_dma) ||
(current_mem_params.external_mem_8bit < mem_monitor_params.external_mem_8bit)) {
threshold_exceeded = true;
}
#else
// external memory not enabled: only compare internal memory
if ((current_mem_params.internal_mem_dma < mem_monitor_params.internal_mem_dma) ||
(current_mem_params.internal_mem_8bit < mem_monitor_params.internal_mem_8bit)) {
threshold_exceeded = true;
}
#endif
// send an event if the current threshold was exceeded or report_always is true
if (threshold_exceeded || mem_monitor_report_always) {
mem_monitor_event_t mem_monitor_event = { 0 };
mem_monitor_event.total_free_heap_size = esp_get_free_heap_size();
mem_monitor_event.min_free_heap_size = esp_get_minimum_free_heap_size();
// copy the curr heap free sizes
memcpy(&mem_monitor_event.free_size, &current_mem_params, sizeof(current_mem_params));
// get the largest free block size
mem_monitor_event.largest_free_block.internal_mem_dma =
heap_caps_get_largest_free_block(MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
mem_monitor_event.largest_free_block.internal_mem_8bit =
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
mem_monitor_event.largest_free_block.external_mem_dma =
heap_caps_get_largest_free_block(MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM);
mem_monitor_event.largest_free_block.external_mem_8bit =
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
send_event_data_to_host(RPC_ID__Event_MemMonitor, &mem_monitor_event, sizeof(mem_monitor_event));
}
}
static esp_err_t mem_monitor_check_params(RpcReqMemMonitor *req_payload)
{
// check for missing allocated params in request
if (!req_payload->internal || !req_payload->external) {
ESP_LOGW(TAG, "%s: missing internal / external params in request", __func__);
return ESP_ERR_INVALID_ARG;
}
// checks when enabling mem monitor
if (req_payload->config == RPC__MEM_MONITOR_CONFIG__MEMMONITOR_ENABLE) {
// interval cannot be zero
if (!req_payload->interval_sec) {
return ESP_ERR_INVALID_ARG;
}
// thresholds should be valid if report_always is not set
if (!req_payload->report_always) {
if (!req_payload->internal->threshold_mem_dma &&
!req_payload->internal->threshold_mem_8bit &&
!req_payload->external->threshold_mem_dma &&
!req_payload->external->threshold_mem_8bit) {
return ESP_ERR_INVALID_ARG;
}
}
}
return ESP_OK;
}
static esp_err_t mem_monitor_setup(RpcReqMemMonitor *req_payload)
{
if ((req_payload->config == RPC__MEM_MONITOR_CONFIG__MEMMONITOR_ENABLE) ||
(req_payload->config == RPC__MEM_MONITOR_CONFIG__MEMMONITOR_DISABLE)) {
// destroy current timer if config is disable or (re)enable
if (mem_monitor_timer_handle) {
if (xTimerIsTimerActive(mem_monitor_timer_handle)) {
xTimerStop(mem_monitor_timer_handle, portMAX_DELAY);
}
xTimerDelete(mem_monitor_timer_handle, portMAX_DELAY);
mem_monitor_timer_handle = NULL;
}
}
// do we start a new timer
if (req_payload->config == RPC__MEM_MONITOR_CONFIG__MEMMONITOR_ENABLE) {
// set up params before enabling
memset(&mem_monitor_params, 0, sizeof(mem_monitor_params));
mem_monitor_params.internal_mem_dma = req_payload->internal->threshold_mem_dma;
mem_monitor_params.internal_mem_8bit = req_payload->internal->threshold_mem_8bit;
mem_monitor_params.external_mem_dma = req_payload->external->threshold_mem_dma;
mem_monitor_params.external_mem_8bit = req_payload->external->threshold_mem_8bit;
mem_monitor_report_always = req_payload->report_always;
mem_monitor_interval_sec = req_payload->interval_sec;
// create monitor timer
mem_monitor_timer_handle = xTimerCreate("MemMonitorTimer",
pdMS_TO_TICKS(mem_monitor_interval_sec * 1000),
pdTRUE,
0,
mem_monitor_timer_cb);
if (!mem_monitor_timer_handle) {
ESP_LOGE(TAG, "failed to create mem monitor timer");
return ESP_FAIL;
}
// start the timer
if (!xTimerStart(mem_monitor_timer_handle, portMAX_DELAY)) {
ESP_LOGE(TAG, "failed to start mem monitor timer");
xTimerDelete(mem_monitor_timer_handle, portMAX_DELAY);
mem_monitor_timer_handle = NULL;
return ESP_FAIL;
}
}
return ESP_OK;
}
static void mem_monitor_fill_resp_stats(RpcRespMemMonitor *resp_payload)
{
// fill the response with the memory statistics
resp_payload->curr_total_heap_size = esp_get_free_heap_size();
resp_payload->curr_internal->mem_dma->free_size = heap_caps_get_free_size(MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
resp_payload->curr_internal->mem_dma->largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
resp_payload->curr_internal->mem_8bit->free_size = heap_caps_get_free_size(MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL);
resp_payload->curr_internal->mem_8bit->largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
resp_payload->curr_external->mem_dma->free_size = heap_caps_get_free_size(MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM);
resp_payload->curr_external->mem_dma->largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM);
resp_payload->curr_external->mem_8bit->free_size = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
resp_payload->curr_external->mem_8bit->largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
ESP_LOGD(TAG, "Total heap size: %"PRIu32, resp_payload->curr_total_heap_size);
ESP_LOGD(TAG, "Internal->DMA->free_size: %"PRIu32, resp_payload->curr_internal->mem_dma->free_size);
ESP_LOGD(TAG, "Internal->DMA->largest_free_block: %"PRIu32, resp_payload->curr_internal->mem_dma->largest_free_block);
ESP_LOGD(TAG, "Internal->8bit->free_size: %"PRIu32, resp_payload->curr_internal->mem_8bit->free_size);
ESP_LOGD(TAG, "Internal->8bit->largest_free_block: %"PRIu32, resp_payload->curr_internal->mem_8bit->largest_free_block);
ESP_LOGD(TAG, "External->DMA->free_size: %"PRIu32, resp_payload->curr_external->mem_dma->free_size);
ESP_LOGD(TAG, "External->DMA->largest_free_block: %"PRIu32, resp_payload->curr_external->mem_dma->largest_free_block);
ESP_LOGD(TAG, "External->8bit->free_size: %"PRIu32, resp_payload->curr_external->mem_8bit->free_size);
ESP_LOGD(TAG, "External->8bit->largest_free_block: %"PRIu32, resp_payload->curr_external->mem_8bit->largest_free_block);
}
static esp_err_t req_mem_monitor(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespMemMonitor, resp_mem_monitor,
RpcReqMemMonitor, req_mem_monitor,
rpc__resp__mem_monitor__init);
esp_err_t res = mem_monitor_check_params(req_payload);
if (res != ESP_OK) {
resp_payload->resp = res;
goto err;
}
res = mem_monitor_setup(req_payload);
if (res != ESP_OK) {
resp_payload->resp = res;
goto err;
}
// prepare the response
resp_payload->config = req_payload->config;
// return current settings
resp_payload->report_always = mem_monitor_report_always;
resp_payload->interval_sec = mem_monitor_interval_sec;
RPC_ALLOC_ELEMENT(HeapInfo, resp_payload->curr_internal, heap_info__init);
RPC_ALLOC_ELEMENT(MemInfo, resp_payload->curr_internal->mem_dma, mem_info__init);
RPC_ALLOC_ELEMENT(MemInfo, resp_payload->curr_internal->mem_8bit, mem_info__init);
RPC_ALLOC_ELEMENT(HeapInfo, resp_payload->curr_external, heap_info__init);
RPC_ALLOC_ELEMENT(MemInfo, resp_payload->curr_external->mem_dma, mem_info__init);
RPC_ALLOC_ELEMENT(MemInfo, resp_payload->curr_external->mem_8bit, mem_info__init);
mem_monitor_fill_resp_stats(resp_payload);
err:
return ESP_OK;
}
#endif // CONFIG_ESP_HOSTED_MEM_MONITOR
#ifdef CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER
/* Internal RPC bridge - delegates to registered handler */
static esp_err_t handle_custom_rpc_request(uint32_t msg_id, uint8_t *req_data, uint32_t req_len)
{
/* --------- Caution ----------
* Keep this function as simple, small and fast as possible
* This function is as callback in the Rx thread.
* Do not use any blocking calls here
* ----------------------------
*/
if (msg_id == (uint32_t)-1) {
ESP_LOGE(TAG, "Invalid message ID 0xFFFFFFFF received");
return ESP_ERR_INVALID_ARG;
}
/* Find callback under mutex protection */
void (*cb)(uint32_t, const uint8_t *, size_t, void *) = NULL;
void *cb_local_context = NULL;
if (custom_callbacks_mutex && xSemaphoreTake(custom_callbacks_mutex, portMAX_DELAY) == pdTRUE) {
for (int i = 0; i < CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS; i++) {
if (custom_msg_callbacks[i].msg_id == msg_id && custom_msg_callbacks[i].callback) {
cb = custom_msg_callbacks[i].callback;
cb_local_context = custom_msg_callbacks[i].local_context;
break;
}
}
xSemaphoreGive(custom_callbacks_mutex);
}
/* Invoke callback outside mutex to avoid deadlock */
if (cb) {
cb(msg_id, req_data, req_len, cb_local_context);
return ESP_OK;
}
/* No handler registered for this message ID */
ESP_LOGW(TAG, "No custom handler registered for message ID %" PRIu32, msg_id);
return ESP_ERR_NOT_FOUND;
}
esp_err_t esp_hosted_send_custom_data(uint32_t msg_id_to_send, const uint8_t *data_to_send, size_t data_len_to_send)
{
if ((!data_to_send && data_len_to_send != 0) || (data_to_send && data_len_to_send == 0)) {
return ESP_ERR_INVALID_ARG;
}
/* Validate payload size */
if (data_len_to_send > 8166) {
/* Why 8166?
* pserial r.data has max 8192 bytes size.
* We want to get rid of this static buffer later.
* to restrict the data size, 8192 - (serial header + esp hosted header + headroom)
* we keep it 8166, as part of r.data[8192] removal, this code would be changed.
*/
return ESP_ERR_INVALID_SIZE;
}
/* Allocate buffer for [msg_id (4 bytes)][data...] */
size_t total_len = sizeof(msg_id_to_send) + data_len_to_send;
uint8_t *buf = malloc(total_len);
if (!buf) {
ESP_LOGE(TAG, "Failed to allocate %zu bytes", total_len);
return ESP_ERR_NO_MEM;
}
/* Pack msg_id as little-endian uint32_t */
memcpy(buf, &msg_id_to_send, sizeof(msg_id_to_send));
/* Copy user data after msg_id */
if (data_len_to_send > 0) {
memcpy(buf + sizeof(msg_id_to_send), data_to_send, data_len_to_send);
}
/* Send to RPC layer - rpc_evt_custom_rpc will unpack and wrap in protobuf */
send_event_data_to_host(RPC_ID__Event_CustomRpc, buf, (int)total_len);
free(buf);
return ESP_OK;
}
esp_err_t esp_hosted_register_custom_callback(uint32_t msg_id_exp,
void (*callback)(uint32_t msg_id_recvd, const uint8_t *data_recvd, size_t data_len_recvd, void *local_context),
void *local_context)
{
/* Validate message ID (-1/0xFFFFFFFF is invalid) */
if (msg_id_exp == (uint32_t)-1) {
ESP_LOGE(TAG, "Invalid message ID 0xFFFFFFFF");
return ESP_ERR_INVALID_ARG;
}
/* Initialize mutex on first use */
if (!custom_callbacks_mutex) {
custom_callbacks_mutex = xSemaphoreCreateMutex();
if (!custom_callbacks_mutex) {
ESP_LOGE(TAG, "Failed to create mutex");
return ESP_ERR_NO_MEM;
}
}
if (xSemaphoreTake(custom_callbacks_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Failed to lock mutex");
return ESP_FAIL;
}
/* Search for existing registration */
for (int i = 0; i < CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS; i++) {
if (custom_msg_callbacks[i].msg_id == msg_id_exp) {
/* Found existing registration */
if (callback == NULL) {
/* Deregister: clean up entry */
custom_msg_callbacks[i].msg_id = (uint32_t)-1; /* Mark as invalid */
custom_msg_callbacks[i].callback = NULL;
custom_msg_callbacks[i].local_context = NULL;
ESP_LOGI(TAG, "Deregistered callback for message ID %" PRIu32, msg_id_exp);
} else {
/* Update existing callback */
custom_msg_callbacks[i].callback = callback;
custom_msg_callbacks[i].local_context = local_context;
ESP_LOGI(TAG, "Updated callback for message ID %" PRIu32, msg_id_exp);
}
xSemaphoreGive(custom_callbacks_mutex);
return ESP_OK;
}
}
/* msg_id_exp not found */
if (callback == NULL) {
/* Cannot deregister what doesn't exist */
ESP_LOGW(TAG, "Cannot deregister message ID %" PRIu32 " - not registered", msg_id_exp);
xSemaphoreGive(custom_callbacks_mutex);
return ESP_ERR_NOT_FOUND;
}
/* Find empty slot for new registration */
for (int i = 0; i < CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS; i++) {
if (custom_msg_callbacks[i].callback == NULL) {
custom_msg_callbacks[i].msg_id = msg_id_exp;
custom_msg_callbacks[i].callback = callback;
custom_msg_callbacks[i].local_context = local_context;
ESP_LOGI(TAG, "Registered callback for message ID %" PRIu32, msg_id_exp);
xSemaphoreGive(custom_callbacks_mutex);
return ESP_OK;
}
}
ESP_LOGW(TAG, "No space for callback (max %d)", CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS);
xSemaphoreGive(custom_callbacks_mutex);
return ESP_ERR_NO_MEM;
}
static esp_err_t req_custom_rpc_handler(Rpc *req, Rpc *resp, void *priv_data)
{
RPC_TEMPLATE(RpcRespCustomRpc, resp_custom_rpc,
RpcReqCustomRpc, req_custom_rpc,
rpc__resp__custom_rpc__init);
/* Call the internal handler with message ID */
esp_err_t ret = handle_custom_rpc_request(
req_payload->custom_msg_id,
req_payload->data.data,
req_payload->data.len
);
/* Fill response with just status */
resp_payload->custom_msg_id = 0; /* Not used */
resp_payload->resp = ret;
return ESP_OK;
}
#endif /* CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER */
static esp_rpc_req_t req_table[] = {
{
.req_num = RPC_ID__Req_OTABegin,
.command_handler = req_ota_begin_handler
},
{
.req_num = RPC_ID__Req_OTAWrite,
.command_handler = req_ota_write_handler
},
{
.req_num = RPC_ID__Req_OTAEnd,
.command_handler = req_ota_end_handler
},
{
.req_num = RPC_ID__Req_OTAActivate,
.command_handler = req_ota_activate_handler
},
{
.req_num = RPC_ID__Req_ConfigHeartbeat,
.command_handler = req_config_heartbeat
},
{
.req_num = RPC_ID__Req_GetCoprocessorFwVersion,
.command_handler = req_get_coprocessor_fw_version
},
{
.req_num = RPC_ID__Req_IfaceMacAddrSetGet,
.command_handler = req_iface_mac_addr_set_get
},
{
.req_num = RPC_ID__Req_IfaceMacAddrLenGet,
.command_handler = req_iface_mac_addr_len_get
},
{
.req_num = RPC_ID__Req_FeatureControl,
.command_handler = req_feature_control
},
{
.req_num = RPC_ID__Req_AppGetDesc,
.command_handler = req_app_get_desc
},
#ifdef CONFIG_ESP_HOSTED_CP_WIFI
{
.req_num = RPC_ID__Req_GetMACAddress ,
.command_handler = req_wifi_get_mac
},
{
.req_num = RPC_ID__Req_GetWifiMode,
.command_handler = req_wifi_get_mode
},
{
.req_num = RPC_ID__Req_SetWifiMode,
.command_handler = req_wifi_set_mode
},
{
.req_num = RPC_ID__Req_SetMacAddress,
.command_handler = req_wifi_set_mac
},
{
.req_num = RPC_ID__Req_WifiSetPs,
.command_handler = req_wifi_set_ps
},
{
.req_num = RPC_ID__Req_WifiGetPs,
.command_handler = req_wifi_get_ps
},
{
.req_num = RPC_ID__Req_WifiSetMaxTxPower,
.command_handler = req_wifi_set_max_tx_power
},
{
.req_num = RPC_ID__Req_WifiGetMaxTxPower,
.command_handler = req_wifi_get_max_tx_power
},
{
.req_num = RPC_ID__Req_WifiInit,
.command_handler = req_wifi_init
},
{
.req_num = RPC_ID__Req_WifiDeinit,
.command_handler = req_wifi_deinit
},
{
.req_num = RPC_ID__Req_WifiStart,
.command_handler = req_wifi_start
},
{
.req_num = RPC_ID__Req_WifiStop,
.command_handler = req_wifi_stop
},
{
.req_num = RPC_ID__Req_WifiConnect,
.command_handler = req_wifi_connect
},
{
.req_num = RPC_ID__Req_WifiDisconnect,
.command_handler = req_wifi_disconnect
},
{
.req_num = RPC_ID__Req_WifiSetConfig,
.command_handler = req_wifi_set_config
},
{
.req_num = RPC_ID__Req_WifiScanParams,
.command_handler = req_wifi_scan_params
},
{
.req_num = RPC_ID__Req_WifiGetConfig,
.command_handler = req_wifi_get_config
},
{
.req_num = RPC_ID__Req_WifiScanStart,
.command_handler = req_wifi_scan_start
},
{
.req_num = RPC_ID__Req_WifiScanStop,
.command_handler = req_wifi_scan_stop
},
{
.req_num = RPC_ID__Req_WifiScanGetApNum,
.command_handler = req_wifi_scan_get_ap_num
},
{
.req_num = RPC_ID__Req_WifiScanGetApRecord,
.command_handler = req_wifi_scan_get_ap_record
},
{
.req_num = RPC_ID__Req_WifiScanGetApRecords,
.command_handler = req_wifi_scan_get_ap_records
},
{
.req_num = RPC_ID__Req_WifiClearApList,
.command_handler = req_wifi_clear_ap_list
},
{
.req_num = RPC_ID__Req_WifiRestore,
.command_handler = req_wifi_restore
},
{
.req_num = RPC_ID__Req_WifiClearFastConnect,
.command_handler = req_wifi_clear_fast_connect
},
{
.req_num = RPC_ID__Req_WifiStaGetApInfo,
.command_handler = req_wifi_sta_get_ap_info
},
{
.req_num = RPC_ID__Req_WifiDeauthSta,
.command_handler = req_wifi_deauth_sta
},
{
.req_num = RPC_ID__Req_WifiSetStorage,
.command_handler = req_wifi_set_storage
},
{
.req_num = RPC_ID__Req_WifiSetProtocol,
.command_handler = req_wifi_set_protocol
},
{
.req_num = RPC_ID__Req_WifiGetProtocol,
.command_handler = req_wifi_get_protocol
},
{
.req_num = RPC_ID__Req_WifiSetBandwidth,
.command_handler = req_wifi_set_bandwidth
},
{
.req_num = RPC_ID__Req_WifiGetBandwidth,
.command_handler = req_wifi_get_bandwidth
},
{
.req_num = RPC_ID__Req_WifiSetChannel,
.command_handler = req_wifi_set_channel
},
{
.req_num = RPC_ID__Req_WifiGetChannel,
.command_handler = req_wifi_get_channel
},
{
.req_num = RPC_ID__Req_WifiSetCountryCode,
.command_handler = req_wifi_set_country_code
},
{
.req_num = RPC_ID__Req_WifiGetCountryCode,
.command_handler = req_wifi_get_country_code
},
{
.req_num = RPC_ID__Req_WifiSetCountry,
.command_handler = req_wifi_set_country
},
{
.req_num = RPC_ID__Req_WifiGetCountry,
.command_handler = req_wifi_get_country
},
{
.req_num = RPC_ID__Req_WifiApGetStaList,
.command_handler = req_wifi_ap_get_sta_list
},
{
.req_num = RPC_ID__Req_WifiApGetStaAid,
.command_handler = req_wifi_ap_get_sta_aid
},
{
.req_num = RPC_ID__Req_WifiStaGetRssi,
.command_handler = req_wifi_sta_get_rssi
},
{
.req_num = RPC_ID__Req_WifiStaGetAid,
.command_handler = req_wifi_sta_get_aid
},
{
.req_num = RPC_ID__Req_WifiStaGetNegotiatedPhymode,
.command_handler = req_wifi_sta_get_negotiated_phymode
},
#if H_PRESENT_IN_ESP_IDF_5_4_0
{
.req_num = RPC_ID__Req_WifiSetProtocols,
.command_handler = req_wifi_set_protocols
},
{
.req_num = RPC_ID__Req_WifiGetProtocols,
.command_handler = req_wifi_get_protocols
},
{
.req_num = RPC_ID__Req_WifiSetBandwidths,
.command_handler = req_wifi_set_bandwidths
},
{
.req_num = RPC_ID__Req_WifiGetBandwidths,
.command_handler = req_wifi_get_bandwidths
},
{
.req_num = RPC_ID__Req_WifiSetBand,
.command_handler = req_wifi_set_band
},
{
.req_num = RPC_ID__Req_WifiGetBand,
.command_handler = req_wifi_get_band
},
{
.req_num = RPC_ID__Req_WifiSetBandMode,
.command_handler = req_wifi_set_band_mode
},
{
.req_num = RPC_ID__Req_WifiGetBandMode,
.command_handler = req_wifi_get_band_mode
},
#endif
{
.req_num = RPC_ID__Req_WifiSetInactiveTime,
.command_handler = req_wifi_set_inactive_time
},
{
.req_num = RPC_ID__Req_WifiGetInactiveTime,
.command_handler = req_wifi_get_inactive_time
},
{
.req_num = RPC_ID__Req_WifiDisablePmfConfig,
.command_handler = req_wifi_disable_pmf_config
},
{
.req_num = RPC_ID__Req_SetDhcpDnsStatus,
.command_handler = req_set_dhcp_dns_status
},
{
.req_num = RPC_ID__Req_GetDhcpDnsStatus,
.command_handler = req_get_dhcp_dns_status
},
#if CONFIG_SOC_WIFI_HE_SUPPORT
#if H_WIFI_HE_GREATER_THAN_ESP_IDF_5_3
{
.req_num = RPC_ID__Req_WifiStaTwtConfig,
.command_handler = req_wifi_sta_twt_config
},
#endif
{
.req_num = RPC_ID__Req_WifiStaItwtSetup,
.command_handler = req_wifi_sta_itwt_setup
},
{
.req_num = RPC_ID__Req_WifiStaItwtTeardown,
.command_handler = req_wifi_sta_itwt_teardown
},
{
.req_num = RPC_ID__Req_WifiStaItwtSuspend,
.command_handler = req_wifi_sta_itwt_suspend
},
{
.req_num = RPC_ID__Req_WifiStaItwtGetFlowIdStatus,
.command_handler = req_wifi_sta_itwt_get_flow_id_status
},
{
.req_num = RPC_ID__Req_WifiStaItwtSendProbeReq,
.command_handler = req_wifi_sta_itwt_send_probe_req
},
{
.req_num = RPC_ID__Req_WifiStaItwtSetTargetWakeTimeOffset,
.command_handler = req_wifi_sta_itwt_set_target_wake_time_offset
},
#endif // CONFIG_SOC_WIFI_HE_SUPPORT
#endif // CONFIG_ESP_HOSTED_CP_WIFI
#if H_WIFI_ENTERPRISE_SUPPORT
{
.req_num = RPC_ID__Req_WifiStaEnterpriseEnable,
.command_handler = req_wifi_sta_enterprise_enable
},
{
.req_num = RPC_ID__Req_WifiStaEnterpriseDisable,
.command_handler = req_wifi_sta_enterprise_disable
},
{
.req_num = RPC_ID__Req_EapSetIdentity,
.command_handler = req_eap_set_identity
},
{
.req_num = RPC_ID__Req_EapClearIdentity,
.command_handler = req_eap_clear_identity
},
{
.req_num = RPC_ID__Req_EapSetUsername,
.command_handler = req_eap_set_username
},
{
.req_num = RPC_ID__Req_EapClearUsername,
.command_handler = req_eap_clear_username
},
{
.req_num = RPC_ID__Req_EapSetPassword,
.command_handler = req_eap_set_password
},
{
.req_num = RPC_ID__Req_EapClearPassword,
.command_handler = req_eap_clear_password
},
{
.req_num = RPC_ID__Req_EapSetNewPassword,
.command_handler = req_eap_set_new_password
},
{
.req_num = RPC_ID__Req_EapClearNewPassword,
.command_handler = req_eap_clear_new_password
},
{
.req_num = RPC_ID__Req_EapSetCaCert,
.command_handler = req_eap_set_ca_cert
},
{
.req_num = RPC_ID__Req_EapClearCaCert,
.command_handler = req_eap_clear_ca_cert
},
{
.req_num = RPC_ID__Req_EapSetCertificateAndKey,
.command_handler = req_eap_set_certificate_and_key
},
{
.req_num = RPC_ID__Req_EapClearCertificateAndKey,
.command_handler = req_eap_clear_certificate_and_key
},
{
.req_num = RPC_ID__Req_EapGetDisableTimeCheck,
.command_handler = req_eap_get_disable_time_check
},
{
.req_num = RPC_ID__Req_EapSetTtlsPhase2Method,
.command_handler = req_eap_set_ttls_phase2_method
},
{
.req_num = RPC_ID__Req_EapSetSuitebCertification,
.command_handler = req_eap_set_suiteb_certification
},
{
.req_num = RPC_ID__Req_EapSetPacFile,
.command_handler = req_eap_set_pac_file
},
{
.req_num = RPC_ID__Req_EapSetFastParams,
.command_handler = req_eap_set_fast_params
},
{
.req_num = RPC_ID__Req_EapUseDefaultCertBundle,
.command_handler = req_eap_use_default_cert_bundle
},
#if H_GOT_EAP_OKC_SUPPORT
{
.req_num = RPC_ID__Req_WifiSetOkcSupport,
.command_handler = req_wifi_set_okc_support
},
#endif
#if H_GOT_EAP_SET_DOMAIN_NAME
{
.req_num = RPC_ID__Req_EapSetDomainName,
.command_handler = req_eap_set_domain_name
},
#endif
{
.req_num = RPC_ID__Req_EapSetDisableTimeCheck,
.command_handler = req_eap_set_disable_time_check
},
#if H_GOT_SET_EAP_METHODS_API
{
.req_num = RPC_ID__Req_EapSetEapMethods,
.command_handler = req_eap_set_eap_methods
},
#endif
#endif // #if H_WIFI_ENTERPRISE_SUPPORT
#if H_DPP_SUPPORT
{
.req_num = RPC_ID__Req_SuppDppInit,
.command_handler = req_supp_dpp_init
},
{
.req_num = RPC_ID__Req_SuppDppDeinit,
.command_handler = req_supp_dpp_deinit
},
{
.req_num = RPC_ID__Req_SuppDppDeinit,
.command_handler = req_supp_dpp_deinit
},
{
.req_num = RPC_ID__Req_SuppDppBootstrapGen,
.command_handler = req_supp_dpp_bootstrap_gen
},
{
.req_num = RPC_ID__Req_SuppDppStartListen,
.command_handler = req_supp_dpp_start_listen,
},
{
.req_num = RPC_ID__Req_SuppDppStopListen,
.command_handler = req_supp_dpp_stop_listen,
},
#endif // H_DPP_SUPPORT
#ifdef CONFIG_ESP_HOSTED_MEM_MONITOR
{
.req_num = RPC_ID__Req_MemMonitor,
.command_handler = req_mem_monitor
},
#endif
#ifdef CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER
{
.req_num = RPC_ID__Req_CustomRpc,
.command_handler = req_custom_rpc_handler
},
#endif
#if H_GPIO_EXPANDER_SUPPORT
{
.req_num = RPC_ID__Req_GpioConfig,
.command_handler = req_gpio_config
},
{
.req_num = RPC_ID__Req_GpioResetPin,
.command_handler = req_gpio_reset
},
{
.req_num = RPC_ID__Req_GpioSetLevel,
.command_handler = req_gpio_set_level
},
{
.req_num = RPC_ID__Req_GpioGetLevel,
.command_handler = req_gpio_get_level
},
{
.req_num = RPC_ID__Req_GpioSetDirection,
.command_handler = req_gpio_set_direction
},
{
.req_num = RPC_ID__Req_GpioInputEnable,
.command_handler = req_gpio_input_enable
},
{
.req_num = RPC_ID__Req_GpioSetPullMode,
.command_handler = req_gpio_set_pull_mode
},
#endif // H_GPIO_EXPANDER_SUPPORT
#if H_EXT_COEX_SUPPORT
{
.req_num = RPC_ID__Req_ExtCoex,
.command_handler = req_ext_coex
},
#endif
};
static int lookup_req_handler(int req_id)
{
for (int i = 0; i < sizeof(req_table)/sizeof(esp_rpc_req_t); i++) {
if (req_table[i].req_num == req_id) {
return i;
}
}
return -1;
}
static esp_err_t esp_rpc_command_dispatcher(
Rpc *req, Rpc *resp,
void *priv_data)
{
esp_err_t ret = ESP_OK;
int req_index = 0;
if (!req || !resp) {
ESP_LOGE(TAG, "Invalid parameters in command");
return ESP_FAIL;
}
if ((req->msg_id <= RPC_ID__Req_Base) ||
(req->msg_id >= RPC_ID__Req_Max)) {
ESP_LOGE(TAG, "RPC Req [0x%x] is out-of-range", req->msg_id);
goto err_not_supported;
}
if (req->msg_id != RPC_ID__Req_OTAWrite) {
ESP_LOGI(TAG, "RPC Req [0x%x] received", req->msg_id);
}
req_index = lookup_req_handler(req->msg_id);
if (req_index < 0) {
ESP_LOGW(TAG, "RPC Req [0x%x] is not supported, return failure", req->msg_id);
goto err_not_supported;
} else {
ESP_LOGI(TAG, "RPC Req [0x%x] is supported, index %d", req->msg_id, req_index);
}
ret = req_table[req_index].command_handler(req, resp, priv_data);
if (ret) {
ESP_LOGE(TAG, "RPC Req [0x%x] execution failed", req->msg_id);
goto err_cmd_error;
}
return ESP_OK;
err_not_supported:
// response ID Resp_Base means RPC Request was not supported
resp->msg_id = RPC_ID__Resp_Base;
err_cmd_error:
return ESP_OK;
}
/* use rpc__free_unpacked to free memory
* For RPC structure to be freed correctly with no memory leaks:
* - n_xxx must be set to number of 'repeated xxx' structures in RPC msg
* - xxx_case must be set for 'oneof xxx' structures in RPC msg
* - xxx.len must be set for 'bytes xxx' or 'string xxx' in RPC msg
*/
static void esp_rpc_cleanup(Rpc *resp)
{
if (resp) {
rpc__free_unpacked(resp, NULL);
}
}
esp_err_t data_transfer_handler(uint32_t session_id,const uint8_t *inbuf,
ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data)
{
Rpc *req = NULL;
esp_err_t ret = ESP_OK;
Rpc *resp = (Rpc *)calloc(1, sizeof(Rpc)); // resp deallocated in esp_rpc_cleanup()
if (!resp) {
ESP_LOGE(TAG, "%s calloc failed", __func__);
return ESP_FAIL;
}
if (!inbuf || !outbuf || !outlen) {
ESP_LOGE(TAG,"Buffers are NULL");
return ESP_FAIL;
}
req = rpc__unpack(NULL, inlen, inbuf);
if (!req) {
ESP_LOGE(TAG, "Unable to unpack config data");
return ESP_FAIL;
}
rpc__init (resp);
resp->msg_type = RPC_TYPE__Resp;
resp->msg_id = req->msg_id - RPC_ID__Req_Base + RPC_ID__Resp_Base;
resp->uid = req->uid;
resp->payload_case = resp->msg_id;
if (resp->msg_id != RPC_ID__Resp_OTAWrite) {
ESP_LOGI(TAG, "Resp_MSGId for req[0x%x] is [0x%x], uid %ld", req->msg_id, resp->msg_id, resp->uid);
}
ret = esp_rpc_command_dispatcher(req,resp,NULL);
if (ret) {
ESP_LOGE(TAG, "Command dispatching not happening");
goto err;
}
rpc__free_unpacked(req, NULL);
*outlen = rpc__get_packed_size (resp);
if (*outlen <= 0) {
ESP_LOGE(TAG, "Invalid encoding for response");
goto err;
}
// ESP_LOGE(TAG, "len %" PRIi16, *outlen);
*outbuf = (uint8_t *)calloc(1, *outlen);
if (!*outbuf) {
ESP_LOGE(TAG, "No memory allocated for outbuf");
esp_rpc_cleanup(resp);
return ESP_ERR_NO_MEM;
}
rpc__pack (resp, *outbuf);
//printf("Resp outbuf:\n");
//ESP_LOG_BUFFER_HEXDUMP("Resp outbuf", *outbuf, *outlen, ESP_LOG_INFO);
esp_rpc_cleanup(resp);
return ESP_OK;
err:
esp_rpc_cleanup(resp);
return ESP_FAIL;
}
/* Function ESPInit Notification */
static esp_err_t rpc_evt_ESPInit(Rpc *ntfy)
{
RpcEventESPInit *ntfy_payload = NULL;
ESP_LOGI(TAG,"event ESPInit");
ntfy_payload = (RpcEventESPInit *)
calloc(1,sizeof(RpcEventESPInit));
if (!ntfy_payload) {
ESP_LOGE(TAG,"Failed to allocate memory");
return ESP_ERR_NO_MEM;
}
rpc__event__espinit__init(ntfy_payload);
ntfy->payload_case = RPC__PAYLOAD_EVENT_ESP_INIT;
ntfy->event_esp_init = ntfy_payload;
ntfy_payload->cp_reset_reason = esp_reset_reason();
return ESP_OK;
}
static esp_err_t rpc_evt_heartbeat(Rpc *ntfy)
{
RpcEventHeartbeat *ntfy_payload = NULL;
ntfy_payload = (RpcEventHeartbeat*)
calloc(1,sizeof(RpcEventHeartbeat));
if (!ntfy_payload) {
ESP_LOGE(TAG,"Failed to allocate memory");
return ESP_ERR_NO_MEM;
}
rpc__event__heartbeat__init(ntfy_payload);
ntfy_payload->hb_num = hb_num;
ntfy->payload_case = RPC__PAYLOAD_EVENT_HEARTBEAT;
ntfy->event_heartbeat = ntfy_payload;
return ESP_OK;
}
static esp_err_t rpc_evt_Event_WifiEventNoArgs(Rpc *ntfy,
const uint8_t *data, ssize_t len)
{
NTFY_TEMPLATE(RPC_ID__Event_WifiEventNoArgs,
RpcEventWifiEventNoArgs, event_wifi_event_no_args,
rpc__event__wifi_event_no_args__init);
int32_t event_id = (int32_t)*data;
ESP_LOGI(TAG, "Sending Wi-Fi event [%ld]", event_id);
ntfy_payload->event_id = event_id;
ntfy_payload->resp = SUCCESS;
return ESP_OK;
}
#ifdef CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER
/* Custom RPC event handler - converts raw data to protobuf */
static esp_err_t rpc_evt_custom_rpc(Rpc *ntfy, const uint8_t *data, ssize_t len)
{
NTFY_TEMPLATE(RPC_ID__Event_CustomRpc,
RpcEventCustomRpc, event_custom_rpc,
rpc__event__custom_rpc__init);
ntfy_payload->resp = SUCCESS;
/* Extract msg_id from first 4 bytes */
uint32_t msg_id;
memcpy(&msg_id, data, sizeof(msg_id));
ntfy_payload->custom_event_id = msg_id;
/* Copy user data (skip msg_id at start) */
ssize_t user_data_len = len - sizeof(msg_id);
if (user_data_len > 0) {
NTFY_COPY_BYTES(ntfy_payload->data, data + sizeof(msg_id), user_data_len);
}
return ESP_OK;
}
#endif
#ifdef CONFIG_ESP_HOSTED_MEM_MONITOR
static esp_err_t rpc_evt_mem_monitor(Rpc *ntfy, const uint8_t *data, ssize_t len)
{
NTFY_TEMPLATE(RPC_ID__Event_MemMonitor,
RpcEventMemMonitor, event_mem_monitor,
rpc__event__mem_monitor__init);
mem_monitor_event_t *ptr = (mem_monitor_event_t *)data;
NTFY_ALLOC_ELEMENT(HeapInfo, ntfy_payload->curr_internal, heap_info__init);
NTFY_ALLOC_ELEMENT(MemInfo, ntfy_payload->curr_internal->mem_dma, mem_info__init);
NTFY_ALLOC_ELEMENT(MemInfo, ntfy_payload->curr_internal->mem_8bit, mem_info__init);
NTFY_ALLOC_ELEMENT(HeapInfo, ntfy_payload->curr_external, heap_info__init);
NTFY_ALLOC_ELEMENT(MemInfo, ntfy_payload->curr_external->mem_dma, mem_info__init);
NTFY_ALLOC_ELEMENT(MemInfo, ntfy_payload->curr_external->mem_8bit, mem_info__init);
ntfy_payload->curr_total_free_heap_size = ptr->total_free_heap_size;
ntfy_payload->curr_min_free_heap_size = ptr->min_free_heap_size;
ntfy_payload->curr_internal->mem_dma->free_size = ptr->free_size.internal_mem_dma;
ntfy_payload->curr_internal->mem_8bit->free_size = ptr->free_size.internal_mem_8bit;
ntfy_payload->curr_internal->mem_dma->largest_free_block = ptr->largest_free_block.internal_mem_dma;
ntfy_payload->curr_internal->mem_8bit->largest_free_block = ptr->largest_free_block.internal_mem_8bit;
ntfy_payload->curr_external->mem_dma->free_size = ptr->free_size.external_mem_dma;
ntfy_payload->curr_external->mem_8bit->free_size = ptr->free_size.external_mem_8bit;
ntfy_payload->curr_external->mem_dma->largest_free_block = ptr->largest_free_block.external_mem_dma;
ntfy_payload->curr_external->mem_8bit->largest_free_block = ptr->largest_free_block.external_mem_8bit;
return ESP_OK;
err:
return ESP_FAIL;
}
#endif // CONFIG_ESP_HOSTED_MEM_MONITOR
esp_err_t rpc_evt_handler(uint32_t session_id,const uint8_t *inbuf,
ssize_t inlen, uint8_t **outbuf, ssize_t *outlen, void *priv_data)
{
int ret = SUCCESS;
Rpc *ntfy = (Rpc *)calloc(1, sizeof(Rpc)); // ntfy deallocated in esp_rpc_cleanup()
if (!ntfy) {
ESP_LOGE(TAG, "%s calloc failed", __func__);
return ESP_FAIL;
}
if (!outbuf || !outlen) {
ESP_LOGE(TAG,"Buffers are NULL");
return ESP_FAIL;
}
rpc__init (ntfy);
ntfy->msg_id = session_id;
ntfy->msg_type = RPC_TYPE__Event;
switch ((int)ntfy->msg_id) {
case RPC_ID__Event_ESPInit : {
ret = rpc_evt_ESPInit(ntfy);
break;
} case RPC_ID__Event_Heartbeat: {
ret = rpc_evt_heartbeat(ntfy);
break;
#ifdef CONFIG_ESP_HOSTED_CP_WIFI
} case RPC_ID__Event_AP_StaConnected: {
ret = rpc_evt_ap_staconn_conn_disconn(ntfy, inbuf, inlen, WIFI_EVENT_AP_STACONNECTED);
break;
} case RPC_ID__Event_AP_StaDisconnected: {
ret = rpc_evt_ap_staconn_conn_disconn(ntfy, inbuf, inlen, WIFI_EVENT_AP_STADISCONNECTED);
break;
} case RPC_ID__Event_StaScanDone: {
ret = rpc_evt_sta_scan_done(ntfy, inbuf, inlen, WIFI_EVENT_SCAN_DONE);
break;
} case RPC_ID__Event_StaConnected: {
ret = rpc_evt_sta_connected(ntfy, inbuf, inlen, WIFI_EVENT_STA_CONNECTED);
break;
} case RPC_ID__Event_StaDisconnected: {
ret = rpc_evt_sta_disconnected(ntfy, inbuf, inlen, WIFI_EVENT_STA_DISCONNECTED);
break;
#if CONFIG_SOC_WIFI_HE_SUPPORT
} case RPC_ID__Event_StaItwtSetup: {
ret = rpc_evt_itwt_setup(ntfy, inbuf, inlen, WIFI_EVENT_ITWT_SETUP);
break;
} case RPC_ID__Event_StaItwtTeardown: {
ret = rpc_evt_itwt_teardown(ntfy, inbuf, inlen, WIFI_EVENT_ITWT_TEARDOWN);
break;
} case RPC_ID__Event_StaItwtSuspend: {
ret = rpc_evt_itwt_suspend(ntfy, inbuf, inlen, WIFI_EVENT_ITWT_SUSPEND);
break;
} case RPC_ID__Event_StaItwtProbe: {
ret = rpc_evt_itwt_probe(ntfy, inbuf, inlen, WIFI_EVENT_ITWT_PROBE);
break;
#endif
#endif // CONFIG_ESP_HOSTED_CP_WIFI
} case RPC_ID__Event_WifiEventNoArgs: {
ret = rpc_evt_Event_WifiEventNoArgs(ntfy, inbuf, inlen);
break;
#ifdef CONFIG_ESP_HOSTED_NETWORK_SPLIT_ENABLED
} case RPC_ID__Event_DhcpDnsStatus: {
ret = rpc_evt_Event_DhcpDnsStatus(ntfy, inbuf, inlen);
break;
#endif
#if H_SUPP_DPP_SUPPORT
} case RPC_ID__Event_SuppDppUriReady: {
ret = rpc_evt_supp_dpp_uri_ready(ntfy, inbuf, inlen);
break;
} case RPC_ID__Event_SuppDppCfgRecvd: {
ret = rpc_evt_supp_dpp_cfg_recvd(ntfy, inbuf, inlen);
break;
} case RPC_ID__Event_SuppDppFail: {
ret = rpc_evt_supp_dpp_fail(ntfy, inbuf, inlen);
break;
#endif // H_SUPP_DPP_SUPPORT
#if H_WIFI_DPP_SUPPORT
} case RPC_ID__Event_WifiDppUriReady: {
ret = rpc_evt_wifi_dpp_uri_ready(ntfy, inbuf, inlen);
break;
} case RPC_ID__Event_WifiDppCfgRecvd: {
ret = rpc_evt_wifi_dpp_cfg_recvd(ntfy, inbuf, inlen);
break;
} case RPC_ID__Event_WifiDppFail: {
ret = rpc_evt_wifi_dpp_fail(ntfy, inbuf, inlen);
break;
#endif // H_WIFI_DPP_SUPPORT
#ifdef CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER
} case RPC_ID__Event_CustomRpc: {
ret = rpc_evt_custom_rpc(ntfy, inbuf, inlen);
break;
#endif
#ifdef CONFIG_ESP_HOSTED_MEM_MONITOR
} case RPC_ID__Event_MemMonitor: {
ret = rpc_evt_mem_monitor(ntfy, inbuf, inlen);
break;
#endif // CONFIG_ESP_HOSTED_MEM_MONITOR
} default: {
ESP_LOGE(TAG, "Incorrect/unsupported Ctrl Notification[%u]\n",ntfy->msg_id);
goto err;
break;
}
}
if (ret) {
ESP_LOGI(TAG, "notification[%u] not sent\n", ntfy->msg_id);
goto err;
}
*outlen = rpc__get_packed_size (ntfy);
if (*outlen <= 0) {
ESP_LOGE(TAG, "Invalid encoding for notify");
goto err;
}
*outbuf = (uint8_t *)calloc(1, *outlen);
if (!*outbuf) {
ESP_LOGE(TAG, "No memory allocated for outbuf");
esp_rpc_cleanup(ntfy);
return ESP_ERR_NO_MEM;
}
rpc__pack (ntfy, *outbuf);
//printf("event outbuf:\n");
//ESP_LOG_BUFFER_HEXDUMP("event outbuf", *outbuf, *outlen, ESP_LOG_INFO);
esp_rpc_cleanup(ntfy);
return ESP_OK;
err:
if (!*outbuf) {
free(*outbuf);
*outbuf = NULL;
}
esp_rpc_cleanup(ntfy);
return ESP_FAIL;
}