Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/NimBLEDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ void NimBLEDevice::onSync(void) {
*/
void NimBLEDevice::host_task(void* param) {
NIMBLE_LOGI(LOG_TAG, "NimBLE Started!");
NimBLEUtils::m_hostTaskHandle = ble_npl_get_current_task_id();
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
nimble_port_freertos_deinit();
} // host_task
Expand Down
88 changes: 88 additions & 0 deletions src/NimBLEUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@

# ifdef USING_NIMBLE_ARDUINO_HEADERS
# include "nimble/nimble/host/include/host/ble_hs.h"
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# else
# include "host/ble_hs.h"
# include "nimble/nimble_port.h"
# endif

/**** FIX COMPILATION ****/
Expand All @@ -34,6 +36,8 @@

# include <stdlib.h>
# include <climits>
# include <memory>
# include <new>

# ifndef MYNEWT_VAL_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT
# ifdef CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT
Expand Down Expand Up @@ -64,6 +68,9 @@ static const char* LOG_TAG = "NimBLEUtils";
std::vector<NimBLEUtils::TaskSemEntry*> NimBLEUtils::m_taskSemEntries{};
std::atomic<int> NimBLEUtils::m_semUserCount{0};
std::atomic_flag NimBLEUtils::m_taskSemLock = ATOMIC_FLAG_INIT;
std::atomic<void*> NimBLEUtils::m_hostTaskHandle{nullptr};
std::atomic_flag NimBLEUtils::m_hostTaskLock = ATOMIC_FLAG_INIT;
NimBLEUtils::HostTaskContext* NimBLEUtils::m_pendingHostTasks{nullptr};

/**
* @brief Construct a NimBLEUtils::TaskData instance.
Expand Down Expand Up @@ -165,6 +172,8 @@ void NimBLEUtils::taskRelease(const TaskData& taskData, int flags) {
* @details This should be called when the NimBLE stack is deinitialized to clean up any resources used by waiting tasks.
*/
void NimBLEUtils::deinit() {
drainHostTasks();

// wait for any active users of the semaphore vector to finish
while (m_semUserCount.load() > 0) {
ble_npl_time_delay(1);
Expand All @@ -186,6 +195,85 @@ void NimBLEUtils::deinit() {
m_taskSemEntries.clear();
}

struct NimBLEUtils::HostTaskContext {
ble_npl_event ev;
std::function<void()> func;
HostTaskContext* next{nullptr};
};

/**
* @brief Run a callable on the NimBLE host task.
* @param [in] func The callable to run; capture any needed context by value (or shared_ptr).
* @return true if func was run or queued, false if it was dropped because the host task is not running.
* @details If called from the host task, func runs immediately. Otherwise it is moved into
* an owned context and queued, so it runs asynchronously later. Anything func borrows by
* reference or raw pointer must remain valid until it runs. func must not throw.
* Tasks still queued when the stack is deinitialized are discarded without running.
*/
bool NimBLEUtils::runOnHostTask(std::function<void()> func) {
if (!func) {
return false;
}

if (m_hostTaskHandle.load() == ble_npl_get_current_task_id()) {
func();
return true;
}

auto* ctx = new HostTaskContext{{}, std::move(func)};
ble_npl_event_init(&ctx->ev, executeHostTask, ctx);

// The handle check and queueing are under the lock so deinit() cannot drain between them.
SpinGuard guard(m_hostTaskLock);
if (m_hostTaskHandle.load() == nullptr) {
delete ctx;
return false;
}

ctx->next = m_pendingHostTasks;
m_pendingHostTasks = ctx;
ble_npl_eventq_put(nimble_port_get_dflt_eventq(), &ctx->ev);
return true;
}

void NimBLEUtils::executeHostTask(ble_npl_event* ev) {
auto* ctx = static_cast<HostTaskContext*>(ev->arg);
{
SpinGuard guard(m_hostTaskLock);
for (HostTaskContext** p = &m_pendingHostTasks; *p != nullptr; p = &(*p)->next) {
if (*p == ctx) {
*p = ctx->next;
break;
}
}
}

std::unique_ptr<HostTaskContext> owner{ctx};
ctx->func();
}

/**
* @brief Stops accepting host task work and discards any tasks that were queued but never ran.
*/
void NimBLEUtils::drainHostTasks() {
HostTaskContext* list = nullptr;
{
SpinGuard guard(m_hostTaskLock);
m_hostTaskHandle.store(nullptr);
list = m_pendingHostTasks;
m_pendingHostTasks = nullptr;
for (auto* c = list; c != nullptr; c = c->next) {
ble_npl_eventq_remove(nimble_port_get_dflt_eventq(), &c->ev);
}
}

while (list != nullptr) {
auto* next = list->next;
delete list;
list = next;
}
}

/**
* @brief Converts a return code from the NimBLE stack to a text string.
* @param [in] rc The return code to convert.
Expand Down
10 changes: 10 additions & 0 deletions src/NimBLEUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ void nimble_cpp_assert(const char* file, unsigned line) __attribute((weak, noret
# include <string>
# include <vector>
# include <atomic>
# include <functional>

class NimBLEAddress;
struct ble_npl_sem;
struct ble_npl_event;

/**
* @brief A BLE Utility class with methods for debugging and general purpose use.
Expand Down Expand Up @@ -94,13 +96,21 @@ class NimBLEUtils {
static NimBLEAddress generateAddr(bool nrpa);
static bool taskWait(const TaskData& taskData, uint32_t timeout);
static void taskRelease(const TaskData& taskData, int rc = 0);
static bool runOnHostTask(std::function<void()> func);

private:
friend class NimBLEDevice;
static void deinit();
static std::vector<TaskSemEntry*> m_taskSemEntries;
static std::atomic<int> m_semUserCount;
static std::atomic_flag m_taskSemLock;
static std::atomic<void*> m_hostTaskHandle;

struct HostTaskContext;
static std::atomic_flag m_hostTaskLock;
static HostTaskContext* m_pendingHostTasks;
static void executeHostTask(ble_npl_event* ev);
static void drainHostTasks();
};

#endif // CONFIG_BT_NIMBLE_ENABLED
Expand Down
Loading