Merge branch 'bugfix/fix_nimble_issues_3092026_v6.0' into 'release/v6.0'

feat(nimble): Fix few nimble issues 03092026(v6.0)

See merge request espressif/esp-idf!52355
This commit is contained in:
Rahul Tank
2026-09-04 05:50:01 +05:30
28 changed files with 4058 additions and 14 deletions

View File

@@ -61,6 +61,11 @@ if(CONFIG_IDF_DOC_BUILD OR CONFIG_BT_ENABLED)
list(APPEND srcs ${ble_mesh_srcs})
list(APPEND include_dirs ${ble_mesh_include_dirs})
# BLE profiles (host-agnostic profile cores + shared profile infrastructure)
add_subdirectory(ble_profiles)
list(APPEND srcs ${ble_profiles_srcs})
list(APPEND include_dirs ${ble_profiles_include_dirs})
# When log compression is enabled, selected logs are replaced
# by auto-generated macros that emit pre-encoded data.
# This eliminates the original format strings, reducing firmware size and

View File

@@ -109,6 +109,11 @@ menu "Bluetooth"
This option is to configure the buffer size of the hci adv report cache in hci debug mode.
This is a ring buffer, the new data will overwrite the oldest data if the buffer is full.
menu "BLE Profiles"
depends on BT_ENABLED
source "$IDF_PATH/components/bt/ble_profiles/Kconfig.in"
endmenu
config BT_HCI_LOG_INSIGHTS_ENABLE
depends on BT_HCI_LOG_DEBUG_EN
bool "Enable Insights for HCI LOGS BT Stack"

View File

@@ -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)

View File

@@ -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"

View File

@@ -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.

View File

@@ -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

View File

@@ -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;
}

View File

@@ -114,6 +114,7 @@ list(APPEND bt_common_srcs
"${CMAKE_CURRENT_LIST_DIR}/osi/osi.c"
"${CMAKE_CURRENT_LIST_DIR}/osi/semaphore.c"
"${CMAKE_CURRENT_LIST_DIR}/ble_log/deprecated/ble_log_spi_out.c"
"${CMAKE_CURRENT_LIST_DIR}/osal/src/bt_osal_freertos.c"
)
list(APPEND bt_common_include_dirs
@@ -124,6 +125,7 @@ list(APPEND bt_common_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/hci_log/include"
"${CMAKE_CURRENT_LIST_DIR}/ble_log/include"
"${CMAKE_CURRENT_LIST_DIR}/ble_log/deprecated/include"
"${CMAKE_CURRENT_LIST_DIR}/osal/include"
)
list(APPEND bt_common_priv_include_dirs

View File

@@ -21,6 +21,21 @@ config BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT
this mode; use the Controller's own configuration instead of toggling
this host symbol manually.
menu "OSAL (OS abstraction layer)"
depends on BT_ENABLED
config BT_OSAL_USE_ESP_TIMER
bool "Use esp_timer to implement the OSAL callout"
default y
help
Use esp_timer instead of the FreeRTOS software timer to implement the OSAL
callout. esp_timer offers higher precision and runs its callbacks in a
dedicated high-priority task rather than the shared FreeRTOS timer task.
Disable to fall back to the FreeRTOS software timer.
endmenu
choice BT_SMP_CRYPTO_STACK
prompt "SMP cryptographic stack"
depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE || BT_NIMBLE_SECURITY_ENABLE || BT_LE_SECURITY_ENABLE)

View File

@@ -316,6 +316,11 @@ static void btc_thread_handler(void *arg)
static bt_status_t btc_task_post(btc_msg_t *msg, uint32_t timeout)
{
if (btc_thread == NULL) {
BTC_TRACE_WARNING("%s btc_thread is NULL\n", __func__);
return BT_STATUS_NOT_READY;
}
if (osi_thread_post(btc_thread, btc_thread_handler, msg, 0, timeout) == false) {
return BT_STATUS_BUSY;
}

View File

@@ -502,6 +502,10 @@ esp_blufi_gap_event(struct ble_gap_event *event, void *arg)
blufi_env.aggr_buf = NULL;
}
if (!blufi_env.enabled) {
return 0;
}
btc_msg_t msg;
esp_blufi_cb_param_t param;

View File

@@ -0,0 +1,686 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file bt_osal.h
* @brief OS Abstraction Layer (OSAL) for the Bluetooth host
*
* Provides the OS primitives the Bluetooth host relies on: event queues,
* mutexes, semaphores, callouts (one-shot timers), time conversion and
* critical sections.
*
* Every API declared here dispatches through the function table installed by
* bt_osal_freertos_funcs_init(), which MUST be called before any other bt_osal_*
* API is used.
*
*/
#ifndef _BT_OSAL_H_
#define _BT_OSAL_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct bt_osal_event;
/**
* @brief Event handler invoked when a queued event is dispatched
*
* @param ev Event being dispatched. Use bt_osal_event_get_arg() to retrieve the
* argument registered at bt_osal_event_init() time.
*/
typedef void bt_osal_event_fn(struct bt_osal_event *ev);
/**
* @brief OSAL error codes
*/
enum bt_osal_error {
BT_OSAL_OK = 0, /*!< Success */
BT_OSAL_ENOMEM = 1, /*!< Out of memory */
BT_OSAL_EINVAL = 2, /*!< Invalid argument or invalid object state */
BT_OSAL_INVALID_PARAM = 3, /*!< Object is not initialized, or allocation for it failed */
BT_OSAL_MEM_NOT_ALIGNED = 4, /*!< Memory is not aligned as required */
BT_OSAL_BAD_MUTEX = 5, /*!< Mutex is not owned by the calling task */
BT_OSAL_TIMEOUT = 6, /*!< Operation timed out before it could complete */
BT_OSAL_ERR_IN_ISR = 7, /*!< Operation is not allowed from interrupt context */
BT_OSAL_ERR_PRIV = 8, /*!< Operation requires privileges the caller does not have */
BT_OSAL_OS_NOT_STARTED = 9, /*!< The OS scheduler has not been started yet */
BT_OSAL_ENOENT = 10, /*!< Requested object does not exist */
BT_OSAL_EBUSY = 11, /*!< Object is busy */
BT_OSAL_ERROR = 12, /*!< Unspecified error */
};
/**
* @brief OSAL error code type
*/
typedef enum bt_osal_error bt_osal_error_t;
/* Include OS-specific definitions */
#include "bt_osal_os.h"
/*
* Generic
*/
/**
* @brief Check whether the OS scheduler is running
*
* @return true if the scheduler has been started, false otherwise
*/
bool bt_osal_os_started(void);
/**
* @brief Get an opaque identifier of the calling task
*
* @return Handle of the calling task
*/
void *bt_osal_get_current_task_id(void);
/*
* Event queue
*/
/**
* @brief Initialize an event queue
*
* Allocates the queue's backing storage on first use. Calling this on an
* already-initialized queue reuses the storage and discards any pending events,
* leaving the queue empty.
*
* @note Allocates memory, so it must not be called from interrupt context.
*
* @param evq Event queue to initialize
*/
void bt_osal_eventq_init(struct bt_osal_eventq *evq);
/**
* @brief Deinitialize an event queue and release its resources
*
* Deletes the queue's processing task if one was started, then frees the
* backing storage. It is safe to call this on a queue that was never
* initialized or already deinitialized.
*
* @param evq Event queue to deinitialize
*/
void bt_osal_eventq_deinit(struct bt_osal_eventq *evq);
/**
* @brief Dequeue the next event, waiting for one if the queue is empty
*
* The returned event is no longer marked as queued, so it may be posted again.
*
* @note From interrupt context @p tmo MUST be 0; a non-zero timeout asserts.
*
* @param evq Event queue to take the event from
* @param tmo Maximum time to wait, in ticks. Use 0 to poll, or
* BT_OSAL_TIME_FOREVER to wait indefinitely.
*
* @return Next event in the queue, or NULL if none became available within @p tmo
*/
struct bt_osal_event *bt_osal_eventq_get(struct bt_osal_eventq *evq,
bt_osal_time_t tmo);
/**
* @brief Append an event to the back of an event queue
*
* Does nothing if @p ev is already queued, so an event is never enqueued twice.
*
* @param evq Event queue to post to
* @param ev Event to post
*/
void bt_osal_eventq_put(struct bt_osal_eventq *evq, struct bt_osal_event *ev);
/**
* @brief Insert an event at the front of an event queue
*
* Same as bt_osal_eventq_put() except the event is dequeued before all events
* already pending. Does nothing if @p ev is already queued.
*
* @param evq Event queue to post to
* @param ev Event to post
*/
void bt_osal_eventq_put_to_front(struct bt_osal_eventq *evq,
struct bt_osal_event *ev);
/**
* @brief Remove a pending event from an event queue
*
* Does nothing if @p ev is not currently queued.
*
* @param evq Event queue holding the event
* @param ev Event to remove
*/
void bt_osal_eventq_remove(struct bt_osal_eventq *evq,
struct bt_osal_event *ev);
/**
* @brief Start a dedicated task that processes an event queue
*
* Spawns a task that blocks on bt_osal_eventq_get() and dispatches each dequeued
* event with bt_osal_event_run(), so events posted to @p evq are handled
* asynchronously in this task's context. The queue must already be initialized
* with bt_osal_eventq_init().
*
* @note Only one processing task may be started per queue.
*
* @param evq Event queue to process
* @param info Attributes (name, priority, stack size, core affinity) of the
* processing task; @c info->name must not be NULL. Set
* @c info->core_id to BT_OSAL_TASK_NO_AFFINITY to leave the task
* unpinned — a zero-initialized @p info pins it to core 0.
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_EINVAL if @p info or @c info->name is NULL, @c info->core_id is
* neither a valid core nor BT_OSAL_TASK_NO_AFFINITY, the queue is not
* initialized, or it already has a processing task
* - BT_OSAL_ENOMEM if the task could not be created
*/
bt_osal_error_t bt_osal_eventq_start(struct bt_osal_eventq *evq,
const struct bt_osal_task_info *info);
/**
* @brief Post a one-shot function to be run from an event queue
*
* Allocates a self-contained event, posts it to @p evq, and runs @p fn when the
* event is dispatched by the queue's processing task (see bt_osal_eventq_start()).
* The event's backing storage is freed automatically after @p fn returns, so no
* event object needs to be managed by the caller. Inside @p fn the argument
* @p arg is retrievable via bt_osal_event_get_arg().
*
* @note Allocates memory, so it must not be called from interrupt context.
*
* @param evq Event queue to post the work to
* @param fn Handler to run when the event is dispatched
* @param arg Argument passed to @p fn
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_EINVAL if the queue is not initialized or @p fn is NULL
* - BT_OSAL_ERR_IN_ISR if called from interrupt context
* - BT_OSAL_ENOMEM if the work item could not be allocated
*/
bt_osal_error_t bt_osal_eventq_post_func(struct bt_osal_eventq *evq,
bt_osal_event_fn *fn, void *arg);
/**
* @brief Initialize an event with its handler and argument
*
* Allocates the event's backing storage on first use. Calling this on an
* already-initialized event reuses the storage and overwrites the handler and
* argument.
*
* @note Allocates memory, so it must not be called from interrupt context.
*
* @param ev Event to initialize
* @param fn Handler to run when the event is dispatched
* @param arg Argument passed to @p fn, retrievable via bt_osal_event_get_arg()
*/
void bt_osal_event_init(struct bt_osal_event *ev, bt_osal_event_fn *fn,
void *arg);
/**
* @brief Deinitialize an event and free its backing storage
*
* The event must not be queued; remove it with bt_osal_eventq_remove() first.
* It is safe to call this on an event that was never initialized or already
* deinitialized.
*
* @param ev Event to deinitialize
*/
void bt_osal_event_deinit(struct bt_osal_event *ev);
/**
* @brief Clear the queued flag of an event
*
* Only clears the flag; it does NOT unlink the event from a queue it is still
* pending on. To take a queued event out of its queue, use
* bt_osal_eventq_remove().
*
* @warning The event MUST NOT be queued when this is called. Resetting a still-
* queued event desynchronizes its flag from the queue and, with the
* intrusive-list backend, leaves it linked while marked free — a later
* put() then relinks the same node and corrupts the list. Callers must
* bt_osal_eventq_remove() (or let it be dispatched) first; the flag is
* checked with an assertion in debug builds.
*
* @param ev Event to reset (must not be currently queued)
*/
void bt_osal_event_reset(struct bt_osal_event *ev);
/**
* @brief Check whether an event is pending in an event queue
*
* @param ev Event to query
*
* @return true if the event is queued and not yet dispatched, false otherwise
*/
bool bt_osal_event_is_queued(struct bt_osal_event *ev);
/**
* @brief Get the argument registered with an event
*
* @param ev Event to query
*
* @return Argument passed to bt_osal_event_init() or bt_osal_event_set_arg()
*/
void *bt_osal_event_get_arg(struct bt_osal_event *ev);
/**
* @brief Replace the argument registered with an event
*
* @param ev Event to update
* @param arg New argument to pass to the event handler
*/
void bt_osal_event_set_arg(struct bt_osal_event *ev, void *arg);
/**
* @brief Check whether an event queue holds no pending events
*
* @param evq Event queue to query
*
* @return true if the queue is empty, false otherwise
*/
bool bt_osal_eventq_is_empty(struct bt_osal_eventq *evq);
/**
* @brief Run an event's handler in the calling context
*
* Invokes the handler registered by bt_osal_event_init() synchronously; the
* event is not required to have been queued.
*
* @param ev Event to dispatch
*/
void bt_osal_event_run(struct bt_osal_event *ev);
/*
* Mutexes
*/
/**
* @brief Initialize a recursive mutex
*
* Allocates the mutex on first use; calling this on an already-initialized
* mutex leaves it untouched and succeeds.
*
* @note Allocates memory, so it must not be called from interrupt context.
*
* @param mu Mutex to initialize
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_ENOMEM if the mutex could not be allocated
*/
bt_osal_error_t bt_osal_mutex_init(struct bt_osal_mutex *mu);
/**
* @brief Acquire a mutex, waiting for it if it is held by another task
*
* The mutex is recursive: a task already owning it acquires it again without
* blocking, and must release it once per successful pend.
*
* @note Must not be called from interrupt context; doing so asserts.
*
* @param mu Mutex to acquire
* @param timeout Maximum time to wait, in ticks. Use 0 to try without blocking,
* or BT_OSAL_TIME_FOREVER to wait indefinitely.
*
* @return
* - BT_OSAL_OK if the mutex was acquired
* - BT_OSAL_TIMEOUT if it was still held when @p timeout elapsed
* - BT_OSAL_INVALID_PARAM if the mutex is not initialized
*/
bt_osal_error_t bt_osal_mutex_pend(struct bt_osal_mutex *mu,
bt_osal_time_t timeout);
/**
* @brief Release a mutex held by the calling task
*
* A recursively acquired mutex is only released to other tasks once this has
* been called as many times as it was successfully pended.
*
* @note Must not be called from interrupt context; doing so asserts.
*
* @param mu Mutex to release
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_BAD_MUTEX if the calling task does not own the mutex
* - BT_OSAL_INVALID_PARAM if the mutex is not initialized
*/
bt_osal_error_t bt_osal_mutex_release(struct bt_osal_mutex *mu);
/**
* @brief Deinitialize a mutex and free its resources
*
* The mutex must not be held when it is deinitialized.
*
* @param mu Mutex to deinitialize
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_INVALID_PARAM if the mutex is not initialized
*/
bt_osal_error_t bt_osal_mutex_deinit(struct bt_osal_mutex *mu);
/*
* Semaphores
*/
/**
* @brief Initialize a counting semaphore
*
* Allocates the semaphore on first use; calling this on an already-initialized
* semaphore leaves it untouched (including its token count) and succeeds.
*
* @note Allocates memory, so it must not be called from interrupt context.
*
* @param sem Semaphore to initialize
* @param tokens Initial number of tokens
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_ENOMEM if the semaphore could not be allocated
*/
bt_osal_error_t bt_osal_sem_init(struct bt_osal_sem *sem, uint16_t tokens);
/**
* @brief Take one token from a semaphore, waiting if none is available
*
* @note From interrupt context @p timeout MUST be 0; a non-zero timeout asserts.
*
* @param sem Semaphore to take a token from
* @param timeout Maximum time to wait, in ticks. Use 0 to try without blocking,
* or BT_OSAL_TIME_FOREVER to wait indefinitely.
*
* @return
* - BT_OSAL_OK if a token was taken
* - BT_OSAL_TIMEOUT if no token became available within @p timeout
* - BT_OSAL_INVALID_PARAM if the semaphore is not initialized
*/
bt_osal_error_t bt_osal_sem_pend(struct bt_osal_sem *sem,
bt_osal_time_t timeout);
/**
* @brief Return one token to a semaphore, unblocking a waiter if any
*
* @param sem Semaphore to give a token to
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_INVALID_PARAM if the semaphore is not initialized
* - BT_OSAL_ERROR if the token could not be returned (semaphore already full)
*/
bt_osal_error_t bt_osal_sem_release(struct bt_osal_sem *sem);
/**
* @brief Deinitialize a semaphore and free its resources
*
* No task may be pending on the semaphore when it is deinitialized.
*
* @param sem Semaphore to deinitialize
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_INVALID_PARAM if the semaphore is not initialized
*/
bt_osal_error_t bt_osal_sem_deinit(struct bt_osal_sem *sem);
/**
* @brief Get the number of tokens currently held by a semaphore
*
* @param sem Semaphore to query
*
* @return Current token count
*/
uint16_t bt_osal_sem_get_count(struct bt_osal_sem *sem);
/*
* Callouts
*/
/**
* @brief Initialize a callout (one-shot timer)
*
* The callout is created stopped; arm it with bt_osal_callout_reset().
*
* On expiry, if @p evq is non-NULL the callout's event is posted to that queue
* and @p ev_cb runs in the context of the task processing the queue. If @p evq
* is NULL, @p ev_cb is instead run directly in timer context, where it must not
* block and must keep its stack usage small.
*
* Allocates the callout's backing storage on first use; calling this on an
* already-initialized callout reuses the storage and rebinds the queue, handler
* and argument.
*
* @note Allocates memory, so it must not be called from interrupt context.
*
* @param co Callout to initialize
* @param evq Event queue to post the event to on expiry, or NULL to run
* @p ev_cb in timer context
* @param ev_cb Handler to run when the callout expires
* @param ev_arg Argument passed to @p ev_cb
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_ENOMEM if the callout or its underlying timer could not be created
*/
bt_osal_error_t bt_osal_callout_init(struct bt_osal_callout *co, struct bt_osal_eventq *evq,
bt_osal_event_fn *ev_cb, void *ev_arg);
/**
* @brief Stop a callout, delete its timer and free its resources
*
* It is safe to call this on a callout that was never initialized or already
* deinitialized.
*
* @param co Callout to deinitialize
*/
void bt_osal_callout_deinit(struct bt_osal_callout *co);
/**
* @brief (Re)arm a callout to expire after the given delay
*
* Stops the callout if it is already running, then restarts it, so the delay is
* always measured from this call.
*
* @param co Callout to arm
* @param ticks Delay before expiry, in ticks
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_INVALID_PARAM or BT_OSAL_EINVAL if the underlying timer rejected the request
* - BT_OSAL_ERROR on any other failure
*/
bt_osal_error_t bt_osal_callout_reset(struct bt_osal_callout *co,
bt_osal_time_t ticks);
/**
* @brief Stop a callout so it will not expire
*
* Does nothing if the callout is not running. If a previous expiry already
* posted an event to the callout's event queue, that event is removed from
* the queue as part of stopping (unless it has already been dequeued for processing).
*
* @param co Callout to stop
*/
void bt_osal_callout_stop(struct bt_osal_callout *co);
/**
* @brief Check whether a callout is armed and has not expired yet
*
* @param co Callout to query
*
* @return true if the callout is running, false otherwise
*/
bool bt_osal_callout_is_active(struct bt_osal_callout *co);
/**
* @brief Get the absolute time at which a callout will expire
*
* @param co Callout to query
*
* @return Expiry time in ticks, or 0 if it cannot be determined
*/
bt_osal_time_t bt_osal_callout_get_ticks(struct bt_osal_callout *co);
/**
* @brief Get the time left before a callout expires
*
* @param co Callout to query
* @param time Reference time to measure from, in ticks (typically bt_osal_time_get())
*
* @return Ticks remaining until expiry, or 0 if the callout has already expired
* at @p time or its expiry time cannot be determined
*/
bt_osal_time_t bt_osal_callout_remaining_ticks(struct bt_osal_callout *co,
bt_osal_time_t time);
/**
* @brief Replace the argument passed to a callout's handler
*
* @param co Callout to update
* @param arg New argument to pass to the handler on expiry
*/
void bt_osal_callout_set_arg(struct bt_osal_callout *co,
void *arg);
/**
* @brief Clear the queued flag of a callout's event
*
* Lets the callout be armed again after its event was dropped without being
* dispatched. It does not stop the timer.
*
* @param co Callout whose event should be reset
*/
void bt_osal_callout_mem_reset(struct bt_osal_callout *co);
/*
* Time functions
*/
/**
* @brief Get the current time
*
* @return Time since boot, in ticks
*/
bt_osal_time_t bt_osal_time_get(void);
/**
* @brief Get the tick value that means "wait forever"
*
* Use it as the timeout of a pend/get to block indefinitely. Also available as
* the BT_OSAL_TIME_FOREVER macro.
*
* @return Tick value representing an infinite timeout
*/
bt_osal_time_t bt_osal_get_time_forever(void);
/**
* @brief Convert milliseconds to ticks
*
* @param[in] ms Duration in milliseconds
* @param[out] out_ticks Resulting duration in ticks
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_EINVAL if the result does not fit in bt_osal_time_t
*/
bt_osal_error_t bt_osal_time_ms_to_ticks(uint32_t ms, bt_osal_time_t *out_ticks);
/**
* @brief Convert ticks to milliseconds
*
* @param[in] ticks Duration in ticks
* @param[out] out_ms Resulting duration in milliseconds
*
* @return
* - BT_OSAL_OK on success
* - BT_OSAL_EINVAL if the result does not fit in uint32_t
*/
bt_osal_error_t bt_osal_time_ticks_to_ms(bt_osal_time_t ticks, uint32_t *out_ms);
/**
* @brief Convert milliseconds to ticks, without overflow checking
*
* @param ms Duration in milliseconds
*
* @return Duration in ticks
*/
bt_osal_time_t bt_osal_time_ms_to_ticks32(uint32_t ms);
/**
* @brief Convert ticks to milliseconds, without overflow checking
*
* @param ticks Duration in ticks
*
* @return Duration in milliseconds
*/
uint32_t bt_osal_time_ticks_to_ms32(bt_osal_time_t ticks);
/**
* @brief Block the calling task for the given duration
*
* @note Must not be called from interrupt context.
*
* @param ticks Duration to block for, in ticks
*/
void bt_osal_time_delay(bt_osal_time_t ticks);
/*
* Hardware-specific
*
* These symbols should be most likely defined by application since they are
* specific to hardware, not to OS.
*/
#if NIMBLE_CFG_CONTROLLER
/**
* @brief Install an interrupt handler for the given interrupt
*
* @param irqn Interrupt number to install the handler for
* @param addr Address of the interrupt handler
*/
void bt_osal_hw_set_isr(int irqn, uint32_t addr);
#endif
/**
* @brief Enter a critical section, disabling interrupts
*
* Critical sections nest: each call must be paired with a
* bt_osal_hw_exit_critical() call, and interrupts are only re-enabled by the
* outermost one. Keep the section as short as possible and never block in it.
*
* @return Context to be passed back to bt_osal_hw_exit_critical()
*/
uint32_t bt_osal_hw_enter_critical(void);
/**
* @brief Leave a critical section entered with bt_osal_hw_enter_critical()
*
* @param ctx Context returned by the matching bt_osal_hw_enter_critical() call
*/
void bt_osal_hw_exit_critical(uint32_t ctx);
/**
* @brief Check whether the caller is inside a critical section
*
* @return true if at least one critical section is currently entered, false otherwise
*/
bool bt_osal_hw_is_in_critical(void);
#ifdef __cplusplus
}
#endif
#endif /* _BT_OSAL_H_ */

View File

@@ -0,0 +1,156 @@
/*
* SPDX-FileCopyrightText: 2015-2022 The Apache Software Foundation (ASF)
*
* SPDX-License-Identifier: Apache-2.0
*
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*/
#ifndef _BT_OSAL_FREERTOS_H_
#define _BT_OSAL_FREERTOS_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "sdkconfig.h"
/* Use esp timer instead of FreeRTOS timer to implement the callout. */
#ifdef CONFIG_BT_OSAL_USE_ESP_TIMER
#define BT_OSAL_USE_ESP_TIMER (1)
#else
#define BT_OSAL_USE_ESP_TIMER (0)
#endif
typedef void bt_osal_event_fn(struct bt_osal_event *ev);
struct bt_osal_event_freertos {
bool queued;
bt_osal_event_fn *fn;
void *arg;
struct bt_osal_event *next; /*!< Next event in the queue's list, or NULL; valid only while @ref queued */
};
struct bt_osal_eventq_freertos {
struct bt_osal_event *head; /*!< First event to dequeue, NULL when the queue is empty */
struct bt_osal_event *tail; /*!< Last event in the list, NULL when the queue is empty */
SemaphoreHandle_t sem; /*!< Binary "work pending" flag waking a blocked eventq_get(); the list is the source of truth, this only signals */
portMUX_TYPE lock; /*!< Spinlock protecting head/tail and each event's next (task- and ISR-safe) */
TaskHandle_t task; /*!< Handle of the task started to process this event queue, NULL if none */
SemaphoreHandle_t done; /*!< Signalled by the worker just before it self-deletes, so eventq_deinit() can wait for a clean exit; NULL if no task */
bool stop; /*!< Set by eventq_deinit() to ask the worker to exit at its next safe point (between events) */
};
struct bt_osal_callout_freertos {
#if BT_OSAL_USE_ESP_TIMER
esp_timer_handle_t handle;
#else
TimerHandle_t handle;
#endif
struct bt_osal_eventq *evq;
struct bt_osal_event ev;
};
struct bt_osal_mutex_freertos {
SemaphoreHandle_t handle;
};
struct bt_osal_sem_freertos {
SemaphoreHandle_t handle;
};
typedef void bt_osal_event_fn_freertos(struct bt_osal_event_freertos *ev);
// Eventq
struct bt_osal_event *bt_osal_freertos_eventq_get(struct bt_osal_eventq *evq,
bt_osal_time_t tmo);
void bt_osal_freertos_eventq_put(struct bt_osal_eventq *evq,
struct bt_osal_event *ev);
void bt_osal_freertos_eventq_put_to_front(struct bt_osal_eventq *evq,
struct bt_osal_event *ev);
void bt_osal_freertos_eventq_remove(struct bt_osal_eventq *evq,
struct bt_osal_event *ev);
bt_osal_error_t bt_osal_freertos_eventq_start(struct bt_osal_eventq *evq,
const struct bt_osal_task_info *info);
bt_osal_error_t bt_osal_freertos_eventq_post_func(struct bt_osal_eventq *evq,
bt_osal_event_fn *fn, void *arg);
// Mutex
bt_osal_error_t bt_osal_freertos_mutex_init(struct bt_osal_mutex *mu);
bt_osal_error_t bt_osal_freertos_mutex_deinit(struct bt_osal_mutex *mu);
bt_osal_error_t bt_osal_freertos_mutex_pend(struct bt_osal_mutex *mu,
bt_osal_time_t timeout);
bt_osal_error_t bt_osal_freertos_mutex_release(struct bt_osal_mutex *mu);
// Semephore
bt_osal_error_t bt_osal_freertos_sem_init(struct bt_osal_sem *sem, uint16_t tokens);
bt_osal_error_t bt_osal_freertos_sem_deinit(struct bt_osal_sem *sem);
bt_osal_error_t bt_osal_freertos_sem_pend(struct bt_osal_sem *sem,
bt_osal_time_t timeout);
bt_osal_error_t bt_osal_freertos_sem_release(struct bt_osal_sem *sem);
// Callout
bt_osal_error_t bt_osal_freertos_callout_init(struct bt_osal_callout *co,
struct bt_osal_eventq *evq,
bt_osal_event_fn *ev_cb, void *ev_arg);
void bt_osal_freertos_callout_deinit(struct bt_osal_callout *co);
void bt_osal_freertos_callout_stop(struct bt_osal_callout *co);
bool bt_osal_freertos_callout_is_active(struct bt_osal_callout *co);
bt_osal_time_t bt_osal_freertos_callout_get_ticks(struct bt_osal_callout *co);
bt_osal_error_t bt_osal_freertos_callout_reset(struct bt_osal_callout *co,
bt_osal_time_t ticks);
bt_osal_time_t bt_osal_freertos_callout_remaining_ticks(struct bt_osal_callout *co,
bt_osal_time_t now);
// Time
bt_osal_error_t bt_osal_freertos_time_ms_to_ticks(uint32_t ms,
bt_osal_time_t *out_ticks);
bt_osal_error_t bt_osal_freertos_time_ticks_to_ms(bt_osal_time_t ticks,
uint32_t *out_ms);
// Hardware
uint32_t bt_osal_freertos_hw_enter_critical(void);
void bt_osal_freertos_hw_exit_critical(uint32_t ctx);
/**
* @brief Allocate and populate the OSAL function table
*
* Must be called before any bt_osal_* API is used; they all dispatch through the
* table. Asserts if the table cannot be allocated.
*/
void bt_osal_freertos_funcs_init(void);
/**
* @brief Free the OSAL function table allocated by bt_osal_freertos_funcs_init()
*/
void bt_osal_freertos_funcs_deinit(void);
/**
* @brief Get the OSAL function table
*
* @return Pointer to the table, or NULL if bt_osal_freertos_funcs_init() has not
* been called (or the table has since been deinitialized)
*/
struct bt_osal_funcs_t *bt_osal_freertos_funcs_get(void);
#ifdef __cplusplus
}
#endif
#endif /* _BT_OSAL_FREERTOS_H_ */

View File

@@ -0,0 +1,441 @@
/*
* SPDX-FileCopyrightText: 2015-2022 The Apache Software Foundation (ASF)
*
* SPDX-License-Identifier: Apache-2.0
*
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*/
#ifndef _BT_OSAL_OS_H_
#define _BT_OSAL_OS_H_
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_timer.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) \
(sizeof(array) / sizeof((array)[0]))
#endif
extern int ets_printf(const char *fmt, ...);
#define BT_OSAL_ASSERT(con) \
do{ \
if(!(con)) { \
ets_printf("assertion:%s\n",#con); \
ets_printf("line:%d,function:%s\n", __LINE__, __func__);\
assert(0); \
} \
}while(0)
#define BT_OSAL_OS_ALIGNMENT (4)/*bt_osal_get_os_alignment()*/
#define BT_OSAL_TIME_FOREVER bt_osal_get_time_forever()
/* This should be compatible with TickType_t */
typedef uint32_t bt_osal_time_t;
typedef int32_t bt_osal_stime_t;
struct bt_osal_event;
typedef void bt_osal_event_fn(struct bt_osal_event *ev);
struct bt_osal_event {
void *event;
};
struct bt_osal_eventq {
void *eventq;
};
struct bt_osal_callout {
void *co;
};
struct bt_osal_mutex {
void *mutex;
};
struct bt_osal_sem {
void *sem;
};
/**
* @brief Core affinity value meaning "let the scheduler pick any core"
*/
#define BT_OSAL_TASK_NO_AFFINITY ((BaseType_t)tskNO_AFFINITY)
/**
* @brief Attributes for the task started to service an event queue
*
* Passed to bt_osal_eventq_start() so each queue's worker task can be given its
* own name, priority, stack and core affinity.
*/
struct bt_osal_task_info {
const char *name; /*!< Name of the task */
uint8_t prio; /*!< FreeRTOS priority of the task */
uint32_t stack_size; /*!< Stack depth of the task, in bytes */
BaseType_t core_id; /*!< Core to pin the task to, or BT_OSAL_TASK_NO_AFFINITY to leave it unpinned */
};
/*
* Simple APIs are just defined as static inline below, but some are a bit more
* complex or require some global state variables and thus are defined in .c
* file instead and static inline wrapper just calls proper implementation.
* We need declarations of these functions and they are defined in header below.
*/
#include "bt_osal_freertos.h"
struct bt_osal_funcs_t {
bool (*p_bt_osal_os_started)(void);
void *(*p_bt_osal_get_current_task_id)(void);
void (*p_bt_osal_eventq_init)(struct bt_osal_eventq *);
void (*p_bt_osal_eventq_deinit)(struct bt_osal_eventq *);
struct bt_osal_event * (*p_bt_osal_eventq_get)(struct bt_osal_eventq *, bt_osal_time_t);
void (*p_bt_osal_eventq_put)(struct bt_osal_eventq *, struct bt_osal_event *);
void (*p_bt_osal_eventq_remove)(struct bt_osal_eventq *, struct bt_osal_event *);
void (*p_bt_osal_event_run)(struct bt_osal_event *);
bool (*p_bt_osal_eventq_is_empty)(struct bt_osal_eventq *);
void (*p_bt_osal_event_init)(struct bt_osal_event *, bt_osal_event_fn *, void *);
void (*p_bt_osal_event_deinit)(struct bt_osal_event *);
void (*p_bt_osal_event_reset)(struct bt_osal_event *);
bool (*p_bt_osal_event_is_queued)(struct bt_osal_event *);
void * (*p_bt_osal_event_get_arg)(struct bt_osal_event *);
void (*p_bt_osal_event_set_arg)(struct bt_osal_event *, void *);
bt_osal_error_t (*p_bt_osal_mutex_init)(struct bt_osal_mutex *);
bt_osal_error_t (*p_bt_osal_mutex_deinit)(struct bt_osal_mutex *);
bt_osal_error_t (*p_bt_osal_mutex_pend)(struct bt_osal_mutex *, bt_osal_time_t);
bt_osal_error_t (*p_bt_osal_mutex_release)(struct bt_osal_mutex *);
bt_osal_error_t (*p_bt_osal_sem_init)(struct bt_osal_sem *, uint16_t);
bt_osal_error_t (*p_bt_osal_sem_deinit)(struct bt_osal_sem *);
bt_osal_error_t (*p_bt_osal_sem_pend)(struct bt_osal_sem *, bt_osal_time_t);
bt_osal_error_t (*p_bt_osal_sem_release)(struct bt_osal_sem *);
uint16_t (*p_bt_osal_sem_get_count)(struct bt_osal_sem *);
bt_osal_error_t (*p_bt_osal_callout_init)(struct bt_osal_callout *, struct bt_osal_eventq *, bt_osal_event_fn *, void *);
bt_osal_error_t (*p_bt_osal_callout_reset)(struct bt_osal_callout *, bt_osal_time_t);
void (*p_bt_osal_callout_stop)(struct bt_osal_callout *);
void (*p_bt_osal_callout_deinit)(struct bt_osal_callout *);
void (*p_bt_osal_callout_mem_reset)(struct bt_osal_callout *);
bool (*p_bt_osal_callout_is_active)(struct bt_osal_callout *);
bt_osal_time_t (*p_bt_osal_callout_get_ticks)(struct bt_osal_callout *);
uint32_t (*p_bt_osal_callout_remaining_ticks)(struct bt_osal_callout *, bt_osal_time_t);
void (*p_bt_osal_callout_set_arg)(struct bt_osal_callout *, void *);
uint32_t (*p_bt_osal_time_get)(void);
bt_osal_error_t (*p_bt_osal_time_ms_to_ticks)(uint32_t ms, bt_osal_time_t *);
bt_osal_error_t (*p_bt_osal_time_ticks_to_ms)(bt_osal_time_t, uint32_t *);
bt_osal_time_t (*p_bt_osal_time_ms_to_ticks32)(uint32_t);
uint32_t (*p_bt_osal_time_ticks_to_ms32)(bt_osal_time_t);
void (*p_bt_osal_time_delay)(bt_osal_time_t);
void (*p_bt_osal_hw_set_isr)(int, uint32_t);
uint32_t (*p_bt_osal_hw_enter_critical)(void);
void (*p_bt_osal_hw_exit_critical)(uint32_t);
uint32_t (*p_bt_osal_get_time_forever)(void);
uint8_t (*p_bt_osal_hw_is_in_critical)(void);
void (*p_bt_osal_eventq_put_to_front)(struct bt_osal_eventq *, struct bt_osal_event *);
bt_osal_error_t (*p_bt_osal_eventq_start)(struct bt_osal_eventq *evq, const struct bt_osal_task_info *info);
bt_osal_error_t (*p_bt_osal_eventq_post_func)(struct bt_osal_eventq *evq, bt_osal_event_fn *fn, void *arg);
};
extern struct bt_osal_funcs_t *bt_osal_funcs;
static inline bool
IRAM_ATTR bt_osal_os_started(void)
{
return bt_osal_funcs->p_bt_osal_os_started();
}
static inline void *
IRAM_ATTR bt_osal_get_current_task_id(void)
{
return bt_osal_funcs->p_bt_osal_get_current_task_id();
}
static inline void
IRAM_ATTR bt_osal_eventq_init(struct bt_osal_eventq *evq)
{
return bt_osal_funcs->p_bt_osal_eventq_init(evq);
}
static inline void
IRAM_ATTR bt_osal_eventq_deinit(struct bt_osal_eventq *evq)
{
return bt_osal_funcs->p_bt_osal_eventq_deinit(evq);
}
static inline struct bt_osal_event *
IRAM_ATTR bt_osal_eventq_get(struct bt_osal_eventq *evq, bt_osal_time_t tmo)
{
return bt_osal_funcs->p_bt_osal_eventq_get(evq, tmo);
}
static inline void
IRAM_ATTR bt_osal_eventq_put(struct bt_osal_eventq *evq, struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_eventq_put(evq, ev);
}
static inline void
IRAM_ATTR bt_osal_eventq_put_to_front(struct bt_osal_eventq *evq, struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_eventq_put_to_front(evq, ev);
}
static inline void
IRAM_ATTR bt_osal_eventq_remove(struct bt_osal_eventq *evq, struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_eventq_remove(evq, ev);
}
static inline void
IRAM_ATTR bt_osal_event_run(struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_event_run(ev);
}
static inline bool
IRAM_ATTR bt_osal_eventq_is_empty(struct bt_osal_eventq *evq)
{
return bt_osal_funcs->p_bt_osal_eventq_is_empty(evq);
}
static inline void
IRAM_ATTR bt_osal_event_init(struct bt_osal_event *ev, bt_osal_event_fn *fn,
void *arg)
{
return bt_osal_funcs->p_bt_osal_event_init(ev, fn, arg);
}
static inline bool
IRAM_ATTR bt_osal_event_is_queued(struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_event_is_queued(ev);
}
static inline void *
IRAM_ATTR bt_osal_event_get_arg(struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_event_get_arg(ev);
}
static inline void
IRAM_ATTR bt_osal_event_set_arg(struct bt_osal_event *ev, void *arg)
{
return bt_osal_funcs->p_bt_osal_event_set_arg(ev, arg);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_mutex_init(struct bt_osal_mutex *mu)
{
return bt_osal_funcs->p_bt_osal_mutex_init(mu);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_mutex_deinit(struct bt_osal_mutex *mu)
{
return bt_osal_funcs->p_bt_osal_mutex_deinit(mu);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_mutex_pend(struct bt_osal_mutex *mu, bt_osal_time_t timeout)
{
return bt_osal_funcs->p_bt_osal_mutex_pend(mu, timeout);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_mutex_release(struct bt_osal_mutex *mu)
{
return bt_osal_funcs->p_bt_osal_mutex_release(mu);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_sem_init(struct bt_osal_sem *sem, uint16_t tokens)
{
return bt_osal_funcs->p_bt_osal_sem_init(sem, tokens);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_sem_deinit(struct bt_osal_sem *sem)
{
return bt_osal_funcs->p_bt_osal_sem_deinit(sem);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_sem_pend(struct bt_osal_sem *sem, bt_osal_time_t timeout)
{
return bt_osal_funcs->p_bt_osal_sem_pend(sem, timeout);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_sem_release(struct bt_osal_sem *sem)
{
return bt_osal_funcs->p_bt_osal_sem_release(sem);
}
static inline uint16_t
IRAM_ATTR bt_osal_sem_get_count(struct bt_osal_sem *sem)
{
return bt_osal_funcs->p_bt_osal_sem_get_count(sem);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_callout_init(struct bt_osal_callout *co, struct bt_osal_eventq *evq,
bt_osal_event_fn *ev_cb, void *ev_arg)
{
return bt_osal_funcs->p_bt_osal_callout_init(co, evq, ev_cb, ev_arg);
}
static inline void
IRAM_ATTR bt_osal_callout_deinit(struct bt_osal_callout *co)
{
return bt_osal_funcs->p_bt_osal_callout_deinit(co);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_callout_reset(struct bt_osal_callout *co, bt_osal_time_t ticks)
{
return bt_osal_funcs->p_bt_osal_callout_reset(co, ticks);
}
static inline void
IRAM_ATTR bt_osal_callout_stop(struct bt_osal_callout *co)
{
return bt_osal_funcs->p_bt_osal_callout_stop(co);
}
static inline bool
IRAM_ATTR bt_osal_callout_is_active(struct bt_osal_callout *co)
{
return bt_osal_funcs->p_bt_osal_callout_is_active(co);
}
static inline bt_osal_time_t
IRAM_ATTR bt_osal_callout_get_ticks(struct bt_osal_callout *co)
{
return bt_osal_funcs->p_bt_osal_callout_get_ticks(co);
}
static inline bt_osal_time_t
IRAM_ATTR bt_osal_callout_remaining_ticks(struct bt_osal_callout *co,
bt_osal_time_t time)
{
return bt_osal_funcs->p_bt_osal_callout_remaining_ticks(co, time);
}
static inline void
IRAM_ATTR bt_osal_callout_set_arg(struct bt_osal_callout *co, void *arg)
{
return bt_osal_funcs->p_bt_osal_callout_set_arg(co, arg);
}
static inline bt_osal_time_t
IRAM_ATTR bt_osal_time_get(void)
{
return bt_osal_funcs->p_bt_osal_time_get();
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_time_ms_to_ticks(uint32_t ms, bt_osal_time_t *out_ticks)
{
return bt_osal_funcs->p_bt_osal_time_ms_to_ticks(ms, out_ticks);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_time_ticks_to_ms(bt_osal_time_t ticks, uint32_t *out_ms)
{
return bt_osal_funcs->p_bt_osal_time_ticks_to_ms(ticks, out_ms);
}
static inline bt_osal_time_t
IRAM_ATTR bt_osal_time_ms_to_ticks32(uint32_t ms)
{
return bt_osal_funcs->p_bt_osal_time_ms_to_ticks32(ms);
}
static inline uint32_t
IRAM_ATTR bt_osal_time_ticks_to_ms32(bt_osal_time_t ticks)
{
return bt_osal_funcs->p_bt_osal_time_ticks_to_ms32(ticks);
}
static inline void
IRAM_ATTR bt_osal_time_delay(bt_osal_time_t ticks)
{
return bt_osal_funcs->p_bt_osal_time_delay(ticks);
}
#if NIMBLE_CFG_CONTROLLER
static inline void
IRAM_ATTR bt_osal_hw_set_isr(int irqn, uint32_t addr)
{
return bt_osal_funcs->p_bt_osal_hw_set_isr(irqn, addr);
}
#endif
static inline uint32_t
IRAM_ATTR bt_osal_hw_enter_critical(void)
{
return bt_osal_funcs->p_bt_osal_hw_enter_critical();
}
static inline void
IRAM_ATTR bt_osal_hw_exit_critical(uint32_t ctx)
{
return bt_osal_funcs->p_bt_osal_hw_exit_critical(ctx);
}
static inline bool
IRAM_ATTR bt_osal_hw_is_in_critical(void)
{
return bt_osal_funcs->p_bt_osal_hw_is_in_critical();
}
static inline bt_osal_time_t
IRAM_ATTR bt_osal_get_time_forever(void)
{
return bt_osal_funcs->p_bt_osal_get_time_forever();
}
static inline void
IRAM_ATTR bt_osal_callout_mem_reset(struct bt_osal_callout *co)
{
return bt_osal_funcs->p_bt_osal_callout_mem_reset(co);
}
static inline void
IRAM_ATTR bt_osal_event_deinit(struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_event_deinit(ev);
}
static inline void
IRAM_ATTR bt_osal_event_reset(struct bt_osal_event *ev)
{
return bt_osal_funcs->p_bt_osal_event_reset(ev);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_eventq_start(struct bt_osal_eventq *evq, const struct bt_osal_task_info *info)
{
return bt_osal_funcs->p_bt_osal_eventq_start(evq, info);
}
static inline bt_osal_error_t
IRAM_ATTR bt_osal_eventq_post_func(struct bt_osal_eventq *evq, bt_osal_event_fn *fn, void *arg)
{
return bt_osal_funcs->p_bt_osal_eventq_post_func(evq, fn, arg);
}
#ifdef __cplusplus
}
#endif
#endif /* _BT_OSAL_OS_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,8 @@
#include "config/stack_config.h"
#include "hci_log/bt_hci_log.h"
#include "bt_common.h"
#include "bt_osal.h"
#include "bt_prf_task.h"
static esp_bluedroid_status_t s_bt_host_state = ESP_BLUEDROID_STATUS_UNINITIALIZED;
@@ -233,6 +235,17 @@ esp_err_t esp_bluedroid_init_with_cfg(esp_bluedroid_config_t *cfg)
}
#endif // (BT_HCI_LOG_INCLUDED == TRUE)
/* Bring up the host-agnostic bt_osal function table */
bt_osal_freertos_funcs_init();
#if CONFIG_BT_PRF_TASK_ENABLED
/* Start the shared BLE profile task now that the bt_osal table is up, so
* profiles can post work without spawning their own tasks. */
if (bt_prf_task_init() != BT_OSAL_OK) {
LOG_WARN("bt_prf_task_init failed");
}
#endif
s_bt_host_state = ESP_BLUEDROID_STATUS_INITIALIZED;
return ESP_OK;
@@ -289,6 +302,15 @@ esp_err_t esp_bluedroid_deinit(void)
osi_mem_deinit();
#endif
#if CONFIG_BT_PRF_TASK_ENABLED
/* Stop the shared BLE profile task while the bt_osal table is still up,
* symmetrically with the init in esp_bluedroid_init_with_cfg(). */
bt_prf_task_deinit();
#endif
/* Release the bt_osal function table set up in esp_bluedroid_init_with_cfg(). */
bt_osal_freertos_funcs_deinit();
s_bt_host_state = ESP_BLUEDROID_STATUS_UNINITIALIZED;
return ESP_OK;
}

View File

@@ -426,28 +426,28 @@ menu "GATT / ATT"
config BT_NIMBLE_GATT_CACHING_MAX_SVCS
int "Maximum number of services per connection"
depends on BT_NIMBLE_GATT_CACHING
default 64
default 8
help
Set this option to set the upper limit on number of services per connection to be cached.
config BT_NIMBLE_GATT_CACHING_MAX_INCL_SVCS
int "Maximum number of included services per connection"
depends on BT_NIMBLE_GATT_CACHING
default 64
default 8
help
Set this option to set the upper limit on number of included services per connection to be cached.
config BT_NIMBLE_GATT_CACHING_MAX_CHRS
int "Maximum number of characteristics per connection"
depends on BT_NIMBLE_GATT_CACHING
default 64
default 58
help
Set this option to set the upper limit on number of characteristics per connection to be cached.
config BT_NIMBLE_GATT_CACHING_MAX_DSCS
int "Maximum number of descriptors per connection"
depends on BT_NIMBLE_GATT_CACHING
default 64
default 58
help
Set this option to set the upper limit on number of descriptors per connection to be cached.
@@ -1357,6 +1357,14 @@ menu "Extra Features"
instead of DRAM (.iram.text/.text in internal RAM). Suitable for low-speed
GAP/GATT operations to reduce RAM usage.
config BT_NIMBLE_GAP_ALLOW_ADV_RESTART
bool "Allow restarting ongoing advertising"
default n
help
If enabled, stop ongoing advertising before starting it again with
new parameters,if any. If disabled, starting advertising while it is already
active returns BLE_HS_EALREADY.
endmenu
menu "NimBLE Mesh"

View File

@@ -2410,5 +2410,12 @@
#endif
#endif
#ifndef MYNEWT_VAL_BLE_GAP_ALLOW_ADV_RESTART
#ifdef CONFIG_BT_NIMBLE_GAP_ALLOW_ADV_RESTART
#define MYNEWT_VAL_BLE_GAP_ALLOW_ADV_RESTART CONFIG_BT_NIMBLE_GAP_ALLOW_ADV_RESTART
#else
#define MYNEWT_VAL_BLE_GAP_ALLOW_ADV_RESTART (0)
#endif
#endif
#endif

View File

@@ -1,5 +1,5 @@
| Supported Targets | ESP32 | ESP32-C3 |
| ----------------- | ----- | -------- |
| ----------------- | ----- | -------- |
# `bt` component unit tests

View File

@@ -1,6 +1,9 @@
idf_component_register(SRCS "test_bt_main.c"
"test_bt_common.c"
"test_smp.c"
"test_tinycrypt_ecc.c"
"test_osal.c"
"test_prf_task.c"
INCLUDE_DIRS "."
PRIV_REQUIRES unity bt
WHOLE_ARCHIVE)
@@ -11,3 +14,4 @@ target_include_directories(${COMPONENT_LIB} PRIVATE ${bt_dir}/host/bluedroid/sta
target_include_directories(${COMPONENT_LIB} PRIVATE ${bt_dir}/host/bluedroid/common/include)
target_include_directories(${COMPONENT_LIB} PRIVATE ${bt_dir}/host/bluedroid/stack/include)
target_include_directories(${COMPONENT_LIB} PRIVATE ${bt_dir}/common/include)
target_include_directories(${COMPONENT_LIB} PRIVATE ${bt_dir}/common/tinycrypt/include)

View File

@@ -0,0 +1,674 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* Unit tests for the BT OSAL porting layer (components/bt/common/osal): events and
* the list-backed event queue, the queue worker task, mutexes, semaphores,
* callouts, time helpers and critical sections.
*
* Every test brings the OSAL function table up and tears it down again, so the
* per-test heap check in tearDown() also covers the layer's own allocations.
*/
#include <stdbool.h>
#include <stdint.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "bt_osal.h"
#include "bt_osal_freertos.h"
#define TEST_EVQ_STACK_SIZE 3072
#define TEST_EVQ_PRIO (configMAX_PRIORITIES - 3)
#define TEST_HELPER_STACK 2560
/* Long enough for a blocking call to succeed, short enough to keep the suite quick. */
#define TEST_WAIT_MS 1000
/* Waited on when an operation is expected NOT to happen. */
#define TEST_NO_EVENT_MS 50
/* Shared state between the test task and the event/timer callbacks. */
static SemaphoreHandle_t s_done;
static volatile uint32_t s_run_count;
static struct bt_osal_event *s_last_ev;
static void *s_last_arg;
/* Attributes shared by the worker tasks started across these tests. */
static const struct bt_osal_task_info s_test_task_info = {
.name = "osal_test_evq",
.prio = TEST_EVQ_PRIO,
.stack_size = TEST_EVQ_STACK_SIZE,
.core_id = BT_OSAL_TASK_NO_AFFINITY,
};
/* Core the worker task dispatched the last event on; recorded by ev_cb_core(). */
static volatile BaseType_t s_last_core;
static void ev_cb_count(struct bt_osal_event *ev)
{
s_run_count++;
s_last_ev = ev;
s_last_arg = bt_osal_event_get_arg(ev);
xSemaphoreGive(s_done);
}
static void ev_cb_core(struct bt_osal_event *ev)
{
s_last_core = xPortGetCoreID();
ev_cb_count(ev);
}
/* Bring up the OSAL function table; every bt_osal_* call dispatches through it. */
static void osal_test_begin(void)
{
s_run_count = 0;
s_last_ev = NULL;
s_last_arg = NULL;
s_done = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(s_done);
bt_osal_freertos_funcs_init();
TEST_ASSERT_NOT_NULL(bt_osal_freertos_funcs_get());
}
static void osal_test_end(void)
{
bt_osal_freertos_funcs_deinit();
TEST_ASSERT_NULL(bt_osal_freertos_funcs_get());
vSemaphoreDelete(s_done);
s_done = NULL;
/* Tasks deleted during the test (queue workers, helpers) and timers deleted
* by callout_deinit() are freed asynchronously by the idle and timer tasks;
* give them a moment so the heap check in tearDown() is not tripped.
*/
vTaskDelay(pdMS_TO_TICKS(20));
}
TEST_CASE("osal event init, run, arg accessors and deinit", "[osal]")
{
struct bt_osal_event ev = {0};
osal_test_begin();
bt_osal_event_init(&ev, ev_cb_count, (void *)0x1234);
TEST_ASSERT_NOT_NULL(ev.event);
TEST_ASSERT_FALSE(bt_osal_event_is_queued(&ev));
TEST_ASSERT_EQUAL_PTR((void *)0x1234, bt_osal_event_get_arg(&ev));
bt_osal_event_set_arg(&ev, (void *)0x5678);
TEST_ASSERT_EQUAL_PTR((void *)0x5678, bt_osal_event_get_arg(&ev));
/* Running an event outside a queue calls the handler in the caller's context. */
bt_osal_event_run(&ev);
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
TEST_ASSERT_EQUAL_PTR(&ev, s_last_ev);
TEST_ASSERT_EQUAL_PTR((void *)0x5678, s_last_arg);
bt_osal_event_deinit(&ev);
TEST_ASSERT_NULL(ev.event);
/* Deinit of an already released event is a no-op. */
bt_osal_event_deinit(&ev);
osal_test_end();
}
TEST_CASE("osal eventq keeps FIFO order and ignores a duplicate put", "[osal]")
{
struct bt_osal_eventq evq = {0};
struct bt_osal_event ev1 = {0};
struct bt_osal_event ev2 = {0};
struct bt_osal_event ev3 = {0};
osal_test_begin();
bt_osal_eventq_init(&evq);
TEST_ASSERT_NOT_NULL(evq.eventq);
TEST_ASSERT_TRUE(bt_osal_eventq_is_empty(&evq));
TEST_ASSERT_NULL(bt_osal_eventq_get(&evq, 0));
bt_osal_event_init(&ev1, ev_cb_count, NULL);
bt_osal_event_init(&ev2, ev_cb_count, NULL);
bt_osal_event_init(&ev3, ev_cb_count, NULL);
bt_osal_eventq_put(&evq, &ev1);
bt_osal_eventq_put(&evq, &ev2);
/* An event that is already queued must not be linked (or counted) twice. */
bt_osal_eventq_put(&evq, &ev1);
TEST_ASSERT_TRUE(bt_osal_event_is_queued(&ev1));
TEST_ASSERT_FALSE(bt_osal_eventq_is_empty(&evq));
/* put_to_front() jumps the queue. */
bt_osal_eventq_put_to_front(&evq, &ev3);
TEST_ASSERT_EQUAL_PTR(&ev3, bt_osal_eventq_get(&evq, 0));
TEST_ASSERT_EQUAL_PTR(&ev1, bt_osal_eventq_get(&evq, 0));
TEST_ASSERT_EQUAL_PTR(&ev2, bt_osal_eventq_get(&evq, 0));
TEST_ASSERT_TRUE(bt_osal_eventq_is_empty(&evq));
TEST_ASSERT_FALSE(bt_osal_event_is_queued(&ev1));
/* The duplicate put left no extra token behind: the queue is really empty. */
TEST_ASSERT_NULL(bt_osal_eventq_get(&evq, pdMS_TO_TICKS(TEST_NO_EVENT_MS)));
bt_osal_event_deinit(&ev1);
bt_osal_event_deinit(&ev2);
bt_osal_event_deinit(&ev3);
bt_osal_eventq_deinit(&evq);
TEST_ASSERT_NULL(evq.eventq);
osal_test_end();
}
TEST_CASE("osal eventq_remove unlinks head, middle and tail events", "[osal]")
{
struct bt_osal_eventq evq = {0};
struct bt_osal_event ev1 = {0};
struct bt_osal_event ev2 = {0};
struct bt_osal_event ev3 = {0};
osal_test_begin();
bt_osal_eventq_init(&evq);
bt_osal_event_init(&ev1, ev_cb_count, NULL);
bt_osal_event_init(&ev2, ev_cb_count, NULL);
bt_osal_event_init(&ev3, ev_cb_count, NULL);
/* Remove from the middle. */
bt_osal_eventq_put(&evq, &ev1);
bt_osal_eventq_put(&evq, &ev2);
bt_osal_eventq_put(&evq, &ev3);
bt_osal_eventq_remove(&evq, &ev2);
TEST_ASSERT_FALSE(bt_osal_event_is_queued(&ev2));
TEST_ASSERT_EQUAL_PTR(&ev1, bt_osal_eventq_get(&evq, 0));
TEST_ASSERT_EQUAL_PTR(&ev3, bt_osal_eventq_get(&evq, 0));
TEST_ASSERT_TRUE(bt_osal_eventq_is_empty(&evq));
/* The removed event's token was reclaimed, so a get() must block and time out. */
TEST_ASSERT_NULL(bt_osal_eventq_get(&evq, pdMS_TO_TICKS(TEST_NO_EVENT_MS)));
/* Remove the head. */
bt_osal_eventq_put(&evq, &ev1);
bt_osal_eventq_put(&evq, &ev2);
bt_osal_eventq_remove(&evq, &ev1);
TEST_ASSERT_EQUAL_PTR(&ev2, bt_osal_eventq_get(&evq, 0));
TEST_ASSERT_NULL(bt_osal_eventq_get(&evq, pdMS_TO_TICKS(TEST_NO_EVENT_MS)));
/* Remove the tail. */
bt_osal_eventq_put(&evq, &ev1);
bt_osal_eventq_put(&evq, &ev2);
bt_osal_eventq_remove(&evq, &ev2);
TEST_ASSERT_EQUAL_PTR(&ev1, bt_osal_eventq_get(&evq, 0));
TEST_ASSERT_NULL(bt_osal_eventq_get(&evq, pdMS_TO_TICKS(TEST_NO_EVENT_MS)));
/* Removing an event that is not queued is a no-op. */
bt_osal_eventq_remove(&evq, &ev3);
TEST_ASSERT_TRUE(bt_osal_eventq_is_empty(&evq));
/* The queue is reusable after all that shuffling. */
bt_osal_eventq_put(&evq, &ev3);
TEST_ASSERT_EQUAL_PTR(&ev3, bt_osal_eventq_get(&evq, 0));
bt_osal_event_deinit(&ev1);
bt_osal_event_deinit(&ev2);
bt_osal_event_deinit(&ev3);
bt_osal_eventq_deinit(&evq);
osal_test_end();
}
TEST_CASE("osal eventq worker task dispatches queued events", "[osal]")
{
struct bt_osal_eventq evq = {0};
struct bt_osal_eventq not_inited = {0};
struct bt_osal_event ev = {0};
osal_test_begin();
/* A queue that was never initialized has nothing to service. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_EINVAL,
bt_osal_eventq_start(&not_inited, &s_test_task_info));
bt_osal_eventq_init(&evq);
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK,
bt_osal_eventq_start(&evq, &s_test_task_info));
/* Only one task may service a queue. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_EINVAL,
bt_osal_eventq_start(&evq, &s_test_task_info));
bt_osal_event_init(&ev, ev_cb_count, (void *)0xC0FFEE);
bt_osal_eventq_put(&evq, &ev);
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
TEST_ASSERT_EQUAL_PTR(&ev, s_last_ev);
TEST_ASSERT_EQUAL_PTR((void *)0xC0FFEE, s_last_arg);
/* The worker cleared the queued flag before running the handler. */
TEST_ASSERT_FALSE(bt_osal_event_is_queued(&ev));
bt_osal_event_deinit(&ev);
/* deinit() stops the worker task before releasing the queue it waits on. */
bt_osal_eventq_deinit(&evq);
osal_test_end();
}
TEST_CASE("osal eventq worker honours the requested core affinity", "[osal]")
{
struct bt_osal_eventq evq = {0};
struct bt_osal_event ev = {0};
struct bt_osal_task_info info = s_test_task_info;
osal_test_begin();
bt_osal_eventq_init(&evq);
/* A core that does not exist is rejected instead of asserting inside FreeRTOS. */
info.core_id = portNUM_PROCESSORS;
TEST_ASSERT_EQUAL_INT(BT_OSAL_EINVAL, bt_osal_eventq_start(&evq, &info));
info.core_id = -2;
TEST_ASSERT_EQUAL_INT(BT_OSAL_EINVAL, bt_osal_eventq_start(&evq, &info));
/* Pinned to CPU0, the worker must dispatch from CPU0. */
info.core_id = 0;
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_eventq_start(&evq, &info));
s_last_core = -1;
bt_osal_event_init(&ev, ev_cb_core, NULL);
bt_osal_eventq_put(&evq, &ev);
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_INT(0, s_last_core);
bt_osal_event_deinit(&ev);
bt_osal_eventq_deinit(&evq);
osal_test_end();
}
TEST_CASE("osal eventq_post_func runs a one-shot handler", "[osal]")
{
struct bt_osal_eventq evq = {0};
osal_test_begin();
bt_osal_eventq_init(&evq);
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK,
bt_osal_eventq_start(&evq, &s_test_task_info));
TEST_ASSERT_EQUAL_INT(BT_OSAL_EINVAL, bt_osal_eventq_post_func(&evq, NULL, NULL));
TEST_ASSERT_EQUAL_INT(BT_OSAL_EINVAL, bt_osal_eventq_post_func(NULL, ev_cb_count, NULL));
/* The event is allocated by the layer and freed after the handler returns;
* a leak would be caught by the heap check in tearDown().
*/
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK,
bt_osal_eventq_post_func(&evq, ev_cb_count, NULL));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK,
bt_osal_eventq_post_func(&evq, ev_cb_count, (void *)0xABCD));
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_UINT32(2, s_run_count);
TEST_ASSERT_EQUAL_PTR((void *)0xABCD, s_last_arg);
bt_osal_eventq_deinit(&evq);
osal_test_end();
}
TEST_CASE("osal eventq_deinit reclaims still-queued post_func items", "[osal]")
{
struct bt_osal_eventq evq = {0};
osal_test_begin();
bt_osal_eventq_init(&evq);
/* Post one-shot items but never start a worker, so they stay queued and are
* never dispatched. Their backing storage is owned by the layer, so
* eventq_deinit() must reclaim it; otherwise the heap check in tearDown()
* would report a leak.
*/
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_eventq_post_func(&evq, ev_cb_count, NULL));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_eventq_post_func(&evq, ev_cb_count, (void *)0x1));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_eventq_post_func(&evq, ev_cb_count, (void *)0x2));
TEST_ASSERT_FALSE(bt_osal_eventq_is_empty(&evq));
bt_osal_eventq_deinit(&evq);
TEST_ASSERT_NULL(evq.eventq);
/* The items were reclaimed on teardown, not dispatched. */
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
osal_test_end();
}
/* Producer for the concurrency test: posts every event of the array to the queue. */
static void eventq_producer_task(void *arg)
{
struct bt_osal_eventq *evq = (struct bt_osal_eventq *)arg;
static struct bt_osal_event evs[8];
for (int i = 0; i < 8; i++) {
evs[i] = (struct bt_osal_event){0};
bt_osal_event_init(&evs[i], ev_cb_count, (void *)(uintptr_t)i);
bt_osal_eventq_put(evq, &evs[i]);
}
/* Hand the events back to the test task so it can release them. */
s_last_arg = evs;
xSemaphoreGive(s_done);
vTaskDelete(NULL);
}
TEST_CASE("osal eventq delivers every event under a concurrent producer", "[osal]")
{
struct bt_osal_eventq evq = {0};
struct bt_osal_event *evs;
bool seen[8] = {false};
osal_test_begin();
bt_osal_eventq_init(&evq);
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(eventq_producer_task, "osal_prod", TEST_HELPER_STACK,
&evq, uxTaskPriorityGet(NULL), NULL));
/* Drain the queue: each event must come out exactly once. */
for (int i = 0; i < 8; i++) {
struct bt_osal_event *ev = bt_osal_eventq_get(&evq, pdMS_TO_TICKS(TEST_WAIT_MS));
TEST_ASSERT_NOT_NULL(ev);
uintptr_t idx = (uintptr_t)bt_osal_event_get_arg(ev);
TEST_ASSERT_LESS_THAN_UINT32(8, idx);
TEST_ASSERT_FALSE(seen[idx]);
seen[idx] = true;
}
TEST_ASSERT_TRUE(bt_osal_eventq_is_empty(&evq));
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
evs = (struct bt_osal_event *)s_last_arg;
for (int i = 0; i < 8; i++) {
bt_osal_event_deinit(&evs[i]);
}
bt_osal_eventq_deinit(&evq);
osal_test_end();
}
/* Holds the mutex until the test task tells it to let go. */
static SemaphoreHandle_t s_holder_ready;
static void mutex_holder_task(void *arg)
{
struct bt_osal_mutex *mu = (struct bt_osal_mutex *)arg;
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_pend(mu, BT_OSAL_TIME_FOREVER));
xSemaphoreGive(s_holder_ready);
/* Wait for the test task to finish its contended pend, then hand the mutex back. */
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_release(mu));
xSemaphoreGive(s_done);
vTaskDelete(NULL);
}
TEST_CASE("osal mutex is recursive and pend times out while held", "[osal]")
{
struct bt_osal_mutex mu = {0};
struct bt_osal_mutex not_inited = {0};
osal_test_begin();
/* Operations on a mutex that was never initialized are rejected. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_INVALID_PARAM, bt_osal_mutex_pend(&not_inited, 0));
TEST_ASSERT_EQUAL_INT(BT_OSAL_INVALID_PARAM, bt_osal_mutex_release(&not_inited));
TEST_ASSERT_EQUAL_INT(BT_OSAL_INVALID_PARAM, bt_osal_mutex_deinit(&not_inited));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_init(&mu));
TEST_ASSERT_NOT_NULL(mu.mutex);
/* The owner may take it again without deadlocking. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_pend(&mu, 0));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_pend(&mu, 0));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_release(&mu));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_release(&mu));
s_holder_ready = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(s_holder_ready);
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(mutex_holder_task, "osal_hold", TEST_HELPER_STACK,
&mu, uxTaskPriorityGet(NULL), NULL));
/* Another task owns the mutex now: a bounded pend must give up. */
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_holder_ready, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_INT(BT_OSAL_TIMEOUT, bt_osal_mutex_pend(&mu, pdMS_TO_TICKS(TEST_NO_EVENT_MS)));
/* Once the holder releases it, the same pend succeeds. */
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_pend(&mu, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_release(&mu));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_mutex_deinit(&mu));
TEST_ASSERT_NULL(mu.mutex);
vSemaphoreDelete(s_holder_ready);
s_holder_ready = NULL;
osal_test_end();
}
static void sem_releaser_task(void *arg)
{
struct bt_osal_sem *sem = (struct bt_osal_sem *)arg;
vTaskDelay(pdMS_TO_TICKS(50));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_release(sem));
vTaskDelete(NULL);
}
TEST_CASE("osal semaphore counts tokens and blocks until released", "[osal]")
{
struct bt_osal_sem sem = {0};
struct bt_osal_sem not_inited = {0};
osal_test_begin();
TEST_ASSERT_EQUAL_INT(BT_OSAL_INVALID_PARAM, bt_osal_sem_pend(&not_inited, 0));
TEST_ASSERT_EQUAL_INT(BT_OSAL_INVALID_PARAM, bt_osal_sem_release(&not_inited));
TEST_ASSERT_EQUAL_INT(BT_OSAL_INVALID_PARAM, bt_osal_sem_deinit(&not_inited));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_init(&sem, 2));
TEST_ASSERT_NOT_NULL(sem.sem);
TEST_ASSERT_EQUAL_UINT16(2, bt_osal_sem_get_count(&sem));
/* Drain the initial tokens. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_pend(&sem, 0));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_pend(&sem, 0));
TEST_ASSERT_EQUAL_UINT16(0, bt_osal_sem_get_count(&sem));
/* No token left: a bounded pend gives up. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_TIMEOUT, bt_osal_sem_pend(&sem, pdMS_TO_TICKS(TEST_NO_EVENT_MS)));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_release(&sem));
TEST_ASSERT_EQUAL_UINT16(1, bt_osal_sem_get_count(&sem));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_pend(&sem, 0));
/* A release from another task wakes a blocked pend. */
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(sem_releaser_task, "osal_sem", TEST_HELPER_STACK,
&sem, uxTaskPriorityGet(NULL), NULL));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_pend(&sem, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_UINT16(0, bt_osal_sem_get_count(&sem));
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_sem_deinit(&sem));
TEST_ASSERT_NULL(sem.sem);
osal_test_end();
}
TEST_CASE("osal callout posts its event to the event queue on expiry", "[osal]")
{
struct bt_osal_eventq evq = {0};
struct bt_osal_callout co = {0};
struct bt_osal_event *ev;
bt_osal_time_t ticks;
bt_osal_time_t remaining;
osal_test_begin();
bt_osal_eventq_init(&evq);
TEST_ASSERT_EQUAL_INT(0, bt_osal_callout_init(&co, &evq, ev_cb_count, (void *)0x99));
TEST_ASSERT_NOT_NULL(co.co);
TEST_ASSERT_FALSE(bt_osal_callout_is_active(&co));
ticks = bt_osal_time_ms_to_ticks32(200);
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_callout_reset(&co, ticks));
/* The FreeRTOS timer daemon arms the timer asynchronously, so let it run
* before inspecting the callout's state.
*/
vTaskDelay(pdMS_TO_TICKS(20));
TEST_ASSERT_TRUE(bt_osal_callout_is_active(&co));
/* An armed callout expires in the future and has time left to run. */
TEST_ASSERT_GREATER_THAN_UINT32(bt_osal_time_get(), bt_osal_callout_get_ticks(&co));
remaining = bt_osal_callout_remaining_ticks(&co, bt_osal_time_get());
TEST_ASSERT_GREATER_THAN_UINT32(0, remaining);
TEST_ASSERT_LESS_OR_EQUAL_UINT32(ticks, remaining);
/* Nothing is delivered before the callout expires. */
TEST_ASSERT_NULL(bt_osal_eventq_get(&evq, pdMS_TO_TICKS(TEST_NO_EVENT_MS)));
/* On expiry the callout's event lands in the queue, ready to be run. */
ev = bt_osal_eventq_get(&evq, pdMS_TO_TICKS(TEST_WAIT_MS));
TEST_ASSERT_NOT_NULL(ev);
bt_osal_event_run(ev);
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
TEST_ASSERT_EQUAL_PTR((void *)0x99, s_last_arg);
/* A callout is one-shot. */
TEST_ASSERT_FALSE(bt_osal_callout_is_active(&co));
/* Stopping an armed callout keeps its event out of the queue. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_callout_reset(&co, bt_osal_time_ms_to_ticks32(100)));
bt_osal_callout_stop(&co);
vTaskDelay(pdMS_TO_TICKS(20));
TEST_ASSERT_FALSE(bt_osal_callout_is_active(&co));
TEST_ASSERT_NULL(bt_osal_eventq_get(&evq, pdMS_TO_TICKS(200)));
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
bt_osal_callout_deinit(&co);
TEST_ASSERT_NULL(co.co);
/* Deinit of an already released callout is a no-op. */
bt_osal_callout_deinit(&co);
bt_osal_eventq_deinit(&evq);
osal_test_end();
}
TEST_CASE("osal callout without an event queue runs its handler inline", "[osal]")
{
struct bt_osal_callout co = {0};
osal_test_begin();
/* With no event queue the handler is called straight from timer context. */
TEST_ASSERT_EQUAL_INT(0, bt_osal_callout_init(&co, NULL, ev_cb_count, (void *)0x77));
bt_osal_callout_set_arg(&co, (void *)0x88);
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_callout_reset(&co, bt_osal_time_ms_to_ticks32(50)));
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
TEST_ASSERT_EQUAL_PTR((void *)0x88, s_last_arg);
/* The callout can be re-armed after firing. */
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_callout_reset(&co, bt_osal_time_ms_to_ticks32(50)));
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_UINT32(2, s_run_count);
bt_osal_callout_deinit(&co);
osal_test_end();
}
TEST_CASE("osal time helpers round-trip between ms and ticks", "[osal]")
{
bt_osal_time_t ticks = 0;
uint32_t ms = 0;
uint32_t elapsed;
uint32_t start;
osal_test_begin();
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_time_ms_to_ticks(1000, &ticks));
TEST_ASSERT_GREATER_THAN_UINT32(0, ticks);
TEST_ASSERT_EQUAL_INT(BT_OSAL_OK, bt_osal_time_ticks_to_ms(ticks, &ms));
TEST_ASSERT_EQUAL_UINT32(1000, ms);
/* The 32-bit variants must agree with the checked ones. */
TEST_ASSERT_EQUAL_UINT32(ticks, bt_osal_time_ms_to_ticks32(1000));
TEST_ASSERT_EQUAL_UINT32(1000, bt_osal_time_ticks_to_ms32(ticks));
#if !BT_OSAL_USE_ESP_TIMER && (configTICK_RATE_HZ < 1000)
/* One tick is more than one millisecond here, so a full-range tick count
* cannot be expressed in milliseconds.
*/
TEST_ASSERT_EQUAL_INT(BT_OSAL_EINVAL, bt_osal_time_ticks_to_ms(UINT32_MAX, &ms));
#endif
/* The clock advances by at least the requested delay. */
start = bt_osal_time_get();
bt_osal_time_delay(bt_osal_time_ms_to_ticks32(50));
elapsed = bt_osal_time_get() - start;
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(bt_osal_time_ms_to_ticks32(40), elapsed);
osal_test_end();
}
TEST_CASE("osal reports OS state and nests critical sections", "[osal]")
{
uint32_t outer;
uint32_t inner;
bool in_outer;
bool in_inner;
bool after_inner;
bool after_outer;
osal_test_begin();
TEST_ASSERT_TRUE(bt_osal_os_started());
TEST_ASSERT_EQUAL_PTR(xTaskGetCurrentTaskHandle(), bt_osal_get_current_task_id());
TEST_ASSERT_EQUAL_UINT32(portMAX_DELAY, BT_OSAL_TIME_FOREVER);
TEST_ASSERT_FALSE(bt_osal_hw_is_in_critical());
/* Sample the state inside the critical section but assert outside it: a failing
* assertion longjmps out of the test and would leave the spinlock held.
*/
outer = bt_osal_hw_enter_critical();
in_outer = bt_osal_hw_is_in_critical();
inner = bt_osal_hw_enter_critical();
in_inner = bt_osal_hw_is_in_critical();
bt_osal_hw_exit_critical(inner);
after_inner = bt_osal_hw_is_in_critical();
bt_osal_hw_exit_critical(outer);
after_outer = bt_osal_hw_is_in_critical();
TEST_ASSERT_TRUE(in_outer);
TEST_ASSERT_TRUE(in_inner);
/* Leaving the inner section must not clear the outer one. */
TEST_ASSERT_TRUE(after_inner);
TEST_ASSERT_FALSE(after_outer);
osal_test_end();
}

View File

@@ -0,0 +1,172 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* Unit tests for the shared BLE profile task (components/bt/ble_profiles/common):
* lifecycle, the running-state guard on the shared queue, and the caller-owned
* (zero-allocation) event path.
*
* Each test brings the OSAL function table up (bt_prf_task_init() dispatches
* through it) and tears it down again, so the per-test heap check in tearDown()
* also covers the profile task's own allocations.
*/
#include <stdbool.h>
#include <stdint.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "bt_osal.h"
#include "bt_osal_freertos.h"
#include "bt_prf_task.h"
/* Long enough for the worker to run the handler, short enough to stay quick. */
#define TEST_WAIT_MS 1000
/* Waited on when a handler is expected NOT to run. */
#define TEST_NO_RUN_MS 50
/* Shared state between the test task and the posted handlers. */
static SemaphoreHandle_t s_done;
static volatile uint32_t s_run_count;
static void *s_last_arg;
static TaskHandle_t s_run_task;
static void prf_test_begin(void)
{
s_run_count = 0;
s_last_arg = NULL;
s_run_task = NULL;
s_done = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(s_done);
bt_osal_freertos_funcs_init();
TEST_ASSERT_NOT_NULL(bt_osal_freertos_funcs_get());
}
static void prf_test_end(void)
{
/* Safe even if a test already deinitialized it. */
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_deinit());
bt_osal_freertos_funcs_deinit();
TEST_ASSERT_NULL(bt_osal_freertos_funcs_get());
vSemaphoreDelete(s_done);
s_done = NULL;
/* The worker task is deleted by bt_prf_task_deinit(); its TCB/stack are
* reclaimed asynchronously by the idle task, so give it a moment before the
* heap check in tearDown() runs. */
vTaskDelay(pdMS_TO_TICKS(20));
}
/* Fire-and-forget handler: records the argument and the task it ran on. */
static void handler_record(struct bt_osal_event *ev)
{
s_run_count++;
s_last_arg = bt_osal_event_get_arg(ev);
s_run_task = xTaskGetCurrentTaskHandle();
xSemaphoreGive(s_done);
}
TEST_CASE("prf_task init/deinit lifecycle", "[prf_task]")
{
prf_test_begin();
TEST_ASSERT_FALSE(bt_prf_task_is_running());
TEST_ASSERT_NULL(bt_prf_task_eventq());
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_init());
TEST_ASSERT_TRUE(bt_prf_task_is_running());
TEST_ASSERT_NOT_NULL(bt_prf_task_eventq());
/* Second init while already running is an idempotent no-op. */
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_init());
TEST_ASSERT_TRUE(bt_prf_task_is_running());
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_deinit());
TEST_ASSERT_FALSE(bt_prf_task_is_running());
TEST_ASSERT_NULL(bt_prf_task_eventq());
/* Deinit when not running is a no-op. */
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_deinit());
prf_test_end();
}
TEST_CASE("prf_task eventq post runs handler on the worker task", "[prf_task]")
{
struct bt_osal_event ev = {0};
struct bt_osal_eventq *evq;
prf_test_begin();
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_init());
evq = bt_prf_task_eventq();
TEST_ASSERT_NOT_NULL(evq);
/* Post a caller-owned event straight onto the shared queue. */
bt_osal_event_init(&ev, handler_record, (void *)0xABCD);
bt_osal_eventq_put(evq, &ev);
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
TEST_ASSERT_EQUAL_PTR((void *)0xABCD, s_last_arg);
/* The handler must run on the worker task, not the caller's context. */
TEST_ASSERT_NOT_NULL(s_run_task);
TEST_ASSERT_NOT_EQUAL(xTaskGetCurrentTaskHandle(), s_run_task);
bt_osal_event_deinit(&ev);
prf_test_end();
}
TEST_CASE("prf_task eventq is available only while running", "[prf_task]")
{
prf_test_begin();
/* No queue to post to before the task is running. */
TEST_ASSERT_NULL(bt_prf_task_eventq());
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_init());
TEST_ASSERT_NOT_NULL(bt_prf_task_eventq());
/* The queue is gone again after teardown. */
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_deinit());
TEST_ASSERT_NULL(bt_prf_task_eventq());
prf_test_end();
}
TEST_CASE("prf_task drives a caller-owned event (zero allocation)", "[prf_task]")
{
struct bt_osal_event ev = {0};
struct bt_osal_eventq *evq;
prf_test_begin();
TEST_ASSERT_EQUAL(ESP_OK, bt_prf_task_init());
evq = bt_prf_task_eventq();
TEST_ASSERT_NOT_NULL(evq);
/* Post a caller-owned event straight onto the shared queue. */
bt_osal_event_init(&ev, handler_record, (void *)0x1234);
bt_osal_eventq_put(evq, &ev);
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
TEST_ASSERT_EQUAL_PTR((void *)0x1234, s_last_arg);
bt_osal_event_deinit(&ev);
prf_test_end();
}

View File

@@ -0,0 +1,161 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* Unit tests for TinyCrypt ECC (P-256 / P-192) APIs declared in tinycrypt/ecc.h.
* Procedure mirrors TEST_CASE("ble_smp_public_key_check", "[ble_smp][native_crypto]") in test_smp.c.
*/
#include <stdint.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_random.h"
#include "tinycrypt/ecc.h"
#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) && CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT
static void tinycrypt_fill_private_p256(uint8_t *priv)
{
for (int i = 0; i < NUM_ECC_BYTES; i++) {
priv[i] = (uint8_t)(0x10 + (i & 0x0F));
}
}
static void tinycrypt_bt_rand_p256(uint8_t *priv)
{
memset(priv, 0x55, NUM_ECC_BYTES);
for (int i = 0; i < NUM_ECC_BYTES / (int)sizeof(uint32_t); i++) {
uint32_t r = esp_random();
memcpy(priv + i * sizeof(uint32_t), &r, sizeof(uint32_t));
}
}
#if uECC_SUPPORTS_secp192r1
static void tinycrypt_fill_private_p192(uint8_t *priv)
{
for (int i = 0; i < NUM_ECC_BYTES_SECP192R1; i++) {
priv[i] = (uint8_t)(0x10 + (i & 0x0F));
}
}
static void tinycrypt_bt_rand_p192(uint8_t *priv)
{
memset(priv, 0x55, NUM_ECC_BYTES_SECP192R1);
for (int i = 0; i < NUM_ECC_BYTES_SECP192R1 / (int)sizeof(uint32_t); i++) {
uint32_t r = esp_random();
memcpy(priv + i * sizeof(uint32_t), &r, sizeof(uint32_t));
}
}
#endif // uECC_SUPPORTS_secp192r1
/**
* @brief P-256: public key from private, validation, generator on curve, tamper, random keys.
*
* Same flow as ble_smp_public_key_check (native): fixed private → public → valid;
* zero Y → invalid; generator point check; random private loop.
*/
TEST_CASE("tinycrypt_ecc_p256_public_key_check", "[ble_smp][tinycrypt_ecc]")
{
vTaskDelay(200 / portTICK_PERIOD_MS);
uECC_Curve curve = uECC_secp256r1();
uint8_t private_key[NUM_ECC_BYTES];
uint8_t public_key[NUM_ECC_BYTES * 2];
tinycrypt_fill_private_p256(private_key);
TEST_ASSERT_EQUAL_INT(1, uECC_compute_public_key(private_key, public_key, curve));
TEST_ASSERT_EQUAL_INT(0, uECC_valid_public_key(public_key, curve));
for (int i = 0; i < NUM_ECC_BYTES; i++) {
public_key[NUM_ECC_BYTES + i] = 0;
}
TEST_ASSERT_NOT_EQUAL(0, uECC_valid_public_key(public_key, curve));
uint8_t g_pub[NUM_ECC_BYTES * 2];
uECC_vli_nativeToBytes(g_pub, NUM_ECC_BYTES, curve->G);
uECC_vli_nativeToBytes(g_pub + NUM_ECC_BYTES, NUM_ECC_BYTES, curve->G + NUM_ECC_WORDS);
TEST_ASSERT_EQUAL_INT(-4, uECC_valid_public_key(g_pub, curve));
for (int j = 0; j < 100; j++) {
tinycrypt_bt_rand_p256(private_key);
if (uECC_compute_public_key(private_key, public_key, curve) != 1) {
continue;
}
TEST_ASSERT_EQUAL_INT(0, uECC_valid_public_key(public_key, curve));
}
}
TEST_CASE("tinycrypt_ecc_p256_vli_mmod_fast", "[ble_smp][tinycrypt_ecc]")
{
vTaskDelay(200 / portTICK_PERIOD_MS);
uECC_Curve curve = uECC_secp256r1();
unsigned int prod[NUM_ECC_WORDS * 2];
unsigned int res[NUM_ECC_WORDS];
memset(prod, 0, sizeof(prod));
uECC_vli_set(prod, curve->p, NUM_ECC_WORDS);
vli_mmod_fast_secp256r1(res, prod);
TEST_ASSERT(uECC_vli_isZero(res, NUM_ECC_WORDS));
}
#if uECC_SUPPORTS_secp192r1
/**
* @brief P-192: same procedure and uECC_valid_public_key expectations as P-256 (valid key 0, G is -4).
*/
TEST_CASE("tinycrypt_ecc_p192_public_key_check", "[ble_smp][tinycrypt_ecc]")
{
vTaskDelay(200 / portTICK_PERIOD_MS);
uECC_Curve curve = uECC_secp192r1();
uint8_t private_key[NUM_ECC_BYTES_SECP192R1];
uint8_t public_key[NUM_ECC_BYTES_SECP192R1 * 2];
tinycrypt_fill_private_p192(private_key);
TEST_ASSERT_EQUAL_INT(1, uECC_compute_public_key(private_key, public_key, curve));
TEST_ASSERT_EQUAL_INT(0, uECC_valid_public_key(public_key, curve));
for (int i = 0; i < NUM_ECC_BYTES_SECP192R1; i++) {
public_key[NUM_ECC_BYTES_SECP192R1 + i] = 0;
}
TEST_ASSERT_NOT_EQUAL(0, uECC_valid_public_key(public_key, curve));
uint8_t g_pub[NUM_ECC_BYTES_SECP192R1 * 2];
uECC_vli_nativeToBytes(g_pub, NUM_ECC_BYTES_SECP192R1, curve->G);
uECC_vli_nativeToBytes(g_pub + NUM_ECC_BYTES_SECP192R1, NUM_ECC_BYTES_SECP192R1,
curve->G + NUM_ECC_WORDS_SECP192R1);
TEST_ASSERT_EQUAL_INT(-4, uECC_valid_public_key(g_pub, curve));
for (int j = 0; j < 100; j++) {
tinycrypt_bt_rand_p192(private_key);
if (uECC_compute_public_key(private_key, public_key, curve) != 1) {
continue;
}
TEST_ASSERT_EQUAL_INT(0, uECC_valid_public_key(public_key, curve));
}
}
TEST_CASE("tinycrypt_ecc_p192_vli_mmod_fast", "[ble_smp][tinycrypt_ecc]")
{
vTaskDelay(200 / portTICK_PERIOD_MS);
uECC_Curve curve = uECC_secp192r1();
unsigned int prod[NUM_ECC_WORDS_SECP192R1 * 2];
unsigned int res[NUM_ECC_WORDS_SECP192R1];
memset(prod, 0, sizeof(prod));
uECC_vli_set(prod, curve->p, NUM_ECC_WORDS_SECP192R1);
vli_mmod_fast_secp192r1(res, prod);
TEST_ASSERT(uECC_vli_isZero(res, NUM_ECC_WORDS_SECP192R1));
}
#endif /* #if uECC_SUPPORTS_secp192r1 */
#endif /* CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT */

View File

@@ -1,2 +1,3 @@
CONFIG_BT_ENABLED=y
CONFIG_BT_PRF_TASK_ENABLED=y
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n

View File

@@ -159,17 +159,17 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
adv_config_done |= scan_rsp_config_flag;
break;
case ESP_GATTS_READ_EVT:
if (g_ble_cfg_p) {
if (g_ble_cfg_p && g_ble_cfg_p->read_fn) {
g_ble_cfg_p->read_fn(event, gatts_if, param);
}
break;
case ESP_GATTS_WRITE_EVT:
if (g_ble_cfg_p) {
if (g_ble_cfg_p && g_ble_cfg_p->write_fn) {
g_ble_cfg_p->write_fn(event, gatts_if, param);
}
break;
case ESP_GATTS_EXEC_WRITE_EVT:
if (g_ble_cfg_p) {
if (g_ble_cfg_p && g_ble_cfg_p->exec_write_fn) {
g_ble_cfg_p->exec_write_fn(event, gatts_if, param);
}
break;
@@ -187,7 +187,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGD(TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
if (g_ble_cfg_p) {
if (g_ble_cfg_p && g_ble_cfg_p->connect_fn) {
g_ble_cfg_p->connect_fn(event, gatts_if, param);
}
esp_ble_conn_update_params_t conn_params = {0};
@@ -202,7 +202,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGD(TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason);
if (g_ble_cfg_p) {
if (g_ble_cfg_p && g_ble_cfg_p->disconnect_fn) {
g_ble_cfg_p->disconnect_fn(event, gatts_if, param);
}
memset(s_cached_remote_bda, 0, sizeof(esp_bd_addr_t));
@@ -263,7 +263,6 @@ esp_err_t simple_ble_deinit(void)
simple_ble_cfg_t *ble_cfg = g_ble_cfg_p;
g_ble_cfg_p = NULL;
if (ble_cfg) {
free(ble_cfg->gatt_db);
ble_cfg->gatt_db = NULL;
free(ble_cfg);
}

View File

@@ -74,6 +74,7 @@ typedef struct _protocomm_ble {
} _protocomm_ble_internal_t;
static _protocomm_ble_internal_t *protoble_internal;
static esp_gatts_attr_db_t *s_gatt_db;
static bool protocomm_ble_transport_active(void)
{
@@ -715,6 +716,8 @@ static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
static void protocomm_ble_cleanup(void)
{
protocomm_ble_reset_prepare_write();
free(s_gatt_db);
s_gatt_db = NULL;
if (protoble_internal) {
if (protoble_internal->service_uuid) {
free(protoble_internal->service_uuid);
@@ -891,6 +894,7 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
ble_config->device_name = protocomm_ble_device_name;
ble_config->gatt_db_count = populate_gatt_db(&ble_config->gatt_db);
s_gatt_db = ble_config->gatt_db;
ble_config->ble_bonding = config->ble_bonding;
ble_config->ble_sm_sc = config->ble_sm_sc;
@@ -904,7 +908,9 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con
if (ble_config->gatt_db_count == -1) {
ESP_LOGE(TAG, "Invalid GATT database count");
free(ble_config->gatt_db);
free(s_gatt_db);
s_gatt_db = NULL;
ble_config->gatt_db = NULL;
free(ble_config);
protocomm_ble_cleanup();
return ESP_ERR_INVALID_STATE;

View File

@@ -18,6 +18,10 @@
#define BLE_PAWR_RSP_SLOT_SPACING (10) /*!< Time between response slots (N * 0.125 ms) */
#define BLE_PAWR_NUM_RSP_SLOTS (25) /*!< Number of subevent response slots */
#define BLE_PAWR_SUB_DATA_LEN (20)
/* Give the controller a few periodic intervals to report the outcome of a
* synchronized connection attempt before retrying from another subevent.
*/
#define BLE_PAWR_CONN_TIMEOUT_MS (3 * BLE_PAWR_EVENT_PERIODIC_INTERVAL_MS)
#define TAG "NimBLE_BLE_PAwR_CONN"
@@ -162,7 +166,7 @@ gap_event_cb(struct ble_gap_event *event, void *arg)
phy_mask = 0x01;
if (conn == 0) {
rc = ble_gap_connect_with_synced(own_addr_type,adv_handle,subevent,&peer_addr,30000,phy_mask,NULL,NULL,NULL,gap_event_cb,NULL);
rc = ble_gap_connect_with_synced(own_addr_type,adv_handle,subevent,&peer_addr,BLE_PAWR_CONN_TIMEOUT_MS,phy_mask,NULL,NULL,NULL,gap_event_cb,NULL);
if (rc != 0 ) {
ESP_LOGI(TAG,"Error: Failed to connect to device , rc = %d\n",rc);
} else {