diff --git a/include/zephyr/bluetooth/classic/avrcp.h b/include/zephyr/bluetooth/classic/avrcp.h new file mode 100644 index 00000000000000..f00157eab8049d --- /dev/null +++ b/include/zephyr/bluetooth/classic/avrcp.h @@ -0,0 +1,88 @@ +/** @file + * @brief Audio Video Remote Control Profile header. + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AVRCP_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AVRCP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief AVRCP structure */ +struct bt_avrcp; + +struct bt_avrcp_cb { + /** @brief An AVRCP connection has been established. + * + * This callback notifies the application of an avrcp connection, + * i.e., an AVCTP L2CAP connection. + * + * @param avrcp AVRCP connection object. + */ + void (*connected)(struct bt_avrcp *avrcp); + /** @brief An AVRCP connection has been disconnected. + * + * This callback notifies the application that an avrcp connection + * has been disconnected. + * + * @param avrcp AVRCP connection object. + */ + void (*disconnected)(struct bt_avrcp *avrcp); +}; + +/** @brief Connect AVRCP. + * + * This function is to be called after the conn parameter is obtained by + * performing a GAP procedure. The API is to be used to establish AVRCP + * connection between devices. + * + * @param conn Pointer to bt_conn structure. + * + * @return pointer to struct bt_avrcp in case of success or NULL in case + * of error. + */ +struct bt_avrcp *bt_avrcp_connect(struct bt_conn *conn); + +/** @brief Disconnect AVRCP. + * + * This function close AVCTP L2CAP connection. + * + * @param avrcp The AVRCP instance. + * + * @return 0 in case of success or error code in case of error. + */ +int bt_avrcp_disconnect(struct bt_avrcp *avrcp); + +/** @brief Register callback. + * + * Register AVRCP callbacks to monitor the state and interact with the remote device. + * + * @param cb The callback function. + * + * @return 0 in case of success or error code in case of error. + */ +int bt_avrcp_register_cb(struct bt_avrcp_cb *cb); + +/** @brief Disconnect AVRCP Unit Info. + * + * This function obtains information that pertains to the unit as a whole. + * + * @param avrcp The AVRCP instance. + * + * @return 0 in case of success or error code in case of error. + */ +int bt_avrcp_get_unit_info(struct bt_avrcp *avrcp); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AVRCP_H_ */ diff --git a/include/zephyr/bluetooth/uuid.h b/include/zephyr/bluetooth/uuid.h index df1af452a3d21f..aa0413dc13a3f6 100644 --- a/include/zephyr/bluetooth/uuid.h +++ b/include/zephyr/bluetooth/uuid.h @@ -5195,6 +5195,8 @@ struct bt_uuid_128 { #define BT_UUID_HCRP_NOTE BT_UUID_DECLARE_16(BT_UUID_HCRP_NOTE_VAL) #define BT_UUID_AVCTP_VAL 0x0017 #define BT_UUID_AVCTP BT_UUID_DECLARE_16(BT_UUID_AVCTP_VAL) +#define BT_UUID_AVCTP_BROWSING_VAL 0x0018 +#define BT_UUID_AVCTP_BROWSING BT_UUID_DECLARE_16(BT_UUID_AVCTP_BROWSING_VAL) #define BT_UUID_AVDTP_VAL 0x0019 #define BT_UUID_AVDTP BT_UUID_DECLARE_16(BT_UUID_AVDTP_VAL) #define BT_UUID_CMTP_VAL 0x001b diff --git a/subsys/bluetooth/Kconfig.logging b/subsys/bluetooth/Kconfig.logging index d1a20362f7f6c3..7ccaf55c2d6be5 100644 --- a/subsys/bluetooth/Kconfig.logging +++ b/subsys/bluetooth/Kconfig.logging @@ -387,6 +387,14 @@ module = BT_A2DP module-str = "Bluetooth A2DP" source "subsys/logging/Kconfig.template.log_config_inherit" +module = BT_AVCTP +module-str = "Bluetooth AVCTP" +source "subsys/logging/Kconfig.template.log_config_inherit" + +module = BT_AVRCP +module-str = "Bluetooth AVRCP" +source "subsys/logging/Kconfig.template.log_config_inherit" + module = BT_SDP module-str = "Bluetooth Service Discovery Protocol (SDP)" source "subsys/logging/Kconfig.template.log_config_inherit" diff --git a/subsys/bluetooth/host/classic/CMakeLists.txt b/subsys/bluetooth/host/classic/CMakeLists.txt index 2b70e3c6e64478..0f7f01d5c00b52 100644 --- a/subsys/bluetooth/host/classic/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/CMakeLists.txt @@ -7,6 +7,8 @@ zephyr_library_link_libraries(subsys__bluetooth) zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c a2dp_codec_sbc.c) zephyr_library_sources_ifdef(CONFIG_BT_AVDTP avdtp.c) +zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c) +zephyr_library_sources_ifdef(CONFIG_BT_AVCTP avctp.c) zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c) zephyr_library_sources_ifdef( diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 5eb08ba3e77a43..3df8f30e10462f 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -178,6 +178,32 @@ config BT_A2DP_SINK endif # BT_A2DP +config BT_AVCTP + bool "Bluetooth AVCTP protocol support" + help + This option enables Bluetooth AVCTP support + +config BT_AVRCP + bool "Bluetooth AVRCP Profile [EXPERIMENTAL]" + select BT_AVCTP + select EXPERIMENTAL + help + This option enables the AVRCP profile + +if BT_AVRCP + +config BT_AVRCP_TARGET + bool "Bluetooth AVRCP Profile Target Function" + help + This option enables the AVRCP profile target function + +config BT_AVRCP_CONTROLLER + bool "Bluetooth AVRCP Profile Controller Function" + help + This option enables the AVRCP profile controller function + +endif # BT_AVRCP + config BT_PAGE_TIMEOUT hex "Bluetooth Page Timeout" default 0x2000 diff --git a/subsys/bluetooth/host/classic/avctp.c b/subsys/bluetooth/host/classic/avctp.c new file mode 100644 index 00000000000000..09e6e58606b708 --- /dev/null +++ b/subsys/bluetooth/host/classic/avctp.c @@ -0,0 +1,221 @@ +/** @file + * @brief Audio Video Control Transport Protocol + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "avctp_internal.h" +#include "host/hci_core.h" +#include "host/conn_internal.h" +#include "l2cap_br_internal.h" + +#define LOG_LEVEL CONFIG_BT_AVCTP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_avctp); + +#define AVCTP_CHAN(_ch) CONTAINER_OF(_ch, struct bt_avctp, br_chan.chan) + +static struct bt_avctp_event_cb *event_cb; + +static void avctp_l2cap_connected(struct bt_l2cap_chan *chan) +{ + struct bt_avctp *session; + + if (!chan) { + LOG_ERR("Invalid AVCTP chan"); + return; + } + + session = AVCTP_CHAN(chan); + LOG_DBG("chan %p session %p", chan, session); + + if (session->ops && session->ops->connected) { + session->ops->connected(session); + } +} + +static void avctp_l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_avctp *session = AVCTP_CHAN(chan); + + LOG_DBG("chan %p session %p", chan, session); + session->br_chan.chan.conn = NULL; + + if (session->ops && session->ops->disconnected) { + session->ops->disconnected(session); + } +} + +static void avctp_l2cap_encrypt_changed(struct bt_l2cap_chan *chan, uint8_t status) +{ + LOG_DBG(""); +} + +static int avctp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct net_buf *rsp; + struct bt_avctp *session = AVCTP_CHAN(chan); + struct bt_avctp_header *hdr = (void *)buf->data; + uint8_t tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr); + bt_avctp_cr_t cr = BT_AVCTP_HDR_GET_CR(hdr); + + if (buf->len < sizeof(*hdr)) { + LOG_ERR("invalid AVCTP header received"); + return -EINVAL; + } + + switch (hdr->pid) { +#if defined(CONFIG_BT_AVRCP) + case sys_cpu_to_be16(BT_SDP_AV_REMOTE_SVCLASS): + break; +#endif + default: + LOG_ERR("unsupported AVCTP PID received: 0x%04x", sys_be16_to_cpu(hdr->pid)); + if (cr == BT_AVCTP_CMD) { + rsp = bt_avctp_create_pdu(session, BT_AVCTP_RESPONSE, + BT_AVCTP_PKT_TYPE_SINGLE, BT_AVCTP_IPID_INVALID, + &tid, hdr->pid); + if (!rsp) { + return -ENOMEM; + } + return bt_avctp_send(session, rsp); + } + return 0; /* No need to report to the upper layer */ + } + + return session->ops->recv(session, buf); +} + +int bt_avctp_connect(struct bt_conn *conn, struct bt_avctp *session) +{ + static const struct bt_l2cap_chan_ops ops = { + .connected = avctp_l2cap_connected, + .disconnected = avctp_l2cap_disconnected, + .encrypt_change = avctp_l2cap_encrypt_changed, + .recv = avctp_l2cap_recv, + }; + + if (!session) { + return -EINVAL; + } + + session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; + session->br_chan.chan.ops = &ops; + session->br_chan.required_sec_level = BT_SECURITY_L2; + + return bt_l2cap_chan_connect(conn, &session->br_chan.chan, BT_L2CAP_PSM_AVCTP); +} + +int bt_avctp_disconnect(struct bt_avctp *session) +{ + if (!session) { + return -EINVAL; + } + + LOG_DBG("session %p", session); + + return bt_l2cap_chan_disconnect(&session->br_chan.chan); +} + +struct net_buf *bt_avctp_create_pdu(struct bt_avctp *session, bt_avctp_cr_t cr, + bt_avctp_pkt_type_t pkt_type, bt_avctp_ipid_t ipid, + uint8_t *tid, uint16_t pid) +{ + struct net_buf *buf; + struct bt_avctp_header *hdr; + + LOG_DBG(""); + + buf = bt_l2cap_create_pdu(NULL, 0); + if (!buf) { + LOG_ERR("No buff available"); + return buf; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + BT_AVCTP_HDR_SET_TRANSACTION_LABLE(hdr, *tid); + BT_AVCTP_HDR_SET_PACKET_TYPE(hdr, pkt_type); + BT_AVCTP_HDR_SET_CR(hdr, cr); + BT_AVCTP_HDR_SET_IPID(hdr, ipid); + hdr->pid = pid; + + if (cr == BT_AVCTP_CMD) { + *tid = (*tid + 1) & 0x0F; /* Incremented by one */ + } + + LOG_DBG("cr:0x%lX, tid:0x%02lX", BT_AVCTP_HDR_GET_CR(hdr), + BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr)); + return buf; +} + +int bt_avctp_send(struct bt_avctp *session, struct net_buf *buf) +{ + int err; + + err = bt_l2cap_chan_send(&session->br_chan.chan, buf); + if (err < 0) { + net_buf_unref(buf); + LOG_ERR("L2CAP send fail err = %d", err); + return err; + } + + return err; +} + +int bt_avctp_register(struct bt_avctp_event_cb *cb) +{ + LOG_DBG(""); + + if (event_cb) { + return -EALREADY; + } + + event_cb = cb; + + return 0; +} + +static int avctp_l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + /* TODO */ + + return -ENOTSUP; +} + +int bt_avctp_init(void) +{ + int err; + static struct bt_l2cap_server avctp_l2cap = { + .psm = BT_L2CAP_PSM_AVCTP, + .sec_level = BT_SECURITY_L2, + .accept = avctp_l2cap_accept, + }; + + LOG_DBG(""); + + /* Register AVCTP PSM with L2CAP */ + err = bt_l2cap_br_server_register(&avctp_l2cap); + if (err < 0) { + LOG_ERR("AVCTP L2CAP registration failed %d", err); + } + + return err; +} diff --git a/subsys/bluetooth/host/classic/avctp_internal.h b/subsys/bluetooth/host/classic/avctp_internal.h new file mode 100644 index 00000000000000..3624696271189d --- /dev/null +++ b/subsys/bluetooth/host/classic/avctp_internal.h @@ -0,0 +1,109 @@ +/** @file + * @brief Audio Video Control Transport Protocol internal header. + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_L2CAP_PSM_AVCTP 0x0017 + +typedef enum __packed { + BT_AVCTP_IPID_NONE = 0b0, + BT_AVCTP_IPID_INVALID = 0b1, +} bt_avctp_ipid_t; + +typedef enum __packed { + BT_AVCTP_CMD = 0b0, + BT_AVCTP_RESPONSE = 0b1, +} bt_avctp_cr_t; + +typedef enum __packed { + BT_AVCTP_PKT_TYPE_SINGLE = 0b00, + BT_AVCTP_PKT_TYPE_START = 0b01, + BT_AVCTP_PKT_TYPE_CONTINUE = 0b10, + BT_AVCTP_PKT_TYPE_END = 0b11, +} bt_avctp_pkt_type_t; + +struct bt_avctp_header { + uint8_t byte0; /** [7:4]: Transaction label, [3:2]: Packet_type, [1]: C/R, [0]: IPID */ + uint16_t pid; /** Profile Identifier */ +} __packed; + +/** Transaction label provided by the application and is replicated by the sender of the message in + * each packet of the sequence. It isused at the receiver side to identify packets that belong to + * the same message. + */ +#define BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr) FIELD_GET(GENMASK(7, 4), ((hdr)->byte0)) +/** Set to zero (00) to indicate that the command/response message is transmitted in a single L2CAP + * packet. Alternatively, set to (01) for start, (10) for continue, or (11) for end packet. + */ +#define BT_AVCTP_HDR_GET_PACKET_TYPE(hdr) FIELD_GET(GENMASK(3, 2), ((hdr)->byte0)) +/** Indicates whether the messageconveys a command frame (0) or a response frame (1). */ +#define BT_AVCTP_HDR_GET_CR(hdr) FIELD_GET(BIT(1), ((hdr)->byte0)) +/** The IPID bit is set in a response message to indicate an invalid Profile Identifier received in + * the command message of the same transaction; otherwise this bit is set to zero. In command + * messages this bit is set to zero. This field is only present in the start packet of the message. + */ +#define BT_AVCTP_HDR_GET_IPID(hdr) FIELD_GET(BIT(0), ((hdr)->byte0)) + +/** Transaction label provided by the application and is replicated by the sender of the message in + * each packet of the sequence. It isused at the receiver side to identify packets that belong to + * the same message. + */ +#define BT_AVCTP_HDR_SET_TRANSACTION_LABLE(hdr, tl) \ + (hdr)->byte0 = (((hdr)->byte0) & ~GENMASK(7, 4)) | FIELD_PREP(GENMASK(7, 4), (tl)) +/** Set to zero (00) to indicate that the command/response message is transmitted in a single L2CAP + * packet. Alternatively, set to (01) for start, (10) for continue, or (11) for end packet. + */ +#define BT_AVCTP_HDR_SET_PACKET_TYPE(hdr, packet_type) \ + (hdr)->byte0 = (((hdr)->byte0) & ~GENMASK(3, 2)) | FIELD_PREP(GENMASK(3, 2), (packet_type)) +/** Indicates whether the messageconveys a command frame (0) or a response frame (1). */ +#define BT_AVCTP_HDR_SET_CR(hdr, cr) \ + (hdr)->byte0 = (((hdr)->byte0) & ~BIT(1)) | FIELD_PREP(BIT(1), (cr)) +/** The IPID bit is set in a response message to indicate an invalid Profile Identifier received in + * the command message of the same transaction; otherwise this bit is set to zero. In command + * messages this bit is set to zero. This field is only present in the start packet of the message. + */ +#define BT_AVCTP_HDR_SET_IPID(hdr, ipid) \ + (hdr)->byte0 = (((hdr)->byte0) & ~BIT(0)) | FIELD_PREP(BIT(0), (ipid)) + +struct bt_avctp; + +struct bt_avctp_ops_cb { + void (*connected)(struct bt_avctp *session); + void (*disconnected)(struct bt_avctp *session); + int (*recv)(struct bt_avctp *session, struct net_buf *buf); +}; + +struct bt_avctp { + struct bt_l2cap_br_chan br_chan; + const struct bt_avctp_ops_cb *ops; +}; + +struct bt_avctp_event_cb { + int (*accept)(struct bt_conn *conn, struct bt_avctp **session); +}; + +/* Initialize AVCTP layer*/ +int bt_avctp_init(void); + +/* Application register with AVCTP layer */ +int bt_avctp_register(struct bt_avctp_event_cb *cb); + +/* AVCTP connect */ +int bt_avctp_connect(struct bt_conn *conn, struct bt_avctp *session); + +/* AVCTP disconnect */ +int bt_avctp_disconnect(struct bt_avctp *session); + +/* Create AVCTP PDU */ +struct net_buf *bt_avctp_create_pdu(struct bt_avctp *session, bt_avctp_cr_t cr, + bt_avctp_pkt_type_t pkt_type, bt_avctp_ipid_t ipid, + uint8_t *tid, uint16_t pid); + +/* Send AVCTP PDU */ +int bt_avctp_send(struct bt_avctp *session, struct net_buf *buf); diff --git a/subsys/bluetooth/host/classic/avrcp.c b/subsys/bluetooth/host/classic/avrcp.c new file mode 100644 index 00000000000000..1d6fc93bd6ffe5 --- /dev/null +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -0,0 +1,460 @@ +/** @file + * @brief Audio Video Remote Control Profile + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "host/hci_core.h" +#include "host/conn_internal.h" +#include "host/l2cap_internal.h" +#include "avctp_internal.h" +#include "avrcp_internal.h" + +#define LOG_LEVEL CONFIG_BT_AVRCP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_avrcp); + +struct bt_avrcp { + struct bt_avctp session; + struct bt_avrcp_req req; + struct k_work_delayable timeout_work; + uint8_t local_tid; +}; + +static struct bt_avrcp_cb *avrcp_cb; + +#define AVRCP_TIMEOUT K_SECONDS(3) /* Shell be greater than TMTP (1000ms) */ +#define AVRCP_AVCTP(_avctp) CONTAINER_OF(_avctp, struct bt_avrcp, session) +#define AVRCP_KWORK(_work) \ + CONTAINER_OF(CONTAINER_OF(_work, struct k_work_delayable, work), struct bt_avrcp, \ + timeout_work) + +static struct bt_avrcp_cb *avrcp_cb; +static struct bt_avrcp avrcp_connection[CONFIG_BT_MAX_CONN]; + +#if defined(CONFIG_BT_AVRCP_TARGET) +static struct bt_sdp_attribute avrcp_tg_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_TARGET_SVCLASS) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST({BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP)}, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) + }, + ) + }, + ) + ), + /* C1: Browsing not supported */ + /* C2: Cover Art not supported */ + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVRCP_VER_1_6) + }, + ) + }, + ) + ), + BT_SDP_SUPPORTED_FEATURES(AVRCP_CAT_1 | AVRCP_CAT_2), + /* O: Provider Name not presented */ + BT_SDP_SERVICE_NAME("AVRCP Target"), +}; + +static struct bt_sdp_record avrcp_tg_rec = BT_SDP_RECORD(avrcp_tg_attrs); +#endif /* CONFIG_BT_AVRCP_TARGET */ + +#if defined(CONFIG_BT_AVRCP_CONTROLLER) +static struct bt_sdp_attribute avrcp_ct_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_CONTROLLER_SVCLASS) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) + }, + ) + }, + ) + ), + /* C1: Browsing not supported */ + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVRCP_VER_1_6) + }, + ) + }, + ) + ), + BT_SDP_SUPPORTED_FEATURES(AVRCP_CAT_1 | AVRCP_CAT_2), + /* O: Provider Name not presented */ + BT_SDP_SERVICE_NAME("AVRCP Controller"), +}; + +static struct bt_sdp_record avrcp_ct_rec = BT_SDP_RECORD(avrcp_ct_attrs); +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + +static struct bt_avrcp *get_new_connection(struct bt_conn *conn) +{ + struct bt_avrcp *avrcp; + + if (!conn) { + LOG_ERR("Invalid Input (err: %d)", -EINVAL); + return NULL; + } + + avrcp = &avrcp_connection[bt_conn_index(conn)]; + memset(avrcp, 0, sizeof(struct bt_avrcp)); + return avrcp; +} + +static void avrcp_timeout(struct k_work *work) +{ + struct bt_avrcp *avrcp = AVRCP_KWORK(work); + + LOG_WRN("Timeout: tid 0x%X, opc 0x%02X", avrcp->req.tid, avrcp->req.opcode); +} + +/* The AVCTP L2CAP channel established */ +static void avrcp_connected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_AVCTP(session); + + if ((avrcp_cb != NULL) && (avrcp_cb->connected != NULL)) { + avrcp_cb->connected(avrcp); + } + + k_work_init_delayable(&avrcp->timeout_work, avrcp_timeout); +} + +/* The AVCTP L2CAP channel released */ +static void avrcp_disconnected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_AVCTP(session); + + if ((avrcp_cb != NULL) && (avrcp_cb->disconnected != NULL)) { + avrcp_cb->disconnected(avrcp); + } +} + +/* An AVRCP message received */ +static int avrcp_recv(struct bt_avctp *session, struct net_buf *buf) +{ + struct bt_avrcp *avrcp = AVRCP_AVCTP(session); + struct bt_avctp_header *avctp_hdr; + struct bt_avrcp_header *avrcp_hdr; + uint8_t tid; + bt_avctp_cr_t cr; + bt_avrcp_ctype_t ctype; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_subunit_type_t subunit_type; + + avctp_hdr = (void *)buf->data; + net_buf_pull(buf, sizeof(*avctp_hdr)); + avrcp_hdr = (void *)buf->data; + + tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(avctp_hdr); + cr = BT_AVCTP_HDR_GET_CR(avctp_hdr); + ctype = BT_AVRCP_HDR_GET_CTYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + + if (avctp_hdr->pid != sys_cpu_to_be16(BT_SDP_AV_REMOTE_SVCLASS)) { + return -EINVAL; /* Ignore other profile */ + } + + LOG_DBG("AVRCP msg received, cr:0x%X, tid:0x%X, ctype: 0x%X, opc:0x%02X,", cr, tid, ctype, + avrcp_hdr->opcode); + if (cr == BT_AVCTP_RESPONSE) { + if (avrcp_hdr->opcode == BT_AVRCP_OPC_VENDOR_DEPENDENT && + ctype == BT_AVRCP_CTYPE_CHANGED) { + /* Status changed notifiation, do not reset timer */ + } else if (avrcp_hdr->opcode == BT_AVRCP_OPC_PASS_THROUGH) { + /* No max response time for pass through commands */ + } else if (tid != avrcp->req.tid || subunit_id != avrcp->req.subunit || + avrcp_hdr->opcode != avrcp->req.opcode) { + LOG_WRN("unexpected AVRCP response, expected tid:0x%X, subunit:0x%X, " + "opc:0x%02X", + avrcp->req.tid, avrcp->req.subunit, avrcp->req.opcode); + } else { + k_work_cancel_delayable(&avrcp->timeout_work); + } + } + + /* TODO: add handlers */ + + return 0; +} + +static const struct bt_avctp_ops_cb avctp_ops = { + .connected = avrcp_connected, + .disconnected = avrcp_disconnected, + .recv = avrcp_recv, +}; + +static int avrcp_accept(struct bt_conn *conn, struct bt_avctp **session) +{ + struct bt_avrcp *avrcp; + + avrcp = get_new_connection(conn); + if (!avrcp) { + return -ENOMEM; + } + + *session = &(avrcp->session); + avrcp->session.ops = &avctp_ops; + + LOG_DBG("session: %p", &(avrcp->session)); + + return 0; +} + +static struct bt_avctp_event_cb avctp_cb = { + .accept = avrcp_accept, +}; + +int bt_avrcp_init(void) +{ + int err; + + /* Register event handlers with AVCTP */ + err = bt_avctp_register(&avctp_cb); + if (err < 0) { + LOG_ERR("AVRCP registration failed"); + return err; + } + +#if defined(CONFIG_BT_AVRCP_TARGET) + bt_sdp_register_service(&avrcp_tg_rec); +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + +#if defined(CONFIG_BT_AVRCP_CONTROLLER) + bt_sdp_register_service(&avrcp_ct_rec); +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + + LOG_DBG("AVRCP Initialized successfully."); + return 0; +} + +struct bt_avrcp *bt_avrcp_connect(struct bt_conn *conn) +{ + struct bt_avrcp *avrcp; + int err; + + avrcp = get_new_connection(conn); + if (!avrcp) { + LOG_ERR("Cannot allocate memory"); + return NULL; + } + + avrcp->session.ops = &avctp_ops; + err = bt_avctp_connect(conn, &(avrcp->session)); + if (err < 0) { + /* If error occurs, undo the saving and return the error */ + memset(avrcp, 0, sizeof(struct bt_avrcp)); + LOG_DBG("AVCTP Connect failed"); + return NULL; + } + + LOG_DBG("Connection request sent"); + return avrcp; +} + +int bt_avrcp_disconnect(struct bt_avrcp *avrcp) +{ + int err; + + err = bt_avctp_disconnect(&(avrcp->session)); + if (err < 0) { + LOG_DBG("AVCTP Disconnect failed"); + return err; + } + + return err; +} + +static struct net_buf *avrcp_create_pdu(struct bt_avrcp *avrcp, bt_avctp_cr_t cr) +{ + struct net_buf *buf; + + buf = bt_avctp_create_pdu(&(avrcp->session), cr, BT_AVCTP_PKT_TYPE_SINGLE, + BT_AVCTP_IPID_NONE, &avrcp->local_tid, + sys_cpu_to_be16(BT_SDP_AV_REMOTE_SVCLASS)); + + return buf; +} + +static struct net_buf *avrcp_create_unit_pdu(struct bt_avrcp *avrcp, bt_avctp_cr_t cr) +{ + struct net_buf *buf; + struct bt_avrcp_unit_info_cmd *cmd; + + buf = avrcp_create_pdu(avrcp, cr); + if (!buf) { + return buf; + } + + cmd = net_buf_add(buf, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + BT_AVRCP_HDR_SET_CTYPE(&cmd->hdr, cr == BT_AVCTP_CMD ? BT_AVRCP_CTYPE_STATUS + : BT_AVRCP_CTYPE_IMPLEMENTED_STABLE); + BT_AVRCP_HDR_SET_SUBUNIT_ID(&cmd->hdr, BT_AVRCP_SUBUNIT_ID_IGNORE); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(&cmd->hdr, BT_AVRCP_SUBUNIT_TYPE_UNIT); + cmd->hdr.opcode = BT_AVRCP_OPC_UNIT_INFO; + + return buf; +} + +static int avrcp_send(struct bt_avrcp *avrcp, struct net_buf *buf) +{ + int err; + struct bt_avctp_header *avctp_hdr = (struct bt_avctp_header *)(buf->data); + struct bt_avrcp_header *avrcp_hdr = + (struct bt_avrcp_header *)(buf->data + sizeof(*avctp_hdr)); + uint8_t tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(avctp_hdr); + bt_avctp_cr_t cr = BT_AVCTP_HDR_GET_CR(avctp_hdr); + bt_avrcp_ctype_t ctype = BT_AVRCP_HDR_GET_CTYPE(avrcp_hdr); + bt_avrcp_subunit_type_t subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + + LOG_DBG("AVRCP send cr:0x%X, tid:0x%X, ctype: 0x%X, opc:0x%02X\n", cr, tid, ctype, + avrcp_hdr->opcode); + err = bt_avctp_send(&(avrcp->session), buf); + if (err < 0) { + return err; + } + + if (cr == BT_AVCTP_CMD && avrcp_hdr->opcode != BT_AVRCP_OPC_PASS_THROUGH) { + avrcp->req.tid = tid; + avrcp->req.subunit = subunit_id; + avrcp->req.opcode = avrcp_hdr->opcode; + + k_work_reschedule(&avrcp->timeout_work, AVRCP_TIMEOUT); + } + + return 0; +} + +int bt_avrcp_get_unit_info(struct bt_avrcp *avrcp) +{ + struct net_buf *buf; + uint8_t param[5]; + + buf = avrcp_create_unit_pdu(avrcp, BT_AVCTP_CMD); + if (!buf) { + return -ENOMEM; + } + + memset(param, 0xFF, 5); + net_buf_add_mem(buf, param, sizeof(param)); + + return avrcp_send(avrcp, buf); +} + +int bt_avrcp_register_cb(struct bt_avrcp_cb *cb) +{ + avrcp_cb = cb; + return 0; +} diff --git a/subsys/bluetooth/host/classic/avrcp_internal.h b/subsys/bluetooth/host/classic/avrcp_internal.h new file mode 100644 index 00000000000000..a82279d73f3645 --- /dev/null +++ b/subsys/bluetooth/host/classic/avrcp_internal.h @@ -0,0 +1,102 @@ +/** @file + * @brief Audio Video Remote Control Profile internal header. + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define AVCTP_VER_1_4 (0x0104u) +#define AVRCP_VER_1_6 (0x0106u) + +#define AVRCP_CAT_1 BIT(0) /* Player/Recorder */ +#define AVRCP_CAT_2 BIT(1) /* Monitor/Amplifier */ +#define AVRCP_CAT_3 BIT(2) /* Tuner */ +#define AVRCP_CAT_4 BIT(3) /* Menu */ + +typedef enum __packed { + BT_AVRCP_CTYPE_CONTROL = 0x0, + BT_AVRCP_CTYPE_STATUS = 0x1, + BT_AVRCP_CTYPE_SPECIFIC_INQUIRY = 0x2, + BT_AVRCP_CTYPE_NOTIFY = 0x3, + BT_AVRCP_CTYPE_GENERAL_INQUIRY = 0x4, + BT_AVRCP_CTYPE_NOT_IMPLEMENTED = 0x8, + BT_AVRCP_CTYPE_ACCEPTED = 0x9, + BT_AVRCP_CTYPE_REJECTED = 0xA, + BT_AVRCP_CTYPE_IN_TRANSITION = 0xB, + BT_AVRCP_CTYPE_IMPLEMENTED_STABLE = 0xC, + BT_AVRCP_CTYPE_CHANGED = 0xD, + BT_AVRCP_CTYPE_INTERIM = 0xF, +} bt_avrcp_ctype_t; + +typedef enum __packed { + BT_AVRCP_SUBUNIT_ID_ZERO = 0x0, + BT_AVRCP_SUBUNIT_ID_IGNORE = 0x7, +} bt_avrcp_subunit_id_t; + +typedef enum __packed { + BT_AVRCP_SUBUNIT_TYPE_PANEL = 0x9, + BT_AVRCP_SUBUNIT_TYPE_UNIT = 0x1F, +} bt_avrcp_subunit_type_t; + +typedef enum __packed { + BT_AVRCP_OPC_VENDOR_DEPENDENT = 0x0, + BT_AVRCP_OPC_UNIT_INFO = 0x30, + BT_AVRCP_OPC_SUBUNIT_INFO = 0x31, + BT_AVRCP_OPC_PASS_THROUGH = 0x7c, +} bt_avrcp_opcode_t; + +struct bt_avrcp_req { + uint8_t tid; + uint8_t subunit; + uint8_t opcode; +}; + +struct bt_avrcp_header { + uint8_t byte0; /** [7:4]: RFA, [3:0]: Ctype */ + uint8_t byte1; /** [7:3]: Subunit_type, [2:0]: Subunit_ID */ + uint8_t opcode; /** Unit Info, Subunit Info, Vendor Dependent, or Pass Through */ +} __packed; + +/** The 4-bit command type or the 4-bit response code. */ +#define BT_AVRCP_HDR_GET_CTYPE(hdr) FIELD_GET(GENMASK(3, 0), ((hdr)->byte0)) +/** Taken together, the subunit_type and subunit_ID fields define the command recipient’s address + * within the target. These fields enable the target to determine whether the command is + * addressed to the target unit, or to a specific subunit within the target. The values in these + * fields remain unchanged in the response frame. + */ +#define BT_AVRCP_HDR_GET_SUBUNIT_ID(hdr) FIELD_GET(GENMASK(2, 0), ((hdr)->byte1)) +/** Taken together, the subunit_type and subunit_ID fields define the command recipient’s address + * within the target. These fields enable the target to determine whether the command is + * addressed to the target unit, or to a specific subunit within the target. The values in these + * fields remain unchanged in the response frame. + */ +#define BT_AVRCP_HDR_GET_SUBUNIT_TYPE(hdr) FIELD_GET(GENMASK(7, 3), ((hdr)->byte1)) + +/** The 4-bit command type or the 4-bit response code. */ +#define BT_AVRCP_HDR_SET_CTYPE(hdr, ctype) \ + (hdr)->byte0 = (((hdr)->byte0) & ~GENMASK(3, 0)) | FIELD_PREP(GENMASK(3, 0), (ctype)) +/** Taken together, the subunit_type and subunit_ID fields define the command recipient’s address + * within the target. These fields enable the target to determine whether the command is + * addressed to the target unit, or to a specific subunit within the target. The values in these + * fields remain unchanged in the response frame. + */ +#define BT_AVRCP_HDR_SET_SUBUNIT_ID(hdr, subunit_id) \ + (hdr)->byte1 = (((hdr)->byte1) & ~GENMASK(2, 0)) | FIELD_PREP(GENMASK(2, 0), (subunit_id)) +/** Taken together, the subunit_type and subunit_ID fields define the command recipient’s address + * within the target. These fields enable the target to determine whether the command is + * addressed to the target unit, or to a specific subunit within the target. The values in these + * fields remain unchanged in the response frame. + */ +#define BT_AVRCP_HDR_SET_SUBUNIT_TYPE(hdr, subunit_type) \ + (hdr)->byte1 = (((hdr)->byte1) & ~GENMASK(7, 3)) | FIELD_PREP(GENMASK(7, 3), (subunit_type)) + +struct bt_avrcp_unit_info_cmd { + struct bt_avrcp_header hdr; + uint8_t data[0]; +} __packed; + +int bt_avrcp_init(void); diff --git a/subsys/bluetooth/host/classic/l2cap_br.c b/subsys/bluetooth/host/classic/l2cap_br.c index e5c52d6e208feb..d1e558a7b3d664 100644 --- a/subsys/bluetooth/host/classic/l2cap_br.c +++ b/subsys/bluetooth/host/classic/l2cap_br.c @@ -25,6 +25,8 @@ #include "l2cap_br_internal.h" #include "avdtp_internal.h" #include "a2dp_internal.h" +#include "avctp_internal.h" +#include "avrcp_internal.h" #include "rfcomm_internal.h" #include "sdp_internal.h" @@ -2074,9 +2076,17 @@ void bt_l2cap_br_init(void) bt_avdtp_init(); } + if (IS_ENABLED(CONFIG_BT_AVCTP)) { + bt_avctp_init(); + } + bt_sdp_init(); if (IS_ENABLED(CONFIG_BT_A2DP)) { bt_a2dp_init(); } + + if (IS_ENABLED(CONFIG_BT_AVRCP)) { + bt_avrcp_init(); + } } diff --git a/subsys/bluetooth/host/classic/shell/CMakeLists.txt b/subsys/bluetooth/host/classic/shell/CMakeLists.txt index 7710f36e5bd3d8..dbf42a44cb1acd 100644 --- a/subsys/bluetooth/host/classic/shell/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/shell/CMakeLists.txt @@ -4,3 +4,4 @@ zephyr_library() zephyr_library_sources(bredr.c) zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c) zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c) +zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c) diff --git a/subsys/bluetooth/host/classic/shell/avrcp.c b/subsys/bluetooth/host/classic/shell/avrcp.c new file mode 100644 index 00000000000000..4bf758f6178960 --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/avrcp.c @@ -0,0 +1,158 @@ +/** @file + * @brief Audio Video Remote Control Profile shell functions. + */ + +/* + * Copyright (c) 2024 Xiaomi InC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "host/shell/bt.h" + +struct bt_avrcp *default_avrcp; +static bool avrcp_registered; + +static void avrcp_connected(struct bt_avrcp *avrcp) +{ + default_avrcp = avrcp; + shell_print(ctx_shell, "AVRCP connected"); +} + +static void avrcp_disconnected(struct bt_avrcp *avrcp) +{ + shell_print(ctx_shell, "AVRCP disconnected"); +} + +static struct bt_avrcp_cb avrcp_cb = { + .connected = avrcp_connected, + .disconnected = avrcp_disconnected, +}; + +static int register_cb(const struct shell *sh) +{ + int err; + + if (avrcp_registered) { + return 0; + } + + err = bt_avrcp_register_cb(&avrcp_cb); + if (!err) { + avrcp_registered = true; + shell_print(sh, "AVRCP callbacks registered"); + } else { + shell_print(sh, "failed to register callbacks"); + } + + return err; +} + +static int cmd_register_cb(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (avrcp_registered) { + shell_print(sh, "already registered"); + return 0; + } + + register_cb(sh); + + return 0; +} + +static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (!avrcp_registered) { + if (register_cb(sh) != 0) { + return -ENOEXEC; + } + } + + if (!default_conn) { + shell_error(sh, "BR/EDR not connected"); + return -ENOEXEC; + } + + default_avrcp = bt_avrcp_connect(default_conn); + if (NULL == default_avrcp) { + shell_error(sh, "fail to connect AVRCP"); + } + + return 0; +} + +static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (!avrcp_registered) { + if (register_cb(sh) != 0) { + return -ENOEXEC; + } + } + + if (default_avrcp != NULL) { + bt_avrcp_disconnect(default_avrcp); + default_avrcp = NULL; + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (!avrcp_registered) { + if (register_cb(sh) != 0) { + return -ENOEXEC; + } + } + + if (default_avrcp != NULL) { + bt_avrcp_get_unit_info(default_avrcp); + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(avrcp_cmds, + SHELL_CMD_ARG(register_cb, NULL, "register avrcp callbacks", + cmd_register_cb, 1, 0), + SHELL_CMD_ARG(connect, NULL, "
", cmd_connect, 2, 0), + SHELL_CMD_ARG(disconnect, NULL, "
", cmd_disconnect, 2, 0), + SHELL_CMD_ARG(get_unit, NULL, "
", cmd_get_unit_info, 2, 0), + SHELL_SUBCMD_SET_END); + +static int cmd_avrcp(const struct shell *sh, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(sh); + /* sh returns 1 when help is printed */ + return 1; + } + + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + + return -ENOEXEC; +} + +SHELL_CMD_ARG_REGISTER(avrcp, &avrcp_cmds, "Bluetooth AVRCP sh commands", cmd_avrcp, 1, 1);