From 926af9eda1d74db4a071737437f01a0b198fce77 Mon Sep 17 00:00:00 2001 From: Shu Chen Date: Mon, 26 Dec 2022 15:48:07 +0800 Subject: [PATCH] openthread: open source openthread port layer --- components/ieee802154/Kconfig | 6 + components/openthread/CMakeLists.txt | 67 +-- .../openthread/include/esp_openthread_lock.h | 8 +- components/openthread/port/esp_openthread.cpp | 91 ++++ .../openthread/port/esp_openthread_alarm.c | 149 ++++++ .../openthread/port/esp_openthread_cli.c | 94 ++++ .../{src => port}/esp_openthread_dns64.c | 0 .../openthread/port/esp_openthread_flash.c | 65 +++ .../openthread/port/esp_openthread_lock.c | 63 +++ .../openthread/port/esp_openthread_logging.c | 63 +++ .../port/esp_openthread_lwip_netif.c | 143 ++++++ .../openthread/port/esp_openthread_memory.c | 21 + .../openthread/port/esp_openthread_misc.c | 80 +++ .../openthread/port/esp_openthread_ncp.cpp | 155 ++++++ .../port/esp_openthread_netif_glue.c | 374 ++++++++++++++ .../port/esp_openthread_platform.cpp | 164 ++++++ .../openthread/port/esp_openthread_radio.c | 472 ++++++++++++++++++ .../port/esp_openthread_radio_uart.cpp | 312 ++++++++++++ .../port/esp_openthread_task_queue.c | 97 ++++ .../openthread/port/esp_openthread_uart.c | 132 +++++ .../openthread/port/esp_openthread_udp.c | 424 ++++++++++++++++ .../port/esp_uart_spinel_interface.cpp | 286 +++++++++++ .../private_include/esp_openthread_alarm.h | 56 +++ .../esp_openthread_common_macro.h | 23 + .../private_include/esp_openthread_flash.h | 25 + .../private_include/esp_openthread_ncp.h | 23 + .../esp_openthread_netif_glue_priv.h | 49 ++ .../private_include/esp_openthread_platform.h | 144 ++++++ .../private_include/esp_openthread_radio.h | 58 +++ .../esp_openthread_task_queue.h | 82 +++ .../private_include/esp_openthread_uart.h | 72 +++ .../esp_uart_spinel_interface.hpp | 168 +++++++ .../openthread/src/esp_openthread_stubs.c | 31 -- 33 files changed, 3912 insertions(+), 85 deletions(-) create mode 100644 components/openthread/port/esp_openthread.cpp create mode 100644 components/openthread/port/esp_openthread_alarm.c create mode 100644 components/openthread/port/esp_openthread_cli.c rename components/openthread/{src => port}/esp_openthread_dns64.c (100%) create mode 100644 components/openthread/port/esp_openthread_flash.c create mode 100644 components/openthread/port/esp_openthread_lock.c create mode 100644 components/openthread/port/esp_openthread_logging.c create mode 100644 components/openthread/port/esp_openthread_lwip_netif.c create mode 100644 components/openthread/port/esp_openthread_memory.c create mode 100644 components/openthread/port/esp_openthread_misc.c create mode 100644 components/openthread/port/esp_openthread_ncp.cpp create mode 100644 components/openthread/port/esp_openthread_netif_glue.c create mode 100644 components/openthread/port/esp_openthread_platform.cpp create mode 100644 components/openthread/port/esp_openthread_radio.c create mode 100644 components/openthread/port/esp_openthread_radio_uart.cpp create mode 100644 components/openthread/port/esp_openthread_task_queue.c create mode 100644 components/openthread/port/esp_openthread_uart.c create mode 100644 components/openthread/port/esp_openthread_udp.c create mode 100644 components/openthread/port/esp_uart_spinel_interface.cpp create mode 100644 components/openthread/private_include/esp_openthread_alarm.h create mode 100644 components/openthread/private_include/esp_openthread_common_macro.h create mode 100644 components/openthread/private_include/esp_openthread_flash.h create mode 100644 components/openthread/private_include/esp_openthread_ncp.h create mode 100644 components/openthread/private_include/esp_openthread_netif_glue_priv.h create mode 100644 components/openthread/private_include/esp_openthread_platform.h create mode 100644 components/openthread/private_include/esp_openthread_radio.h create mode 100644 components/openthread/private_include/esp_openthread_task_queue.h create mode 100644 components/openthread/private_include/esp_openthread_uart.h create mode 100644 components/openthread/private_include/esp_uart_spinel_interface.hpp delete mode 100644 components/openthread/src/esp_openthread_stubs.c diff --git a/components/ieee802154/Kconfig b/components/ieee802154/Kconfig index ecfffc9eb4..99bf1d1a90 100644 --- a/components/ieee802154/Kconfig +++ b/components/ieee802154/Kconfig @@ -5,4 +5,10 @@ menu "IEEE 802.15.4" bool default "y" if SOC_IEEE802154_SUPPORTED + config IEEE802154_RX_BUFFER_SIZE + int "The number of 802.15.4 receive buffers" + depends on IEEE802154_ENABLED + default 20 + range 2 100 + endmenu # IEEE 802.15.4 diff --git a/components/openthread/CMakeLists.txt b/components/openthread/CMakeLists.txt index a43dbf7f86..420aaeb938 100644 --- a/components/openthread/CMakeLists.txt +++ b/components/openthread/CMakeLists.txt @@ -14,6 +14,7 @@ if(CONFIG_OPENTHREAD_ENABLED) "private_include") set(src_dirs + "port" "openthread/examples/platforms/utils" "openthread/src/core/api" "openthread/src/core/common" @@ -33,7 +34,6 @@ if(CONFIG_OPENTHREAD_ENABLED) if(CONFIG_OPENTHREAD_FTD OR CONFIG_OPENTHREAD_MTD) list(APPEND src_dirs - "src" "openthread/src/core/backbone_router" "openthread/src/core/coap" "openthread/src/core/meshcop" @@ -105,6 +105,15 @@ if(CONFIG_OPENTHREAD_ENABLED) "openthread/src/core/utils/child_supervision.cpp") endif() + if(CONFIG_OPENTHREAD_RADIO_NATIVE) + list(APPEND exclude_srcs + "port/esp_openthread_radio_uart.cpp" + "port/esp_uart_spinel_interface.cpp") + elseif(CONFIG_OPENTHREAD_RADIO_SPINEL_UART) + list(APPEND exclude_srcs + "port/esp_openthread_radio.c") + endif() + if(CONFIG_OPENTHREAD_BORDER_ROUTER) list(APPEND src_dirs "openthread/src/core/border_router") @@ -148,7 +157,7 @@ idf_component_register(SRC_DIRS "${src_dirs}" EXCLUDE_SRCS "${exclude_srcs}" INCLUDE_DIRS "${public_include_dirs}" PRIV_INCLUDE_DIRS "${private_include_dirs}" - REQUIRES esp_event mbedtls ieee802154 console lwip) + REQUIRES esp_event mbedtls ieee802154 console lwip esp_timer esp_netif esp_partition spi_flash driver) if(CONFIG_OPENTHREAD_ENABLED) if(CONFIG_OPENTHREAD_RADIO) @@ -168,64 +177,20 @@ if(CONFIG_OPENTHREAD_ENABLED) "PACKAGE_VERSION=\"${IDF_VERSION_FOR_OPENTHREAD_PACKAGE}-${OPENTHREAD_VERSION}\"" "OPENTHREAD_BUILD_DATETIME=\"${OT_BUILD_TIMESTAMP}\"" ) + if(CONFIG_OPENTHREAD_RADIO) file(WRITE ${CMAKE_BINARY_DIR}/rcp_version ${OT_FULL_VERSION_STRING}) endif() - if($ENV{OPENTHREAD_ESP_LIB_FROM_INTERNAL_SRC}) - idf_component_get_property(openthread_port_lib openthread_port COMPONENT_LIB) - idf_component_get_property(lwip_lib lwip COMPONENT_LIB) - idf_component_get_property(esp_netif_lib esp_netif COMPONENT_LIB) - idf_component_get_property(esp_system_lib esp_system COMPONENT_LIB) - target_link_libraries(${COMPONENT_LIB} PUBLIC - $ - $) - target_link_libraries(${COMPONENT_LIB} PUBLIC - $ - $ - $ - $ - $) - if(CONFIG_OPENTHREAD_BORDER_ROUTER) + if(CONFIG_OPENTHREAD_BORDER_ROUTER) + if($ENV{OPENTHREAD_ESP_BR_LIB_FROM_INTERNAL_SRC}) idf_component_get_property(openthread_br_lib openthread_br COMPONENT_LIB) target_link_libraries(${COMPONENT_LIB} PUBLIC $) - endif() - - else() - if(IDF_TARGET STREQUAL "esp32h4") - if(CONFIG_IDF_TARGET_ESP32H4_BETA_VERSION_1) - add_prebuilt_library(openthread_port - "${CMAKE_CURRENT_SOURCE_DIR}/lib/${idf_target}/rev1/libopenthread_port.a" - REQUIRES openthread) - endif() - if(CONFIG_IDF_TARGET_ESP32H4_BETA_VERSION_2) - add_prebuilt_library(openthread_port - "${CMAKE_CURRENT_SOURCE_DIR}/lib/${idf_target}/rev2/libopenthread_port.a" - REQUIRES openthread) - endif() - elseif(IDF_TARGET STREQUAL "esp32c6") - if(CONFIG_OPENTHREAD_BORDER_ROUTER) - add_prebuilt_library(openthread_port - "${CMAKE_CURRENT_SOURCE_DIR}/lib/${idf_target}/br/libopenthread_port.a" - REQUIRES openthread) - else() - add_prebuilt_library(openthread_port - "${CMAKE_CURRENT_SOURCE_DIR}/lib/${idf_target}/cli/libopenthread_port.a" - REQUIRES openthread) - endif() else() - add_prebuilt_library(openthread_port "${CMAKE_CURRENT_SOURCE_DIR}/lib/${idf_target}/libopenthread_port.a" - REQUIRES openthread) - endif() - add_prebuilt_library(openthread_br "${CMAKE_CURRENT_SOURCE_DIR}/lib/${idf_target}/libopenthread_br.a" - REQUIRES openthread) - - target_link_libraries(${COMPONENT_LIB} INTERFACE openthread_port) - - if(CONFIG_OPENTHREAD_BORDER_ROUTER) + add_prebuilt_library(openthread_br "${CMAKE_CURRENT_SOURCE_DIR}/lib/${idf_target}/libopenthread_br.a" + REQUIRES openthread) target_link_libraries(${COMPONENT_LIB} INTERFACE openthread_br) endif() - endif() endif() diff --git a/components/openthread/include/esp_openthread_lock.h b/components/openthread/include/esp_openthread_lock.h index 61c74ad0ed..c0c70f1393 100644 --- a/components/openthread/include/esp_openthread_lock.h +++ b/components/openthread/include/esp_openthread_lock.h @@ -58,17 +58,19 @@ void esp_openthread_lock_release(void); * * @note In OpenThread API context, it waits for some actions to be done in other tasks (like lwip), * after task switching, it needs to call OpenThread API again. Normally it's not allowed, - * since the previous OpenThread API lock is not released yet. This task_switching lock allows + * since the previous OpenThread API lock is not released yet. This task_switching_lock allows * the OpenThread API can be called in this case. * - * @note Please use esp_openthread_lock_acquire() for normal cases. + * @note Please use @ref esp_openthread_lock_acquire() for normal cases. + * + * @param[in] block_ticks The maxinum number of RTOS ticks to wait for the lock. * * @return * - True on lock acquired * - False on failing to acquire the lock with the timeout. * */ -bool esp_openthread_task_switching_lock_acquire(void); +bool esp_openthread_task_switching_lock_acquire(TickType_t block_ticks); /** * @brief This function releases the OpenThread API task switching lock. diff --git a/components/openthread/port/esp_openthread.cpp b/components/openthread/port/esp_openthread.cpp new file mode 100644 index 0000000000..c4b1f00405 --- /dev/null +++ b/components/openthread/port/esp_openthread.cpp @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread.h" +#include "esp_check.h" +#include "esp_openthread_border_router.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_lock.h" +#include "esp_openthread_platform.h" +#include "esp_openthread_task_queue.h" +#include "esp_openthread_types.h" +#include "freertos/FreeRTOS.h" +#include "lwip/dns.h" +#include "openthread/instance.h" +#include "openthread/netdata.h" +#include "openthread/tasklet.h" + +esp_err_t esp_openthread_init(const esp_openthread_platform_config_t *config) +{ + ESP_RETURN_ON_ERROR(esp_openthread_platform_init(config), OT_PLAT_LOG_TAG, + "Failed to initialize OpenThread platform driver"); + esp_openthread_lock_acquire(portMAX_DELAY); + ESP_RETURN_ON_FALSE(otInstanceInitSingle() != NULL, ESP_FAIL, OT_PLAT_LOG_TAG, + "Failed to initialize OpenThread instance"); + esp_openthread_lock_release(); + + return ESP_OK; +} + +esp_err_t esp_openthread_launch_mainloop(void) +{ + esp_openthread_mainloop_context_t mainloop; + otInstance *instance = esp_openthread_get_instance(); + esp_err_t error = ESP_OK; + + while (true) { + FD_ZERO(&mainloop.read_fds); + FD_ZERO(&mainloop.write_fds); + FD_ZERO(&mainloop.error_fds); + + mainloop.max_fd = -1; + mainloop.timeout.tv_sec = 10; + mainloop.timeout.tv_usec = 0; + + esp_openthread_lock_acquire(portMAX_DELAY); + esp_openthread_platform_update(&mainloop); + if (otTaskletsArePending(instance)) { + mainloop.timeout.tv_sec = 0; + mainloop.timeout.tv_usec = 0; + } + esp_openthread_lock_release(); + + if (select(mainloop.max_fd + 1, &mainloop.read_fds, &mainloop.write_fds, &mainloop.error_fds, + &mainloop.timeout) >= 0) { + esp_openthread_lock_acquire(portMAX_DELAY); + error = esp_openthread_platform_process(instance, &mainloop); + while (otTaskletsArePending(instance)) { + otTaskletsProcess(instance); + } + esp_openthread_lock_release(); + if (error != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "esp_openthread_platform_process failed"); + break; + } + } else { + error = ESP_FAIL; + ESP_LOGE(OT_PLAT_LOG_TAG, "OpenThread system polling failed"); + break; + } + } + return error; +} + +esp_err_t esp_openthread_deinit(void) +{ + otInstanceFinalize(esp_openthread_get_instance()); + return esp_openthread_platform_deinit(); +} + +static void stub_task(void *context) +{ + // this is a empty function used for ot-task signal pending +} + +void otTaskletsSignalPending(otInstance *aInstance) +{ + esp_openthread_task_queue_post(stub_task, NULL); +} diff --git a/components/openthread/port/esp_openthread_alarm.c b/components/openthread/port/esp_openthread_alarm.c new file mode 100644 index 0000000000..b04d2a059b --- /dev/null +++ b/components/openthread/port/esp_openthread_alarm.c @@ -0,0 +1,149 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_alarm.h" + +#include +#include +#include + +#include "esp_log.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_platform.h" +#include "esp_timer.h" +#include "openthread-core-config.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "openthread/platform/alarm-micro.h" +#include "openthread/platform/alarm-milli.h" +#include "openthread/platform/diag.h" +#include "openthread/platform/radio.h" +#include "openthread/platform/time.h" + +static uint64_t s_alarm_ms_t0 = 0; +static uint64_t s_alarm_ms_dt = 0; +static bool s_is_ms_running = false; +static uint64_t s_alarm_us_t0 = 0; +static uint64_t s_alarm_us_dt = 0; +static bool s_is_us_running = false; +static const char *alarm_workflow = "alarm"; + +uint64_t otPlatTimeGet(void) +{ + struct timeval tv_now; + + int err = gettimeofday(&tv_now, NULL); + assert(err == 0); + + return (uint64_t)tv_now.tv_sec * US_PER_S + tv_now.tv_usec; +} + +void otPlatAlarmMilliStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) +{ + OT_UNUSED_VARIABLE(aInstance); + + s_alarm_ms_t0 = aT0; + s_alarm_ms_dt = aDt; + s_is_ms_running = true; + + ESP_LOGD(OT_PLAT_LOG_TAG, "Millisecond timer alarm start running, t0=%llu, dt=%llu", s_alarm_ms_t0, s_alarm_ms_dt); +} + +void otPlatAlarmMilliStop(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + s_is_ms_running = false; +} + +uint32_t otPlatAlarmMilliGetNow(void) +{ + return esp_timer_get_time() / US_PER_MS; +} + +void otPlatAlarmMicroStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) +{ + OT_UNUSED_VARIABLE(aInstance); + + s_alarm_us_t0 = aT0; + s_alarm_us_dt = aDt; + s_is_us_running = true; + + ESP_LOGD(OT_PLAT_LOG_TAG, "Microsecond timer alarm start running, t0=%llu, dt=%llu", s_alarm_us_t0, s_alarm_us_dt); +} + +void otPlatAlarmMicroStop(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + s_is_us_running = false; +} + +uint32_t otPlatAlarmMicroGetNow(void) +{ + return esp_timer_get_time(); +} + +esp_err_t esp_openthread_alarm_init(void) +{ + return esp_openthread_platform_workflow_register(&esp_openthread_alarm_update, &esp_openthread_alarm_process, + alarm_workflow); +} + +void esp_openthread_alarm_deinit(void) +{ + esp_openthread_platform_workflow_unregister(alarm_workflow); +} + +void esp_openthread_alarm_update(esp_openthread_mainloop_context_t *mainloop) +{ + struct timeval *timeout = &mainloop->timeout; + uint64_t now = esp_timer_get_time(); + int64_t remain_min_time_us = INT64_MAX; + int64_t remaining_us = 0; + if (s_is_ms_running) { + remaining_us = (s_alarm_ms_dt + s_alarm_ms_t0) * US_PER_MS - now; + if (remain_min_time_us > remaining_us) { + remain_min_time_us = remaining_us; + } + } + if (s_is_us_running) { + remaining_us = s_alarm_us_dt + s_alarm_us_t0 - now; + if (remain_min_time_us > remaining_us) { + remain_min_time_us = remaining_us; + } + } + if (remain_min_time_us > 0) { + timeout->tv_sec = remain_min_time_us / US_PER_S; + timeout->tv_usec = remain_min_time_us % US_PER_S; + } else { + timeout->tv_sec = 0; + timeout->tv_usec = 0; + } +} + +esp_err_t esp_openthread_alarm_process(otInstance *aInstance, const esp_openthread_mainloop_context_t *mainloop) +{ + if (s_is_ms_running && s_alarm_ms_t0 + s_alarm_ms_dt <= otPlatAlarmMilliGetNow()) { + s_is_ms_running = false; + +#if OPENTHREAD_CONFIG_DIAG_ENABLE + if (otPlatDiagModeGet()) { + otPlatDiagAlarmFired(aInstance); + } else +#endif + { + otPlatAlarmMilliFired(aInstance); + } + + ESP_LOGD(OT_PLAT_LOG_TAG, "Millisecond timer alarm fired"); + } + if (s_is_us_running && s_alarm_us_t0 + s_alarm_us_dt <= esp_timer_get_time()) { + s_is_us_running = false; + otPlatAlarmMicroFired(aInstance); + ESP_LOGD(OT_PLAT_LOG_TAG, "Microsecond timer alarm fired"); + } + return ESP_OK; +} diff --git a/components/openthread/port/esp_openthread_cli.c b/components/openthread/port/esp_openthread_cli.c new file mode 100644 index 0000000000..947bf892b9 --- /dev/null +++ b/components/openthread/port/esp_openthread_cli.c @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_cli.h" + +#include +#include + +#include "openthread/cli.h" + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_openthread.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_task_queue.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "linenoise/linenoise.h" + +#define OT_CLI_MAX_LINE_LENGTH 256 + +static TaskHandle_t s_cli_task; + +static int cli_output_callback(void *context, const char *format, va_list args) +{ + char prompt_check[3]; + int ret = 0; + + vsnprintf(prompt_check, sizeof(prompt_check), format, args); + if (!strncmp(prompt_check, "> ", sizeof(prompt_check)) && s_cli_task) { + xTaskNotifyGive(s_cli_task); + } else { + ret = vprintf(format, args); + } + return ret; +} + +void esp_openthread_cli_init(void) +{ + otCliInit(esp_openthread_get_instance(), cli_output_callback, NULL); +} + +void line_handle_task(void *context) +{ + char *line = (char *)context; + + otCliInputLine(line); + free(line); +} + +esp_err_t esp_openthread_cli_input(const char *line) +{ + char *line_copy = strdup(line); + + ESP_RETURN_ON_FALSE(line_copy != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "Failed to copy OpenThread CLI line input"); + + return esp_openthread_task_queue_post(line_handle_task, line_copy); +} + +static void ot_cli_loop(void *context) +{ + const char *prompt = "> "; + + linenoiseSetMultiLine(true); + linenoiseHistorySetMaxLen(100); + linenoiseSetMaxLineLen(OT_CLI_MAX_LINE_LENGTH); + linenoiseAllowEmpty(false); + + if (linenoiseProbe()) { + linenoiseSetDumbMode(1); + } + + while (true) { + char *line = linenoise(prompt); + if (line && strnlen(line, OT_CLI_MAX_LINE_LENGTH)) { + printf("\r\n"); + esp_openthread_cli_input(line); + linenoiseHistoryAdd(line); + xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); + } + linenoiseFree(line); + } +} + +void esp_openthread_cli_create_task() +{ + xTaskCreate(ot_cli_loop, "ot_cli", 4096, xTaskGetCurrentTaskHandle(), 4, &s_cli_task); + + return; +} diff --git a/components/openthread/src/esp_openthread_dns64.c b/components/openthread/port/esp_openthread_dns64.c similarity index 100% rename from components/openthread/src/esp_openthread_dns64.c rename to components/openthread/port/esp_openthread_dns64.c diff --git a/components/openthread/port/esp_openthread_flash.c b/components/openthread/port/esp_openthread_flash.c new file mode 100644 index 0000000000..e9e79bd030 --- /dev/null +++ b/components/openthread/port/esp_openthread_flash.c @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_flash.h" +#include "esp_partition.h" +#include "openthread/instance.h" +#include "openthread/platform/flash.h" +#include "openthread/platform/settings.h" + +#define ESP_OT_FLASH_PAGE_NUM 2 +#define ESP_OT_FLASH_PAGE_SIZE 4096 + +static const esp_partition_t *s_ot_partition = NULL; + +void esp_openthread_flash_set_partition(const esp_partition_t *partition) +{ + s_ot_partition = partition; +} + +void otPlatFlashInit(otInstance *instance) +{ + assert(s_ot_partition != NULL); + assert(s_ot_partition->size >= otPlatFlashGetSwapSize(instance)); +} + +uint32_t otPlatFlashGetSwapSize(otInstance *instance) +{ + return ESP_OT_FLASH_PAGE_SIZE; +} + +void otPlatFlashErase(otInstance *instance, uint8_t index) +{ + uint32_t address = ESP_OT_FLASH_PAGE_SIZE * (index != 0); + uint32_t size = ESP_OT_FLASH_PAGE_SIZE; + esp_err_t err = ESP_OK; + + err = esp_partition_erase_range(s_ot_partition, address, size); + + assert(err == ESP_OK); +} + +void otPlatFlashRead(otInstance *instance, uint8_t index, uint32_t offset, void *data, uint32_t size) +{ + esp_err_t err = ESP_OK; + + offset += ESP_OT_FLASH_PAGE_SIZE * (index != 0); + + err = esp_partition_read(s_ot_partition, offset, data, size); + + assert(err == ESP_OK); +} + +void otPlatFlashWrite(otInstance *instance, uint8_t index, uint32_t offset, const void *data, uint32_t size) +{ + esp_err_t err = ESP_OK; + + offset += ESP_OT_FLASH_PAGE_SIZE * (index != 0); + + err = esp_partition_write(s_ot_partition, offset, data, size); + + assert(err == ESP_OK); +} diff --git a/components/openthread/port/esp_openthread_lock.c b/components/openthread/port/esp_openthread_lock.c new file mode 100644 index 0000000000..6994d62115 --- /dev/null +++ b/components/openthread/port/esp_openthread_lock.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_lock.h" + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +static SemaphoreHandle_t s_openthread_task_mutex = NULL; +static SemaphoreHandle_t s_openthread_mutex = NULL; + +bool esp_openthread_lock_acquire(TickType_t block_ticks) +{ + BaseType_t ret = xSemaphoreTakeRecursive(s_openthread_mutex, block_ticks) && + xSemaphoreTakeRecursive(s_openthread_task_mutex, block_ticks); + return (ret == pdTRUE); +} + +void esp_openthread_lock_release(void) +{ + xSemaphoreGiveRecursive(s_openthread_task_mutex); + xSemaphoreGiveRecursive(s_openthread_mutex); +} + +bool esp_openthread_task_switching_lock_acquire(TickType_t block_ticks) +{ + BaseType_t ret = xSemaphoreTakeRecursive(s_openthread_task_mutex, block_ticks); + return (ret == pdTRUE); +} + +void esp_openthread_task_switching_lock_release(void) +{ + xSemaphoreGiveRecursive(s_openthread_task_mutex); +} + +esp_err_t esp_openthread_lock_init(void) +{ + if (s_openthread_mutex != NULL || s_openthread_task_mutex != NULL) { + return ESP_ERR_INVALID_STATE; + } + s_openthread_mutex = xSemaphoreCreateRecursiveMutex(); + s_openthread_task_mutex = xSemaphoreCreateRecursiveMutex(); + if (s_openthread_mutex == NULL || s_openthread_task_mutex == NULL) { + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +void esp_openthread_lock_deinit(void) +{ + if (s_openthread_mutex) { + vSemaphoreDelete(s_openthread_mutex); + s_openthread_mutex = NULL; + } + if (s_openthread_task_mutex) { + vSemaphoreDelete(s_openthread_task_mutex); + s_openthread_task_mutex = NULL; + } +} diff --git a/components/openthread/port/esp_openthread_logging.c b/components/openthread/port/esp_openthread_logging.c new file mode 100644 index 0000000000..8eef6bbcfb --- /dev/null +++ b/components/openthread/port/esp_openthread_logging.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_openthread.h" + +#include + +#include "esp_log.h" +#include "esp_openthread_common_macro.h" +#include "openthread/platform/logging.h" + +/** + * The default platform logging tag. + * + */ +#if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED) || \ + (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_NCP_SPINEL) +OT_TOOL_WEAK void otPlatLog(otLogLevel log_level, otLogRegion log_region, const char *format, ...) +{ + va_list args; + + va_start(args, format); + + switch (log_level) { + case OT_LOG_LEVEL_CRIT: + if (LOG_LOCAL_LEVEL >= ESP_LOG_ERROR) { + esp_log_write(ESP_LOG_ERROR, OT_PLAT_LOG_TAG, LOG_COLOR_E "E(%lu) %s:", esp_log_timestamp(), + OT_PLAT_LOG_TAG); + esp_log_writev(ESP_LOG_ERROR, OT_PLAT_LOG_TAG, format, args); + esp_log_write(ESP_LOG_ERROR, OT_PLAT_LOG_TAG, LOG_RESET_COLOR "\n"); + } + break; + case OT_LOG_LEVEL_WARN: + if (LOG_LOCAL_LEVEL >= ESP_LOG_WARN) { + esp_log_write(ESP_LOG_WARN, OT_PLAT_LOG_TAG, LOG_COLOR_W "W(%lu) %s:", esp_log_timestamp(), + OT_PLAT_LOG_TAG); + esp_log_writev(ESP_LOG_WARN, OT_PLAT_LOG_TAG, format, args); + esp_log_write(ESP_LOG_WARN, OT_PLAT_LOG_TAG, LOG_RESET_COLOR "\n"); + } + break; + case OT_LOG_LEVEL_NOTE: + case OT_LOG_LEVEL_INFO: + if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) { + esp_log_write(ESP_LOG_INFO, OT_PLAT_LOG_TAG, LOG_COLOR_I "I(%lu) %s:", esp_log_timestamp(), + OT_PLAT_LOG_TAG); + esp_log_writev(ESP_LOG_INFO, OT_PLAT_LOG_TAG, format, args); + esp_log_write(ESP_LOG_INFO, OT_PLAT_LOG_TAG, LOG_RESET_COLOR "\n"); + } + break; + default: + if (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) { + esp_log_write(ESP_LOG_DEBUG, OT_PLAT_LOG_TAG, LOG_COLOR_D "D(%lu) %s:", esp_log_timestamp(), + OT_PLAT_LOG_TAG); + esp_log_writev(ESP_LOG_DEBUG, OT_PLAT_LOG_TAG, format, args); + esp_log_write(ESP_LOG_DEBUG, OT_PLAT_LOG_TAG, LOG_RESET_COLOR "\n"); + } + break; + } + va_end(args); +} +#endif diff --git a/components/openthread/port/esp_openthread_lwip_netif.c b/components/openthread/port/esp_openthread_lwip_netif.c new file mode 100644 index 0000000000..db90092f4b --- /dev/null +++ b/components/openthread/port/esp_openthread_lwip_netif.c @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "esp_netif.h" +#include "esp_netif_net_stack.h" +#include "esp_openthread.h" +#include "esp_openthread_lock.h" +#include "freertos/FreeRTOS.h" +#include "lwip/err.h" +#include "lwip/esp_netif_net_stack.h" +#include "lwip/netif.h" +#include "lwip/pbuf.h" +#include "openthread/error.h" +#include "openthread/ip6.h" +#include "openthread/link.h" +#include "openthread/message.h" + +#define OPENTHREAD_IP6_MTU 1280 + +static err_t openthread_netif_init(struct netif *netif); +static void openthread_netif_input(void *h, void *buffer, size_t len, void *eb); + +const struct esp_netif_netstack_config g_esp_netif_netstack_default_openthread = { + .lwip = { + .init_fn = openthread_netif_init, + .input_fn = openthread_netif_input, + }}; + +static err_t openthread_output_ip6(struct netif *netif, struct pbuf *p, const struct ip6_addr *peer_addr) +{ + struct pbuf *q = p; + esp_netif_t *esp_netif = esp_netif_get_handle_from_netif_impl(netif); + esp_err_t ret = ESP_FAIL; + if (!esp_netif) { + LWIP_DEBUGF(NETIF_DEBUG, ("corresponding esp-netif is NULL: netif=%p pbuf=%p len=%d\n", netif, p, p->len)); + return ERR_IF; + } + + if (q->next == NULL) { + ret = esp_netif_transmit(esp_netif, q->payload, q->len); + } else { + LWIP_DEBUGF(PBUF_DEBUG, ("low_level_output: pbuf is a list, application may has bug")); + q = pbuf_alloc(PBUF_RAW_TX, p->tot_len, PBUF_RAM); + if (q != NULL) { + pbuf_copy(q, p); + } else { + return ERR_MEM; + } + ret = esp_netif_transmit(esp_netif, q->payload, q->len); + /* content in payload has been copied to OpenThread queue, it's safe to free pbuf now */ + pbuf_free(q); + } + /* Check error */ + switch (ret) { + case ESP_ERR_NO_MEM: + return ERR_MEM; + + case ESP_OK: + return ERR_OK; + + default: + return ERR_ABRT; + } +} + +static void openthread_netif_input(void *h, void *buffer, size_t len, void *eb) +{ + struct netif *netif = h; + struct pbuf *p; + otMessage *message = (otMessage *)buffer; + + if (unlikely(buffer == NULL || !netif_is_up(netif))) { + return; + } + + /* Allocate LINK buffer in case it's forwarded to WiFi/ETH */ + p = pbuf_alloc(PBUF_LINK, len, PBUF_POOL); + if (p == NULL) { + LWIP_DEBUGF(NETIF_DEBUG, ("Failed to allocate input pbuf for OpenThread netif\n")); + return; + } + + if (unlikely(otMessageRead(message, 0, p->payload, len) != otMessageGetLength(message))) { + LWIP_DEBUGF(NETIF_DEBUG, ("Failed to read OpenThread message\n")); + pbuf_free(p); + return; + } + + /* full packet send to tcpip_thread to process */ + if (unlikely(netif->input(p, netif) != ERR_OK)) { + LWIP_DEBUGF(NETIF_DEBUG, ("openthread_netif_input: IP input error\n")); + pbuf_free(p); + } + /* the pbuf will be free in upper layer, eg: tcpip_input */ +} + +static err_t openthread_netif_multicast_handler(struct netif *netif, const ip6_addr_t *group, + enum netif_mac_filter_action action) +{ + otError error = OT_ERROR_NONE; + otIp6Address multicast_addr; + + memcpy(multicast_addr.mFields.m8, group->addr, sizeof(group->addr)); + esp_openthread_task_switching_lock_acquire(portMAX_DELAY); + if (action == NETIF_ADD_MAC_FILTER) { + error = otIp6SubscribeMulticastAddress(esp_openthread_get_instance(), &multicast_addr); + } else { + error = otIp6UnsubscribeMulticastAddress(esp_openthread_get_instance(), &multicast_addr); + } + esp_openthread_task_switching_lock_release(); + switch (error) { + case OT_ERROR_NONE: + case OT_ERROR_ALREADY: + return ERR_OK; + case OT_ERROR_NO_BUFS: + return ERR_MEM; + case OT_ERROR_INVALID_ARGS: + return ERR_ARG; + default: + return ERR_IF; + } +} + +static err_t openthread_netif_init(struct netif *netif) +{ + netif->name[0] = 'o'; + netif->name[1] = 't'; + netif->hwaddr_len = sizeof(otExtAddress); + memset(netif->hwaddr, 0, sizeof(netif->hwaddr)); + netif->mtu = OPENTHREAD_IP6_MTU; + netif->flags = NETIF_FLAG_BROADCAST; + netif->output = NULL; + netif->output_ip6 = openthread_output_ip6; + netif->mld_mac_filter = openthread_netif_multicast_handler; + netif_set_link_up(netif); + + return ERR_OK; +} diff --git a/components/openthread/port/esp_openthread_memory.c b/components/openthread/port/esp_openthread_memory.c new file mode 100644 index 0000000000..661e62c70e --- /dev/null +++ b/components/openthread/port/esp_openthread_memory.c @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread.h" + +#include "openthread/platform/memory.h" + +#include + +void *otPlatCAlloc(size_t num, size_t size) +{ + return calloc(num, size); +} + +void otPlatFree(void *ptr) +{ + free(ptr); +} diff --git a/components/openthread/port/esp_openthread_misc.c b/components/openthread/port/esp_openthread_misc.c new file mode 100644 index 0000000000..95ac612a6a --- /dev/null +++ b/components/openthread/port/esp_openthread_misc.c @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_openthread.h" +#include "esp_openthread_common_macro.h" +#include "esp_system.h" +#include "common/logging.hpp" +#include "openthread/platform/misc.h" + +static otPlatMcuPowerState s_mcu_power_state = OT_PLAT_MCU_POWER_STATE_ON; + +void otPlatReset(otInstance *aInstance) +{ + esp_restart(); +} + +otPlatResetReason otPlatGetResetReason(otInstance *instance) +{ + switch (esp_reset_reason()) { + case ESP_RST_UNKNOWN: + return OT_PLAT_RESET_REASON_UNKNOWN; + case ESP_RST_POWERON: + return OT_PLAT_RESET_REASON_POWER_ON; + case ESP_RST_EXT: + return OT_PLAT_RESET_REASON_EXTERNAL; + case ESP_RST_SW: + return OT_PLAT_RESET_REASON_SOFTWARE; + case ESP_RST_PANIC: + return OT_PLAT_RESET_REASON_FAULT; + case ESP_RST_INT_WDT: + return OT_PLAT_RESET_REASON_WATCHDOG; + case ESP_RST_TASK_WDT: + return OT_PLAT_RESET_REASON_WATCHDOG; + case ESP_RST_WDT: + return OT_PLAT_RESET_REASON_WATCHDOG; + default: + return OT_PLAT_RESET_REASON_OTHER; + } +} + +void otPlatWakeHost(void) +{ + // Not Implemented. +} + +otError otPlatSetMcuPowerState(otInstance *instance, otPlatMcuPowerState state) +{ + otError error = OT_ERROR_NONE; + + OT_UNUSED_VARIABLE(instance); + + switch (state) { + case OT_PLAT_MCU_POWER_STATE_ON: + case OT_PLAT_MCU_POWER_STATE_LOW_POWER: + s_mcu_power_state = state; + break; + + default: + error = OT_ERROR_FAILED; + break; + } + + return error; +} + +otPlatMcuPowerState otPlatGetMcuPowerState(otInstance *instance) +{ + OT_UNUSED_VARIABLE(instance); + return s_mcu_power_state; +} + +void otPlatAssertFail(const char *filename, int line) +{ + ESP_LOGE(OT_PLAT_LOG_TAG, "Assert failed at %s:%d", filename, line); + assert(false); +} diff --git a/components/openthread/port/esp_openthread_ncp.cpp b/components/openthread/port/esp_openthread_ncp.cpp new file mode 100644 index 0000000000..62d6ad5d2e --- /dev/null +++ b/components/openthread/port/esp_openthread_ncp.cpp @@ -0,0 +1,155 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_ncp.h" + +#include "esp_app_format.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_openthread.h" +#include "esp_openthread_common_macro.h" +#include "esp_ota_ops.h" +#include "esp_system.h" +#include "common/instance.hpp" +#include "common/new.hpp" +#include "common/non_copyable.hpp" +#include "lib/spinel/spinel.h" +#include "ncp/ncp_base.hpp" +#include "ncp/ncp_hdlc.hpp" +#include "openthread/error.h" +#include "openthread/platform/toolchain.h" +#include "utils/uart.h" + +static bool s_ota_triggered = false; +static bool s_header_checked = false; +static esp_ota_handle_t s_update_handle; +static const esp_partition_t *s_update_partition = NULL; + +static otError start_ota(const uint8_t *header, uint16_t header_size) +{ + esp_app_desc_t new_app_info; + esp_app_desc_t running_app_info; + esp_app_desc_t invalid_app_info; + const esp_partition_t *running_partition = esp_ota_get_running_partition(); + const esp_partition_t *invalid_partition = esp_ota_get_last_invalid_partition(); + + s_update_partition = esp_ota_get_next_update_partition(NULL); + ESP_RETURN_ON_FALSE(s_update_partition != NULL, OT_ERROR_FAILED, OT_PLAT_LOG_TAG, "Failed to get update partition"); + ESP_RETURN_ON_FALSE(header_size >= + sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t), + OT_ERROR_INVALID_ARGS, OT_PLAT_LOG_TAG, "Image header size too small"); + memcpy(&new_app_info, &header[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], + sizeof(esp_app_desc_t)); + + ESP_RETURN_ON_FALSE(esp_ota_get_partition_description(running_partition, &running_app_info) == ESP_OK, + OT_ERROR_FAILED, OT_PLAT_LOG_TAG, "Failed to get running app description"); + ESP_LOGD(OT_PLAT_LOG_TAG, "Running firmware version: %s", running_app_info.version); + ESP_LOGD(OT_PLAT_LOG_TAG, "New firmware version: %s", new_app_info.version); + + if (esp_ota_get_partition_description(invalid_partition, &invalid_app_info) == ESP_OK) { + ESP_RETURN_ON_FALSE(strncmp(invalid_app_info.version, new_app_info.version, sizeof(invalid_app_info.version)), + OT_ERROR_FAILED, OT_PLAT_LOG_TAG, "The updated firmware has been rolled back, reject..."); + } + ESP_RETURN_ON_FALSE(esp_ota_begin(s_update_partition, OTA_WITH_SEQUENTIAL_WRITES, &s_update_handle) == ESP_OK, + OT_ERROR_FAILED, OT_PLAT_LOG_TAG, "Failed to start OTA"); + s_header_checked = true; + return OT_ERROR_NONE; +} + +namespace ot { +namespace Ncp { + +otError NcpBase::VendorCommandHandler(uint8_t header, unsigned int command) +{ + return OT_ERROR_NOT_FOUND; +} + +otError NcpBase::VendorGetPropertyHandler(spinel_prop_key_t key) +{ + return OT_ERROR_NOT_FOUND; +} + +void NcpBase::VendorHandleFrameRemovedFromNcpBuffer(Spinel::Buffer::FrameTag tag) +{ +} + +otError NcpBase::VendorSetPropertyHandler(spinel_prop_key_t key) +{ + otError error = OT_ERROR_NONE; + const uint8_t *data = NULL; + uint16_t data_size = 0; + + switch (key) { + case SPINEL_PROP_VENDOR_ESP_OTA_TRIGGER: + ESP_LOGD(OT_PLAT_LOG_TAG, "Trigger OTA"); + if (error != OT_ERROR_NONE) { + break; + } + s_ota_triggered = true; + break; + case SPINEL_PROP_VENDOR_ESP_OTA_DATA: + if (s_ota_triggered) { + error = mDecoder.ReadDataWithLen(data, data_size); + if (!s_header_checked) { + ESP_LOGD(OT_PLAT_LOG_TAG, "Start OTA"); + error = start_ota(data, data_size); + } + if (esp_ota_write(s_update_handle, data, data_size) != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "esp_ota_write failed"); + error = OT_ERROR_FAILED; + } + if (error != OT_ERROR_NONE) { + esp_ota_abort(s_update_handle); + s_ota_triggered = false; + } + } else { + error = OT_ERROR_INVALID_STATE; + } + break; + case SPINEL_PROP_VENDOR_ESP_OTA_FINISH: + ESP_LOGD(OT_PLAT_LOG_TAG, "Finish OTA"); + if (!s_ota_triggered || !s_header_checked) { + error = OT_ERROR_INVALID_STATE; + break; + } + if (esp_ota_set_boot_partition(s_update_partition) != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "esp_ota_set_boot_partition failed"); + error = OT_ERROR_FAILED; + break; + } + esp_restart(); + break; + default: + error = OT_ERROR_NOT_FOUND; + break; + } + + return error; +} + +} // namespace Ncp +} // namespace ot + +static OT_DEFINE_ALIGNED_VAR(s_ncp, sizeof(ot::Ncp::NcpHdlc), uint64_t); + +static int NcpSend(const uint8_t *aBuf, uint16_t aBufLength) +{ + IgnoreError(otPlatUartSend(aBuf, aBufLength)); + return aBufLength; +} + +extern "C" void otAppNcpInit(otInstance *instance) +{ + ot::Ncp::NcpHdlc *ncp = nullptr; + ot::Instance *ot_instance = static_cast(instance); + + IgnoreError(otPlatUartEnable()); + ncp = new (&s_ncp) ot::Ncp::NcpHdlc(ot_instance, NcpSend); + + if (ncp == nullptr || ncp != ot::Ncp::NcpBase::GetNcpInstance()) { + OT_ASSERT(false); + } +} diff --git a/components/openthread/port/esp_openthread_netif_glue.c b/components/openthread/port/esp_openthread_netif_glue.c new file mode 100644 index 0000000000..41cb7e975f --- /dev/null +++ b/components/openthread/port/esp_openthread_netif_glue.c @@ -0,0 +1,374 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_netif_glue.h" + +#include +#include +#include + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_openthread.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_lock.h" +#include "esp_openthread_netif_glue_priv.h" +#include "esp_openthread_platform.h" +#include "esp_openthread_types.h" +#include "esp_vfs_eventfd.h" +#include "sdkconfig.h" +#include "common/code_utils.hpp" +#include "config/link_quality.h" +#include "freertos/FreeRTOS.h" +#include "openthread/error.h" +#include "openthread/icmp6.h" +#include "openthread/instance.h" +#include "openthread/ip6.h" +#include "openthread/link.h" +#include "openthread/message.h" +#include "openthread/thread.h" + +typedef struct { + esp_netif_driver_base_t base; + int event_fd; +} esp_openthread_netif_glue_t; + +static esp_openthread_netif_glue_t s_openthread_netif_glue = { + .event_fd = -1, +}; + +ESP_EVENT_DEFINE_BASE(OPENTHREAD_EVENT); + +static QueueHandle_t s_packet_queue; +static esp_netif_t *s_openthread_netif; +static const char *netif_glue_workflow = "netif_glue"; +const esp_netif_inherent_config_t g_esp_netif_inherent_openthread_config = ESP_NETIF_INHERENT_DEFAULT_OPENTHREAD(); + +#define NETIF_OUTPUT_SIGNAL 1 + +static bool is_link_local_addr(const otIp6Address *address) +{ + return address->mFields.m32[0] == 0xfe800000 && address->mFields.m32[1] == 0; +} + +static bool is_mesh_local_addr(const otIp6Address *address) +{ + return memcmp(address->mFields.m8, otThreadGetMeshLocalPrefix(esp_openthread_get_instance())->m8, + sizeof(otThreadGetMeshLocalPrefix(esp_openthread_get_instance())->m8)) == 0; +} + +static bool is_addr_locator(const otIp6Address *address) +{ + return address->mFields.m32[2] == 0xff000000 && address->mFields.m16[6] == 0xfe; +} + +static bool is_openthread_internal_mesh_local_addr(const otIp6AddressInfo *address_info) +{ + return is_addr_locator(address_info->mAddress) && is_mesh_local_addr(address_info->mAddress); +} + +static esp_err_t notify_packets_pending(void) +{ + uint64_t signal = NETIF_OUTPUT_SIGNAL; + ssize_t ret = write(s_openthread_netif_glue.event_fd, &signal, sizeof(signal)); + if (ret != sizeof(signal)) { + ESP_LOGW(OT_PLAT_LOG_TAG, "Thread netif failed to notify eventfd"); + return ESP_FAIL; + } + return ESP_OK; +} + +void process_thread_address(const otIp6AddressInfo *address_info, bool is_added, void *context) +{ + bool is_multicast = address_info->mAddress->mFields.m8[0] == 0xff; + esp_ip6_addr_t addr; + + if (is_openthread_internal_mesh_local_addr(address_info)) { + return; + } + + memcpy(addr.addr, address_info->mAddress->mFields.m8, sizeof(addr.addr)); + if (is_added) { + if (is_multicast) { + if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_MULTICAST_GROUP_JOIN, &addr, sizeof(addr), 0) != + ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to post OpenThread join multicast group event"); + } + } else { + ip_event_add_ip6_t add_addr; + add_addr.addr = addr; + // if an address is not mesh local or link local, we set preferred for this address. + add_addr.preferred = + is_mesh_local_addr(address_info->mAddress) || is_link_local_addr(address_info->mAddress) ? 0 : 1; + if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_GOT_IP6, &add_addr, sizeof(add_addr), 0) != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to post OpenThread got ip6 address event"); + } + } + } else { + if (is_multicast) { + if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_MULTICAST_GROUP_LEAVE, &addr, sizeof(addr), 0) != + ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to post OpenThread leave multicast group event"); + } + } else { + if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_LOST_IP6, &addr, sizeof(addr), 0) != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to post OpenThread lost ip6 address event"); + } + } + } +} + +static void process_thread_receive(otMessage *message, void *context) +{ + esp_err_t error; + uint16_t length = otMessageGetLength(message); + + error = esp_netif_receive(s_openthread_netif_glue.base.netif, message, length, NULL); + + if (error != ESP_OK) { + ESP_LOGW(OT_PLAT_LOG_TAG, "process_thread_receive failed: %s", esp_err_to_name(error)); + } + otMessageFree(message); +} + +static esp_err_t process_thread_transmit(otInstance *instance) +{ + otMessage *msg = NULL; + esp_err_t error = ESP_OK; + uint64_t event; + + int ret = read(s_openthread_netif_glue.event_fd, &event, sizeof(event)); + assert(ret == sizeof(event)); + while (xQueueReceive(s_packet_queue, &msg, 0) == pdTRUE) { + if (msg) { + otError ot_error = otIp6Send(esp_openthread_get_instance(), msg); + if (ot_error != OT_ERROR_NONE && ot_error != OT_ERROR_DROP) { + ESP_LOGW(OT_PLAT_LOG_TAG, "ThreadNetif Failed to send OpenThread IP6 message: %s", + otThreadErrorToString(ot_error)); + } + if (ot_error == OT_ERROR_DROP) { + // OpenThread will intentionally drop some multicast and ICMPv6 packets + // which are not required for the Thread network. + ESP_LOGD(OT_PLAT_LOG_TAG, "OpenThread stack filtered netif packet"); + } + if (ot_error != OT_ERROR_NONE) { + break; + } + } + } + + if (uxQueueMessagesWaiting(s_packet_queue) > 0) { + error = notify_packets_pending(); + } + + return error; +} + +void esp_openthread_netif_glue_state_callback(otChangedFlags changed_flags, void *ctx) +{ + otInstance *instance = esp_openthread_get_instance(); + esp_err_t err = ESP_OK; + + if (s_packet_queue != NULL && (OT_CHANGED_THREAD_NETIF_STATE & changed_flags)) { + if (otLinkIsEnabled(instance)) { + ESP_LOGI(OT_PLAT_LOG_TAG, "netif up"); + if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_IF_UP, NULL, 0, 0) != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to post OpenThread if up event"); + } + } else { + ESP_LOGI(OT_PLAT_LOG_TAG, "netif down"); + if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_IF_DOWN, NULL, 0, 0) != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to post OpenThread if down event"); + } + } + } + + if (err != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to configure netif state"); + } +} + +static esp_err_t openthread_netif_transmit(void *handle, void *buffer, size_t len) +{ + esp_err_t error = ESP_OK; + otError ot_error = OT_ERROR_NONE; + + esp_openthread_task_switching_lock_acquire(portMAX_DELAY); + otMessage *message = otIp6NewMessage(esp_openthread_get_instance(), NULL); + if (message == NULL) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate OpenThread message"); + ExitNow(error = ESP_ERR_NO_MEM); + } + + ot_error = otMessageAppend(message, buffer, len); + if (ot_error != OT_ERROR_NONE) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to copy to OpenThread message: %s", otThreadErrorToString(ot_error)); + ExitNow(error = ESP_ERR_NO_MEM); + } + + if (xQueueSend(s_packet_queue, &message, 0) != pdTRUE) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to send to Thread netif: packet queue full"); + ExitNow(error = ESP_ERR_NO_MEM, ot_error = OT_ERROR_NO_BUFS); + } + VerifyOrExit(notify_packets_pending() == ESP_OK, error = ESP_FAIL); + +exit: + if (ot_error != OT_ERROR_NONE && message != NULL) { + otMessageFree(message); + } + esp_openthread_task_switching_lock_release(); + return error; +} + +static esp_err_t register_openthread_event_handlers(esp_netif_t *esp_netif) +{ + ESP_RETURN_ON_ERROR( + esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_START, esp_netif_action_start, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread start event register failed"); + ESP_RETURN_ON_ERROR( + esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_STOP, esp_netif_action_stop, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread stop event register failed"); + ESP_RETURN_ON_ERROR( + esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_IF_UP, esp_netif_action_connected, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread interface up event register failed"); + ESP_RETURN_ON_ERROR(esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_IF_DOWN, + esp_netif_action_disconnected, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread interface down event register failed"); + ESP_RETURN_ON_ERROR(esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_GOT_IP6, + esp_netif_action_add_ip6_address, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread interface got ip6 event register failed"); + ESP_RETURN_ON_ERROR(esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_LOST_IP6, + esp_netif_action_remove_ip6_address, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread interface remove ip6 event register failed"); + ESP_RETURN_ON_ERROR(esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_MULTICAST_GROUP_JOIN, + esp_netif_action_join_ip6_multicast_group, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread interface join ip6 multicast group event register failed"); + ESP_RETURN_ON_ERROR(esp_event_handler_register(OPENTHREAD_EVENT, OPENTHREAD_EVENT_MULTICAST_GROUP_LEAVE, + esp_netif_action_leave_ip6_multicast_group, esp_netif), + OT_PLAT_LOG_TAG, "OpenThread interface leave ip6 multicast group event register failed"); + return ESP_OK; +} + +static void unregister_openthread_event_handlers(void) +{ + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_START, esp_netif_action_start); + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_STOP, esp_netif_action_stop); + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_IF_UP, esp_netif_action_connected); + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_IF_DOWN, esp_netif_action_disconnected); + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_GOT_IP6, esp_netif_action_add_ip6_address); + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_LOST_IP6, esp_netif_action_remove_ip6_address); + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_MULTICAST_GROUP_JOIN, + esp_netif_action_join_ip6_multicast_group); + esp_event_handler_unregister(OPENTHREAD_EVENT, OPENTHREAD_EVENT_MULTICAST_GROUP_LEAVE, + esp_netif_action_leave_ip6_multicast_group); +} + +static esp_err_t openthread_netif_post_attach(esp_netif_t *esp_netif, void *args) +{ + esp_netif_driver_base_t *base = (esp_netif_driver_base_t *)args; + base->netif = esp_netif; + + // set driver related config to esp-netif + esp_netif_driver_ifconfig_t driver_ifconfig = { + .handle = &s_openthread_netif_glue, .transmit = openthread_netif_transmit, .driver_free_rx_buffer = NULL}; + + ESP_ERROR_CHECK(esp_netif_set_driver_config(esp_netif, &driver_ifconfig)); + + ESP_LOGI(OT_PLAT_LOG_TAG, "OpenThread attached to netif"); + esp_err_t error = register_openthread_event_handlers(esp_netif); + s_openthread_netif = esp_netif; + if (error == ESP_OK) { + error = esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_START, NULL, 0, 0); + } + + return error; +} + +void *esp_openthread_netif_glue_init(const esp_openthread_platform_config_t *config) +{ + otInstance *instance = esp_openthread_get_instance(); + esp_err_t error = ESP_OK; + + if (instance == NULL || s_packet_queue || s_openthread_netif_glue.event_fd >= 0) { + return NULL; + } + ESP_RETURN_ON_FALSE(otSetStateChangedCallback(instance, esp_openthread_netif_glue_state_callback, NULL) == + OT_ERROR_NONE, + NULL, OT_PLAT_LOG_TAG, "Failed to install netif glue state callback"); + + s_packet_queue = xQueueCreate(config->port_config.netif_queue_size, sizeof(otMessage *)); + if (s_packet_queue == NULL) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate Thread netif packet queue"); + ExitNow(error = ESP_ERR_NO_MEM); + } + + otIp6SetAddressCallback(instance, process_thread_address, instance); + otIp6SetReceiveCallback(instance, process_thread_receive, instance); + otIp6SetReceiveFilterEnabled(instance, true); + otIcmp6SetEchoMode(instance, OT_ICMP6_ECHO_HANDLER_DISABLED); + + s_openthread_netif_glue.event_fd = eventfd(0, 0); + if (s_openthread_netif_glue.event_fd < 0) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to create event fd for Thread netif"); + ExitNow(error = ESP_FAIL); + } + s_openthread_netif_glue.base.post_attach = openthread_netif_post_attach; + error = esp_openthread_platform_workflow_register(&esp_openthread_netif_glue_update, + &esp_openthread_netif_glue_process, netif_glue_workflow); +exit: + if (error != ESP_OK) { + return NULL; + } + + return &s_openthread_netif_glue.base; +} + +void esp_openthread_netif_glue_deinit(void) +{ + otInstance *instance = esp_openthread_get_instance(); + otIp6SetAddressCallback(instance, NULL, NULL); + otIp6SetReceiveCallback(instance, NULL, NULL); + if (s_packet_queue) { + vQueueDelete(s_packet_queue); + s_packet_queue = NULL; + } + if (s_openthread_netif_glue.event_fd >= 0) { + close(s_openthread_netif_glue.event_fd); + s_openthread_netif_glue.event_fd = -1; + } + if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_STOP, NULL, 0, 0) != ESP_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to stop OpenThread netif"); + } + s_openthread_netif = NULL; + unregister_openthread_event_handlers(); + esp_openthread_platform_workflow_unregister(netif_glue_workflow); +} + +void esp_openthread_netif_glue_update(esp_openthread_mainloop_context_t *mainloop) +{ + if (s_openthread_netif_glue.event_fd >= 0) { + FD_SET(s_openthread_netif_glue.event_fd, &mainloop->read_fds); + if (s_openthread_netif_glue.event_fd > mainloop->max_fd) { + mainloop->max_fd = s_openthread_netif_glue.event_fd; + } + } +} + +esp_err_t esp_openthread_netif_glue_process(otInstance *instance, const esp_openthread_mainloop_context_t *context) +{ + if (s_openthread_netif_glue.event_fd >= 0 && FD_ISSET(s_openthread_netif_glue.event_fd, &context->read_fds)) { + return process_thread_transmit(instance); + } + return ESP_OK; +} + +esp_netif_t *esp_openthread_get_netif(void) +{ + return s_openthread_netif; +} diff --git a/components/openthread/port/esp_openthread_platform.cpp b/components/openthread/port/esp_openthread_platform.cpp new file mode 100644 index 0000000000..f8d79269bc --- /dev/null +++ b/components/openthread/port/esp_openthread_platform.cpp @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_platform.h" + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_openthread_alarm.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_flash.h" +#include "esp_openthread_lock.h" +#include "esp_openthread_radio.h" +#include "esp_openthread_task_queue.h" +#include "esp_openthread_types.h" +#include "esp_openthread_uart.h" +#include "esp_partition.h" +#include "common/code_utils.hpp" +#include "common/logging.hpp" +#include "core/common/instance.hpp" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "openthread/cli.h" +#include "openthread/instance.h" +#include "openthread/tasklet.h" + +static esp_openthread_platform_config_t s_platform_config; +static bool s_openthread_platform_initialized = false; +static esp_openthread_platform_workflow_t *s_workflow_list = NULL; + +esp_err_t esp_openthread_platform_workflow_register(esp_openthread_update_func update_func, + esp_openthread_process_func process_func, const char *name) +{ + uint8_t name_len = strnlen(name, WORKFLOW_MAX_NAMELEN); + esp_openthread_platform_workflow_t *current_workflow = s_workflow_list; + esp_openthread_platform_workflow_t *before_workflow = NULL; + esp_openthread_platform_workflow_t *add_workflow = + static_cast(malloc(sizeof(esp_openthread_platform_workflow_t))); + ESP_RETURN_ON_FALSE(add_workflow != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, + "Failed to alloc memory for esp_openthread_workflow"); + strncpy(add_workflow->name, name, name_len); + add_workflow->update_func = update_func; + add_workflow->process_func = process_func; + add_workflow->next = NULL; + while (current_workflow) { + if (strncmp(current_workflow->name, name, name_len) == 0) { + free(add_workflow); + add_workflow = NULL; // handle has be added in the list. + break; + } else { + before_workflow = current_workflow; + current_workflow = current_workflow->next; + } + } + if (add_workflow) { + if (before_workflow) { + before_workflow->next = add_workflow; // Add handle to the list end + } else { + s_workflow_list = add_workflow; // No handle in the list + } + } + return ESP_OK; +} + +void esp_openthread_platform_workflow_unregister(const char *name) +{ + esp_openthread_platform_workflow_t *current_workflow = s_workflow_list; + esp_openthread_platform_workflow_t *before_workflow = NULL; + while (current_workflow) { + if (strncmp(current_workflow->name, name, strnlen(name, WORKFLOW_MAX_NAMELEN)) == 0) { + if (before_workflow) { + before_workflow->next = current_workflow->next; + } else { + s_workflow_list = current_workflow->next; + } + free(current_workflow); + break; + } + before_workflow = current_workflow; + current_workflow = current_workflow->next; + } +} + +esp_err_t esp_openthread_platform_init(const esp_openthread_platform_config_t *config) +{ + ESP_RETURN_ON_FALSE(config->radio_config.radio_mode == RADIO_MODE_NATIVE || + config->radio_config.radio_mode == RADIO_MODE_UART_RCP, + ESP_ERR_INVALID_ARG, OT_PLAT_LOG_TAG, "Radio mode not supported"); + ESP_RETURN_ON_FALSE(config->host_config.host_connection_mode == HOST_CONNECTION_MODE_NONE || + config->host_config.host_connection_mode == HOST_CONNECTION_MODE_CLI_UART || + config->host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_UART, + ESP_ERR_INVALID_ARG, OT_PLAT_LOG_TAG, "Host connection mode not supported"); + ESP_RETURN_ON_FALSE(!s_openthread_platform_initialized, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, + "OpenThread platform already initialized"); + + s_openthread_platform_initialized = true; + esp_err_t ret = ESP_OK; + const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, + config->port_config.storage_partition_name); + + ESP_RETURN_ON_FALSE(partition, ESP_ERR_INVALID_ARG, OT_PLAT_LOG_TAG, "OpenThread storage partition not found"); + + s_platform_config = *config; + esp_openthread_flash_set_partition(partition); + ESP_GOTO_ON_ERROR(esp_openthread_lock_init(), exit, OT_PLAT_LOG_TAG, "esp_openthread_lock_init failed"); + ESP_GOTO_ON_ERROR(esp_openthread_alarm_init(), exit, OT_PLAT_LOG_TAG, "esp_openthread_alarm_init failed"); + if (config->host_config.host_connection_mode == HOST_CONNECTION_MODE_CLI_UART || + config->host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_UART) { + ESP_GOTO_ON_ERROR(esp_openthread_uart_init(config), exit, OT_PLAT_LOG_TAG, "esp_openthread_uart_init failed"); + } + ESP_GOTO_ON_ERROR(esp_openthread_task_queue_init(config), exit, OT_PLAT_LOG_TAG, + "esp_openthread_task_queue_init failed"); + ESP_GOTO_ON_ERROR(esp_openthread_radio_init(config), exit, OT_PLAT_LOG_TAG, "esp_openthread_radio_init failed"); + +exit: + if (ret != ESP_OK) { + esp_openthread_platform_deinit(); + } + + return ret; +} + +otInstance *esp_openthread_get_instance(void) +{ + return (otInstance *)&ot::Instance::Get(); +} + +esp_err_t esp_openthread_platform_deinit(void) +{ + ESP_RETURN_ON_FALSE(s_openthread_platform_initialized, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, + "OpenThread platform not initialized"); + s_openthread_platform_initialized = false; + esp_openthread_task_queue_deinit(); + esp_openthread_radio_deinit(); + if (s_platform_config.host_config.host_connection_mode == HOST_CONNECTION_MODE_CLI_UART) { + esp_openthread_uart_deinit(); + } + esp_openthread_lock_deinit(); + esp_openthread_alarm_deinit(); + return ESP_OK; +} + +void esp_openthread_platform_update(esp_openthread_mainloop_context_t *mainloop) +{ + esp_openthread_platform_workflow_t *current_workflow = s_workflow_list; + while (current_workflow) { + current_workflow->update_func(mainloop); + current_workflow = current_workflow->next; + } +} + +esp_err_t esp_openthread_platform_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop) +{ + esp_openthread_platform_workflow_t *current_workflow = s_workflow_list; + while (current_workflow) { + ESP_RETURN_ON_ERROR(current_workflow->process_func(instance, mainloop), OT_PLAT_LOG_TAG, "process %s failed", + current_workflow->name); + current_workflow = current_workflow->next; + } + return ESP_OK; +} diff --git a/components/openthread/port/esp_openthread_radio.c b/components/openthread/port/esp_openthread_radio.c new file mode 100644 index 0000000000..3d4da55a21 --- /dev/null +++ b/components/openthread/port/esp_openthread_radio.c @@ -0,0 +1,472 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_radio.h" + +#include "sdkconfig.h" +#include "esp_check.h" +#include "esp_ieee802154.h" +#include "esp_ieee802154_types.h" +#include "esp_mac.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_platform.h" +#include "esp_openthread_types.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "esp_vfs.h" +#include "esp_vfs_eventfd.h" +#include "rom/ets_sys.h" + +#include "openthread-core-config.h" +#include "openthread/platform/diag.h" +#include "openthread/platform/radio.h" + +#define ESP_RECEIVE_SENSITIVITY -120 + +typedef struct { + uint8_t length; + uint8_t psdu[OT_RADIO_FRAME_MAX_SIZE]; +} esp_openthread_radio_tx_psdu; + +static otRadioFrame s_transmit_frame; + +static esp_openthread_radio_tx_psdu s_transmit_psdu; +static otRadioFrame s_receive_frame[CONFIG_IEEE802154_RX_BUFFER_SIZE]; +static otRadioFrame s_ack_frame; +static int s_energy_detect_power; +static esp_ieee802154_tx_error_t s_tx_error; +static int s_radio_event_fd = -1; +static bool s_diag_mode = false; +static const char *radio_workflow = "radio"; + +#define EVENT_TX_DONE (1 << 0) +#define EVENT_TX_FAILED (1 << 1) +#define EVENT_RX_DONE (1 << 2) +#define EVENT_ENERGY_DETECT_DONE (1 << 3) + +static uint8_t s_txrx_events; + +typedef struct { + uint8_t head; + uint8_t tail; + uint8_t used; +} esp_openthread_circular_queue_info_t; + +static esp_openthread_circular_queue_info_t recv_queue = {.head = 0, .tail = 0, .used = 0}; + +static void set_event(uint8_t event) +{ + uint64_t event_write = event; + s_txrx_events |= event; + int ret = write(s_radio_event_fd, &event_write, sizeof(event_write)); + assert(ret == sizeof(event_write)); +} + +static void clr_event(uint8_t event) +{ + s_txrx_events &= ~event; +} + +static bool get_event(uint8_t event) +{ + return s_txrx_events & event; +} + +esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *config) +{ + ESP_RETURN_ON_FALSE(s_radio_event_fd == -1, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, + "Radio was initalized already!"); + + s_radio_event_fd = eventfd(0, EFD_SUPPORT_ISR); + + s_transmit_frame.mPsdu = s_transmit_psdu.psdu; + + for (uint8_t i = 0; i < CONFIG_IEEE802154_RX_BUFFER_SIZE; i++) { + s_receive_frame[i].mPsdu = NULL; + } + + s_ack_frame.mPsdu = NULL; + + esp_ieee802154_enable(); + esp_ieee802154_set_promiscuous(false); + esp_ieee802154_set_rx_when_idle(true); + + return esp_openthread_platform_workflow_register(&esp_openthread_radio_update, &esp_openthread_radio_process, + radio_workflow); +} + +void esp_openthread_radio_deinit(void) +{ + if (s_radio_event_fd > 0) { + close(s_radio_event_fd); + s_radio_event_fd = -1; + } + + esp_ieee802154_disable(); + esp_openthread_platform_workflow_unregister(radio_workflow); +} + +void esp_openthread_radio_update(esp_openthread_mainloop_context_t *mainloop) +{ + FD_SET(s_radio_event_fd, &mainloop->read_fds); + if (s_radio_event_fd > mainloop->max_fd) { + mainloop->max_fd = s_radio_event_fd; + } +} + +esp_err_t esp_openthread_radio_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop) +{ + uint64_t event_read; + int ret = read(s_radio_event_fd, &event_read, sizeof(event_read)); + assert(ret == sizeof(event_read)); + + if (get_event(EVENT_TX_DONE)) { + clr_event(EVENT_TX_DONE); +#if OPENTHREAD_CONFIG_DIAG_ENABLE + if (otPlatDiagModeGet()) { + otPlatDiagRadioTransmitDone(instance, &s_transmit_frame, OT_ERROR_NONE); + } else +#endif + { + if (s_ack_frame.mPsdu == NULL) { + otPlatRadioTxDone(instance, &s_transmit_frame, NULL, OT_ERROR_NONE); + } else { + otPlatRadioTxDone(instance, &s_transmit_frame, &s_ack_frame, OT_ERROR_NONE); + s_ack_frame.mPsdu = NULL; + } + } + } + + if (get_event(EVENT_TX_FAILED)) { + clr_event(EVENT_TX_FAILED); +#if OPENTHREAD_CONFIG_DIAG_ENABLE + if (otPlatDiagModeGet()) { + otPlatDiagRadioTransmitDone(instance, &s_transmit_frame, OT_ERROR_CHANNEL_ACCESS_FAILURE); + } else +#endif + { + otError err = OT_ERROR_NONE; + + switch (s_tx_error) { + case ESP_IEEE802154_TX_ERR_CCA_BUSY: + case ESP_IEEE802154_TX_ERR_ABORT: + case ESP_IEEE802154_TX_ERR_COEXIST: + case ESP_IEEE802154_TX_ERR_COEXIST_REJ: + err = OT_ERROR_CHANNEL_ACCESS_FAILURE; + break; + + case ESP_IEEE802154_TX_ERR_NO_ACK: + case ESP_IEEE802154_TX_ERR_INVALID_ACK: + case ESP_IEEE802154_TX_ERR_COEXIST_ACK: + err = OT_ERROR_NO_ACK; + break; + + default: + ETS_ASSERT(false); + break; + } + + otPlatRadioTxDone(instance, &s_transmit_frame, NULL, err); + } + } + + if (get_event(EVENT_ENERGY_DETECT_DONE)) { + clr_event(EVENT_ENERGY_DETECT_DONE); + otPlatRadioEnergyScanDone(instance, s_energy_detect_power); + } + + while (recv_queue.used) { + if (s_receive_frame[recv_queue.head].mPsdu != NULL) { +#if OPENTHREAD_CONFIG_DIAG_ENABLE + if (otPlatDiagModeGet()) { + otPlatDiagRadioReceiveDone(instance, &s_receive_frame[recv_queue.head], OT_ERROR_NONE); + } else +#endif + { + otPlatRadioReceiveDone(instance, &s_receive_frame[recv_queue.head], OT_ERROR_NONE); + } + s_receive_frame[recv_queue.head].mPsdu = NULL; + recv_queue.head = (recv_queue.head + 1) % CONFIG_IEEE802154_RX_BUFFER_SIZE; + recv_queue.used--; + } + } + + return ESP_OK; +} + +void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) +{ + uint8_t eui64[8] = {0}; + esp_read_mac(eui64, ESP_MAC_IEEE802154); + memcpy(aIeeeEui64, eui64, sizeof(eui64)); +} + +void otPlatRadioSetPanId(otInstance *aInstance, uint16_t panid) +{ + esp_ieee802154_set_panid(panid); +} + +void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress) +{ + esp_ieee802154_set_extended_address(aAddress->m8); +} + +void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t aAddress) +{ + esp_ieee802154_set_short_address(aAddress); +} + +void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable) +{ + esp_ieee802154_set_promiscuous(aEnable); +} + +bool otPlatRadioIsEnabled(otInstance *aInstance) +{ + return esp_ieee802154_get_state() != ESP_IEEE802154_RADIO_DISABLE; +} + +otError otPlatRadioEnable(otInstance *aInstance) +{ + // radio has been enabled in platformRadioInit() + + return OT_ERROR_NONE; +} + +otError otPlatRadioDisable(otInstance *aInstance) +{ + esp_ieee802154_disable(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioSleep(otInstance *aInstance) +{ + esp_ieee802154_sleep(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) +{ + esp_ieee802154_set_channnel(aChannel); + esp_ieee802154_receive(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) +{ + esp_ieee802154_set_channnel(aFrame->mChannel); + + aFrame->mPsdu[-1] = aFrame->mLength; // lenth locates one byte before the psdu (esp_openthread_radio_tx_psdu); + + esp_ieee802154_transmit(&aFrame->mPsdu[-1], aFrame->mInfo.mTxInfo.mCsmaCaEnabled); + + otPlatRadioTxStarted(aInstance, aFrame); + + return OT_ERROR_NONE; +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) +{ + return &s_transmit_frame; +} + +int8_t otPlatRadioGetRssi(otInstance *aInstance) +{ + return 0; +} + +otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) +{ + return (otRadioCaps)(OT_RADIO_CAPS_ENERGY_SCAN | OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_SLEEP_TO_TX); +} + +bool otPlatRadioGetPromiscuous(otInstance *aInstance) +{ + return esp_ieee802154_get_promiscuous(); +} + +void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable) +{ + esp_ieee802154_set_pending_mode(ESP_IEEE802154_AUTO_PENDING_ENABLE); +} + +otError otPlatRadioAddSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress) +{ + esp_ieee802154_add_pending_addr((uint8_t *)&aShortAddress, true); + return OT_ERROR_NONE; +} + +otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + esp_ieee802154_add_pending_addr(aExtAddress->m8, false); + return OT_ERROR_NONE; +} + +otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress) +{ + esp_ieee802154_clear_pending_addr((uint8_t *)&aShortAddress, true); + return OT_ERROR_NONE; +} + +otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + esp_ieee802154_clear_pending_addr(aExtAddress->m8, false); + return OT_ERROR_NONE; +} + +void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance) +{ + esp_ieee802154_reset_pending_table(true); +} + +void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance) +{ + esp_ieee802154_reset_pending_table(false); +} + +otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration) +{ + esp_ieee802154_energy_detect(aScanDuration); + + return OT_ERROR_NONE; +} + +otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower) +{ + *aPower = esp_ieee802154_get_txpower(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower) +{ + esp_ieee802154_set_txpower(aPower); + + return OT_ERROR_NONE; +} + +otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t *aThreshold) +{ + *aThreshold = esp_ieee802154_get_cca_threshold(); + + return OT_ERROR_NONE; +} + +otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t aThreshold) +{ + esp_ieee802154_set_cca_threshold(aThreshold); + + return OT_ERROR_NONE; +} + +int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) +{ + return ESP_RECEIVE_SENSITIVITY; +} + +void otPlatDiagModeSet(bool mode) +{ + s_diag_mode = mode; +} + +bool otPlatDiagModeGet(void) +{ + return s_diag_mode; +} + +void otPlatDiagTxPowerSet(int8_t tx_power) +{ + OT_UNUSED_VARIABLE(tx_power); +} + +void otPlatDiagChannelSet(uint8_t channel) +{ + OT_UNUSED_VARIABLE(channel); +} + +void otPlatDiagRadioReceived(otInstance *instance, otRadioFrame *frame, otError error) +{ + OT_UNUSED_VARIABLE(instance); + OT_UNUSED_VARIABLE(frame); + OT_UNUSED_VARIABLE(error); +} + +void otPlatDiagAlarmCallback(otInstance *instance) +{ + OT_UNUSED_VARIABLE(instance); +} + +// events +void IRAM_ATTR esp_ieee802154_transmit_done(const uint8_t *frame, const uint8_t *ack, + esp_ieee802154_frame_info_t *s_ack_frame_info) +{ + ETS_ASSERT(frame == (uint8_t *)&s_transmit_psdu); + + if (ack != NULL) { + s_ack_frame.mLength = (uint16_t)(*ack); + s_ack_frame.mPsdu = (uint8_t *)(ack + 1); + } + + set_event(EVENT_TX_DONE); +} + +void IRAM_ATTR esp_ieee802154_receive_done(uint8_t *data, esp_ieee802154_frame_info_t *frame_info) +{ + if (recv_queue.used == CONFIG_IEEE802154_RX_BUFFER_SIZE) { + ESP_EARLY_LOGE(OT_PLAT_LOG_TAG, "radio receive buffer full!"); + } + + s_receive_frame[recv_queue.tail].mPsdu = data + 1; + s_receive_frame[recv_queue.tail].mLength = *data; + s_receive_frame[recv_queue.tail].mChannel = frame_info->channel; + s_receive_frame[recv_queue.tail].mInfo.mRxInfo.mRssi = frame_info->rssi; + s_receive_frame[recv_queue.tail].mInfo.mRxInfo.mAckedWithFramePending = frame_info->pending; + s_receive_frame[recv_queue.tail].mInfo.mRxInfo.mTimestamp = esp_timer_get_time(); + + recv_queue.tail = (recv_queue.tail + 1) % CONFIG_IEEE802154_RX_BUFFER_SIZE; + recv_queue.used++; + set_event(EVENT_RX_DONE); +} + +void IRAM_ATTR esp_ieee802154_transmit_failed(const uint8_t *frame, esp_ieee802154_tx_error_t error) +{ + ETS_ASSERT(frame == (uint8_t *)&s_transmit_psdu); + + s_tx_error = error; + + set_event(EVENT_TX_FAILED); +} + +void IRAM_ATTR esp_ieee802154_receive_sfd_done(void) +{ +} + +void IRAM_ATTR esp_ieee802154_transmit_sfd_done(uint8_t *frame) +{ +} + +void IRAM_ATTR esp_ieee802154_energy_detect_done(int8_t power) +{ + s_energy_detect_power = power; + + set_event(EVENT_ENERGY_DETECT_DONE); +} + +void IRAM_ATTR esp_ieee802154_cca_done(bool channel_free) +{ +} +// TODO irregular implementation +otError otPlatEntropyGet(uint8_t *aOutput, uint16_t aOutputLength) +{ + otError error = OT_ERROR_NONE; + for (uint16_t i = 0; i < aOutputLength; i++) { + aOutput[i] = (uint8_t)(esp_timer_get_time() % 256); + } + return error; +} diff --git a/components/openthread/port/esp_openthread_radio_uart.cpp b/components/openthread/port/esp_openthread_radio_uart.cpp new file mode 100644 index 0000000000..8634320980 --- /dev/null +++ b/components/openthread/port/esp_openthread_radio_uart.cpp @@ -0,0 +1,312 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_radio.h" + +#include "esp_check.h" +#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_uart_spinel_interface.hpp" +#include "openthread-core-config.h" +#include "lib/spinel/radio_spinel.hpp" +#include "lib/spinel/spinel.h" +#include "openthread/platform/diag.h" +#include "openthread/platform/radio.h" + +using esp::openthread::UartSpinelInterface; +using ot::Spinel::RadioSpinel; + +static RadioSpinel s_radio; +static const char *radiouart_workflow = "radio_uart"; + +esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *config) +{ + ESP_RETURN_ON_ERROR(s_radio.GetSpinelInterface().Init(config->radio_config.radio_uart_config), OT_PLAT_LOG_TAG, + "Spinel interface init falied"); + s_radio.Init(/*reset_radio=*/true, /*restore_dataset_from_ncp=*/false, /*skip_rcp_compatibility_check=*/false); + return esp_openthread_platform_workflow_register(&esp_openthread_radio_update, &esp_openthread_radio_process, + radiouart_workflow); +} + +void esp_openthread_register_rcp_failure_handler(esp_openthread_rcp_failure_handler handler) +{ + s_radio.GetSpinelInterface().RegisterRcpFailureHandler(handler); +} + +void esp_openthread_rcp_deinit(void) +{ + s_radio.GetSpinelInterface().Deinit(); +} + +void esp_openthread_radio_deinit(void) +{ + s_radio.Deinit(); + esp_openthread_platform_workflow_unregister(radiouart_workflow); +} + +esp_err_t esp_openthread_radio_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop) +{ + s_radio.Process(*mainloop); + + return ESP_OK; +} + +void esp_openthread_radio_update(esp_openthread_mainloop_context_t *mainloop) +{ + s_radio.GetSpinelInterface().Update(*mainloop); +} + +void otPlatRadioGetIeeeEui64(otInstance *instance, uint8_t *ieee_eui64) +{ + SuccessOrDie(s_radio.GetIeeeEui64(ieee_eui64)); +} + +void otPlatRadioSetPanId(otInstance *instance, uint16_t pan_id) +{ + SuccessOrDie(s_radio.SetPanId(pan_id)); +} + +void otPlatRadioSetExtendedAddress(otInstance *instance, const otExtAddress *address) +{ + otExtAddress addr; + + for (size_t i = 0; i < sizeof(addr); i++) { + addr.m8[i] = address->m8[sizeof(addr) - 1 - i]; + } + + SuccessOrDie(s_radio.SetExtendedAddress(addr)); +} + +void otPlatRadioSetShortAddress(otInstance *instance, uint16_t address) +{ + SuccessOrDie(s_radio.SetShortAddress(address)); +} + +void otPlatRadioSetPromiscuous(otInstance *instance, bool enable) +{ + SuccessOrDie(s_radio.SetPromiscuous(enable)); +} + +bool otPlatRadioIsEnabled(otInstance *instance) +{ + return s_radio.IsEnabled(); +} + +otError otPlatRadioEnable(otInstance *instance) +{ + return s_radio.Enable(instance); +} + +otError otPlatRadioDisable(otInstance *instance) +{ + return s_radio.Disable(); +} + +otError otPlatRadioSleep(otInstance *instance) +{ + return s_radio.Sleep(); +} + +otError otPlatRadioReceive(otInstance *instance, uint8_t channel) +{ + return s_radio.Receive(channel); +} + +otError otPlatRadioTransmit(otInstance *instance, otRadioFrame *frame) +{ + return s_radio.Transmit(*frame); +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *instance) +{ + return &s_radio.GetTransmitFrame(); +} + +int8_t otPlatRadioGetRssi(otInstance *instance) +{ + return s_radio.GetRssi(); +} + +otRadioCaps otPlatRadioGetCaps(otInstance *instance) +{ + return s_radio.GetRadioCaps(); +} + +bool otPlatRadioGetPromiscuous(otInstance *instance) +{ + return s_radio.IsPromiscuous(); +} + +void otPlatRadioEnableSrcMatch(otInstance *instance, bool enable) +{ + SuccessOrDie(s_radio.EnableSrcMatch(enable)); +} + +otError otPlatRadioAddSrcMatchShortEntry(otInstance *instance, uint16_t short_address) +{ + return s_radio.AddSrcMatchShortEntry(short_address); +} + +otError otPlatRadioAddSrcMatchExtEntry(otInstance *instance, const otExtAddress *ext_address) +{ + otExtAddress addr; + + for (size_t i = 0; i < sizeof(addr); i++) { + addr.m8[i] = ext_address->m8[sizeof(addr) - 1 - i]; + } + + return s_radio.AddSrcMatchExtEntry(addr); +} + +otError otPlatRadioClearSrcMatchShortEntry(otInstance *instance, uint16_t short_address) +{ + return s_radio.ClearSrcMatchShortEntry(short_address); +} + +otError otPlatRadioClearSrcMatchExtEntry(otInstance *instance, const otExtAddress *ext_address) +{ + otExtAddress addr; + + for (size_t i = 0; i < sizeof(addr); i++) { + addr.m8[i] = ext_address->m8[sizeof(addr) - 1 - i]; + } + + return s_radio.ClearSrcMatchExtEntry(addr); +} + +void otPlatRadioClearSrcMatchShortEntries(otInstance *instance) +{ + SuccessOrDie(s_radio.ClearSrcMatchShortEntries()); +} + +void otPlatRadioClearSrcMatchExtEntries(otInstance *instance) +{ + SuccessOrDie(s_radio.ClearSrcMatchExtEntries()); +} + +otError otPlatRadioEnergyScan(otInstance *instance, uint8_t channel, uint16_t duration) +{ + return s_radio.EnergyScan(channel, duration); +} + +otError otPlatRadioGetTransmitPower(otInstance *instance, int8_t *power) +{ + otError error; + + VerifyOrExit(power != NULL, error = OT_ERROR_INVALID_ARGS); + error = s_radio.GetTransmitPower(*power); + +exit: + return error; +} + +otError otPlatRadioSetTransmitPower(otInstance *instance, int8_t power) +{ + return s_radio.SetTransmitPower(power); +} + +otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *instance, int8_t *threshold) +{ + otError error; + + VerifyOrExit(threshold != NULL, error = OT_ERROR_INVALID_ARGS); + error = s_radio.GetCcaEnergyDetectThreshold(*threshold); + +exit: + return error; +} + +otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *instance, int8_t threshold) +{ + return s_radio.SetCcaEnergyDetectThreshold(threshold); +} + +int8_t otPlatRadioGetReceiveSensitivity(otInstance *instance) +{ + return s_radio.GetReceiveSensitivity(); +} + +void otPlatRadioSetMacKey(otInstance *aInstance, uint8_t aKeyIdMode, uint8_t aKeyId, const otMacKeyMaterial *aPrevKey, + const otMacKeyMaterial *aCurrKey, const otMacKeyMaterial *aNextKey, otRadioKeyType aKeyType) +{ + SuccessOrDie(s_radio.SetMacKey(aKeyIdMode, aKeyId, aPrevKey, aCurrKey, aNextKey)); +} + +void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + SuccessOrDie(s_radio.SetMacFrameCounter(aMacFrameCounter)); +} + +#if OPENTHREAD_CONFIG_DIAG_ENABLE +otError otPlatDiagProcess(otInstance *instance, int argc, char *argv[], char *output, size_t output_max_len) +{ + // deliver the platform specific diags commands to radio only ncp. + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE] = {'\0'}; + char *cur = cmd; + char *end = cmd + sizeof(cmd); + + for (int index = 0; index < argc; index++) { + cur += snprintf(cur, static_cast(end - cur), "%s ", argv[index]); + } + + return s_radio.PlatDiagProcess(cmd, output, output_max_len); +} + +void otPlatDiagModeSet(bool aMode) +{ + SuccessOrExit(s_radio.PlatDiagProcess(aMode ? "start" : "stop", NULL, 0)); + s_radio.SetDiagEnabled(aMode); + +exit: + return; +} + +bool otPlatDiagModeGet(void) +{ + return s_radio.IsDiagEnabled(); +} + +void otPlatDiagTxPowerSet(int8_t tx_power) +{ + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE]; + + snprintf(cmd, sizeof(cmd), "power %d", tx_power); + SuccessOrExit(s_radio.PlatDiagProcess(cmd, NULL, 0)); + +exit: + return; +} + +void otPlatDiagChannelSet(uint8_t channel) +{ + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE]; + + snprintf(cmd, sizeof(cmd), "channel %d", channel); + SuccessOrExit(s_radio.PlatDiagProcess(cmd, NULL, 0)); + +exit: + return; +} + +void otPlatDiagRadioReceived(otInstance *instance, otRadioFrame *frame, otError error) +{ +} + +void otPlatDiagAlarmCallback(otInstance *instance) +{ +} + +const char *otPlatRadioGetVersionString(otInstance *aInstance) +{ + return s_radio.GetVersion(); +} + +#endif // OPENTHREAD_CONFIG_DIAG_ENABLE diff --git a/components/openthread/port/esp_openthread_task_queue.c b/components/openthread/port/esp_openthread_task_queue.c new file mode 100644 index 0000000000..659a829e6c --- /dev/null +++ b/components/openthread/port/esp_openthread_task_queue.c @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_task_queue.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_platform.h" +#include "esp_openthread_types.h" +#include "esp_vfs.h" +#include "esp_vfs_eventfd.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" + +static QueueHandle_t s_task_queue = NULL; +static int s_task_queue_event_fd = -1; +static const char *task_queue_workflow = "task_queue"; + +typedef struct { + esp_openthread_task_t task; + void *arg; +} task_storage_t; + +esp_err_t esp_openthread_task_queue_init(const esp_openthread_platform_config_t *config) +{ + s_task_queue_event_fd = eventfd(0, EFD_SUPPORT_ISR); + ESP_RETURN_ON_FALSE(s_task_queue_event_fd >= 0, ESP_FAIL, OT_PLAT_LOG_TAG, + "Failed to create OpenThread task queue event fd"); + s_task_queue = xQueueCreate(config->port_config.task_queue_size, sizeof(task_storage_t)); + ESP_RETURN_ON_FALSE(s_task_queue != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, + "Failed to create OpenThread task queue"); + return esp_openthread_platform_workflow_register(&esp_openthread_task_queue_update, + &esp_openthread_task_queue_process, task_queue_workflow); +} + +esp_err_t esp_openthread_task_queue_post(esp_openthread_task_t task, void *arg) +{ + task_storage_t task_storage = { + .task = task, + .arg = arg, + }; + uint64_t val = 1; + ssize_t ret; + + ESP_RETURN_ON_FALSE(xQueueSend(s_task_queue, &task_storage, portMAX_DELAY), ESP_FAIL, OT_PLAT_LOG_TAG, + "Failed to post task to OpenThread task queue"); + ret = write(s_task_queue_event_fd, &val, sizeof(val)); + assert(ret == sizeof(val)); + + return ESP_OK; +} + +void esp_openthread_task_queue_update(esp_openthread_mainloop_context_t *mainloop) +{ + if (s_task_queue_event_fd >= 0) { + FD_SET(s_task_queue_event_fd, &mainloop->read_fds); + if (s_task_queue_event_fd > mainloop->max_fd) { + mainloop->max_fd = s_task_queue_event_fd; + } + } +} + +esp_err_t esp_openthread_task_queue_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop) +{ + task_storage_t task_storage; + + if (FD_ISSET(s_task_queue_event_fd, &mainloop->read_fds)) { + uint64_t val; + ssize_t ret = read(s_task_queue_event_fd, &val, sizeof(val)); + assert(ret == sizeof(val)); + } + + ESP_RETURN_ON_FALSE(s_task_queue != NULL, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, + "OpenThread task queue not initialized"); + while (xQueueReceive(s_task_queue, &task_storage, 0) == pdTRUE) { + task_storage.task(task_storage.arg); + } + + return ESP_OK; +} + +esp_err_t esp_openthread_task_queue_deinit(void) +{ + if (s_task_queue) { + vQueueDelete(s_task_queue); + s_task_queue = NULL; + } + if (s_task_queue_event_fd >= 0) { + close(s_task_queue_event_fd); + s_task_queue_event_fd = -1; + } + esp_openthread_platform_workflow_unregister(task_queue_workflow); + return ESP_OK; +} diff --git a/components/openthread/port/esp_openthread_uart.c b/components/openthread/port/esp_openthread_uart.c new file mode 100644 index 0000000000..500c99cddd --- /dev/null +++ b/components/openthread/port/esp_openthread_uart.c @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread_uart.h" + +#include +#include +#include + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_openthread.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_platform.h" +#include "esp_openthread_types.h" +#include "esp_vfs_dev.h" +#include "common/logging.hpp" +#include "driver/uart.h" +#include "utils/uart.h" + +static int s_uart_port; +static int s_uart_fd; +static uint8_t s_uart_buffer[ESP_OPENTHREAD_UART_BUFFER_SIZE]; +static const char *uart_workflow = "uart"; + +otError otPlatUartEnable(void) +{ + return OT_ERROR_NONE; +} + +otError otPlatUartDisable(void) +{ + return OT_ERROR_NONE; +} + +otError otPlatUartFlush(void) +{ + return OT_ERROR_NONE; +} + +otError otPlatUartSend(const uint8_t *buf, uint16_t buf_length) +{ + int rval = write(s_uart_fd, buf, buf_length); + + if (rval != (int)buf_length) { + return OT_ERROR_FAILED; + } + + otPlatUartSendDone(); + + return OT_ERROR_NONE; +} + +esp_err_t esp_openthread_uart_init_port(const esp_openthread_uart_config_t *config) +{ + ESP_RETURN_ON_ERROR(uart_param_config(config->port, &config->uart_config), OT_PLAT_LOG_TAG, + "uart_param_config failed"); + ESP_RETURN_ON_ERROR( + uart_set_pin(config->port, config->tx_pin, config->rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), + OT_PLAT_LOG_TAG, "uart_set_pin failed"); + ESP_RETURN_ON_ERROR(uart_driver_install(config->port, ESP_OPENTHREAD_UART_BUFFER_SIZE, 0, 0, NULL, 0), + OT_PLAT_LOG_TAG, "uart_driver_install failed"); + esp_vfs_dev_uart_use_driver(config->port); + return ESP_OK; +} + +esp_err_t esp_openthread_uart_init(const esp_openthread_platform_config_t *config) +{ + char uart_path[16]; + esp_err_t ret = ESP_OK; + + // Disable IO buffer. + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + // Install UART driver for interrupt-driven reads and writes. + ESP_RETURN_ON_FALSE(config->host_config.host_connection_mode == HOST_CONNECTION_MODE_CLI_UART || + config->host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_UART, + ESP_FAIL, OT_PLAT_LOG_TAG, "unsupport host connect mode"); + s_uart_port = config->host_config.host_uart_config.port; + ESP_RETURN_ON_ERROR(esp_openthread_uart_init_port(&config->host_config.host_uart_config), OT_PLAT_LOG_TAG, + "esp_openthread_uart_init_port failed"); + + if (config->host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_UART) { + esp_vfs_dev_uart_port_set_rx_line_endings(s_uart_port, ESP_LINE_ENDINGS_LF); + esp_vfs_dev_uart_port_set_tx_line_endings(s_uart_port, ESP_LINE_ENDINGS_LF); + snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", s_uart_port); + s_uart_fd = open(uart_path, O_RDWR | O_NONBLOCK); + ESP_RETURN_ON_FALSE(s_uart_fd >= 0, ESP_FAIL, OT_PLAT_LOG_TAG, "open uart_path failed"); + ret = esp_openthread_platform_workflow_register(&esp_openthread_uart_update, &esp_openthread_uart_process, + uart_workflow); + } + + return ret; +} + +void esp_openthread_uart_deinit() +{ + if (s_uart_fd != -1) { + close(s_uart_fd); + s_uart_fd = -1; + } + uart_driver_delete(s_uart_port); + esp_openthread_platform_workflow_unregister(uart_workflow); +} + +void esp_openthread_uart_update(esp_openthread_mainloop_context_t *mainloop) +{ + FD_SET(s_uart_fd, &mainloop->read_fds); + if (s_uart_fd > mainloop->max_fd) { + mainloop->max_fd = s_uart_fd; + } +} + +esp_err_t esp_openthread_uart_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop) +{ + int rval = read(s_uart_fd, s_uart_buffer, sizeof(s_uart_buffer)); + + if (rval > 0) { + otPlatUartReceived(s_uart_buffer, (uint16_t)rval); + } else if (rval < 0) { + if (errno != EAGAIN) { + ESP_LOGW(OT_PLAT_LOG_TAG, "read uart failed: %d", errno); + return ESP_FAIL; + } + } + return ESP_OK; +} diff --git a/components/openthread/port/esp_openthread_udp.c b/components/openthread/port/esp_openthread_udp.c new file mode 100644 index 0000000000..4ddc65a680 --- /dev/null +++ b/components/openthread/port/esp_openthread_udp.c @@ -0,0 +1,424 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_netif.h" +#include "esp_openthread.h" +#include "esp_openthread_border_router.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_lock.h" +#include "esp_openthread_netif_glue.h" +#include "esp_openthread_task_queue.h" +#include "common/code_utils.hpp" +#include "common/logging.hpp" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "lwip/ip6.h" +#include "lwip/ip6_addr.h" +#include "lwip/ip_addr.h" +#include "lwip/mld6.h" +#include "lwip/pbuf.h" +#include "lwip/tcpip.h" +#include "lwip/udp.h" +#include "openthread/error.h" +#include "openthread/platform/udp.h" + +typedef struct { + otUdpSocket *socket; + struct pbuf *recv_buf; + ip_addr_t addr; + uint16_t port; + uint8_t hop_limit; + bool is_host_interface; +} udp_recv_task_t; + +typedef struct { + TaskHandle_t source_task; + otUdpSocket *socket; + struct udp_pcb *pcb_ret; +} udp_new_task_t; + +typedef struct { + TaskHandle_t source_task; + struct udp_pcb *pcb; + ip_addr_t addr; + uint16_t port; + err_t ret; +} udp_bind_connect_task_t; + +typedef struct { + TaskHandle_t source_task; + struct udp_pcb *pcb; + uint8_t netif_index; +} udp_bind_netif_task_t; + +typedef struct { + struct udp_pcb *pcb; + otMessage *message; + ip_addr_t source_addr; + uint16_t source_port; + ip_addr_t peer_addr; + uint16_t peer_port; + bool multicast_loop; + uint8_t hop_limit; + uint8_t netif_index; +} udp_send_task_t; + +typedef struct { + bool is_join; + uint8_t netif_index; + ip6_addr_t addr; +} udp_multicast_join_leave_task_t; + +static void wait_for_task_notification(void) +{ + esp_openthread_task_switching_lock_release(); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + esp_openthread_task_switching_lock_acquire(portMAX_DELAY); +} + +static ip_addr_t map_openthread_addr_to_lwip_addr(const otIp6Address *address) +{ + ip_addr_t addr; + + memcpy(ip_2_ip6(&addr)->addr, address->mFields.m8, sizeof(ip_2_ip6(&addr)->addr)); + if (ip6_addr_isipv4mappedipv6(ip_2_ip6(&addr))) { + unmap_ipv4_mapped_ipv6(ip_2_ip4(&addr), ip_2_ip6(&addr)); + addr.type = IPADDR_TYPE_V4; + } else { + addr.type = IPADDR_TYPE_V6; +#if LWIP_IPV6_SCOPES + addr.u_addr.ip6.zone = IP6_NO_ZONE; +#endif + } + return addr; +} + +static void udp_recv_task(void *ctx) +{ + udp_recv_task_t *task = (udp_recv_task_t *)ctx; + + otMessageInfo message_info; + otMessage *message = NULL; + otMessageSettings msg_settings = {.mLinkSecurityEnabled = false, .mPriority = OT_MESSAGE_PRIORITY_NORMAL}; + struct pbuf *recv_buf = task->recv_buf; + uint8_t *data_buf = (uint8_t *)recv_buf->payload; + uint8_t *data_buf_to_free = NULL; + + message_info.mSockPort = 0; + memset(&message_info.mSockAddr, 0, sizeof(message_info.mSockAddr)); + message_info.mHopLimit = task->hop_limit; + message_info.mPeerPort = task->port; + if (task->addr.type == IPADDR_TYPE_V4) { + ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&task->addr), ip_2_ip4(&task->addr)); + } + memcpy(&message_info.mPeerAddr, ip_2_ip6(&task->addr)->addr, sizeof(message_info.mPeerAddr)); + + if (recv_buf->next != NULL) { + data_buf = (uint8_t *)malloc(recv_buf->tot_len); + if (data_buf != NULL) { + data_buf_to_free = data_buf; + pbuf_copy_partial(recv_buf, data_buf, recv_buf->tot_len, 0); + } + } + VerifyOrExit(data_buf != NULL, + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate data buf when receiving OpenThread plat UDP")); + message = otUdpNewMessage(esp_openthread_get_instance(), &msg_settings); + VerifyOrExit(message != NULL, + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate OpenThread message when receiving OpenThread plat UDP")); + VerifyOrExit(otMessageAppend(message, data_buf, recv_buf->tot_len) == OT_ERROR_NONE, + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to copy OpenThread message when receiving OpenThread plat UDP")); + task->socket->mHandler(task->socket->mContext, message, &message_info); + otMessageFree(message); + +exit: + free(task); + if (data_buf_to_free) { + free(data_buf_to_free); + } + pbuf_free(recv_buf); + return; +} + +static void handle_udp_recv(void *ctx, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, uint16_t port) +{ + udp_recv_task_t *task = (udp_recv_task_t *)malloc(sizeof(udp_recv_task_t)); + const struct ip6_hdr *ip6_hdr = ip6_current_header(); + const struct ip_hdr *ip4_hdr = ip4_current_header(); + struct netif *source_netif = ip_current_netif(); + + if (task == NULL) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate recv task when receiving OpenThread plat UDP"); + } + task->socket = (otUdpSocket *)ctx; + task->recv_buf = p; + task->addr = *addr; + task->port = port; + task->hop_limit = (addr->type == IPADDR_TYPE_V6) ? IP6H_HOPLIM(ip6_hdr) : IPH_TTL(ip4_hdr); + task->is_host_interface = + (netif_get_index(source_netif) == esp_netif_get_netif_impl_index(esp_openthread_get_backbone_netif())); + + if (esp_openthread_task_queue_post(udp_recv_task, task) != ESP_OK) { + free(task); + } +} + +static void udp_new_task(void *ctx) +{ + udp_new_task_t *task = (udp_new_task_t *)ctx; + + task->pcb_ret = udp_new(); + udp_recv(task->pcb_ret, handle_udp_recv, task->socket); + xTaskNotifyGive(task->source_task); +} + +otError otPlatUdpSocket(otUdpSocket *udp_socket) +{ + otError error = OT_ERROR_NONE; + + udp_new_task_t task = {.source_task = xTaskGetCurrentTaskHandle(), .socket = udp_socket}; + tcpip_callback(udp_new_task, &task); + wait_for_task_notification(); + VerifyOrExit(task.pcb_ret != NULL, error = OT_ERROR_FAILED); + udp_socket->mHandle = task.pcb_ret; + +exit: + return error; +} + +static void udp_close_task(void *ctx) +{ + struct udp_pcb *pcb = (struct udp_pcb *)ctx; + + udp_remove(pcb); +} + +otError otPlatUdpClose(otUdpSocket *udp_socket) +{ + struct udp_pcb *pcb = (struct udp_pcb *)udp_socket->mHandle; + + if (pcb) { + tcpip_callback(udp_close_task, pcb); + } + + return OT_ERROR_NONE; +} + +static void udp_bind_task(void *ctx) +{ + udp_bind_connect_task_t *task = (udp_bind_connect_task_t *)ctx; + + task->ret = udp_bind(task->pcb, &task->addr, task->port); + xTaskNotifyGive(task->source_task); +} + +otError otPlatUdpBind(otUdpSocket *udp_socket) +{ + udp_bind_connect_task_t task = { + .source_task = xTaskGetCurrentTaskHandle(), + .pcb = (struct udp_pcb *)udp_socket->mHandle, + .port = udp_socket->mSockName.mPort, + }; + ESP_LOGI(OT_PLAT_LOG_TAG, "Platform UDP bound to port %d", udp_socket->mSockName.mPort); + + task.addr.type = IPADDR_TYPE_ANY; + memcpy(ip_2_ip6(&task.addr)->addr, udp_socket->mSockName.mAddress.mFields.m8, sizeof(ip_2_ip6(&task.addr)->addr)); + tcpip_callback(udp_bind_task, &task); + wait_for_task_notification(); + + return task.ret == ERR_OK ? OT_ERROR_NONE : OT_ERROR_FAILED; +} + +static void udp_bind_netif_task(void *ctx) +{ + udp_bind_netif_task_t *task = (udp_bind_netif_task_t *)ctx; + + udp_bind_netif(task->pcb, netif_get_by_index(task->netif_index)); + xTaskNotifyGive(task->source_task); +} + +static uint8_t get_netif_index(otNetifIdentifier netif_identifier) +{ + switch (netif_identifier) { + case OT_NETIF_UNSPECIFIED: + return NETIF_NO_INDEX; + case OT_NETIF_THREAD: + return esp_netif_get_netif_impl_index(esp_openthread_get_netif()); + case OT_NETIF_BACKBONE: + return esp_netif_get_netif_impl_index(esp_openthread_get_backbone_netif()); + default: + return NETIF_NO_INDEX; + } +} + +otError otPlatUdpBindToNetif(otUdpSocket *udp_socket, otNetifIdentifier netif_identifier) +{ + udp_bind_netif_task_t task = { + .source_task = xTaskGetCurrentTaskHandle(), + .pcb = (struct udp_pcb *)udp_socket->mHandle, + .netif_index = get_netif_index(netif_identifier), + }; + + tcpip_callback(udp_bind_netif_task, &task); + wait_for_task_notification(); + + return OT_ERROR_NONE; +} + +static void udp_connect_task(void *ctx) +{ + udp_bind_connect_task_t *task = (udp_bind_connect_task_t *)ctx; + + task->ret = udp_connect(task->pcb, &task->addr, task->port); + xTaskNotifyGive(task->source_task); +} + +otError otPlatUdpConnect(otUdpSocket *udp_socket) +{ + udp_bind_connect_task_t task = { + .source_task = xTaskGetCurrentTaskHandle(), + .pcb = (struct udp_pcb *)udp_socket->mHandle, + .port = udp_socket->mPeerName.mPort, + }; + + task.addr = map_openthread_addr_to_lwip_addr(&udp_socket->mPeerName.mAddress); + tcpip_callback(udp_connect_task, &task); + wait_for_task_notification(); + + return task.ret == ERR_OK ? OT_ERROR_NONE : OT_ERROR_FAILED; +} + +static bool is_link_local(const otIp6Address *address) +{ + return address->mFields.m8[0] == 0xfe && address->mFields.m8[1] == 0x80; +} + +static bool is_multicast(const otIp6Address *address) +{ + return address->mFields.m8[0] == 0xff; +} + +static void udp_send_task(void *ctx) +{ + udp_send_task_t *task = (udp_send_task_t *)ctx; + struct pbuf *send_buf = NULL; + uint16_t len = otMessageGetLength(task->message); + + task->pcb->ttl = task->hop_limit; + task->pcb->netif_idx = task->netif_index; +#if LWIP_IPV6_SCOPES + if (task->peer_addr.type == IPADDR_TYPE_V6) { + ip_2_ip6(&task->peer_addr)->zone = task->netif_index; + } + if (task->source_addr.type == IPADDR_TYPE_V6) { + ip_2_ip6(&task->source_addr)->zone = task->netif_index; + } +#endif + task->pcb->flags = (task->pcb->flags & (~UDP_FLAGS_MULTICAST_LOOP)); + task->pcb->local_ip = task->source_addr; + task->pcb->local_port = task->source_port; + if (task->multicast_loop) { + task->pcb->flags |= UDP_FLAGS_MULTICAST_LOOP; + } + send_buf = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + otMessageRead(task->message, 0, send_buf->payload, len); + VerifyOrExit(send_buf != NULL); + udp_sendto(task->pcb, send_buf, &task->peer_addr, task->peer_port); + +exit: + if (send_buf) { + pbuf_free(send_buf); + } + esp_openthread_task_switching_lock_acquire(portMAX_DELAY); + otMessageFree(task->message); + esp_openthread_task_switching_lock_release(); + free(task); +} + +static inline bool is_addr_ip6_any(const ip_addr_t *addr) +{ + return addr->type == IPADDR_TYPE_V6 && addr->u_addr.ip6.addr[0] == 0 && addr->u_addr.ip6.addr[1] == 0 && + addr->u_addr.ip6.addr[2] == 0 && addr->u_addr.ip6.addr[3] == 0; +} + +otError otPlatUdpSend(otUdpSocket *udp_socket, otMessage *message, const otMessageInfo *message_info) +{ + udp_send_task_t *task = (udp_send_task_t *)malloc(sizeof(udp_send_task_t)); + otError error = OT_ERROR_NONE; + + VerifyOrExit(task != NULL, error = OT_ERROR_NO_BUFS); + task->pcb = (struct udp_pcb *)udp_socket->mHandle; + task->message = message; + task->source_port = message_info->mSockPort; + task->peer_port = message_info->mPeerPort; + task->multicast_loop = message_info->mMulticastLoop; + task->hop_limit = message_info->mHopLimit ? message_info->mHopLimit : UDP_TTL; + task->netif_index = NETIF_NO_INDEX; + task->source_addr = map_openthread_addr_to_lwip_addr(&message_info->mSockAddr); + task->peer_addr = map_openthread_addr_to_lwip_addr(&message_info->mPeerAddr); + if (task->peer_addr.type == IPADDR_TYPE_V4 && is_addr_ip6_any(&task->source_addr)) { + task->source_addr.type = IPADDR_TYPE_ANY; + } + + if (is_link_local(&message_info->mPeerAddr) || is_multicast(&message_info->mPeerAddr)) { + task->netif_index = get_netif_index(message_info->mIsHostInterface ? OT_NETIF_BACKBONE : OT_NETIF_THREAD); + } + tcpip_callback(udp_send_task, task); + +exit: + return error; +} + +static void udp_multicast_join_leave_task(void *ctx) +{ + udp_multicast_join_leave_task_t *task = (udp_multicast_join_leave_task_t *)ctx; + + if (task->is_join) { + if (mld6_joingroup_netif(netif_get_by_index(task->netif_index), &task->addr) != ERR_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to join multicast group"); + } + } else { + if (mld6_leavegroup_netif(netif_get_by_index(task->netif_index), &task->addr) != ERR_OK) { + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to leave multicast group"); + } + } + free(task); +} + +otError otPlatUdpJoinMulticastGroup(otUdpSocket *socket, otNetifIdentifier netif_id, const otIp6Address *addr) +{ + udp_multicast_join_leave_task_t *task = + (udp_multicast_join_leave_task_t *)malloc(sizeof(udp_multicast_join_leave_task_t)); + otError error = OT_ERROR_NONE; + + VerifyOrExit(task != NULL, error = OT_ERROR_NO_BUFS); + memcpy(task->addr.addr, addr->mFields.m8, sizeof(task->addr.addr)); + task->is_join = true; + task->netif_index = get_netif_index(netif_id); + tcpip_callback(udp_multicast_join_leave_task, task); + +exit: + return error; +} + +otError otPlatUdpLeaveMulticastGroup(otUdpSocket *socket, otNetifIdentifier netif_id, const otIp6Address *addr) +{ + udp_multicast_join_leave_task_t *task = + (udp_multicast_join_leave_task_t *)malloc(sizeof(udp_multicast_join_leave_task_t)); + otError error = OT_ERROR_NONE; + + VerifyOrExit(task != NULL, error = OT_ERROR_NO_BUFS); + memcpy(task->addr.addr, addr->mFields.m8, sizeof(task->addr.addr)); + task->is_join = false; + task->netif_index = get_netif_index(netif_id); + tcpip_callback(udp_multicast_join_leave_task, task); + +exit: + return error; +} diff --git a/components/openthread/port/esp_uart_spinel_interface.cpp b/components/openthread/port/esp_uart_spinel_interface.cpp new file mode 100644 index 0000000000..e992665d05 --- /dev/null +++ b/components/openthread/port/esp_uart_spinel_interface.cpp @@ -0,0 +1,286 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_uart_spinel_interface.hpp" + +#include +#include +#include +#include + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_types.h" +#include "esp_openthread_uart.h" +#include "esp_vfs_dev.h" +#include "core/common/code_utils.hpp" +#include "core/common/logging.hpp" +#include "driver/uart.h" +#include "lib/platform/exit_code.h" +#include "openthread/platform/time.h" + +namespace esp { +namespace openthread { + +UartSpinelInterface::UartSpinelInterface(ot::Spinel::SpinelInterface::ReceiveFrameCallback callback, + void *callback_context, + ot::Spinel::SpinelInterface::RxFrameBuffer &frame_buffer) + : m_receiver_frame_callback(callback) + , m_receiver_frame_context(callback_context) + , m_receive_frame_buffer(frame_buffer) + , m_hdlc_decoder(frame_buffer, HandleHdlcFrame, this) + , m_uart_fd(-1) + , mRcpFailureHandler(nullptr) +{ +} + +UartSpinelInterface::~UartSpinelInterface(void) +{ +} + +esp_err_t UartSpinelInterface::Init(const esp_openthread_uart_config_t &radio_uart_config) +{ + m_uart_rx_buffer = static_cast(heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_8BIT)); + if (m_uart_rx_buffer == NULL) { + return ESP_ERR_NO_MEM; + } + + return InitUart(radio_uart_config); +} + +esp_err_t UartSpinelInterface::Deinit(void) +{ + if (m_uart_rx_buffer) { + heap_caps_free(m_uart_rx_buffer); + } + m_uart_rx_buffer = NULL; + + return DeinitUart(); +} + +otError UartSpinelInterface::SendFrame(const uint8_t *frame, uint16_t length) +{ + otError error = OT_ERROR_NONE; + ot::Hdlc::FrameBuffer encoder_buffer; + ot::Hdlc::Encoder hdlc_encoder(encoder_buffer); + + SuccessOrExit(error = hdlc_encoder.BeginFrame()); + SuccessOrExit(error = hdlc_encoder.Encode(frame, length)); + SuccessOrExit(error = hdlc_encoder.EndFrame()); + + SuccessOrExit(error = Write(encoder_buffer.GetFrame(), encoder_buffer.GetLength())); + +exit: + if (error != OT_ERROR_NONE) { + ESP_LOGE(OT_PLAT_LOG_TAG, "send radio frame failed"); + } else { + ESP_LOGD(OT_PLAT_LOG_TAG, "sent radio frame"); + } + + return error; +} + +void UartSpinelInterface::Process(const esp_openthread_mainloop_context_t &mainloop) +{ + if (FD_ISSET(m_uart_fd, &mainloop.read_fds)) { + ESP_LOGD(OT_PLAT_LOG_TAG, "radio uart read event"); + TryReadAndDecode(); + } +} + +void UartSpinelInterface::Update(esp_openthread_mainloop_context_t &mainloop) +{ + // Register only READ events for radio UART and always wait + // for a radio WRITE to complete. + FD_SET(m_uart_fd, &mainloop.read_fds); + if (m_uart_fd > mainloop.max_fd) { + mainloop.max_fd = m_uart_fd; + } +} + +int UartSpinelInterface::TryReadAndDecode(void) +{ + uint8_t buffer[UART_FIFO_LEN]; + ssize_t rval; + + do { + rval = read(m_uart_fd, buffer, sizeof(buffer)); + if (rval > 0) { + m_hdlc_decoder.Decode(buffer, static_cast(rval)); + } + } while (rval > 0); + + if ((rval < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK)) { + ESP_ERROR_CHECK(TryRecoverUart()); + } + + return rval; +} + +otError UartSpinelInterface::WaitForWritable(void) +{ + otError error = OT_ERROR_NONE; + struct timeval timeout = {kMaxWaitTime / MS_PER_S, (kMaxWaitTime % MS_PER_S) * US_PER_MS}; + uint64_t now = otPlatTimeGet(); + uint64_t end = now + kMaxWaitTime * US_PER_MS; + fd_set write_fds; + fd_set error_fds; + int rval; + + while (true) { + FD_ZERO(&write_fds); + FD_ZERO(&error_fds); + FD_SET(m_uart_fd, &write_fds); + FD_SET(m_uart_fd, &error_fds); + + rval = select(m_uart_fd + 1, NULL, &write_fds, &error_fds, &timeout); + + if (rval > 0) { + if (FD_ISSET(m_uart_fd, &write_fds)) { + ExitNow(); + } else if (FD_ISSET(m_uart_fd, &error_fds)) { + ExitNow(error = OT_ERROR_FAILED); + } + } else if ((rval < 0) && (errno != EINTR)) { + ESP_ERROR_CHECK(TryRecoverUart()); + ExitNow(error = OT_ERROR_FAILED); + } + + now = otPlatTimeGet(); + + if (end > now) { + uint64_t remain = end - now; + + timeout.tv_sec = static_cast(remain / 1000000); + timeout.tv_usec = static_cast(remain % 1000000); + } else { + break; + } + } + + error = OT_ERROR_FAILED; + +exit: + return error; +} + +otError UartSpinelInterface::Write(const uint8_t *aFrame, uint16_t length) +{ + otError error = OT_ERROR_NONE; + + while (length) { + ssize_t rval; + + rval = write(m_uart_fd, aFrame, length); + + if (rval > 0) { + assert(rval <= length); + length -= static_cast(rval); + aFrame += static_cast(rval); + continue; + } else if (rval < 0) { + ESP_ERROR_CHECK(TryRecoverUart()); + ExitNow(error = OT_ERROR_FAILED); + } + + SuccessOrExit(error = WaitForWritable()); + } + +exit: + return error; +} + +otError UartSpinelInterface::WaitForFrame(uint64_t timeout_us) +{ + otError error = OT_ERROR_NONE; + struct timeval timeout; + fd_set read_fds; + fd_set error_fds; + int rval; + + FD_ZERO(&read_fds); + FD_ZERO(&error_fds); + FD_SET(m_uart_fd, &read_fds); + FD_SET(m_uart_fd, &error_fds); + + timeout.tv_sec = static_cast(timeout_us / US_PER_S); + timeout.tv_usec = static_cast(timeout_us % US_PER_S); + + rval = select(m_uart_fd + 1, &read_fds, NULL, &error_fds, &timeout); + + if (rval > 0) { + if (FD_ISSET(m_uart_fd, &read_fds)) { + TryReadAndDecode(); + } else if (FD_ISSET(m_uart_fd, &error_fds)) { + ESP_ERROR_CHECK(TryRecoverUart()); + ExitNow(error = OT_ERROR_FAILED); + } + } else if (rval == 0) { + ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT); + } else { + ESP_ERROR_CHECK(TryRecoverUart()); + ExitNow(error = OT_ERROR_FAILED); + } + +exit: + return error; +} + +void UartSpinelInterface::HandleHdlcFrame(void *context, otError error) +{ + static_cast(context)->HandleHdlcFrame(error); +} + +void UartSpinelInterface::HandleHdlcFrame(otError error) +{ + if (error == OT_ERROR_NONE) { + ESP_LOGD(OT_PLAT_LOG_TAG, "received hdlc radio frame"); + m_receiver_frame_callback(m_receiver_frame_context); + } else { + ESP_LOGE(OT_PLAT_LOG_TAG, "dropping radio frame: %s", otThreadErrorToString(error)); + m_receive_frame_buffer.DiscardFrame(); + } +} + +esp_err_t UartSpinelInterface::InitUart(const esp_openthread_uart_config_t &radio_uart_config) +{ + char uart_path[16]; + + m_uart_config = radio_uart_config; + ESP_RETURN_ON_ERROR(esp_openthread_uart_init_port(&radio_uart_config), OT_PLAT_LOG_TAG, + "esp_openthread_uart_init_port failed"); + // We have a driver now installed so set up the read/write functions to use driver also. + esp_vfs_dev_uart_port_set_tx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF); + esp_vfs_dev_uart_port_set_rx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF); + + snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", radio_uart_config.port); + m_uart_fd = open(uart_path, O_RDWR | O_NONBLOCK); + + return m_uart_fd >= 0 ? ESP_OK : ESP_FAIL; +} + +esp_err_t UartSpinelInterface::DeinitUart(void) +{ + if (m_uart_fd != -1) { + close(m_uart_fd); + m_uart_fd = -1; + return uart_driver_delete(m_uart_config.port); + } else { + return ESP_ERR_INVALID_STATE; + } +} + +esp_err_t UartSpinelInterface::TryRecoverUart(void) +{ + ESP_RETURN_ON_ERROR(DeinitUart(), OT_PLAT_LOG_TAG, "DeInitUart failed"); + ESP_RETURN_ON_ERROR(InitUart(m_uart_config), OT_PLAT_LOG_TAG, "InitUart failed"); + return ESP_OK; +} + +} // namespace openthread +} // namespace esp diff --git a/components/openthread/private_include/esp_openthread_alarm.h b/components/openthread/private_include/esp_openthread_alarm.h new file mode 100644 index 0000000000..44dd4232b6 --- /dev/null +++ b/components/openthread/private_include/esp_openthread_alarm.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_openthread_types.h" +#include "openthread/instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initializes the OpenThread alarm. + * + * @return + * -ESP_OK on success + * -ESP_ERR_NO_MEM on allocation failure + * -ESP_FAIL on other failures + * + */ +esp_err_t esp_openthread_alarm_init(void); + +/** + * @brief This function deinitializes the OpenThread alarm. + */ +void esp_openthread_alarm_deinit(void); + +/** + * @brief Updates the file descriptor with the OpenThread alarm timeout. + * + * @param[inout] mainloop The main loop context. + * + */ +void esp_openthread_alarm_update(esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief Performs the alarm process and triggers the fired timers for OpenThread. + * + * @param[in] instance The OpenThread instance. + * @param[in] mainloop The main loop context. + * + * return + * - ESP_OK on success + * - ESP_ERROR on other failures + * + */ +esp_err_t esp_openthread_alarm_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_openthread_common_macro.h b/components/openthread/private_include/esp_openthread_common_macro.h new file mode 100644 index 0000000000..9bb23e5891 --- /dev/null +++ b/components/openthread/private_include/esp_openthread_common_macro.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#define OT_PLAT_LOG_TAG "OPENTHREAD" + +#ifndef MS_PER_S +#define MS_PER_S 1000 +#endif + +#ifndef US_PER_MS +#define US_PER_MS 1000 +#endif + +#ifndef US_PER_S +#define US_PER_S (MS_PER_S * US_PER_MS) +#endif + +#define ESP_OPENTHREAD_UART_BUFFER_SIZE (UART_FIFO_LEN * 2) diff --git a/components/openthread/private_include/esp_openthread_flash.h b/components/openthread/private_include/esp_openthread_flash.h new file mode 100644 index 0000000000..d6ba1467c6 --- /dev/null +++ b/components/openthread/private_include/esp_openthread_flash.h @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_partition.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set the partition to store OpenThread dataset. + * + * @param[in] partition The storage partition. + * + */ +void esp_openthread_flash_set_partition(const esp_partition_t *partition); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_openthread_ncp.h b/components/openthread/private_include/esp_openthread_ncp.h new file mode 100644 index 0000000000..922d6ab5f6 --- /dev/null +++ b/components/openthread/private_include/esp_openthread_ncp.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_openthread.h" + +#include "lib/spinel/spinel.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + SPINEL_PROP_VENDOR_ESP_OTA_TRIGGER = (SPINEL_PROP_VENDOR_ESP__BEGIN + 0), + SPINEL_PROP_VENDOR_ESP_OTA_DATA = (SPINEL_PROP_VENDOR_ESP__BEGIN + 1), + SPINEL_PROP_VENDOR_ESP_OTA_FINISH = (SPINEL_PROP_VENDOR_ESP__BEGIN + 2), +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_openthread_netif_glue_priv.h b/components/openthread/private_include/esp_openthread_netif_glue_priv.h new file mode 100644 index 0000000000..8e06d5fc86 --- /dev/null +++ b/components/openthread/private_include/esp_openthread_netif_glue_priv.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_openthread.h" +#include "openthread/instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The state handler to be called when OpenThread state changes + * + * @param[in] changed_flags The changed Openthread states + * @param[in] ctx A pointer to application-specific context + * + */ +void esp_openthread_netif_glue_state_callback(otChangedFlags changed_flags, void *ctx); + +/** + * @brief This function updates the netif fds and timeouts to the main loop. + * + * @param[inout] mainloop The main loop context. + * + */ +void esp_openthread_netif_glue_update(esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This function performs the netif process. + * + * @param[in] instance The OpenThread instance. + * @param[in] mainloop The main loop context. + * + * @return + * - ESP_OK on success + * - ESP_FAIL on OpenThread failure + * - ESP_ERR_NO_MEM on memory allocation failure + * + */ +esp_err_t esp_openthread_netif_glue_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_openthread_platform.h b/components/openthread/private_include/esp_openthread_platform.h new file mode 100644 index 0000000000..4c7f4ddcb4 --- /dev/null +++ b/components/openthread/private_include/esp_openthread_platform.h @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_openthread_types.h" +#include "openthread/error.h" +#include "openthread/instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WORKFLOW_MAX_NAMELEN 16 + +/** + * @brief update function declaration + * + * @param[inout] mainloop The main loop context. + * + */ +typedef void (*esp_openthread_update_func)(esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief process function declaration + * + * @param[in] instance The OpenThread instance. + * @param[in] mainloop The main loop context. + * + * @return + * - ESP_OK + * - ESP_FAIL + * + */ +typedef esp_err_t (*esp_openthread_process_func)(otInstance *instance, + const esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This struct contains a platform update function, a platform process function + * and the workflow name + * + * @note The structs will form a list, and the update functions and process functions in the list + * will be called in esp_openthread_platform_update and esp_openthread_platform_process. + * + */ +typedef struct esp_openthread_platform_workflow { + char name[WORKFLOW_MAX_NAMELEN]; + esp_openthread_update_func update_func; + esp_openthread_process_func process_func; + struct esp_openthread_platform_workflow *next; +} esp_openthread_platform_workflow_t; + +/** + * @brief This function adds a esp_openthread_platform_workflow to the workflow list + * + * + * @param[in] updatefcn The update function of the workflow added to the list. + * @param[in] processfcn The process function of the workflow added to the list. + * @param[in] name The name of the added workflow + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM on allocation failure + * - ESP_FAIL on other failures + * + */ +esp_err_t esp_openthread_platform_workflow_register(esp_openthread_update_func update_func, + esp_openthread_process_func process_func, const char *name); + +/** + * @brief This function removes a esp_openthread_platform_workflow according the input name + * from the workflow list + * + * @param[in] name The name of the removed workflow + * + */ +void esp_openthread_platform_workflow_unregister(const char *name); + +/** + * @brief Initializes the platform-specific support for the OpenThread stack. + * + * @note This function is not called by and will not call the OpenThread library. + * The user needs to call otInstanceInitSingle to intialize the OpenThread + * stack after calling this function. + * + * @param[in] init_config The initialization configuration. + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if allocation has failed + * - ESP_ERR_INVALID_ARG if radio or host connection mode not supported + * - ESP_ERR_INVALID_STATE if already initialized + * + */ +esp_err_t esp_openthread_platform_init(const esp_openthread_platform_config_t *init_config); + +/** + * This function performs all platform-specific deinitialization for OpenThread's drivers. + * + * @note This function is not called by the OpenThread library. Instead, the user should + * call this function when deinitialization of OpenThread's drivers is most appropriate. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if not initialized + * + */ +esp_err_t esp_openthread_platform_deinit(void); + +/** + * @brief This function updates the platform fds and timeouts + * + * @note This function will not update the OpenThread core stack pending events. + * The users need to call `otTaskletsArePending` to check whether there being + * pending OpenThread tasks. + * + * @param[inout] mainloop The main loop context. + * + */ +void esp_openthread_platform_update(esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This function performs the OpenThread related platform process (radio, uart, alarm etc.) + * + * @note This function will call the OpenThread core stack process functions. + * The users need to call `otTaskletsProcess` by self. + * + * @param[in] instance The OpenThread instance. + * @param[in] mainloop The main loop context. + * + * @return + * - ESP_OK on success + * - ESP_FAIL on failure + * + */ +esp_err_t esp_openthread_platform_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop); + +#ifdef __cplusplus +} // end of extern "C" +#endif diff --git a/components/openthread/private_include/esp_openthread_radio.h b/components/openthread/private_include/esp_openthread_radio.h new file mode 100644 index 0000000000..7cf41a559b --- /dev/null +++ b/components/openthread/private_include/esp_openthread_radio.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "esp_err.h" +#include "esp_openthread_types.h" +#include "openthread/instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief This function initializes the OpenThread radio. + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if allocation has failed + * + */ +esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *config); + +/** + * @brief This function deinitializes the OpenThread radio. + * + */ +void esp_openthread_radio_deinit(void); + +/** + * @brief This function updates the radio fds and timeouts to the main loop. + * + * @param[inout] mainloop The main loop context. + * + */ +void esp_openthread_radio_update(esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This function performs the OpenThread radio process. + * + * @param[in] instance The OpenThread instance. + * @param[in] mainloop The main loop context. + * + * @return + * - ESP_OK on success + * - ESP_FAIL on failure + * + */ +esp_err_t esp_openthread_radio_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_openthread_task_queue.h b/components/openthread/private_include/esp_openthread_task_queue.h new file mode 100644 index 0000000000..da115beb0a --- /dev/null +++ b/components/openthread/private_include/esp_openthread_task_queue.h @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_openthread.h" +#include "esp_openthread_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief OpenThread task declaration + * + */ +typedef void (*esp_openthread_task_t)(void *); + +/** + * @brief This function allocs and initializes the OpenThread task queue. + * + * @param[in] config The platform configuration + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM on queue allocation failure + * - ESP_FAIL on other failures + * + */ +esp_err_t esp_openthread_task_queue_init(const esp_openthread_platform_config_t *config); + +/** + * @brief This function posts a task to the OpenThread task queue. + * + * @param[in] task The task to execute. + * @param[in] arg The context argument to be passed to the task. + * + * @return + * - ESP_OK + * - ESP_FAIL + * + */ +esp_err_t esp_openthread_task_queue_post(esp_openthread_task_t task, void *arg); + +/** + * @brief This function updates the task queue inner fd to the main loop. + * + * @param[inout] mainloop The main loop context. + * + */ +void esp_openthread_task_queue_update(esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This function drives the execution of the task queue. + * + * @param[in] instance The OpenThread instance. + * @param[in] mainloop The main loop context. + * + * @return + * - ESP_OK + * - ESP_FAIL + * + */ +esp_err_t esp_openthread_task_queue_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This function deinitializes the task queue. + * + * @return + * - ESP_OK + * - ESP_FAIL + * + */ +esp_err_t esp_openthread_task_queue_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_openthread_uart.h b/components/openthread/private_include/esp_openthread_uart.h new file mode 100644 index 0000000000..3d2352d82d --- /dev/null +++ b/components/openthread/private_include/esp_openthread_uart.h @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_openthread_types.h" +#include "openthread/instance.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initializes an uart port with the given config. + * + * @note The user still needs to open the file descriptor by self. + * + * @param[in] config The uart configuration. + * + * @return + * - ESP_OK on success + * - ESP_ERROR on failure + * + */ +esp_err_t esp_openthread_uart_init_port(const esp_openthread_uart_config_t *config); + +/** + * @brief Initializes the uart for OpenThread host connection. + * + * @param[in] config The uart configuration. + * + * @return + * - ESP_OK on success + * - ESP_ERROR on failure + * + */ +esp_err_t esp_openthread_uart_init(const esp_openthread_platform_config_t *config); + +/** + * @brief Deintializes the uart for OpenThread host connection. + * + */ +void esp_openthread_uart_deinit(void); + +/** + * @brief Deintializes the uart for OpenThread host connection. + * + * @param[inout] mainloop The main loop context. + * + */ +void esp_openthread_uart_update(esp_openthread_mainloop_context_t *context); + +/** + * @brief Performs the uart I/O for OpenThread. + * + * @param[in] instance The Openthread instance. + * @param[in] mainloop The main loop context. + * + * @return + * - ESP_OK on success + * - ESP_ERROR on failure + * + */ +esp_err_t esp_openthread_uart_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/private_include/esp_uart_spinel_interface.hpp b/components/openthread/private_include/esp_uart_spinel_interface.hpp new file mode 100644 index 0000000000..d1d0e7a938 --- /dev/null +++ b/components/openthread/private_include/esp_uart_spinel_interface.hpp @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_openthread.h" +#include "esp_openthread_types.h" +#include "hal/uart_types.h" +#include "lib/spinel/spinel_interface.hpp" +#include "openthread/error.h" + +namespace esp { +namespace openthread { + +/** + * This class defines an UART interface to the Radio Co-processor (RCP). + * + */ +class UartSpinelInterface { +public: + /** + * @brief This constructor of object. + * + * @param[in] callback Callback on frame received + * @param[in] callback_context Callback context + * @param[in] frame_buffer A reference to a `RxFrameBuffer` object. + * + */ + UartSpinelInterface(ot::Spinel::SpinelInterface::ReceiveFrameCallback callback, void *callback_context, + ot::Spinel::SpinelInterface::RxFrameBuffer &frame_buffer); + + /** + * @brief This destructor of the object. + * + */ + ~UartSpinelInterface(void); + + /** + * @brief This method initializes the HDLC interface. + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if allocation has failed + * - ESP_ERROR on failure + */ + esp_err_t Init(const esp_openthread_uart_config_t &radio_uart_config); + + /** + * @brief This method deinitializes the HDLC interface. + * + */ + esp_err_t Deinit(void); + + /** + * @brief This method encodes and sends a spinel frame to Radio Co-processor (RCP) over the socket. + * + * @note This is blocking call, i.e., if the socket is not writable, this method waits for it to become writable + * for up to `kMaxWaitTime` interval. + * + * @param[in] frame A pointer to buffer containing the spinel frame to send. + * @param[in] length The length (number of bytes) in the frame. + * + * @return + * -OT_ERROR_NONE Successfully encoded and sent the spinel frame. + * -OT_ERROR_NO_BUFS Insufficient buffer space available to encode the frame. + * -OT_ERROR_FAILED Failed to send due to socket not becoming writable within `kMaxWaitTime`. + * + */ + otError SendFrame(const uint8_t *frame, uint16_t length); + + /** + * This method waits for receiving part or all of spinel frame within specified timeout. + * + * @param[in] timeout_us The timeout value in microseconds. + * + * @return + * -OT_ERROR_NONE Part or all of spinel frame is received. + * -OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p timeout_us. + * + */ + otError WaitForFrame(uint64_t timeout_us); + + /** + * This method performs uart processing to the RCP. + * + * @param[in] mainloop The mainloop context + * + */ + void Process(const esp_openthread_mainloop_context_t &mainloop); + + /** + * This methods updates the mainloop context. + * + * @param[inout] mainloop The mainloop context. + * + */ + void Update(esp_openthread_mainloop_context_t &mainloop); + + /** + * This methods registers the callback for RCP failure. + * + * @param[in] handler The RCP failure handler. + * + */ + void RegisterRcpFailureHandler(esp_openthread_rcp_failure_handler handler) { mRcpFailureHandler = handler; } + + void OnRcpReset(void) + { + if (mRcpFailureHandler) { + mRcpFailureHandler(); + } + } + + otError ResetConnection(void) { return OT_ERROR_NONE; } + +private: + enum { + /** + * Maximum spinel frame size. + * + */ + kMaxFrameSize = ot::Spinel::SpinelInterface::kMaxFrameSize, + + /** + * Maximum wait time in Milliseconds for socket to become writable (see `SendFrame`). + * + */ + kMaxWaitTime = 2000, + }; + + esp_err_t InitUart(const esp_openthread_uart_config_t &radio_uart_config); + + esp_err_t DeinitUart(void); + + int TryReadAndDecode(void); + + otError WaitForWritable(void); + + otError Write(const uint8_t *frame, uint16_t length); + + esp_err_t TryRecoverUart(void); + + static void HandleHdlcFrame(void *context, otError error); + void HandleHdlcFrame(otError error); + + ot::Spinel::SpinelInterface::ReceiveFrameCallback m_receiver_frame_callback; + void *m_receiver_frame_context; + ot::Spinel::SpinelInterface::RxFrameBuffer &m_receive_frame_buffer; + + ot::Hdlc::Decoder m_hdlc_decoder; + uint8_t *m_uart_rx_buffer; + + esp_openthread_uart_config_t m_uart_config; + int m_uart_fd; + + // Non-copyable, intentionally not implemented. + UartSpinelInterface(const UartSpinelInterface &); + UartSpinelInterface &operator=(const UartSpinelInterface &); + + esp_openthread_rcp_failure_handler mRcpFailureHandler; +}; + +} // namespace openthread +} // namespace esp diff --git a/components/openthread/src/esp_openthread_stubs.c b/components/openthread/src/esp_openthread_stubs.c deleted file mode 100644 index c129d04e85..0000000000 --- a/components/openthread/src/esp_openthread_stubs.c +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include - -OT_TOOL_WEAK void otPlatUartReceived(const uint8_t *, uint16_t) -{ -} - -OT_TOOL_WEAK void otPlatUartSendDone(void) -{ -} - -OT_TOOL_WEAK void otPlatDiagRadioTransmitDone(otInstance *, otRadioFrame *, otError) -{ -} - -OT_TOOL_WEAK void otPlatDiagRadioReceiveDone(otInstance *, otRadioFrame *, otError) -{ -} - -OT_TOOL_WEAK void otPlatDiagAlarmFired(otInstance *) -{ -}