Commit 6f2b8f8a authored by Lyle Zhu's avatar Lyle Zhu Committed by Anas Nashif
Browse files

Bluetooth: Classic: L2CAP: implement ECHO REQ/RSP



Handle the ECHO request/response of classic L2CAP signaling packets.

Add the functions `bt_l2cap_br_echo_cb_register()` and
`bt_l2cap_br_echo_cb_unregister()` to register/unregister the ECHO
callbacks to monitor the ECHO REQ and RSP.

Add the function `bt_l2cap_br_echo_req()` to send the ECHO REQ through
classic L2CAP signaling channel.

Add the function `bt_l2cap_br_echo_rsp()` to reply the ECHO REQ
through the classic L2CAP signaling channel.

Signed-off-by: default avatarLyle Zhu <lyle.zhu@nxp.com>
parent 02f2beab
Loading
Loading
Loading
Loading
+144 −0
Original line number Diff line number Diff line
/** @file
 *  @brief Bluetooth L2CAP BR/EDR handling
 */

/*
 * Copyright 2025 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_L2CAP_BR_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_L2CAP_BR_H_

/**
 * @brief L2CAP
 * @defgroup bt_l2cap L2CAP
 * @ingroup bluetooth
 * @{
 */

#include <stddef.h>
#include <stdint.h>

#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/kernel.h>
#include <zephyr/net_buf.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/util.h>
#include <sys/types.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief ECHO request/response callback structure.
 *
 * This structure is used for tracking the ECHO request/response signaling packets of L2CAP BR.
 * It is registered with the help of the bt_l2cap_br_echo_cb_register() API.
 * It's permissible to register multiple instances of this @ref bt_l2cap_br_echo_cb type, in case
 * different modules of an application are interested in tracking the ECHO request/response
 * signaling packets. If a callback is not of interest for an instance, it may be set to NULL and
 * will as a consequence not be used for that instance.
 */
struct bt_l2cap_br_echo_cb {
	/**
	 * @brief A ECHO request has been received.
	 *
	 * This callback notifies the application of a ECHO request has been received.
	 * The ECHO response should be performed by calling the bt_l2cap_br_echo_rsp() API.
	 *
	 * @param conn The ACL connection object.
	 * @param identifier The identifier of the ECHO request.
	 * @param buf Received ECHO data.
	 */
	void (*req)(struct bt_conn *conn, uint8_t identifier, struct net_buf *buf);

	/**
	 * @brief A ECHO response has been received.
	 *
	 * This callback notifies the application of a ECHO response has been received.
	 *
	 * @param conn The ACL connection object.
	 * @param buf Received ECHO data.
	 */
	void (*rsp)(struct bt_conn *conn, struct net_buf *buf);

	/** @internal Internally used field for list handling */
	sys_snode_t _node;
};

/**
 * @brief Register ECHO callbacks.
 *
 * Register callbacks to monitor the packets of L2CAP BR echo request/response.
 *
 * @param cb Callback struct. Must point to memory that remains valid.
 *
 * @retval 0 Success.
 * @retval -EEXIST if @p cb was already registered.
 */
int bt_l2cap_br_echo_cb_register(struct bt_l2cap_br_echo_cb *cb);

/**
 * @brief Unregister ECHO callbacks.
 *
 * Unregister callbacks that are used to monitor the packets of L2CAP BR echo request/response.
 *
 * @param cb Callback struct point to memory that remains valid.
 *
 * @retval 0 Success.
 * @retval -EINVAL If @p cb is NULL.
 * @retval -ENOENT if @p cb was not registered.
 */
int bt_l2cap_br_echo_cb_unregister(struct bt_l2cap_br_echo_cb *cb);

/**
 *  @brief Headroom needed for outgoing L2CAP ECHO REQ PDUs.
 */
#define BT_L2CAP_BR_ECHO_REQ_RESERVE BT_L2CAP_BUF_SIZE(4)

/**
 *  @brief Headroom needed for outgoing L2CAP ECHO RSP PDUs.
 */
#define BT_L2CAP_BR_ECHO_RSP_RESERVE BT_L2CAP_BUF_SIZE(4)

/**
 * @brief Send ECHO data through ECHO request
 *
 * Send ECHO data through ECHO request. The application is required to have reserved
 * @ref BT_L2CAP_BR_ECHO_REQ_RESERVE bytes in the buffer before sending.
 *
 * @param conn The ACL connection object.
 * @param buf Sending ECHO data.
 *
 * @return 0 in case of success or negative value in case of error.
 */
int bt_l2cap_br_echo_req(struct bt_conn *conn, struct net_buf *buf);

/**
 * @brief Send ECHO data through ECHO response
 *
 * Send ECHO data through ECHO response. The application is required to have reserved
 * @ref BT_L2CAP_BR_ECHO_RSP_RESERVE bytes in the buffer before sending.
 *
 * @param conn The ACL connection object.
 * @param identifier The identifier of the ECHO request.
 * @param buf Sending ECHO data.
 *
 * @return 0 in case of success or negative value in case of error.
 */
int bt_l2cap_br_echo_rsp(struct bt_conn *conn, uint8_t identifier, struct net_buf *buf);

#ifdef __cplusplus
}
#endif

/**
 * @}
 */

#endif /* ZEPHYR_INCLUDE_BLUETOOTH_L2CAP_BR_H_ */
+177 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
#include <errno.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/crc.h>
@@ -18,6 +19,7 @@
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/classic/l2cap_br.h>

#include "host/buf_view.h"
#include "host/hci_core.h"
@@ -75,6 +77,7 @@ LOG_MODULE_REGISTER(bt_l2cap_br, CONFIG_BT_L2CAP_LOG_LEVEL);
#define L2CAP_BR_CFG_TIMEOUT     K_SECONDS(4)
#define L2CAP_BR_DISCONN_TIMEOUT K_SECONDS(1)
#define L2CAP_BR_CONN_TIMEOUT    K_SECONDS(40)
#define L2CAP_BR_ECHO_TIMEOUT    K_SECONDS(30)

#define L2CAP_FEAT_FC_MASK           BIT(0)
#define L2CAP_FEAT_RET_MASK          BIT(1)
@@ -228,6 +231,8 @@ struct bt_l2cap_br {

static struct bt_l2cap_br bt_l2cap_br_pool[CONFIG_BT_MAX_CONN];

static sys_slist_t bt_l2cap_br_echo_cbs = SYS_SLIST_STATIC_INIT(&bt_l2cap_br_echo_cbs);

struct bt_l2cap_chan *bt_l2cap_br_lookup_rx_cid(struct bt_conn *conn,
						uint16_t cid)
{
@@ -343,6 +348,7 @@ static void l2cap_br_rtx_timeout(struct k_work *work)
	if (chan->rx.cid == BT_L2CAP_CID_BR_SIG) {
		LOG_DBG("Skip BR/EDR signalling channel ");
		atomic_clear_bit(chan->flags, L2CAP_FLAG_SIG_INFO_PENDING);
		chan->ident = 0;
		return;
	}

@@ -4521,6 +4527,7 @@ static void l2cap_br_disconnected(struct bt_l2cap_chan *chan)
		 * so this should always succeed.
		 */
		(void)k_work_cancel_delayable(&br_chan->rtx_work);
		br_chan->ident = 0;
	}
}

@@ -4820,6 +4827,40 @@ static void l2cap_br_reject_rsp(struct bt_l2cap_br *l2cap, uint8_t ident, struct
	} while (chan != NULL);
}

static void l2cap_br_echo_req(struct bt_l2cap_br *l2cap, uint8_t ident, struct net_buf *buf)
{
	struct bt_conn *conn = l2cap->chan.chan.conn;
	struct bt_l2cap_br_echo_cb *callback;

	SYS_SLIST_FOR_EACH_CONTAINER(&bt_l2cap_br_echo_cbs, callback, _node) {
		if (callback->req) {
			callback->req(conn, ident, buf);
		}
	}
}

static void l2cap_br_echo_rsp(struct bt_l2cap_br *l2cap, uint8_t ident, struct net_buf *buf)
{
	struct bt_conn *conn = l2cap->chan.chan.conn;
	struct bt_l2cap_br_echo_cb *callback;

	if (ident != l2cap->chan.ident) {
		LOG_WRN("ident mismatch (%u != %u)!", l2cap->chan.ident, ident);
		goto failed;
	}

	SYS_SLIST_FOR_EACH_CONTAINER(&bt_l2cap_br_echo_cbs, callback, _node) {
		if (callback->rsp) {
			callback->rsp(conn, buf);
		}
	}

failed:
	l2cap->chan.ident = 0;
	/* Release RTX work since got the response */
	k_work_cancel_delayable(&l2cap->chan.rtx_work);
}

static void l2cap_br_sig_handle(struct bt_l2cap_br *l2cap, struct bt_l2cap_sig_hdr *hdr,
				struct net_buf *buf)
{
@@ -4858,6 +4899,12 @@ static void l2cap_br_sig_handle(struct bt_l2cap_br *l2cap, struct bt_l2cap_sig_h
	case BT_L2CAP_CMD_REJECT:
		l2cap_br_reject_rsp(l2cap, hdr->ident, buf);
		break;
	case BT_L2CAP_ECHO_REQ:
		l2cap_br_echo_req(l2cap, hdr->ident, buf);
		break;
	case BT_L2CAP_ECHO_RSP:
		l2cap_br_echo_rsp(l2cap, hdr->ident, buf);
		break;
	default:
		LOG_WRN("Unknown/Unsupported L2CAP PDU code 0x%02x", hdr->code);
		l2cap_br_send_reject(l2cap->chan.chan.conn, hdr->ident, BT_L2CAP_REJ_NOT_UNDERSTOOD,
@@ -6003,3 +6050,133 @@ void bt_l2cap_br_init(void)
		bt_avrcp_init();
	}
}

int bt_l2cap_br_echo_cb_register(struct bt_l2cap_br_echo_cb *cb)
{
	CHECKIF(cb == NULL) {
		return -EINVAL;
	}

	if (sys_slist_find(&bt_l2cap_br_echo_cbs, &cb->_node, NULL)) {
		return -EEXIST;
	}

	sys_slist_append(&bt_l2cap_br_echo_cbs, &cb->_node);

	return 0;
}

int bt_l2cap_br_echo_cb_unregister(struct bt_l2cap_br_echo_cb *cb)
{
	CHECKIF(cb == NULL) {
		return -EINVAL;
	}

	if (!sys_slist_find_and_remove(&bt_l2cap_br_echo_cbs, &cb->_node)) {
		return -ENOENT;
	}

	return 0;
}

int bt_l2cap_br_echo_req(struct bt_conn *conn, struct net_buf *buf)
{
	struct bt_l2cap_chan *chan;
	struct bt_l2cap_sig_hdr *hdr;
	int err;

	if ((conn == NULL) || (buf == NULL)) {
		return -EINVAL;
	}

	LOG_DBG("ACL conn %p buf %p len %u", conn, buf, buf->len);

	if (buf->ref != 1) {
		LOG_WRN("Expecting 1 ref, got %d", buf->ref);
		return -EINVAL;
	}

	if (buf->len >= (L2CAP_BR_MIN_MTU - sizeof(*hdr))) {
		LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, L2CAP_BR_MIN_MTU);
		return -EMSGSIZE;
	}

	if (net_buf_headroom(buf) < BT_L2CAP_BR_ECHO_REQ_RESERVE) {
		/* Call `net_buf_reserve(buf, BT_L2CAP_BR_ECHO_REQ_RESERVE)`
		 * when allocating buffers intended for bt_l2cap_br_echo_req().
		 */
		LOG_ERR("Not enough headroom in buf %p", buf);
		return -EINVAL;
	}

	chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG);
	if (chan == NULL) {
		LOG_ERR("Signaling Channel %u not found", BT_L2CAP_CID_BR_SIG);
		return -ENOTCONN;
	}

	if (BR_CHAN(chan)->ident) {
		LOG_ERR("Waiting for ECHO RSP");
		return -EBUSY;
	}

	hdr = net_buf_push(buf, sizeof(*hdr));

	hdr->code = BT_L2CAP_ECHO_REQ;
	hdr->ident = l2cap_br_get_ident();
	hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));

	/* Set the ident for the signaling request */
	BR_CHAN(chan)->ident = hdr->ident;

	err = bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, buf, NULL, NULL);
	if (err == 0) {
		k_work_reschedule(&BR_CHAN(chan)->rtx_work, L2CAP_BR_ECHO_TIMEOUT);
	}

	return err;
}

int bt_l2cap_br_echo_rsp(struct bt_conn *conn, uint8_t identifier, struct net_buf *buf)
{
	struct bt_l2cap_chan *chan;
	struct bt_l2cap_sig_hdr *hdr;

	if ((conn == NULL) || (buf == NULL) || (identifier == 0)) {
		return -EINVAL;
	}

	LOG_DBG("ACL conn %p buf %p len %u", conn, buf, buf->len);

	if (buf->ref != 1) {
		LOG_WRN("Expecting 1 ref, got %d", buf->ref);
		return -EINVAL;
	}

	if (buf->len >= (L2CAP_BR_MIN_MTU - sizeof(*hdr))) {
		LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, L2CAP_BR_MIN_MTU);
		return -EMSGSIZE;
	}

	if (net_buf_headroom(buf) < BT_L2CAP_BR_ECHO_RSP_RESERVE) {
		/* Call `net_buf_reserve(buf, BT_L2CAP_BR_ECHO_RSP_RESERVE)`
		 * when allocating buffers intended for bt_l2cap_br_echo_rsp().
		 */
		LOG_ERR("Not enough headroom in buf %p", buf);
		return -EINVAL;
	}

	chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG);
	if (chan == NULL) {
		LOG_ERR("Signaling Channel %u not found", BT_L2CAP_CID_BR_SIG);
		return -ENOTCONN;
	}

	hdr = net_buf_push(buf, sizeof(*hdr));

	hdr->code = BT_L2CAP_ECHO_RSP;
	hdr->ident = identifier;
	hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr));

	return bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, buf, NULL, NULL);
}
+10 −0
Original line number Diff line number Diff line
@@ -174,6 +174,16 @@ struct bt_l2cap_disconn_rsp {
	uint16_t scid;
} __packed;

#define BT_L2CAP_ECHO_REQ               0x08
struct bt_l2cap_echo_req {
	uint8_t data[0];
} __packed;

#define BT_L2CAP_ECHO_RSP               0x09
struct bt_l2cap_echo_rsp {
	uint8_t data[0];
} __packed;

#define BT_L2CAP_INFO_FEAT_MASK         0x0002
#define BT_L2CAP_INFO_FIXED_CHAN        0x0003