From 7c2d698baa13905d39115e2eb9bf46adf314e727 Mon Sep 17 00:00:00 2001 From: Zhou Xiao Date: Mon, 31 Aug 2026 17:57:26 +0800 Subject: [PATCH] refactor(ble_log): extract the UART redirection stream writer Move the stream write interface (redir_get_trans / redir_seal / stream_write / stream_flush) out of ble_log_lbm_v2.c into a dedicated ble_log_redir.c with its own internal header, matching the rt/ts module layout. The pool keeps the recycle inflight hook and the flush hooks. The REDIR console stream keeps its own 24-bit frame sequence in ble_log_redir_t: a raw console stream is not a log attempt, so it never consumes the Global SN (which would fake loss gaps). g_frame_sn stays TU-local to ble_log_lbm_v2.c, and the redundant modulo masks are dropped: BLE_LOG_MAKE_FRAME_META enforces the 24-bit wire field at the packing site, so BLE_LOG_SN_MODULO is removed. REDIR payloads are a raw console stream with no timestamp prefix: a per-frame ESP timer timestamp anchored at most ~1s of aggregated text, cost a breaking wire change, and had no consumer on the receiver side. Alignment with the core timeline uses receiver arrival time (bounded by the periodic redirection flush). --- components/bt/common/CMakeLists.txt | 1 + components/bt/common/ble_log/README.md | 26 ++-- .../bt/common/ble_log/src/ble_log_lbm_v2.c | 124 +---------------- .../bt/common/ble_log/src/ble_log_redir.c | 126 ++++++++++++++++++ .../src/internal_include/ble_log_lbm_v2.h | 28 ++-- .../src/internal_include/ble_log_redir.h | 21 +++ .../ble_log/src/prph/ble_log_prph_uart_dma.c | 4 +- 7 files changed, 183 insertions(+), 147 deletions(-) create mode 100644 components/bt/common/ble_log/src/ble_log_redir.c create mode 100644 components/bt/common/ble_log/src/internal_include/ble_log_redir.h diff --git a/components/bt/common/CMakeLists.txt b/components/bt/common/CMakeLists.txt index 1c39e904b4d..7688210c025 100644 --- a/components/bt/common/CMakeLists.txt +++ b/components/bt/common/CMakeLists.txt @@ -171,6 +171,7 @@ if(CONFIG_BLE_LOG_ENABLED) list(APPEND bt_common_srcs "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log.c" "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_lbm_v2.c" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_redir.c" "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_rt.c" "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_util.c" ) diff --git a/components/bt/common/ble_log/README.md b/components/bt/common/ble_log/README.md index bc9b25f2058..ae23c710aba 100644 --- a/components/bt/common/ble_log/README.md +++ b/components/bt/common/ble_log/README.md @@ -57,16 +57,20 @@ The checksum is `ble_log_fast_checksum()` over the six-byte header and exactly `payload length` bytes. It excludes the checksum field and peripheral-only DMA padding. -Core frames begin their payload with: +Core frames (all sources except REDIR) begin their payload with: ```text [4-byte low32 esp_timer_get_time() microseconds][source payload] ``` -The timestamp is captured at API entry before any pool or UART0 redirection -mutex wait. It wraps approximately every 71.6 minutes and must be unwrapped -modulo 2^32 by the receiver. UART0 redirection retains its extension-specific -stream aggregation while using the same ESP Timer timestamp semantics. +The timestamp is captured at API entry before any pool mutex wait. It wraps +approximately every 71.6 minutes and must be unwrapped modulo 2^32 by the +receiver. + +UART0 redirection payload is the raw console stream with no timestamp +prefix: the redirection stream keeps its own frame sequence, and its +receiver-side arrival time (aggregation delay bounded by the periodic +redirection flush) is the alignment reference against the core timeline. ### Sources @@ -86,11 +90,13 @@ value directly, so existing decoders keep working: 8 REDIR extension ``` -All non-INTERNAL sources share one 24-bit Global SN: it is consumed at API -entry, so it totally orders log attempts — including equal-timestamp records -from different sources — and every lost or rejected attempt leaves a gap -in the sequence. Internal Snapshot frames carry their own separate -sequence; a gap there counts skipped snapshots. Neither sequence is reset. +Log sources except INTERNAL and REDIR share one 24-bit Global SN: it is +consumed at API entry, so it totally orders log attempts — including +equal-timestamp records from different sources — and every lost or rejected +attempt leaves a gap in the sequence. Internal Snapshot frames carry their +own separate sequence (a gap counts skipped snapshots), and the REDIR +console stream keeps its own sequence as well (a gap counts a dropped +console batch). None of these sequences is reset. Actual ISR and critical-section records carry `NON_YIELD` in source bit 7. Controller-side HCI records are not emitted by BLE Log, and the controller no diff --git a/components/bt/common/ble_log/src/ble_log_lbm_v2.c b/components/bt/common/ble_log/src/ble_log_lbm_v2.c index a52039fb04a..b7bc01735e3 100644 --- a/components/bt/common/ble_log/src/ble_log_lbm_v2.c +++ b/components/bt/common/ble_log/src/ble_log_lbm_v2.c @@ -83,10 +83,12 @@ BLE_LOG_STATIC BLE_LOG_DRAM_ATTR uint32_t lbm_enabled = 0; BLE_LOG_STATIC volatile bool flush_in_progress = false; BLE_LOG_STATIC BLE_LOG_DRAM_ATTR ble_log_pool_t g_pool; BLE_LOG_STATIC BLE_LOG_DRAM_ATTR ble_log_stat_mgr_t stat_mgr_ctx[BLE_LOG_SRC_MAX]; -/* Global SN (all non-INTERNAL sources) and the separate Internal Snapshot - * sequence; see ble_log_lbm_v2.h. */ +/* Global SN (the log sources except INTERNAL and REDIR) and the separate + * Internal Snapshot sequence; see ble_log_lbm_v2.h. */ BLE_LOG_STATIC BLE_LOG_DRAM_ATTR uint32_t g_frame_sn; BLE_LOG_STATIC BLE_LOG_DRAM_ATTR uint32_t g_snapshot_sn; +#define BLE_LOG_GET_GLOBAL_SN() BLE_LOG_GET_FRAME_SN(g_frame_sn) +#define BLE_LOG_GET_SNAPSHOT_SN() BLE_LOG_GET_FRAME_SN(g_snapshot_sn) BLE_LOG_STATIC BLE_LOG_DRAM_ATTR ble_log_prph_trans_t *internal_trans; BLE_LOG_STATIC BLE_LOG_DRAM_ATTR ble_log_pool_claim_t pool_claim_ctx[BLE_LOG_POOL_TRANS_CNT]; BLE_LOG_STATIC ble_log_internal_snapshot_t internal_snapshot; @@ -123,9 +125,6 @@ BLE_LOG_STATIC void ble_log_stat_mgr_mark_lost(ble_log_src_t src_code); BLE_LOG_STATIC void ble_log_snapshot_stats(ble_log_source_stat_t *snapshots); BLE_LOG_STATIC bool ble_log_pool_flush_all_trans(void); BLE_LOG_STATIC void ble_log_pool_wake_all(void); -#if BLE_LOG_UART_REDIR_ENABLED -BLE_LOG_STATIC void ble_log_redir_seal(ble_log_prph_trans_t *trans, ble_log_src_t src_code); -#endif /* BLE_LOG_UART_REDIR_ENABLED */ /* ------------------------- */ /* BITMAP HELPERS */ @@ -571,121 +570,6 @@ void ble_log_commit(uint32_t handle, size_t actual_len) BLE_LOG_REF_COUNT_RELEASE(&lbm_ref_count); } -#if BLE_LOG_UART_REDIR_ENABLED -/* ------------------------------------------------- */ -/* STREAM WRITE INTERFACE (UART redirection) */ -/* */ -/* Stream mode appends raw data into a transport */ -/* buffer with deferred frame encapsulation. */ -/* Redirection transports are single-writer under */ -/* redir->mutex; their only concurrent mutation is */ -/* the UART tx-done recycle (state SENDING -> FREE), */ -/* so state access uses atomics. */ -/* ------------------------------------------------- */ -BLE_LOG_STATIC -ble_log_prph_trans_t *ble_log_redir_get_trans(ble_log_redir_t *redir, - ble_log_src_t src_code) -{ - for (int i = 0; i < BLE_LOG_TRANS_BUF_CNT; i++) { - ble_log_prph_trans_t *trans = redir->trans[redir->trans_idx]; - if (BLE_LOG_ATOMIC_LOAD_ACQUIRE(trans->state) != BLE_LOG_TRANS_STATE_SENDING) { - if (BLE_LOG_TRANS_FREE_SPACE(trans) >= BLE_LOG_FRAME_OVERHEAD) { - return trans; - } - if (trans->pos > BLE_LOG_FRAME_HEAD_LEN) { - ble_log_redir_seal(trans, src_code); - } - } - redir->trans_idx = (redir->trans_idx + 1) & (BLE_LOG_TRANS_BUF_CNT - 1); - } - return NULL; -} - -BLE_LOG_STATIC -void ble_log_redir_seal(ble_log_prph_trans_t *trans, ble_log_src_t src_code) -{ - if (trans->pos <= BLE_LOG_FRAME_HEAD_LEN) { - return; - } - - uint16_t payload_len = trans->pos - BLE_LOG_FRAME_HEAD_LEN; - /* REDIR frames take the Global SN at seal time; the stream has no - * core-stat slot. */ - uint32_t frame_sn = BLE_LOG_GET_GLOBAL_SN(); - ble_log_frame_head_t frame_head = { - .length = payload_len, - .frame_meta = BLE_LOG_MAKE_FRAME_META(src_code, frame_sn), - }; - BLE_LOG_MEMCPY(trans->buf, &frame_head, BLE_LOG_FRAME_HEAD_LEN); - - uint32_t checksum = ble_log_fast_checksum(trans->buf, trans->pos); - BLE_LOG_MEMCPY(trans->buf + trans->pos, &checksum, BLE_LOG_FRAME_TAIL_LEN); - trans->pos += BLE_LOG_FRAME_TAIL_LEN; - - ble_log_redir_t *redir = ble_log_prph_get_redir_lbm(); - BLE_LOG_ASSERT(redir); - uint32_t infl = __atomic_add_fetch(&redir->inflight, 1, __ATOMIC_RELAXED); - uint32_t peak = BLE_LOG_ATOMIC_LOAD_RELAXED(redir->inflight_peak); - while (infl > peak && - !__atomic_compare_exchange_n(&redir->inflight_peak, &peak, infl, true, - __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { - } - - BLE_LOG_ATOMIC_STORE_RELAXED(trans->state, BLE_LOG_TRANS_STATE_SENDING); - ble_log_rt_submit_trans(trans); -} - -void ble_log_lbm_stream_write(ble_log_redir_t *redir, ble_log_src_t src_code, - uint32_t timestamp, const uint8_t *data, size_t len) -{ - while (len > 0) { - ble_log_prph_trans_t *trans = ble_log_redir_get_trans(redir, src_code); - if (!trans) { - /* Burn one Global SN so the dropped console batch leaves a - * sequence gap. */ - (void)BLE_LOG_GET_GLOBAL_SN(); - return; - } - - if (trans->pos == 0) { - trans->pos = BLE_LOG_FRAME_HEAD_LEN; - BLE_LOG_MEMCPY(trans->buf + trans->pos, ×tamp, - sizeof(timestamp)); - trans->pos += sizeof(timestamp); - } - - uint16_t available = BLE_LOG_TRANS_FREE_SPACE(trans); - if (available <= BLE_LOG_FRAME_TAIL_LEN) { - ble_log_redir_seal(trans, src_code); - continue; - } - available -= BLE_LOG_FRAME_TAIL_LEN; - - size_t to_write = (len < available) ? len : available; - BLE_LOG_MEMCPY(trans->buf + trans->pos, data, to_write); - trans->pos += to_write; - data += to_write; - len -= to_write; - - if (BLE_LOG_TRANS_FREE_SPACE(trans) <= BLE_LOG_FRAME_OVERHEAD) { - ble_log_redir_seal(trans, src_code); - } - } -} - -void ble_log_lbm_stream_flush(ble_log_redir_t *redir, ble_log_src_t src_code) -{ - int trans_idx = redir->trans_idx; - for (int i = 0; i < BLE_LOG_TRANS_BUF_CNT; i++) { - ble_log_prph_trans_t *trans = redir->trans[trans_idx]; - if (BLE_LOG_ATOMIC_LOAD_ACQUIRE(trans->state) != BLE_LOG_TRANS_STATE_SENDING && - trans->pos > BLE_LOG_FRAME_HEAD_LEN) { - ble_log_redir_seal(trans, src_code); - } - trans_idx = (trans_idx + 1) & (BLE_LOG_TRANS_BUF_CNT - 1); - } -} -#endif /* BLE_LOG_UART_REDIR_ENABLED */ BLE_LOG_IRAM_ATTR BLE_LOG_STATIC void ble_log_stat_mgr_mark_lost(ble_log_src_t src_code) diff --git a/components/bt/common/ble_log/src/ble_log_redir.c b/components/bt/common/ble_log/src/ble_log_redir.c new file mode 100644 index 00000000000..baad2794791 --- /dev/null +++ b/components/bt/common/ble_log/src/ble_log_redir.c @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* ------------------------------------------------- */ +/* BLE Log - UART Redirection Stream Writer */ +/* */ +/* Stream mode appends raw data into a transport */ +/* buffer with deferred frame encapsulation. */ +/* Redirection transports are single-writer under */ +/* redir->mutex; their only concurrent mutation is */ +/* the UART tx-done recycle (state SENDING -> FREE), */ +/* so state access uses atomics. */ +/* ------------------------------------------------- */ + +/* INCLUDE */ +#include "ble_log_redir.h" +#include "ble_log_rt.h" + +#if BLE_LOG_UART_REDIR_ENABLED +BLE_LOG_STATIC void ble_log_redir_seal(ble_log_prph_trans_t *trans, ble_log_src_t src_code); + +BLE_LOG_STATIC +ble_log_prph_trans_t *ble_log_redir_get_trans(ble_log_redir_t *redir, + ble_log_src_t src_code) +{ + for (int i = 0; i < BLE_LOG_TRANS_BUF_CNT; i++) { + ble_log_prph_trans_t *trans = redir->trans[redir->trans_idx]; + if (BLE_LOG_ATOMIC_LOAD_ACQUIRE(trans->state) != BLE_LOG_TRANS_STATE_SENDING) { + if (BLE_LOG_TRANS_FREE_SPACE(trans) >= BLE_LOG_FRAME_OVERHEAD) { + return trans; + } + if (trans->pos > BLE_LOG_FRAME_HEAD_LEN) { + ble_log_redir_seal(trans, src_code); + } + } + redir->trans_idx = (redir->trans_idx + 1) & (BLE_LOG_TRANS_BUF_CNT - 1); + } + return NULL; +} + +BLE_LOG_STATIC +void ble_log_redir_seal(ble_log_prph_trans_t *trans, ble_log_src_t src_code) +{ + if (trans->pos <= BLE_LOG_FRAME_HEAD_LEN) { + return; + } + + uint16_t payload_len = trans->pos - BLE_LOG_FRAME_HEAD_LEN; + ble_log_redir_t *redir = ble_log_prph_get_redir_lbm(); + BLE_LOG_ASSERT(redir); + /* REDIR keeps its own stream sequence: a raw console stream is not a + * log attempt, so it never eats Global SNs or fakes loss gaps. The + * stream has no core-stat slot. */ + uint32_t frame_sn = BLE_LOG_GET_FRAME_SN(redir->frame_sn); + ble_log_frame_head_t frame_head = { + .length = payload_len, + .frame_meta = BLE_LOG_MAKE_FRAME_META(src_code, frame_sn), + }; + BLE_LOG_MEMCPY(trans->buf, &frame_head, BLE_LOG_FRAME_HEAD_LEN); + + uint32_t checksum = ble_log_fast_checksum(trans->buf, trans->pos); + BLE_LOG_MEMCPY(trans->buf + trans->pos, &checksum, BLE_LOG_FRAME_TAIL_LEN); + trans->pos += BLE_LOG_FRAME_TAIL_LEN; + + uint32_t infl = __atomic_add_fetch(&redir->inflight, 1, __ATOMIC_RELAXED); + uint32_t peak = BLE_LOG_ATOMIC_LOAD_RELAXED(redir->inflight_peak); + while (infl > peak && + !__atomic_compare_exchange_n(&redir->inflight_peak, &peak, infl, true, + __ATOMIC_RELAXED, __ATOMIC_RELAXED)) { + } + + BLE_LOG_ATOMIC_STORE_RELAXED(trans->state, BLE_LOG_TRANS_STATE_SENDING); + ble_log_rt_submit_trans(trans); +} + +void ble_log_lbm_stream_write(ble_log_redir_t *redir, ble_log_src_t src_code, + const uint8_t *data, size_t len) +{ + while (len > 0) { + ble_log_prph_trans_t *trans = ble_log_redir_get_trans(redir, src_code); + if (!trans) { + /* Burn one stream SN so the dropped console batch leaves a + * gap in the REDIR sequence. */ + (void)BLE_LOG_GET_FRAME_SN(redir->frame_sn); + return; + } + + if (trans->pos == 0) { + trans->pos = BLE_LOG_FRAME_HEAD_LEN; + } + + uint16_t available = BLE_LOG_TRANS_FREE_SPACE(trans); + if (available <= BLE_LOG_FRAME_TAIL_LEN) { + ble_log_redir_seal(trans, src_code); + continue; + } + available -= BLE_LOG_FRAME_TAIL_LEN; + + size_t to_write = (len < available) ? len : available; + BLE_LOG_MEMCPY(trans->buf + trans->pos, data, to_write); + trans->pos += to_write; + data += to_write; + len -= to_write; + + if (BLE_LOG_TRANS_FREE_SPACE(trans) <= BLE_LOG_FRAME_OVERHEAD) { + ble_log_redir_seal(trans, src_code); + } + } +} + +void ble_log_lbm_stream_flush(ble_log_redir_t *redir, ble_log_src_t src_code) +{ + int trans_idx = redir->trans_idx; + for (int i = 0; i < BLE_LOG_TRANS_BUF_CNT; i++) { + ble_log_prph_trans_t *trans = redir->trans[trans_idx]; + if (BLE_LOG_ATOMIC_LOAD_ACQUIRE(trans->state) != BLE_LOG_TRANS_STATE_SENDING && + trans->pos > BLE_LOG_FRAME_HEAD_LEN) { + ble_log_redir_seal(trans, src_code); + } + trans_idx = (trans_idx + 1) & (BLE_LOG_TRANS_BUF_CNT - 1); + } +} +#endif /* BLE_LOG_UART_REDIR_ENABLED */ diff --git a/components/bt/common/ble_log/src/internal_include/ble_log_lbm_v2.h b/components/bt/common/ble_log/src/internal_include/ble_log_lbm_v2.h index aa0260ae377..5754bd8259e 100644 --- a/components/bt/common/ble_log/src/internal_include/ble_log_lbm_v2.h +++ b/components/bt/common/ble_log/src/internal_include/ble_log_lbm_v2.h @@ -84,6 +84,10 @@ typedef struct { ble_log_prph_trans_t *trans[BLE_LOG_TRANS_BUF_CNT]; SemaphoreHandle_t mutex; int trans_idx; + /* The REDIR console stream keeps its own 24-bit frame sequence: it is + * not a log attempt, so it never consumes the Global SN; a gap counts + * a dropped console batch. Zeroed when the manager is created. */ + volatile uint32_t frame_sn; volatile uint32_t inflight; volatile uint32_t inflight_peak; } ble_log_redir_t; @@ -106,18 +110,15 @@ typedef struct { #define BLE_LOG_GET_FRAME_SN(VAR) BLE_LOG_ATOMIC_ADD_RELAXED(VAR, 1) -/* One 24-bit Global SN is shared by every non-INTERNAL source: consumed at - * API entry (before pool contention), it orders all log attempts — - * including equal-timestamp records from different sources — and every - * lost or rejected attempt leaves a gap. Internal Snapshot frames keep - * their own separate sequence: a skipped periodic snapshot burns one - * snapshot SN, so a gap there counts lost snapshots without a dedicated - * field. Neither sequence is ever reset. */ -#define BLE_LOG_SN_MODULO 0x00ffffffU -#define BLE_LOG_GET_GLOBAL_SN() \ - ((uint32_t)BLE_LOG_GET_FRAME_SN(g_frame_sn) & BLE_LOG_SN_MODULO) -#define BLE_LOG_GET_SNAPSHOT_SN() \ - ((uint32_t)BLE_LOG_GET_FRAME_SN(g_snapshot_sn) & BLE_LOG_SN_MODULO) +/* One 24-bit Global SN is shared by the log sources except INTERNAL and + * REDIR: consumed at API entry (before pool contention), it orders all + * log attempts — including equal-timestamp records from different + * sources — and every lost or rejected attempt leaves a gap. INTERNAL + * snapshot frames and the REDIR console stream keep their own separate + * sequences (a gap counts a skipped snapshot or a dropped console batch). + * None of the sequences is ever reset. The per-counter macros live next + * to their counters in the owning translation units; the 24-bit wire + * field is enforced where the frame meta is packed. */ /* -------------------------------- */ /* Internal Snapshot Frame */ @@ -231,9 +232,6 @@ uint8_t *ble_log_claim(ble_log_src_t src_code, size_t max_len, uint32_t *handle) void ble_log_commit(uint32_t handle, size_t actual_len); #if BLE_LOG_UART_REDIR_ENABLED -void ble_log_lbm_stream_write(ble_log_redir_t *redir, ble_log_src_t src_code, - uint32_t timestamp, const uint8_t *data, size_t len); -void ble_log_lbm_stream_flush(ble_log_redir_t *redir, ble_log_src_t src_code); ble_log_redir_t *ble_log_prph_get_redir_lbm(void); #endif diff --git a/components/bt/common/ble_log/src/internal_include/ble_log_redir.h b/components/bt/common/ble_log/src/internal_include/ble_log_redir.h new file mode 100644 index 00000000000..94437db6e61 --- /dev/null +++ b/components/bt/common/ble_log/src/internal_include/ble_log_redir.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __BLE_LOG_REDIR_H__ +#define __BLE_LOG_REDIR_H__ + +/* ---------------------------------------------- */ +/* BLE Log - UART Redirection Stream API */ +/* ---------------------------------------------- */ + +#include "ble_log_lbm_v2.h" + +#if BLE_LOG_UART_REDIR_ENABLED +void ble_log_lbm_stream_write(ble_log_redir_t *redir, ble_log_src_t src_code, + const uint8_t *data, size_t len); +void ble_log_lbm_stream_flush(ble_log_redir_t *redir, ble_log_src_t src_code); +#endif /* BLE_LOG_UART_REDIR_ENABLED */ + +#endif /* __BLE_LOG_REDIR_H__ */ diff --git a/components/bt/common/ble_log/src/prph/ble_log_prph_uart_dma.c b/components/bt/common/ble_log/src/prph/ble_log_prph_uart_dma.c index 2916c29637c..ce0333e577b 100644 --- a/components/bt/common/ble_log/src/prph/ble_log_prph_uart_dma.c +++ b/components/bt/common/ble_log/src/prph/ble_log_prph_uart_dma.c @@ -12,6 +12,7 @@ #include "ble_log_prph_uart_dma.h" #include "ble_log.h" #include "ble_log_lbm_v2.h" +#include "ble_log_redir.h" #include "ble_log_rt.h" #if BLE_LOG_PRPH_UART_DMA_REDIR @@ -326,9 +327,8 @@ bool ble_log_redir_uart_tx_chars(const char *src, size_t len) __atomic_sub_fetch(&redir_writer_count, 1, __ATOMIC_SEQ_CST); return true; } - uint32_t timestamp = (uint32_t)esp_timer_get_time(); xSemaphoreTake(redir_lbm->mutex, portMAX_DELAY); - ble_log_lbm_stream_write(redir_lbm, BLE_LOG_SRC_REDIR, timestamp, + ble_log_lbm_stream_write(redir_lbm, BLE_LOG_SRC_REDIR, (const uint8_t *)src, len); xSemaphoreGive(redir_lbm->mutex); __atomic_sub_fetch(&redir_writer_count, 1, __ATOMIC_SEQ_CST);