Skip to content

BR avrcp subunit info passthrough and Pass Through command reception on the AVRCP target #93549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions include/zephyr/bluetooth/classic/avrcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ struct bt_avrcp_passthrough_rsp {
uint8_t data[];
};

struct bt_avrcp_passthrough_opvu_data {
uint8_t company_id[3];
uint8_t op_len;
uint16_t opid_vu;
};

struct bt_avrcp_get_cap_rsp {
uint8_t cap_id; /**< bt_avrcp_cap_t */
uint8_t cap_cnt; /**< number of items contained in *cap */
Expand Down Expand Up @@ -354,6 +360,30 @@ struct bt_avrcp_tg_cb {
* @param tg AVRCP TG connection object.
*/
void (*unit_info_req)(struct bt_avrcp_tg *tg, uint8_t tid);

/** @brief Subunit Info Request callback.
*
* This callback is called whenever an AVRCP subunit info is requested.
*
* @param tid The transaction label of the request.
* @param tg AVRCP TG connection object.
*/
void (*subunit_info_req)(struct bt_avrcp_tg *tg, uint8_t tid);

/** @brief Pass Through command request callback.
*
* This callback is called whenever an AVRCP Pass Through command is request.
*
* @param tg AVRCP TG connection object.
* @param tid The transaction label of the request.
* @param opid The user operation id, see @ref bt_avrcp_opid_t.
* @param state The button state, see @ref bt_avrcp_button_state_t.
* @param payload The payload of the pass through command.
* @param len The length of the payload.
*/
void (*passthrough_cmd_req)(struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_opid_t opid,
bt_avrcp_button_state_t state, const uint8_t *data,
uint8_t len);
};

/** @brief Register callback.
Expand All @@ -378,6 +408,37 @@ int bt_avrcp_tg_register_cb(const struct bt_avrcp_tg_cb *cb);
*/
int bt_avrcp_tg_send_unit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid,
struct bt_avrcp_unit_info_rsp *rsp);

/** @brief Send the subunit info response.
*
* This function is called by the application to send the subunit info response.
*
* @param tg The AVRCP TG instance.
* @param tid The transaction label of the response, valid from 0 to 15.
* @param rsp The response for SUBUNIT INFO command.
*
* @return 0 in case of success or error code in case of error.
*/
int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid,
struct bt_avrcp_subunit_info_rsp *rsp);

/** @brief Send AVRCP Pass Through response.
*
* This function is called by the application to send the Pass Through response.
*
* @param tg The AVRCP TG instance.
* @param tid The transaction label of the response, valid from 0 to 15.
* @param result The response code, see @ref bt_avrcp_rsp_t.
* @param opid The user pass through operation id, see @ref bt_avrcp_opid_t.
* @param state The button state, see @ref bt_avrcp_button_state_t.
* @param payload The payload of the pass through response. Should not be NULL if len is not zero.
* @param len The length of the payload.
*
* @return 0 in case of success or error code in case of error.
*/
int bt_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result,
bt_avrcp_opid_t opid, bt_avrcp_button_state_t state,
const uint8_t *payload, uint8_t len);
#ifdef __cplusplus
}
#endif
Expand Down
202 changes: 196 additions & 6 deletions subsys/bluetooth/host/classic/avrcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ static struct net_buf *avrcp_create_unit_pdu(struct bt_avrcp *avrcp, uint8_t tid
}

static struct net_buf *avrcp_create_subunit_pdu(struct bt_avrcp *avrcp, uint8_t tid,
bt_avctp_cr_t cr)
bt_avctp_cr_t cr, uint8_t ctype_or_rsp)
{
struct net_buf *buf;
struct bt_avrcp_frame *cmd;
Expand All @@ -337,8 +337,7 @@ static struct net_buf *avrcp_create_subunit_pdu(struct bt_avrcp *avrcp, uint8_t

cmd = net_buf_add(buf, sizeof(*cmd));
memset(cmd, 0, sizeof(*cmd));
BT_AVRCP_HDR_SET_CTYPE_OR_RSP(&cmd->hdr, cr == BT_AVCTP_CMD ? BT_AVRCP_CTYPE_STATUS
: BT_AVRCP_RSP_STABLE);
BT_AVRCP_HDR_SET_CTYPE_OR_RSP(&cmd->hdr, ctype_or_rsp);
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_SUBUNIT_INFO;
Expand Down Expand Up @@ -423,6 +422,25 @@ static int bt_avrcp_send_unit_info_err_rsp(struct bt_avrcp *avrcp, uint8_t tid)
return avrcp_send(avrcp, buf);
}

static int bt_avrcp_send_subunit_info_err_rsp(struct bt_avrcp *avrcp)
{
struct net_buf *buf;
uint8_t param[BT_AVRCP_SUBUNIT_INFO_RSP_SIZE];

buf = avrcp_create_subunit_pdu(avrcp, 0x0, BT_AVCTP_RESPONSE, BT_AVRCP_RSP_REJECTED);
if (!buf) {
LOG_WRN("Insufficient buffer");
return -ENOMEM;
}

memset(param, 0xFF, ARRAY_SIZE(param));
param[0] = FIELD_PREP(GENMASK(6, 4), AVRCP_SUBUNIT_PAGE) |
FIELD_PREP(GENMASK(2, 0), AVRCP_SUBUNIT_EXTENSION_CODE);
net_buf_add_mem(buf, param, sizeof(param));

return avrcp_send(avrcp, buf);
}

static void process_get_cap_rsp(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf)
{
struct bt_avrcp_avc_pdu *pdu;
Expand Down Expand Up @@ -634,13 +652,110 @@ static void avrcp_vendor_dependent_cmd_handler(struct bt_avrcp *avrcp, uint8_t t
static void avrcp_subunit_info_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid,
struct net_buf *buf)
{
/* ToDo */
struct bt_avrcp_header *avrcp_hdr;
bt_avrcp_subunit_type_t subunit_type;
bt_avrcp_subunit_id_t subunit_id;
bt_avrcp_ctype_t ctype;
uint8_t page;
uint8_t extension_code;
int err;

if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->subunit_info_req == NULL)) {
goto err_rsp;
}

if (buf->len < sizeof(*avrcp_hdr)) {
goto err_rsp;
}

avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr));
if (buf->len != BT_AVRCP_SUBUNIT_INFO_CMD_SIZE) {
LOG_ERR("Invalid subunit info length");
goto err_rsp;
}

subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr);
subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr);
ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr);

/* First byte contains page and extension code */
page = FIELD_GET(GENMASK(6, 4), buf->data[0]);
extension_code = FIELD_GET(GENMASK(2, 0), buf->data[0]);

if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_UNIT) || (ctype != BT_AVRCP_CTYPE_STATUS) ||
(subunit_id != BT_AVRCP_SUBUNIT_ID_IGNORE) || (page != AVRCP_SUBUNIT_PAGE) ||
(avrcp_hdr->opcode != BT_AVRCP_OPC_SUBUNIT_INFO) ||
(extension_code != AVRCP_SUBUNIT_EXTENSION_CODE)) {
LOG_ERR("Invalid subunit info command");
goto err_rsp;
}

return avrcp_tg_cb->subunit_info_req(get_avrcp_tg(avrcp), tid);

err_rsp:
err = bt_avrcp_send_subunit_info_err_rsp(avrcp);
if (err) {
LOG_ERR("Failed to send subunit info error response");
}
}

static void avrcp_pass_through_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid,
struct net_buf *buf)
{
/* ToDo */
struct bt_avrcp_header *avrcp_hdr;
bt_avrcp_opid_t opid;
bt_avrcp_button_state_t state;
uint8_t data_len;
uint8_t tmp;
uint8_t *data;
int err;

if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->passthrough_cmd_req == NULL)) {
goto err_rsp;
}

if (buf->len < (sizeof(*avrcp_hdr) + BT_AVRCP_PASSTHROUGH_CMD_SIZE)) {
LOG_ERR("Invalid passthrough command length: %d", buf->len);
goto err_rsp;
}
avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr));

if (BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr) != BT_AVRCP_SUBUNIT_TYPE_PANEL ||
BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr) != BT_AVRCP_SUBUNIT_ID_ZERO ||
BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr) != BT_AVRCP_CTYPE_CONTROL) {
LOG_ERR("Invalid passthrough command ");
goto err_rsp;
}

tmp = net_buf_pull_u8(buf);
state = FIELD_GET(BIT(7), tmp);
opid = FIELD_GET(GENMASK(6, 0), tmp);

data_len = net_buf_pull_u8(buf);
if (data_len > 0U) {
if (buf->len < data_len) {
LOG_ERR("Invalid passthrough data length: %d, buf length = %d", data_len,
buf->len);
goto err_rsp;
}
data = buf->data;
} else {
data = NULL;
}

if ((opid < BT_AVRCP_OPID_SELECT) || (opid > BT_AVRCP_OPID_VENDOR_UNIQUE)) {
LOG_ERR("Invalid passthrough operation ID: 0x%02X", opid);
goto err_rsp;
}
return avrcp_tg_cb->passthrough_cmd_req(get_avrcp_tg(avrcp), tid, opid, state, data,
data_len);

err_rsp:
err = bt_avrcp_tg_send_passthrough_rsp(get_avrcp_tg(avrcp), tid, BT_AVRCP_RSP_REJECTED, 0U,
0U, NULL, 0U);
if (err) {
LOG_ERR("Failed to send passthrough error response");
}
}

static const struct avrcp_handler cmd_handlers[] = {
Expand Down Expand Up @@ -879,7 +994,7 @@ int bt_avrcp_ct_get_subunit_info(struct bt_avrcp_ct *ct, uint8_t tid)
return -ENOTSUP;
}

buf = avrcp_create_subunit_pdu(ct->avrcp, tid, BT_AVCTP_CMD);
buf = avrcp_create_subunit_pdu(ct->avrcp, tid, BT_AVCTP_CMD, BT_AVRCP_CTYPE_STATUS);
if (!buf) {
return -ENOMEM;
}
Expand Down Expand Up @@ -977,3 +1092,78 @@ int bt_avrcp_tg_send_unit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid,

return avrcp_send(tg->avrcp, buf);
}

int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid,
struct bt_avrcp_subunit_info_rsp *rsp)
{
struct net_buf *buf;
uint8_t param[5];

if ((tg == NULL) || (tg->avrcp == NULL) || (rsp == NULL)) {
return -EINVAL;
}

if (!IS_TG_ROLE_SUPPORTED()) {
return -ENOTSUP;
}

/* It should be always value 0 in the max_subunit_ID and PANEL in the subunit_type field */
if ((rsp->max_subunit_id != 0u) || (rsp->subunit_type != BT_AVRCP_SUBUNIT_TYPE_PANEL)) {
return -EINVAL;
}

buf = avrcp_create_subunit_pdu(tg->avrcp, tid, BT_AVCTP_RESPONSE, BT_AVRCP_RSP_STABLE);
if (!buf) {
LOG_WRN("Insufficient buffer");
return -ENOMEM;
}

memset(param, 0xFF, ARRAY_SIZE(param));

/* First byte contains page number and extension code */
param[0] = FIELD_PREP(GENMASK(6, 4), AVRCP_SUBUNIT_PAGE) |
FIELD_PREP(GENMASK(2, 0), AVRCP_SUBUNIT_EXTENSION_CODE);

/* Add subunit information */
param[1] = FIELD_PREP(GENMASK(7, 3), rsp->subunit_type |
FIELD_PREP(GENMASK(2, 0), rsp->max_subunit_id));

net_buf_add_mem(buf, param, sizeof(param));

return avrcp_send(tg->avrcp, buf);
}

int bt_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result,
bt_avrcp_opid_t opid, bt_avrcp_button_state_t state,
const uint8_t *payload, uint8_t len)
{
struct net_buf *buf;

if ((tg == NULL) || (tg->avrcp == NULL)) {
return -EINVAL;
}

if (!IS_TG_ROLE_SUPPORTED()) {
return -ENOTSUP;
}

buf = avrcp_create_passthrough_pdu(tg->avrcp, tid, BT_AVCTP_RESPONSE, result);
if (!buf) {
LOG_WRN("Insufficient buffer");
return -ENOMEM;
}

/* Add operation ID and state */
net_buf_add_u8(buf, FIELD_PREP(BIT(7), state) | FIELD_PREP(GENMASK(6, 0), opid));

net_buf_add_u8(buf, len);
if (len > 0U) {
if (payload == NULL) {
net_buf_unref(buf);
return -EINVAL;
}
net_buf_add_mem(buf, payload, len);
}

return avrcp_send(tg->avrcp, buf);
}
2 changes: 2 additions & 0 deletions subsys/bluetooth/host/classic/avrcp_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#define BT_AVRCP_UNIT_INFO_CMD_SIZE (5)
#define BT_AVRCP_UNIT_INFO_RSP_SIZE (5)
#define BT_AVRCP_SUBUNIT_INFO_RSP_SIZE (5)
#define BT_AVRCP_SUBUNIT_INFO_CMD_SIZE (5)
#define BT_AVRCP_PASSTHROUGH_CMD_SIZE (2)

typedef enum __packed {
BT_AVRCP_SUBUNIT_ID_ZERO = 0x0,
Expand Down
Loading