Merge branch 'feat/ble-log-build-and-chip-version-report' into 'master'

feat(ble_log): Periodically Report ESP-IDF & BLE Controller Build Commit and Chip Version

See merge request espressif/esp-idf!51947
This commit is contained in:
Island
2026-08-25 11:19:13 +08:00
13 changed files with 467 additions and 14 deletions

View File

@@ -119,6 +119,7 @@ if(CONFIG_BT_ENABLED)
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough -Wno-unused-const-variable)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
register_ble_log_idf_commit()
register_bt_ctrl_libs()
set_bluedroid_host_compile_flags()
set_nimble_host_compile_flags()

View File

@@ -66,6 +66,35 @@ function(wrap_uart_log_tx_interface)
endif()
endfunction()
# Inject the ESP-IDF build commit into the bt component as a private compile
# definition (BLE_LOG_IDF_COMMIT), reported later in the BLE Log version info
# frame. Does nothing when the commit cannot be determined.
function(register_ble_log_idf_commit)
idf_build_get_property(idf_path IDF_PATH)
# "git rev-parse" walks up parent dirs; only trust it when the IDF tree
# itself is a repo/worktree. Release trees have no .git and must leave
# BLE_LOG_IDF_COMMIT undefined instead of picking up an enclosing repo.
if(NOT EXISTS "${idf_path}/.git")
return()
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD
WORKING_DIRECTORY "${idf_path}"
RESULT_VARIABLE ble_log_git_result
OUTPUT_VARIABLE ble_log_idf_commit
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
string(LENGTH "${ble_log_idf_commit}" ble_log_idf_commit_len)
# Take the first 12 chars of the full hash; --short=N may return a longer
# ambiguous abbreviation, so never rely on abbreviated output
if(ble_log_git_result EQUAL 0 AND ble_log_idf_commit_len EQUAL 40)
string(SUBSTRING "${ble_log_idf_commit}" 0 12 ble_log_idf_commit)
target_compile_definitions(${COMPONENT_LIB} PRIVATE
"BLE_LOG_IDF_COMMIT=\"${ble_log_idf_commit}\"")
endif()
endfunction()
set(bt_common_srcs "" PARENT_SCOPE)
set(bt_common_include_dirs "" PARENT_SCOPE)
set(bt_common_priv_include_dirs "" PARENT_SCOPE)

View File

@@ -14,10 +14,39 @@
#include "ble_log_lbm.h"
#include "esp_log.h"
#include "esp_chip_info.h"
/* MACRO */
#define TAG "ble_log_rt"
#if CONFIG_BT_CONTROLLER_ENABLED
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
extern const char *btdm_controller_get_compile_version(void);
#define BLE_LOG_CONTROLLER_GET_COMMIT() btdm_controller_get_compile_version()
#elif !CONFIG_BT_DUAL_MODE_ARCH || CONFIG_BT_CTRL_BLE_ENABLE
/* BR/EDR-only dual-mode builds do not link the BLE controller lib */
extern char *ble_controller_get_compile_version(void);
#define BLE_LOG_CONTROLLER_GET_COMMIT() ble_controller_get_compile_version()
#endif
#if CONFIG_BT_DUAL_MODE_ARCH
/* BTDM common lib (dual-mode arch only) */
extern const char *r_btdm_get_compile_version(void);
#define BLE_LOG_BTDM_COMMON_GET_COMMIT() r_btdm_get_compile_version()
#endif
#endif
#if CONFIG_BLE_MESH && CONFIG_BLE_MESH_V11_SUPPORT
/* "Bluetooth Mesh v1.1 commit: <hash>" */
extern const char bt_mesh_v11_commit_str[];
#endif
#if CONFIG_BT_AUDIO && CONFIG_SOC_BLE_AUDIO_SUPPORTED
extern const char *lib_audio_commit_get(void);
#endif
_Static_assert(sizeof(ble_log_version_info_t) == 58,
"Unexpected BLE Log version info frame size");
/* VARIABLE */
BLE_LOG_STATIC BLE_LOG_DRAM_ATTR uint32_t rt_inited = 0;
BLE_LOG_STATIC BLE_LOG_DRAM_ATTR volatile uint32_t rt_ref_count = 0;
@@ -35,6 +64,12 @@ BLE_LOG_STATIC void ble_log_rt_ts_trigger(void *arg);
#endif /* CONFIG_BLE_LOG_TS_ENABLED */
/* PRIVATE FUNCTION */
/* Copies a NUL-terminated commit string into a fixed-width zero-padded field */
BLE_LOG_STATIC void ble_log_commit_copy(uint8_t *dst, const char *src, size_t len)
{
BLE_LOG_MEMCPY(dst, src, strnlen(src, len));
}
BLE_LOG_STATIC void ble_log_rt_task(void *pvParameters)
{
(void)pvParameters;
@@ -56,12 +91,42 @@ BLE_LOG_STATIC void ble_log_rt_task(void *pvParameters)
}
last_hook_os_ts = curr_os_ts;
/* Write BLE Log info log */
ble_log_info_t ble_log_info = {
.int_src_code = BLE_LOG_INT_SRC_INFO,
/* Write version info: BLE Log version, idf commit (build-time),
* linked-in BLE lib commits, chip model/revision (efuse, runtime-only).
* Libs absent from the build leave their fields zero. */
ble_log_version_info_t version_info = {
.int_src_code = BLE_LOG_INT_SRC_VERSION_INFO,
.version = BLE_LOG_VERSION,
};
ble_log_write_hex(BLE_LOG_SRC_INTERNAL, (const uint8_t *)&ble_log_info, sizeof(ble_log_info_t));
#ifdef BLE_LOG_IDF_COMMIT
BLE_LOG_MEMCPY(version_info.idf_commit, BLE_LOG_IDF_COMMIT, BLE_LOG_IDF_COMMIT_LEN);
#endif
#if CONFIG_BT_CONTROLLER_ENABLED && defined(BLE_LOG_CONTROLLER_GET_COMMIT)
ble_log_commit_copy(version_info.controller_commit, BLE_LOG_CONTROLLER_GET_COMMIT(),
BLE_LOG_LIB_COMMIT_LEN);
#endif
#if CONFIG_BT_CONTROLLER_ENABLED && defined(BLE_LOG_BTDM_COMMON_GET_COMMIT)
ble_log_commit_copy(version_info.btdm_common_commit, BLE_LOG_BTDM_COMMON_GET_COMMIT(),
BLE_LOG_LIB_COMMIT_LEN);
#endif
#if CONFIG_BLE_MESH && CONFIG_BLE_MESH_V11_SUPPORT
/* The hash is the substring after the last space of the lib string */
const char *mesh_commit = strrchr(bt_mesh_v11_commit_str, ' ');
if (mesh_commit) {
ble_log_commit_copy(version_info.mesh_commit, mesh_commit + 1,
BLE_LOG_LIB_COMMIT_LEN);
}
#endif
#if CONFIG_BT_AUDIO && CONFIG_SOC_BLE_AUDIO_SUPPORTED
ble_log_commit_copy(version_info.audio_commit, lib_audio_commit_get(),
BLE_LOG_LIB_COMMIT_LEN);
#endif
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
version_info.chip_model = (uint16_t)chip_info.model;
version_info.chip_revision = chip_info.revision;
ble_log_write_hex(BLE_LOG_SRC_INTERNAL, (const uint8_t *)&version_info,
sizeof(version_info));
ble_log_write_enh_stat();
ble_log_write_buf_util();

View File

@@ -91,7 +91,10 @@ void ble_log_cas_release(volatile bool *cas_lock)
__atomic_store_n(cas_lock, false, __ATOMIC_RELEASE);
}
#define BLE_LOG_VERSION (5)
#define BLE_LOG_VERSION (6)
#define BLE_LOG_IDF_COMMIT_LEN (12)
/* Lib commit hashes are at most 10 hex chars; zero-padded when shorter */
#define BLE_LOG_LIB_COMMIT_LEN (10)
/* TYPEDEF */
typedef enum {
@@ -102,6 +105,7 @@ typedef enum {
BLE_LOG_INT_SRC_FLUSH,
BLE_LOG_INT_SRC_BUF_UTIL,
BLE_LOG_INT_SRC_FINAL_STAT,
BLE_LOG_INT_SRC_VERSION_INFO,
BLE_LOG_INT_SRC_MAX,
} ble_log_int_src_t;
@@ -110,6 +114,18 @@ typedef struct {
uint8_t version;
} __attribute__((packed)) ble_log_info_t;
typedef struct {
uint8_t int_src_code;
uint8_t version;
uint8_t idf_commit[BLE_LOG_IDF_COMMIT_LEN];
uint8_t controller_commit[BLE_LOG_LIB_COMMIT_LEN];
uint8_t btdm_common_commit[BLE_LOG_LIB_COMMIT_LEN];
uint8_t mesh_commit[BLE_LOG_LIB_COMMIT_LEN];
uint8_t audio_commit[BLE_LOG_LIB_COMMIT_LEN];
uint16_t chip_model;
uint16_t chip_revision;
} __attribute__((packed)) ble_log_version_info_t;
/* INTERFACE */
uint32_t ble_log_fast_checksum(const uint8_t *data, size_t len);

View File

@@ -0,0 +1,17 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
components/bt/common/ble_log/test_apps/ble_log_perf_test:
disable:
- if: IDF_TARGET != "none"
temporary: true
reason: No BLE Log test runners are available yet
depends_components:
- bt
components/bt/common/ble_log/test_apps/ble_log_test:
disable:
- if: IDF_TARGET != "none"
temporary: true
reason: No BLE Log test runners are available yet
depends_components:
- bt

View File

@@ -1,9 +0,0 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
components/bt/common/ble_log/test_apps:
disable:
- if: IDF_TARGET != "none"
temporary: true
reason: No BLE Log test runners are available yet
depends_components:
- bt

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: CC0-1.0
cmake_minimum_required(VERSION 3.22)
list(PREPEND SDKCONFIG_DEFAULTS
"$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers"
"sdkconfig.defaults")
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ble_log_test)

View File

@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: Apache-2.0
# BLE Log Functional Test
| Supported Targets |
| ----------------- |
This test app verifies the BLE Log runtime behaviour on target, using the
in-memory test peripheral (`CONFIG_BLE_LOG_PRPH_TEST=y`) to capture the
transport stream written by the runtime task hook.
Currently covered:
- `BLE_LOG_INT_SRC_VERSION_INFO` frame: BLE Log version, ESP-IDF build commit,
controller lib commit, btdm_common lib commit, BLE Mesh and BLE Audio lib
commits, chip model and chip revision

View File

@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: Apache-2.0
idf_component_register(
SRCS "test_ble_log_main.c" "test_ble_log_rt.c"
INCLUDE_DIRS "."
PRIV_REQUIRES unity bt esp_hw_support esp_timer
WHOLE_ARCHIVE
)
idf_component_get_property(bt_dir bt COMPONENT_DIR)
target_include_directories(${COMPONENT_LIB} PRIVATE
"${bt_dir}/common/ble_log/include"
"${bt_dir}/common/ble_log/src/internal_include"
"${bt_dir}/common/ble_log/src/internal_include/prph"
)

View File

@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "ble_log.h"
#include "ble_log_lbm.h"
#include "test_ble_log_main.h"
bool test_ble_log_walk_frames(const uint8_t *data, size_t len,
test_ble_log_frame_observer_t observer, void *ctx)
{
size_t offset = 0;
while (len - offset >= BLE_LOG_FRAME_OVERHEAD) {
ble_log_frame_head_t head;
memcpy(&head, data + offset, sizeof(head));
size_t frame_len = BLE_LOG_FRAME_OVERHEAD + head.length;
if (frame_len > len - offset) {
return false;
}
uint32_t checksum;
memcpy(&checksum, data + offset + BLE_LOG_FRAME_HEAD_LEN + head.length,
sizeof(checksum));
if (checksum != ble_log_fast_checksum(data + offset,
BLE_LOG_FRAME_HEAD_LEN + head.length)) {
return false;
}
if (observer) {
test_ble_log_frame_t frame = {
.src = head.frame_meta & 0xff,
.sn = head.frame_meta >> 8,
.payload = data + offset + BLE_LOG_FRAME_HEAD_LEN,
.payload_len = head.length,
};
observer(&frame, ctx);
}
offset += frame_len;
}
return offset == len;
}
void setUp(void)
{
}
void tearDown(void)
{
}
void app_main(void)
{
/* The BLE Log module has no automatic system init on this branch; the
* controller normally calls ble_log_init(). Initialize it explicitly. */
TEST_ASSERT_TRUE_MESSAGE(ble_log_init(), "BLE Log init failed");
unity_run_menu();
}

View File

@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "ble_log.h"
typedef struct {
ble_log_src_t src;
uint32_t sn;
const uint8_t *payload;
size_t payload_len;
} test_ble_log_frame_t;
typedef void (*test_ble_log_frame_observer_t)(const test_ble_log_frame_t *frame, void *ctx);
/* Walks a captured transport buffer, validating frame headers and checksums.
* Returns true when the whole buffer consists of valid frames. */
bool test_ble_log_walk_frames(const uint8_t *data, size_t len,
test_ble_log_frame_observer_t observer, void *ctx);

View File

@@ -0,0 +1,188 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "esp_chip_info.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "unity.h"
#include "ble_log.h"
#include "ble_log_prph_test.h"
#include "ble_log_rt.h"
#include "test_ble_log_main.h"
#if !CONFIG_BLE_LOG_PRPH_TEST
#error "BLE Log test app requires CONFIG_BLE_LOG_PRPH_TEST"
#endif
/* The runtime task hook is throttled to one pass per
* BLE_LOG_TS_TRIGGER_TIMEOUT_MS; let the window elapse between write bursts
* so a hook pass is guaranteed to run after the settle delay. */
#define TEST_HOOK_SETTLE_MS (BLE_LOG_TS_TRIGGER_TIMEOUT_MS + 100)
#define TEST_READ_TIMEOUT_MS (50)
#define TEST_MAX_ROUNDS (3)
#define TEST_WRITES_PER_ROUND (64)
#define TEST_PAYLOAD_LEN (64)
#define TEST_READ_BUF_SIZE (4096)
#define TEST_READER_STACK_SIZE (3072)
#define TEST_READER_PRIO (2)
typedef struct {
size_t version_info_count;
ble_log_version_info_t version_info;
} version_capture_t;
typedef struct {
version_capture_t capture;
bool malformed;
volatile bool stop;
SemaphoreHandle_t done;
} reader_ctx_t;
static uint8_t s_read_buf[TEST_READ_BUF_SIZE];
/* A commit field is hex characters, zero-padded after a shorter value;
* anything else (garbage, non-hex, zeros after data) is invalid. */
static bool commit_is_valid(const uint8_t *commit, size_t len)
{
bool padding = false;
for (size_t i = 0; i < len; i++) {
if (commit[i] == 0) {
padding = true;
continue;
}
if (padding ||
!((commit[i] >= '0' && commit[i] <= '9') ||
(commit[i] >= 'a' && commit[i] <= 'f') ||
(commit[i] >= 'A' && commit[i] <= 'F'))) {
return false;
}
}
return true;
}
static bool commit_is_zero(const uint8_t *commit, size_t len)
{
for (size_t i = 0; i < len; i++) {
if (commit[i] != 0) {
return false;
}
}
return true;
}
static void capture_version_info_frame(const test_ble_log_frame_t *frame, void *ctx)
{
version_capture_t *capture = ctx;
/* Every frame payload starts with a 4-byte timestamp prefix */
const uint8_t *record = frame->payload + sizeof(uint32_t);
size_t record_len = frame->payload_len - sizeof(uint32_t);
if (frame->src == BLE_LOG_SRC_INTERNAL &&
record_len == sizeof(ble_log_version_info_t) &&
record[0] == BLE_LOG_INT_SRC_VERSION_INFO) {
memcpy(&capture->version_info, record, sizeof(capture->version_info));
capture->version_info_count++;
}
}
/* Consumes pending test transports concurrently with the writer: transports
* are recycled only once read, and the LBM has a small pool of them. */
static void test_reader_task(void *arg)
{
reader_ctx_t *ctx = arg;
while (!ctx->stop) {
size_t len = ble_log_prph_test_read(s_read_buf, sizeof(s_read_buf),
pdMS_TO_TICKS(TEST_READ_TIMEOUT_MS), 0);
if (len > 0 &&
!test_ble_log_walk_frames(s_read_buf, len, capture_version_info_frame,
&ctx->capture)) {
ctx->malformed = true;
}
}
xSemaphoreGive(ctx->done);
vTaskDelete(NULL);
}
TEST_CASE("BLE Log runtime hook reports build and chip versions", "[ble_log]")
{
static const uint8_t payload[TEST_PAYLOAD_LEN] = {0};
reader_ctx_t ctx = {0};
ctx.done = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(ctx.done);
TaskHandle_t reader;
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(test_reader_task, "ble_log_rd",
TEST_READER_STACK_SIZE, &ctx,
TEST_READER_PRIO, &reader));
TEST_ASSERT_TRUE(ble_log_enable(true));
/* Transports are auto-submitted once full, which wakes the runtime task;
* after the throttle window elapses, a hook pass writes the version frame
* into the LBM and a later transport carries it out. ble_log_flush()
* cannot be used here: it disables the module while waiting for the
* transports to drain, so the hook frame written during the flush window
* would be dropped. */
for (int round = 0; round < TEST_MAX_ROUNDS && ctx.capture.version_info_count == 0; round++) {
vTaskDelay(pdMS_TO_TICKS(TEST_HOOK_SETTLE_MS));
for (int i = 0; i < TEST_WRITES_PER_ROUND; i++) {
ble_log_write_hex(BLE_LOG_SRC_CUSTOM, payload, sizeof(payload));
}
vTaskDelay(pdMS_TO_TICKS(200));
}
ctx.stop = true;
TEST_ASSERT_TRUE(xSemaphoreTake(ctx.done, pdMS_TO_TICKS(1000)));
TEST_ASSERT_FALSE(ctx.malformed);
TEST_ASSERT_GREATER_OR_EQUAL_size_t(1, ctx.capture.version_info_count);
const ble_log_version_info_t *vi = &ctx.capture.version_info;
TEST_ASSERT_EQUAL_UINT8(BLE_LOG_INT_SRC_VERSION_INFO, vi->int_src_code);
TEST_ASSERT_EQUAL_UINT8(BLE_LOG_VERSION, vi->version);
TEST_ASSERT_TRUE(commit_is_valid(vi->idf_commit, BLE_LOG_IDF_COMMIT_LEN));
/* Built from an IDF git checkout; the build commit must be injected */
TEST_ASSERT_FALSE(commit_is_zero(vi->idf_commit, BLE_LOG_IDF_COMMIT_LEN));
TEST_ASSERT_TRUE(commit_is_valid(vi->controller_commit, BLE_LOG_LIB_COMMIT_LEN));
TEST_ASSERT_TRUE(commit_is_valid(vi->btdm_common_commit, BLE_LOG_LIB_COMMIT_LEN));
TEST_ASSERT_TRUE(commit_is_valid(vi->mesh_commit, BLE_LOG_LIB_COMMIT_LEN));
TEST_ASSERT_TRUE(commit_is_valid(vi->audio_commit, BLE_LOG_LIB_COMMIT_LEN));
/* A commit field is non-zero only when the matching lib is linked in */
#if CONFIG_BT_CONTROLLER_ENABLED && (!CONFIG_BT_DUAL_MODE_ARCH || CONFIG_BT_CTRL_BLE_ENABLE)
TEST_ASSERT_FALSE(commit_is_zero(vi->controller_commit, BLE_LOG_LIB_COMMIT_LEN));
#else
TEST_ASSERT_TRUE(commit_is_zero(vi->controller_commit, BLE_LOG_LIB_COMMIT_LEN));
#endif
#if CONFIG_BT_CONTROLLER_ENABLED && CONFIG_BT_DUAL_MODE_ARCH
TEST_ASSERT_FALSE(commit_is_zero(vi->btdm_common_commit, BLE_LOG_LIB_COMMIT_LEN));
#else
TEST_ASSERT_TRUE(commit_is_zero(vi->btdm_common_commit, BLE_LOG_LIB_COMMIT_LEN));
#endif
#if CONFIG_BLE_MESH && CONFIG_BLE_MESH_V11_SUPPORT
TEST_ASSERT_FALSE(commit_is_zero(vi->mesh_commit, BLE_LOG_LIB_COMMIT_LEN));
#else
TEST_ASSERT_TRUE(commit_is_zero(vi->mesh_commit, BLE_LOG_LIB_COMMIT_LEN));
#endif
#if CONFIG_BT_AUDIO && CONFIG_SOC_BLE_AUDIO_SUPPORTED
TEST_ASSERT_FALSE(commit_is_zero(vi->audio_commit, BLE_LOG_LIB_COMMIT_LEN));
#else
TEST_ASSERT_TRUE(commit_is_zero(vi->audio_commit, BLE_LOG_LIB_COMMIT_LEN));
#endif
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
TEST_ASSERT_EQUAL_UINT16((uint16_t)chip_info.model, vi->chip_model);
TEST_ASSERT_EQUAL_UINT16(chip_info.revision, vi->chip_revision);
vSemaphoreDelete(ctx.done);
}

View File

@@ -0,0 +1,6 @@
CONFIG_BT_ENABLED=y
CONFIG_BLE_LOG_ENABLED=y
CONFIG_BLE_LOG_PRPH_TEST=y
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
CONFIG_UNITY_ENABLE_64BIT=y
CONFIG_BLE_MESH=y