Skip to content
Open
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
7 changes: 6 additions & 1 deletion subsys/bluetooth/controller/include/ll_feat.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@
* !CONFIG_BT_CTLR_SYNC_PERIODIC_ADI_SUPPORT
*/

/* FIXME: Conditional Compilation for FSU feature, and handle feature bits > 64 bits */
#define LL_FEAT_BIT_FRAME_SPACE 0U
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LL_FEAT_BIT_FRAME_SPACE is defined as 0U which conflicts with existing feature bit 0. This should be assigned a unique, unused bit position in the feature mask. The comment on line 256 acknowledges this needs fixing for feature bits > 64 bits.

Suggested change
#define LL_FEAT_BIT_FRAME_SPACE 0U
#define LL_FEAT_BIT_FRAME_SPACE BIT64(BT_LE_FEAT_BIT_FRAME_SPACE)

Copilot uses AI. Check for mistakes.

/* All defined feature bits */
#define LL_FEAT_BIT_MASK 0xFFFFFFFFFFULL

Expand Down Expand Up @@ -296,7 +299,9 @@
LL_FEAT_BIT_SYNC_RECEIVER | \
LL_FEAT_BIT_PERIODIC_ADI_SUPPORT | \
LL_FEAT_BIT_SYNC_TRANSFER_RECEIVER | \
LL_FEAT_BIT_SYNC_TRANSFER_SENDER)
LL_FEAT_BIT_SYNC_TRANSFER_SENDER | \
LL_FEAT_BIT_FRAME_SPACE | \
0U)

/* Connected Isochronous Stream (Host Support) bit is controlled by host */
#if defined(CONFIG_BT_CTLR_CONN_ISO)
Expand Down
13 changes: 13 additions & 0 deletions subsys/bluetooth/controller/ll_sw/lll_conn.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ struct data_pdu_length {
};
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */

struct data_pdu_fsu {
uint16_t fsu_min;
uint16_t fsu_max;
uint8_t phys;
uint16_t spacing_type;
};

struct lll_conn {
struct lll_hdr hdr;

Expand Down Expand Up @@ -133,6 +140,12 @@ struct lll_conn {
uint8_t update;
} dle;
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
struct {
struct data_pdu_fsu local;
struct data_pdu_fsu perphy[4]; /* store frame-space for each PHY */
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array size hardcoded as 4 but code elsewhere uses size 3 (e.g., ull_conn.c lines 2383, 2427, 2965). This inconsistency could lead to bugs. Consider using a named constant (e.g., FSU_NUM_PHYS) or document why the sizes differ.

Copilot uses AI. Check for mistakes.
struct data_pdu_fsu eff;
uint8_t update;
} fsu;

#if defined(CONFIG_BT_CTLR_PHY)
uint8_t phy_tx:3;
Expand Down
24 changes: 24 additions & 0 deletions subsys/bluetooth/controller/ll_sw/pdu.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@
#define PHY_FLAGS_S2 0
#define PHY_FLAGS_S8 BIT(0)

#define T_IFS_ACL_CP BIT(0)
#define T_IFS_ACL_PC BIT(1)
#define T_MCES BIT(2)
#define T_IFS_CIS BIT(3)
#define T_MSS_CIS BIT(4)

/* Macros for getting/setting did/sid from pdu_adv_adi */
#define PDU_ADV_ADI_DID_GET(adi) ((adi)->did_sid_packed[0] | \
(((adi)->did_sid_packed[1] & 0x0F) << 8))
Expand Down Expand Up @@ -624,6 +630,9 @@ enum pdu_data_llctrl_type {
PDU_DATA_LLCTRL_TYPE_CIS_RSP = 0x20,
PDU_DATA_LLCTRL_TYPE_CIS_IND = 0x21,
PDU_DATA_LLCTRL_TYPE_CIS_TERMINATE_IND = 0x22,
PDU_DATA_LLCTRL_TYPE_FRAME_SPACE_REQ = 0x3B,
PDU_DATA_LLCTRL_TYPE_FRAME_SPACE_RSP = 0x3C,

PDU_DATA_LLCTRL_TYPE_UNUSED = 0xFF
};

Expand Down Expand Up @@ -788,6 +797,19 @@ struct pdu_data_llctrl_length_req_rsp_common {
uint16_t max_tx_time;
} __packed;

struct pdu_data_llctrl_fsu_req {
uint16_t fsu_min;
uint16_t fsu_max;
uint8_t phys;
uint16_t spacing_type;
} __packed;

struct pdu_data_llctrl_fsu_rsp {
uint16_t fsu;
uint8_t phys;
uint16_t spacing_type;
} __packed;

struct pdu_data_llctrl_phy_req {
uint8_t tx_phys;
uint8_t rx_phys;
Expand Down Expand Up @@ -936,6 +958,8 @@ struct pdu_data_llctrl {
struct pdu_data_llctrl_ping_rsp ping_rsp;
struct pdu_data_llctrl_length_req length_req;
struct pdu_data_llctrl_length_rsp length_rsp;
struct pdu_data_llctrl_fsu_req fsu_req;
struct pdu_data_llctrl_fsu_rsp fsu_rsp;
struct pdu_data_llctrl_phy_req phy_req;
struct pdu_data_llctrl_phy_rsp phy_rsp;
struct pdu_data_llctrl_phy_upd_ind phy_upd_ind;
Expand Down
2 changes: 1 addition & 1 deletion subsys/bluetooth/controller/ll_sw/ull_central.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ uint8_t ll_create_connection(uint16_t scan_interval, uint16_t scan_window,
#if defined(CONFIG_BT_CTLR_DATA_LENGTH)
ull_dle_init(conn, PHY_1M);
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */

ull_fsu_init(conn);
#if defined(CONFIG_BT_CTLR_CONN_RSSI)
conn_lll->rssi_latest = BT_HCI_LE_RSSI_NOT_AVAILABLE;
#if defined(CONFIG_BT_CTLR_CONN_RSSI_EVENT)
Expand Down
141 changes: 141 additions & 0 deletions subsys/bluetooth/controller/ll_sw/ull_conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,13 @@ void ull_conn_update_parameters(struct ll_conn *conn, uint8_t is_cu_proc, uint8_
lll->tifs_tx_us = EVENT_IFS_DEFAULT_US;
lll->tifs_rx_us = EVENT_IFS_DEFAULT_US;
lll->tifs_hcto_us = EVENT_IFS_DEFAULT_US;
for (size_t i = 0; i < 3; i++) {
conn->lll.fsu.perphy[i].fsu_min = EVENT_IFS_DEFAULT_US;
conn->lll.fsu.perphy[i].fsu_max = EVENT_IFS_DEFAULT_US;
conn->lll.fsu.perphy[i].phys = PHY_1M | PHY_2M | PHY_CODED;
conn->lll.fsu.perphy[i].spacing_type =
T_IFS_ACL_PC | T_IFS_ACL_CP | T_IFS_CIS;
}

#if defined(CONFIG_BT_CTLR_DATA_LENGTH) && \
defined(CONFIG_BT_CTLR_SLOT_RESERVATION_UPDATE)
Expand Down Expand Up @@ -2417,6 +2424,14 @@ void ull_conn_update_parameters(struct ll_conn *conn, uint8_t is_cu_proc, uint8_
lll->tifs_tx_us = CONFIG_BT_CTLR_EVENT_IFS_LOW_LAT_US;
lll->tifs_rx_us = CONFIG_BT_CTLR_EVENT_IFS_LOW_LAT_US;
lll->tifs_hcto_us = CONFIG_BT_CTLR_EVENT_IFS_LOW_LAT_US;
for (size_t i = 0; i < 3; i++) {
conn->lll.fsu.perphy[i].fsu_min =
CONFIG_BT_CTLR_EVENT_IFS_LOW_LAT_US;
conn->lll.fsu.perphy[i].fsu_max = EVENT_IFS_US;
conn->lll.fsu.perphy[i].phys = PHY_1M | PHY_2M | PHY_CODED;
conn->lll.fsu.perphy[i].spacing_type =
T_IFS_ACL_PC | T_IFS_ACL_CP | T_IFS_CIS;
}
/* Reserve only the processing overhead, on overlap the
* is_abort_cb mechanism will ensure to continue the event so
* as to not loose anchor point sync.
Expand Down Expand Up @@ -2832,6 +2847,132 @@ void ull_conn_default_tx_time_set(uint16_t tx_time)
}
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */

uint8_t ull_fsu_update_eff(struct ll_conn *conn)
{
uint8_t fsu_changed = 0U;
uint8_t phy_tx;
uint8_t phy_rx;

#if defined(CONFIG_BT_PHY_UPDATE)
phy_tx = conn->lll.phy_tx;
phy_rx = conn->lll.phy_rx;
#else
phy_tx = PHY_1M;
phy_rx = PHY_1M;
#endif /* CONFIG_BT_PHY_UPDATE */

fsu_changed = ull_fsu_update_eff_from_local(conn);

if (fsu_changed) {
/* TODO: confirm that we do not need to do something here? */
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comment is unclear about what needs to be confirmed or what action might be needed. Consider either resolving this TODO before merging or documenting specifically what needs to be investigated (e.g., 'TODO: Verify if timing parameter recalculation is needed when FSU changes').

Suggested change
/* TODO: confirm that we do not need to do something here? */
/* TODO: Verify if additional actions (e.g., timing parameter recalculation or event notification) are needed when FSU changes. */

Copilot uses AI. Check for mistakes.
}

if ((conn->lll.fsu.local.spacing_type & T_IFS_CIS) == T_IFS_CIS) {

if (conn->lll.tifs_cis_us == conn->lll.fsu.eff.fsu_min) {
fsu_changed = 1;
}
conn->lll.tifs_cis_us = conn->lll.fsu.eff.fsu_min;
}

if ((conn->lll.fsu.local.spacing_type & T_IFS_ACL_CP) == T_IFS_ACL_CP) {
if (conn->lll.role == BT_HCI_ROLE_PERIPHERAL) {
if (conn->lll.fsu.local.phys & phy_tx) {
if (conn->lll.tifs_tx_us ==
conn->lll.fsu.eff.fsu_min) {
fsu_changed = 1;
}
conn->lll.tifs_tx_us = conn->lll.fsu.eff.fsu_min;
}
} else {
if (conn->lll.fsu.local.phys & phy_rx) {
if (conn->lll.tifs_rx_us ==
conn->lll.fsu.eff.fsu_min) {
fsu_changed = 1;
}
conn->lll.tifs_rx_us = conn->lll.fsu.eff.fsu_min;
}
}
}

if ((conn->lll.fsu.local.spacing_type & T_IFS_ACL_PC) == T_IFS_ACL_PC) {
if (conn->lll.role == BT_HCI_ROLE_PERIPHERAL) {
if (conn->lll.fsu.local.phys & phy_rx) {
if (conn->lll.tifs_rx_us ==
conn->lll.fsu.eff.fsu_min) {
fsu_changed = 1;
}
conn->lll.tifs_rx_us = conn->lll.fsu.eff.fsu_min;
}
} else {
if (conn->lll.fsu.local.phys & phy_tx) {
if (conn->lll.tifs_tx_us ==
conn->lll.fsu.eff.fsu_min) {
fsu_changed = 1;
}
conn->lll.tifs_tx_us = conn->lll.fsu.eff.fsu_min;
}
}
}
if (fsu_changed == 1) {
conn->lll.fsu.local.phys = 0;
conn->lll.fsu.local.spacing_type = 0;
}

return fsu_changed;
}

uint8_t ull_fsu_update_eff_from_local(struct ll_conn *conn)
{
uint8_t fsu_changed = 0U;
uint16_t fsu_min, fsu_max;

fsu_min = MAX(conn->lll.fsu.local.fsu_min, CONFIG_BT_CTLR_EVENT_IFS_LOW_LAT_US);
fsu_max = MAX(conn->lll.fsu.local.fsu_max, CONFIG_BT_CTLR_EVENT_IFS_LOW_LAT_US);

conn->lll.fsu.eff.fsu_min = fsu_min;
conn->lll.fsu.eff.fsu_max = fsu_max;
conn->lll.fsu.local.fsu_min = fsu_min;
conn->lll.fsu.local.fsu_max = fsu_max;

return fsu_changed;
}

void ull_fsu_local_tx_update(struct ll_conn *conn, uint16_t fsu_min,
uint16_t fsu_max, uint8_t phys, uint16_t spacing_type)
{
conn->lll.fsu.local.fsu_min = fsu_min;
if (conn->lll.tifs_rx_us > fsu_max) {
fsu_max = conn->lll.tifs_rx_us;
}
if (conn->lll.tifs_tx_us > fsu_max) {
fsu_max = conn->lll.tifs_tx_us;
}
conn->lll.fsu.local.fsu_max = fsu_max;
conn->lll.fsu.local.phys = phys;
conn->lll.fsu.local.spacing_type = spacing_type;
}

uint8_t ull_fsu_init(struct ll_conn *conn)
{
conn->lll.tifs_rx_us = EVENT_IFS_US;
conn->lll.tifs_tx_us = EVENT_IFS_US;
conn->lll.tifs_cis_us = EVENT_IFS_US;
conn->lll.fsu.local.fsu_min = CONFIG_BT_CTLR_EVENT_IFS_LOW_LAT_US;
conn->lll.fsu.local.fsu_max = EVENT_IFS_MAX_US;
conn->lll.fsu.eff.fsu_min = EVENT_IFS_US;
conn->lll.fsu.eff.fsu_max = EVENT_IFS_US;
for (size_t i = 0; i < 3; i++) {
conn->lll.fsu.perphy[i].fsu_min = EVENT_IFS_US;
conn->lll.fsu.perphy[i].fsu_max = EVENT_IFS_US;
conn->lll.fsu.perphy[i].phys = PHY_1M | PHY_2M | PHY_CODED;
conn->lll.fsu.perphy[i].spacing_type =
T_IFS_ACL_PC | T_IFS_ACL_CP | T_IFS_CIS;
}

return 0;
}

#if defined(CONFIG_BT_CTLR_SYNC_TRANSFER_SENDER)
static bool ticker_op_id_match_func(uint8_t ticker_id, uint32_t ticks_slot,
uint32_t ticks_to_expire, void *op_context)
Expand Down
7 changes: 7 additions & 0 deletions subsys/bluetooth/controller/ll_sw/ull_conn_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,15 @@ uint8_t ull_dle_update_eff(struct ll_conn *conn);
uint8_t ull_dle_update_eff_tx(struct ll_conn *conn);
uint8_t ull_dle_update_eff_rx(struct ll_conn *conn);

uint8_t ull_fsu_update_eff(struct ll_conn *conn);
uint8_t ull_fsu_update_eff_from_local(struct ll_conn *conn);
uint8_t ull_fsu_init(struct ll_conn *conn);

void ull_dle_local_tx_update(struct ll_conn *conn, uint16_t tx_octets, uint16_t tx_time);

void ull_fsu_local_tx_update(struct ll_conn *conn, uint16_t fsu_min,
uint16_t fsu_max, uint8_t phys, uint16_t spacing_type);

void ull_conn_default_phy_tx_set(uint8_t tx);

void ull_conn_default_phy_rx_set(uint8_t rx);
Expand Down
59 changes: 59 additions & 0 deletions subsys/bluetooth/controller/ll_sw/ull_llcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ static uint8_t MALIGN(4) buffer_mem_remote_ctx[PROC_CTX_BUF_SIZE *
CONFIG_BT_CTLR_LLCP_REMOTE_PROC_CTX_BUF_NUM];
static struct llcp_mem_pool mem_remote_ctx = { .pool = buffer_mem_remote_ctx };

uint8_t ull_cp_fsu(struct ll_conn *conn, uint16_t fsu_min, uint16_t fsu_max,
uint8_t phys, uint16_t spacing_type);

/*
* LLCP Resource Management
*/
Expand Down Expand Up @@ -856,6 +859,20 @@ uint8_t ull_cp_cis_create(struct ll_conn *conn, struct ll_conn_iso_stream *cis)
}
#endif /* defined(CONFIG_BT_CTLR_CENTRAL_ISO) */

uint8_t bt_ull_cp_fsu(uint16_t handle, uint16_t fsu_min, uint16_t fsu_max,
uint8_t phys, uint16_t spacing_type)
{
struct ll_conn *conn;
uint8_t err;

conn = ll_connected_get(handle);
if (!conn) {
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
err = ull_cp_fsu(conn, fsu_min, fsu_max, phys, spacing_type);
return err;
}

#if defined(CONFIG_BT_CENTRAL)
uint8_t ull_cp_chan_map_update(struct ll_conn *conn, const uint8_t chm[5])
{
Expand Down Expand Up @@ -926,6 +943,27 @@ uint8_t ull_cp_data_length_update(struct ll_conn *conn, uint16_t max_tx_octets,
}
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */

uint8_t ull_cp_fsu(struct ll_conn *conn, uint16_t fsu_min, uint16_t fsu_max,
uint8_t phys, uint16_t spacing_type)
{
struct proc_ctx *ctx;

if (!feature_fsu(conn)) {
return BT_HCI_ERR_SUCCESS;
}

ctx = llcp_create_local_procedure(PROC_FRAME_SPACE);
if (!ctx) {
return BT_HCI_ERR_CMD_DISALLOWED;
}

ull_fsu_local_tx_update(conn, fsu_min, fsu_max, phys, spacing_type);

llcp_lr_enqueue(conn, ctx);

return BT_HCI_ERR_SUCCESS;
}

#if defined(CONFIG_BT_CTLR_SCA_UPDATE)
uint8_t ull_cp_req_peer_sca(struct ll_conn *conn)
{
Expand Down Expand Up @@ -1153,6 +1191,15 @@ uint8_t ull_cp_remote_dle_pending(struct ll_conn *conn)
}
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */

uint8_t ull_cp_remote_fsu_pending(struct ll_conn *conn)
{
struct proc_ctx *ctx;

ctx = llcp_rr_peek(conn);

return (ctx && ctx->proc == PROC_FRAME_SPACE);
}

#if defined(CONFIG_BT_CTLR_CONN_PARAM_REQ)
void ull_cp_conn_param_req_reply(struct ll_conn *conn)
{
Expand Down Expand Up @@ -1770,6 +1817,16 @@ static bool pdu_validate_length_rsp(struct pdu_data *pdu)
return VALIDATE_PDU_LEN(pdu, length_rsp);
}

static bool pdu_validate_fsu_req(struct pdu_data *pdu)
{
return VALIDATE_PDU_LEN(pdu, fsu_req);
}

static bool pdu_validate_fsu_rsp(struct pdu_data *pdu)
{
return VALIDATE_PDU_LEN(pdu, fsu_rsp);
}

#if defined(CONFIG_BT_CTLR_PHY)
static bool pdu_validate_phy_req(struct pdu_data *pdu)
{
Expand Down Expand Up @@ -1881,6 +1938,8 @@ static const struct pdu_validate pdu_validate[] = {
[PDU_DATA_LLCTRL_TYPE_LENGTH_REQ] = { pdu_validate_length_req },
#endif /* CONFIG_BT_CTLR_DATA_LENGTH */
[PDU_DATA_LLCTRL_TYPE_LENGTH_RSP] = { pdu_validate_length_rsp },
[PDU_DATA_LLCTRL_TYPE_FRAME_SPACE_REQ] = { pdu_validate_fsu_req },
[PDU_DATA_LLCTRL_TYPE_FRAME_SPACE_RSP] = { pdu_validate_fsu_rsp },
#if defined(CONFIG_BT_CTLR_PHY)
[PDU_DATA_LLCTRL_TYPE_PHY_REQ] = { pdu_validate_phy_req },
#endif /* CONFIG_BT_CTLR_PHY */
Expand Down
Loading