Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2020 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <sys/printk.h>
#include <sys/byteorder.h>
#include <zephyr.h>
#include <init.h>

#include <net/buf.h>

#include "ots_l2cap_internal.h"

#include <logging/log.h>

LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);

/* 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),
			  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,
			  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 %u", chan, *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 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);

	SYS_SLIST_FOR_EACH_CONTAINER(&channels, l2cap_ctx, node) {
		if (l2cap_ctx->ot_chan.chan.conn) {
			continue;
		}

		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;
}

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);