openthread: add SPI support in Radio Co-Processor

This commit is contained in:
xieqinan 2022-11-16 16:59:57 +08:00
parent 6cac537791
commit 5a1578e692
20 changed files with 812 additions and 57 deletions

View File

@ -111,9 +111,11 @@ if(CONFIG_OPENTHREAD_ENABLED)
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)
"port/esp_openthread_radio_spinel.cpp"
"port/esp_spi_spinel_interface.cpp"
"port/esp_uart_spinel_interface.cpp"
)
elseif(CONFIG_OPENTHREAD_RADIO_SPINEL_UART OR CONFIG_OPENTHREAD_RADIO_SPINEL_SPI)
list(APPEND exclude_srcs
"port/esp_openthread_radio.c")
endif()
@ -166,9 +168,9 @@ idf_component_register(SRC_DIRS "${src_dirs}"
EXCLUDE_SRCS "${exclude_srcs}"
INCLUDE_DIRS "${public_include_dirs}"
PRIV_INCLUDE_DIRS "${private_include_dirs}"
REQUIRES esp_netif lwip
REQUIRES esp_netif lwip driver
LDFRAGMENTS linker.lf
PRIV_REQUIRES console driver esp_event esp_partition esp_timer
PRIV_REQUIRES console esp_event esp_partition esp_timer
ieee802154 mbedtls spi_flash)
if(CONFIG_OPENTHREAD_ENABLED)

View File

@ -61,6 +61,12 @@ menu "OpenThread"
bool "Connect via UART"
help
Select this to connect to a Radio Co-Processor via UART.
config OPENTHREAD_RADIO_SPINEL_SPI
bool "Connect via SPI"
help
Select this to connect to a Radio Co-Processor via SPI.
endchoice
choice OPENTHREAD_DEVICE_TYPE
@ -89,6 +95,22 @@ menu "OpenThread"
radio only device.
endchoice
choice OPENTHREAD_RCP_TRANSPORT
prompt "The RCP transport type"
depends on OPENTHREAD_RADIO
default OPENTHREAD_RCP_UART
config OPENTHREAD_RCP_UART
bool "UART RCP"
help
Select this to enable UART connection to host.
config OPENTHREAD_RCP_SPI
bool "SPI RCP"
help
Select this to enable SPI connection to host.
endchoice
config OPENTHREAD_CLI
bool "Enable Openthread Command-Line Interface"
depends on OPENTHREAD_ENABLED

View File

@ -11,6 +11,10 @@
#include <sys/select.h>
#include "esp_event_base.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/spi_slave.h"
#include "hal/gpio_types.h"
#include "hal/uart_types.h"
#ifdef __cplusplus
@ -60,10 +64,33 @@ typedef struct {
typedef struct {
uart_port_t port; /*!< UART port number */
uart_config_t uart_config; /*!< UART configuration, see uart_config_t docs */
int rx_pin; /*!< UART RX pin */
int tx_pin; /*!< UART TX pin */
gpio_num_t rx_pin; /*!< UART RX pin */
gpio_num_t tx_pin; /*!< UART TX pin */
} esp_openthread_uart_config_t;
/**
* @brief The spi port config for OpenThread.
*
*/
typedef struct {
spi_host_device_t host_device; /*!< SPI host device */
spi_dma_chan_t dma_channel; /*!< DMA channel */
spi_bus_config_t spi_interface; /*!< SPI bus */
spi_device_interface_config_t spi_device; /*!< SPI peripheral device */
gpio_num_t intr_pin; /*!< SPI interrupt pin */
} esp_openthread_spi_host_config_t;
/**
* @brief The spi slave config for OpenThread.
*
*/
typedef struct {
spi_host_device_t host_device; /*!< SPI host device */
spi_bus_config_t bus_config; /*!< SPI bus config */
spi_slave_interface_config_t slave_config; /*!< SPI slave config */
gpio_num_t intr_pin; /*!< SPI interrupt pin */
} esp_openthread_spi_slave_config_t;
/**
* @brief The radio mode of OpenThread.
*
@ -82,6 +109,7 @@ typedef enum {
HOST_CONNECTION_MODE_NONE = 0x0, /*!< Disable host connection */
HOST_CONNECTION_MODE_CLI_UART = 0x1, /*!< CLI UART connection to the host */
HOST_CONNECTION_MODE_RCP_UART = 0x2, /*!< RCP UART connection to the host */
HOST_CONNECTION_MODE_RCP_SPI = 0x3, /*!< RCP SPI connection to the host */
} esp_openthread_host_connection_mode_t;
/**
@ -90,7 +118,10 @@ typedef enum {
*/
typedef struct {
esp_openthread_radio_mode_t radio_mode; /*!< The radio mode */
union {
esp_openthread_uart_config_t radio_uart_config; /*!< The uart configuration to RCP */
esp_openthread_spi_host_config_t radio_spi_config; /*!< The spi configuration to RCP */
};
} esp_openthread_radio_config_t;
/**
@ -99,7 +130,10 @@ typedef struct {
*/
typedef struct {
esp_openthread_host_connection_mode_t host_connection_mode; /*!< The host connection mode */
union {
esp_openthread_uart_config_t host_uart_config; /*!< The uart configuration to host */
esp_openthread_spi_slave_config_t spi_slave_config; /*!< The spi configuration to host */
};
} esp_openthread_host_connection_config_t;
/**

View File

@ -8,3 +8,6 @@ entries:
link_metrics (noflash_text)
mac (noflash_text)
sub_mac (noflash_text)
if OPENTHREAD_RCP_SPI = y:
ncp_spi (noflash_text)

View File

@ -14,6 +14,7 @@
#include "esp_openthread_flash.h"
#include "esp_openthread_lock.h"
#include "esp_openthread_radio.h"
#include "esp_openthread_spi_slave.h"
#include "esp_openthread_task_queue.h"
#include "esp_openthread_types.h"
#include "esp_openthread_uart.h"
@ -87,30 +88,40 @@ void esp_openthread_platform_workflow_unregister(const char *name)
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,
config->radio_config.radio_mode == RADIO_MODE_UART_RCP ||
config->radio_config.radio_mode == RADIO_MODE_SPI_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,
config->host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_UART ||
config->host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_SPI,
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;
/* Avoid to compile flash in RADIO type device */
#if !CONFIG_OPENTHREAD_RADIO
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");
esp_openthread_flash_set_partition(partition);
#endif
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 ||
if (config->host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_SPI) {
ESP_GOTO_ON_ERROR(esp_openthread_spi_slave_init(config), exit, OT_PLAT_LOG_TAG,
"esp_openthread_spi_slave_init failed");
}else 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");
@ -135,9 +146,14 @@ esp_err_t esp_openthread_platform_deinit(void)
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) {
if (s_platform_config.host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_SPI){
esp_openthread_spi_slave_deinit();
}else if (s_platform_config.host_config.host_connection_mode == HOST_CONNECTION_MODE_CLI_UART ||
s_platform_config.host_config.host_connection_mode == HOST_CONNECTION_MODE_RCP_UART) {
esp_openthread_uart_deinit();
}
esp_openthread_lock_deinit();
esp_openthread_alarm_deinit();
return ESP_OK;

View File

@ -736,3 +736,12 @@ otError otPlatEntropyGet(uint8_t *aOutput, uint16_t aOutputLength)
return OT_ERROR_NONE;
}
otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aChannel);
OT_UNUSED_VARIABLE(aMaxPower);
return OT_ERROR_NONE;
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -13,6 +13,7 @@
#include "esp_openthread_platform.h"
#include "esp_openthread_types.h"
#include "esp_system.h"
#include "esp_spi_spinel_interface.hpp"
#include "esp_uart_spinel_interface.hpp"
#include "openthread-core-config.h"
#include "lib/spinel/radio_spinel.hpp"
@ -20,19 +21,30 @@
#include "openthread/platform/diag.h"
#include "openthread/platform/radio.h"
using esp::openthread::UartSpinelInterface;
using ot::Spinel::RadioSpinel;
#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
using esp::openthread::UartSpinelInterface;
static RadioSpinel<UartSpinelInterface, esp_openthread_mainloop_context_t> s_radio;
static const char *radiouart_workflow = "radio_uart";
#else // CONFIG_OPENTHREAD_RADIO_SPINEL_SPI
using esp::openthread::SpiSpinelInterface;
static RadioSpinel<SpiSpinelInterface, esp_openthread_mainloop_context_t> s_radio;
#endif // CONFIG_OPENTHREAD_RADIO_SPINEL_UART
static const char *radiospinel_workflow = "radio_spinel";
esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *config)
{
#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
ESP_RETURN_ON_ERROR(s_radio.GetSpinelInterface().Init(config->radio_config.radio_uart_config), OT_PLAT_LOG_TAG,
"Spinel interface init falied");
#else // CONFIG_OPENTHREAD_RADIO_SPINEL_SPI
ESP_RETURN_ON_ERROR(s_radio.GetSpinelInterface().Init(config->radio_config.radio_spi_config), OT_PLAT_LOG_TAG,
"Spinel interface init failed");
#endif // CONFIG_OPENTHREAD_RADIO_SPINEL_UART
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);
radiospinel_workflow);
}
void esp_openthread_register_rcp_failure_handler(esp_openthread_rcp_failure_handler handler)
@ -48,7 +60,7 @@ void esp_openthread_rcp_deinit(void)
void esp_openthread_radio_deinit(void)
{
s_radio.Deinit();
esp_openthread_platform_workflow_unregister(radiouart_workflow);
esp_openthread_platform_workflow_unregister(radiospinel_workflow);
}
esp_err_t esp_openthread_radio_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop)

View File

@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/* SPI Slave example, receiver (uses SPI Slave driver to communicate with sender)
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <openthread/platform/spi-slave.h>
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_openthread_common_macro.h"
#include "esp_openthread_task_queue.h"
#include "esp_openthread_types.h"
#include "esp_rom_sys.h"
#include <stdint.h>
#include <string.h>
#include "driver/gpio.h"
#include "driver/spi_slave.h"
#include "esp_private/cache_utils.h"
#include "esp_private/spi_slave_internal.h"
#include "ncp/ncp_config.h"
#include "openthread/error.h"
static const char *SPI_SLAVE_TAG = "spi_slave";
static void *s_context = NULL;
static uint8_t *s_prev_output_buf;
static uint16_t s_prev_output_len;
static uint8_t *s_prev_input_buf;
static uint16_t s_prev_input_len;
static bool s_request_transaction = false;
static esp_openthread_spi_slave_config_t s_spi_config;
static otPlatSpiSlaveTransactionProcessCallback s_process_callback = NULL;
static otPlatSpiSlaveTransactionCompleteCallback s_complete_callback = NULL;
static spi_slave_transaction_t s_spi_transaction;
typedef struct {
uint16_t output_buf_len;
uint16_t input_buf_len;
} pending_transaction_t;
static void IRAM_ATTR handle_spi_setup_done(spi_slave_transaction_t *trans)
{
if (s_request_transaction) {
gpio_set_level(s_spi_config.intr_pin, 0);
}
}
static void IRAM_ATTR handle_spi_transaction_done(spi_slave_transaction_t *trans)
{
gpio_set_level(s_spi_config.intr_pin, 1);
pending_transaction_t *pending_transaction = (pending_transaction_t *)&(trans->user);
trans->trans_len /= CHAR_BIT;
if (s_complete_callback &&
s_complete_callback(s_context, (uint8_t *)trans->tx_buffer, pending_transaction->output_buf_len,
trans->rx_buffer, pending_transaction->input_buf_len, trans->trans_len)) {
esp_openthread_task_queue_post(s_process_callback, s_context);
}
trans = NULL;
}
esp_err_t esp_openthread_spi_slave_init(const esp_openthread_platform_config_t *config)
{
s_spi_config = config->host_config.spi_slave_config;
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1 << s_spi_config.intr_pin),
};
ESP_RETURN_ON_ERROR(gpio_config(&io_conf), OT_PLAT_LOG_TAG, "fail to configure SPI gpio");
gpio_set_pull_mode(s_spi_config.bus_config.mosi_io_num, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(s_spi_config.bus_config.sclk_io_num, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(s_spi_config.slave_config.spics_io_num, GPIO_PULLUP_ONLY);
/* Initialize SPI slave interface */
s_spi_config.slave_config.post_setup_cb = handle_spi_setup_done;
s_spi_config.slave_config.post_trans_cb = handle_spi_transaction_done;
ESP_RETURN_ON_ERROR(spi_slave_initialize(s_spi_config.host_device, &s_spi_config.bus_config,
&s_spi_config.slave_config, SPI_DMA_CH_AUTO),
OT_PLAT_LOG_TAG, "fail to initialize SPI slave");
return ESP_OK;
}
void esp_openthread_spi_slave_deinit(void)
{
spi_slave_free(s_spi_config.host_device);
s_spi_config.slave_config.post_setup_cb = NULL;
s_spi_config.slave_config.post_trans_cb = NULL;
return;
}
otError otPlatSpiSlaveEnable(otPlatSpiSlaveTransactionCompleteCallback aCompleteCallback,
otPlatSpiSlaveTransactionProcessCallback aProcessCallback, void *aContext)
{
s_process_callback = aProcessCallback;
s_complete_callback = aCompleteCallback;
s_context = aContext;
return OT_ERROR_NONE;
}
otError IRAM_ATTR otPlatSpiSlavePrepareTransaction(uint8_t *aOutputBuf, uint16_t aOutputBufLen, uint8_t *aInputBuf,
uint16_t aInputBufLen, bool aRequestTransactionFlag)
{
esp_err_t trans_state = ESP_OK;
pending_transaction_t *pending_transaction = NULL;
if (aOutputBuf == NULL) {
aOutputBuf = s_prev_output_buf;
aOutputBufLen = s_prev_output_len;
}
if (aInputBuf == NULL) {
aInputBuf = s_prev_input_buf;
aInputBufLen = s_prev_input_len;
}
s_prev_output_buf = aOutputBuf;
s_prev_output_len = aOutputBufLen;
s_prev_input_buf = aInputBuf;
s_prev_input_len = aInputBufLen;
s_spi_transaction.length = aOutputBufLen > aInputBufLen ? aOutputBufLen : aInputBufLen;
s_spi_transaction.length *= CHAR_BIT;
s_spi_transaction.rx_buffer = aInputBuf;
s_spi_transaction.tx_buffer = aOutputBuf;
assert(sizeof(s_spi_transaction.user) >= sizeof(pending_transaction_t));
pending_transaction = (pending_transaction_t *)&(s_spi_transaction.user);
pending_transaction->input_buf_len = aInputBufLen;
pending_transaction->output_buf_len = aOutputBufLen;
s_spi_transaction.user = pending_transaction;
s_request_transaction = aRequestTransactionFlag;
if ((gpio_get_level(s_spi_config.slave_config.spics_io_num) == 0)) {
ESP_EARLY_LOGE(SPI_SLAVE_TAG, "SPI busy");
return OT_ERROR_BUSY;
}
if (xPortCanYield()) {
spi_slave_queue_reset(s_spi_config.host_device);
trans_state = spi_slave_queue_trans(s_spi_config.host_device, &s_spi_transaction, 0);
} else {
spi_slave_queue_reset_isr(s_spi_config.host_device);
trans_state = spi_slave_queue_trans_isr(s_spi_config.host_device, &s_spi_transaction);
}
if (trans_state == ESP_OK) {
return OT_ERROR_NONE;
} else {
return OT_ERROR_FAILED;
}
}

View File

@ -13,6 +13,7 @@
#include "esp_vfs.h"
#include "esp_vfs_eventfd.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/queue.h"
static QueueHandle_t s_task_queue = NULL;
@ -36,7 +37,7 @@ esp_err_t esp_openthread_task_queue_init(const esp_openthread_platform_config_t
&esp_openthread_task_queue_process, task_queue_workflow);
}
esp_err_t esp_openthread_task_queue_post(esp_openthread_task_t task, void *arg)
esp_err_t IRAM_ATTR esp_openthread_task_queue_post(esp_openthread_task_t task, void *arg)
{
task_storage_t task_storage = {
.task = task,
@ -44,12 +45,21 @@ esp_err_t esp_openthread_task_queue_post(esp_openthread_task_t task, void *arg)
};
uint64_t val = 1;
ssize_t ret;
BaseType_t task_woken = pdFALSE;
if (!xPortCanYield()) {
ESP_RETURN_ON_FALSE_ISR(xQueueSendFromISR(s_task_queue, &task_storage, &task_woken), ESP_FAIL, OT_PLAT_LOG_TAG,
"Failed to post task to OpenThread task queue");
} else {
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));
if (task_woken) {
portYIELD_FROM_ISR_NO_ARG();
}
return ESP_OK;
}

View File

@ -27,6 +27,7 @@ static int s_uart_fd;
static uint8_t s_uart_buffer[ESP_OPENTHREAD_UART_BUFFER_SIZE];
static const char *uart_workflow = "uart";
#if (CONFIG_OPENTHREAD_CLI || (CONFIG_OPENTHREAD_RADIO && CONFIG_OPENTHREAD_RCP_UART))
otError otPlatUartEnable(void)
{
return OT_ERROR_NONE;
@ -54,6 +55,7 @@ otError otPlatUartSend(const uint8_t *buf, uint16_t buf_length)
return OT_ERROR_NONE;
}
#endif
esp_err_t esp_openthread_uart_init_port(const esp_openthread_uart_config_t *config)
{
@ -117,7 +119,7 @@ esp_err_t esp_openthread_uart_process(otInstance *instance, const esp_openthread
int rval = read(s_uart_fd, s_uart_buffer, sizeof(s_uart_buffer));
if (rval > 0) {
#if CONFIG_OPENTHREAD_CLI || CONFIG_OPENTHREAD_RADIO
#if (CONFIG_OPENTHREAD_CLI || (CONFIG_OPENTHREAD_RADIO && CONFIG_OPENTHREAD_RCP_UART))
otPlatUartReceived(s_uart_buffer, (uint16_t)rval);
#endif
} else if (rval < 0) {

View File

@ -0,0 +1,252 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_spi_spinel_interface.hpp"
#include "error.h"
#include "esp_check.h"
#include "esp_openthread_common_macro.h"
#include "esp_rom_sys.h"
#include "esp_vfs.h"
#include "esp_vfs_eventfd.h"
#include <stdint.h>
#include "common/logging.hpp"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "hal/gpio_types.h"
#include "ncp/ncp_spi.hpp"
using ot::Ncp::SpiFrame;
using ot::Spinel::SpinelInterface;
namespace esp {
namespace openthread {
SpiSpinelInterface::SpiSpinelInterface(SpinelInterface::ReceiveFrameCallback callback, void *callback_context,
SpinelInterface::RxFrameBuffer &frame_buffer)
: m_event_fd(-1)
, m_receiver_frame_callback(callback)
, m_receiver_frame_context(callback_context)
, m_receive_frame_buffer(frame_buffer)
, mRcpFailureHandler(nullptr)
{
}
esp_err_t SpiSpinelInterface::Init(const esp_openthread_spi_host_config_t &spi_config)
{
ESP_RETURN_ON_FALSE(m_event_fd < 0, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, "event fd was initialized");
m_spi_config = spi_config;
ESP_RETURN_ON_ERROR(spi_bus_initialize(spi_config.host_device, &spi_config.spi_interface, SPI_DMA_CH_AUTO),
OT_PLAT_LOG_TAG, "fail to initialize spi bus");
ESP_RETURN_ON_ERROR(spi_bus_add_device(spi_config.host_device, &spi_config.spi_device, &m_device), OT_PLAT_LOG_TAG,
"fail to add spi bus device");
gpio_config_t io_conf;
memset(&io_conf, 0, sizeof(io_conf));
io_conf.intr_type = GPIO_INTR_NEGEDGE;
io_conf.pin_bit_mask = (1ULL << spi_config.intr_pin);
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
ESP_RETURN_ON_ERROR(gpio_config(&io_conf), OT_PLAT_LOG_TAG, "fail to config spi gpio");
ESP_RETURN_ON_ERROR(gpio_install_isr_service(0), OT_PLAT_LOG_TAG, "fail to install gpio isr service");
ESP_RETURN_ON_ERROR(gpio_isr_handler_add(spi_config.intr_pin, GpioIntrHandler, this), OT_PLAT_LOG_TAG,
"fail to add gpio isr handler");
m_has_pending_device_frame = false;
m_event_fd = eventfd(0, EFD_SUPPORT_ISR);
m_pending_data_len = 0;
ESP_RETURN_ON_FALSE(m_event_fd >= 0, ESP_FAIL, OT_PLAT_LOG_TAG, "fail to get event fd");
ESP_LOGI(OT_PLAT_LOG_TAG, "spinel SPI interface initialization completed");
return ConductSPITransaction(true, 0, 0);
}
esp_err_t SpiSpinelInterface::Deinit(void)
{
if (m_event_fd >= 0) {
close(m_event_fd);
m_event_fd = -1;
ESP_RETURN_ON_ERROR(gpio_isr_handler_remove(m_spi_config.intr_pin), OT_PLAT_LOG_TAG,
"fail to remove gpio isr handler");
ESP_RETURN_ON_ERROR(spi_bus_remove_device(m_device), OT_PLAT_LOG_TAG, "fail to remove spi bus device");
ESP_RETURN_ON_ERROR(spi_bus_free(m_spi_config.host_device), OT_PLAT_LOG_TAG, "fail to free spi bus");
gpio_uninstall_isr_service();
}
return ESP_OK;
}
SpiSpinelInterface::~SpiSpinelInterface(void)
{
Deinit();
}
otError SpiSpinelInterface::SendFrame(const uint8_t *frame, uint16_t length)
{
ESP_RETURN_ON_FALSE(frame, OT_ERROR_INVALID_ARGS, OT_PLAT_LOG_TAG, "empty frame");
ESP_RETURN_ON_FALSE(length <= SpinelInterface::kMaxFrameSize, OT_ERROR_NO_BUFS, OT_PLAT_LOG_TAG,
"send frame is too long");
memcpy(&m_tx_buffer[kSPIFrameHeaderSize], frame, length);
uint16_t rx_data_size =
length < kSmallPacketSize ? kSmallPacketSize : length; // We'll use tx_size to receive small packets piggybacked
if (ConductSPITransaction(false, length, rx_data_size) == ESP_OK) {
return OT_ERROR_NONE;
} else {
return OT_ERROR_FAILED;
}
}
esp_err_t SpiSpinelInterface::ConductSPITransaction(bool reset, uint16_t tx_data_size, uint16_t rx_data_size)
{
ESP_RETURN_ON_FALSE(tx_data_size <= kSPIFrameSize && rx_data_size <= kSPIFrameSize, ESP_ERR_INVALID_ARG,
OT_PLAT_LOG_TAG, "invalid arguments");
SpiFrame tx_frame(m_tx_buffer);
tx_frame.SetHeaderFlagByte(reset);
tx_frame.SetHeaderDataLen(tx_data_size);
tx_frame.SetHeaderAcceptLen(rx_data_size);
uint8_t *rx_buffer;
otError err = m_receive_frame_buffer.SetSkipLength(kSPIFrameHeaderSize);
ESP_RETURN_ON_FALSE(err == OT_ERROR_NONE, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "buffer space is insufficient");
rx_buffer = m_receive_frame_buffer.GetFrame() - kSPIFrameHeaderSize;
if (m_receive_frame_buffer.GetFrameMaxLength() < rx_data_size) {
rx_data_size = m_receive_frame_buffer.GetFrameMaxLength();
}
uint16_t data_size = tx_data_size > rx_data_size ? tx_data_size : rx_data_size;
data_size += kSPIFrameHeaderSize;
spi_transaction_t transaction;
memset(&transaction, 0, sizeof(transaction));
transaction.length = data_size * CHAR_BIT;
transaction.rxlength = (rx_data_size + kSPIFrameHeaderSize) * CHAR_BIT;
transaction.tx_buffer = m_tx_buffer;
transaction.rx_buffer = rx_buffer;
ESP_RETURN_ON_ERROR(spi_device_polling_transmit(m_device, &transaction), OT_PLAT_LOG_TAG, "SPI transaction failed");
SpiFrame rx_frame(rx_buffer);
if (!rx_frame.IsValid() || rx_frame.GetHeaderAcceptLen() > kSPIFrameSize ||
rx_frame.GetHeaderDataLen() > kSPIFrameSize) {
vTaskDelay(pdMS_TO_TICKS(15));
ESP_RETURN_ON_ERROR(spi_device_polling_transmit(m_device, &transaction), OT_PLAT_LOG_TAG,
"fail to retry SPI invalid transaction");
}
if (rx_frame.IsResetFlagSet()) {
ESP_LOGW(OT_PLAT_LOG_TAG, "RCP Reset");
m_receive_frame_buffer.DiscardFrame();
return ESP_OK;
}
if (rx_frame.GetHeaderDataLen() == 0 && rx_frame.GetHeaderAcceptLen() == 0) {
vTaskDelay(pdMS_TO_TICKS(15));
ESP_RETURN_ON_ERROR(spi_device_polling_transmit(m_device, &transaction), OT_PLAT_LOG_TAG,
"fail to retry SPI empty transaction");
}
if (rx_frame.GetHeaderDataLen() > 0 && rx_frame.GetHeaderDataLen() < tx_frame.GetHeaderAcceptLen()) {
if (gpio_get_level(m_spi_config.intr_pin) == 1) {
m_pending_data_len = 0;
}
if (m_receive_frame_buffer.SetLength(rx_frame.GetHeaderDataLen()) != OT_ERROR_NONE) {
ESP_LOGW(OT_PLAT_LOG_TAG, "insufficient buffer space to hold a frame of length %d...",
rx_frame.GetHeaderDataLen());
m_receive_frame_buffer.DiscardFrame();
return ESP_ERR_NO_MEM;
}
m_receiver_frame_callback(m_receiver_frame_context);
} else {
m_pending_data_len = 0;
m_receive_frame_buffer.DiscardFrame();
}
m_pending_data_len = 0;
return ESP_OK;
}
void SpiSpinelInterface::GpioIntrHandler(void *arg)
{
SpiSpinelInterface *instance = static_cast<SpiSpinelInterface *>(arg);
instance->m_pending_data_len = SpinelInterface::kMaxFrameSize;
uint64_t event = SpinelInterface::kMaxFrameSize;
write(instance->m_event_fd, &event, sizeof(event));
}
void SpiSpinelInterface::Update(esp_openthread_mainloop_context_t &mainloop)
{
if (m_pending_data_len > 0) {
mainloop.timeout.tv_sec = 0;
mainloop.timeout.tv_usec = 0;
}
FD_SET(m_event_fd, &mainloop.read_fds);
FD_SET(m_event_fd, &mainloop.error_fds);
if (m_event_fd > mainloop.max_fd) {
mainloop.max_fd = m_event_fd;
}
}
void SpiSpinelInterface::Process(const esp_openthread_mainloop_context_t &mainloop)
{
if (FD_ISSET(m_event_fd, &mainloop.error_fds)) {
ESP_LOGE(OT_PLAT_LOG_TAG, "SPI INTR GPIO error event");
return;
}
if (FD_ISSET(m_event_fd, &mainloop.read_fds)) {
uint64_t event;
read(m_event_fd, &event, sizeof(event));
m_pending_data_len = SpinelInterface::kMaxFrameSize;
if (ConductSPITransaction(false, 0, m_pending_data_len) != ESP_OK) {
ESP_LOGW(OT_PLAT_LOG_TAG, "fail to process SPI transaction");
}
}
return;
}
otError SpiSpinelInterface::WaitForFrame(uint64_t timeout_us)
{
fd_set read_fds, error_fds;
struct timeval timeout;
uint64_t event = 0;
if (m_pending_data_len == 0) {
FD_ZERO(&read_fds);
FD_ZERO(&error_fds);
FD_SET(m_event_fd, &read_fds);
FD_SET(m_event_fd, &error_fds);
timeout.tv_sec = timeout_us / US_PER_S;
timeout.tv_usec = timeout_us % US_PER_S;
int ret = select(m_event_fd + 1, &read_fds, NULL, &error_fds, &timeout);
if (ret <= 0 || !FD_ISSET(m_event_fd, &read_fds)) {
if (FD_ISSET(m_event_fd, &error_fds)) {
ESP_LOGW(OT_PLAT_LOG_TAG, "FD error!\n");
}
ESP_LOGW(OT_PLAT_LOG_TAG, "SPI transaction timeout for %llu us, result %d\n", timeout_us, ret);
return OT_ERROR_RESPONSE_TIMEOUT;
}
read(m_event_fd, &event, sizeof(event));
}
ESP_RETURN_ON_FALSE(ConductSPITransaction(false, 0, SpinelInterface::kMaxFrameSize) == ESP_OK, OT_ERROR_FAILED,
OT_PLAT_LOG_TAG, "fail to complete SPI transaction during wait for frame");
return OT_ERROR_NONE;
}
void SpiSpinelInterface::OnRcpReset(void)
{
if (mRcpFailureHandler) {
mRcpFailureHandler();
ConductSPITransaction(true, 0, 0); // clear
}
} // namespace openthread
} // namespace esp
}

View File

@ -45,12 +45,15 @@ UartSpinelInterface::~UartSpinelInterface(void)
esp_err_t UartSpinelInterface::Init(const esp_openthread_uart_config_t &radio_uart_config)
{
esp_err_t error = ESP_OK;
m_uart_rx_buffer = static_cast<uint8_t *>(heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_8BIT));
if (m_uart_rx_buffer == NULL) {
return ESP_ERR_NO_MEM;
}
return InitUart(radio_uart_config);
error = InitUart(radio_uart_config);
ESP_LOGI(OT_PLAT_LOG_TAG, "spinel UART interface initialization completed");
return error;
}
esp_err_t UartSpinelInterface::Deinit(void)
@ -282,5 +285,13 @@ esp_err_t UartSpinelInterface::TryRecoverUart(void)
return ESP_OK;
}
void UartSpinelInterface::OnRcpReset(void)
{
if (mRcpFailureHandler) {
mRcpFailureHandler();
TryRecoverUart();
}
}
} // namespace openthread
} // namespace esp

View File

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_openthread.h"
#include "esp_openthread_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief This function initializes the OpenThread spinel SPI slave.
*
*/
esp_err_t esp_openthread_spi_slave_init(const esp_openthread_platform_config_t *config);
/**
* @brief This function deinitializes the OpenThread spinel SPI slave.
*
*/
void esp_openthread_spi_slave_deinit(void);
#ifdef __cplusplus
} /* extern "C" */
#endif

View File

@ -0,0 +1,144 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "esp_openthread_types.h"
#include "driver/spi_master.h"
#include "lib/spinel/spinel_interface.hpp"
namespace esp {
namespace openthread {
class SpiSpinelInterface {
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.
*
*/
SpiSpinelInterface(ot::Spinel::SpinelInterface::ReceiveFrameCallback callback, void *callback_context,
ot::Spinel::SpinelInterface::RxFrameBuffer &frame_buffer);
/**
* @brief This destructor of the object.
*
*/
~SpiSpinelInterface(void);
/**
* @brief This method initializes the spinel interface.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if already initialized
* - ESP_ERR_NO_MEM if allocation has failed
* - ESP_FAIL on failure
*/
esp_err_t Init(const esp_openthread_spi_host_config_t &spi_config);
/**
* @brief This method deinitializes the HDLC interface.
*
* @return
* - ESP_OK on success
* - ESP_FAIL on failure
*/
esp_err_t Deinit(void);
/**
* @brief This method encodes and sends a spinel frame to Radio Co-processor (RCP) over the socket.
*
* @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 spi 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; }
/**
* This method is called when RCP is reset to recreate the connection with it.
* Intentionally empty.
*
*/
otError ResetConnection(void) { return OT_ERROR_NONE; }
/**
* This method is called when RCP failure detected and resets internal states of the interface.
*
*/
void OnRcpReset(void);
private:
static constexpr uint8_t kSPIFrameHeaderSize = 5;
static constexpr uint16_t kSPIFrameSize = ot::Spinel::SpinelInterface::kMaxFrameSize + kSPIFrameHeaderSize;
static constexpr uint8_t kSmallPacketSize = 32;
static constexpr uint16_t kSPIDataEvent = 1;
static void GpioIntrHandler(void *arg);
esp_err_t ConductSPITransaction(bool reset, uint16_t tx_data_size, uint16_t rx_data_size);
esp_openthread_spi_host_config_t m_spi_config;
uint8_t m_tx_buffer[kSPIFrameSize];
int m_event_fd;
volatile uint16_t m_pending_data_len;
ot::Spinel::SpinelInterface::ReceiveFrameCallback m_receiver_frame_callback;
void *m_receiver_frame_context;
ot::Spinel::SpinelInterface::RxFrameBuffer &m_receive_frame_buffer;
bool m_has_pending_device_frame;
spi_device_handle_t m_device;
esp_openthread_rcp_failure_handler mRcpFailureHandler;
};
} // namespace openthread
} // namespace esp

View File

@ -108,12 +108,7 @@ public:
*/
void RegisterRcpFailureHandler(esp_openthread_rcp_failure_handler handler) { mRcpFailureHandler = handler; }
void OnRcpReset(void)
{
if (mRcpFailureHandler) {
mRcpFailureHandler();
}
}
void OnRcpReset(void);
otError ResetConnection(void) { return OT_ERROR_NONE; }

View File

@ -224,7 +224,7 @@
*
*/
#ifndef OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT
#define OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT 1
#define OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT 3
#endif
/**

View File

@ -88,7 +88,7 @@
* Define to 1 to enable NCP SPI support.
*
*/
#define OPENTHREAD_CONFIG_NCP_SPI_ENABLE 0
#define OPENTHREAD_CONFIG_NCP_SPI_ENABLE CONFIG_OPENTHREAD_RCP_SPI
/**
* @def OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER
@ -104,7 +104,7 @@
* Define to 1 to enable NCP HDLC support.
*
*/
#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE CONFIG_OPENTHREAD_RCP_UART
/**
* @def PACKAGE_NAME

View File

@ -21,7 +21,7 @@
{ \
.radio_mode = RADIO_MODE_NATIVE, \
}
#else
#elif CONFIG_OPENTHREAD_RADIO_SPINEL_UART
#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \
{ \
.radio_mode = RADIO_MODE_UART_RCP, \
@ -41,7 +41,32 @@
.tx_pin = 5, \
}, \
}
#endif
#else
#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \
{ \
.radio_mode = RADIO_MODE_SPI_RCP, \
.radio_spi_config = { \
.host_device = SPI2_HOST, \
.dma_channel = 2, \
.spi_interface = \
{ \
.mosi_io_num = 11, \
.sclk_io_num = 12, \
.miso_io_num = 13, \
}, \
.spi_device = \
{ \
.cs_ena_pretrans = 2, \
.input_delay_ns = 100, \
.mode = 0, \
.clock_speed_hz = 2500 * 1000, \
.spics_io_num = 10, \
.queue_size = 5, \
}, \
.intr_pin = 8, \
}, \
}
#endif // CONFIG_OPENTHREAD_RADIO_SPINEL_UART OR CONFIG_OPENTHREAD_RADIO_SPINEL_SPI
#if CONFIG_IDF_TARGET_ESP32C2 && CONFIG_XTAL_FREQ_26
#define HOST_BAUD_RATE 74880

View File

@ -20,6 +20,7 @@
.radio_mode = RADIO_MODE_NATIVE, \
}
#if CONFIG_OPENTHREAD_RCP_UART
#if CONFIG_OPENTHREAD_UART_PIN_MANUAL
#define OPENTHREAD_RCP_UART_RX_PIN CONFIG_OPENTHREAD_UART_RX_PIN
#define OPENTHREAD_RCP_UART_TX_PIN CONFIG_OPENTHREAD_UART_TX_PIN
@ -47,6 +48,30 @@
.tx_pin = OPENTHREAD_RCP_UART_TX_PIN, \
}, \
}
#else // CONFIG_OPENTHREAD_RCP_SPI
#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \
{ \
.host_connection_mode = HOST_CONNECTION_MODE_RCP_SPI, \
.spi_slave_config = { \
.host_device = SPI2_HOST, \
.bus_config = { \
.mosi_io_num = 3, \
.miso_io_num = 1, \
.sclk_io_num = 0, \
.quadhd_io_num = -1, \
.quadwp_io_num = -1, \
.isr_cpu_id = INTR_CPU_ID_0, \
}, \
.slave_config = { \
.mode = 0, \
.spics_io_num = 2, \
.queue_size = 3, \
.flags = 0, \
}, \
.intr_pin = 9, \
}, \
}
#endif
#define ESP_OPENTHREAD_DEFAULT_PORT_CONFIG() \
{ \

View File

@ -64,5 +64,5 @@ void app_main(void)
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
xTaskCreate(ot_task_worker, "ot_rcp_main", 10240, xTaskGetCurrentTaskHandle(), 5, NULL);
xTaskCreate(ot_task_worker, "ot_rcp_main", 3072, xTaskGetCurrentTaskHandle(), 5, NULL);
}