2015-05-06 10:24:52 +03:00
|
|
|
/* l2cap.c - L2CAP handling */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2015 Intel Corporation
|
|
|
|
*
|
2015-10-06 11:00:37 -05:00
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
2015-05-06 10:24:52 +03:00
|
|
|
*
|
2015-10-06 11:00:37 -05:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2015-05-06 10:24:52 +03:00
|
|
|
*
|
2015-10-06 11:00:37 -05:00
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
2015-05-06 10:24:52 +03:00
|
|
|
*/
|
|
|
|
|
2015-04-28 10:43:14 +03:00
|
|
|
#include <nanokernel.h>
|
2015-06-03 10:35:58 -04:00
|
|
|
#include <arch/cpu.h>
|
2015-04-28 10:43:14 +03:00
|
|
|
#include <toolchain.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
2015-09-15 10:08:04 +02:00
|
|
|
#include <atomic.h>
|
2015-04-28 10:43:14 +03:00
|
|
|
#include <misc/byteorder.h>
|
2015-09-30 14:49:25 +03:00
|
|
|
#include <misc/util.h>
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-06-16 17:25:37 +03:00
|
|
|
#include <bluetooth/log.h>
|
2015-04-28 10:43:14 +03:00
|
|
|
#include <bluetooth/hci.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
2016-01-14 15:02:54 +02:00
|
|
|
#include <bluetooth/conn.h>
|
2015-09-15 10:08:04 +02:00
|
|
|
#include <bluetooth/driver.h>
|
2015-04-28 10:43:14 +03:00
|
|
|
|
|
|
|
#include "hci_core.h"
|
2015-06-15 11:05:35 +03:00
|
|
|
#include "conn_internal.h"
|
2015-10-05 13:53:03 +03:00
|
|
|
#include "l2cap_internal.h"
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-05-13 14:06:34 +03:00
|
|
|
#if !defined(CONFIG_BLUETOOTH_DEBUG_L2CAP)
|
|
|
|
#undef BT_DBG
|
|
|
|
#define BT_DBG(fmt, ...)
|
|
|
|
#endif
|
|
|
|
|
2015-10-29 11:14:33 +02:00
|
|
|
#define L2CAP_LE_MIN_MTU 23
|
|
|
|
#define L2CAP_LE_MAX_CREDITS (CONFIG_BLUETOOTH_ACL_IN_COUNT - 1)
|
|
|
|
#define L2CAP_LE_CREDITS_THRESHOLD (L2CAP_LE_MAX_CREDITS / 2)
|
2015-09-30 14:49:25 +03:00
|
|
|
|
2015-11-02 09:02:51 +02:00
|
|
|
#define L2CAP_LE_DYN_CID_START 0x0040
|
|
|
|
#define L2CAP_LE_DYN_CID_END 0x007f
|
2015-09-30 14:49:25 +03:00
|
|
|
|
2015-11-09 16:20:16 +02:00
|
|
|
#define L2CAP_LE_PSM_START 0x0001
|
|
|
|
#define L2CAP_LE_PSM_END 0x00ff
|
|
|
|
|
2015-10-29 13:45:00 +02:00
|
|
|
/* Size of MTU is based on the maximum amount of data the buffer can hold
|
|
|
|
* excluding ACL and driver headers.
|
|
|
|
*/
|
2015-11-06 12:19:26 +02:00
|
|
|
#define BT_L2CAP_MAX_LE_MPS CONFIG_BLUETOOTH_L2CAP_IN_MTU
|
2015-10-30 14:12:44 +02:00
|
|
|
/* For now use MPS - SDU length to disable segmentation */
|
|
|
|
#define BT_L2CAP_MAX_LE_MTU (BT_L2CAP_MAX_LE_MPS - 2)
|
2015-10-29 13:45:00 +02:00
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
#define l2cap_lookup_ident(conn, ident) __l2cap_lookup_ident(conn, ident, false)
|
|
|
|
#define l2cap_remove_ident(conn, ident) __l2cap_lookup_ident(conn, ident, true)
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
static struct bt_l2cap_fixed_chan *channels;
|
2015-10-08 13:45:40 +03:00
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
2015-09-30 12:36:24 +03:00
|
|
|
static struct bt_l2cap_server *servers;
|
2015-10-08 13:45:40 +03:00
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|
2015-05-21 18:53:13 +03:00
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
/* Pool for outgoing LE signaling packets, MTU is 23 */
|
|
|
|
static struct nano_fifo le_sig;
|
2015-11-12 13:42:10 +02:00
|
|
|
static NET_BUF_POOL(le_sig_pool, CONFIG_BLUETOOTH_MAX_CONN,
|
|
|
|
BT_L2CAP_BUF_SIZE(L2CAP_LE_MIN_MTU), &le_sig, NULL, 0);
|
|
|
|
|
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
|
|
|
/* Pool for outgoing LE data packets, MTU is 23 */
|
|
|
|
static struct nano_fifo le_data;
|
|
|
|
static NET_BUF_POOL(le_data_pool, CONFIG_BLUETOOTH_MAX_CONN,
|
|
|
|
BT_L2CAP_BUF_SIZE(L2CAP_LE_MIN_MTU), &le_data, NULL, 0);
|
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|
2015-11-05 21:01:20 +02:00
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
/* L2CAP signalling channel specific context */
|
|
|
|
struct bt_l2cap {
|
|
|
|
/* The channel this context is associated with */
|
|
|
|
struct bt_l2cap_chan chan;
|
|
|
|
|
|
|
|
uint8_t ident;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct bt_l2cap bt_l2cap_pool[CONFIG_BLUETOOTH_MAX_CONN];
|
|
|
|
|
|
|
|
static struct bt_l2cap *l2cap_chan_get(struct bt_conn *conn)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
|
|
|
|
chan = bt_l2cap_lookup_rx_cid(conn, BT_L2CAP_CID_LE_SIG);
|
|
|
|
if (!chan) {
|
|
|
|
BT_ERR("Unable to find L2CAP Signalling channel");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CONTAINER_OF(chan, struct bt_l2cap, chan);
|
|
|
|
}
|
|
|
|
|
2015-04-28 10:43:14 +03:00
|
|
|
static uint8_t get_ident(struct bt_conn *conn)
|
|
|
|
{
|
2015-10-02 16:21:18 +03:00
|
|
|
|
|
|
|
struct bt_l2cap *l2cap;
|
|
|
|
|
|
|
|
l2cap = l2cap_chan_get(conn);
|
|
|
|
if (!l2cap) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap->ident++;
|
2015-04-28 10:43:14 +03:00
|
|
|
|
|
|
|
/* handle integer overflow (0 is not valid) */
|
2015-10-02 16:21:18 +03:00
|
|
|
if (!l2cap->ident) {
|
|
|
|
l2cap->ident++;
|
2015-05-05 10:50:14 +03:00
|
|
|
}
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
return l2cap->ident;
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
void bt_l2cap_fixed_chan_register(struct bt_l2cap_fixed_chan *chan)
|
2015-05-21 18:53:13 +03:00
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("CID 0x%04x", chan->cid);
|
2015-05-21 18:53:13 +03:00
|
|
|
|
|
|
|
chan->_next = channels;
|
|
|
|
channels = chan;
|
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
static void l2cap_chan_alloc_cid(struct bt_conn *conn,
|
|
|
|
struct bt_l2cap_chan *chan)
|
2015-05-21 19:03:17 +03:00
|
|
|
{
|
2015-10-02 16:21:18 +03:00
|
|
|
uint16_t cid;
|
2015-05-21 19:03:17 +03:00
|
|
|
|
2015-11-02 11:16:37 +02:00
|
|
|
/*
|
|
|
|
* No action needed if there's already a CID allocated, e.g. in
|
|
|
|
* the case of a fixed channel.
|
|
|
|
*/
|
2015-10-02 16:21:18 +03:00
|
|
|
if (chan->rx.cid > 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: Check conn type before assigning cid */
|
2015-11-02 09:04:00 +02:00
|
|
|
for (cid = L2CAP_LE_DYN_CID_START; cid <= L2CAP_LE_DYN_CID_END; cid++) {
|
2015-10-02 16:21:18 +03:00
|
|
|
if (!bt_l2cap_lookup_rx_cid(conn, cid)) {
|
|
|
|
chan->rx.cid = cid;
|
|
|
|
return;
|
2015-05-21 19:03:17 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
static int l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan)
|
2015-05-21 19:03:17 +03:00
|
|
|
{
|
2015-10-02 16:21:18 +03:00
|
|
|
l2cap_chan_alloc_cid(conn, chan);
|
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
if (!chan->rx.cid) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Unable to allocate L2CAP CID");
|
2015-11-04 14:46:56 +02:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
/* Attach channel to the connection */
|
|
|
|
chan->_next = conn->channels;
|
|
|
|
conn->channels = chan;
|
|
|
|
chan->conn = conn;
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("conn %p chan %p cid 0x%04x", conn, chan, chan->rx.cid);
|
2015-11-04 14:46:56 +02:00
|
|
|
|
|
|
|
return 0;
|
2015-10-02 16:21:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void bt_l2cap_connected(struct bt_conn *conn)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_fixed_chan *fchan;
|
2015-05-21 19:03:17 +03:00
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
|
2016-01-13 12:10:59 +01:00
|
|
|
/* TODO Add support for fixed channels on BR/EDR transport */
|
|
|
|
if (conn->type != BT_CONN_TYPE_LE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
for (fchan = channels; fchan; fchan = fchan->_next) {
|
|
|
|
if (fchan->accept(conn, &chan) < 0) {
|
|
|
|
continue;
|
2015-05-21 19:03:17 +03:00
|
|
|
}
|
2015-09-30 14:49:25 +03:00
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
chan->rx.cid = fchan->cid;
|
2015-11-02 11:18:06 +02:00
|
|
|
chan->tx.cid = fchan->cid;
|
2015-10-02 16:21:18 +03:00
|
|
|
|
|
|
|
l2cap_chan_add(conn, chan);
|
|
|
|
|
|
|
|
if (chan->ops->connected) {
|
|
|
|
chan->ops->connected(chan);
|
2015-09-30 14:49:25 +03:00
|
|
|
}
|
|
|
|
}
|
2015-05-21 19:03:17 +03:00
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
void bt_l2cap_disconnected(struct bt_conn *conn)
|
2015-05-21 19:03:17 +03:00
|
|
|
{
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
for (chan = conn->channels; chan;) {
|
|
|
|
struct bt_l2cap_chan *next;
|
|
|
|
|
|
|
|
/* prefetch since disconnected callback may cleanup */
|
|
|
|
next = chan->_next;
|
|
|
|
|
|
|
|
if (chan->ops->disconnected) {
|
|
|
|
chan->ops->disconnected(chan);
|
2015-05-21 19:03:17 +03:00
|
|
|
}
|
2015-10-02 16:21:18 +03:00
|
|
|
|
|
|
|
chan->conn = NULL;
|
|
|
|
chan = next;
|
2015-05-21 19:03:17 +03:00
|
|
|
}
|
2015-09-30 14:49:25 +03:00
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
conn->channels = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void bt_l2cap_encrypt_change(struct bt_conn *conn)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
|
|
|
|
for (chan = conn->channels; chan; chan = chan->_next) {
|
|
|
|
if (chan->ops->encrypt_change) {
|
|
|
|
chan->ops->encrypt_change(chan);
|
2015-09-30 14:49:25 +03:00
|
|
|
}
|
|
|
|
}
|
2015-05-21 19:03:17 +03:00
|
|
|
}
|
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
struct net_buf *bt_l2cap_create_pdu(struct nano_fifo *fifo)
|
2015-04-28 10:43:14 +03:00
|
|
|
{
|
2015-11-05 21:01:20 +02:00
|
|
|
return bt_conn_create_pdu(fifo, sizeof(struct bt_l2cap_hdr));
|
2015-05-20 14:58:18 +03:00
|
|
|
}
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
void bt_l2cap_send(struct bt_conn *conn, uint16_t cid, struct net_buf *buf)
|
2015-05-20 14:58:18 +03:00
|
|
|
{
|
|
|
|
struct bt_l2cap_hdr *hdr;
|
2015-05-06 10:59:43 +03:00
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
hdr = net_buf_push(buf, sizeof(*hdr));
|
2015-05-20 14:58:18 +03:00
|
|
|
hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));
|
2015-04-28 10:43:14 +03:00
|
|
|
hdr->cid = sys_cpu_to_le16(cid);
|
|
|
|
|
2015-05-20 14:58:18 +03:00
|
|
|
bt_conn_send(conn, buf);
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
2015-10-28 15:35:53 +02:00
|
|
|
static void l2cap_send_reject(struct bt_conn *conn, uint8_t ident,
|
|
|
|
uint16_t reason)
|
2015-04-28 10:43:14 +03:00
|
|
|
{
|
|
|
|
struct bt_l2cap_cmd_reject *rej;
|
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
2015-10-28 10:41:10 +02:00
|
|
|
struct net_buf *buf;
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
2015-05-05 10:50:14 +03:00
|
|
|
if (!buf) {
|
2015-04-28 10:43:14 +03:00
|
|
|
return;
|
2015-05-05 10:50:14 +03:00
|
|
|
}
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
2015-04-28 10:43:14 +03:00
|
|
|
hdr->code = BT_L2CAP_CMD_REJECT;
|
|
|
|
hdr->ident = ident;
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*rej));
|
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
rej = net_buf_add(buf, sizeof(*rej));
|
2015-10-28 15:35:53 +02:00
|
|
|
rej->reason = sys_cpu_to_le16(reason);
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-05-20 14:58:18 +03:00
|
|
|
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
static void le_conn_param_rsp(struct bt_l2cap *l2cap, struct net_buf *buf)
|
2015-04-28 10:43:14 +03:00
|
|
|
{
|
|
|
|
struct bt_l2cap_conn_param_rsp *rsp = (void *)buf->data;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*rsp)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small LE conn param rsp");
|
2015-04-28 10:43:14 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("LE conn param rsp result %u", sys_le16_to_cpu(rsp->result));
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
2015-09-07 18:25:59 +02:00
|
|
|
#if defined(CONFIG_BLUETOOTH_CENTRAL)
|
2015-10-02 16:21:18 +03:00
|
|
|
static void le_conn_param_update_req(struct bt_l2cap *l2cap, uint8_t ident,
|
2015-10-28 10:41:10 +02:00
|
|
|
struct net_buf *buf)
|
2015-06-12 14:47:54 +02:00
|
|
|
{
|
2015-10-02 16:21:18 +03:00
|
|
|
struct bt_conn *conn = l2cap->chan.conn;
|
2016-02-02 12:48:42 +01:00
|
|
|
const struct bt_le_conn_param *param;
|
2015-07-29 13:45:33 +03:00
|
|
|
uint16_t min, max, latency, timeout;
|
|
|
|
bool params_valid;
|
2015-06-12 14:47:54 +02:00
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
|
|
|
struct bt_l2cap_conn_param_rsp *rsp;
|
|
|
|
struct bt_l2cap_conn_param_req *req = (void *)buf->data;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small LE conn update param req");
|
2015-06-12 14:47:54 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conn->role != BT_HCI_ROLE_MASTER) {
|
2015-10-28 15:35:53 +02:00
|
|
|
l2cap_send_reject(conn, ident, BT_L2CAP_REJ_NOT_UNDERSTOOD);
|
2015-06-12 14:47:54 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
min = sys_le16_to_cpu(req->min_interval);
|
|
|
|
max = sys_le16_to_cpu(req->max_interval);
|
|
|
|
latency = sys_le16_to_cpu(req->latency);
|
|
|
|
timeout = sys_le16_to_cpu(req->timeout);
|
2016-02-02 12:48:42 +01:00
|
|
|
param = BT_LE_CONN_PARAM(min, max, latency, timeout);
|
2015-06-12 14:47:54 +02:00
|
|
|
|
|
|
|
BT_DBG("min 0x%4.4x max 0x%4.4x latency: 0x%4.4x timeout: 0x%4.4x",
|
|
|
|
min, max, latency, timeout);
|
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
2015-06-12 14:47:54 +02:00
|
|
|
if (!buf) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-29 13:45:33 +03:00
|
|
|
params_valid = bt_le_conn_params_valid(min, max, latency, timeout);
|
2015-06-12 14:47:54 +02:00
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
2015-06-12 14:47:54 +02:00
|
|
|
hdr->code = BT_L2CAP_CONN_PARAM_RSP;
|
|
|
|
hdr->ident = ident;
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*rsp));
|
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
rsp = net_buf_add(buf, sizeof(*rsp));
|
2015-07-29 13:45:33 +03:00
|
|
|
if (params_valid) {
|
|
|
|
rsp->result = sys_cpu_to_le16(BT_L2CAP_CONN_PARAM_ACCEPTED);
|
|
|
|
} else {
|
|
|
|
rsp->result = sys_cpu_to_le16(BT_L2CAP_CONN_PARAM_REJECTED);
|
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
bt_l2cap_send(l2cap->chan.conn, BT_L2CAP_CID_LE_SIG, buf);
|
2015-06-12 14:47:54 +02:00
|
|
|
|
2015-07-29 13:45:33 +03:00
|
|
|
if (params_valid) {
|
2016-02-02 12:48:42 +01:00
|
|
|
bt_conn_le_conn_update(conn, param);
|
2015-06-12 14:47:54 +02:00
|
|
|
}
|
|
|
|
}
|
2015-09-07 18:25:59 +02:00
|
|
|
#endif /* CONFIG_BLUETOOTH_CENTRAL */
|
2015-06-12 14:47:54 +02:00
|
|
|
|
2015-10-08 13:45:40 +03:00
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
|
|
|
static struct bt_l2cap_server *l2cap_server_lookup_psm(uint16_t psm)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_server *server;
|
|
|
|
|
|
|
|
for (server = servers; server; server = server->_next) {
|
|
|
|
if (server->psm == psm) {
|
|
|
|
return server;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int bt_l2cap_server_register(struct bt_l2cap_server *server)
|
|
|
|
{
|
2015-11-09 16:20:16 +02:00
|
|
|
if (server->psm < L2CAP_LE_PSM_START ||
|
|
|
|
server->psm > L2CAP_LE_PSM_END || !server->accept) {
|
2015-10-08 13:45:40 +03:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check if given PSM is already in use */
|
|
|
|
if (l2cap_server_lookup_psm(server->psm)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("PSM already registered");
|
2015-10-08 13:45:40 +03:00
|
|
|
return -EADDRINUSE;
|
|
|
|
}
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("PSM 0x%04x", server->psm);
|
2015-10-08 13:45:40 +03:00
|
|
|
|
|
|
|
server->_next = servers;
|
|
|
|
servers = server;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
static void l2cap_chan_rx_init(struct bt_l2cap_chan *chan)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p", chan);
|
2015-11-16 14:44:44 +02:00
|
|
|
|
|
|
|
/* Use existing MTU if defined */
|
|
|
|
if (!chan->rx.mtu) {
|
|
|
|
chan->rx.mtu = BT_L2CAP_MAX_LE_MTU;
|
|
|
|
}
|
|
|
|
|
|
|
|
chan->rx.mps = BT_L2CAP_MAX_LE_MPS;
|
|
|
|
nano_sem_init(&chan->rx.credits);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_chan_tx_init(struct bt_l2cap_chan *chan)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p", chan);
|
2015-11-16 14:44:44 +02:00
|
|
|
|
|
|
|
memset(&chan->tx, 0, sizeof(chan->tx));
|
|
|
|
nano_sem_init(&chan->tx.credits);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_chan_tx_give_credits(struct bt_l2cap_chan *chan,
|
|
|
|
uint16_t credits)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p credits %u", chan, credits);
|
2015-11-16 14:44:44 +02:00
|
|
|
|
|
|
|
while (credits--) {
|
|
|
|
nano_sem_give(&chan->tx.credits);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_chan_rx_give_credits(struct bt_l2cap_chan *chan,
|
|
|
|
uint16_t credits)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p credits %u", chan, credits);
|
2015-11-16 14:44:44 +02:00
|
|
|
|
|
|
|
while (credits--) {
|
|
|
|
nano_sem_give(&chan->rx.credits);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
static void le_conn_req(struct bt_l2cap *l2cap, uint8_t ident,
|
2015-10-28 10:41:10 +02:00
|
|
|
struct net_buf *buf)
|
2015-09-30 14:49:25 +03:00
|
|
|
{
|
2015-10-02 16:21:18 +03:00
|
|
|
struct bt_conn *conn = l2cap->chan.conn;
|
2015-09-30 14:49:25 +03:00
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
struct bt_l2cap_server *server;
|
|
|
|
struct bt_l2cap_le_conn_req *req = (void *)buf->data;
|
|
|
|
struct bt_l2cap_le_conn_rsp *rsp;
|
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
2015-10-02 16:21:18 +03:00
|
|
|
uint16_t psm, scid, mtu, mps, credits;
|
2015-09-30 14:49:25 +03:00
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small LE conn req packet size");
|
2015-09-30 14:49:25 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
psm = sys_le16_to_cpu(req->psm);
|
|
|
|
scid = sys_le16_to_cpu(req->scid);
|
|
|
|
mtu = sys_le16_to_cpu(req->mtu);
|
|
|
|
mps = sys_le16_to_cpu(req->mps);
|
2015-10-02 16:21:18 +03:00
|
|
|
credits = sys_le16_to_cpu(req->credits);
|
2015-09-30 14:49:25 +03:00
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("psm 0x%02x scid 0x%04x mtu %u mps %u credits %u", psm, scid,
|
2015-10-02 16:21:18 +03:00
|
|
|
mtu, mps, credits);
|
2015-09-30 14:49:25 +03:00
|
|
|
|
|
|
|
if (mtu < L2CAP_LE_MIN_MTU || mps < L2CAP_LE_MIN_MTU) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Invalid LE-Conn Req params");
|
2015-09-30 14:49:25 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
2015-09-30 14:49:25 +03:00
|
|
|
if (!buf) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
2015-09-30 14:49:25 +03:00
|
|
|
hdr->code = BT_L2CAP_LE_CONN_RSP;
|
|
|
|
hdr->ident = ident;
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*rsp));
|
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
rsp = net_buf_add(buf, sizeof(*rsp));
|
2015-09-30 14:49:25 +03:00
|
|
|
memset(rsp, 0, sizeof(*rsp));
|
|
|
|
|
|
|
|
/* Check if there is a server registered */
|
|
|
|
server = l2cap_server_lookup_psm(psm);
|
2016-02-12 17:42:26 +02:00
|
|
|
if (!server) {
|
2015-09-30 14:49:25 +03:00
|
|
|
rsp->result = BT_L2CAP_ERR_PSM_NOT_SUPP;
|
|
|
|
goto rsp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: Add security check */
|
|
|
|
|
2015-11-02 09:02:59 +02:00
|
|
|
if (scid < L2CAP_LE_DYN_CID_START || scid > L2CAP_LE_DYN_CID_END) {
|
|
|
|
rsp->result = BT_L2CAP_ERR_INVALID_SCID;
|
|
|
|
goto rsp;
|
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
chan = bt_l2cap_lookup_tx_cid(conn, scid);
|
2015-09-30 14:49:25 +03:00
|
|
|
if (chan) {
|
2015-11-02 08:44:55 +02:00
|
|
|
rsp->result = BT_L2CAP_ERR_SCID_IN_USE;
|
2015-09-30 14:49:25 +03:00
|
|
|
goto rsp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Request server to accept the new connection and allocate the
|
|
|
|
* channel.
|
|
|
|
*
|
|
|
|
* TODO: Handle different errors, it may be required to respond async.
|
|
|
|
*/
|
|
|
|
if (server->accept(conn, &chan) < 0) {
|
|
|
|
rsp->result = BT_L2CAP_ERR_NO_RESOURCES;
|
|
|
|
goto rsp;
|
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
/* Init TX parameters */
|
2015-11-16 14:44:44 +02:00
|
|
|
l2cap_chan_tx_init(chan);
|
2015-10-02 16:21:18 +03:00
|
|
|
chan->tx.cid = scid;
|
|
|
|
chan->tx.mps = mps;
|
|
|
|
chan->tx.mtu = mtu;
|
2015-11-16 14:44:44 +02:00
|
|
|
l2cap_chan_tx_give_credits(chan, credits);
|
2015-10-02 16:21:18 +03:00
|
|
|
|
|
|
|
/* Init RX parameters */
|
2015-11-16 14:44:44 +02:00
|
|
|
l2cap_chan_rx_init(chan);
|
|
|
|
l2cap_chan_rx_give_credits(chan, L2CAP_LE_MAX_CREDITS);
|
2015-09-30 14:49:25 +03:00
|
|
|
|
|
|
|
l2cap_chan_add(conn, chan);
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
if (chan->ops->connected) {
|
|
|
|
chan->ops->connected(chan);
|
2015-09-30 14:49:25 +03:00
|
|
|
}
|
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
rsp->dcid = sys_cpu_to_le16(chan->rx.cid);
|
|
|
|
rsp->mps = sys_cpu_to_le16(chan->rx.mps);
|
|
|
|
rsp->mtu = sys_cpu_to_le16(chan->rx.mtu);
|
2015-11-16 14:44:44 +02:00
|
|
|
rsp->credits = sys_cpu_to_le16(L2CAP_LE_MAX_CREDITS);
|
2015-09-30 14:49:25 +03:00
|
|
|
rsp->result = BT_L2CAP_SUCCESS;
|
|
|
|
|
|
|
|
rsp:
|
|
|
|
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
|
|
|
|
}
|
2015-10-28 15:35:53 +02:00
|
|
|
|
|
|
|
static struct bt_l2cap_chan *l2cap_remove_tx_cid(struct bt_conn *conn,
|
|
|
|
uint16_t cid)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_chan *chan, *prev;
|
|
|
|
|
|
|
|
for (chan = conn->channels, prev = NULL; chan;
|
|
|
|
prev = chan, chan = chan->_next) {
|
|
|
|
if (chan->tx.cid != cid) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!prev) {
|
|
|
|
conn->channels = chan->_next;
|
|
|
|
} else {
|
|
|
|
prev->_next = chan->_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
static void l2cap_chan_del(struct bt_l2cap_chan *chan)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("conn %p chan %p cid 0x%04x", chan->conn, chan, chan->rx.cid);
|
2015-11-04 14:46:56 +02:00
|
|
|
|
|
|
|
chan->conn = NULL;
|
|
|
|
|
|
|
|
if (chan->ops->disconnected) {
|
|
|
|
chan->ops->disconnected(chan);
|
|
|
|
}
|
2015-11-16 14:44:44 +02:00
|
|
|
|
|
|
|
/* There could be a writer waiting for credits so return a dummy credit
|
|
|
|
* to wake it up.
|
|
|
|
*/
|
|
|
|
if (!chan->tx.credits.nsig) {
|
|
|
|
l2cap_chan_tx_give_credits(chan, 1);
|
|
|
|
}
|
2015-11-16 18:18:45 +02:00
|
|
|
|
|
|
|
/* Destroy segmented SDU if it exists */
|
|
|
|
if (chan->_sdu) {
|
|
|
|
net_buf_unref(chan->_sdu);
|
|
|
|
chan->_sdu = NULL;
|
|
|
|
chan->_sdu_len = 0;
|
|
|
|
}
|
2015-11-04 14:46:56 +02:00
|
|
|
}
|
|
|
|
|
2015-10-28 15:35:53 +02:00
|
|
|
static void le_disconn_req(struct bt_l2cap *l2cap, uint8_t ident,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
|
|
|
struct bt_conn *conn = l2cap->chan.conn;
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
struct bt_l2cap_disconn_req *req = (void *)buf->data;
|
|
|
|
struct bt_l2cap_disconn_rsp *rsp;
|
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
|
|
|
uint16_t scid, dcid;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*req)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small LE conn req packet size");
|
2015-10-28 15:35:53 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dcid = sys_le16_to_cpu(req->dcid);
|
|
|
|
scid = sys_le16_to_cpu(req->scid);
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("scid 0x%04x dcid 0x%04x", dcid, scid);
|
2015-10-28 15:35:53 +02:00
|
|
|
|
|
|
|
chan = l2cap_remove_tx_cid(conn, scid);
|
|
|
|
if (!chan) {
|
|
|
|
l2cap_send_reject(conn, ident, BT_L2CAP_REJ_INVALID_CID);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
2015-10-28 15:35:53 +02:00
|
|
|
if (!buf) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
|
|
|
hdr->code = BT_L2CAP_DISCONN_RSP;
|
|
|
|
hdr->ident = ident;
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*rsp));
|
|
|
|
|
|
|
|
rsp = net_buf_add(buf, sizeof(*rsp));
|
|
|
|
rsp->dcid = sys_cpu_to_le16(chan->rx.cid);
|
|
|
|
rsp->scid = sys_cpu_to_le16(chan->tx.cid);
|
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
l2cap_chan_del(chan);
|
2015-10-28 15:35:53 +02:00
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct bt_l2cap_chan *__l2cap_lookup_ident(struct bt_conn *conn,
|
|
|
|
uint16_t ident, bool remove)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_chan *chan, *prev;
|
|
|
|
|
|
|
|
for (chan = conn->channels, prev = NULL; chan;
|
|
|
|
prev = chan, chan = chan->_next) {
|
|
|
|
if (chan->_ident != ident) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!remove) {
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!prev) {
|
|
|
|
conn->channels = chan->_next;
|
|
|
|
} else {
|
|
|
|
prev->_next = chan->_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return chan;
|
2015-10-28 15:35:53 +02:00
|
|
|
}
|
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void le_conn_rsp(struct bt_l2cap *l2cap, uint8_t ident,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
|
|
|
struct bt_conn *conn = l2cap->chan.conn;
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
struct bt_l2cap_le_conn_rsp *rsp = (void *)buf->data;
|
|
|
|
uint16_t dcid, mtu, mps, credits, result;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*rsp)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small LE conn rsp packet size");
|
2015-11-04 14:46:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dcid = sys_le16_to_cpu(rsp->dcid);
|
|
|
|
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);
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("dcid 0x%04x mtu %u mps %u credits %u result 0x%04x", dcid,
|
2015-11-04 14:46:56 +02:00
|
|
|
mtu, mps, credits, result);
|
|
|
|
|
|
|
|
if (result == BT_L2CAP_SUCCESS) {
|
|
|
|
chan = l2cap_lookup_ident(conn, ident);
|
|
|
|
} else {
|
|
|
|
chan = l2cap_remove_ident(conn, ident);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chan) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Cannot find channel for ident %u", ident);
|
2015-11-04 14:46:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
case BT_L2CAP_SUCCESS:
|
|
|
|
/* Reset _ident since it is no longer pending */
|
|
|
|
chan->_ident = 0;
|
|
|
|
chan->tx.cid = dcid;
|
|
|
|
chan->tx.mtu = mtu;
|
|
|
|
chan->tx.mps = mps;
|
|
|
|
|
|
|
|
if (chan->ops->connected) {
|
|
|
|
chan->ops->connected(chan);
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
/* Give credits */
|
|
|
|
l2cap_chan_tx_give_credits(chan, credits);
|
|
|
|
l2cap_chan_rx_give_credits(chan, L2CAP_LE_MAX_CREDITS);
|
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
break;
|
|
|
|
/* TODO: Retry on Authentication and Encryption errors */
|
|
|
|
default:
|
|
|
|
l2cap_chan_del(chan);
|
|
|
|
}
|
2015-10-28 15:35:53 +02:00
|
|
|
}
|
2015-11-06 13:12:44 +02:00
|
|
|
|
|
|
|
static void le_disconn_rsp(struct bt_l2cap *l2cap, uint8_t ident,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
|
|
|
struct bt_conn *conn = l2cap->chan.conn;
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
struct bt_l2cap_disconn_rsp *rsp = (void *)buf->data;
|
|
|
|
uint16_t dcid, scid;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*rsp)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small LE disconn rsp packet size");
|
2015-11-06 13:12:44 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dcid = sys_le16_to_cpu(rsp->dcid);
|
|
|
|
scid = sys_le16_to_cpu(rsp->scid);
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("dcid 0x%04x scid 0x%04x", dcid, scid);
|
2015-11-06 13:12:44 +02:00
|
|
|
|
2016-01-28 17:06:36 +01:00
|
|
|
chan = l2cap_remove_tx_cid(conn, dcid);
|
2015-11-06 13:12:44 +02:00
|
|
|
if (!chan) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap_chan_del(chan);
|
|
|
|
}
|
2015-11-12 12:15:56 +02:00
|
|
|
|
|
|
|
static void le_credits(struct bt_l2cap *l2cap, uint8_t ident,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
|
|
|
struct bt_conn *conn = l2cap->chan.conn;
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
struct bt_l2cap_le_credits *ev = (void *)buf->data;
|
|
|
|
uint16_t credits, cid;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*ev)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small LE Credits packet size");
|
2015-11-12 12:15:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cid = sys_le16_to_cpu(ev->cid);
|
|
|
|
credits = sys_le16_to_cpu(ev->credits);
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("cid 0x%04x credits %u", cid, credits);
|
2015-11-12 12:15:56 +02:00
|
|
|
|
|
|
|
chan = bt_l2cap_lookup_tx_cid(conn, cid);
|
|
|
|
if (!chan) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Unable to find channel of LE Credits packet");
|
2015-11-12 12:15:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
if (chan->tx.credits.nsig + credits > UINT16_MAX) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Credits overflow");
|
2015-11-12 12:15:56 +02:00
|
|
|
bt_l2cap_chan_disconnect(chan);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
l2cap_chan_tx_give_credits(chan, credits);
|
2015-11-12 12:15:56 +02:00
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p total credits %u", chan, chan->tx.credits.nsig);
|
2015-11-12 12:15:56 +02:00
|
|
|
}
|
2016-03-01 13:06:31 +02:00
|
|
|
|
|
|
|
static void reject_cmd(struct bt_l2cap *l2cap, uint8_t ident,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
|
|
|
struct bt_conn *conn = l2cap->chan.conn;
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
|
|
|
|
/* Check if there is a outstanding channel */
|
|
|
|
chan = l2cap_remove_ident(conn, ident);
|
|
|
|
if (!chan) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap_chan_del(chan);
|
|
|
|
}
|
2015-10-08 13:45:40 +03:00
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|
2015-09-30 14:49:25 +03:00
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
static void l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
2015-04-28 10:43:14 +03:00
|
|
|
{
|
2015-10-02 16:21:18 +03:00
|
|
|
struct bt_l2cap *l2cap = CONTAINER_OF(chan, struct bt_l2cap, chan);
|
2015-04-28 10:43:14 +03:00
|
|
|
struct bt_l2cap_sig_hdr *hdr = (void *)buf->data;
|
|
|
|
uint16_t len;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*hdr)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small L2CAP LE signaling PDU");
|
2015-11-02 16:11:15 +02:00
|
|
|
return;
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
len = sys_le16_to_cpu(hdr->len);
|
2015-10-28 10:41:10 +02:00
|
|
|
net_buf_pull(buf, sizeof(*hdr));
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("LE signaling code 0x%02x ident %u len %u", hdr->code,
|
2015-04-28 10:43:14 +03:00
|
|
|
hdr->ident, len);
|
|
|
|
|
|
|
|
if (buf->len != len) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("L2CAP length mismatch (%u != %u)", buf->len, len);
|
2015-11-02 16:11:15 +02:00
|
|
|
return;
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!hdr->ident) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Invalid ident value in L2CAP PDU");
|
2015-11-02 16:11:15 +02:00
|
|
|
return;
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (hdr->code) {
|
|
|
|
case BT_L2CAP_CONN_PARAM_RSP:
|
2015-10-02 16:21:18 +03:00
|
|
|
le_conn_param_rsp(l2cap, buf);
|
2015-04-28 10:43:14 +03:00
|
|
|
break;
|
2015-09-07 18:25:59 +02:00
|
|
|
#if defined(CONFIG_BLUETOOTH_CENTRAL)
|
2015-06-12 14:47:54 +02:00
|
|
|
case BT_L2CAP_CONN_PARAM_REQ:
|
2015-10-02 16:21:18 +03:00
|
|
|
le_conn_param_update_req(l2cap, hdr->ident, buf);
|
2015-06-12 14:47:54 +02:00
|
|
|
break;
|
2015-09-07 18:25:59 +02:00
|
|
|
#endif /* CONFIG_BLUETOOTH_CENTRAL */
|
2015-10-08 13:45:40 +03:00
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
2015-09-30 14:49:25 +03:00
|
|
|
case BT_L2CAP_LE_CONN_REQ:
|
2015-10-02 16:21:18 +03:00
|
|
|
le_conn_req(l2cap, hdr->ident, buf);
|
2015-09-30 14:49:25 +03:00
|
|
|
break;
|
2015-11-04 14:46:56 +02:00
|
|
|
case BT_L2CAP_LE_CONN_RSP:
|
|
|
|
le_conn_rsp(l2cap, hdr->ident, buf);
|
|
|
|
break;
|
2015-10-28 15:35:53 +02:00
|
|
|
case BT_L2CAP_DISCONN_REQ:
|
|
|
|
le_disconn_req(l2cap, hdr->ident, buf);
|
|
|
|
break;
|
2015-11-06 13:12:44 +02:00
|
|
|
case BT_L2CAP_DISCONN_RSP:
|
|
|
|
le_disconn_rsp(l2cap, hdr->ident, buf);
|
|
|
|
break;
|
2015-11-12 12:15:56 +02:00
|
|
|
case BT_L2CAP_LE_CREDITS:
|
|
|
|
le_credits(l2cap, hdr->ident, buf);
|
|
|
|
break;
|
2016-03-01 13:06:31 +02:00
|
|
|
case BT_L2CAP_CMD_REJECT:
|
|
|
|
reject_cmd(l2cap, hdr->ident, buf);
|
|
|
|
break;
|
|
|
|
#else
|
|
|
|
case BT_L2CAP_CMD_REJECT:
|
|
|
|
/* Ignored */
|
|
|
|
break;
|
2015-10-08 13:45:40 +03:00
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|
2015-04-28 10:43:14 +03:00
|
|
|
default:
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_WARN("Unknown L2CAP PDU code 0x%02x", hdr->code);
|
2015-10-28 15:35:53 +02:00
|
|
|
l2cap_send_reject(chan->conn, hdr->ident,
|
|
|
|
BT_L2CAP_REJ_NOT_UNDERSTOOD);
|
2015-04-28 10:43:14 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-03 15:53:37 +02:00
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
2015-10-29 11:14:33 +02:00
|
|
|
static void l2cap_chan_update_credits(struct bt_l2cap_chan *chan)
|
|
|
|
{
|
|
|
|
struct net_buf *buf;
|
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
|
|
|
struct bt_l2cap_le_credits *ev;
|
|
|
|
uint16_t credits;
|
|
|
|
|
|
|
|
/* Only give more credits if it went bellow the defined threshold */
|
2015-11-16 14:44:44 +02:00
|
|
|
if (chan->rx.credits.nsig > L2CAP_LE_CREDITS_THRESHOLD) {
|
2015-10-29 11:14:33 +02:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Restore credits */
|
2015-11-16 14:44:44 +02:00
|
|
|
credits = L2CAP_LE_MAX_CREDITS - chan->rx.credits.nsig;
|
|
|
|
l2cap_chan_rx_give_credits(chan, credits);
|
2015-10-29 11:14:33 +02:00
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
2015-10-29 11:14:33 +02:00
|
|
|
if (!buf) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Unable to send credits");
|
2015-10-29 11:14:33 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
|
|
|
hdr->code = BT_L2CAP_LE_CREDITS;
|
|
|
|
hdr->ident = get_ident(chan->conn);
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*ev));
|
|
|
|
|
|
|
|
ev = net_buf_add(buf, sizeof(*ev));
|
2015-11-12 12:12:54 +02:00
|
|
|
ev->cid = sys_cpu_to_le16(chan->rx.cid);
|
2015-10-29 11:14:33 +02:00
|
|
|
ev->credits = sys_cpu_to_le16(credits);
|
|
|
|
|
|
|
|
bt_l2cap_send(chan->conn, BT_L2CAP_CID_LE_SIG, buf);
|
|
|
|
|
|
|
|
done:
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p credits %u", chan, chan->rx.credits.nsig);
|
2015-10-29 11:14:33 +02:00
|
|
|
}
|
|
|
|
|
2015-11-16 18:18:45 +02:00
|
|
|
static void l2cap_chan_le_recv_sdu(struct bt_l2cap_chan *chan,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p len %u sdu len %u", chan, buf->len, chan->_sdu->len);
|
2015-11-16 18:18:45 +02:00
|
|
|
|
|
|
|
if (chan->_sdu->len + buf->len > chan->_sdu_len) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("SDU length mismatch");
|
2015-11-16 18:18:45 +02:00
|
|
|
bt_l2cap_chan_disconnect(chan);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(net_buf_add(chan->_sdu, buf->len), buf->data, buf->len);
|
|
|
|
|
|
|
|
if (chan->_sdu->len == chan->_sdu_len) {
|
|
|
|
/* Receiving complete SDU, notify channel and reset SDU buf */
|
|
|
|
chan->ops->recv(chan, chan->_sdu);
|
|
|
|
net_buf_unref(chan->_sdu);
|
|
|
|
chan->_sdu = NULL;
|
|
|
|
chan->_sdu_len = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap_chan_update_credits(chan);
|
|
|
|
}
|
|
|
|
|
2015-10-29 11:14:33 +02:00
|
|
|
static void l2cap_chan_le_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
|
|
{
|
|
|
|
uint16_t sdu_len;
|
|
|
|
|
2015-12-18 15:36:34 -05:00
|
|
|
if (!nano_fiber_sem_take(&chan->rx.credits, TICKS_NONE)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("No credits to receive packet");
|
2015-11-06 17:29:38 +02:00
|
|
|
bt_l2cap_chan_disconnect(chan);
|
2015-10-29 11:14:33 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-16 18:18:45 +02:00
|
|
|
/* Check if segments already exist */
|
|
|
|
if (chan->_sdu) {
|
|
|
|
l2cap_chan_le_recv_sdu(chan, buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-29 11:14:33 +02:00
|
|
|
sdu_len = net_buf_pull_le16(buf);
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p len %u sdu_len %u", chan, buf->len, sdu_len);
|
2015-10-29 11:14:33 +02:00
|
|
|
|
|
|
|
if (sdu_len > chan->rx.mtu) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Invalid SDU length");
|
2015-11-06 17:29:38 +02:00
|
|
|
bt_l2cap_chan_disconnect(chan);
|
2015-10-29 11:14:33 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-26 13:25:06 +02:00
|
|
|
/* Always allocate buffer from the channel if supported. */
|
|
|
|
if (chan->ops->alloc_buf) {
|
2015-11-16 18:18:45 +02:00
|
|
|
chan->_sdu = chan->ops->alloc_buf(chan);
|
2015-11-26 13:14:58 +02:00
|
|
|
if (!chan->_sdu) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Unable to allocate buffer for SDU");
|
2015-11-16 18:18:45 +02:00
|
|
|
bt_l2cap_chan_disconnect(chan);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
chan->_sdu_len = sdu_len;
|
|
|
|
l2cap_chan_le_recv_sdu(chan, buf);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-29 11:14:33 +02:00
|
|
|
chan->ops->recv(chan, buf);
|
|
|
|
|
|
|
|
l2cap_chan_update_credits(chan);
|
|
|
|
}
|
2015-11-03 15:53:37 +02:00
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|
2015-10-29 11:14:33 +02:00
|
|
|
|
|
|
|
static void l2cap_chan_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
|
|
{
|
2015-11-03 15:53:37 +02:00
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
2015-10-29 11:14:33 +02:00
|
|
|
/* TODO: Check the conn type to differentiate BR/EDR and LE or
|
|
|
|
* introduce a mode.
|
|
|
|
*/
|
|
|
|
if (chan->rx.cid >= L2CAP_LE_DYN_CID_START &&
|
|
|
|
chan->rx.cid <= L2CAP_LE_DYN_CID_END) {
|
|
|
|
l2cap_chan_le_recv(chan, buf);
|
|
|
|
return;
|
|
|
|
}
|
2015-11-03 15:53:37 +02:00
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|
2015-10-29 11:14:33 +02:00
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p len %u", chan, buf->len);
|
2015-10-29 11:14:33 +02:00
|
|
|
|
|
|
|
chan->ops->recv(chan, buf);
|
|
|
|
}
|
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
void bt_l2cap_recv(struct bt_conn *conn, struct net_buf *buf)
|
2015-04-28 10:43:14 +03:00
|
|
|
{
|
|
|
|
struct bt_l2cap_hdr *hdr = (void *)buf->data;
|
2015-05-21 18:53:13 +03:00
|
|
|
struct bt_l2cap_chan *chan;
|
2015-04-28 10:43:14 +03:00
|
|
|
uint16_t cid;
|
|
|
|
|
|
|
|
if (buf->len < sizeof(*hdr)) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Too small L2CAP PDU received");
|
2015-10-28 10:41:10 +02:00
|
|
|
net_buf_unref(buf);
|
2015-04-28 10:43:14 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cid = sys_le16_to_cpu(hdr->cid);
|
2015-10-28 10:41:10 +02:00
|
|
|
net_buf_pull(buf, sizeof(*hdr));
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("Packet for CID %u len %u", cid, buf->len);
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
chan = bt_l2cap_lookup_rx_cid(conn, cid);
|
2015-05-21 18:53:13 +03:00
|
|
|
if (!chan) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_WARN("Ignoring data for unknown CID 0x%04x", cid);
|
2015-10-28 10:41:10 +02:00
|
|
|
net_buf_unref(buf);
|
2015-10-02 16:21:18 +03:00
|
|
|
return;
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
2015-05-21 18:53:13 +03:00
|
|
|
|
2015-10-29 11:14:33 +02:00
|
|
|
l2cap_chan_recv(chan, buf);
|
2015-11-02 16:11:15 +02:00
|
|
|
|
|
|
|
net_buf_unref(buf);
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
|
|
|
|
2016-02-02 12:48:42 +01:00
|
|
|
int bt_l2cap_update_conn_param(struct bt_conn *conn,
|
|
|
|
const struct bt_le_conn_param *param)
|
2015-04-28 10:43:14 +03:00
|
|
|
{
|
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
|
|
|
struct bt_l2cap_conn_param_req *req;
|
2015-10-28 10:41:10 +02:00
|
|
|
struct net_buf *buf;
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-11-05 21:01:20 +02:00
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
2015-05-05 10:50:14 +03:00
|
|
|
if (!buf) {
|
2015-08-23 20:17:11 +02:00
|
|
|
return -ENOBUFS;
|
2015-05-05 10:50:14 +03:00
|
|
|
}
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
2015-04-28 10:43:14 +03:00
|
|
|
hdr->code = BT_L2CAP_CONN_PARAM_REQ;
|
|
|
|
hdr->ident = get_ident(conn);
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*req));
|
|
|
|
|
2015-10-28 10:41:10 +02:00
|
|
|
req = net_buf_add(buf, sizeof(*req));
|
2016-02-02 12:48:42 +01:00
|
|
|
req->min_interval = sys_cpu_to_le16(param->interval_min);
|
|
|
|
req->max_interval = sys_cpu_to_le16(param->interval_max);
|
|
|
|
req->latency = sys_cpu_to_le16(param->latency);
|
|
|
|
req->timeout = sys_cpu_to_le16(param->timeout);
|
2015-04-28 10:43:14 +03:00
|
|
|
|
2015-05-20 14:58:18 +03:00
|
|
|
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
|
2015-08-23 20:17:11 +02:00
|
|
|
|
|
|
|
return 0;
|
2015-04-28 10:43:14 +03:00
|
|
|
}
|
2015-05-21 18:53:13 +03:00
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
static void l2cap_connected(struct bt_l2cap_chan *chan)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p cid 0x%04x", chan, chan->rx.cid);
|
2015-10-02 16:21:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void l2cap_disconnected(struct bt_l2cap_chan *chan)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p cid 0x%04x", chan, chan->rx.cid);
|
2015-10-02 16:21:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
static struct bt_l2cap_chan_ops ops = {
|
|
|
|
.connected = l2cap_connected,
|
|
|
|
.disconnected = l2cap_disconnected,
|
|
|
|
.recv = l2cap_recv,
|
|
|
|
};
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("conn %p handle %u", conn, conn->handle);
|
2015-10-02 16:21:18 +03:00
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_l2cap_pool); i++) {
|
|
|
|
struct bt_l2cap *l2cap = &bt_l2cap_pool[i];
|
|
|
|
|
|
|
|
if (l2cap->chan.conn) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
l2cap->chan.ops = &ops;
|
|
|
|
|
|
|
|
*chan = &l2cap->chan;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("No available L2CAP context for conn %p", conn);
|
2015-10-02 16:21:18 +03:00
|
|
|
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2015-11-03 17:15:40 +02:00
|
|
|
void bt_l2cap_init(void)
|
2015-05-21 18:53:13 +03:00
|
|
|
{
|
2015-10-02 16:21:18 +03:00
|
|
|
static struct bt_l2cap_fixed_chan chan = {
|
2015-05-21 18:53:13 +03:00
|
|
|
.cid = BT_L2CAP_CID_LE_SIG,
|
2015-10-02 16:21:18 +03:00
|
|
|
.accept = l2cap_accept,
|
2015-05-21 18:53:13 +03:00
|
|
|
};
|
|
|
|
|
2015-11-12 13:42:10 +02:00
|
|
|
net_buf_pool_init(le_sig_pool);
|
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
|
|
|
net_buf_pool_init(le_data_pool);
|
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|
2015-11-05 21:01:20 +02:00
|
|
|
|
2015-10-02 16:21:18 +03:00
|
|
|
bt_l2cap_fixed_chan_register(&chan);
|
2015-05-21 18:53:13 +03:00
|
|
|
}
|
2015-10-02 16:21:18 +03:00
|
|
|
|
|
|
|
struct bt_l2cap_chan *bt_l2cap_lookup_tx_cid(struct bt_conn *conn,
|
|
|
|
uint16_t cid)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
|
|
|
|
for (chan = conn->channels; chan; chan = chan->_next) {
|
|
|
|
if (chan->tx.cid == cid)
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct bt_l2cap_chan *bt_l2cap_lookup_rx_cid(struct bt_conn *conn,
|
|
|
|
uint16_t cid)
|
|
|
|
{
|
|
|
|
struct bt_l2cap_chan *chan;
|
|
|
|
|
|
|
|
for (chan = conn->channels; chan; chan = chan->_next) {
|
|
|
|
if (chan->rx.cid == cid) {
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2015-11-04 14:46:56 +02:00
|
|
|
|
|
|
|
#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL)
|
|
|
|
static int l2cap_le_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan,
|
|
|
|
uint16_t psm)
|
|
|
|
{
|
|
|
|
struct net_buf *buf;
|
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
|
|
|
struct bt_l2cap_le_conn_req *req;
|
|
|
|
|
|
|
|
if (psm < L2CAP_LE_PSM_START || psm > L2CAP_LE_PSM_END) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
l2cap_chan_tx_init(chan);
|
|
|
|
l2cap_chan_rx_init(chan);
|
|
|
|
|
2015-11-04 14:46:56 +02:00
|
|
|
if (l2cap_chan_add(conn, chan) < 0) {
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
|
|
|
if (!buf) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Unable to send L2CP connection request");
|
2015-11-04 14:46:56 +02:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
chan->_ident = get_ident(chan->conn);
|
|
|
|
|
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
|
|
|
hdr->code = BT_L2CAP_LE_CONN_REQ;
|
|
|
|
hdr->ident = chan->_ident;
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*req));
|
|
|
|
|
|
|
|
req = net_buf_add(buf, sizeof(*req));
|
|
|
|
req->psm = sys_cpu_to_le16(psm);
|
|
|
|
req->scid = sys_cpu_to_le16(chan->rx.cid);
|
|
|
|
req->mtu = sys_cpu_to_le16(chan->rx.mtu);
|
|
|
|
req->mps = sys_cpu_to_le16(chan->rx.mps);
|
|
|
|
req->credits = sys_cpu_to_le16(L2CAP_LE_MAX_CREDITS);
|
|
|
|
|
|
|
|
bt_l2cap_send(chan->conn, BT_L2CAP_CID_LE_SIG, buf);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan,
|
|
|
|
uint16_t psm)
|
|
|
|
{
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("conn %p chan %p psm 0x%04x", conn, chan, psm);
|
2015-11-04 14:46:56 +02:00
|
|
|
|
|
|
|
if (!conn || conn->state != BT_CONN_CONNECTED) {
|
|
|
|
return -ENOTCONN;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chan) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: Check conn/address type when BR/EDR is introduced */
|
|
|
|
return l2cap_le_connect(conn, chan, psm);
|
|
|
|
}
|
2015-11-06 13:12:44 +02:00
|
|
|
|
|
|
|
int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
|
|
|
|
{
|
|
|
|
struct bt_conn *conn = chan->conn;
|
|
|
|
struct net_buf *buf;
|
|
|
|
struct bt_l2cap_disconn_req *req;
|
|
|
|
struct bt_l2cap_sig_hdr *hdr;
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p scid 0x%04x dcid 0x%04x", chan, chan->rx.cid,
|
2015-11-06 13:12:44 +02:00
|
|
|
chan->tx.cid);
|
|
|
|
|
|
|
|
if (!conn) {
|
|
|
|
return -ENOTCONN;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = bt_l2cap_create_pdu(&le_sig);
|
|
|
|
if (!buf) {
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_ERR("Unable to send L2CP disconnect request");
|
2015-11-06 13:12:44 +02:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdr = net_buf_add(buf, sizeof(*hdr));
|
|
|
|
hdr->code = BT_L2CAP_DISCONN_REQ;
|
|
|
|
hdr->ident = get_ident(conn);
|
|
|
|
hdr->len = sys_cpu_to_le16(sizeof(*req));
|
|
|
|
|
|
|
|
req = net_buf_add(buf, sizeof(*req));
|
|
|
|
req->dcid = sys_cpu_to_le16(chan->tx.cid);
|
|
|
|
req->scid = sys_cpu_to_le16(chan->rx.cid);
|
|
|
|
|
|
|
|
bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2015-11-12 13:42:10 +02:00
|
|
|
|
|
|
|
static struct net_buf *l2cap_chan_create_seg(struct bt_l2cap_chan *chan,
|
|
|
|
struct net_buf *buf,
|
2016-01-28 17:09:12 -05:00
|
|
|
size_t sdu_hdr_len)
|
2015-11-12 13:42:10 +02:00
|
|
|
{
|
|
|
|
struct net_buf *seg;
|
2016-01-28 17:09:12 -05:00
|
|
|
uint16_t headroom;
|
2016-01-28 21:28:14 +01:00
|
|
|
uint16_t len;
|
2015-11-12 13:42:10 +02:00
|
|
|
|
2016-01-28 17:09:12 -05:00
|
|
|
/* Segment if data (+ data headroom) is bigger than MPS */
|
|
|
|
if (buf->len + sdu_hdr_len > chan->tx.mps) {
|
2015-11-12 13:42:10 +02:00
|
|
|
goto segment;
|
|
|
|
}
|
|
|
|
|
2016-01-28 17:09:12 -05:00
|
|
|
headroom = sizeof(struct bt_hci_acl_hdr) +
|
|
|
|
sizeof(struct bt_l2cap_hdr) + sdu_hdr_len;
|
2015-11-12 13:42:10 +02:00
|
|
|
|
2016-01-28 17:09:12 -05:00
|
|
|
/* Check if original buffer has enough headroom */
|
2015-11-12 13:42:10 +02:00
|
|
|
if (net_buf_headroom(buf) >= headroom) {
|
2016-01-28 17:09:12 -05:00
|
|
|
if (sdu_hdr_len) {
|
2015-11-12 13:42:10 +02:00
|
|
|
/* Push SDU length if set */
|
2016-01-28 16:40:18 -05:00
|
|
|
net_buf_push_le16(buf, buf->len);
|
2015-11-12 13:42:10 +02:00
|
|
|
}
|
|
|
|
return net_buf_ref(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
segment:
|
|
|
|
seg = bt_l2cap_create_pdu(&le_data);
|
|
|
|
if (!seg) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-01-28 17:09:12 -05:00
|
|
|
if (sdu_hdr_len) {
|
2016-01-28 21:28:14 +01:00
|
|
|
net_buf_add_le16(seg, buf->len);
|
2015-11-12 13:42:10 +02:00
|
|
|
}
|
|
|
|
|
2016-01-28 17:09:12 -05:00
|
|
|
len = min(min(buf->len, L2CAP_LE_MIN_MTU - sdu_hdr_len), chan->tx.mps);
|
2015-11-12 13:42:10 +02:00
|
|
|
memcpy(net_buf_add(seg, len), buf->data, len);
|
|
|
|
net_buf_pull(buf, len);
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p seg %p len %u", chan, seg, seg->len);
|
2015-11-12 13:42:10 +02:00
|
|
|
|
|
|
|
return seg;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int l2cap_chan_le_send(struct bt_l2cap_chan *chan, struct net_buf *buf,
|
2016-01-28 17:09:12 -05:00
|
|
|
uint16_t sdu_hdr_len)
|
2015-11-12 13:42:10 +02:00
|
|
|
{
|
2016-01-28 21:28:14 +01:00
|
|
|
int len;
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
/* Wait for credits */
|
2015-12-18 15:36:34 -05:00
|
|
|
nano_sem_take(&chan->tx.credits, TICKS_UNLIMITED);
|
2015-11-12 13:42:10 +02:00
|
|
|
|
2016-01-28 17:09:12 -05:00
|
|
|
buf = l2cap_chan_create_seg(chan, buf, sdu_hdr_len);
|
2015-11-12 13:42:10 +02:00
|
|
|
if (!buf) {
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
/* Channel may have been disconnected while waiting for credits */
|
|
|
|
if (!chan->conn) {
|
2016-01-28 12:05:53 +01:00
|
|
|
net_buf_unref(buf);
|
2015-11-16 14:44:44 +02:00
|
|
|
return -ECONNRESET;
|
|
|
|
}
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p cid 0x%04x len %u credits %u", chan, chan->tx.cid,
|
2015-11-16 14:44:44 +02:00
|
|
|
buf->len, chan->tx.credits.nsig);
|
2015-11-12 13:42:10 +02:00
|
|
|
|
2015-11-27 14:56:24 +02:00
|
|
|
len = buf->len;
|
|
|
|
|
2015-11-12 13:42:10 +02:00
|
|
|
bt_l2cap_send(chan->conn, chan->tx.cid, buf);
|
|
|
|
|
2015-11-27 14:56:24 +02:00
|
|
|
return len;
|
2015-11-12 13:42:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int l2cap_chan_le_send_sdu(struct bt_l2cap_chan *chan,
|
|
|
|
struct net_buf *buf)
|
|
|
|
{
|
2016-01-28 16:23:58 +02:00
|
|
|
int ret, sent, total_len;
|
2015-11-12 13:42:10 +02:00
|
|
|
|
|
|
|
if (buf->len > chan->tx.mtu) {
|
|
|
|
return -EMSGSIZE;
|
|
|
|
}
|
|
|
|
|
2016-01-28 16:23:58 +02:00
|
|
|
total_len = buf->len;
|
|
|
|
|
2015-11-12 13:42:10 +02:00
|
|
|
/* Add SDU length for the first segment */
|
2016-01-28 17:09:12 -05:00
|
|
|
ret = l2cap_chan_le_send(chan, buf, BT_L2CAP_SDU_HDR_LEN);
|
2015-11-27 14:56:24 +02:00
|
|
|
if (ret < 0) {
|
|
|
|
return ret;
|
2015-11-12 13:42:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Send remaining segments */
|
2016-01-28 16:23:58 +02:00
|
|
|
for (sent = ret; sent < total_len; sent += ret) {
|
2016-01-28 17:09:12 -05:00
|
|
|
ret = l2cap_chan_le_send(chan, buf, 0);
|
2015-11-27 14:56:24 +02:00
|
|
|
if (ret < 0) {
|
|
|
|
return ret;
|
2015-11-12 13:42:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-16 13:45:22 +02:00
|
|
|
BT_DBG("chan %p cid 0x%04x sent %u", chan, chan->tx.cid, sent);
|
2015-11-27 14:56:24 +02:00
|
|
|
|
2015-11-12 13:42:10 +02:00
|
|
|
net_buf_unref(buf);
|
|
|
|
|
2016-01-28 16:23:58 +02:00
|
|
|
return sent;
|
2015-11-12 13:42:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
|
|
|
|
{
|
2016-02-17 16:37:26 +02:00
|
|
|
int err;
|
|
|
|
|
2015-11-25 14:26:48 +01:00
|
|
|
BT_DBG("chan %p buf %p len %u", chan, buf, buf->len);
|
2015-11-12 13:42:10 +02:00
|
|
|
|
|
|
|
if (!buf) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:44:44 +02:00
|
|
|
if (!chan->conn || chan->conn->state != BT_CONN_CONNECTED) {
|
|
|
|
return -ENOTCONN;
|
|
|
|
}
|
|
|
|
|
2015-11-12 13:42:10 +02:00
|
|
|
/* TODO: Check conn/address type when BR/EDR is introduced */
|
2016-02-17 16:37:26 +02:00
|
|
|
err = l2cap_chan_le_send_sdu(chan, buf);
|
|
|
|
if (err < 0) {
|
|
|
|
BT_ERR("failed to send message %d", err);
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
2015-11-12 13:42:10 +02:00
|
|
|
}
|
2015-11-04 14:46:56 +02:00
|
|
|
#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */
|