Files
esp-idf/components/bt/common/ble_log/ble_log_spi_out.c
2025-02-26 12:27:22 +08:00

503 lines
15 KiB
C

/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ble_log/ble_log_spi_out.h"
#if CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED
// Private defines
#define SPI_OUT_BUS SPI2_HOST
#define SPI_OUT_TAIL 0xAA
#define SPI_OUT_FLUSHOUT_TIMEOUT (1000 * 1000)
#define SPI_OUT_TS_SYNC_TIMEOUT (1000 * 1000)
// Private typedefs
typedef struct spi_out_trans {
spi_transaction_t trans;
struct spi_out_trans *next;
} spi_out_trans_t;
// Private variables
static spi_device_handle_t spi_handle = NULL;
static spi_out_trans_t *trans_head = NULL;
static SemaphoreHandle_t mutex_handle = NULL;
static bool spi_out_inited = false;
static esp_timer_handle_t flushout_timer_handle = NULL;
static uint32_t loss_frame_cnt = 0;
#if CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
static bool sync_io_level = false;
static esp_timer_handle_t ts_sync_timer_handle = NULL;
#endif // CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// Private function declarations
static void spi_out_init_trans(void);
static void spi_out_deinit_trans(void);
static void spi_out_recycle_trans(uint32_t ms_to_wait);
static void spi_out_append_trans(void);
static int spi_out_write(const uint8_t *addr, uint16_t len);
static void esp_timer_cb_flushout(void);
static void esp_timer_cb_ts_sync(void);
#if CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
#if defined(CONFIG_IDF_TARGET_ESP32H2) || defined(CONFIG_IDF_TARGET_ESP32C6)
extern uint32_t r_ble_lll_timer_current_tick_get(void);
#endif // CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C6
#if defined(CONFIG_IDF_TARGET_ESP32C2)
extern uint32_t r_os_cputime_get32(void);
#endif // CONFIG_IDF_TARGET_ESP32C2
#endif // CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// Private functions
static void spi_out_init_trans(void)
{
for (int i = 0; i < CONFIG_BT_BLE_LOG_SPI_OUT_QUEUE_SIZE; i++) {
// Allocate memory for SPI transaction
uint8_t *buf = (uint8_t *)spi_bus_dma_memory_alloc(SPI_OUT_BUS, CONFIG_BT_BLE_LOG_SPI_OUT_TRANS_BUF_SIZE, 0);
assert(buf);
// Initialize new trans
spi_out_trans_t *new_trans = (spi_out_trans_t *)malloc(sizeof(spi_out_trans_t));
assert(new_trans);
memset(new_trans, 0, sizeof(spi_out_trans_t));
new_trans->trans.tx_buffer = buf;
new_trans->trans.length = 0;
// Append new trans to free trans list
new_trans->next = trans_head;
trans_head = new_trans;
}
return;
}
static void spi_out_deinit_trans(void)
{
// Wait up to QUEUE_SIZE * 100 ms for all transactions to complete and be recycled
spi_out_recycle_trans(100);
// Release memory
spi_out_trans_t *next;
while (trans_head != NULL) {
next = trans_head->next;
free((uint8_t *)trans_head->trans.tx_buffer);
free(trans_head);
trans_head = next;
}
trans_head = NULL;
return;
}
// CRITICAL: Do not recycle trans when trans_head is not empty!
IRAM_ATTR static void spi_out_recycle_trans(uint32_t ms_to_wait)
{
// Try to recycle transaction
spi_transaction_t *ret_trans;
spi_out_trans_t *recycled_trans;
while (ESP_OK == spi_device_get_trans_result(spi_handle, &ret_trans, pdMS_TO_TICKS(ms_to_wait))) {
recycled_trans = __containerof(ret_trans, spi_out_trans_t, trans);
recycled_trans->next = trans_head;
trans_head = recycled_trans;
trans_head->trans.length = 0;
trans_head->trans.rxlength = 0;
}
}
IRAM_ATTR static void spi_out_append_trans(void)
{
// Stop flushout timer
esp_timer_stop(flushout_timer_handle);
// Transaction head shall not be NULL for appending
if (trans_head) {
// Detach transaction head
spi_out_trans_t *trans_to_append = trans_head;
trans_head = trans_head->next;
trans_to_append->next = NULL;
// CRITICAL: Length unit conversion from bytes to bits
trans_to_append->trans.length *= 8;
ESP_ERROR_CHECK(spi_device_queue_trans(spi_handle, &trans_to_append->trans, 0));
}
// Try to recycle trans
spi_out_recycle_trans(0);
// Restart flushout timer
esp_timer_start_once(flushout_timer_handle, SPI_OUT_FLUSHOUT_TIMEOUT);
}
IRAM_ATTR static int spi_out_write(const uint8_t *addr, uint16_t len)
{
// Recycle trans if free buffer list is empty
if (!trans_head) {
spi_out_recycle_trans(0);
}
// Copy user data to buffer
uint16_t copy_buf_len;
uint16_t data_left_len = len;
uint16_t empty_buf_len = CONFIG_BT_BLE_LOG_SPI_OUT_TRANS_BUF_SIZE - trans_head->trans.length;
while (data_left_len) {
// There shall always be available buffer in free buffer list during write operation
if (!trans_head) {
return -1;
}
// Copy data to buffer and update length
copy_buf_len = (data_left_len > empty_buf_len) ? empty_buf_len : data_left_len;
memcpy((uint8_t *)trans_head->trans.tx_buffer + trans_head->trans.length, addr + (len - data_left_len), copy_buf_len);
trans_head->trans.length += copy_buf_len;
data_left_len -= copy_buf_len;
// If buffer is full, append transaction and reset buffer length
if (trans_head->trans.length == CONFIG_BT_BLE_LOG_SPI_OUT_TRANS_BUF_SIZE) {
spi_out_append_trans();
empty_buf_len = CONFIG_BT_BLE_LOG_SPI_OUT_TRANS_BUF_SIZE;
}
}
return 0;
}
// CRITICAL: This function is called in ESP Timer task
IRAM_ATTR static void esp_timer_cb_flushout(void)
{
// Take semaphore
assert(xSemaphoreTakeRecursive(mutex_handle, portMAX_DELAY) == pdTRUE);
// Flushout
if (trans_head) {
// Make sure there's enough space for loss frame counter
if (trans_head->next && loss_frame_cnt) {
ble_log_spi_out_write(BLE_LOG_SPI_OUT_SOURCE_LOSS, (uint8_t *)&loss_frame_cnt, sizeof(loss_frame_cnt));
loss_frame_cnt = 0;
}
if (trans_head->trans.length) {
spi_out_append_trans();
}
}
// Restart flushout timer if not active
if (!esp_timer_is_active(flushout_timer_handle)) {
esp_timer_start_once(flushout_timer_handle, SPI_OUT_FLUSHOUT_TIMEOUT);
}
// Release semaphore
xSemaphoreGiveRecursive(mutex_handle);
}
#if CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// CRITICAL: This function is called in ESP Timer task
IRAM_ATTR static void esp_timer_cb_ts_sync(void)
{
// Initialize variables
uint32_t lc_ts = 0;
uint32_t esp_ts = 0;
// Toggle sync IO
sync_io_level = !sync_io_level;
// Enter critical
portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL_SAFE(&spinlock);
// Get LC timestamp
#if defined(CONFIG_IDF_TARGET_ESP32H2) || defined(CONFIG_IDF_TARGET_ESP32C6)
lc_ts = r_ble_lll_timer_current_tick_get();
#endif // CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C6
#if defined(CONFIG_IDF_TARGET_ESP32C2)
lc_ts = r_os_cputime_get32();
#endif // CONFIG_IDF_TARGET_ESP32C2
// Set sync IO level
gpio_set_level(CONFIG_BT_BLE_LOG_SPI_OUT_SYNC_IO_NUM, (uint32_t)sync_io_level);
// Get ESP timestamp
esp_ts = esp_timer_get_time();
portEXIT_CRITICAL_SAFE(&spinlock);
// Exit critical
// Write timestamp sync log
uint8_t sync_frame[9];
sync_frame[0] = ((uint8_t)sync_io_level & 0xFF);
memcpy(sync_frame + 1, &lc_ts, sizeof(lc_ts));
memcpy(sync_frame + 5, &esp_ts, sizeof(esp_ts));
ble_log_spi_out_write(BLE_LOG_SPI_OUT_SOURCE_SYNC, sync_frame, 9);
}
#endif // CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// Public functions
void ble_log_spi_out_init(void)
{
// Avoid double init
if (spi_out_inited) {
return;
}
// Initialize mutex
mutex_handle = xSemaphoreCreateRecursiveMutex();
// Initialize SPI
spi_bus_config_t bus_config = {
.miso_io_num = -1,
.mosi_io_num = CONFIG_BT_BLE_LOG_SPI_OUT_MOSI_IO_NUM,
.sclk_io_num = CONFIG_BT_BLE_LOG_SPI_OUT_SCLK_IO_NUM,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 10240
};
spi_device_interface_config_t dev_config = {
.clock_speed_hz = SPI_MASTER_FREQ_20M,
.mode = 0,
.spics_io_num = CONFIG_BT_BLE_LOG_SPI_OUT_CS_IO_NUM,
.queue_size = CONFIG_BT_BLE_LOG_SPI_OUT_QUEUE_SIZE
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI_OUT_BUS, &bus_config, SPI_DMA_CH_AUTO));
ESP_ERROR_CHECK(spi_bus_add_device(SPI_OUT_BUS, &dev_config, &spi_handle));
// Initialize transaction link nodes
spi_out_init_trans();
// Initialize flushout timer
esp_timer_create_args_t timer_args = {
.callback = (esp_timer_cb_t)esp_timer_cb_flushout,
.dispatch_method = ESP_TIMER_TASK
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &flushout_timer_handle));
esp_timer_start_once(flushout_timer_handle, SPI_OUT_FLUSHOUT_TIMEOUT);
loss_frame_cnt = 0;
#if CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// Initialize timestamp synchronizer
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1UL << CONFIG_BT_BLE_LOG_SPI_OUT_SYNC_IO_NUM),
.pull_down_en = 0,
.pull_up_en = 0
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
sync_io_level = false;
gpio_set_level(CONFIG_BT_BLE_LOG_SPI_OUT_SYNC_IO_NUM, sync_io_level);
esp_timer_create_args_t ts_sync_timer_args = {
.callback = (esp_timer_cb_t)esp_timer_cb_ts_sync,
.dispatch_method = ESP_TIMER_TASK
};
ESP_ERROR_CHECK(esp_timer_create(&ts_sync_timer_args, &ts_sync_timer_handle));
#endif // CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// Set init flag
spi_out_inited = true;
}
void ble_log_spi_out_deinit(void)
{
// Avoid double deinit
if (!spi_out_inited) {
return;
}
#if CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// Deinitialize timestamp synchronizer
esp_timer_stop(ts_sync_timer_handle);
esp_timer_delete(ts_sync_timer_handle);
gpio_reset_pin(CONFIG_BT_BLE_LOG_SPI_OUT_SYNC_IO_NUM);
#endif // CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
// Deinitialize flushout timer
esp_timer_stop(flushout_timer_handle);
esp_timer_delete(flushout_timer_handle);
// Deinitialize transaction link nodes
spi_out_deinit_trans();
// Deinitialize SPI
ESP_ERROR_CHECK(spi_bus_remove_device(spi_handle));
ESP_ERROR_CHECK(spi_bus_free(SPI_OUT_BUS));
spi_handle = NULL;
// Deinitialize mutex
vSemaphoreDelete(mutex_handle);
mutex_handle = NULL;
// Reset init flag
spi_out_inited = false;
}
#if CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
void ble_log_spi_out_ts_sync_start(void)
{
// Check if SPI out is initialized
if (!spi_out_inited) {
return;
}
// Start timestamp sync timer
if (ts_sync_timer_handle) {
if (!esp_timer_is_active(ts_sync_timer_handle)) {
esp_timer_start_periodic(ts_sync_timer_handle, SPI_OUT_TS_SYNC_TIMEOUT);
}
}
}
void ble_log_spi_out_ts_sync_stop(void)
{
// Check if SPI out is initialized
if (!spi_out_inited) {
return;
}
// Stop timestamp sync timer
if (ts_sync_timer_handle) {
if (esp_timer_is_active(ts_sync_timer_handle)) {
esp_timer_stop(ts_sync_timer_handle);
}
// Set sync IO to low level
sync_io_level = 0;
gpio_set_level(CONFIG_BT_BLE_LOG_SPI_OUT_SYNC_IO_NUM, (uint32_t)sync_io_level);
}
}
#endif // CONFIG_BT_BLE_LOG_SPI_OUT_TS_SYNC_ENABLED
IRAM_ATTR void ble_log_spi_out_write_esp(uint32_t len, const uint8_t *addr, bool end)
{
return ble_log_spi_out_write(BLE_LOG_SPI_OUT_SOURCE_ESP, addr, len);
}
IRAM_ATTR void ble_log_spi_out_write(uint8_t source, const uint8_t *addr, uint16_t len)
{
// Initialize frame sequence number
static uint8_t frame_sn = 0;
// Take semaphore
assert(xSemaphoreTakeRecursive(mutex_handle, portMAX_DELAY) == pdTRUE);
// Prepare frame head and frame tail
const uint8_t head[4] = {len & 0xFF, (len >> 8) & 0xFF, (uint8_t)source, frame_sn};
const uint8_t tail = SPI_OUT_TAIL;
// Write frame head first, then payload, finally frame tail
do {
if (spi_out_write(head, 4) != 0) {
loss_frame_cnt++;
break;
}
if (spi_out_write(addr, len) != 0) {
loss_frame_cnt++;
break;
}
if (spi_out_write(&tail, 1) != 0) {
loss_frame_cnt++;
break;
}
} while (0);
// Update frame sequence number
frame_sn++;
// Release semaphore
xSemaphoreGiveRecursive(mutex_handle);
return;
}
IRAM_ATTR int ble_log_spi_out_printf(uint8_t source, const char *format, ...)
{
// Get esp timestamp
uint32_t esp_ts = esp_timer_get_time();
// Get arguments
va_list args;
va_start(args, format);
// Get len as ref to allocate heap memory
va_list args_copy;
va_copy(args_copy, args);
int len = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
// Length validation
if ((len < 0) || (len > 0xFFFF)) {
va_end(args);
return -1;
}
// Allocate memory
uint8_t *buffer = malloc(len + 1);
if (!buffer) {
va_end(args);
return -1;
}
// Generate string
vsnprintf((char *)buffer, len + 1, format, args);
va_end(args);
// Write to SPI
ble_log_spi_out_write(source, (const uint8_t *)&esp_ts, 4);
ble_log_spi_out_write(source, (const uint8_t *)buffer, len);
// Release
free(buffer);
return 0;
}
IRAM_ATTR int ble_log_spi_out_printf_enh(uint8_t source, uint8_t level, const char *tag, const char *format, ...)
{
// Get ESP timestamp
uint32_t esp_ts = esp_timer_get_time();
// Create log prefix in the format: "[level][tag] "
char prefix[32];
int prefix_len = snprintf(prefix, sizeof(prefix), "[%d][%s] ", level, tag ? tag : "NULL");
// Compute the length of the formatted log message
va_list args;
va_start(args, format);
va_list args_copy;
va_copy(args_copy, args);
int log_len = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
// Validate length
if (log_len < 0 || log_len > 0xFFFF) {
va_end(args);
return -1;
}
// Compute total log length (prefix + formatted message)
int total_len = prefix_len + log_len;
// Allocate memory for the complete log message
uint8_t *buffer = malloc(total_len + 1);
if (!buffer) {
va_end(args);
return -1;
}
// Construct the final log message
memcpy(buffer, prefix, prefix_len); // Copy the prefix
vsnprintf((char *)(buffer + prefix_len), log_len + 1, format, args);
va_end(args);
// Transmit log data via SPI
ble_log_spi_out_write(source, (const uint8_t *)&esp_ts, 4);
ble_log_spi_out_write(source, buffer, total_len);
// Free allocated memory
free(buffer);
return 0;
}
IRAM_ATTR void ble_log_spi_out_write_with_ts(uint8_t source, const uint8_t *addr, uint16_t len)
{
// Get esp timestamp
uint32_t esp_ts = esp_timer_get_time();
// Write to SPI
ble_log_spi_out_write(source, (const uint8_t *)&esp_ts, 4);
ble_log_spi_out_write(source, addr, len);
}
#endif // CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED