mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'feat/adapt_uhci_code_260824_v6.0' into 'release/v6.0'
refactor(hci): switch HCI UART DMA transport to UHCI driver APIs (6.0) See merge request espressif/esp-idf!52160
This commit is contained in:
@@ -119,11 +119,16 @@ menu "HCI Config"
|
||||
The amount of rx memory received at the same time
|
||||
|
||||
config BT_LE_HCI_LLDESCS_POOL_NUM
|
||||
int "The amount of lldecs memory for driver dma mode"
|
||||
int "The amount of lldecs memory for driver dma mode (Deprecated)"
|
||||
depends on BT_LE_UART_HCI_DMA_MODE
|
||||
default 20
|
||||
warning "This option is deprecated and is no longer used"
|
||||
help
|
||||
The amount of lldecs memory for driver dma mode
|
||||
The amount of lldecs memory for driver dma mode.
|
||||
|
||||
Deprecated: this option is no longer used after HCI UART DMA
|
||||
switched to UHCI driver APIs, and will be removed in a future release.
|
||||
|
||||
endmenu
|
||||
|
||||
config BT_LE_CONTROLLER_NPL_OS_PORTING_SUPPORT
|
||||
|
||||
@@ -119,11 +119,15 @@ menu "HCI Config"
|
||||
The amount of rx memory received at the same time
|
||||
|
||||
config BT_LE_HCI_LLDESCS_POOL_NUM
|
||||
int "The amount of lldecs memory for driver dma mode"
|
||||
int "The amount of lldecs memory for driver dma mode (Deprecated)"
|
||||
depends on BT_LE_UART_HCI_DMA_MODE
|
||||
default 20
|
||||
warning "This option is deprecated and is no longer used"
|
||||
help
|
||||
The amount of lldecs memory for driver dma mode
|
||||
The amount of lldecs memory for driver dma mode.
|
||||
|
||||
Deprecated: this option is no longer used after HCI UART DMA
|
||||
switched to UHCI driver APIs, and will be removed in a future release.
|
||||
endmenu
|
||||
|
||||
config BT_LE_CONTROLLER_NPL_OS_PORTING_SUPPORT
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -7,11 +7,13 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/uhci.h"
|
||||
#include "esp_hci_transport.h"
|
||||
#include "esp_hci_internal.h"
|
||||
#include "common/hci_driver_h4.h"
|
||||
@@ -19,458 +21,340 @@
|
||||
#include "common/hci_driver_mem.h"
|
||||
#include "hci_driver_uart.h"
|
||||
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "esp_private/gdma.h"
|
||||
#include "hal/uhci_ll.h"
|
||||
|
||||
/*
|
||||
* UART DMA Desc struct
|
||||
/**
|
||||
* HCI UART DMA transport on top of the UHCI driver (driver/uhci.h).
|
||||
*
|
||||
* --------------------------------------------------------------
|
||||
* | own | EoF | sub_sof | 5'b0 | length [11:0] | size [11:0] |
|
||||
* --------------------------------------------------------------
|
||||
* | buf_ptr [31:0] |
|
||||
* --------------------------------------------------------------
|
||||
* | next_desc_ptr [31:0] |
|
||||
* --------------------------------------------------------------
|
||||
* RX (uhci_start_receive_continuous)
|
||||
* TX (uhci_multi_buffer_transmit)
|
||||
*/
|
||||
|
||||
/* this bitfield is start from the LSB!!! */
|
||||
typedef struct uhci_lldesc_s {
|
||||
volatile uint32_t size : 12,
|
||||
length: 12,
|
||||
offset: 5, /* h/w reserved 5bit, s/w use it as offset in buffer */
|
||||
sosf : 1, /* start of sub-frame */
|
||||
eof : 1, /* end of frame */
|
||||
owner : 1; /* hw or sw */
|
||||
volatile const uint8_t *buf; /* point to buffer data */
|
||||
union {
|
||||
volatile uint32_t empty;
|
||||
STAILQ_ENTRY(uhci_lldesc_s) qe; /* pointing to the next desc */
|
||||
};
|
||||
} uhci_lldesc_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration of HCI transport transmission states.
|
||||
*/
|
||||
typedef enum {
|
||||
HCI_TRANS_TX_IDLE, ///< HCI Transport TX is in idle state.
|
||||
HCI_TRANS_TX_START, ///< HCI Transport TX is starting transmission.
|
||||
HCI_TRANS_TX_END, ///< HCI Transport TX has completed transmission.
|
||||
HCI_TRANS_TX_IDLE, /*!< No UHCI TX transaction in flight; task may dequeue the next packet. */
|
||||
HCI_TRANS_TX_BUSY, /*!< uhci_multi_buffer_transmit() has been submitted; wait for on_tx_trans_done. */
|
||||
} hci_trans_tx_state_t;
|
||||
|
||||
/**
|
||||
* @brief Runtime context for the UHCI-based HCI UART DMA transport.
|
||||
*/
|
||||
typedef struct {
|
||||
TaskHandle_t task_handler;
|
||||
hci_driver_uart_params_config_t *hci_uart_params;
|
||||
SemaphoreHandle_t process_sem;
|
||||
struct hci_h4_sm *h4_sm;
|
||||
hci_driver_forward_fn *forward_cb;
|
||||
struct os_mempool *hci_rx_data_pool; /*!< Init a memory pool for rx_data cache */
|
||||
uint8_t *hci_rx_data_buffer;
|
||||
struct os_mempool *hci_rxinfo_pool; /*!< Init a memory pool for rxinfo cache */
|
||||
os_membuf_t *hci_rxinfo_buffer;
|
||||
volatile bool rxinfo_mem_exhausted; /*!< Indicate rxinfo memory does not exist */
|
||||
volatile bool is_continue_rx; /*!< Continue to rx */
|
||||
volatile hci_trans_tx_state_t hci_tx_state; /*!< HCI Tx State */
|
||||
struct os_mempool lldesc_mem_pool;/*!< Init a memory pool for uhci_lldesc_t */
|
||||
uhci_lldesc_t *lldesc_mem;
|
||||
TaskHandle_t task_handler; /*!< Process task: starts TX when idle and feeds RX bytes to H4. */
|
||||
hci_driver_uart_params_config_t *hci_uart_params; /*!< UART port / pins / baud used by uhci_controller_config_t. */
|
||||
SemaphoreHandle_t process_sem; /*!< Wakes the process task (TX enqueue, TX done, RX event). */
|
||||
struct hci_h4_sm *h4_sm; /*!< H4 state machine that reassembles HCI packets from the byte stream. */
|
||||
hci_driver_forward_fn *forward_cb; /*!< Host-bound callback invoked when H4 completes one packet. */
|
||||
uhci_controller_handle_t uhci_ctrl; /*!< Handle returned by uhci_new_controller(). */
|
||||
uint8_t *rx_dma_ring; /*!< Storage for uhci_start_receive_continuous(); valid until uhci_stop_receive(). */
|
||||
size_t rx_dma_ring_size; /*!< Size of rx_dma_ring, passed as buffer_size to continuous RX. */
|
||||
RingbufHandle_t rx_copy_ringbuf; /*!< ISR copies DMA-ring slices here; the process task drains it. */
|
||||
uhci_transmit_buffer_info_t *tx_segments; /*!< Scratch array of UHCI TX segments for one multi-buffer transaction. */
|
||||
volatile hci_trans_tx_state_t hci_tx_state; /*!< Only one HCI packet is in flight (tx-list entries cannot be mixed). */
|
||||
volatile bool rx_copy_overflow; /*!< Set in ISR when rx_copy_ringbuf cannot accept a DMA slice. */
|
||||
} hci_driver_uart_dma_env_t;
|
||||
|
||||
#define ESP_BT_HCI_TL_STATUS_OK (0) /*!< HCI_TL Tx/Rx operation status OK */
|
||||
/* The number of lldescs pool */
|
||||
#define HCI_LLDESCS_POOL_NUM (CONFIG_BT_LE_HCI_LLDESCS_POOL_NUM)
|
||||
/* Default block size for HCI RX data */
|
||||
#define HCI_RX_DATA_BLOCK_SIZE (DEFAULT_BT_LE_ACL_BUF_SIZE + HCI_TRANSPORT_CMD_SZ)
|
||||
#define HCI_RX_DATA_POOL_NUM (CONFIG_BT_LE_HCI_TRANS_RX_MEM_NUM)
|
||||
#define HCI_RX_INFO_POOL_NUM (CONFIG_BT_LE_HCI_TRANS_RX_MEM_NUM + 1)
|
||||
/* Max UHCI TX segments in one uhci_multi_buffer_transmit(); maps to max_transmit_buffer_count. */
|
||||
#define HCI_TX_MAX_SEGMENT_COUNT (20)
|
||||
/* Combined size of all TX segments in one transaction (H4 type byte + one ACL payload). */
|
||||
#define HCI_TX_MAX_SIZE (DEFAULT_BT_LE_ACL_BUF_SIZE + 1)
|
||||
/* One HCI packet budget; used only to size the RX rings, not a mempool block anymore. */
|
||||
#define HCI_RX_PKT_BUDGET (DEFAULT_BT_LE_ACL_BUF_SIZE + HCI_TRANSPORT_CMD_SZ)
|
||||
|
||||
/**
|
||||
* @brief callback function for HCI Transport Layer send/receive operations
|
||||
#define HCI_RX_DMA_RING_SIZE ((HCI_RX_PKT_BUDGET) > 4096 ? (HCI_RX_PKT_BUDGET) : 4096)
|
||||
/*
|
||||
* uhci_controller_config_t.max_receive_internal_mem decides how many RX DMA descriptors
|
||||
* UHCI allocates (node_count = size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE). Keep this large
|
||||
* enough for at least two nodes so continuous RX can ping-pong instead of overwriting
|
||||
* a single node while the callback still copies. This is not the allocated DMA ring size.
|
||||
*/
|
||||
typedef void (* esp_bt_hci_tl_callback_t) (void *arg, uint8_t status);
|
||||
|
||||
struct uart_txrxchannel {
|
||||
esp_bt_hci_tl_callback_t callback;
|
||||
void *arg;
|
||||
uhci_lldesc_t *link_head;
|
||||
};
|
||||
|
||||
struct uart_env_tag {
|
||||
struct uart_txrxchannel tx;
|
||||
struct uart_txrxchannel rx;
|
||||
};
|
||||
|
||||
typedef struct hci_message {
|
||||
void *ptr; ///< Pointer to the message data.
|
||||
uint32_t length; ///< Length of the message data.
|
||||
STAILQ_ENTRY(hci_message) next; ///< Next element in the linked list.
|
||||
} hci_message_t;
|
||||
|
||||
static void hci_driver_uart_dma_recv_async(uint8_t *buf, uint32_t size, esp_bt_hci_tl_callback_t callback, void *arg);
|
||||
int hci_driver_uart_dma_rx_start(uint8_t *rx_data, uint32_t length);
|
||||
int hci_driver_uart_dma_tx_start(esp_bt_hci_tl_callback_t callback, void *arg);
|
||||
#define HCI_UHCI_RX_DESC_MEM (CONFIG_BT_LE_HCI_TRANS_RX_MEM_NUM * HCI_RX_DMA_RING_SIZE)
|
||||
#define HCI_RX_COPY_RINGBUF_SIZE HCI_RX_DMA_RING_SIZE
|
||||
|
||||
static const char *TAG = "uart_dma";
|
||||
static hci_driver_uart_dma_env_t s_hci_driver_uart_dma_env;
|
||||
static struct hci_h4_sm s_hci_driver_uart_h4_sm;
|
||||
static portMUX_TYPE s_hci_tx_state_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
/* The list for hci_rx_data */
|
||||
STAILQ_HEAD(g_hci_rxinfo_list, hci_message);
|
||||
static int hci_driver_uart_dma_tx_submit(void);
|
||||
|
||||
DRAM_ATTR struct g_hci_rxinfo_list g_hci_rxinfo_head;
|
||||
static DRAM_ATTR struct uart_env_tag uart_env;
|
||||
static uhci_dev_t *s_uhci_hw = &UHCI0;
|
||||
static DRAM_ATTR gdma_channel_handle_t s_rx_channel;
|
||||
static DRAM_ATTR gdma_channel_handle_t s_tx_channel;
|
||||
|
||||
static int hci_driver_uart_dma_memory_deinit(void)
|
||||
/**
|
||||
* @brief Free the DMA ring, software RX ringbuf and TX segment scratch array.
|
||||
*
|
||||
* Must be called only after uhci_stop_receive() has returned, so the DMA ring
|
||||
* is no longer referenced by UHCI.
|
||||
*/
|
||||
static void
|
||||
hci_driver_uart_dma_memory_deinit(void)
|
||||
{
|
||||
|
||||
if (s_hci_driver_uart_dma_env.hci_rxinfo_buffer) {
|
||||
free(s_hci_driver_uart_dma_env.hci_rxinfo_buffer);
|
||||
s_hci_driver_uart_dma_env.hci_rxinfo_buffer = NULL;
|
||||
if (s_hci_driver_uart_dma_env.rx_copy_ringbuf) {
|
||||
vRingbufferDelete(s_hci_driver_uart_dma_env.rx_copy_ringbuf);
|
||||
s_hci_driver_uart_dma_env.rx_copy_ringbuf = NULL;
|
||||
}
|
||||
|
||||
if (s_hci_driver_uart_dma_env.hci_rxinfo_pool) {
|
||||
free(s_hci_driver_uart_dma_env.hci_rxinfo_pool);
|
||||
s_hci_driver_uart_dma_env.hci_rxinfo_pool = NULL;
|
||||
if (s_hci_driver_uart_dma_env.rx_dma_ring) {
|
||||
heap_caps_free(s_hci_driver_uart_dma_env.rx_dma_ring);
|
||||
s_hci_driver_uart_dma_env.rx_dma_ring = NULL;
|
||||
}
|
||||
|
||||
if (s_hci_driver_uart_dma_env.hci_rx_data_buffer) {
|
||||
free(s_hci_driver_uart_dma_env.hci_rx_data_buffer);
|
||||
s_hci_driver_uart_dma_env.hci_rx_data_buffer = NULL;
|
||||
if (s_hci_driver_uart_dma_env.tx_segments) {
|
||||
free(s_hci_driver_uart_dma_env.tx_segments);
|
||||
s_hci_driver_uart_dma_env.tx_segments = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate UHCI RX/TX working buffers.
|
||||
*
|
||||
* rx_dma_ring is DMA-capable internal memory required by uhci_start_receive_continuous().
|
||||
* tx_segments holds the segment descriptors for uhci_multi_buffer_transmit().
|
||||
*/
|
||||
static int
|
||||
hci_driver_uart_dma_memory_init(void)
|
||||
{
|
||||
s_hci_driver_uart_dma_env.rx_dma_ring_size = HCI_UHCI_RX_DESC_MEM;
|
||||
/* DMA + internal: UHCI/GDMA writes here; cache-safe path also expects internal RAM. */
|
||||
s_hci_driver_uart_dma_env.rx_dma_ring = heap_caps_calloc(1, s_hci_driver_uart_dma_env.rx_dma_ring_size,
|
||||
MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
if (!s_hci_driver_uart_dma_env.rx_dma_ring) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
if (s_hci_driver_uart_dma_env.hci_rx_data_pool) {
|
||||
free(s_hci_driver_uart_dma_env.hci_rx_data_pool);
|
||||
s_hci_driver_uart_dma_env.hci_rx_data_pool = NULL;
|
||||
/* BYTEBUF so ISR can push arbitrary UHCI slice lengths without pre-sized items. */
|
||||
s_hci_driver_uart_dma_env.rx_copy_ringbuf = xRingbufferCreate(HCI_RX_COPY_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (!s_hci_driver_uart_dma_env.rx_copy_ringbuf) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
if (s_hci_driver_uart_dma_env.lldesc_mem) {
|
||||
free(s_hci_driver_uart_dma_env.lldesc_mem);
|
||||
s_hci_driver_uart_dma_env.lldesc_mem = NULL;
|
||||
s_hci_driver_uart_dma_env.tx_segments = calloc(HCI_TX_MAX_SEGMENT_COUNT, sizeof(uhci_transmit_buffer_info_t));
|
||||
if (!s_hci_driver_uart_dma_env.tx_segments) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hci_driver_uart_dma_memory_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
s_hci_driver_uart_dma_env.lldesc_mem = malloc(OS_MEMPOOL_SIZE(HCI_LLDESCS_POOL_NUM,
|
||||
sizeof (uhci_lldesc_t)) * sizeof(os_membuf_t));
|
||||
if (!s_hci_driver_uart_dma_env.lldesc_mem) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = os_mempool_init(&s_hci_driver_uart_dma_env.lldesc_mem_pool, HCI_LLDESCS_POOL_NUM,
|
||||
sizeof (uhci_lldesc_t), s_hci_driver_uart_dma_env.lldesc_mem, "hci_lldesc_pool");
|
||||
if (rc) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
s_hci_driver_uart_dma_env.hci_rx_data_pool = (struct os_mempool *)malloc(sizeof(struct os_mempool));
|
||||
if (!s_hci_driver_uart_dma_env.hci_rx_data_pool) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
memset(s_hci_driver_uart_dma_env.hci_rx_data_pool, 0, sizeof(struct os_mempool));
|
||||
s_hci_driver_uart_dma_env.hci_rx_data_buffer = malloc(OS_MEMPOOL_SIZE(HCI_RX_DATA_POOL_NUM,
|
||||
HCI_RX_DATA_BLOCK_SIZE) * sizeof(os_membuf_t));
|
||||
if (!s_hci_driver_uart_dma_env.hci_rx_data_buffer) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
memset(s_hci_driver_uart_dma_env.hci_rx_data_buffer, 0, OS_MEMPOOL_SIZE(HCI_RX_DATA_POOL_NUM,
|
||||
HCI_RX_DATA_BLOCK_SIZE) * sizeof(os_membuf_t));
|
||||
rc = os_mempool_init(s_hci_driver_uart_dma_env.hci_rx_data_pool, HCI_RX_DATA_POOL_NUM,
|
||||
HCI_RX_DATA_BLOCK_SIZE, s_hci_driver_uart_dma_env.hci_rx_data_buffer,
|
||||
"hci_rx_data_pool");
|
||||
if (rc) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
|
||||
/* Malloc hci rxinfo pool */
|
||||
s_hci_driver_uart_dma_env.hci_rxinfo_pool = (struct os_mempool *)malloc(sizeof(struct os_mempool));
|
||||
if (!s_hci_driver_uart_dma_env.hci_rxinfo_pool) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
memset(s_hci_driver_uart_dma_env.hci_rxinfo_pool, 0, sizeof(struct os_mempool));
|
||||
s_hci_driver_uart_dma_env.hci_rxinfo_buffer = malloc(OS_MEMPOOL_SIZE(HCI_RX_INFO_POOL_NUM,
|
||||
sizeof(hci_message_t)) * sizeof(os_membuf_t));
|
||||
if (!s_hci_driver_uart_dma_env.hci_rxinfo_buffer) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
memset(s_hci_driver_uart_dma_env.hci_rxinfo_buffer, 0, OS_MEMPOOL_SIZE(HCI_RX_INFO_POOL_NUM,
|
||||
sizeof(hci_message_t)) * sizeof(os_membuf_t));
|
||||
rc = os_mempool_init(s_hci_driver_uart_dma_env.hci_rxinfo_pool, HCI_RX_INFO_POOL_NUM,
|
||||
sizeof(hci_message_t), s_hci_driver_uart_dma_env.hci_rxinfo_buffer,
|
||||
"hci_rxinfo_pool");
|
||||
if (rc) {
|
||||
goto init_err;
|
||||
}
|
||||
|
||||
return rc;
|
||||
init_err:
|
||||
hci_driver_uart_dma_memory_deinit();
|
||||
return rc;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static IRAM_ATTR bool hci_uart_tl_rx_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
|
||||
static void IRAM_ATTR
|
||||
hci_driver_uart_dma_txstate_set(hci_trans_tx_state_t tx_state)
|
||||
{
|
||||
esp_bt_hci_tl_callback_t callback = uart_env.rx.callback;
|
||||
void *arg = uart_env.rx.arg;
|
||||
assert(dma_chan == s_rx_channel);
|
||||
assert(uart_env.rx.callback != NULL);
|
||||
// clear callback pointer
|
||||
uart_env.rx.callback = NULL;
|
||||
uart_env.rx.arg = NULL;
|
||||
// call handler
|
||||
callback(arg, ESP_BT_HCI_TL_STATUS_OK);
|
||||
return true;
|
||||
}
|
||||
|
||||
static IRAM_ATTR bool hci_uart_tl_tx_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
esp_bt_hci_tl_callback_t callback = uart_env.tx.callback;
|
||||
assert(dma_chan == s_tx_channel);
|
||||
assert(uart_env.tx.callback != NULL);
|
||||
// clear callback pointer
|
||||
uart_env.tx.callback = NULL;
|
||||
// call handler
|
||||
callback(uart_env.tx.arg, ESP_BT_HCI_TL_STATUS_OK);
|
||||
uart_env.tx.arg = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t * IRAM_ATTR hci_driver_uart_dma_rxdata_memory_get(void)
|
||||
{
|
||||
uint8_t *rx_data;
|
||||
rx_data = os_memblock_get(s_hci_driver_uart_dma_env.hci_rx_data_pool);
|
||||
return rx_data;
|
||||
}
|
||||
|
||||
hci_message_t * IRAM_ATTR hci_driver_uart_dma_rxinfo_memory_get(void)
|
||||
{
|
||||
hci_message_t *rx_info;
|
||||
rx_info = os_memblock_get(s_hci_driver_uart_dma_env.hci_rxinfo_pool);
|
||||
return rx_info;
|
||||
}
|
||||
|
||||
void IRAM_ATTR hci_driver_uart_dma_cache_rxinfo(hci_message_t *hci_rxinfo)
|
||||
{
|
||||
os_sr_t sr;
|
||||
|
||||
OS_ENTER_CRITICAL(sr);
|
||||
STAILQ_INSERT_TAIL(&g_hci_rxinfo_head, hci_rxinfo, next);
|
||||
OS_EXIT_CRITICAL(sr);
|
||||
}
|
||||
|
||||
void IRAM_ATTR hci_driver_uart_dma_continue_rx_enable(bool enable)
|
||||
{
|
||||
os_sr_t sr;
|
||||
OS_ENTER_CRITICAL(sr);
|
||||
s_hci_driver_uart_dma_env.is_continue_rx = enable;
|
||||
OS_EXIT_CRITICAL(sr);
|
||||
}
|
||||
|
||||
void IRAM_ATTR hci_driver_uart_dma_rxinfo_mem_exhausted_set(bool is_exhausted)
|
||||
{
|
||||
os_sr_t sr;
|
||||
OS_ENTER_CRITICAL(sr);
|
||||
s_hci_driver_uart_dma_env.rxinfo_mem_exhausted = is_exhausted;
|
||||
OS_EXIT_CRITICAL(sr);
|
||||
}
|
||||
|
||||
void IRAM_ATTR hci_driver_uart_dma_recv_callback(void *arg, uint8_t status)
|
||||
{
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
hci_message_t *hci_rxinfo;
|
||||
uint8_t *rx_data;
|
||||
|
||||
if (s_hci_driver_uart_dma_env.rxinfo_mem_exhausted) {
|
||||
ESP_LOGE(TAG, "Will lost rx data, need adjust rxinfo memory count\n");
|
||||
assert(0);
|
||||
}
|
||||
|
||||
hci_rxinfo = hci_driver_uart_dma_rxinfo_memory_get();
|
||||
if (!hci_rxinfo) {
|
||||
ESP_LOGW(TAG, "set rxinfo mem exhausted flag\n");
|
||||
hci_driver_uart_dma_rxinfo_mem_exhausted_set(true);
|
||||
xSemaphoreGiveFromISR(s_hci_driver_uart_dma_env.process_sem, &xHigherPriorityTaskWoken);
|
||||
return;
|
||||
}
|
||||
|
||||
hci_rxinfo->ptr = (void *)uart_env.rx.link_head->buf;
|
||||
hci_rxinfo->length = uart_env.rx.link_head->length;
|
||||
hci_driver_uart_dma_cache_rxinfo(hci_rxinfo);
|
||||
xSemaphoreGiveFromISR(s_hci_driver_uart_dma_env.process_sem, &xHigherPriorityTaskWoken);
|
||||
rx_data = hci_driver_uart_dma_rxdata_memory_get();
|
||||
if (!rx_data) {
|
||||
hci_driver_uart_dma_continue_rx_enable(true);
|
||||
}else {
|
||||
hci_driver_uart_dma_rx_start(rx_data, HCI_RX_DATA_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR hci_driver_uart_dma_txstate_set(hci_trans_tx_state_t tx_state)
|
||||
{
|
||||
os_sr_t sr;
|
||||
OS_ENTER_CRITICAL(sr);
|
||||
portENTER_CRITICAL_SAFE(&s_hci_tx_state_mux);
|
||||
s_hci_driver_uart_dma_env.hci_tx_state = tx_state;
|
||||
OS_EXIT_CRITICAL(sr);
|
||||
portEXIT_CRITICAL_SAFE(&s_hci_tx_state_mux);
|
||||
}
|
||||
|
||||
void IRAM_ATTR hci_driver_uart_dma_send_callback(void *arg, uint8_t status)
|
||||
/**
|
||||
* @brief UHCI on_rx_trans_event callback (ISR context, must be non-blocking).
|
||||
*
|
||||
* edata->data points into rx_dma_ring and is only guaranteed readable during this
|
||||
* callback. Copy the slice out immediately: continuous RX does not stop DMA at EOF,
|
||||
* so the same node will be overwritten on wrap-around. Both partial-node and EOF
|
||||
* events are forwarded; H4 (not UHCI) decides packet boundaries.
|
||||
*
|
||||
* @return Whether a higher-priority task was woken (UHCI ISR yield contract).
|
||||
*/
|
||||
IRAM_ATTR static bool
|
||||
hci_driver_uart_dma_rx_event_cb(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
uhci_lldesc_t *lldesc_head;
|
||||
uhci_lldesc_t *lldesc_nxt;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
lldesc_head = uart_env.tx.link_head;
|
||||
while (lldesc_head) {
|
||||
lldesc_nxt = lldesc_head->qe.stqe_next;
|
||||
os_memblock_put(&s_hci_driver_uart_dma_env.lldesc_mem_pool, lldesc_head);
|
||||
lldesc_head = lldesc_nxt;
|
||||
(void)uhci_ctrl;
|
||||
(void)user_ctx;
|
||||
|
||||
/* Abnormal EOF: UHCI reports data == NULL and recv_size == 0. Keep the session running. */
|
||||
if (!edata->data || edata->recv_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uart_env.tx.link_head = NULL;
|
||||
/*
|
||||
* xRingbufferSendFromISR copies the DMA-ring slice. If this fails the DMA session
|
||||
* still runs (no overrun callback from UHCI); the process task reports HCI sync loss.
|
||||
*/
|
||||
if (xRingbufferSendFromISR(s_hci_driver_uart_dma_env.rx_copy_ringbuf, edata->data, edata->recv_size,
|
||||
&xHigherPriorityTaskWoken) != pdTRUE) {
|
||||
s_hci_driver_uart_dma_env.rx_copy_overflow = true;
|
||||
}
|
||||
|
||||
xSemaphoreGiveFromISR(s_hci_driver_uart_dma_env.process_sem, &xHigherPriorityTaskWoken);
|
||||
return xHigherPriorityTaskWoken == pdTRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UHCI on_tx_trans_done callback (ISR context).
|
||||
*
|
||||
* For a multi-buffer transaction, edata->buffer only points at the first segment
|
||||
* and is treated as a transaction id, not as sent_size bytes of contiguous memory.
|
||||
* The tx-list entry is recycled on the next dequeue after last_frame, so the
|
||||
* process task must run again now that the buffers may be freed.
|
||||
*/
|
||||
IRAM_ATTR static bool
|
||||
hci_driver_uart_dma_tx_done_cb(uhci_controller_handle_t uhci_ctrl, const uhci_tx_done_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
(void)uhci_ctrl;
|
||||
(void)edata;
|
||||
(void)user_ctx;
|
||||
|
||||
hci_driver_uart_dma_txstate_set(HCI_TRANS_TX_IDLE);
|
||||
xSemaphoreGiveFromISR(s_hci_driver_uart_dma_env.process_sem, &xHigherPriorityTaskWoken);
|
||||
return xHigherPriorityTaskWoken == pdTRUE;
|
||||
}
|
||||
|
||||
static IRAM_ATTR void hci_driver_uart_dma_recv_async(uint8_t *buf, uint32_t size, esp_bt_hci_tl_callback_t callback, void *arg)
|
||||
{
|
||||
uhci_lldesc_t *lldesc_head;
|
||||
assert(buf != NULL);
|
||||
assert(size != 0);
|
||||
assert(callback != NULL);
|
||||
uart_env.rx.callback = callback;
|
||||
uart_env.rx.arg = arg;
|
||||
lldesc_head = uart_env.rx.link_head;
|
||||
|
||||
while (lldesc_head) {
|
||||
os_memblock_put(&s_hci_driver_uart_dma_env.lldesc_mem_pool, lldesc_head),
|
||||
lldesc_head = lldesc_head->qe.stqe_next;
|
||||
}
|
||||
|
||||
uart_env.rx.link_head = NULL;
|
||||
lldesc_head = os_memblock_get(&s_hci_driver_uart_dma_env.lldesc_mem_pool);
|
||||
assert(lldesc_head);
|
||||
memset(lldesc_head, 0, sizeof(uhci_lldesc_t));
|
||||
lldesc_head->buf = buf;
|
||||
lldesc_head->size = size;
|
||||
lldesc_head->eof = 0;
|
||||
s_uhci_hw->pkt_thres.pkt_thrs = size;
|
||||
uart_env.rx.link_head = lldesc_head;
|
||||
gdma_start(s_rx_channel, (intptr_t)(uart_env.rx.link_head));
|
||||
}
|
||||
|
||||
int IRAM_ATTR hci_driver_uart_dma_rx_start(uint8_t *rx_data, uint32_t length)
|
||||
{
|
||||
hci_driver_uart_dma_recv_async(rx_data, length, hci_driver_uart_dma_recv_callback, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hci_driver_uart_dma_tx_start(esp_bt_hci_tl_callback_t callback, void *arg)
|
||||
/**
|
||||
* @brief Dequeue one HCI packet and submit it with uhci_multi_buffer_transmit().
|
||||
*
|
||||
* tx_list_dequeue() is one-entry-at-a-time: the first call yields the 1-byte H4
|
||||
* type, later calls yield payload fragments, last_frame marks the end of that
|
||||
* packet. All fragments stay valid until on_tx_trans_done (the next dequeue
|
||||
* after last_frame frees the event buffer / mbuf).
|
||||
*
|
||||
* @return 0 if a transaction was queued, -1 if the TX list is empty.
|
||||
*/
|
||||
static int
|
||||
hci_driver_uart_dma_tx_submit(void)
|
||||
{
|
||||
void *data;
|
||||
bool last_frame;
|
||||
bool head_is_setted;
|
||||
bool last_frame = false;
|
||||
uint32_t tx_len;
|
||||
uhci_lldesc_t *lldesc_data;
|
||||
uhci_lldesc_t *lldesc_head;
|
||||
uhci_lldesc_t *lldesc_tail;
|
||||
size_t seg_count = 0;
|
||||
esp_err_t err;
|
||||
|
||||
lldesc_head = NULL;
|
||||
lldesc_tail = NULL;
|
||||
head_is_setted = false;
|
||||
last_frame = false;
|
||||
while (true) {
|
||||
while (seg_count < HCI_TX_MAX_SEGMENT_COUNT) {
|
||||
tx_len = hci_driver_util_tx_list_dequeue(0xffffff, &data, &last_frame);
|
||||
if (!tx_len) {
|
||||
break;
|
||||
}
|
||||
|
||||
lldesc_data = os_memblock_get(&s_hci_driver_uart_dma_env.lldesc_mem_pool);
|
||||
/* According to the current processing logic, It should not be empty */
|
||||
assert(lldesc_data);
|
||||
memset(lldesc_data, 0, sizeof(uhci_lldesc_t));
|
||||
lldesc_data->length = tx_len;
|
||||
lldesc_data->buf = data;
|
||||
lldesc_data->eof = 0;
|
||||
if (!head_is_setted) {
|
||||
lldesc_head = lldesc_data;
|
||||
head_is_setted = true;
|
||||
} else {
|
||||
lldesc_tail->qe.stqe_next = lldesc_data;
|
||||
}
|
||||
|
||||
lldesc_tail = lldesc_data;
|
||||
/* Each fragment is a UHCI TX segment; UHCI concatenates them on the UART wire. */
|
||||
s_hci_driver_uart_dma_env.tx_segments[seg_count].write_buffer = data;
|
||||
s_hci_driver_uart_dma_env.tx_segments[seg_count].buffer_size = tx_len;
|
||||
seg_count++;
|
||||
if (last_frame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lldesc_head) {
|
||||
lldesc_tail->eof = 1;
|
||||
uart_env.tx.link_head = lldesc_head;
|
||||
uart_env.tx.callback = callback;
|
||||
uart_env.tx.arg = arg;
|
||||
/* The DMA interrupt may have been triggered before setting the tx_state,
|
||||
* So we set it first.
|
||||
*/
|
||||
hci_driver_uart_dma_txstate_set(HCI_TRANS_TX_START);
|
||||
gdma_start(s_tx_channel, (intptr_t)(uart_env.tx.link_head));
|
||||
return 0;
|
||||
} else {
|
||||
if (seg_count == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!last_frame) {
|
||||
/* array_size is capped by max_transmit_buffer_count; a gap before the rest may look like idle EOF on the peer. */
|
||||
ESP_LOGW(TAG, "HCI TX packet exceeds max_transmit_buffer_count (%d), sending partial packet",
|
||||
HCI_TX_MAX_SEGMENT_COUNT);
|
||||
}
|
||||
|
||||
/* Mark BUSY first so a completion ISR cannot be observed as still-idle. */
|
||||
hci_driver_uart_dma_txstate_set(HCI_TRANS_TX_BUSY);
|
||||
err = uhci_multi_buffer_transmit(s_hci_driver_uart_dma_env.uhci_ctrl,
|
||||
s_hci_driver_uart_dma_env.tx_segments, seg_count);
|
||||
/* Transmit must not fail; assert directly. */
|
||||
assert(err == ESP_OK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create the UHCI controller, register ISR callbacks and start continuous RX.
|
||||
*
|
||||
* UART pins/baud must already be programmed (uart_param_config / uart_set_pin).
|
||||
* UHCI and BT HCI share the same hardware; this driver is the exclusive UHCI user.
|
||||
*/
|
||||
static int
|
||||
hci_driver_uart_dma_uhci_install(void)
|
||||
{
|
||||
uhci_controller_config_t uhci_cfg = {
|
||||
.uart_port = s_hci_driver_uart_dma_env.hci_uart_params->hci_uart_port, /* Attach this UART to UHCI. */
|
||||
.tx_trans_queue_depth = 2, /* One in-flight HCI packet is enough; +1 for slack. */
|
||||
.max_transmit_size = HCI_TX_MAX_SIZE, /* Total bytes of all segments in one transaction. */
|
||||
.max_transmit_buffer_count = HCI_TX_MAX_SEGMENT_COUNT, /* Caps uhci_multi_buffer_transmit() array_size. */
|
||||
.max_receive_internal_mem = HCI_UHCI_RX_DESC_MEM, /* Sizes the RX DMA descriptor chain, not the ring. */
|
||||
.dma_burst_size = 32, /* Power-of-two burst; 0 would disable burst. */
|
||||
.rx_eof_flags.idle_eof = 1, /* Frame ends when the UART RX line goes idle. */
|
||||
};
|
||||
uhci_event_callbacks_t uhci_cbs = {
|
||||
.on_rx_trans_event = hci_driver_uart_dma_rx_event_cb, /* Partial node and/or frame EOF. */
|
||||
.on_tx_trans_done = hci_driver_uart_dma_tx_done_cb,
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "uart attach uhci");
|
||||
if (uhci_new_controller(&uhci_cfg, &s_hci_driver_uart_dma_env.uhci_ctrl) != ESP_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Register before start_receive_continuous so the first frame is not dropped. */
|
||||
if (uhci_register_event_callbacks(s_hci_driver_uart_dma_env.uhci_ctrl, &uhci_cbs, NULL) != ESP_OK) {
|
||||
uhci_del_controller(s_hci_driver_uart_dma_env.uhci_ctrl);
|
||||
s_hci_driver_uart_dma_env.uhci_ctrl = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Arm once. Do not call uhci_receive() / start again until uhci_stop_receive(). */
|
||||
if (uhci_start_receive_continuous(s_hci_driver_uart_dma_env.uhci_ctrl,
|
||||
s_hci_driver_uart_dma_env.rx_dma_ring,
|
||||
s_hci_driver_uart_dma_env.rx_dma_ring_size) != ESP_OK) {
|
||||
uhci_del_controller(s_hci_driver_uart_dma_env.uhci_ctrl);
|
||||
s_hci_driver_uart_dma_env.uhci_ctrl = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop continuous RX so UHCI RX callbacks cannot run anymore.
|
||||
*
|
||||
* Does not delete the controller: the process task may still submit TX until it
|
||||
* is torn down. No-op if UHCI was never installed (init error path).
|
||||
*/
|
||||
static void
|
||||
hci_driver_uart_dma_uhci_stop_rx(void)
|
||||
{
|
||||
if (!s_hci_driver_uart_dma_env.uhci_ctrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
uhci_stop_receive(s_hci_driver_uart_dma_env.uhci_ctrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wait for in-flight TX and delete the UHCI controller.
|
||||
*
|
||||
* Call only after the process task is gone so it cannot uhci_multi_buffer_transmit()
|
||||
* on a handle that is being deleted. The DMA ring may be freed only after this returns.
|
||||
*/
|
||||
static void
|
||||
hci_driver_uart_dma_uhci_del(void)
|
||||
{
|
||||
if (!s_hci_driver_uart_dma_env.uhci_ctrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
uhci_wait_all_tx_transaction_done(s_hci_driver_uart_dma_env.uhci_ctrl, 100);
|
||||
uhci_del_controller(s_hci_driver_uart_dma_env.uhci_ctrl);
|
||||
s_hci_driver_uart_dma_env.uhci_ctrl = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
hci_driver_uart_dma_task_delete(void)
|
||||
{
|
||||
if (s_hci_driver_uart_dma_env.task_handler) {
|
||||
vTaskDelete(s_hci_driver_uart_dma_env.task_handler);
|
||||
s_hci_driver_uart_dma_env.task_handler = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void hci_driver_uart_dma_install(void)
|
||||
{
|
||||
periph_module_enable(PERIPH_UHCI0_MODULE);
|
||||
periph_module_reset(PERIPH_UHCI0_MODULE);
|
||||
// install DMA driver
|
||||
gdma_channel_alloc_config_t channel_config = {0};
|
||||
|
||||
ESP_ERROR_CHECK(gdma_new_ahb_channel(&channel_config, &s_tx_channel, &s_rx_channel));
|
||||
gdma_connect(s_tx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0));
|
||||
gdma_connect(s_rx_channel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_UHCI, 0));
|
||||
gdma_strategy_config_t strategy_config = {
|
||||
.auto_update_desc = false,
|
||||
.owner_check = false
|
||||
};
|
||||
|
||||
gdma_apply_strategy(s_tx_channel, &strategy_config);
|
||||
gdma_apply_strategy(s_rx_channel, &strategy_config);
|
||||
gdma_rx_event_callbacks_t rx_cbs = {
|
||||
.on_recv_eof = hci_uart_tl_rx_eof_callback
|
||||
};
|
||||
|
||||
gdma_register_rx_event_callbacks(s_rx_channel, &rx_cbs, NULL);
|
||||
gdma_tx_event_callbacks_t tx_cbs = {
|
||||
.on_trans_eof = hci_uart_tl_tx_eof_callback
|
||||
};
|
||||
|
||||
gdma_register_tx_event_callbacks(s_tx_channel, &tx_cbs, NULL);
|
||||
// configure UHCI
|
||||
uhci_ll_init((uhci_dev_t *)s_uhci_hw);
|
||||
// uhci_ll_rx_set_eof_mode((uhci_dev_t *)s_uhci_hw, UHCI_RX_LEN_EOF);
|
||||
uhci_ll_rx_set_eof_mode((uhci_dev_t *)s_uhci_hw, UHCI_RX_IDLE_EOF);
|
||||
// disable software flow control
|
||||
s_uhci_hw->escape_conf.val = 0;
|
||||
uhci_ll_attach_uart_port((uhci_dev_t *)s_uhci_hw, s_hci_driver_uart_dma_env.hci_uart_params->hci_uart_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueue a controller-to-host HCI packet and wake the process task.
|
||||
*
|
||||
* The actual UART DMA send happens in hci_driver_uart_dma_tx_submit() when TX is idle.
|
||||
*/
|
||||
static int
|
||||
hci_driver_uart_dma_tx(hci_driver_data_type_t data_type, uint8_t *data, uint32_t length,
|
||||
hci_driver_direction_t dir)
|
||||
hci_driver_direction_t dir)
|
||||
{
|
||||
/* By now, this layer is only used by controller. */
|
||||
assert(dir == HCI_DRIVER_DIR_C2H);
|
||||
@@ -482,10 +366,14 @@ hci_driver_uart_dma_tx(hci_driver_data_type_t data_type, uint8_t *data, uint32_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief H4 has assembled one host-bound HCI packet; forward it upward.
|
||||
*/
|
||||
static int
|
||||
hci_driver_uart_dma_h4_frame_cb(uint8_t pkt_type, void *data)
|
||||
{
|
||||
hci_driver_forward_fn *forward_cb;
|
||||
|
||||
forward_cb = s_hci_driver_uart_dma_env.forward_cb;
|
||||
if (!forward_cb) {
|
||||
return -1;
|
||||
@@ -494,63 +382,59 @@ hci_driver_uart_dma_h4_frame_cb(uint8_t pkt_type, void *data)
|
||||
return forward_cb(pkt_type, data, 0, HCI_DRIVER_DIR_H2C);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Drain the software RX ringbuf into the H4 state machine.
|
||||
*
|
||||
* Timeout is 0: the process task is already woken by process_sem. Chunks may be
|
||||
* one DMA node or a short EOF tail; H4 concatenates them into HCI packets.
|
||||
*/
|
||||
static void
|
||||
hci_driver_uart_dma_process_rx(void)
|
||||
{
|
||||
size_t item_size;
|
||||
uint8_t *rx_data;
|
||||
int ret;
|
||||
|
||||
if (s_hci_driver_uart_dma_env.rx_copy_overflow) {
|
||||
ESP_LOGE(TAG, "RX software ring buffer overflow, HCI stream may lose sync");
|
||||
s_hci_driver_uart_dma_env.rx_copy_overflow = false;
|
||||
r_ble_ll_hci_ev_hw_err(ESP_HCI_SYNC_LOSS_ERR);
|
||||
}
|
||||
|
||||
while ((rx_data = xRingbufferReceive(s_hci_driver_uart_dma_env.rx_copy_ringbuf, &item_size, 0)) != NULL) {
|
||||
ESP_LOGD(TAG, "uart rx");
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, rx_data, item_size, ESP_LOG_DEBUG);
|
||||
ret = hci_h4_sm_rx(s_hci_driver_uart_dma_env.h4_sm, rx_data, (uint16_t)item_size);
|
||||
/* Return the item before parsing the next slice so the ringbuf can accept more ISR copies. */
|
||||
vRingbufferReturnItem(s_hci_driver_uart_dma_env.rx_copy_ringbuf, rx_data);
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, "parse rx data error!\n");
|
||||
r_ble_ll_hci_ev_hw_err(ESP_HCI_SYNC_LOSS_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serialized TX kick + RX parse.
|
||||
*
|
||||
* Woken by: controller TX enqueue, UHCI TX done, or UHCI RX event. A binary
|
||||
* semaphore is enough because both sides are polled every wake-up.
|
||||
*/
|
||||
static void
|
||||
hci_driver_uart_dma_process_task(void *p)
|
||||
{
|
||||
hci_message_t *rxinfo_container;
|
||||
os_sr_t sr;
|
||||
int ret;
|
||||
uint8_t* rx_data;
|
||||
uint32_t rx_len;
|
||||
(void)p;
|
||||
|
||||
while (true) {
|
||||
xSemaphoreTake(s_hci_driver_uart_dma_env.process_sem, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "task run:%d\n",s_hci_driver_uart_dma_env.hci_tx_state);
|
||||
/* Process Tx data */
|
||||
ESP_LOGD(TAG, "task run:%d\n", s_hci_driver_uart_dma_env.hci_tx_state);
|
||||
|
||||
/* Do not start another uhci_multi_buffer_transmit() while one is in flight. */
|
||||
if (s_hci_driver_uart_dma_env.hci_tx_state == HCI_TRANS_TX_IDLE) {
|
||||
hci_driver_uart_dma_tx_start(hci_driver_uart_dma_send_callback, (void*)&uart_env);
|
||||
hci_driver_uart_dma_tx_submit();
|
||||
}
|
||||
|
||||
if (s_hci_driver_uart_dma_env.rxinfo_mem_exhausted) {
|
||||
rx_data = (void *)uart_env.rx.link_head->buf;
|
||||
rx_len = uart_env.rx.link_head->length;
|
||||
ESP_LOGD(TAG, "rxinfo exhausted:");
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, rx_data, rx_len, ESP_LOG_DEBUG);
|
||||
ret = hci_h4_sm_rx(s_hci_driver_uart_dma_env.h4_sm, rx_data, rx_len);
|
||||
hci_driver_uart_dma_rx_start(rx_data, HCI_RX_DATA_BLOCK_SIZE);
|
||||
hci_driver_uart_dma_rxinfo_mem_exhausted_set(false);
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, "parse rx data error!\n");
|
||||
r_ble_ll_hci_ev_hw_err(ESP_HCI_SYNC_LOSS_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
while (!STAILQ_EMPTY(&g_hci_rxinfo_head)) {
|
||||
OS_ENTER_CRITICAL(sr);
|
||||
rxinfo_container = STAILQ_FIRST(&g_hci_rxinfo_head);
|
||||
STAILQ_REMOVE_HEAD(&g_hci_rxinfo_head, next);
|
||||
OS_EXIT_CRITICAL(sr);
|
||||
|
||||
rx_data = rxinfo_container->ptr;
|
||||
rx_len = rxinfo_container->length;
|
||||
ESP_LOGD(TAG, "uart rx");
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, rx_data, rx_len, ESP_LOG_DEBUG);
|
||||
ret = hci_h4_sm_rx(s_hci_driver_uart_dma_env.h4_sm, rx_data, rx_len);
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, "parse rx data error!\n");
|
||||
r_ble_ll_hci_ev_hw_err(ESP_HCI_SYNC_LOSS_ERR);
|
||||
}
|
||||
|
||||
os_memblock_put(s_hci_driver_uart_dma_env.hci_rxinfo_pool, rxinfo_container);
|
||||
/* No need to enter CRITICAL */
|
||||
if (s_hci_driver_uart_dma_env.is_continue_rx) {
|
||||
/* We should set continux rx flag first, RX interrupted may happened when rx start soon */
|
||||
hci_driver_uart_dma_continue_rx_enable(false);
|
||||
hci_driver_uart_dma_rx_start(rx_data, HCI_RX_DATA_BLOCK_SIZE);
|
||||
} else {
|
||||
os_memblock_put(s_hci_driver_uart_dma_env.hci_rx_data_pool, rx_data);
|
||||
}
|
||||
}
|
||||
hci_driver_uart_dma_process_rx();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,26 +454,39 @@ hci_driver_uart_dma_task_create(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Tear down in reverse-dependency order, with UHCI RX stopped before the task.
|
||||
*
|
||||
* 1. uhci_stop_receive: RX ISR must not write a ringbuf / give a sem we are about to free.
|
||||
* 2. Delete the process task: it must not call UHCI TX APIs during del_controller.
|
||||
* 3. Wait TX + uhci_del_controller: DMA ring is then unused.
|
||||
* 4. UART, working buffers, process_sem, TX list.
|
||||
*/
|
||||
static void
|
||||
hci_driver_uart_dma_deinit(void)
|
||||
{
|
||||
if (s_hci_driver_uart_dma_env.task_handler) {
|
||||
vTaskDelete(s_hci_driver_uart_dma_env.task_handler);
|
||||
s_hci_driver_uart_dma_env.task_handler = NULL;
|
||||
hci_driver_uart_dma_uhci_stop_rx();
|
||||
hci_driver_uart_dma_task_delete();
|
||||
hci_driver_uart_dma_uhci_del();
|
||||
|
||||
if (s_hci_driver_uart_dma_env.hci_uart_params) {
|
||||
/* uart_param_config/set_pin do not uart_driver_install(); delete may be a no-op. */
|
||||
(void)uart_driver_delete(s_hci_driver_uart_dma_env.hci_uart_params->hci_uart_port);
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(uart_driver_delete(s_hci_driver_uart_dma_env.hci_uart_params->hci_uart_port));
|
||||
hci_driver_uart_dma_memory_deinit();
|
||||
if (s_hci_driver_uart_dma_env.process_sem) {
|
||||
vSemaphoreDelete(s_hci_driver_uart_dma_env.process_sem);
|
||||
s_hci_driver_uart_dma_env.process_sem = NULL;
|
||||
}
|
||||
|
||||
hci_driver_util_deinit();
|
||||
memset(&s_hci_driver_uart_dma_env, 0, sizeof(hci_driver_uart_dma_env_t));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Bring up H4, TX list, UART, UHCI continuous RX and the process task.
|
||||
*/
|
||||
static int
|
||||
hci_driver_uart_dma_init(hci_driver_forward_fn *cb)
|
||||
{
|
||||
@@ -598,7 +495,8 @@ hci_driver_uart_dma_init(hci_driver_forward_fn *cb)
|
||||
memset(&s_hci_driver_uart_dma_env, 0, sizeof(hci_driver_uart_dma_env_t));
|
||||
|
||||
s_hci_driver_uart_dma_env.h4_sm = &s_hci_driver_uart_h4_sm;
|
||||
hci_h4_sm_init(s_hci_driver_uart_dma_env.h4_sm, &s_hci_driver_mem_alloc, &s_hci_driver_mem_free, hci_driver_uart_dma_h4_frame_cb);
|
||||
hci_h4_sm_init(s_hci_driver_uart_dma_env.h4_sm, &s_hci_driver_mem_alloc, &s_hci_driver_mem_free,
|
||||
hci_driver_uart_dma_h4_frame_cb);
|
||||
|
||||
rc = hci_driver_util_init();
|
||||
if (rc) {
|
||||
@@ -617,12 +515,13 @@ hci_driver_uart_dma_init(hci_driver_forward_fn *cb)
|
||||
|
||||
s_hci_driver_uart_dma_env.forward_cb = cb;
|
||||
s_hci_driver_uart_dma_env.hci_uart_params = hci_driver_uart_config_param_get();
|
||||
/* UART must be parameterized before uhci_new_controller() attaches the port. */
|
||||
hci_driver_uart_config(s_hci_driver_uart_dma_env.hci_uart_params);
|
||||
|
||||
ESP_LOGI(TAG, "uart attach uhci!");
|
||||
hci_driver_uart_dma_install();
|
||||
|
||||
STAILQ_INIT(&g_hci_rxinfo_head);
|
||||
rc = hci_driver_uart_dma_uhci_install();
|
||||
if (rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = hci_driver_uart_dma_task_create();
|
||||
if (rc) {
|
||||
@@ -630,10 +529,6 @@ hci_driver_uart_dma_init(hci_driver_forward_fn *cb)
|
||||
}
|
||||
|
||||
s_hci_driver_uart_dma_env.hci_tx_state = HCI_TRANS_TX_IDLE;
|
||||
s_hci_driver_uart_dma_env.rxinfo_mem_exhausted = false;
|
||||
s_hci_driver_uart_dma_env.is_continue_rx = false;
|
||||
hci_driver_uart_dma_rx_start(os_memblock_get(s_hci_driver_uart_dma_env.hci_rx_data_pool),
|
||||
HCI_RX_DATA_BLOCK_SIZE);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
@@ -644,10 +539,10 @@ error:
|
||||
int
|
||||
hci_driver_uart_dma_reconfig_pin(int tx_pin, int rx_pin, int cts_pin, int rts_pin)
|
||||
{
|
||||
/* UHCI stays attached to the same UART port; only the GPIO mapping changes. */
|
||||
return hci_driver_uart_pin_update(tx_pin, rx_pin, cts_pin, rts_pin);
|
||||
}
|
||||
|
||||
|
||||
hci_driver_ops_t hci_driver_uart_dma_ops = {
|
||||
.hci_driver_tx = hci_driver_uart_dma_tx,
|
||||
.hci_driver_init = hci_driver_uart_dma_init,
|
||||
|
||||
Reference in New Issue
Block a user