mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
feat(bt): Add host-agnostic BT OSAL and shared BLE profile task
- Add bt_osal: event queues, mutexes, semaphores, callouts, etc. - Add the shared BLE profile task and event queue - Bring both up and tear them down in the host init/deinit paths - Add unit tests for the OSAL and the profile task
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# BLE profiles: host-agnostic profile cores and the shared profile
|
||||
# infrastructure they build on (e.g. the shared profile task in common/).
|
||||
#
|
||||
|
||||
set(ble_profiles_srcs "" PARENT_SCOPE)
|
||||
set(ble_profiles_include_dirs "" PARENT_SCOPE)
|
||||
|
||||
if(NOT CONFIG_BT_ENABLED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(_srcs "")
|
||||
# The header is always visible so profiles can include it unconditionally; the
|
||||
# implementation is compiled only when the shared task is enabled.
|
||||
set(_include_dirs "${CMAKE_CURRENT_LIST_DIR}/common/include")
|
||||
|
||||
if(CONFIG_BT_PRF_TASK_ENABLED)
|
||||
list(APPEND _srcs "${CMAKE_CURRENT_LIST_DIR}/common/src/bt_prf_task.c")
|
||||
endif()
|
||||
|
||||
set(ble_profiles_srcs "${_srcs}" PARENT_SCOPE)
|
||||
set(ble_profiles_include_dirs "${_include_dirs}" PARENT_SCOPE)
|
||||
@@ -0,0 +1,7 @@
|
||||
# BLE profiles: aggregation point for the host-agnostic profile cores and the
|
||||
# shared profile infrastructure they build on. Each profile (and the shared
|
||||
# infrastructure in common/) contributes its own Kconfig.in here, so the
|
||||
# top-level bt/Kconfig only ever needs to source this file.
|
||||
#
|
||||
|
||||
source "$IDF_PATH/components/bt/ble_profiles/common/Kconfig.in"
|
||||
@@ -0,0 +1,45 @@
|
||||
config BT_PRF_TASK_ENABLED
|
||||
bool "Enable the shared BLE profile task (event queue worker)"
|
||||
depends on BT_ENABLED
|
||||
default n
|
||||
help
|
||||
Build the shared BLE profile task: a single process-wide event queue
|
||||
served by one dedicated FreeRTOS task, onto which BLE profiles post
|
||||
short, non-blocking handlers instead of each spawning its own task.
|
||||
|
||||
Host-agnostic (depends only on the BT OSAL and FreeRTOS), so both the
|
||||
Bluedroid and NimBLE hosts can use it. A profile that relies on the
|
||||
shared task should "select BT_PRF_TASK_ENABLED" in its own Kconfig.
|
||||
|
||||
config BT_PRF_TASK_STACK_SIZE
|
||||
int "Shared BLE profile task stack size (bytes)"
|
||||
depends on BT_PRF_TASK_ENABLED
|
||||
range 2048 16384
|
||||
default 3072
|
||||
help
|
||||
Stack size, in bytes, of the shared BLE profile task. It must be large
|
||||
enough for the deepest profile handler posted to the queue; raise it if
|
||||
a handler needs more stack.
|
||||
|
||||
config BT_PRF_TASK_PRIORITY
|
||||
int "Shared BLE profile task priority"
|
||||
depends on BT_PRF_TASK_ENABLED
|
||||
range 1 24
|
||||
default 5
|
||||
help
|
||||
FreeRTOS priority of the shared BLE profile task. Keep it below the BLE
|
||||
host task so profile handling never starves the protocol stack.
|
||||
|
||||
config BT_PRF_TASK_CORE_ID
|
||||
int "Shared BLE profile task core affinity"
|
||||
depends on BT_PRF_TASK_ENABLED
|
||||
range -1 0 if FREERTOS_UNICORE
|
||||
range -1 1
|
||||
default 0
|
||||
help
|
||||
Core the shared BLE profile task is pinned to: 0 for CPU0, 1 for CPU1,
|
||||
or -1 to leave it unpinned so the scheduler may run it on either core.
|
||||
|
||||
On a single-core target the only valid values are 0 and -1. Pinning the
|
||||
task to the core that does not run the BLE host can keep profile
|
||||
handlers from competing with the protocol stack for CPU time.
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file bt_prf_task.h
|
||||
* @brief Shared BLE profile task (event queue worker)
|
||||
*
|
||||
* A single, process-wide event queue served by one dedicated FreeRTOS task,
|
||||
* shared by BLE profiles that need to run their business logic off the host
|
||||
* task. Profiles post short, non-blocking handlers here instead of each
|
||||
* spawning its own task, which keeps task count and RAM bounded.
|
||||
*
|
||||
* Profiles post work with bt_prf_task_eventq(): obtain the underlying queue and
|
||||
* drive it directly with a caller-owned ::bt_osal_event embedded in the profile's
|
||||
* own context. This avoids per-post allocation and lets an in-flight event
|
||||
* coalesce (an already-queued event is never enqueued twice), and it is the
|
||||
* queue to bind a ::bt_osal_callout to for delayed work.
|
||||
*
|
||||
* Built only when CONFIG_BT_PRF_TASK_ENABLED is set. A profile that relies on
|
||||
* the shared task should `select BT_PRF_TASK_ENABLED` in its own Kconfig.
|
||||
*
|
||||
* @note This is the profile-facing policy layer on top of the OS-primitive OSAL
|
||||
* (see bt_osal.h). The handler runs in the shared task's context, so it
|
||||
* MUST be non-blocking and quick; hand long or blocking work off to a
|
||||
* dedicated task.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "bt_osal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Bring up the shared BLE profile task
|
||||
*
|
||||
* @note The active host bring-up already calls this (esp_bluedroid_init() /
|
||||
* nimble_port_init()), so profiles do NOT need to. Exposed mainly for tests.
|
||||
*/
|
||||
bt_osal_error_t bt_prf_task_init(void);
|
||||
|
||||
/**
|
||||
* @brief Tear down the shared BLE profile task
|
||||
*
|
||||
* @note The active host tear-down already calls this (esp_bluedroid_deinit() /
|
||||
* nimble_port_deinit()), so profiles do NOT need to. Exposed mainly for tests.
|
||||
*/
|
||||
bt_osal_error_t bt_prf_task_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Get the shared profile task's event queue
|
||||
*
|
||||
* Use it to drive the queue directly with a caller-owned event (zero-allocation
|
||||
* posting via bt_osal_eventq_put()) or to bind a ::bt_osal_callout for delayed
|
||||
* work that runs in the shared task's context.
|
||||
*
|
||||
* @return Pointer to the shared event queue, or NULL if the task is not running
|
||||
*/
|
||||
struct bt_osal_eventq *bt_prf_task_eventq(void);
|
||||
|
||||
/**
|
||||
* @brief Check whether the shared BLE profile task is running
|
||||
*
|
||||
* @return true if bt_prf_task_init() has succeeded and the task is up
|
||||
*/
|
||||
bool bt_prf_task_is_running(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "bt_osal.h"
|
||||
#include "bt_osal_freertos.h"
|
||||
#include "bt_prf_task.h"
|
||||
|
||||
static const char *TAG = "bt_prf_task";
|
||||
|
||||
/**
|
||||
* @brief Internal state of the shared BLE profile task
|
||||
*
|
||||
* Allocated in bt_prf_task_init() and freed in bt_prf_task_deinit(). The
|
||||
* pointer @ref s_prf_task is written only during single-threaded bring-up and
|
||||
* tear-down and read (never written) by bt_prf_task_post(); the documented
|
||||
* lifecycle contract (init happens-before any post, deinit happens-after the
|
||||
* last post) makes it safe without a lock.
|
||||
*/
|
||||
typedef struct {
|
||||
struct bt_osal_eventq evq; /*!< Shared event queue served by the worker task */
|
||||
bool running; /*!< true once the worker task has been started */
|
||||
} bt_prf_task_ctx_t;
|
||||
|
||||
static bt_prf_task_ctx_t *s_prf_task;
|
||||
|
||||
bt_osal_error_t bt_prf_task_init(void)
|
||||
{
|
||||
bt_prf_task_ctx_t *ctx;
|
||||
bt_osal_error_t rc;
|
||||
|
||||
/* Idempotent: a second bring-up while already running is a no-op. */
|
||||
if (s_prf_task != NULL) {
|
||||
return BT_OSAL_OK;
|
||||
}
|
||||
|
||||
ctx = calloc(1, sizeof(*ctx));
|
||||
if (ctx == NULL) {
|
||||
ESP_LOGE(TAG, "no memory for profile task context");
|
||||
return BT_OSAL_ENOMEM;
|
||||
}
|
||||
|
||||
/* Allocates the queue's backing storage; asserts internally on OOM. */
|
||||
bt_osal_eventq_init(&ctx->evq);
|
||||
|
||||
/* Kconfig encodes "any core" as -1; the OSAL wants its own sentinel. */
|
||||
struct bt_osal_task_info task_info = {
|
||||
.name = "bt_profiles",
|
||||
.prio = CONFIG_BT_PRF_TASK_PRIORITY,
|
||||
.stack_size = CONFIG_BT_PRF_TASK_STACK_SIZE,
|
||||
.core_id = (CONFIG_BT_PRF_TASK_CORE_ID < 0) ? BT_OSAL_TASK_NO_AFFINITY
|
||||
: CONFIG_BT_PRF_TASK_CORE_ID,
|
||||
};
|
||||
rc = bt_osal_eventq_start(&ctx->evq, &task_info);
|
||||
if (rc != BT_OSAL_OK) {
|
||||
ESP_LOGE(TAG, "failed to start profile task: %d", rc);
|
||||
bt_osal_eventq_deinit(&ctx->evq);
|
||||
free(ctx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
ctx->running = true;
|
||||
s_prf_task = ctx;
|
||||
|
||||
ESP_LOGI(TAG, "profile task started (stack %d, prio %d)",
|
||||
CONFIG_BT_PRF_TASK_STACK_SIZE, CONFIG_BT_PRF_TASK_PRIORITY);
|
||||
return BT_OSAL_OK;
|
||||
}
|
||||
|
||||
bt_osal_error_t bt_prf_task_deinit(void)
|
||||
{
|
||||
bt_prf_task_ctx_t *ctx = s_prf_task;
|
||||
|
||||
/* Symmetric no-op if bring-up never happened. */
|
||||
if (ctx == NULL) {
|
||||
return BT_OSAL_OK;
|
||||
}
|
||||
|
||||
/* Publish the torn-down state before releasing resources so a stray
|
||||
* bt_prf_task_post() observes "not running" rather than a dangling queue. */
|
||||
s_prf_task = NULL;
|
||||
|
||||
/* Stops (deletes) the worker task, then frees the queue's storage. */
|
||||
bt_osal_eventq_deinit(&ctx->evq);
|
||||
free(ctx);
|
||||
|
||||
ESP_LOGI(TAG, "profile task stopped");
|
||||
return BT_OSAL_OK;
|
||||
}
|
||||
|
||||
struct bt_osal_eventq *bt_prf_task_eventq(void)
|
||||
{
|
||||
bt_prf_task_ctx_t *ctx = s_prf_task;
|
||||
|
||||
return (ctx != NULL && ctx->running) ? &ctx->evq : NULL;
|
||||
}
|
||||
|
||||
bool bt_prf_task_is_running(void)
|
||||
{
|
||||
bt_prf_task_ctx_t *ctx = s_prf_task;
|
||||
|
||||
return ctx != NULL && ctx->running;
|
||||
}
|
||||
Reference in New Issue
Block a user