Skip to content

Commit

Permalink
Bluetooth: Add "Connection event notification callback" feature
Browse files Browse the repository at this point in the history
The connection event callback feature replaces
the radio notification feature. It can be used to
synchronize data sampling with Bluetooth connection events.

The old feature had several weaknesses:
 - It was not possible to know which connection
   the notification belonged to.
 - The trigger also happened upon flash access.
 - The time from trigger until event start was fixed.
   That is, not configurable per use case.

The new feature is built on top of the connection event
trigger feature and is therefore able to known
which connection a given notification belongs to.

Documentation describing the feature in detail will be added
separately.

The feature is marked as experimental until it is verified.
Currently this feature will cancels out peripheral latency
which we indent to change.

Signed-off-by: Rubin Gerritsen <[email protected]>
  • Loading branch information
rugeGerritsen committed Jun 20, 2024
1 parent f2416b8 commit d46d6cf
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 0 deletions.
69 changes: 69 additions & 0 deletions include/bluetooth/conn_evt_prepare_cb.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/** @file
* @brief Connection Event Prepare callback APIs
*/

/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#ifndef BT_CONN_PREPARE_CB_H__
#define BT_CONN_PREPARE_CB_H__

#include <stdint.h>
#include <zephyr/bluetooth/conn.h>

/**
* @file
* @defgroup bt_conn_evt_prepare_cb Connection Event Prepare callback
* @{
* @brief APIs to setup a connection event prepare callback
*
* The connection event prepare callbacks are triggered right before
* the start of a connection event. This allows the application to
* reduce the total system latency as data can be sampled right before
* it is sent on air.
*/

#ifdef __cplusplus
extern "C" {
#endif

/** Connection event prepare callback
*
* The callback is called from ISR context.
* That is, the user needs to offload processing
* to a thread or workqueue thread before it calls Bluetooth APIs.
*
* @param[in] conn The connection context.
* @param[in] user_data User provided data.
*/
typedef void (*bt_conn_evt_prepare_cb_t)(const struct bt_conn *conn,
void *user_data);

/** Set the connect event prepare callback for a specified connection context.
*
* @param[in] conn The connection context.
* @param[in] cb The callback to be used.
* @param[in] prepare_distance_us The distance in time from the start of the
* callback to the start of the connection event.
*/
int bt_conn_evt_prepare_cb_set(const struct bt_conn *conn,
bt_conn_evt_prepare_cb_t cb,
void *user_data,
uint32_t prepare_distance_us);

/**
* @}
*/

#ifdef __cplusplus
}
#endif

/**
* @}
*/

#endif /* BT_CONN_PREPARE_CB_H__ */
1 change: 1 addition & 0 deletions subsys/bluetooth/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ config BT_LL_SOFTDEVICE_HEADERS_INCLUDE
if BT_CTLR
rsource "controller/Kconfig"
endif
rsource "host_extensions/Kconfig"

comment "BLE Libraries"
rsource "Kconfig.pool"
Expand Down
1 change: 1 addition & 0 deletions subsys/bluetooth/host_extensions/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
#

zephyr_sources(host_extensions.c)
zephyr_sources_ifdef(CONFIG_BT_CONN_EVT_PREPARE_CB conn_evt_prepare_cb.c)
70 changes: 70 additions & 0 deletions subsys/bluetooth/host_extensions/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#
# Copyright (c) 2024 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

config BT_CONN_EVT_PREPARE_CB
bool "Connection event prepare callback [EXPERIMENTAL]"
depends on BT_LL_SOFTDEVICE
select BT_CTLR_SDC_EVENT_TRIGGER
select NRFX_PPI if HAS_HW_NRF_PPI
select NRFX_DPPI if HAS_HW_NRF_DPPIC
select NRFX_EGU0
select EXPERIMENTAL
help
Enables the support for setting up a callback to
trigger a configurable amount of time before the
connection event starts.

choice BT_CONN_EVT_PREPARE_CB_EGU_INST
bool "Connection event prepare callback EGU instance"
help
The connection event prepare callback implementation uses
an EGU instance. This config selects which instance to be
used.

config BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU0
bool "Use EGU0"
depends on $(dt_nodelabel_has_compat,egu0,$(DT_COMPAT_NORDIC_NRF_EGU))
select NRFX_EGU0

config BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU1
bool "Use EGU1"
depends on $(dt_nodelabel_has_compat,egu1,$(DT_COMPAT_NORDIC_NRF_EGU))
select NRFX_EGU1

config BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU2
bool "Use EGU2"
depends on $(dt_nodelabel_has_compat,egu2,$(DT_COMPAT_NORDIC_NRF_EGU))
select NRFX_EGU2

config BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU3
bool "Use EGU3"
depends on $(dt_nodelabel_has_compat,egu3,$(DT_COMPAT_NORDIC_NRF_EGU))
select NRFX_EGU3

config BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU4
bool "Use EGU3"
depends on $(dt_nodelabel_has_compat,egu4,$(DT_COMPAT_NORDIC_NRF_EGU))
select NRFX_EGU4

config BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU5
bool "Use EGU5"
depends on $(dt_nodelabel_has_compat,egu5,$(DT_COMPAT_NORDIC_NRF_EGU))
select NRFX_EGU5

config BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU020
bool "Use EGU020"
depends on $(dt_nodelabel_has_compat,egu020,$(DT_COMPAT_NORDIC_NRF_EGU))
select NRFX_EGU020

endchoice

config BT_CONN_EVENT_NOTIFICATION_TRIGGER_EGU_ISR_PRIO
int
depends on BT_CONN_EVT_PREPARE_CB
default 2
help
The ISR priority of the connection event trigger
used to setup the timer for the prepare callback.
205 changes: 205 additions & 0 deletions subsys/bluetooth/host_extensions/conn_evt_prepare_cb.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/kernel.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci.h>
#include <bluetooth/conn_evt_prepare_cb.h>
#include <helpers/nrfx_gppi.h>
#include <bluetooth/hci_vs_sdc.h>

#include <nrfx_egu.h>

#if defined(CONFIG_BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU0)
#define EGU_INST_IDX 0
#elif defined(CONFIG_BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU1)
#define EGU_INST_IDX 1
#elif defined(CONFIG_BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU2)
#define EGU_INST_IDX 2
#elif defined(CONFIG_BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU3)
#define EGU_INST_IDX 3
#elif defined(CONFIG_BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU4)
#define EGU_INST_IDX 4
#elif defined(CONFIG_BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU5)
#define EGU_INST_IDX 5
#elif defined(CONFIG_BT_CONN_EVT_PREPARE_CB_EGU_INST_EGU020)
#define EGU_INST_IDX 020
#else
#error "Unknown EGU instance"
#endif

static nrfx_egu_t egu_inst = NRFX_EGU_INSTANCE(EGU_INST_IDX);

struct notification_cfg {
bt_conn_evt_prepare_cb_t cb;
const struct bt_conn *conn;
void *user_data;
uint32_t prepare_time_us;

uint8_t ppi_channel;
uint8_t egu_channel;
uint16_t conn_interval_us;
};

static struct k_timer timers[CONFIG_BT_MAX_CONN];
static struct notification_cfg cfg[CONFIG_BT_MAX_CONN];

static void on_timer_expired(struct k_timer *timer)
{
uint8_t index = ARRAY_INDEX(timers, timer);

cfg[index].cb(cfg[index].conn, cfg[index].user_data);
}

static void egu_event_handler(uint8_t event_idx, void *context)
{
ARG_UNUSED(context);

__ASSERT_NO_MSG(event_idx < ARRAY_SIZE(timers));
__ASSERT_NO_MSG(cfg[event_idx].conn_interval_us > cfg[event_idx].prepare_time_us);

const uint32_t timer_distance_us =
cfg[event_idx].conn_interval_us - cfg[event_idx].prepare_time_us;

k_timer_start(&timers[event_idx], K_USEC(timer_distance_us), K_NO_WAIT);
}

static void on_conn_param_updated(struct bt_conn *conn,
uint16_t interval,
uint16_t latency,
uint16_t timeout)
{
uint8_t index = bt_conn_index(conn);

if (!cfg[index].cb) {
return;
}

cfg[index].conn_interval_us = BT_CONN_INTERVAL_TO_US(interval);
}

static void on_disconnected(struct bt_conn *conn, uint8_t reason)
{
uint8_t index = bt_conn_index(conn);

if (!cfg[index].cb) {
return;
}

cfg[index].cb = NULL;
nrfx_egu_int_disable(&egu_inst, nrf_egu_channel_int_get(cfg[index].egu_channel));
k_timer_stop(&timers[index]);

nrfx_err_t err = nrfx_gppi_channel_free(cfg[index].ppi_channel);

__ASSERT_NO_MSG(err == NRFX_SUCCESS);
}

BT_CONN_CB_DEFINE(conn_params_updated_cb) = {
.le_param_updated = on_conn_param_updated,
.disconnected = on_disconnected,
};

static int setup_connection_event_trigger(struct notification_cfg *cfg)
{
int err;
uint16_t conn_handle;

err = bt_hci_get_conn_handle(cfg->conn, &conn_handle);
if (err) {
printk("Failed obtaining conn_handle (err %d)\n", err);
return err;
}

sdc_hci_cmd_vs_get_next_conn_event_counter_t get_next_conn_event_count_params = {
.conn_handle = conn_handle,
};
sdc_hci_cmd_vs_get_next_conn_event_counter_return_t get_next_conn_event_count_ret_params;

err = hci_vs_sdc_get_next_conn_event_counter(
&get_next_conn_event_count_params, &get_next_conn_event_count_ret_params);
if (err) {
printk("Failed obtaining next conn event count (err %d)\n", err);
return err;
}

const sdc_hci_cmd_vs_set_conn_event_trigger_t params = {
.conn_handle = conn_handle,
.role = SDC_HCI_VS_CONN_EVENT_TRIGGER_ROLE_CONN,
.ppi_ch_id = cfg->ppi_channel,
.period_in_events = 1, /* Trigger every connection event. */
.conn_evt_counter_start =
get_next_conn_event_count_ret_params.next_conn_event_counter + 10,
.task_endpoint = nrfx_egu_task_address_get(&egu_inst,
nrf_egu_trigger_task_get(cfg->egu_channel)),
};

return hci_vs_sdc_set_conn_event_trigger(&params);
}

int bt_conn_evt_prepare_cb_set(const struct bt_conn *conn,
bt_conn_evt_prepare_cb_t cb,
void *user_data,
uint32_t prepare_distance_us)
{
int err;
struct bt_conn_info info;
uint8_t index = bt_conn_index(conn);

if (cfg[index].cb) {
return -EALREADY;
}

if (nrfx_gppi_channel_alloc(&cfg[index].ppi_channel) != NRFX_SUCCESS) {
return -ENOMEM;
}

err = bt_conn_get_info(conn, &info);
if (err) {
return err;
}

cfg[index].cb = cb;
cfg[index].egu_channel = index;
cfg[index].conn_interval_us = BT_CONN_INTERVAL_TO_US(info.le.interval);
cfg[index].conn = conn;
cfg[index].prepare_time_us = prepare_distance_us;
cfg[index].user_data = user_data;
k_timer_init(&timers[index], on_timer_expired, NULL);

nrfx_egu_int_enable(&egu_inst, nrf_egu_channel_int_get(cfg[index].egu_channel));

setup_connection_event_trigger(&cfg[index]);

return 0;
}

static int driver_init(const struct device *dev)
{
nrfx_err_t err;

err = nrfx_egu_init(&egu_inst,
CONFIG_BT_CONN_EVENT_NOTIFICATION_TRIGGER_EGU_ISR_PRIO,
egu_event_handler,
NULL);
if (err != NRFX_SUCCESS) {
return -EALREADY;
}

IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_EGU_INST_GET(EGU_INST_IDX)),
CONFIG_BT_CONN_EVENT_NOTIFICATION_TRIGGER_EGU_ISR_PRIO,
NRFX_EGU_INST_HANDLER_GET(EGU_INST_IDX),
NULL, 0);
irq_enable(NRFX_IRQ_NUMBER_GET(NRF_EGU_INST_GET(EGU_INST_IDX)));

return 0;
}

DEVICE_DEFINE(conn_event_notification, "conn_event_notification",
driver_init, NULL,
NULL, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
NULL);

0 comments on commit d46d6cf

Please sign in to comment.