mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
feat(openthread): support RCP console debug via spinel
This commit is contained in:
@@ -196,6 +196,11 @@ if(CONFIG_OPENTHREAD_ENABLED)
|
||||
"src/port/esp_openthread_messagepool.c")
|
||||
endif()
|
||||
|
||||
if(NOT CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE)
|
||||
list(APPEND exclude_srcs
|
||||
"src/ncp/esp_openthread_ncp_console.cpp")
|
||||
endif()
|
||||
|
||||
if(CONFIG_OPENTHREAD_FTD)
|
||||
set(device_type "OPENTHREAD_FTD=1")
|
||||
elseif(CONFIG_OPENTHREAD_MTD)
|
||||
|
||||
@@ -233,6 +233,12 @@ menu "OpenThread"
|
||||
default y
|
||||
help
|
||||
Select this to enable OpenThread NCP vendor commands.
|
||||
|
||||
config OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
bool "Enable RCP console via Spinel"
|
||||
default n
|
||||
help
|
||||
Select this to enable sending console commands to OpenThread RCP via Spinel.
|
||||
endmenu
|
||||
|
||||
config OPENTHREAD_BORDER_ROUTER
|
||||
|
||||
@@ -76,6 +76,14 @@ esp_err_t esp_openthread_rcp_init(void);
|
||||
*/
|
||||
esp_err_t esp_openthread_rcp_version_set(const char *version_str);
|
||||
|
||||
/**
|
||||
* @brief Sends a console command to RCP.
|
||||
*
|
||||
* @param[in] input The pointer to a command string to be run on RCP.
|
||||
*
|
||||
*/
|
||||
void esp_openthread_rcp_send_command(const char *input);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -12,6 +12,10 @@ extern "C" {
|
||||
|
||||
void otAppNcpInit(otInstance *aInstance);
|
||||
|
||||
#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
esp_err_t esp_console_redirect_to_otlog(void);
|
||||
#endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -11,3 +11,5 @@
|
||||
#define SPINEL_PROP_VENDOR_ESP_SET_PENDINGMODE (SPINEL_PROP_VENDOR_ESP__BEGIN + 2) /* Vendor command for pending mode.*/
|
||||
|
||||
#define SPINEL_PROP_VENDOR_ESP_COEX_EVENT (SPINEL_PROP_VENDOR_ESP__BEGIN + 3)
|
||||
|
||||
#define SPINEL_PROP_VENDOR_ESP_SET_CONSOLE_CMD (SPINEL_PROP_VENDOR_ESP__BEGIN + 4)
|
||||
|
||||
@@ -192,10 +192,10 @@
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_LOG_OUTPUT
|
||||
*
|
||||
* The ESP-IDF platform provides an otPlatLog() function.
|
||||
* Use app log for RCP to transmit logs to host via Spinel.
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_LOG_OUTPUT
|
||||
#define OPENTHREAD_CONFIG_LOG_OUTPUT OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED
|
||||
#define OPENTHREAD_CONFIG_LOG_OUTPUT OPENTHREAD_CONFIG_LOG_OUTPUT_APP
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_ieee802154.h"
|
||||
#include "esp_openthread_ncp.h"
|
||||
#include "esp_spinel_ncp_vendor_macro.h"
|
||||
@@ -18,6 +19,24 @@
|
||||
#include "utils/uart.h"
|
||||
#endif
|
||||
|
||||
#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
#include "esp_console.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_openthread_common_macro.h"
|
||||
|
||||
static constexpr size_t s_console_command_max_length = 256;
|
||||
static constexpr size_t s_console_command_queue_length = 8;
|
||||
static QueueHandle_t s_console_command_queue = nullptr;
|
||||
|
||||
#define NO_LOG_TAG "" // don't use a tag to reduce log lengths from RCP
|
||||
|
||||
struct ConsoleCmdMsg
|
||||
{
|
||||
char cmd[s_console_command_max_length];
|
||||
};
|
||||
#endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
|
||||
#if CONFIG_OPENTHREAD_RCP_UART
|
||||
extern "C" {
|
||||
static int NcpSend(const uint8_t *aBuf, uint16_t aBufLength)
|
||||
@@ -28,15 +47,69 @@ extern "C" {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
static void console_command_worker_task(void *arg)
|
||||
{
|
||||
ConsoleCmdMsg msg;
|
||||
|
||||
for (;;) {
|
||||
if (xQueueReceive(s_console_command_queue, &msg, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
esp_err_t err = esp_console_run(msg.cmd, &ret);
|
||||
|
||||
if (err == ESP_ERR_NOT_FOUND){
|
||||
ESP_LOGI(NO_LOG_TAG, "Unrecognized command: %s", msg.cmd);
|
||||
} else if (err == ESP_ERR_INVALID_ARG) {
|
||||
ESP_LOGI(NO_LOG_TAG, "Command is empty");
|
||||
} else if (err == ESP_OK && ret != ESP_OK) {
|
||||
ESP_LOGI(NO_LOG_TAG, "Command returned non-zero error code: 0x%x (%s)", ret, esp_err_to_name(ret));
|
||||
} else if (err != ESP_OK) {
|
||||
ESP_LOGI(NO_LOG_TAG, "Internal error running '%s': %s", msg.cmd, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t init_console_command_worker()
|
||||
{
|
||||
if (s_console_command_queue) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_console_command_queue = xQueueCreate(s_console_command_queue_length, sizeof(ConsoleCmdMsg));
|
||||
ESP_RETURN_ON_FALSE(s_console_command_queue, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "Failed to create s_console_command_queue");
|
||||
|
||||
BaseType_t ret = xTaskCreatePinnedToCore(console_command_worker_task, "ot_console", 3072, nullptr, 3, nullptr, tskNO_AFFINITY);
|
||||
|
||||
if (ret != pdPASS) {
|
||||
vQueueDelete(s_console_command_queue);
|
||||
s_console_command_queue = nullptr;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
|
||||
extern "C" void otAppNcpInit(otInstance *aInstance)
|
||||
{
|
||||
#if CONFIG_OPENTHREAD_RCP_SPI
|
||||
otNcpSpiInit(aInstance);
|
||||
#else
|
||||
#if CONFIG_OPENTHREAD_RCP_UART
|
||||
IgnoreError(otPlatUartEnable());
|
||||
|
||||
otNcpHdlcInit(aInstance, NcpSend);
|
||||
#else
|
||||
otNcpSpiInit(aInstance);
|
||||
#endif
|
||||
|
||||
#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
esp_err_t err = esp_console_redirect_to_otlog();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(NO_LOG_TAG, "Failed to redirect console to otPlatLog: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
init_console_command_worker();
|
||||
#endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
}
|
||||
|
||||
namespace ot {
|
||||
@@ -127,6 +200,30 @@ otError NcpBase::VendorSetPropertyHandler(spinel_prop_key_t aPropKey)
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
case SPINEL_PROP_VENDOR_ESP_SET_CONSOLE_CMD: {
|
||||
const uint8_t *data = nullptr;
|
||||
uint16_t len = 0;
|
||||
mDecoder.ReadDataWithLen(data, len);
|
||||
|
||||
if (len >= s_console_command_max_length) {
|
||||
ESP_LOGW(NO_LOG_TAG, "Console command too long (%u bytes, max %zu), dropping", len, s_console_command_max_length - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
ConsoleCmdMsg msg{};
|
||||
memcpy(msg.cmd, data, len);
|
||||
msg.cmd[len] = '\0';
|
||||
|
||||
// Use a separate task to run the command so that RCP does not time out
|
||||
if (s_console_command_queue == nullptr) {
|
||||
ESP_LOGW(NO_LOG_TAG, "Console worker not initialized, dropping cmd: %s", msg.cmd);
|
||||
} else if (xQueueSend(s_console_command_queue, &msg, 0) != pdTRUE) {
|
||||
ESP_LOGW(NO_LOG_TAG, "Console cmd queue full, dropping cmd: %s", msg.cmd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif // CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
|
||||
default:
|
||||
error = OT_ERROR_NOT_FOUND;
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/stream_buffer.h"
|
||||
#include "openthread/platform/logging.h"
|
||||
#include "esp_openthread_common_macro.h"
|
||||
#include "esp_openthread_ncp.h"
|
||||
|
||||
#define OTLOG_LINE_MAX 256
|
||||
#define ROM_TAP_SB_SIZE 1024
|
||||
#define ROM_TAP_TRIGLVL 1
|
||||
|
||||
typedef struct {
|
||||
char line[OTLOG_LINE_MAX];
|
||||
size_t len;
|
||||
} line_buf_t;
|
||||
|
||||
static SemaphoreHandle_t s_mutex;
|
||||
static line_buf_t s_stdout_buf;
|
||||
static line_buf_t s_stderr_buf;
|
||||
static StreamBufferHandle_t s_rom_tap_sb;
|
||||
static line_buf_t s_rom_line;
|
||||
|
||||
static inline line_buf_t *get_buf_for_fd(int fd)
|
||||
{
|
||||
if (fd == STDERR_FILENO) return &s_stderr_buf;
|
||||
// default: treat as stdout
|
||||
return &s_stdout_buf;
|
||||
}
|
||||
|
||||
// Flush one assembled line to otPlatLog (strip trailing CR/LF)
|
||||
static void flush_line_to_otlog(line_buf_t *b, otLogLevel level, bool allow_empty)
|
||||
{
|
||||
while (b->len > 0 && (b->line[b->len - 1] == '\n' || b->line[b->len - 1] == '\r')) {
|
||||
b->len--;
|
||||
}
|
||||
b->line[b->len] = '\0';
|
||||
|
||||
if (b->len == 0 && allow_empty) {
|
||||
otPlatLog(level, OT_LOG_REGION_NCP, "%s", " ");
|
||||
return;
|
||||
}
|
||||
|
||||
otPlatLog(level, OT_LOG_REGION_NCP, "%s", b->line);
|
||||
b->len = 0;
|
||||
}
|
||||
|
||||
// Accumulate bytes; emit at newline or buffer full.
|
||||
static void buffer_and_maybe_flush(int fd, const char *data, size_t size)
|
||||
{
|
||||
line_buf_t *b = get_buf_for_fd(fd);
|
||||
otLogLevel lvl = (fd == STDERR_FILENO) ? OT_LOG_LEVEL_WARN : OT_LOG_LEVEL_NOTE;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
char c = data[i];
|
||||
if (b->len < OTLOG_LINE_MAX - 1) {
|
||||
b->line[b->len++] = c;
|
||||
}
|
||||
|
||||
if (c == '\n') {
|
||||
flush_line_to_otlog(b, lvl, /*allow_empty=*/true);
|
||||
} else if (b->len == OTLOG_LINE_MAX - 1) {
|
||||
flush_line_to_otlog(b, lvl, /*allow_empty=*/false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t otlog_write(int fd, const void *data, size_t size)
|
||||
{
|
||||
if (!data || size == 0) return 0;
|
||||
|
||||
if (s_mutex) xSemaphoreTake(s_mutex, portMAX_DELAY);
|
||||
buffer_and_maybe_flush(fd, (const char *)data, size);
|
||||
if (s_mutex) xSemaphoreGive(s_mutex);
|
||||
|
||||
return (ssize_t)size;
|
||||
}
|
||||
|
||||
static ssize_t otlog_read(int fd, void *dst, size_t size)
|
||||
{
|
||||
(void)fd; (void)dst; (void)size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int otlog_open(const char *path, int flags, int mode)
|
||||
{
|
||||
(void)path; (void)flags; (void)mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int otlog_close(int fd)
|
||||
{
|
||||
(void)fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static IRAM_ATTR void rom_putc_tap(char c)
|
||||
{
|
||||
BaseType_t woken = pdFALSE;
|
||||
|
||||
if (!xPortCanYield()) {
|
||||
xStreamBufferSendFromISR(s_rom_tap_sb, &c, 1, &woken);
|
||||
if (woken) portYIELD_FROM_ISR();
|
||||
} else {
|
||||
xStreamBufferSend(s_rom_tap_sb, &c, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t esp_rom_printf_mirror_install(void)
|
||||
{
|
||||
s_rom_tap_sb = xStreamBufferCreate(ROM_TAP_SB_SIZE, ROM_TAP_TRIGLVL);
|
||||
ESP_RETURN_ON_FALSE(s_rom_tap_sb, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "Failed to create s_rom_tap_sb buffer");
|
||||
|
||||
esp_rom_install_channel_putc(1, rom_putc_tap);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void rom_tap_consumer_task(void *arg)
|
||||
{
|
||||
const otLogLevel lvl = OT_LOG_LEVEL_NOTE;
|
||||
uint8_t ch;
|
||||
|
||||
for (;;) {
|
||||
if (xStreamBufferReceive(s_rom_tap_sb, &ch, 1, portMAX_DELAY) != 1) continue;
|
||||
|
||||
if (s_rom_line.len < OTLOG_LINE_MAX - 1) {
|
||||
s_rom_line.line[s_rom_line.len++] = (char)ch;
|
||||
}
|
||||
if (ch == '\n' || s_rom_line.len == OTLOG_LINE_MAX - 1) {
|
||||
flush_line_to_otlog(&s_rom_line, lvl, /*allow_empty=*/true);
|
||||
s_rom_line.len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t esp_console_redirect_to_otlog(void)
|
||||
{
|
||||
if (!s_mutex) {
|
||||
s_mutex = xSemaphoreCreateMutex();
|
||||
ESP_RETURN_ON_FALSE(s_mutex, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "Failed to create s_mutex");
|
||||
}
|
||||
|
||||
esp_vfs_t vfs = {};
|
||||
vfs.flags = ESP_VFS_FLAG_DEFAULT;
|
||||
vfs.write = &otlog_write;
|
||||
vfs.read = &otlog_read;
|
||||
vfs.open = &otlog_open;
|
||||
vfs.close = &otlog_close;
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_vfs_register("/dev/otlog", &vfs, NULL), OT_PLAT_LOG_TAG, "vfs register failed");
|
||||
|
||||
FILE *fout = freopen("/dev/otlog", "w", stdout);
|
||||
FILE *ferr = freopen("/dev/otlog", "w", stderr);
|
||||
ESP_RETURN_ON_FALSE(fout && ferr, ESP_FAIL, OT_PLAT_LOG_TAG, "freopen failed (stdout=%p, stderr=%p)", fout, ferr);
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_rom_printf_mirror_install(), OT_PLAT_LOG_TAG, "esp_rom_printf mirror install failed");
|
||||
xTaskCreatePinnedToCore(rom_tap_consumer_task, "ot_rom_tap", 2048, NULL, 4, NULL, tskNO_AFFINITY);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -11,10 +11,12 @@
|
||||
#include "esp_err.h"
|
||||
#include "esp_openthread_border_router.h"
|
||||
#include "esp_openthread_common_macro.h"
|
||||
#include "esp_openthread_ncp.h"
|
||||
#include "esp_openthread_platform.h"
|
||||
#include "esp_openthread_types.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_spinel_interface.hpp"
|
||||
#include "esp_spinel_ncp_vendor_macro.h"
|
||||
#include "esp_spi_spinel_interface.hpp"
|
||||
#include "esp_uart_spinel_interface.hpp"
|
||||
#include "openthread-core-config.h"
|
||||
@@ -210,6 +212,11 @@ void esp_openthread_handle_netif_state_change(bool state)
|
||||
s_radio.SetTimeSyncState(state);
|
||||
}
|
||||
|
||||
void esp_openthread_rcp_send_command(const char *input)
|
||||
{
|
||||
s_radio.Set(SPINEL_PROP_VENDOR_ESP_SET_CONSOLE_CMD, SPINEL_DATATYPE_DATA_WLEN_S, input, strlen(input));
|
||||
}
|
||||
|
||||
void otPlatRadioGetIeeeEui64(otInstance *instance, uint8_t *ieee_eui64)
|
||||
{
|
||||
SuccessOrDie(s_radio.GetIeeeEui64(ieee_eui64));
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
#error "RCP is only supported for the SoCs which have IEEE 802.15.4 module"
|
||||
#endif
|
||||
|
||||
#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
#include "esp_console.h"
|
||||
#endif
|
||||
|
||||
#define TAG "ot_esp_rcp"
|
||||
|
||||
extern void otAppNcpInit(otInstance *instance);
|
||||
@@ -59,5 +63,11 @@ void app_main(void)
|
||||
},
|
||||
};
|
||||
|
||||
#if CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE
|
||||
esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT();
|
||||
esp_console_init(&console_config);
|
||||
esp_console_register_help_command();
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(esp_openthread_start(&config));
|
||||
}
|
||||
|
||||
@@ -52,3 +52,9 @@ CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC=n
|
||||
CONFIG_OPENTHREAD_LOG_LEVEL_NONE=y
|
||||
CONFIG_OPENTHREAD_TIMING_OPTIMIZATION=y
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
#
|
||||
# Turn on RCP console by default, overriding default log level from above
|
||||
#
|
||||
CONFIG_OPENTHREAD_RCP_SPINEL_CONSOLE=y
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
|
||||
Reference in New Issue
Block a user