From f5927a9142efd10ffba3bc7d9a152c183cd249c6 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 9 Jul 2024 12:58:57 +0800 Subject: [PATCH 01/10] Bluetooth: AVCTP: Implementation of AVCTP. This patch implementing avctp.c New Kconfig BT_AVCTP is provided to enable this layer. avctp_internal.h shows the APIs for the upper layer, i.e., AVRCP. Only connection and disconnection interfaces are provided in this patch. Signed-off-by: Zihao Gao --- include/zephyr/bluetooth/uuid.h | 2 + subsys/bluetooth/Kconfig.logging | 4 + subsys/bluetooth/host/classic/CMakeLists.txt | 1 + subsys/bluetooth/host/classic/Kconfig | 5 + subsys/bluetooth/host/classic/avctp.c | 149 ++++++++++++++++++ .../bluetooth/host/classic/avctp_internal.h | 40 +++++ subsys/bluetooth/host/classic/l2cap_br.c | 5 + 7 files changed, 206 insertions(+) create mode 100644 subsys/bluetooth/host/classic/avctp.c create mode 100644 subsys/bluetooth/host/classic/avctp_internal.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..aacd3059c698f8 100644 --- a/subsys/bluetooth/Kconfig.logging +++ b/subsys/bluetooth/Kconfig.logging @@ -387,6 +387,10 @@ 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_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..6b0934f02d8a65 100644 --- a/subsys/bluetooth/host/classic/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/CMakeLists.txt @@ -7,6 +7,7 @@ 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_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..d39f20f16db697 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -178,6 +178,11 @@ config BT_A2DP_SINK endif # BT_A2DP +config BT_AVCTP + bool "Bluetooth AVCTP protocol support" + help + This option enables Bluetooth AVCTP support + 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..5ddc8c613a2687 --- /dev/null +++ b/subsys/bluetooth/host/classic/avctp.c @@ -0,0 +1,149 @@ +/** @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 "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) +{ + LOG_DBG(""); + + /* TODO */ + + return -ENOTSUP; +} + +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); +} + +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..d3196d3be4bd95 --- /dev/null +++ b/subsys/bluetooth/host/classic/avctp_internal.h @@ -0,0 +1,40 @@ +/** @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 + +struct bt_avctp; + +struct bt_avctp_ops_cb { + void (*connected)(struct bt_avctp *session); + void (*disconnected)(struct bt_avctp *session); +}; + +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); diff --git a/subsys/bluetooth/host/classic/l2cap_br.c b/subsys/bluetooth/host/classic/l2cap_br.c index e5c52d6e208feb..76c33e027049e9 100644 --- a/subsys/bluetooth/host/classic/l2cap_br.c +++ b/subsys/bluetooth/host/classic/l2cap_br.c @@ -25,6 +25,7 @@ #include "l2cap_br_internal.h" #include "avdtp_internal.h" #include "a2dp_internal.h" +#include "avctp_internal.h" #include "rfcomm_internal.h" #include "sdp_internal.h" @@ -2074,6 +2075,10 @@ 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)) { From d02b8d0b89864b32beff05998d698734bf8fa7a6 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 9 Jul 2024 15:16:39 +0800 Subject: [PATCH 02/10] Bluetooth: AVRCP: Implemation of AVRCP. This patch implementing avrcp.c New Kconfig BT_AVRCP is provided to enable this layer. BT_AVRCP_TARGET and BT_AVRCP_CONTROLLER are then provided to enable one of the two roles independently. avrcp.h shows the APIs for the upper layer. Only connection and disconnection interfaces are provided in this patch. Signed-off-by: Zihao Gao --- include/zephyr/bluetooth/classic/avrcp.h | 78 +++++++++ subsys/bluetooth/Kconfig.logging | 4 + subsys/bluetooth/host/classic/CMakeLists.txt | 1 + subsys/bluetooth/host/classic/Kconfig | 21 +++ subsys/bluetooth/host/classic/avrcp.c | 158 ++++++++++++++++++ .../bluetooth/host/classic/avrcp_internal.h | 12 ++ subsys/bluetooth/host/classic/l2cap_br.c | 5 + 7 files changed, 279 insertions(+) create mode 100644 include/zephyr/bluetooth/classic/avrcp.h create mode 100644 subsys/bluetooth/host/classic/avrcp.c create mode 100644 subsys/bluetooth/host/classic/avrcp_internal.h diff --git a/include/zephyr/bluetooth/classic/avrcp.h b/include/zephyr/bluetooth/classic/avrcp.h new file mode 100644 index 00000000000000..ef79df8eb9e47a --- /dev/null +++ b/include/zephyr/bluetooth/classic/avrcp.h @@ -0,0 +1,78 @@ +/** @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); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AVRCP_H_ */ diff --git a/subsys/bluetooth/Kconfig.logging b/subsys/bluetooth/Kconfig.logging index aacd3059c698f8..7ccaf55c2d6be5 100644 --- a/subsys/bluetooth/Kconfig.logging +++ b/subsys/bluetooth/Kconfig.logging @@ -391,6 +391,10 @@ 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 6b0934f02d8a65..0f7f01d5c00b52 100644 --- a/subsys/bluetooth/host/classic/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/CMakeLists.txt @@ -7,6 +7,7 @@ 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) diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index d39f20f16db697..3df8f30e10462f 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -183,6 +183,27 @@ config BT_AVCTP 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/avrcp.c b/subsys/bluetooth/host/classic/avrcp.c new file mode 100644 index 00000000000000..afdd8a5fed37b5 --- /dev/null +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -0,0 +1,158 @@ +/** @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 "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; +}; + +#define AVRCP_AVCTP(_avctp) CONTAINER_OF(_avctp, struct bt_avrcp, session) + +static struct bt_avrcp_cb *avrcp_cb; +static struct bt_avrcp avrcp_connection[CONFIG_BT_MAX_CONN]; + +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; +} + +/* 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); + } +} + +/* 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); + } +} + +static const struct bt_avctp_ops_cb avctp_ops = { + .connected = avrcp_connected, + .disconnected = avrcp_disconnected, +}; + +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; + } + + 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; +} + +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..8e2454a6e237dc --- /dev/null +++ b/subsys/bluetooth/host/classic/avrcp_internal.h @@ -0,0 +1,12 @@ +/** @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 + */ + +int bt_avrcp_init(void); diff --git a/subsys/bluetooth/host/classic/l2cap_br.c b/subsys/bluetooth/host/classic/l2cap_br.c index 76c33e027049e9..d1e558a7b3d664 100644 --- a/subsys/bluetooth/host/classic/l2cap_br.c +++ b/subsys/bluetooth/host/classic/l2cap_br.c @@ -26,6 +26,7 @@ #include "avdtp_internal.h" #include "a2dp_internal.h" #include "avctp_internal.h" +#include "avrcp_internal.h" #include "rfcomm_internal.h" #include "sdp_internal.h" @@ -2084,4 +2085,8 @@ void bt_l2cap_br_init(void) if (IS_ENABLED(CONFIG_BT_A2DP)) { bt_a2dp_init(); } + + if (IS_ENABLED(CONFIG_BT_AVRCP)) { + bt_avrcp_init(); + } } From 02601b97b9e95ab0597b3e5c95b19b04cb022e18 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 9 Jul 2024 12:25:37 +0800 Subject: [PATCH 03/10] Bluetooth: AVRCP: Add SDP attributes. This patch add SDP records for both CT and TG role. The SDP attribute would be registered according to the configuration. OBEX and Browsing commands are optional and yet not supported. SDP registration is implemented at AVRCP level to simplify the workload of the upper layer. We assume the App can have limited knowledge on SDP structures. Signed-off-by: Zihao Gao --- subsys/bluetooth/host/classic/avrcp.c | 157 ++++++++++++++++++ .../bluetooth/host/classic/avrcp_internal.h | 8 + 2 files changed, 165 insertions(+) diff --git a/subsys/bluetooth/host/classic/avrcp.c b/subsys/bluetooth/host/classic/avrcp.c index afdd8a5fed37b5..71e09487d5690c 100644 --- a/subsys/bluetooth/host/classic/avrcp.c +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -18,6 +18,7 @@ #include #include +#include #include #include "host/hci_core.h" @@ -39,6 +40,154 @@ struct bt_avrcp { 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; @@ -110,6 +259,14 @@ int bt_avrcp_init(void) 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; } diff --git a/subsys/bluetooth/host/classic/avrcp_internal.h b/subsys/bluetooth/host/classic/avrcp_internal.h index 8e2454a6e237dc..ed378283c07b45 100644 --- a/subsys/bluetooth/host/classic/avrcp_internal.h +++ b/subsys/bluetooth/host/classic/avrcp_internal.h @@ -9,4 +9,12 @@ * 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 */ + int bt_avrcp_init(void); From e06c43ffb141cdd7eb564ebc16a62ef24d147b63 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 9 Jul 2024 12:23:48 +0800 Subject: [PATCH 04/10] Bluetooth: AVRCP: add shell tools for AVRCP functions. Only the basic functions for establishing an AVCTP connection are provided at this stage. An BR/EDR ACL connection is necessary before AVRCP function. Register callbacks before utilizing AVRCP. Signed-off-by: Zihao Gao --- .../host/classic/shell/CMakeLists.txt | 1 + subsys/bluetooth/host/classic/shell/avrcp.c | 140 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 subsys/bluetooth/host/classic/shell/avrcp.c 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..c6624df2a44e75 --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/avrcp.c @@ -0,0 +1,140 @@ +/** @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 uint8_t 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 = 1; + 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 == 0) { + 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 == 0) { + 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; +} + +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_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); From a222d415b125711f1d764edf3812f68666b71fde Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 9 Jul 2024 17:10:52 +0800 Subject: [PATCH 05/10] Bluetooth: AVCTP: allow to create and send AVCTP message This patch defines the message format for general AVCTP. They would be further called by AVRCP layer. Signed-off-by: Zihao Gao --- subsys/bluetooth/host/classic/avctp.c | 45 ++++++++++++++++++- .../bluetooth/host/classic/avctp_internal.h | 29 ++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/avctp.c b/subsys/bluetooth/host/classic/avctp.c index 5ddc8c613a2687..02f4a58069cde6 100644 --- a/subsys/bluetooth/host/classic/avctp.c +++ b/subsys/bluetooth/host/classic/avctp.c @@ -107,6 +107,49 @@ int bt_avctp_disconnect(struct bt_avctp *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_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)); + hdr->cr = cr; + hdr->ipid = ipid; + hdr->pkt_type = BT_AVCTP_PKT_TYPE_SINGLE; + hdr->tid = *tid; + hdr->pid = pid; + + if (cr == BT_AVCTP_CMD) { + *tid = (*tid + 1) & 0x0F; /* Incremented by one */ + } + + LOG_DBG("cr:0x%X, tid:0x%02X", hdr->cr, hdr->tid); + 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(""); @@ -121,7 +164,7 @@ int bt_avctp_register(struct bt_avctp_event_cb *cb) } static int avctp_l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server, - struct bt_l2cap_chan **chan) + struct bt_l2cap_chan **chan) { /* TODO */ diff --git a/subsys/bluetooth/host/classic/avctp_internal.h b/subsys/bluetooth/host/classic/avctp_internal.h index d3196d3be4bd95..afec3587ad5add 100644 --- a/subsys/bluetooth/host/classic/avctp_internal.h +++ b/subsys/bluetooth/host/classic/avctp_internal.h @@ -11,6 +11,28 @@ #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_t; + +struct bt_avctp_header { + uint8_t ipid: 1; /* Invalid Profile Identifier (1), otherwise zero (0) */ + uint8_t cr: 1; /* Command(0) or Respone(1) */ + uint8_t pkt_type: 2; /* Set to zero (00) for single L2CAP packet */ + uint8_t tid: 4; /* Transaction label */ + uint16_t pid; /* Profile Identifier */ +} __packed; + struct bt_avctp; struct bt_avctp_ops_cb { @@ -38,3 +60,10 @@ 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_ipid_t ipid, uint8_t *tid, uint16_t pid); + +/* Send AVCTP PDU */ +int bt_avctp_send(struct bt_avctp *session, struct net_buf *buf); From 4dcb8404b38789deb11f8df5a30ae96eaaedd376 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 9 Jul 2024 12:25:37 +0800 Subject: [PATCH 06/10] Bluetooth: AVRCP: allow to create and send AVRCP unit message This patch defines the message format for AVCTP unit message. This is the first out of the four types of commands and can be used by the CT to obtain the unit info from the TG device. Signed-off-by: Zihao Gao --- include/zephyr/bluetooth/classic/avrcp.h | 10 ++ subsys/bluetooth/host/classic/avrcp.c | 93 +++++++++++++++++++ .../bluetooth/host/classic/avrcp_internal.h | 51 ++++++++++ 3 files changed, 154 insertions(+) diff --git a/include/zephyr/bluetooth/classic/avrcp.h b/include/zephyr/bluetooth/classic/avrcp.h index ef79df8eb9e47a..f00157eab8049d 100644 --- a/include/zephyr/bluetooth/classic/avrcp.h +++ b/include/zephyr/bluetooth/classic/avrcp.h @@ -71,6 +71,16 @@ int bt_avrcp_disconnect(struct bt_avrcp *avrcp); */ 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 diff --git a/subsys/bluetooth/host/classic/avrcp.c b/subsys/bluetooth/host/classic/avrcp.c index 71e09487d5690c..b310741af9528d 100644 --- a/subsys/bluetooth/host/classic/avrcp.c +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -33,9 +33,18 @@ 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]; @@ -202,6 +211,13 @@ static struct bt_avrcp *get_new_connection(struct bt_conn *conn) 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) { @@ -210,6 +226,8 @@ static void avrcp_connected(struct bt_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 */ @@ -308,6 +326,81 @@ int bt_avrcp_disconnect(struct bt_avrcp *avrcp) 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_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)); + cmd->hdr.ctype = + (cr == BT_AVCTP_CMD) ? BT_AVRCP_CTYPE_STATUS : BT_AVRCP_CTYPE_IMPLEMENTED_STABLE; + cmd->hdr.subunit_id = BT_AVRCP_SUBUNIT_ID_IGNORE; + cmd->hdr.subunit_type = 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_avrcp_header avrcp_hdr; + + memcpy(&avctp_hdr, buf->data, sizeof(avctp_hdr)); + memcpy(&avrcp_hdr, buf->data + sizeof(avctp_hdr), sizeof(avrcp_hdr)); + + LOG_DBG("AVRCP send cr:0x%X, tid:0x%X, ctype: 0x%X, opc:0x%02X\n", avctp_hdr.cr, + avctp_hdr.tid, avrcp_hdr.ctype, avrcp_hdr.opcode); + err = bt_avctp_send(&(avrcp->session), buf); + if (err < 0) { + return err; + } + + if (avctp_hdr.cr == BT_AVCTP_CMD && avrcp_hdr.opcode != BT_AVRCP_OPC_PASS_THROUGH) { + avrcp->req.tid = avctp_hdr.tid; + avrcp->req.subunit = avrcp_hdr.subunit_id; + avrcp->req.opcode = avrcp_hdr.opcode; + + k_work_reschedule(&avrcp->timeout_work, AVRCP_TIMEOUT); + /* TODO: k_work_cancel_delayable(&avrcp->timeout_work); when response received */ + } + + 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; diff --git a/subsys/bluetooth/host/classic/avrcp_internal.h b/subsys/bluetooth/host/classic/avrcp_internal.h index ed378283c07b45..b6ea8416df0aa6 100644 --- a/subsys/bluetooth/host/classic/avrcp_internal.h +++ b/subsys/bluetooth/host/classic/avrcp_internal.h @@ -17,4 +17,55 @@ #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 ctype: 4; /* Command type codes */ + uint8_t rfa: 4; /* Zero according to AV/C command frame */ + uint8_t subunit_id: 3; /* Zero (0x0) or Ignore (0x7) according to AVRCP */ + uint8_t subunit_type: 5; /* Unit (0x1F) or Panel (0x9) according to AVRCP */ + uint8_t opcode; /* Unit Info, Subunit Info, Vendor Dependent, or Pass Through */ +} __packed; + +struct bt_avrcp_unit_info_cmd { + struct bt_avrcp_header hdr; + uint8_t data[0]; +} __packed; + int bt_avrcp_init(void); From 99ec248d9c5ede74c509f290f2c1d18344b2257d Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 9 Jul 2024 18:17:19 +0800 Subject: [PATCH 07/10] Bluetooth: Shell: add command to obtain unit info This patch allow to acquire the unit info of the remote device. Signed-off-by: Zihao Gao --- subsys/bluetooth/host/classic/shell/avrcp.c | 39 ++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/subsys/bluetooth/host/classic/shell/avrcp.c b/subsys/bluetooth/host/classic/shell/avrcp.c index c6624df2a44e75..46e87e8795a676 100644 --- a/subsys/bluetooth/host/classic/shell/avrcp.c +++ b/subsys/bluetooth/host/classic/shell/avrcp.c @@ -29,7 +29,16 @@ #include "host/shell/bt.h" struct bt_avrcp *default_avrcp; -static uint8_t avrcp_registered; +static bool avrcp_registered; + +#define CHECK_REGISTER_CALLBACKS(_sh, _errno) \ + do { \ + if (!avrcp_registered) { \ + if (register_cb(_sh) != 0) \ + return _errno; \ + } \ + } while (0) + static void avrcp_connected(struct bt_avrcp *avrcp) { default_avrcp = avrcp; @@ -56,7 +65,7 @@ static int register_cb(const struct shell *sh) err = bt_avrcp_register_cb(&avrcp_cb); if (!err) { - avrcp_registered = 1; + avrcp_registered = true; shell_print(sh, "AVRCP callbacks registered"); } else { shell_print(sh, "failed to register callbacks"); @@ -79,11 +88,7 @@ static int cmd_register_cb(const struct shell *sh, int32_t argc, char *argv[]) static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) { - if (avrcp_registered == 0) { - if (register_cb(sh) != 0) { - return -ENOEXEC; - } - } + CHECK_REGISTER_CALLBACKS(sh, -ENOEXEC); if (!default_conn) { shell_error(sh, "BR/EDR not connected"); @@ -100,11 +105,7 @@ static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[]) { - if (avrcp_registered == 0) { - if (register_cb(sh) != 0) { - return -ENOEXEC; - } - } + CHECK_REGISTER_CALLBACKS(sh, -ENOEXEC); if (default_avrcp != NULL) { bt_avrcp_disconnect(default_avrcp); @@ -116,11 +117,25 @@ static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[]) return 0; } +static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[]) +{ + CHECK_REGISTER_CALLBACKS(sh, -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) From dcba86f58249025df55bba941d1c82509333e134 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Fri, 12 Jul 2024 19:51:41 +0800 Subject: [PATCH 08/10] Bluetooth: AVCTP: allow to receive an AVCTP message. This patch received an AVCTP message and forward to the upper layer, e.g., AVRCP. Signed-off-by: Zihao Gao --- subsys/bluetooth/host/classic/avctp.c | 30 +++++++++++++++++-- .../bluetooth/host/classic/avctp_internal.h | 1 + 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/subsys/bluetooth/host/classic/avctp.c b/subsys/bluetooth/host/classic/avctp.c index 02f4a58069cde6..e5c9be3b7eb00a 100644 --- a/subsys/bluetooth/host/classic/avctp.c +++ b/subsys/bluetooth/host/classic/avctp.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "avctp_internal.h" #include "host/hci_core.h" @@ -69,11 +70,34 @@ static void avctp_l2cap_encrypt_changed(struct bt_l2cap_chan *chan, uint8_t stat static int avctp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) { - LOG_DBG(""); + struct net_buf *rsp; + struct bt_avctp *session = AVCTP_CHAN(chan); + struct bt_avctp_header *hdr = (void *)buf->data; - /* TODO */ + if (buf->len < sizeof(*hdr)) { + LOG_ERR("invalid AVCTP header received"); + return -EINVAL; + } - return -ENOTSUP; + 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 (hdr->cr == BT_AVCTP_CMD) { + rsp = bt_avctp_create_pdu(session, BT_AVCTP_RESPONSE, + BT_AVCTP_IPID_INVALID, &hdr->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) diff --git a/subsys/bluetooth/host/classic/avctp_internal.h b/subsys/bluetooth/host/classic/avctp_internal.h index afec3587ad5add..d34055077be77e 100644 --- a/subsys/bluetooth/host/classic/avctp_internal.h +++ b/subsys/bluetooth/host/classic/avctp_internal.h @@ -38,6 +38,7 @@ 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 { From 8cb66349eb9315c1a9efbd3ee937dc4580a9e189 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Fri, 12 Jul 2024 22:16:55 +0800 Subject: [PATCH 09/10] Bluetooth : AVRCP: allow to receive an AVRCP message. This patch received an AVRCP message and remove timeout timers. Signed-off-by: Zihao Gao --- subsys/bluetooth/host/classic/avctp.c | 4 +- subsys/bluetooth/host/classic/avrcp.c | 41 ++++++++++++++++++++- subsys/bluetooth/host/classic/shell/avrcp.c | 41 +++++++++++---------- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/subsys/bluetooth/host/classic/avctp.c b/subsys/bluetooth/host/classic/avctp.c index e5c9be3b7eb00a..cbe004efb4d92f 100644 --- a/subsys/bluetooth/host/classic/avctp.c +++ b/subsys/bluetooth/host/classic/avctp.c @@ -87,8 +87,8 @@ static int avctp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) default: LOG_ERR("unsupported AVCTP PID received: 0x%04x", sys_be16_to_cpu(hdr->pid)); if (hdr->cr == BT_AVCTP_CMD) { - rsp = bt_avctp_create_pdu(session, BT_AVCTP_RESPONSE, - BT_AVCTP_IPID_INVALID, &hdr->tid, hdr->pid); + rsp = bt_avctp_create_pdu(session, BT_AVCTP_RESPONSE, BT_AVCTP_IPID_INVALID, + &hdr->tid, hdr->pid); if (!rsp) { return -ENOMEM; } diff --git a/subsys/bluetooth/host/classic/avrcp.c b/subsys/bluetooth/host/classic/avrcp.c index b310741af9528d..8337d94c9d7af6 100644 --- a/subsys/bluetooth/host/classic/avrcp.c +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -240,9 +240,49 @@ static void avrcp_disconnected(struct bt_avctp *session) } } +/* 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; + + avctp_hdr = (void *)buf->data; + net_buf_pull(buf, sizeof(*avctp_hdr)); + avrcp_hdr = (void *)buf->data; + + 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,", avctp_hdr->cr, + avctp_hdr->tid, avrcp_hdr->ctype, avrcp_hdr->opcode); + if (avctp_hdr->cr == BT_AVCTP_RESPONSE) { + if (avrcp_hdr->opcode == BT_AVRCP_OPC_VENDOR_DEPENDENT && + avrcp_hdr->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 (avrcp->req.tid != avctp_hdr->tid || + avrcp->req.subunit != avrcp_hdr->subunit_id || + avrcp->req.opcode != avrcp_hdr->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) @@ -379,7 +419,6 @@ static int avrcp_send(struct bt_avrcp *avrcp, struct net_buf *buf) avrcp->req.opcode = avrcp_hdr.opcode; k_work_reschedule(&avrcp->timeout_work, AVRCP_TIMEOUT); - /* TODO: k_work_cancel_delayable(&avrcp->timeout_work); when response received */ } return 0; diff --git a/subsys/bluetooth/host/classic/shell/avrcp.c b/subsys/bluetooth/host/classic/shell/avrcp.c index 46e87e8795a676..4bf758f6178960 100644 --- a/subsys/bluetooth/host/classic/shell/avrcp.c +++ b/subsys/bluetooth/host/classic/shell/avrcp.c @@ -31,14 +31,6 @@ struct bt_avrcp *default_avrcp; static bool avrcp_registered; -#define CHECK_REGISTER_CALLBACKS(_sh, _errno) \ - do { \ - if (!avrcp_registered) { \ - if (register_cb(_sh) != 0) \ - return _errno; \ - } \ - } while (0) - static void avrcp_connected(struct bt_avrcp *avrcp) { default_avrcp = avrcp; @@ -88,7 +80,11 @@ static int cmd_register_cb(const struct shell *sh, int32_t argc, char *argv[]) static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) { - CHECK_REGISTER_CALLBACKS(sh, -ENOEXEC); + if (!avrcp_registered) { + if (register_cb(sh) != 0) { + return -ENOEXEC; + } + } if (!default_conn) { shell_error(sh, "BR/EDR not connected"); @@ -105,7 +101,11 @@ static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[]) { - CHECK_REGISTER_CALLBACKS(sh, -ENOEXEC); + if (!avrcp_registered) { + if (register_cb(sh) != 0) { + return -ENOEXEC; + } + } if (default_avrcp != NULL) { bt_avrcp_disconnect(default_avrcp); @@ -119,7 +119,11 @@ static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[]) static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[]) { - CHECK_REGISTER_CALLBACKS(sh, -ENOEXEC); + if (!avrcp_registered) { + if (register_cb(sh) != 0) { + return -ENOEXEC; + } + } if (default_avrcp != NULL) { bt_avrcp_get_unit_info(default_avrcp); @@ -131,12 +135,12 @@ static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[]) } 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); + 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) { @@ -151,5 +155,4 @@ static int cmd_avrcp(const struct shell *sh, size_t argc, char **argv) return -ENOEXEC; } -SHELL_CMD_ARG_REGISTER(avrcp, &avrcp_cmds, "Bluetooth AVRCP sh commands", - cmd_avrcp, 1, 1); +SHELL_CMD_ARG_REGISTER(avrcp, &avrcp_cmds, "Bluetooth AVRCP sh commands", cmd_avrcp, 1, 1); From d0cfd32e08d9360fbae90f711b23f74b127f91a3 Mon Sep 17 00:00:00 2001 From: Zihao Gao Date: Tue, 15 Oct 2024 13:54:21 +0800 Subject: [PATCH 10/10] Bluetooth: AVRCP: fix bitfield issue. The bit order can be incorrect when use bit field definition. Signed-off-by: Zihao Gao --- subsys/bluetooth/host/classic/avctp.c | 23 ++++--- .../bluetooth/host/classic/avctp_internal.h | 51 ++++++++++++++-- subsys/bluetooth/host/classic/avrcp.c | 61 +++++++++++-------- .../bluetooth/host/classic/avrcp_internal.h | 41 +++++++++++-- 4 files changed, 132 insertions(+), 44 deletions(-) diff --git a/subsys/bluetooth/host/classic/avctp.c b/subsys/bluetooth/host/classic/avctp.c index cbe004efb4d92f..09e6e58606b708 100644 --- a/subsys/bluetooth/host/classic/avctp.c +++ b/subsys/bluetooth/host/classic/avctp.c @@ -73,6 +73,8 @@ 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"); @@ -86,9 +88,10 @@ static int avctp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) #endif default: LOG_ERR("unsupported AVCTP PID received: 0x%04x", sys_be16_to_cpu(hdr->pid)); - if (hdr->cr == BT_AVCTP_CMD) { - rsp = bt_avctp_create_pdu(session, BT_AVCTP_RESPONSE, BT_AVCTP_IPID_INVALID, - &hdr->tid, 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; } @@ -132,7 +135,8 @@ int bt_avctp_disconnect(struct bt_avctp *session) } struct net_buf *bt_avctp_create_pdu(struct bt_avctp *session, bt_avctp_cr_t cr, - bt_avctp_ipid_t ipid, uint8_t *tid, uint16_t pid) + 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; @@ -146,17 +150,18 @@ struct net_buf *bt_avctp_create_pdu(struct bt_avctp *session, bt_avctp_cr_t cr, } hdr = net_buf_add(buf, sizeof(*hdr)); - hdr->cr = cr; - hdr->ipid = ipid; - hdr->pkt_type = BT_AVCTP_PKT_TYPE_SINGLE; - hdr->tid = *tid; + 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%X, tid:0x%02X", hdr->cr, hdr->tid); + LOG_DBG("cr:0x%lX, tid:0x%02lX", BT_AVCTP_HDR_GET_CR(hdr), + BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr)); return buf; } diff --git a/subsys/bluetooth/host/classic/avctp_internal.h b/subsys/bluetooth/host/classic/avctp_internal.h index d34055077be77e..3624696271189d 100644 --- a/subsys/bluetooth/host/classic/avctp_internal.h +++ b/subsys/bluetooth/host/classic/avctp_internal.h @@ -23,16 +23,54 @@ typedef enum __packed { 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 ipid: 1; /* Invalid Profile Identifier (1), otherwise zero (0) */ - uint8_t cr: 1; /* Command(0) or Respone(1) */ - uint8_t pkt_type: 2; /* Set to zero (00) for single L2CAP packet */ - uint8_t tid: 4; /* Transaction label */ - uint16_t pid; /* Profile Identifier */ + 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 { @@ -64,7 +102,8 @@ 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_ipid_t ipid, uint8_t *tid, uint16_t pid); + 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 index 8337d94c9d7af6..1d6fc93bd6ffe5 100644 --- a/subsys/bluetooth/host/classic/avrcp.c +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -246,26 +246,36 @@ 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,", avctp_hdr->cr, - avctp_hdr->tid, avrcp_hdr->ctype, avrcp_hdr->opcode); - if (avctp_hdr->cr == BT_AVCTP_RESPONSE) { + 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 && - avrcp_hdr->ctype == BT_AVRCP_CTYPE_CHANGED) { + 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 (avrcp->req.tid != avctp_hdr->tid || - avrcp->req.subunit != avrcp_hdr->subunit_id || - avrcp->req.opcode != avrcp_hdr->opcode) { + } 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); @@ -370,7 +380,8 @@ 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_IPID_NONE, &avrcp->local_tid, + 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; @@ -388,10 +399,10 @@ static struct net_buf *avrcp_create_unit_pdu(struct bt_avrcp *avrcp, bt_avctp_cr cmd = net_buf_add(buf, sizeof(*cmd)); memset(cmd, 0, sizeof(*cmd)); - cmd->hdr.ctype = - (cr == BT_AVCTP_CMD) ? BT_AVRCP_CTYPE_STATUS : BT_AVRCP_CTYPE_IMPLEMENTED_STABLE; - cmd->hdr.subunit_id = BT_AVRCP_SUBUNIT_ID_IGNORE; - cmd->hdr.subunit_type = BT_AVRCP_SUBUNIT_TYPE_UNIT; + 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; @@ -400,23 +411,25 @@ static struct net_buf *avrcp_create_unit_pdu(struct bt_avrcp *avrcp, bt_avctp_cr static int avrcp_send(struct bt_avrcp *avrcp, struct net_buf *buf) { int err; - struct bt_avctp_header avctp_hdr; - struct bt_avrcp_header avrcp_hdr; - - memcpy(&avctp_hdr, buf->data, sizeof(avctp_hdr)); - memcpy(&avrcp_hdr, buf->data + sizeof(avctp_hdr), sizeof(avrcp_hdr)); - - LOG_DBG("AVRCP send cr:0x%X, tid:0x%X, ctype: 0x%X, opc:0x%02X\n", avctp_hdr.cr, - avctp_hdr.tid, avrcp_hdr.ctype, avrcp_hdr.opcode); + 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 (avctp_hdr.cr == BT_AVCTP_CMD && avrcp_hdr.opcode != BT_AVRCP_OPC_PASS_THROUGH) { - avrcp->req.tid = avctp_hdr.tid; - avrcp->req.subunit = avrcp_hdr.subunit_id; - avrcp->req.opcode = avrcp_hdr.opcode; + 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); } diff --git a/subsys/bluetooth/host/classic/avrcp_internal.h b/subsys/bluetooth/host/classic/avrcp_internal.h index b6ea8416df0aa6..a82279d73f3645 100644 --- a/subsys/bluetooth/host/classic/avrcp_internal.h +++ b/subsys/bluetooth/host/classic/avrcp_internal.h @@ -56,13 +56,44 @@ struct bt_avrcp_req { }; struct bt_avrcp_header { - uint8_t ctype: 4; /* Command type codes */ - uint8_t rfa: 4; /* Zero according to AV/C command frame */ - uint8_t subunit_id: 3; /* Zero (0x0) or Ignore (0x7) according to AVRCP */ - uint8_t subunit_type: 5; /* Unit (0x1F) or Panel (0x9) according to AVRCP */ - uint8_t opcode; /* Unit Info, Subunit Info, Vendor Dependent, or Pass Through */ + 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];