/* * 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 #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"); gpio_install_isr_service(0); // The gpio isr service may has been installed. 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(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 }