diff --git a/include/zephyr/bluetooth/l2cap.h b/include/zephyr/bluetooth/l2cap.h index 0117da5ebb9..d3a49f9b3a9 100644 --- a/include/zephyr/bluetooth/l2cap.h +++ b/include/zephyr/bluetooth/l2cap.h @@ -18,6 +18,7 @@ * @{ */ +#include #include #include @@ -552,6 +553,26 @@ int bt_l2cap_ecred_chan_connect(struct bt_conn *conn, */ int bt_l2cap_ecred_chan_reconfigure(struct bt_l2cap_chan **chans, uint16_t mtu); +/** @brief Reconfigure Enhanced Credit Based L2CAP channels + * + * Experimental API to reconfigure with explicit MPS and MTU values. + * + * Reconfigure up to 5 L2CAP channels. Channels must be from the same bt_conn. + * Once reconfiguration is completed each channel reconfigured() callback will + * be called. MTU cannot be decreased on any of provided channels. + * + * @kconfig_dep{CONFIG_BT_L2CAP_RECONFIGURE_EXPLICIT} + * + * @param chans Array of channel objects. Null-terminated. Elements after the + * first 5 are silently ignored. + * @param mtu Channel MTU to reconfigure to. + * @param mps Channel MPS to reconfigure to. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_l2cap_ecred_chan_reconfigure_explicit(struct bt_l2cap_chan **chans, uint16_t mtu, + uint16_t mps); + /** @brief Connect L2CAP channel * * Connect L2CAP channel by PSM, once the connection is completed channel diff --git a/subsys/bluetooth/host/Kconfig.l2cap b/subsys/bluetooth/host/Kconfig.l2cap index ed1428aade7..b886dc9d740 100644 --- a/subsys/bluetooth/host/Kconfig.l2cap +++ b/subsys/bluetooth/host/Kconfig.l2cap @@ -70,4 +70,12 @@ config BT_L2CAP_SEG_RECV This API enforces conformance with L2CAP TS, but is otherwise as flexible and semantically simple as possible. +config BT_L2CAP_RECONFIGURE_EXPLICIT + bool "L2CAP Explicit reconfigure API [EXPERIMENTAL]" + select EXPERIMENTAL + help + + Enable API for explicit reconfiguration of an L2CAP channel's MTU and + MPS. + endmenu diff --git a/subsys/bluetooth/host/l2cap.c b/subsys/bluetooth/host/l2cap.c index 1e3850fd591..ed185d12fd7 100644 --- a/subsys/bluetooth/host/l2cap.c +++ b/subsys/bluetooth/host/l2cap.c @@ -41,6 +41,7 @@ LOG_MODULE_REGISTER(bt_l2cap, CONFIG_BT_L2CAP_LOG_LEVEL); #define L2CAP_LE_MIN_MTU 23 #define L2CAP_ECRED_MIN_MTU 64 +#define L2CAP_ECRED_MIN_MPS 64 #define L2CAP_LE_MAX_CREDITS (CONFIG_BT_BUF_ACL_RX_COUNT - 1) @@ -2557,6 +2558,7 @@ static void l2cap_chan_le_recv_seg_direct(struct bt_l2cap_le_chan *chan, struct if (seg->len > sdu_remaining) { LOG_WRN("L2CAP RX PDU total exceeds SDU"); bt_l2cap_chan_disconnect(&chan->chan); + return; } /* Commit receive. */ @@ -3031,6 +3033,105 @@ int bt_l2cap_ecred_chan_reconfigure(struct bt_l2cap_chan **chans, uint16_t mtu) return 0; } +#if defined(CONFIG_BT_L2CAP_RECONFIGURE_EXPLICIT) +int bt_l2cap_ecred_chan_reconfigure_explicit(struct bt_l2cap_chan **chans, uint16_t mtu, + uint16_t mps) +{ + struct bt_l2cap_ecred_reconf_req *req; + struct bt_conn *conn = NULL; + struct bt_l2cap_le_chan *ch; + struct net_buf *buf; + bool multiple_chan; + uint8_t ident; + int i; + + LOG_DBG("chans %p mtu 0x%04x mps 0x%04x", chans, mtu, mps); + + if (!chans) { + return -EINVAL; + } + + if (chans[0] == NULL) { + return -EINVAL; + } + + if (mps < L2CAP_ECRED_MIN_MPS || mps > BT_L2CAP_RX_MTU) { + return -EINVAL; + } + + multiple_chan = chans[1] != NULL; + + for (i = 0; i < L2CAP_ECRED_CHAN_MAX_PER_REQ; i++) { + if (!chans[i]) { + break; + } + + /* validate that all channels are from same connection */ + if (conn) { + if (conn != chans[i]->conn) { + return -EINVAL; + } + } else { + conn = chans[i]->conn; + } + + /* validate MTU is not decreased */ + if (mtu < BT_L2CAP_LE_CHAN(chans[i])->rx.mtu) { + return -EINVAL; + } + + /* MPS is not allowed to decrease when reconfiguring multiple channels. + * Core Specification 3.A.4.27 v6.0 + */ + if (multiple_chan && mps < BT_L2CAP_LE_CHAN(chans[i])->rx.mps) { + return -EINVAL; + } + } + + if (!conn) { + return -ENOTCONN; + } + + if (conn->type != BT_CONN_TYPE_LE) { + return -EINVAL; + } + + /* allow only 1 request at time */ + if (l2cap_find_pending_reconf(conn)) { + return -EBUSY; + } + + ident = get_ident(); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_ECRED_RECONF_REQ, ident, + sizeof(*req) + (i * sizeof(uint16_t))); + if (!buf) { + return -ENOMEM; + } + + req = net_buf_add(buf, sizeof(*req)); + req->mtu = sys_cpu_to_le16(mtu); + req->mps = sys_cpu_to_le16(mps); + + for (int j = 0; j < i; j++) { + ch = BT_L2CAP_LE_CHAN(chans[j]); + + ch->ident = ident; + ch->pending_rx_mtu = mtu; + + net_buf_add_le16(buf, ch->rx.cid); + }; + + /* We set the RTX timer on one of the supplied channels, but when the + * request resolves or times out we will act on all the channels in the + * supplied array, using the ident field to find them. + */ + l2cap_chan_send_req(chans[0], buf, L2CAP_CONN_TIMEOUT); + + return 0; +} +#endif /* defined(CONFIG_BT_L2CAP_RECONFIGURE_EXPLICIT) */ + #endif /* defined(CONFIG_BT_L2CAP_ECRED) */ int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, diff --git a/tests/bluetooth/tester/prj.conf b/tests/bluetooth/tester/prj.conf index ba5a40d078b..db254d1f668 100644 --- a/tests/bluetooth/tester/prj.conf +++ b/tests/bluetooth/tester/prj.conf @@ -16,6 +16,9 @@ CONFIG_BT_BONDABLE=y CONFIG_BT_ATT_PREPARE_COUNT=12 CONFIG_BT_GATT_CLIENT=y CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y +CONFIG_BT_L2CAP_TX_MTU=255 +CONFIG_BT_L2CAP_SEG_RECV=y +CONFIG_BT_L2CAP_RECONFIGURE_EXPLICIT=y CONFIG_BT_DEVICE_NAME="Tester" CONFIG_BT_DEVICE_NAME_MAX=32 CONFIG_BT_DEVICE_NAME_DYNAMIC=y @@ -25,15 +28,14 @@ CONFIG_BT_L2CAP_ECRED=y CONFIG_BT_EATT_MAX=5 CONFIG_BT_FILTER_ACCEPT_LIST=y CONFIG_BT_EATT_AUTO_CONNECT=n -CONFIG_BT_MAX_CONN=2 -CONFIG_BT_MAX_PAIRED=2 +CONFIG_BT_MAX_CONN=3 +CONFIG_BT_MAX_PAIRED=3 CONFIG_BT_GATT_NOTIFY_MULTIPLE=y CONFIG_BT_ATT_RETRY_ON_SEC_ERR=n CONFIG_BT_GATT_DYNAMIC_DB=y CONFIG_BT_EXT_ADV=y CONFIG_BT_PER_ADV=y CONFIG_BT_PER_ADV_SYNC=y -CONFIG_BT_BUF_ACL_RX_SIZE=100 CONFIG_BT_RX_STACK_SIZE=4096 CONFIG_BT_TESTING=y @@ -54,3 +56,9 @@ CONFIG_BT_DIS_SERIAL_NUMBER=y CONFIG_BT_DIS_FW_REV=y CONFIG_BT_DIS_HW_REV=y CONFIG_BT_DIS_SW_REV=y + +CONFIG_BT_BUF_EVT_RX_COUNT=16 +CONFIG_BT_BUF_EVT_RX_SIZE=255 +CONFIG_BT_BUF_CMD_TX_SIZE=255 +CONFIG_BT_BUF_ACL_TX_SIZE=251 +CONFIG_BT_BUF_ACL_RX_SIZE=255 diff --git a/tests/bluetooth/tester/src/btp_l2cap.c b/tests/bluetooth/tester/src/btp_l2cap.c index 69c438f7f6f..6b62811eaf3 100644 --- a/tests/bluetooth/tester/src/btp_l2cap.c +++ b/tests/bluetooth/tester/src/btp_l2cap.c @@ -19,14 +19,15 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL); #include "btp/btp.h" -#define DATA_MTU_INITIAL 128 -#define DATA_MTU 256 -#define DATA_BUF_SIZE BT_L2CAP_SDU_BUF_SIZE(DATA_MTU) +#define L2CAP_MPS 96 +#define DATA_MTU (3 * L2CAP_MPS) +#define DATA_MTU_INITIAL (2 * L2CAP_MPS) + #define CHANNELS 2 #define SERVERS 1 -NET_BUF_POOL_FIXED_DEFINE(data_pool, CHANNELS, DATA_BUF_SIZE, CONFIG_BT_CONN_TX_USER_DATA_SIZE, - NULL); +NET_BUF_POOL_FIXED_DEFINE(data_pool, CHANNELS, BT_L2CAP_SDU_BUF_SIZE(DATA_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); static bool authorize_flag; static uint8_t req_keysize; @@ -36,18 +37,51 @@ static struct channel { struct bt_l2cap_le_chan le; bool in_use; bool hold_credit; +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + unsigned int pending_credits; + uint8_t recv_cb_buf[DATA_MTU + sizeof(struct btp_l2cap_data_received_ev)]; +#else struct net_buf *pending_credit; +#endif } channels[CHANNELS]; /* TODO Extend to support multiple servers */ static struct bt_l2cap_server servers[SERVERS]; +#if defined(CONFIG_BT_L2CAP_SEG_RECV) +static void seg_recv_cb(struct bt_l2cap_chan *l2cap_chan, size_t sdu_len, off_t seg_offset, + struct net_buf_simple *seg) +{ + struct btp_l2cap_data_received_ev *ev; + struct bt_l2cap_le_chan *l2cap_le_chan = + CONTAINER_OF(l2cap_chan, struct bt_l2cap_le_chan, chan); + struct channel *chan = CONTAINER_OF(l2cap_le_chan, struct channel, le); + + ev = (void *)chan->recv_cb_buf; + memcpy(&ev->data[seg_offset], seg->data, seg->len); + + /* complete SDU received */ + if (seg_offset + seg->len == sdu_len) { + ev->chan_id = chan->chan_id; + ev->data_length = sys_cpu_to_le16(sdu_len); + + tester_event(BTP_SERVICE_ID_L2CAP, BTP_L2CAP_EV_DATA_RECEIVED, chan->recv_cb_buf, + sizeof(*ev) + sdu_len); + } + + if (chan->hold_credit) { + chan->pending_credits++; + } else { + bt_l2cap_chan_give_credits(l2cap_chan, 1); + } +} +#else static struct net_buf *alloc_buf_cb(struct bt_l2cap_chan *chan) { return net_buf_alloc(&data_pool, K_FOREVER); } -static uint8_t recv_cb_buf[DATA_BUF_SIZE + sizeof(struct btp_l2cap_data_received_ev)]; +static uint8_t recv_cb_buf[DATA_MTU + sizeof(struct btp_l2cap_data_received_ev)]; static int recv_cb(struct bt_l2cap_chan *l2cap_chan, struct net_buf *buf) { @@ -73,6 +107,7 @@ static int recv_cb(struct bt_l2cap_chan *l2cap_chan, struct net_buf *buf) return 0; } +#endif static void connected_cb(struct bt_l2cap_chan *l2cap_chan) { @@ -111,11 +146,13 @@ static void disconnected_cb(struct bt_l2cap_chan *l2cap_chan) struct channel *chan = CONTAINER_OF(l2cap_le_chan, struct channel, le); struct bt_conn_info info; +#if !defined(CONFIG_BT_L2CAP_SEG_RECV) /* release netbuf on premature disconnection */ if (chan->pending_credit) { net_buf_unref(chan->pending_credit); chan->pending_credit = NULL; } +#endif (void)memset(&ev, 0, sizeof(struct btp_l2cap_disconnected_ev)); @@ -160,12 +197,16 @@ static void reconfigured_cb(struct bt_l2cap_chan *l2cap_chan) #endif static const struct bt_l2cap_chan_ops l2cap_ops = { - .alloc_buf = alloc_buf_cb, - .recv = recv_cb, - .connected = connected_cb, - .disconnected = disconnected_cb, +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + .seg_recv = seg_recv_cb, +#else + .alloc_buf = alloc_buf_cb, + .recv = recv_cb, +#endif + .connected = connected_cb, + .disconnected = disconnected_cb, #if defined(CONFIG_BT_L2CAP_ECRED) - .reconfigured = reconfigured_cb, + .reconfigured = reconfigured_cb, #endif }; @@ -222,10 +263,15 @@ static uint8_t connect(const void *cmd, uint16_t cmd_len, } chan->le.chan.ops = &l2cap_ops; chan->le.rx.mtu = mtu; +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + chan->le.rx.mps = L2CAP_MPS; +#endif rp->chan_id[i] = chan->chan_id; allocated_channels[i] = &chan->le.chan; chan->hold_credit = cp->options & BTP_L2CAP_CONNECT_OPT_HOLD_CREDIT; + + bt_l2cap_chan_give_credits(&chan->le.chan, 1); } if (cp->num == 1 && !ecfc) { @@ -289,6 +335,7 @@ static uint8_t reconfigure(const void *cmd, uint16_t cmd_len, { const struct btp_l2cap_reconfigure_cmd *cp = cmd; uint16_t mtu; + uint16_t mps; struct bt_conn *conn; int err; struct bt_l2cap_chan *reconf_channels[CHANNELS + 1] = {}; @@ -321,7 +368,8 @@ static uint8_t reconfigure(const void *cmd, uint16_t cmd_len, return BTP_STATUS_FAILED; } - err = bt_l2cap_ecred_chan_reconfigure(reconf_channels, mtu); + mps = MIN(L2CAP_MPS, BT_L2CAP_RX_MTU); + err = bt_l2cap_ecred_chan_reconfigure_explicit(reconf_channels, mtu, mps); if (err) { bt_conn_unref(conn); return BTP_STATUS_FAILED; @@ -454,9 +502,14 @@ static int accept(struct bt_conn *conn, struct bt_l2cap_server *server, chan->le.chan.ops = &l2cap_ops; chan->le.rx.mtu = DATA_MTU_INITIAL; +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + chan->le.rx.mps = L2CAP_MPS; +#endif *l2cap_chan = &chan->le.chan; + bt_l2cap_chan_give_credits(&chan->le.chan, 1); + return 0; } @@ -524,7 +577,15 @@ static uint8_t credits(const void *cmd, uint16_t cmd_len, if (!chan->in_use) { return BTP_STATUS_FAILED; } +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + if (chan->pending_credits) { + if (bt_l2cap_chan_give_credits(&chan->le.chan, chan->pending_credits) < 0) { + return BTP_STATUS_FAILED; + } + chan->pending_credits = 0; + } +#else if (chan->pending_credit) { if (bt_l2cap_chan_recv_complete(&chan->le.chan, chan->pending_credit) < 0) { @@ -533,6 +594,7 @@ static uint8_t credits(const void *cmd, uint16_t cmd_len, chan->pending_credit = NULL; } +#endif return BTP_STATUS_SUCCESS; }