From 7dc31ca862c1bc306df716a17658188d9333018c Mon Sep 17 00:00:00 2001 From: Vinayak Kariappa Chettimada Date: Fri, 5 Sep 2025 06:41:16 +0200 Subject: [PATCH] Bluetooth: Controller: Introduce channel metrics events Introduce channel metrics events that report number of good reception on each ACL data channel in use. Signed-off-by: Vinayak Kariappa Chettimada --- .../bluetooth/controller/Kconfig.ll_sw_split | 14 ++ subsys/bluetooth/controller/hci/hci.c | 11 ++ subsys/bluetooth/controller/ll_sw/lll.h | 1 + subsys/bluetooth/controller/ll_sw/lll_chan.c | 150 ++++++++++++++++++ subsys/bluetooth/controller/ll_sw/lll_chan.h | 41 +++++ subsys/bluetooth/controller/ll_sw/lll_conn.h | 13 ++ .../controller/ll_sw/nordic/lll/lll.c | 1 - .../controller/ll_sw/nordic/lll/lll_central.c | 4 + .../controller/ll_sw/nordic/lll/lll_conn.c | 32 ++++ .../ll_sw/nordic/lll/lll_peripheral.c | 4 + subsys/bluetooth/controller/ll_sw/ull.c | 8 + subsys/bluetooth/controller/ll_sw/ull_adv.c | 4 + subsys/bluetooth/controller/ll_sw/ull_conn.c | 17 ++ 13 files changed, 299 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/controller/Kconfig.ll_sw_split b/subsys/bluetooth/controller/Kconfig.ll_sw_split index 950e9bc7e58b6..9b2607c77c9ec 100644 --- a/subsys/bluetooth/controller/Kconfig.ll_sw_split +++ b/subsys/bluetooth/controller/Kconfig.ll_sw_split @@ -1154,6 +1154,20 @@ config BT_CTLR_CONN_RSSI_EVENT help Generate events for connection RSSI measurement. +config BT_CTLR_CHAN_METRICS_EVENT + bool "Channel Metrics event" + help + Generate events for channel usage metrics. + +config BT_CTLR_CHAN_METRICS_BAD_COUNT + int "Channel bad count threshold" + depends on BT_CTLR_CHAN_METRICS_EVENT + range 1 $(UINT8_MAX) + default 3 + help + Set the number of consecutive failures on transmission acknowledgment that will generate + a channel metrics event. + config BT_CTLR_ALLOW_SAME_PEER_CONN bool "Allow connection requests from same peer" depends on BT_MAX_CONN > 1 diff --git a/subsys/bluetooth/controller/hci/hci.c b/subsys/bluetooth/controller/hci/hci.c index 683c4cadf919e..38cb481d25fe3 100644 --- a/subsys/bluetooth/controller/hci/hci.c +++ b/subsys/bluetooth/controller/hci/hci.c @@ -39,6 +39,7 @@ #include "ll_sw/pdu.h" #include "ll_sw/lll.h" +#include "ll_sw/lll_chan.h" #include "lll/lll_adv_types.h" #include "ll_sw/lll_adv.h" #include "lll/lll_adv_pdu.h" @@ -8902,6 +8903,12 @@ static void encode_control(struct node_rx_pdu *node_rx, return; #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */ +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + case NODE_RX_TYPE_CHAN_METRICS: + lll_chan_metrics_print(); + return; +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + #if defined(CONFIG_BT_CTLR_PERIPHERAL_ISO) case NODE_RX_TYPE_CIS_REQUEST: le_cis_request(pdu_data, node_rx, buf); @@ -9407,6 +9414,10 @@ uint8_t hci_get_class(struct node_rx_pdu *node_rx) case NODE_RX_TYPE_RSSI: #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */ +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + case NODE_RX_TYPE_CHAN_METRICS: +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + #if defined(CONFIG_BT_CTLR_LE_PING) case NODE_RX_TYPE_APTO: #endif /* CONFIG_BT_CTLR_LE_PING */ diff --git a/subsys/bluetooth/controller/ll_sw/lll.h b/subsys/bluetooth/controller/ll_sw/lll.h index f5d33c90d70cd..b26fa3cd91936 100644 --- a/subsys/bluetooth/controller/ll_sw/lll.h +++ b/subsys/bluetooth/controller/ll_sw/lll.h @@ -307,6 +307,7 @@ enum node_rx_type { NODE_RX_TYPE_CHAN_SEL_ALGO, NODE_RX_TYPE_PHY_UPDATE, NODE_RX_TYPE_RSSI, + NODE_RX_TYPE_CHAN_METRICS, NODE_RX_TYPE_PROFILE, NODE_RX_TYPE_ADV_INDICATION, NODE_RX_TYPE_SCAN_INDICATION, diff --git a/subsys/bluetooth/controller/ll_sw/lll_chan.c b/subsys/bluetooth/controller/ll_sw/lll_chan.c index 213fce38a8f5d..b552b716c48d4 100644 --- a/subsys/bluetooth/controller/ll_sw/lll_chan.c +++ b/subsys/bluetooth/controller/ll_sw/lll_chan.c @@ -6,6 +6,7 @@ #include #include +#include #include "hal/ccm.h" #include "hal/radio.h" @@ -331,6 +332,155 @@ static uint8_t chan_d(uint8_t n) } #endif /* CONFIG_BT_CTLR_ISO */ +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) +#include "hal/cpu.h" + +#define CHM_SURVEY_CHAN_COUNT_MAX 37U + +static struct { + struct chan_metrics_chan { + uint16_t count; /* Total metrics count */ + uint16_t prev; /* Previous total metrics count when good tx acknowledgment */ + uint16_t good; /* Total good tx acknowledgments */ + } chan[CHM_SURVEY_CHAN_COUNT_MAX]; + + /* Differential state, context-safe, to notify upper layer */ + uint8_t req; + uint8_t ack; +} chan_metrics; + +void lll_chan_metrics_init(void) +{ + (void)memset((void *)&chan_metrics, 0U, sizeof(chan_metrics)); +} + +bool lll_chan_metrics_is_notify(void) +{ + return chan_metrics.req != chan_metrics.ack; +} + +bool lll_chan_metrics_notify_clear(void) +{ + if (lll_chan_metrics_is_notify()) { + chan_metrics.ack = chan_metrics.req; + + return true; + } + + return false; +} + +static bool chan_metrics_notify_set(void) +{ + uint8_t req; + + req = chan_metrics.req + 1U; + if (req != chan_metrics.ack) { + chan_metrics.req = req; + cpu_dmb(); /* data memory barrier */ + + return true; + } + + return false; +} + +static struct chan_metrics_chan *chan_metrics_count_inc(uint8_t chan_idx) +{ + struct chan_metrics_chan *chan; + + LL_ASSERT(chan_idx < CHM_SURVEY_CHAN_COUNT_MAX); + + /* Mitigate metrics overflow, half the collected good and count value */ + chan = &chan_metrics.chan[chan_idx]; + if (chan->count == UINT16_MAX) { + uint16_t diff; + + /* Current consecutive bad versus good difference */ + diff = chan->count - chan->prev; + + /* Half the count and keep the current consecutive bad versus good difference. + * We do not want to rollover `UINT16_MAX`, hence we normalize the `count` and + * `good`, but keep the absolute `diff` between `total` and `prev`. + */ + chan->count >>= 1; + if (diff < chan->count) { + chan->prev = chan->count - diff; + } else { + chan->prev = 0U; + } + + /* Half the good count */ + chan->good >>= 1; + } + + /* Increment the channel metrics count */ + chan->count++; + + return chan; +} + +void lll_chan_metrics_chan_bad(uint8_t chan_idx) +{ + struct chan_metrics_chan *chan; + uint16_t diff; + + /* Increment the per channel metrics count */ + chan = chan_metrics_count_inc(chan_idx); + + /* Notify beyond a bad versus good difference */ + diff = chan->count - chan->prev; + if (diff > CONFIG_BT_CTLR_CHAN_METRICS_BAD_COUNT) { + (void)chan_metrics_notify_set(); + + /* Reset consecutive bad versus good difference */ + chan->prev = chan->count; + } +} + +void lll_chan_metrics_chan_good(uint8_t chan_idx) +{ + struct chan_metrics_chan *chan; + + /* Increment the per channel metrics count */ + chan = chan_metrics_count_inc(chan_idx); + + /* Reset the consecutive bad versus good difference */ + chan->prev = chan->count; + + /* Increment good count */ + chan->good++; +} + +void lll_chan_metrics_print(void) +{ + const uint8_t max = 100U; + + printk("%s:\n", __func__); + printk("chan #: (actual / expected reception) percentage -\n"); + for (uint8_t i = 0; i < CHM_SURVEY_CHAN_COUNT_MAX; i++) { + const struct chan_metrics_chan *chan; + uint8_t cnt; + char c; + + chan = &chan_metrics.chan[i]; + if (chan->count != 0U) { + cnt = chan->good * max / chan->count; + c = '*'; + } else { + cnt = max; + c = '-'; + } + + printk("%02d: (%05u / %05u) %03u - ", i, chan->good, chan->count, cnt); + for (uint8_t j = 0; j < cnt; j++) { + printk("%c", c); + } + printk("\n"); + } +} +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + #if defined(CONFIG_BT_CTLR_TEST) /* Refer to Bluetooth Specification v5.2 Vol 6, Part C, Section 3 LE Channel * Selection algorithm #2 sample data diff --git a/subsys/bluetooth/controller/ll_sw/lll_chan.h b/subsys/bluetooth/controller/ll_sw/lll_chan.h index bfac6b40dfa56..15787b3979922 100644 --- a/subsys/bluetooth/controller/ll_sw/lll_chan.h +++ b/subsys/bluetooth/controller/ll_sw/lll_chan.h @@ -4,6 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) +#define LLL_CHAN_METRICS +#else /* !CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ +#define LLL_CHAN_METRICS static __attribute__((always_inline)) inline +#endif /* !CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + uint8_t lll_chan_sel_1(uint8_t *chan_use, uint8_t hop, uint16_t latency, uint8_t *chan_map, uint8_t chan_count); @@ -18,4 +24,39 @@ uint8_t lll_chan_iso_subevent(uint16_t chan_id, const uint8_t *chan_map, uint8_t chan_count, uint16_t *prn_subevent_lu, uint16_t *remap_idx); +LLL_CHAN_METRICS void lll_chan_metrics_init(void); +LLL_CHAN_METRICS void lll_chan_metrics_chan_bad(uint8_t chan_idx); +LLL_CHAN_METRICS void lll_chan_metrics_chan_good(uint8_t chan_idx); +LLL_CHAN_METRICS bool lll_chan_metrics_is_notify(void); +LLL_CHAN_METRICS bool lll_chan_metrics_notify_clear(void); +LLL_CHAN_METRICS void lll_chan_metrics_print(void); + void lll_chan_sel_2_ut(void); + +#if !defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) +LLL_CHAN_METRICS void lll_chan_metrics_init(void) +{ +} + +LLL_CHAN_METRICS void lll_chan_metrics_chan_bad(uint8_t chan_idx) +{ +} + +LLL_CHAN_METRICS void lll_chan_metrics_chan_good(uint8_t chan_idx) +{ +} + +LLL_CHAN_METRICS bool lll_chan_metrics_is_notify(void) +{ + return false; +} + +LLL_CHAN_METRICS bool lll_chan_metrics_notify_clear(void) +{ + return false; +} + +LLL_CHAN_METRICS void lll_chan_metrics_print(void) +{ +} +#endif /* !CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ diff --git a/subsys/bluetooth/controller/ll_sw/lll_conn.h b/subsys/bluetooth/controller/ll_sw/lll_conn.h index 98177652ba0a6..91e52c1060538 100644 --- a/subsys/bluetooth/controller/ll_sw/lll_conn.h +++ b/subsys/bluetooth/controller/ll_sw/lll_conn.h @@ -92,12 +92,20 @@ struct lll_conn { uint8_t initiated:1; uint8_t cancelled:1; uint8_t forced:1; + +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + uint8_t chan_curr; +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ }; struct { uint8_t initiated:1; uint8_t cancelled:1; uint8_t forced:1; + +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + uint8_t chan_curr; +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ } central; #if defined(CONFIG_BT_PERIPHERAL) @@ -111,6 +119,11 @@ struct lll_conn { uint8_t phy_rx_event:3; #endif /* CONFIG_BT_CTLR_PHY */ +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + uint8_t chan_curr; + uint8_t chan_prev; +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + uint32_t window_widening_periodic_us; uint32_t window_widening_max_us; uint32_t window_widening_prepare_us; diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c index b44847c05850a..fd81b72a10a7b 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c @@ -689,7 +689,6 @@ void lll_chan_set(uint32_t chan) radio_whiten_iv_set(chan); } - uint32_t lll_radio_is_idle(void) { return radio_is_idle(); diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_central.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_central.c index 4f082b4957a40..b58380d9b0130 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_central.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_central.c @@ -182,6 +182,10 @@ static int prepare_cb(struct lll_prepare_param *p) sys_get_le24(lll->crc_init)); lll_chan_set(data_chan_use); +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + lll->central.chan_curr = data_chan_use; +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + lll_conn_tx_pkt_set(lll, pdu_data_tx); radio_isr_set(lll_conn_isr_tx, lll); diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c index 1527987c02d02..e70c985892388 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c @@ -33,6 +33,7 @@ #include "lll_df_types.h" #include "lll_df.h" #include "lll_conn.h" +#include "lll_chan.h" #include "lll_internal.h" #include "lll_df_internal.h" @@ -389,6 +390,16 @@ void lll_conn_isr_rx(void *param) goto lll_conn_isr_rx_exit; } +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + /* Increase bad channel count for previously a CRC error */ + if (crc_expire != 0U) { + lll_chan_metrics_chan_bad(lll->chan_curr); + } + + /* Increase good channel count for successful reception */ + lll_chan_metrics_chan_good(lll->chan_curr); +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + /* Reset CRC expiry counter */ crc_expire = 0U; @@ -403,8 +414,22 @@ void lll_conn_isr_rx(void *param) /* CRC error countdown */ crc_expire--; is_done = (crc_expire == 0U); + +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + /* Increase bad channel count for consecutive CRC error */ + if (is_done != 0U) { + lll_chan_metrics_chan_bad(lll->chan_curr); + } +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ } +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) && defined(CONFIG_BT_PERIPHERAL) + /* Peripheral tx-rx starts using current channel */ + if (lll->role == BT_HCI_ROLE_PERIPHERAL) { + lll->periph.chan_prev = lll->periph.chan_curr; + } +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT && CONFIG_BT_PERIPHERAL */ + #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RX) && defined(CONFIG_BT_CTLR_LE_ENC) if (lll->enc_rx) { struct pdu_data *pdu_scratch; @@ -1110,6 +1135,13 @@ static inline int isr_rx_pdu(struct lll_conn *lll, struct pdu_data *pdu_data_rx, struct node_tx *tx; memq_link_t *link; +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) && defined(CONFIG_BT_PERIPHERAL) + /* Previous event used channel is good as ack-ed by the peer */ + if (lll->role == BT_HCI_ROLE_PERIPHERAL) { + lll_chan_metrics_chan_good(lll->periph.chan_prev); + } +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT && CONFIG_BT_PERIPHERAL */ + /* Increment sequence number */ lll->sn++; diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c index 8c3b83efd7215..e4a424bac8cc5 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c @@ -211,6 +211,10 @@ static int prepare_cb(struct lll_prepare_param *p) lll_chan_set(data_chan_use); +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + lll->periph.chan_curr = data_chan_use; +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + radio_isr_set(lll_conn_isr_rx, lll); radio_tmr_tifs_set(lll->tifs_tx_us); diff --git a/subsys/bluetooth/controller/ll_sw/ull.c b/subsys/bluetooth/controller/ll_sw/ull.c index bf15820ecd6db..89ffd9ec23636 100644 --- a/subsys/bluetooth/controller/ll_sw/ull.c +++ b/subsys/bluetooth/controller/ll_sw/ull.c @@ -1363,6 +1363,10 @@ void ll_rx_dequeue(void) #if defined(CONFIG_BT_CTLR_CONN_RSSI_EVENT) case NODE_RX_TYPE_RSSI: #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */ + +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + case NODE_RX_TYPE_CHAN_METRICS: +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ #endif /* CONFIG_BT_CONN */ #if defined(CONFIG_BT_CTLR_PROFILE_ISR) @@ -1563,6 +1567,10 @@ void ll_rx_mem_release(void **node_rx) #if defined(CONFIG_BT_CTLR_CONN_RSSI_EVENT) case NODE_RX_TYPE_RSSI: #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */ + +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + case NODE_RX_TYPE_CHAN_METRICS: +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ #endif /* CONFIG_BT_CONN */ #if defined(CONFIG_BT_CTLR_PROFILE_ISR) diff --git a/subsys/bluetooth/controller/ll_sw/ull_adv.c b/subsys/bluetooth/controller/ll_sw/ull_adv.c index 18f3f24b11db2..36d76cdfadb8b 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_adv.c +++ b/subsys/bluetooth/controller/ll_sw/ull_adv.c @@ -1064,6 +1064,10 @@ uint8_t ll_adv_enable(uint8_t enable) #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */ #endif /* CONFIG_BT_CTLR_CONN_RSSI */ +#if defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT) + conn_lll->periph.chan_curr = 0U; +#endif /* CONFIG_BT_CTLR_CHAN_METRICS_EVENT */ + #if defined(CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL) conn_lll->tx_pwr_lvl = RADIO_TXP_DEFAULT; #endif /* CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_conn.c b/subsys/bluetooth/controller/ll_sw/ull_conn.c index e76b3e7147e98..5705fb54684b3 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_conn.c +++ b/subsys/bluetooth/controller/ll_sw/ull_conn.c @@ -33,6 +33,7 @@ #include "lll.h" #include "lll_clock.h" +#include "lll_chan.h" #include "lll/lll_df_types.h" #include "lll_conn.h" #include "lll_conn_iso.h" @@ -1315,6 +1316,22 @@ void ull_conn_done(struct node_rx_event_done *done) } #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */ + if (lll_chan_metrics_is_notify()) { + struct node_rx_pdu *rx; + + rx = ll_pdu_rx_alloc(); + if (rx) { + (void)lll_chan_metrics_notify_clear(); + + /* Prepare the rx packet structure */ + rx->hdr.type = NODE_RX_TYPE_CHAN_METRICS; + rx->hdr.handle = NODE_RX_HANDLE_INVALID; + + /* enqueue chan metrics structure into queue */ + ll_rx_put_sched(rx->hdr.link, rx); + } + } + /* check if latency needs update */ lazy = 0U; if ((force) || (latency_event != lll->latency_event)) {