blob: ae8c89c92df2027531e48408e116a3c83e0dd6eb [file] [log] [blame]
/*
* Copyright (c) 2020 - 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/zephyr.h>
#include <zephyr/init.h>
#include <zephyr/net/buf.h>
#include "ots_l2cap_internal.h"
#include <zephyr/logging/log.h>
/* This l2cap is the only OTS-file in use for OTC.
* If only OTC is used, the OTS log module must be registered here.
*/
#if IS_ENABLED(CONFIG_BT_OTS)
LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
#elif IS_ENABLED(CONFIG_BT_OTS_CLIENT)
LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
#endif
/* According to BLE specification Assigned Numbers that are used in the
* Logical Link Control for protocol/service multiplexers.
*/
#define BT_GATT_OTS_L2CAP_PSM 0x0025
NET_BUF_POOL_FIXED_DEFINE(ot_chan_tx_pool, 1,
BT_L2CAP_SDU_BUF_SIZE(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU), 8,
NULL);
#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU)
NET_BUF_POOL_FIXED_DEFINE(ot_chan_rx_pool, 1, CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU, 8,
NULL);
#endif
/* List of Object Transfer Channels. */
static sys_slist_t channels;
static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
{
int ret;
struct net_buf *buf;
uint32_t len;
/* Calculate maximum length of data chunk. */
len = MIN(l2cap_ctx->ot_chan.tx.mtu, CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU);
len = MIN(len, l2cap_ctx->tx.len - l2cap_ctx->tx.len_sent);
/* Prepare buffer for sending. */
buf = net_buf_alloc(&ot_chan_tx_pool, K_FOREVER);
net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, &l2cap_ctx->tx.data[l2cap_ctx->tx.len_sent], len);
ret = bt_l2cap_chan_send(&l2cap_ctx->ot_chan.chan, buf);
if (ret < 0) {
LOG_ERR("Unable to send data over CoC: %d", ret);
net_buf_unref(buf);
return -ENOEXEC;
}
/* Mark that L2CAP TX was accepted. */
l2cap_ctx->tx.len_sent += len;
LOG_DBG("Sending TX chunk with %d bytes on L2CAP CoC", len);
return 0;
}
#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU)
static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
{
LOG_DBG("Channel %p allocating buffer", chan);
return net_buf_alloc(&ot_chan_rx_pool, K_FOREVER);
}
#endif
static void l2cap_sent(struct bt_l2cap_chan *chan)
{
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Outgoing data channel %p transmitted", chan);
l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan);
/* Ongoing TX - sending next chunk. */
if (l2cap_ctx->tx.len != l2cap_ctx->tx.len_sent) {
ots_l2cap_send(l2cap_ctx);
return;
}
/* TX completed - notify upper layers and clean up. */
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
LOG_DBG("Scheduled TX on L2CAP CoC is complete");
if (l2cap_ctx->tx_done) {
l2cap_ctx->tx_done(l2cap_ctx, chan->conn);
}
}
static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Incoming data channel %p received", chan);
l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan);
if (!l2cap_ctx->rx_done) {
return -ENODEV;
}
return l2cap_ctx->rx_done(l2cap_ctx, chan->conn, buf);
}
static void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status)
{
LOG_DBG("Channel %p status %lu", chan, atomic_get(status));
}
static void l2cap_connected(struct bt_l2cap_chan *chan)
{
LOG_DBG("Channel %p connected", chan);
}
static void l2cap_disconnected(struct bt_l2cap_chan *chan)
{
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Channel %p disconnected", chan);
l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan);
if (l2cap_ctx->closed) {
l2cap_ctx->closed(l2cap_ctx, chan->conn);
}
}
static const struct bt_l2cap_chan_ops l2cap_ops = {
#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU)
.alloc_buf = l2cap_alloc_buf,
#endif
.sent = l2cap_sent,
.recv = l2cap_recv,
.status = l2cap_status,
.connected = l2cap_connected,
.disconnected = l2cap_disconnected,
};
static inline void l2cap_chan_init(struct bt_l2cap_le_chan *chan)
{
chan->rx.mtu = CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU;
chan->chan.ops = &l2cap_ops;
LOG_DBG("RX MTU set to %u", chan->rx.mtu);
}
static struct bt_gatt_ots_l2cap *find_free_l2cap_ctx(void)
{
struct bt_gatt_ots_l2cap *l2cap_ctx;
SYS_SLIST_FOR_EACH_CONTAINER(&channels, l2cap_ctx, node) {
if (l2cap_ctx->ot_chan.chan.conn) {
continue;
}
return l2cap_ctx;
}
return NULL;
}
static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
{
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Incoming conn %p", (void *)conn);
l2cap_ctx = find_free_l2cap_ctx();
if (l2cap_ctx) {
l2cap_chan_init(&l2cap_ctx->ot_chan);
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
*chan = &l2cap_ctx->ot_chan.chan;
return 0;
}
return -ENOMEM;
}
static struct bt_l2cap_server l2cap_server = {
.psm = BT_GATT_OTS_L2CAP_PSM,
.accept = l2cap_accept,
};
static int bt_gatt_ots_l2cap_init(const struct device *arg)
{
int err;
sys_slist_init(&channels);
err = bt_l2cap_server_register(&l2cap_server);
if (err) {
LOG_ERR("Unable to register OTS PSM");
return err;
}
LOG_DBG("Initialized OTS L2CAP");
return 0;
}
bool bt_gatt_ots_l2cap_is_open(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn)
{
return (l2cap_ctx->ot_chan.chan.conn == conn);
}
int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx,
uint8_t *data, uint32_t len)
{
int err;
if (l2cap_ctx->tx.len != 0) {
LOG_ERR("L2CAP TX in progress");
return -EAGAIN;
}
l2cap_ctx->tx.data = data;
l2cap_ctx->tx.len = len;
LOG_DBG("Starting TX on L2CAP CoC with %d byte packet", len);
err = ots_l2cap_send(l2cap_ctx);
if (err) {
LOG_ERR("Unable to send data over CoC: %d", err);
return err;
}
return 0;
}
int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx)
{
sys_slist_append(&channels, &l2cap_ctx->node);
return 0;
}
int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx)
{
sys_slist_find_and_remove(&channels, &l2cap_ctx->node);
return 0;
}
/* Similar to l2cap_accept(), but for the client side */
int bt_gatt_ots_l2cap_connect(struct bt_conn *conn,
struct bt_gatt_ots_l2cap **l2cap_ctx)
{
int err;
struct bt_gatt_ots_l2cap *ctx;
if (!conn) {
LOG_WRN("Invalid Connection");
return -ENOTCONN;
}
if (!l2cap_ctx) {
LOG_WRN("Invalid context");
return -EINVAL;
}
*l2cap_ctx = NULL;
ctx = find_free_l2cap_ctx();
if (!ctx) {
return -ENOMEM;
}
l2cap_chan_init(&ctx->ot_chan);
(void)memset(&ctx->tx, 0, sizeof(ctx->tx));
LOG_DBG("Connecting L2CAP CoC");
err = bt_l2cap_chan_connect(conn, &ctx->ot_chan.chan, BT_GATT_OTS_L2CAP_PSM);
if (err) {
LOG_WRN("Unable to connect to psm %u (err %d)", BT_GATT_OTS_L2CAP_PSM, err);
} else {
LOG_DBG("L2CAP connection pending");
*l2cap_ctx = ctx;
}
return err;
}
int bt_gatt_ots_l2cap_disconnect(struct bt_gatt_ots_l2cap *l2cap_ctx)
{
return bt_l2cap_chan_disconnect(&l2cap_ctx->ot_chan.chan);
}
SYS_INIT(bt_gatt_ots_l2cap_init, APPLICATION,
CONFIG_APPLICATION_INIT_PRIORITY);