Bluetooth: L2CAP: Add initial implementation of ECRED mode

This adds the initial implementation of ECRED mode which can connect up
to 5 channels simultaneously and is required by EATT.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
Luiz Augusto von Dentz 2020-02-26 10:29:23 -08:00 committed by Johan Hedberg
commit f7586b0b04
2 changed files with 508 additions and 51 deletions

View file

@ -335,6 +335,21 @@ int bt_l2cap_server_register(struct bt_l2cap_server *server);
*/
int bt_l2cap_br_server_register(struct bt_l2cap_server *server);
/** @brief Connect Enhanced Credit Based L2CAP channels
*
* Connect up to 5 L2CAP channels by PSM, once the connection is completed
* each channel connected() callback will be called. If the connection is
* rejected disconnected() callback is called instead.
*
* @param conn Connection object.
* @param chans Array of channel objects.
* @param psm Channel PSM to connect to.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_l2cap_ecred_chan_connect(struct bt_conn *conn,
struct bt_l2cap_chan **chans, u16_t psm);
/** @brief Connect L2CAP channel
*
* Connect L2CAP channel by PSM, once the connection is completed channel

View file

@ -30,6 +30,7 @@
#define CHAN_RX(_w) CONTAINER_OF(_w, struct bt_l2cap_le_chan, rx_work)
#define L2CAP_LE_MIN_MTU 23
#define L2CAP_ECRED_MIN_MTU 64
#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
#define L2CAP_LE_MAX_CREDITS (CONFIG_BT_ACL_RX_COUNT - 1)
@ -65,6 +66,8 @@ NET_BUF_POOL_FIXED_DEFINE(disc_pool, 1,
/* For now use MPS - SDU length to disable segmentation */
#define L2CAP_MAX_LE_MTU (L2CAP_MAX_LE_MPS - 2)
#define L2CAP_ECRED_CHAN_MAX 5
#define l2cap_lookup_ident(conn, ident) __l2cap_lookup_ident(conn, ident, false)
#define l2cap_remove_ident(conn, ident) __l2cap_lookup_ident(conn, ident, true)
@ -256,17 +259,24 @@ destroy:
if (chan->destroy) {
chan->destroy(chan);
}
}
static void l2cap_rtx_timeout(struct k_work *work)
{
struct bt_l2cap_le_chan *chan = LE_CHAN_RTX(work);
struct bt_conn *conn = chan->chan.conn;
BT_ERR("chan %p timeout", chan);
bt_l2cap_chan_remove(chan->chan.conn, &chan->chan);
bt_l2cap_chan_remove(conn, &chan->chan);
bt_l2cap_chan_del(&chan->chan);
#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL)
/* Remove other channels if pending on the same ident */
while ((chan = l2cap_remove_ident(conn, chan->chan.ident))) {
bt_l2cap_chan_del(&chan->chan);
}
#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */
}
#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL)
@ -410,7 +420,7 @@ static struct net_buf *l2cap_create_le_sig_pdu(struct net_buf *buf,
}
#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL)
static void l2cap_chan_send_req(struct bt_l2cap_le_chan *chan,
static void l2cap_chan_send_req(struct bt_l2cap_chan *chan,
struct net_buf *buf, k_timeout_t timeout)
{
/* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part A] page 126:
@ -422,9 +432,9 @@ static void l2cap_chan_send_req(struct bt_l2cap_le_chan *chan,
* final expiration, when the response is received, or the physical
* link is lost.
*/
k_delayed_work_submit(&chan->chan.rtx_work, timeout);
k_delayed_work_submit(&chan->rtx_work, timeout);
bt_l2cap_send(chan->chan.conn, BT_L2CAP_CID_LE_SIG, buf);
bt_l2cap_send(chan->conn, BT_L2CAP_CID_LE_SIG, buf);
}
static int l2cap_le_conn_req(struct bt_l2cap_le_chan *ch)
@ -447,15 +457,58 @@ static int l2cap_le_conn_req(struct bt_l2cap_le_chan *ch)
req->mps = sys_cpu_to_le16(ch->rx.mps);
req->credits = sys_cpu_to_le16(ch->rx.init_credits);
l2cap_chan_send_req(ch, buf, L2CAP_CONN_TIMEOUT);
l2cap_chan_send_req(&ch->chan, buf, L2CAP_CONN_TIMEOUT);
return 0;
}
static int l2cap_ecred_conn_req(struct bt_l2cap_chan **chan, int channels)
{
struct net_buf *buf;
struct bt_l2cap_ecred_conn_req *req;
struct bt_l2cap_le_chan *ch;
int i;
u8_t ident;
if (!chan || !channels) {
return -EINVAL;
}
ident = get_ident();
buf = l2cap_create_le_sig_pdu(NULL, BT_L2CAP_ECRED_CONN_REQ, ident,
sizeof(*req) +
(channels * sizeof(u16_t)));
req = net_buf_add(buf, sizeof(*req));
ch = BT_L2CAP_LE_CHAN(chan[0]);
/* Init common parameters */
req->psm = sys_cpu_to_le16(ch->chan.psm);
req->mtu = sys_cpu_to_le16(ch->rx.mtu);
req->mps = sys_cpu_to_le16(ch->rx.mps);
req->credits = sys_cpu_to_le16(ch->rx.init_credits);
for (i = 0; i < channels; i++) {
ch = BT_L2CAP_LE_CHAN(chan[i]);
ch->chan.ident = ident;
net_buf_add_le16(buf, ch->rx.cid);
}
l2cap_chan_send_req(*chan, buf, L2CAP_CONN_TIMEOUT);
return 0;
}
static void l2cap_le_encrypt_change(struct bt_l2cap_chan *chan, u8_t status)
{
/* Skip channels already connected or with a pending request */
if (chan->state != BT_L2CAP_CONNECT || chan->ident) {
/* Skip channels already connected or there is an ongoing request */
if (chan->state != BT_L2CAP_CONNECT ||
k_work_pending(&chan->rtx_work.work) ||
k_delayed_work_remaining_get(&chan->rtx_work)) {
return;
}
@ -465,6 +518,20 @@ static void l2cap_le_encrypt_change(struct bt_l2cap_chan *chan, u8_t status)
return;
}
if (chan->ident) {
struct bt_l2cap_chan *echan[L2CAP_ECRED_CHAN_MAX];
struct bt_l2cap_le_chan *ch;
int i = 0;
while ((ch = l2cap_remove_ident(chan->conn, chan->ident))) {
echan[i++] = &ch->chan;
}
/* Retry ecred connect */
l2cap_ecred_conn_req(echan, i);
return;
}
/* Retry to connect */
l2cap_le_conn_req(BT_L2CAP_LE_CHAN(chan));
}
@ -848,16 +915,77 @@ static u16_t le_err_to_result(int err)
}
}
static u16_t l2cap_chan_accept(struct bt_conn *conn,
struct bt_l2cap_server *server, u16_t scid,
u16_t mtu, u16_t mps, u16_t credits,
struct bt_l2cap_chan **chan)
{
struct bt_l2cap_le_chan *ch;
int err;
BT_DBG("conn %p scid 0x%04x chan %p", conn, scid, chan);
if (!L2CAP_LE_CID_IS_DYN(scid)) {
return BT_L2CAP_LE_ERR_INVALID_SCID;
}
*chan = bt_l2cap_le_lookup_tx_cid(conn, scid);
if (*chan) {
return BT_L2CAP_LE_ERR_SCID_IN_USE;
}
/* Request server to accept the new connection and allocate the
* channel.
*/
err = server->accept(conn, chan);
if (err < 0) {
return le_err_to_result(err);
}
(*chan)->required_sec_level = server->sec_level;
if (!l2cap_chan_add(conn, *chan, l2cap_chan_destroy)) {
return BT_L2CAP_LE_ERR_NO_RESOURCES;
}
ch = BT_L2CAP_LE_CHAN(*chan);
/* Init TX parameters */
l2cap_chan_tx_init(ch);
ch->tx.cid = scid;
ch->tx.mps = mps;
ch->tx.mtu = mtu;
ch->tx.init_credits = credits;
l2cap_chan_tx_give_credits(ch, credits);
/* Init RX parameters */
l2cap_chan_rx_init(ch);
l2cap_chan_rx_give_credits(ch, ch->rx.init_credits);
/* Set channel PSM */
(*chan)->psm = server->psm;
/* Update state */
bt_l2cap_chan_set_state(*chan, BT_L2CAP_CONNECTED);
if ((*chan)->ops->connected) {
(*chan)->ops->connected(*chan);
}
return BT_L2CAP_LE_SUCCESS;
}
static void le_conn_req(struct bt_l2cap *l2cap, u8_t ident,
struct net_buf *buf)
{
struct bt_conn *conn = l2cap->chan.chan.conn;
struct bt_l2cap_chan *chan;
struct bt_l2cap_le_chan *ch;
struct bt_l2cap_server *server;
struct bt_l2cap_le_conn_req *req = (void *)buf->data;
struct bt_l2cap_le_conn_rsp *rsp;
u16_t psm, scid, mtu, mps, credits;
int err;
u16_t result;
if (buf->len < sizeof(*req)) {
BT_ERR("Too small LE conn req packet size");
@ -900,63 +1028,200 @@ static void le_conn_req(struct bt_l2cap *l2cap, u8_t ident,
goto rsp;
}
if (!L2CAP_LE_CID_IS_DYN(scid)) {
rsp->result = sys_cpu_to_le16(BT_L2CAP_LE_ERR_INVALID_SCID);
result = l2cap_chan_accept(conn, server, scid, mtu, mps, credits,
&chan);
if (result != BT_L2CAP_LE_SUCCESS) {
rsp->result = sys_cpu_to_le16(result);
goto rsp;
}
chan = bt_l2cap_le_lookup_tx_cid(conn, scid);
if (chan) {
rsp->result = sys_cpu_to_le16(BT_L2CAP_LE_ERR_SCID_IN_USE);
goto rsp;
ch = BT_L2CAP_LE_CHAN(chan);
/* Prepare response protocol data */
rsp->dcid = sys_cpu_to_le16(ch->rx.cid);
rsp->mps = sys_cpu_to_le16(ch->rx.mps);
rsp->mtu = sys_cpu_to_le16(ch->rx.mtu);
rsp->credits = sys_cpu_to_le16(ch->rx.init_credits);
rsp->result = BT_L2CAP_LE_SUCCESS;
rsp:
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
}
static void le_ecred_conn_req(struct bt_l2cap *l2cap, u8_t ident,
struct net_buf *buf)
{
struct bt_conn *conn = l2cap->chan.chan.conn;
struct bt_l2cap_chan *chan[L2CAP_ECRED_CHAN_MAX];
struct bt_l2cap_le_chan *ch = NULL;
struct bt_l2cap_server *server;
struct bt_l2cap_ecred_conn_req *req;
struct bt_l2cap_ecred_conn_rsp *rsp;
u16_t psm, mtu, mps, credits, result = BT_L2CAP_LE_ERR_INVALID_SCID;
u16_t scid, dcid[L2CAP_ECRED_CHAN_MAX];
int i = 0;
if (buf->len < sizeof(*req)) {
BT_ERR("Too small LE conn req packet size");
result = BT_L2CAP_LE_ERR_INVALID_PARAMS;
goto response;
}
/* Request server to accept the new connection and allocate the
* channel.
*/
err = server->accept(conn, &chan);
if (err < 0) {
rsp->result = sys_cpu_to_le16(le_err_to_result(err));
goto rsp;
req = net_buf_pull_mem(buf, sizeof(*req));
psm = sys_le16_to_cpu(req->psm);
mtu = sys_le16_to_cpu(req->mtu);
mps = sys_le16_to_cpu(req->mps);
credits = sys_le16_to_cpu(req->credits);
BT_DBG("psm 0x%02x mtu %u mps %u credits %u", psm, mtu, mps, credits);
if (mtu < L2CAP_ECRED_MIN_MTU || mps < L2CAP_ECRED_MIN_MTU) {
BT_ERR("Invalid ecred conn req params");
result = BT_L2CAP_LE_ERR_UNACCEPT_PARAMS;
goto response;
}
chan->required_sec_level = server->sec_level;
/* Check if there is a server registered */
server = l2cap_server_lookup_psm(psm);
if (!server) {
result = BT_L2CAP_LE_ERR_PSM_NOT_SUPP;
goto response;
}
if (l2cap_chan_add(conn, chan, l2cap_chan_destroy)) {
struct bt_l2cap_le_chan *ch = BT_L2CAP_LE_CHAN(chan);
/* Check if connection has minimum required security level */
if (conn->sec_level < server->sec_level) {
result = BT_L2CAP_LE_ERR_AUTHENTICATION;
goto response;
}
/* Init TX parameters */
l2cap_chan_tx_init(ch);
ch->tx.cid = scid;
ch->tx.mps = mps;
ch->tx.mtu = mtu;
ch->tx.init_credits = credits;
l2cap_chan_tx_give_credits(ch, credits);
while (buf->len >= sizeof(scid)) {
scid = net_buf_pull_le16(buf);
/* Init RX parameters */
l2cap_chan_rx_init(ch);
l2cap_chan_rx_give_credits(ch, ch->rx.init_credits);
/* Set channel PSM */
chan->psm = server->psm;
/* Update state */
bt_l2cap_chan_set_state(chan, BT_L2CAP_CONNECTED);
if (chan->ops->connected) {
chan->ops->connected(chan);
result = l2cap_chan_accept(conn, server, scid, mtu, mps,
credits, &chan[i]);
switch (result) {
case BT_L2CAP_LE_SUCCESS:
ch = BT_L2CAP_LE_CHAN(chan[i]);
dcid[i++] = sys_cpu_to_le16(ch->rx.cid);
continue;
/* Some connections refused invalid Source CID */
case BT_L2CAP_LE_ERR_INVALID_SCID:
/* Some connections refused Source CID already allocated */
case BT_L2CAP_LE_ERR_SCID_IN_USE:
/* If a Destination CID is 0x0000, the channel was not
* established.
*/
dcid[i++] = 0x0000;
continue;
/* Some connections refused not enough resources
* available.
*/
case BT_L2CAP_LE_ERR_NO_RESOURCES:
default:
goto response;
}
}
/* Prepare response protocol data */
rsp->dcid = sys_cpu_to_le16(ch->rx.cid);
response:
if (!i) {
i = buf->len / sizeof(scid);
}
buf = l2cap_create_le_sig_pdu(buf, BT_L2CAP_ECRED_CONN_RSP, ident,
sizeof(*rsp) + (sizeof(scid) * i));
rsp = net_buf_add(buf, sizeof(*rsp));
(void)memset(rsp, 0, sizeof(*rsp));
if (result == BT_L2CAP_LE_ERR_UNACCEPT_PARAMS ||
result == BT_L2CAP_LE_ERR_PSM_NOT_SUPP ||
result == BT_L2CAP_LE_ERR_AUTHENTICATION) {
memset(dcid, 0, sizeof(scid) * i);
} else if (ch) {
rsp->mps = sys_cpu_to_le16(ch->rx.mps);
rsp->mtu = sys_cpu_to_le16(ch->rx.mtu);
rsp->credits = sys_cpu_to_le16(ch->rx.init_credits);
rsp->result = BT_L2CAP_LE_SUCCESS;
} else {
rsp->result = sys_cpu_to_le16(BT_L2CAP_LE_ERR_NO_RESOURCES);
}
rsp:
net_buf_add_mem(buf, dcid, sizeof(scid) * i);
rsp->result = sys_cpu_to_le16(result);
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
}
static void le_ecred_reconf_req(struct bt_l2cap *l2cap, u8_t ident,
struct net_buf *buf)
{
struct bt_conn *conn = l2cap->chan.chan.conn;
struct bt_l2cap_ecred_reconf_req *req;
struct bt_l2cap_ecred_reconf_rsp *rsp;
u16_t mtu, mps;
u16_t scid, result;
if (buf->len < sizeof(*req)) {
BT_ERR("Too small ecred reconf req packet size");
return;
}
req = net_buf_pull_mem(buf, sizeof(*req));
mtu = sys_le16_to_cpu(req->mtu);
mps = sys_le16_to_cpu(req->mps);
if (mtu < L2CAP_ECRED_MIN_MTU) {
result = BT_L2CAP_RECONF_INVALID_MTU;
goto response;
}
if (mps < L2CAP_ECRED_MIN_MTU) {
result = BT_L2CAP_RECONF_INVALID_MTU;
goto response;
}
while (buf->len >= sizeof(scid)) {
struct bt_l2cap_chan *chan;
scid = net_buf_pull_le16(buf);
BT_DBG("scid 0x%04x", scid);
if (!scid) {
continue;
}
chan = bt_l2cap_le_lookup_tx_cid(conn, scid);
if (!chan) {
continue;
}
/* If the MTU value is decreased for any of the included
* channels, then the receiver shall disconnect all
* included channels.
*/
if (BT_L2CAP_LE_CHAN(chan)->tx.mtu > mtu) {
BT_ERR("chan %p decreased MTU %u -> %u", chan,
BT_L2CAP_LE_CHAN(chan)->tx.mtu, mtu);
result = BT_L2CAP_RECONF_INVALID_MTU;
bt_l2cap_chan_disconnect(chan);
goto response;
}
BT_L2CAP_LE_CHAN(chan)->tx.mtu = mtu;
BT_L2CAP_LE_CHAN(chan)->tx.mps = mps;
}
BT_DBG("mtu %u mps %u", mtu, mps);
result = BT_L2CAP_RECONF_SUCCESS;
response:
buf = l2cap_create_le_sig_pdu(buf, BT_L2CAP_ECRED_RECONF_RSP, ident,
sizeof(*rsp));
rsp = net_buf_add(buf, sizeof(*rsp));
rsp->result = sys_cpu_to_le16(result);
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
}
@ -1056,6 +1321,113 @@ static int l2cap_change_security(struct bt_l2cap_le_chan *chan, u16_t err)
chan->chan.required_sec_level);
}
static void le_ecred_conn_rsp(struct bt_l2cap *l2cap, u8_t ident,
struct net_buf *buf)
{
struct bt_conn *conn = l2cap->chan.chan.conn;
struct bt_l2cap_le_chan *chan;
struct bt_l2cap_ecred_conn_rsp *rsp;
u16_t dcid, mtu, mps, credits, result;
if (buf->len < sizeof(*rsp)) {
BT_ERR("Too small ecred conn rsp packet size");
return;
}
rsp = net_buf_pull_mem(buf, sizeof(*rsp));
mtu = sys_le16_to_cpu(rsp->mtu);
mps = sys_le16_to_cpu(rsp->mps);
credits = sys_le16_to_cpu(rsp->credits);
result = sys_le16_to_cpu(rsp->result);
BT_DBG("mtu 0x%04x mps 0x%04x credits 0x%04x result %u", mtu,
mps, credits, result);
switch (result) {
case BT_L2CAP_LE_ERR_AUTHENTICATION:
case BT_L2CAP_LE_ERR_ENCRYPTION:
while ((chan = l2cap_lookup_ident(conn, ident))) {
/* Cancel RTX work */
k_delayed_work_cancel(&chan->chan.rtx_work);
/* If security needs changing wait it to be completed */
if (!l2cap_change_security(chan, result)) {
return;
}
bt_l2cap_chan_remove(conn, &chan->chan);
bt_l2cap_chan_del(&chan->chan);
}
break;
case BT_L2CAP_LE_SUCCESS:
/* Some connections refused invalid Source CID */
case BT_L2CAP_LE_ERR_INVALID_SCID:
/* Some connections refused Source CID already allocated */
case BT_L2CAP_LE_ERR_SCID_IN_USE:
/* Some connections refused not enough resources available */
case BT_L2CAP_LE_ERR_NO_RESOURCES:
while ((chan = l2cap_lookup_ident(conn, ident))) {
struct bt_l2cap_chan *c;
/* Cancel RTX work */
k_delayed_work_cancel(&chan->chan.rtx_work);
dcid = net_buf_pull_le16(buf);
BT_DBG("dcid 0x%04x", dcid);
/* If a Destination CID is 0x0000, the channel was not
* established.
*/
if (!dcid) {
bt_l2cap_chan_remove(conn, &chan->chan);
bt_l2cap_chan_del(&chan->chan);
continue;
}
c = bt_l2cap_le_lookup_tx_cid(conn, dcid);
if (c) {
/* If a device receives a
* L2CAP_CREDIT_BASED_CONNECTION_RSP packet
* with an already assigned Destination CID,
* then both the original channel and the new
* channel shall be immediately discarded and
* not used.
*/
bt_l2cap_chan_remove(conn, &chan->chan);
bt_l2cap_chan_del(&chan->chan);
bt_l2cap_chan_disconnect(c);
continue;
}
chan->tx.cid = dcid;
chan->chan.ident = 0U;
chan->tx.mtu = mtu;
chan->tx.mps = mps;
/* Update state */
bt_l2cap_chan_set_state(&chan->chan,
BT_L2CAP_CONNECTED);
if (chan->chan.ops->connected) {
chan->chan.ops->connected(&chan->chan);
}
/* Give credits */
l2cap_chan_tx_give_credits(chan, credits);
l2cap_chan_rx_give_credits(chan, chan->rx.init_credits);
}
break;
case BT_L2CAP_LE_ERR_PSM_NOT_SUPP:
default:
while ((chan = l2cap_remove_ident(conn, ident))) {
bt_l2cap_chan_del(&chan->chan);
}
break;
}
}
static void le_conn_rsp(struct bt_l2cap *l2cap, u8_t ident,
struct net_buf *buf)
{
@ -1501,6 +1873,15 @@ static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
case BT_L2CAP_CMD_REJECT:
reject_cmd(l2cap, hdr->ident, buf);
break;
case BT_L2CAP_ECRED_CONN_REQ:
le_ecred_conn_req(l2cap, hdr->ident, buf);
break;
case BT_L2CAP_ECRED_CONN_RSP:
le_ecred_conn_rsp(l2cap, hdr->ident, buf);
break;
case BT_L2CAP_ECRED_RECONF_REQ:
le_ecred_reconf_req(l2cap, hdr->ident, buf);
break;
#else
case BT_L2CAP_CMD_REJECT:
/* Ignored */
@ -1945,6 +2326,67 @@ static int l2cap_le_connect(struct bt_conn *conn, struct bt_l2cap_le_chan *ch,
return l2cap_le_conn_req(ch);
}
static int l2cap_ecred_init(struct bt_conn *conn,
struct bt_l2cap_le_chan *ch, u16_t psm)
{
if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) {
return -EINVAL;
}
l2cap_chan_tx_init(ch);
l2cap_chan_rx_init(ch);
if (!l2cap_chan_add(conn, &ch->chan, l2cap_chan_destroy)) {
return -ENOMEM;
}
ch->chan.psm = psm;
BT_DBG("ch %p psm 0x%02x mtu %u mps %u credits %u", ch, ch->chan.psm,
ch->rx.mtu, ch->rx.mps, ch->rx.init_credits);
return 0;
}
int bt_l2cap_ecred_chan_connect(struct bt_conn *conn,
struct bt_l2cap_chan **chan, u16_t psm)
{
int i, err;
BT_DBG("conn %p chan %p psm 0x%04x", conn, chan, psm);
if (!conn || !chan) {
return -EINVAL;
}
/* Init non-null channels */
for (i = 0; i < L2CAP_ECRED_CHAN_MAX; i++) {
if (!chan[i]) {
break;
}
err = l2cap_ecred_init(conn, BT_L2CAP_LE_CHAN(chan[i]), psm);
if (err < 0) {
i--;
goto fail;
}
}
return l2cap_ecred_conn_req(chan, i);
fail:
/* Remove channels added */
for (; i >= 0; i--) {
if (!chan[i]) {
continue;
}
bt_l2cap_chan_remove(conn, chan[i]);
}
return err;
}
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan,
u16_t psm)
{
@ -2005,7 +2447,7 @@ int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
req->dcid = sys_cpu_to_le16(ch->rx.cid);
req->scid = sys_cpu_to_le16(ch->tx.cid);
l2cap_chan_send_req(ch, buf, L2CAP_DISC_TIMEOUT);
l2cap_chan_send_req(chan, buf, L2CAP_DISC_TIMEOUT);
bt_l2cap_chan_set_state(chan, BT_L2CAP_DISCONNECT);
return 0;