From patchwork Tue Oct 18 04:38:28 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 616130 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3E03FC433FE for ; Tue, 18 Oct 2022 04:38:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229722AbiJREir (ORCPT ); Tue, 18 Oct 2022 00:38:47 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52388 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229486AbiJREiq (ORCPT ); Tue, 18 Oct 2022 00:38:46 -0400 Received: from mga09.intel.com (mga09.intel.com [134.134.136.24]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D023BA027F for ; Mon, 17 Oct 2022 21:38:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1666067925; x=1697603925; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=v4gQTTSH4nkFIZi1ot0slnrsUZrrSz9zYNk8jIRhslQ=; b=fO6XuLaYIexAY7oCLfKLCfb+tn4KeOYen7gzGNeMiZkOcwQm+ekqTWWp 50QXMGlISAS4Jm3E4ZupGuxBoLBsDaBOk9GH2p+dLYRGYz8h4nrJfA6mD xU2oCU6pcyeVEcyLMaa52UK7A645h0qahvgHUmNervIuE2pwGVcAQQxab W4xv1Rc+Qm7k9pBnv3DYrvAIfBvxWaSH0/ftJSJ/QGwDb8B1SkBy4cImJ zbVGWZBZOUKccZZzLP3/fdo/kD0+92b1jDTfH1Fjqnuo3VKjwTh4KhKtz v/1433OEAKol1bCbOb29DAzg7RIfvGrmCO+GIrRVyDOyaU89vDBGKT5e1 A==; X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="307079217" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="307079217" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by orsmga102.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 17 Oct 2022 21:38:45 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="957608028" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="957608028" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga005.fm.intel.com with ESMTP; 17 Oct 2022 21:38:44 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ v3 1/4] lib/uuid: Add GMCS UUIDs Date: Tue, 18 Oct 2022 10:08:28 +0530 Message-Id: <20221018043831.342821-2-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> References: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org This adds GMCS UUIDs which will be used by Media Control Profile. --- lib/uuid.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/uuid.h b/lib/uuid.h index f667a74b9..d5e5665e4 100644 --- a/lib/uuid.h +++ b/lib/uuid.h @@ -171,6 +171,21 @@ extern "C" { #define VOL_CP_CHRC_UUID 0x2B7E #define VOL_FLAG_CHRC_UUID 0x2B7F +#define GMCS_UUID 0x1849 +#define MEDIA_PLAYER_NAME_CHRC_UUID 0x2b93 +#define MEDIA_TRACK_CHNGD_CHRC_UUID 0x2b96 +#define MEDIA_TRACK_TITLE_CHRC_UUID 0x2b97 +#define MEDIA_TRACK_DURATION_CHRC_UUID 0x2b98 +#define MEDIA_TRACK_POSTION_CHRC_UUID 0x2b99 +#define MEDIA_PLAYBACK_SPEED_CHRC_UUID 0x2b9a +#define MEDIA_SEEKING_SPEED_CHRC_UUID 0x2b9b +#define MEDIA_PLAYING_ORDER_CHRC_UUID 0x2ba1 +#define MEDIA_PLAY_ORDER_SUPPRTD_CHRC_UUID 0x2ba2 +#define MEDIA_STATE_CHRC_UUID 0x2ba3 +#define MEDIA_CP_CHRC_UUID 0x2ba4 +#define MEDIA_CP_OP_SUPPORTED_CHRC_UUID 0x2ba5 +#define MEDIA_CONTENT_CONTROL_ID_CHRC_UUID 0x2bba + typedef struct { enum { BT_UUID_UNSPEC = 0, From patchwork Tue Oct 18 04:38:29 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 616129 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C91F2C43217 for ; Tue, 18 Oct 2022 04:38:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229751AbiJREiy (ORCPT ); Tue, 18 Oct 2022 00:38:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52472 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229849AbiJREiv (ORCPT ); Tue, 18 Oct 2022 00:38:51 -0400 Received: from mga09.intel.com (mga09.intel.com [134.134.136.24]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 21BE4A02C1 for ; Mon, 17 Oct 2022 21:38:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1666067929; x=1697603929; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=XaSJTMWtcOLwzkf3vtXI8Ss+Pspw5p6mB4cU0jrnYTU=; b=KkrDHL3YiveiBJHFDEKcXXarII+RcuO74pPn35rHGlhrQZJNTPLqGTDH 4VLwBZxY78a7QOYEMSsWHMwUqK9hCUoUI+3FDBrI9Xn7Lh8Wq4nJtg162 ZxWeN7XWcBSa+c63xRP8FC8RxpWAEKviXWQwvocetakUGL0BJ5eYAhRU7 NOJCFmun6F6ONzbcx8KFrpY5IUBsrqRhSooP8u+ggBw6Evl99Ckr6ysuQ TryPHmq5YmCi0JZLhK2ICFOJsZd1Hz6ywJzuTNqusRGwSihHX2drpu3ei YqyqwdDg1h0LbZDfHgAOec5C1RnrHy7Twf5ifo6H8BGHfWycP0frtNsTD Q==; X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="307079221" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="307079221" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by orsmga102.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 17 Oct 2022 21:38:48 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="957608037" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="957608037" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga005.fm.intel.com with ESMTP; 17 Oct 2022 21:38:47 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ v3 2/4] shared/mcp: Add initial code for handling MCP Date: Tue, 18 Oct 2022 10:08:29 +0530 Message-Id: <20221018043831.342821-3-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> References: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org This adds initial code for Media Control Profile for Client Role. --- Makefile.am | 1 + src/shared/mcp.c | 1419 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/mcp.h | 61 ++ src/shared/mcs.h | 65 +++ 4 files changed, 1546 insertions(+) create mode 100644 src/shared/mcp.c create mode 100644 src/shared/mcp.h create mode 100644 src/shared/mcs.h diff --git a/Makefile.am b/Makefile.am index 27715c73d..23f6c1b98 100644 --- a/Makefile.am +++ b/Makefile.am @@ -231,6 +231,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \ src/shared/gap.h src/shared/gap.c \ src/shared/log.h src/shared/log.c \ src/shared/bap.h src/shared/bap.c src/shared/ascs.h \ + src shared/mcs.h src/shared/mcp.h src/shared/mcp.c \ src/shared/vcp.c src/shared/vcp.h \ src/shared/lc3.h src/shared/tty.h diff --git a/src/shared/mcp.c b/src/shared/mcp.c new file mode 100644 index 000000000..b3726ebae --- /dev/null +++ b/src/shared/mcp.c @@ -0,0 +1,1419 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "lib/hci.h" + +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "src/shared/timeout.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" +#include "src/shared/gatt-client.h" +#include "src/shared/mcp.h" +#include "src/shared/mcs.h" + +#define DBG(_mcp, fmt, arg...) \ + mcp_debug(_mcp, "%s:%s() " fmt, __FILE__, __func__, ## arg) + +struct bt_mcp_db { + struct gatt_db *db; + struct bt_mcs *mcs; +}; + +struct bt_mcp_pending { + unsigned int id; + struct bt_mcp *mcp; + bt_gatt_client_read_callback_t func; + void *user_data; +}; + +struct event_callback { + const struct bt_mcp_event_callback *cbs; + void *user_data; +}; + +struct bt_mcp_session_info { + uint8_t content_control_id; + uint32_t cp_op_supported; +}; + +struct bt_mcp { + int ref_count; + struct bt_gatt_client *client; + struct bt_mcp_db *ldb; + struct bt_mcp_db *rdb; + unsigned int mp_name_id; + unsigned int track_changed_id; + unsigned int track_title_id; + unsigned int track_duration_id; + unsigned int track_position_id; + unsigned int media_state_id; + unsigned int media_cp_id; + unsigned int media_cp_op_supported_id; + + struct bt_mcp_session_info session; + struct event_callback *cb; + + struct queue *pending; + + bt_mcp_debug_func_t debug_func; + bt_mcp_destroy_func_t debug_destroy; + void *debug_data; + void *user_data; +}; + +struct bt_mcs { + struct bt_mcp_db *mdb; + struct gatt_db_attribute *service; + struct gatt_db_attribute *mp_name; + struct gatt_db_attribute *track_changed; + struct gatt_db_attribute *track_changed_ccc; + struct gatt_db_attribute *track_title; + struct gatt_db_attribute *track_duration; + struct gatt_db_attribute *track_position; + struct gatt_db_attribute *playback_speed; + struct gatt_db_attribute *seeking_speed; + struct gatt_db_attribute *play_order; + struct gatt_db_attribute *play_order_supported; + struct gatt_db_attribute *media_state; + struct gatt_db_attribute *media_state_ccc; + struct gatt_db_attribute *media_cp; + struct gatt_db_attribute *media_cp_ccc; + struct gatt_db_attribute *media_cp_op_supportd; + struct gatt_db_attribute *content_control_id; + struct gatt_db_attribute *content_control_id_ccc; +}; + +static struct queue *mcp_db; + +static void mcp_debug(struct bt_mcp *mcp, const char *format, ...) +{ + va_list ap; + + if (!mcp || !format || !mcp->debug_func) + return; + + va_start(ap, format); + util_debug_va(mcp->debug_func, mcp->debug_data, format, ap); + va_end(ap); +} + +static bool mcp_db_match(const void *data, const void *match_data) +{ + const struct bt_mcp_db *mdb = data; + const struct gatt_db *db = match_data; + + return (mdb->db == db); +} + +static void mcp_db_free(void *data) +{ + struct bt_mcp_db *bdb = data; + + if (!bdb) + return; + + gatt_db_unref(bdb->db); + + free(bdb->mcs); + free(bdb); +} + +static void mcp_free(void *data) +{ + struct bt_mcp *mcp = data; + + DBG(mcp, ""); + + bt_mcp_detach(mcp); + + mcp_db_free(mcp->rdb); + + queue_destroy(mcp->pending, NULL); + + free(mcp); +} + +struct bt_mcp *bt_mcp_ref(struct bt_mcp *mcp) +{ + if (!mcp) + return NULL; + + __sync_fetch_and_add(&mcp->ref_count, 1); + + return mcp; +} + +void bt_mcp_unref(struct bt_mcp *mcp) +{ + if (!mcp) + return; + + if (__sync_sub_and_fetch(&mcp->ref_count, 1)) + return; + + mcp_free(mcp); +} + +bool bt_mcp_set_user_data(struct bt_mcp *mcp, void *user_data) +{ + if (!mcp) + return false; + + mcp->user_data = user_data; + + return true; +} + +void *bt_mcp_get_user_data(struct bt_mcp *mcp) +{ + if (!mcp) + return NULL; + + return mcp->user_data; +} + +bool bt_mcp_set_debug(struct bt_mcp *mcp, bt_mcp_debug_func_t func, + void *user_data, bt_mcp_destroy_func_t destroy) +{ + if (!mcp) + return false; + + if (mcp->debug_destroy) + mcp->debug_destroy(mcp->debug_data); + + mcp->debug_func = func; + mcp->debug_destroy = destroy; + mcp->debug_data = user_data; + + return true; +} + +static void mcs_mp_name_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + char mp_name[] = ""; + struct iovec iov; + + iov.iov_base = mp_name; + iov.iov_len = sizeof(mp_name); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_track_title_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + char track_title[] = ""; + struct iovec iov; + + iov.iov_base = track_title; + iov.iov_len = 0; + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_track_duration_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + int32_t track_duration = 0xFFFFFFFF; + struct iovec iov; + + iov.iov_base = &track_duration; + iov.iov_len = sizeof(track_duration); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_track_position_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + int32_t track_position = 0xFFFFFFFF; + struct iovec iov; + + iov.iov_base = &track_position; + iov.iov_len = sizeof(track_position); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_track_position_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + gatt_db_attribute_write_result(attrib, id, + BT_ATT_ERROR_INSUFFICIENT_RESOURCES); +} + +static void mcs_playback_speed_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + int8_t playback_speed = 0x00; + struct iovec iov; + + iov.iov_base = &playback_speed; + iov.iov_len = sizeof(playback_speed); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_playback_speed_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + gatt_db_attribute_write_result(attrib, id, + BT_ATT_ERROR_INSUFFICIENT_RESOURCES); +} + +static void mcs_seeking_speed_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + int8_t seeking_speed = 0x00; + struct iovec iov; + + iov.iov_base = &seeking_speed; + iov.iov_len = sizeof(seeking_speed); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_playing_order_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t playing_order = 0x01; + struct iovec iov; + + iov.iov_base = &playing_order; + iov.iov_len = sizeof(playing_order); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_playing_order_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + gatt_db_attribute_write_result(attrib, id, + BT_ATT_ERROR_INSUFFICIENT_RESOURCES); +} + +static void mcs_playing_order_supported_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint16_t playing_order_supported = 0x01; + struct iovec iov; + + iov.iov_base = &playing_order_supported; + iov.iov_len = sizeof(playing_order_supported); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_media_state_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t media_state = 0x00; + struct iovec iov; + + iov.iov_base = &media_state; + iov.iov_len = sizeof(media_state); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_media_cp_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + gatt_db_attribute_write_result(attrib, id, + BT_ATT_ERROR_INSUFFICIENT_RESOURCES); +} + +static void mcs_media_cp_op_supported_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint32_t cp_op_supported = 0x00000000; + struct iovec iov; + + iov.iov_base = &cp_op_supported; + iov.iov_len = sizeof(cp_op_supported); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void mcs_media_content_control_id_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t content_control_id = 0x00; + struct iovec iov; + + iov.iov_base = &content_control_id; + iov.iov_len = sizeof(content_control_id); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static struct bt_mcs *mcs_new(struct gatt_db *db) +{ + struct bt_mcs *mcs; + bt_uuid_t uuid; + + if (!db) + return NULL; + + mcs = new0(struct bt_mcs, 1); + + /* Populate DB with MCS attributes */ + bt_uuid16_create(&uuid, GMCS_UUID); + mcs->service = gatt_db_add_service(db, &uuid, true, 31); + + bt_uuid16_create(&uuid, MEDIA_PLAYER_NAME_CHRC_UUID); + mcs->mp_name = gatt_db_service_add_characteristic(mcs->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + mcs_mp_name_read, NULL, + mcs); + + bt_uuid16_create(&uuid, MEDIA_TRACK_CHNGD_CHRC_UUID); + mcs->track_changed = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_NONE, + BT_GATT_CHRC_PROP_NOTIFY, + NULL, NULL, + mcs); + + mcs->track_changed_ccc = gatt_db_service_add_ccc(mcs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, MEDIA_TRACK_TITLE_CHRC_UUID); + mcs->track_title = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + mcs_track_title_read, NULL, + mcs); + + bt_uuid16_create(&uuid, MEDIA_TRACK_DURATION_CHRC_UUID); + mcs->track_duration = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + mcs_track_duration_read, NULL, + mcs); + + bt_uuid16_create(&uuid, MEDIA_TRACK_POSTION_CHRC_UUID); + mcs->track_position = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_WRITE | + BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, + mcs_track_position_read, + mcs_track_position_write, + mcs); + + bt_uuid16_create(&uuid, MEDIA_PLAYBACK_SPEED_CHRC_UUID); + mcs->playback_speed = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_WRITE | + BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, + mcs_playback_speed_read, + mcs_playback_speed_write, + mcs); + + bt_uuid16_create(&uuid, MEDIA_SEEKING_SPEED_CHRC_UUID); + mcs->seeking_speed = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + mcs_seeking_speed_read, NULL, + mcs); + + bt_uuid16_create(&uuid, MEDIA_PLAYING_ORDER_CHRC_UUID); + mcs->play_order = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_WRITE | + BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, + mcs_playing_order_read, + mcs_playing_order_write, + mcs); + + bt_uuid16_create(&uuid, MEDIA_PLAY_ORDER_SUPPRTD_CHRC_UUID); + mcs->play_order_supported = gatt_db_service_add_characteristic( + mcs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + mcs_playing_order_supported_read, NULL, + mcs); + + bt_uuid16_create(&uuid, MEDIA_STATE_CHRC_UUID); + mcs->media_state = gatt_db_service_add_characteristic(mcs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + mcs_media_state_read, NULL, + mcs); + + mcs->media_state_ccc = gatt_db_service_add_ccc(mcs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, MEDIA_CP_CHRC_UUID); + mcs->media_cp = gatt_db_service_add_characteristic(mcs->service, &uuid, + BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE | + BT_GATT_CHRC_PROP_NOTIFY | + BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, + NULL, mcs_media_cp_write, + mcs); + + mcs->media_cp_ccc = gatt_db_service_add_ccc(mcs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, MEDIA_CP_OP_SUPPORTED_CHRC_UUID); + mcs->media_cp_op_supportd = gatt_db_service_add_characteristic( + mcs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + mcs_media_cp_op_supported_read, NULL, + mcs); + + bt_uuid16_create(&uuid, MEDIA_CONTENT_CONTROL_ID_CHRC_UUID); + mcs->content_control_id = gatt_db_service_add_characteristic( + mcs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + mcs_media_content_control_id_read, + NULL, + mcs); + + mcs->content_control_id_ccc = gatt_db_service_add_ccc(mcs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + gatt_db_service_set_active(mcs->service, false); + + return mcs; +} + +static struct bt_mcs *mcp_get_mcs(struct bt_mcp *mcp) +{ + if (!mcp) + return NULL; + + if (mcp->rdb->mcs) + return mcp->rdb->mcs; + + mcp->rdb->mcs = new0(struct bt_mcs, 1); + mcp->rdb->mcs->mdb = mcp->rdb; + + return mcp->rdb->mcs; +} + +static unsigned int mcp_send(struct bt_mcp *mcp, uint8_t operation) +{ + struct bt_mcs *mcs = mcp_get_mcs(mcp); + int ret; + uint16_t handle; + + DBG(mcp, "mcs %p", mcs); + + if (!mcp->client) + return -1; + + if (!gatt_db_attribute_get_char_data(mcs->media_cp, NULL, &handle, + NULL, NULL, NULL)) + return -1; + + ret = bt_gatt_client_write_without_response(mcp->client, handle, false, + &operation, sizeof(uint8_t)); + if (!ret) + return -1; + + return 0; +} + +unsigned int bt_mcp_play(struct bt_mcp *mcp) +{ + if (!(mcp->session.cp_op_supported & BT_MCS_CMD_PLAY_SUPPORTED)) + return -ENOTSUP; + + DBG(mcp, "mcp %p", mcp); + + return mcp_send(mcp, BT_MCS_CMD_PLAY); +} + +unsigned int bt_mcp_pause(struct bt_mcp *mcp) +{ + if (!(mcp->session.cp_op_supported & BT_MCS_CMD_PAUSE_SUPPORTED)) + return -ENOTSUP; + + DBG(mcp, "mcp %p", mcp); + + return mcp_send(mcp, BT_MCS_CMD_PAUSE); +} + +unsigned int bt_mcp_stop(struct bt_mcp *mcp) +{ + if (!(mcp->session.cp_op_supported & BT_MCS_CMD_STOP_SUPPORTED)) + return -ENOTSUP; + + DBG(mcp, "mcp %p", mcp); + + return mcp_send(mcp, BT_MCS_CMD_STOP); +} + +static void mcp_mp_set_player_name(struct bt_mcp *mcp, const uint8_t *value, + uint16_t length) +{ + struct event_callback *cb = mcp->cb; + + if (cb && cb->cbs && cb->cbs->player_name) + cb->cbs->player_name(mcp, value, length); +} + +static void mcp_mp_set_track_title(struct bt_mcp *mcp, const uint8_t *value, + uint16_t length) +{ + struct event_callback *cb = mcp->cb; + + if (cb && cb->cbs && cb->cbs->track_title) + cb->cbs->track_title(mcp, value, length); +} + +static void mcp_mp_set_title_duration(struct bt_mcp *mcp, int32_t duration) +{ + struct event_callback *cb = mcp->cb; + + DBG(mcp, "Track Duration 0x%08x", duration); + + if (cb && cb->cbs && cb->cbs->track_duration) + cb->cbs->track_duration(mcp, duration); +} + +static void mcp_mp_set_title_position(struct bt_mcp *mcp, int32_t position) +{ + struct event_callback *cb = mcp->cb; + + DBG(mcp, "Track Position 0x%08x", position); + + if (cb && cb->cbs && cb->cbs->track_position) + cb->cbs->track_position(mcp, position); +} + +static void mcp_mp_set_media_state(struct bt_mcp *mcp, uint8_t state) +{ + struct event_callback *cb = mcp->cb; + + DBG(mcp, "Media State 0x%02x", state); + + if (cb && cb->cbs && cb->cbs->media_state) + cb->cbs->media_state(mcp, state); +} + +static void read_media_player_name(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (!success) { + DBG(mcp, "Unable to read media player name: error 0x%02x", + att_ecode); + return; + } + + if (!length) + return; + + mcp_mp_set_player_name(mcp, value, length); +} + +static void read_track_title(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (!success) { + DBG(mcp, "Unable to read track title: error 0x%02x", + att_ecode); + return; + } + + if (!length) + return; + + mcp_mp_set_track_title(mcp, value, length); +} + +static void read_track_duration(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + int32_t duration; + + if (!success) { + DBG(mcp, "Unable to read track duration: error 0x%02x", + att_ecode); + return; + } + + if (length != sizeof(duration)) + DBG(mcp, "Wrong length received Length : %u", length); + + memcpy(&duration, value, length); + mcp_mp_set_title_duration(mcp, duration); +} + +static void read_track_position(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + int32_t position; + + if (!success) { + DBG(mcp, "Unable to read track position: error 0x%02x", + att_ecode); + return; + } + + if (length != sizeof(position)) + DBG(mcp, "Wrong length received Length : %u", length); + + memcpy(&position, value, length); + mcp_mp_set_title_position(mcp, position); +} + +static void read_media_state(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (!success) { + DBG(mcp, "Unable to read media state: error 0x%02x", + att_ecode); + return; + } + + if (length != sizeof(uint8_t)) + DBG(mcp, "Wrong length received Length : %u", length); + + mcp_mp_set_media_state(mcp, *value); +} + +static void read_media_cp_op_supported(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (!success) { + DBG(mcp, "Unable to read media CP OP supported: error 0x%02x", + att_ecode); + return; + } + + if (length != sizeof(uint32_t)) + DBG(mcp, "Wrong length received Length : %u", length); + + memcpy(&mcp->session.cp_op_supported, value, sizeof(uint32_t)); + DBG(mcp, "Media Control Point Opcodes Supported 0x%08x", + mcp->session.cp_op_supported); +} + +static void read_content_control_id(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (!success) { + DBG(mcp, "Unable to read content control id: error 0x%02x", + att_ecode); + return; + } + + if (length != sizeof(uint8_t)) + DBG(mcp, "Wrong length received Length : %u", length); + + DBG(mcp, "Content Control ID 0x%02x", *value); +} + +static void mcp_pending_destroy(void *data) +{ + struct bt_mcp_pending *pending = data; + struct bt_mcp *mcp = pending->mcp; + + queue_remove_if(mcp->pending, NULL, pending); +} + +static void mcp_pending_complete(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_mcp_pending *pending = user_data; + + if (pending->func) + pending->func(success, att_ecode, value, length, + pending->user_data); +} + +static void mcp_read_value(struct bt_mcp *mcp, uint16_t value_handle, + bt_gatt_client_read_callback_t func, + void *user_data) +{ + struct bt_mcp_pending *pending; + + pending = new0(struct bt_mcp_pending, 1); + pending->mcp = mcp; + pending->func = func; + pending->user_data = user_data; + + pending->id = bt_gatt_client_read_value(mcp->client, value_handle, + mcp_pending_complete, pending, + mcp_pending_destroy); + if (!pending->id) { + DBG(mcp, "Unable to send Read request"); + free(pending); + return; + } + + queue_push_tail(mcp->pending, pending); +} + +static void mcp_mp_name_register(uint16_t att_ecode, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Player Name notification failed: 0x%04x", + att_ecode); +} + +static void mcp_mp_name_notify(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (!length) + return; + + mcp_mp_set_player_name(mcp, value, length); +} + +static void mcp_track_changed_register(uint16_t att_ecode, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Track Changed notification failed: 0x%04x", + att_ecode); +} + +static void mcp_track_changed_notify(uint16_t value_handle, + const uint8_t *value, uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + struct event_callback *cb = mcp->cb; + + DBG(mcp, "Track Changed"); + + if (cb && cb->cbs && cb->cbs->track_changed) + cb->cbs->track_changed(mcp); +} + +static void mcp_track_title_register(uint16_t att_ecode, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Track Title notification failed: 0x%04x", + att_ecode); +} + +static void mcp_track_title_notify(uint16_t value_handle, + const uint8_t *value, uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + mcp_mp_set_track_title(mcp, value, length); +} + +static void mcp_track_duration_register(uint16_t att_ecode, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Track Duration notification failed: 0x%04x", + att_ecode); +} + +static void mcp_track_duration_notify(uint16_t value_handle, + const uint8_t *value, uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + int32_t duration; + + memcpy(&duration, value, sizeof(int32_t)); + mcp_mp_set_title_duration(mcp, duration); +} + +static void mcp_track_position_register(uint16_t att_ecode, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Track Position notification failed: 0x%04x", + att_ecode); +} + +static void mcp_track_position_notify(uint16_t value_handle, + const uint8_t *value, uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + int32_t position; + + memcpy(&position, value, sizeof(int32_t)); + mcp_mp_set_title_position(mcp, position); +} + +static void mcp_media_state_register(uint16_t att_ecode, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Media State notification failed: 0x%04x", + att_ecode); +} + +static void mcp_media_state_notify(uint16_t value_handle, + const uint8_t *value, uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + mcp_mp_set_media_state(mcp, *value); +} + +static void mcp_media_cp_register(uint16_t att_ecode, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Media CP notification failed: 0x%04x", + att_ecode); +} + +static void mcp_media_cp_notify(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + DBG(mcp, "Media CP Notification"); +} + +static void mcp_media_cp_op_supported_register(uint16_t att_ecode, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + + if (att_ecode) + DBG(mcp, "Media Media CP OP Supported notify failed: 0x%04x", + att_ecode); +} + +static void mcp_media_cp_op_supported_notify(uint16_t value_handle, + const uint8_t *value, uint16_t length, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + memcpy(&mcp->session.cp_op_supported, value, sizeof(uint32_t)); + DBG(mcp, "Media CP Opcodes Supported Notification 0x%08x", + mcp->session.cp_op_supported); +} + +static void bt_mcp_mp_name_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->mp_name, NULL, &value_handle, + NULL, NULL, NULL)) + return; + + DBG(mcp, "Media Player handle 0x%04x", value_handle); + + mcp_read_value(mcp, value_handle, read_media_player_name, mcp); + + mcp->mp_name_id = bt_gatt_client_register_notify(mcp->client, + value_handle, mcp_mp_name_register, + mcp_mp_name_notify, mcp, NULL); +} + +static void bt_mcp_track_changed_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->track_changed, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Track Changed handle 0x%04x", value_handle); + + mcp->track_changed_id = bt_gatt_client_register_notify(mcp->client, + value_handle, mcp_track_changed_register, + mcp_track_changed_notify, mcp, NULL); +} + +static void bt_mcp_track_title_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->track_title, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Track Title handle 0x%04x", value_handle); + + mcp_read_value(mcp, value_handle, read_track_title, mcp); + + mcp->track_title_id = bt_gatt_client_register_notify(mcp->client, + value_handle, mcp_track_title_register, + mcp_track_title_notify, mcp, NULL); +} + +static void bt_mcp_track_duration_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->track_duration, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Track Duration handle 0x%04x", value_handle); + + mcp_read_value(mcp, value_handle, read_track_duration, mcp); + + mcp->track_duration_id = bt_gatt_client_register_notify(mcp->client, + value_handle, mcp_track_duration_register, + mcp_track_duration_notify, mcp, NULL); +} + +static void bt_mcp_track_position_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->track_position, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Track Position handle 0x%04x", value_handle); + + mcp_read_value(mcp, value_handle, read_track_position, mcp); + + mcp->track_position_id = bt_gatt_client_register_notify(mcp->client, + value_handle, mcp_track_position_register, + mcp_track_position_notify, mcp, NULL); +} + +static void bt_mcp_media_state_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->media_state, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Media State handle 0x%04x", value_handle); + + mcp_read_value(mcp, value_handle, read_media_state, mcp); + + mcp->media_state_id = bt_gatt_client_register_notify(mcp->client, + value_handle, mcp_media_state_register, + mcp_media_state_notify, mcp, NULL); +} + +static void bt_mcp_media_cp_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->media_cp, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Media Control Point handle 0x%04x", value_handle); + + mcp->media_cp_id = bt_gatt_client_register_notify(mcp->client, + value_handle, mcp_media_cp_register, + mcp_media_cp_notify, mcp, NULL); +} + +static void bt_mcp_media_cp_op_supported_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->media_cp_op_supportd, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Media Control Point Opcodes Supported handle 0x%04x", + value_handle); + + mcp_read_value(mcp, value_handle, read_media_cp_op_supported, mcp); + + mcp->media_cp_op_supported_id = bt_gatt_client_register_notify( + mcp->client, value_handle, mcp_media_cp_op_supported_register, + mcp_media_cp_op_supported_notify, mcp, NULL); +} + +static void bt_mcp_content_control_id_supported_attach(struct bt_mcp *mcp) +{ + uint16_t value_handle; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + if (!gatt_db_attribute_get_char_data(mcs->content_control_id, NULL, + &value_handle, NULL, NULL, NULL)) + return; + + DBG(mcp, "Media Content Control id Supported handle 0x%04x", + value_handle); + mcp_read_value(mcp, value_handle, read_content_control_id, mcp); +} + +static void foreach_mcs_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_mcp *mcp = user_data; + uint16_t value_handle; + bt_uuid_t uuid, uuid_mp_name, uuid_track_changed, uuid_track_title, + uuid_track_duration, uuid_track_position, uuid_media_state, + uuid_media_cp, uuid_media_cp_op_supported, + uuid_content_control_id; + struct bt_mcs *mcs; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, + NULL, NULL, &uuid)) + return; + + bt_uuid16_create(&uuid_mp_name, MEDIA_PLAYER_NAME_CHRC_UUID); + bt_uuid16_create(&uuid_track_changed, MEDIA_TRACK_CHNGD_CHRC_UUID); + bt_uuid16_create(&uuid_track_title, MEDIA_TRACK_TITLE_CHRC_UUID); + bt_uuid16_create(&uuid_track_duration, MEDIA_TRACK_DURATION_CHRC_UUID); + bt_uuid16_create(&uuid_track_position, MEDIA_TRACK_POSTION_CHRC_UUID); + bt_uuid16_create(&uuid_media_state, MEDIA_STATE_CHRC_UUID); + bt_uuid16_create(&uuid_media_cp, MEDIA_CP_CHRC_UUID); + bt_uuid16_create(&uuid_media_cp_op_supported, + MEDIA_CP_OP_SUPPORTED_CHRC_UUID); + bt_uuid16_create(&uuid_content_control_id, + MEDIA_CONTENT_CONTROL_ID_CHRC_UUID); + + if (!bt_uuid_cmp(&uuid, &uuid_mp_name)) { + DBG(mcp, "Media Player Name found: handle 0x%04x", + value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->mp_name) + return; + + mcs->mp_name = attr; + bt_mcp_mp_name_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_track_changed)) { + DBG(mcp, "Track Changed found: handle 0x%04x", value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->track_changed) + return; + + mcs->track_changed = attr; + bt_mcp_track_changed_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_track_title)) { + DBG(mcp, "Track Title found: handle 0x%04x", value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->track_title) + return; + + mcs->track_title = attr; + bt_mcp_track_title_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_track_duration)) { + DBG(mcp, "Track Duration found: handle 0x%04x", value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->track_duration) + return; + + mcs->track_duration = attr; + bt_mcp_track_duration_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_track_position)) { + DBG(mcp, "Track Position found: handle 0x%04x", value_handle); + + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->track_position) + return; + + mcs->track_position = attr; + bt_mcp_track_position_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_media_state)) { + DBG(mcp, "Media State found: handle 0x%04x", value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->media_state) + return; + + mcs->media_state = attr; + bt_mcp_media_state_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_media_cp)) { + DBG(mcp, "Media Control Point found: handle 0x%04x", + value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->media_cp) + return; + + mcs->media_cp = attr; + bt_mcp_media_cp_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_media_cp_op_supported)) { + DBG(mcp, "Media CP Opcodes Supported found: handle 0x%04x", + value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->media_cp_op_supportd) + return; + + mcs->media_cp_op_supportd = attr; + bt_mcp_media_cp_op_supported_attach(mcp); + } + + if (!bt_uuid_cmp(&uuid, &uuid_content_control_id)) { + DBG(mcp, "Content Control ID found: handle 0x%04x", + value_handle); + + mcs = mcp_get_mcs(mcp); + if (!mcs || mcs->content_control_id) + return; + + mcs->content_control_id = attr; + bt_mcp_content_control_id_supported_attach(mcp); + } +} + +void bt_mcp_set_event_callbacks(struct bt_mcp *mcp, + const struct bt_mcp_event_callback *cbs, + void *user_data) +{ + struct event_callback *cb; + + if (mcp->cb) + free(mcp->cb); + + cb = new0(struct event_callback, 1); + cb->cbs = cbs; + cb->user_data = user_data; + + mcp->cb = cb; +} + +static void foreach_mcs_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_mcp *mcp = user_data; + struct bt_mcs *mcs = mcp_get_mcs(mcp); + + DBG(mcp, ""); + + mcs->service = attr; + + gatt_db_service_foreach_char(attr, foreach_mcs_char, mcp); +} + +static struct bt_mcp_db *mcp_db_new(struct gatt_db *db) +{ + struct bt_mcp_db *mdb; + + if (!db) + return NULL; + + mdb = new0(struct bt_mcp_db, 1); + mdb->db = gatt_db_ref(db); + + if (!mcp_db) + mcp_db = queue_new(); + + queue_push_tail(mcp_db, mdb); + + mdb->mcs = mcs_new(db); + return mdb; +} + +static struct bt_mcp_db *mcp_get_db(struct gatt_db *db) +{ + struct bt_mcp_db *mdb; + + mdb = queue_find(mcp_db, mcp_db_match, db); + if (mdb) + return mdb; + + return mcp_db_new(db); +} + +struct bt_mcp *bt_mcp_new(struct gatt_db *ldb, struct gatt_db *rdb) +{ + struct bt_mcp *mcp; + struct bt_mcp_db *mdb; + + if (!ldb) + return NULL; + + mdb = mcp_get_db(ldb); + if (!mdb) + return NULL; + + mcp = new0(struct bt_mcp, 1); + mcp->ldb = mdb; + mcp->pending = queue_new(); + + if (!rdb) + goto done; + + mdb = new0(struct bt_mcp_db, 1); + mdb->db = gatt_db_ref(rdb); + + mcp->rdb = mdb; + +done: + bt_mcp_ref(mcp); + + return mcp; +} + +void bt_mcp_register(struct gatt_db *db) +{ + mcp_db_new(db); +} + +bool bt_mcp_attach(struct bt_mcp *mcp, struct bt_gatt_client *client) +{ + bt_uuid_t uuid; + + DBG(mcp, "mcp %p", mcp); + + mcp->client = bt_gatt_client_clone(client); + if (!mcp->client) + return false; + + if (mcp->rdb->mcs) { + bt_mcp_mp_name_attach(mcp); + bt_mcp_track_changed_attach(mcp); + bt_mcp_track_title_attach(mcp); + bt_mcp_track_duration_attach(mcp); + bt_mcp_track_position_attach(mcp); + bt_mcp_media_state_attach(mcp); + bt_mcp_media_cp_attach(mcp); + bt_mcp_media_cp_op_supported_attach(mcp); + bt_mcp_content_control_id_supported_attach(mcp); + + return true; + } + + bt_uuid16_create(&uuid, GMCS_UUID); + gatt_db_foreach_service(mcp->rdb->db, &uuid, foreach_mcs_service, mcp); + + return true; +} + +void bt_mcp_detach(struct bt_mcp *mcp) +{ + DBG(mcp, "%p", mcp); + + bt_gatt_client_unref(mcp->client); + mcp->client = NULL; +} diff --git a/src/shared/mcp.h b/src/shared/mcp.h new file mode 100644 index 000000000..a2cd6fc45 --- /dev/null +++ b/src/shared/mcp.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + */ + +#include +#include + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +struct bt_mcp; +struct bt_mcp_db; +struct bt_mcp_session_info; + +typedef void (*bt_mcp_debug_func_t)(const char *str, void *user_data); +typedef void (*bt_mcp_destroy_func_t)(void *user_data); + +struct bt_mcp_event_callback { + void (*player_name)(struct bt_mcp *mcp, const uint8_t *value, + uint16_t length); + void (*track_changed)(struct bt_mcp *mcp); + void (*track_title)(struct bt_mcp *mcp, const uint8_t *value, + uint16_t length); + void (*track_duration)(struct bt_mcp *mcp, int32_t duration); + void (*track_position)(struct bt_mcp *mcp, int32_t position); + void (*playback_speed)(struct bt_mcp *mcp, int8_t speed); + void (*seeking_speed)(struct bt_mcp *mcp, int8_t speed); + void (*play_order)(struct bt_mcp *mcp, uint8_t order); + void (*play_order_supported)(struct bt_mcp *mcp, + uint16_t order_supported); + void (*media_state)(struct bt_mcp *mcp, uint8_t state); + void (*content_control_id)(struct bt_mcp *mcp, uint8_t cc_id); +}; + +void bt_mcp_set_event_callbacks(struct bt_mcp *mcp, + const struct bt_mcp_event_callback *cbs, + void *user_data); + +bool bt_mcp_set_debug(struct bt_mcp *mcp, bt_mcp_debug_func_t cb, + void *user_data, bt_mcp_destroy_func_t destroy); + +void bt_mcp_register(struct gatt_db *db); +bool bt_mcp_attach(struct bt_mcp *mcp, struct bt_gatt_client *client); +void bt_mcp_detach(struct bt_mcp *mcp); + +struct bt_mcp *bt_mcp_new(struct gatt_db *ldb, struct gatt_db *rdb); +struct bt_mcp *bt_mcp_ref(struct bt_mcp *mcp); +void bt_mcp_unref(struct bt_mcp *mcp); + +bool bt_mcp_set_user_data(struct bt_mcp *mcp, void *user_data); +void *bt_mcp_get_user_data(struct bt_mcp *mcp); + +unsigned int bt_mcp_play(struct bt_mcp *mcp); +unsigned int bt_mcp_pause(struct bt_mcp *mcp); +unsigned int bt_mcp_stop(struct bt_mcp *mcp); diff --git a/src/shared/mcs.h b/src/shared/mcs.h new file mode 100644 index 000000000..09b3bffe8 --- /dev/null +++ b/src/shared/mcs.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + */ + +/* MCP Media State */ +#define BT_MCS_STATUS_INACTIVE 0x00 +#define BT_MCS_STATUS_PLAYING 0x01 +#define BT_MCS_STATUS_PAUSED 0x02 +#define BT_MCS_STATUS_SEEKING 0x03 + +/* MCP Control Point Opcodes */ +#define BT_MCS_CMD_PLAY 0x01 +#define BT_MCS_CMD_PAUSE 0x02 +#define BT_MCS_CMD_FAST_REWIND 0x03 +#define BT_MCS_CMD_FAST_FORWARD 0x04 +#define BT_MCS_CMD_STOP 0x05 + +#define BT_MCS_CMD_MOVE_RELATIVE 0x10 + +#define BT_MCS_CMD_PREV_SEGMENT 0x20 +#define BT_MCS_CMD_NEXT_SEGMENT 0x21 +#define BT_MCS_CMD_FIRST_SEGMENT 0x22 +#define BT_MCS_CMD_LAST_SEGMENT 0x23 +#define BT_MCS_CMD_GOTO_SEGMENT 0x24 + +#define BT_MCS_CMD_PREV_TRACK 0x30 +#define BT_MCS_CMD_NEXT_TRACK 0x31 +#define BT_MCS_CMD_FIRST_TRACK 0x32 +#define BT_MCS_CMD_LAST_TRACK 0x33 +#define BT_MCS_CMD_GOTO_TRACK 0x34 + +#define BT_MCS_CMD_PREV_GROUP 0x40 +#define BT_MCS_CMD_NEXT_GROUP 0x41 +#define BT_MCS_CMD_FIRST_GROUP 0x42 +#define BT_MCS_CMD_LAST_GROUP 0x43 +#define BT_MCS_CMD_GOTO_GROUP 0x44 + + +/* MCP Control Point Opcodes Supported */ +#define BT_MCS_CMD_PLAY_SUPPORTED 0x00000001 +#define BT_MCS_CMD_PAUSE_SUPPORTED 0x00000002 +#define BT_MCS_CMD_FAST_REWIND_SUPPORTED 0x00000004 +#define BT_MCS_CMD_FAST_FORWARD_SUPPORTED 0x00000008 +#define BT_MCS_CMD_STOP_SUPPORTED 0x00000010 +#define BT_MCS_CMD_MOVE_RELATIVE_SUPPORTED 0x00000020 +#define BT_MCS_CMD_PREV_SEGMENT_SUPPORTED 0x00000040 +#define BT_MCS_CMD_NEXT_SEGMENT_SUPPORTED 0x00000080 +#define BT_MCS_CMD_FIRST_SEGMENT_SUPPORTED 0x00000100 +#define BT_MCS_CMD_LAST_SEGMENT_SUPPORTED 0x00000200 +#define BT_MCS_CMD_GOTO_SEGMENT_SUPPORTED 0x00000400 +#define BT_MCS_CMD_PREV_TRACK_SUPPORTED 0x00000800 +#define BT_MCS_CMD_NEXT_TRACK_SUPPORTED 0x00001000 +#define BT_MCS_CMD_FIRST_TRACK_SUPPORTED 0x00002000 +#define BT_MCS_CMD_LAST_TRACK_SUPPORTED 0x00004000 +#define BT_MCS_CMD_GOTO_TRACK_SUPPORTED 0x00008000 +#define BT_MCS_CMD_PREV_GROUP_SUPPORTED 0x00010000 +#define BT_MCS_CMD_NEXT_GROUP_SUPPORTED 0x00020000 +#define BT_MCS_CMD_FIRST_GROUP_SUPPORTED 0x00040000 +#define BT_MCS_CMD_LAST_GROUP_SUPPORTED 0x00080000 +#define BT_MCS_CMD_GOTO_GROUP_SUPPORTED 0x00100000 From patchwork Tue Oct 18 04:38:30 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 617524 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 73FBEC433FE for ; Tue, 18 Oct 2022 04:38:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229885AbiJREix (ORCPT ); Tue, 18 Oct 2022 00:38:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52474 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229725AbiJREiv (ORCPT ); Tue, 18 Oct 2022 00:38:51 -0400 Received: from mga09.intel.com (mga09.intel.com [134.134.136.24]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A8F9CA02D2 for ; Mon, 17 Oct 2022 21:38:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1666067930; x=1697603930; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=N55/Oir4zOlGqXoTgc9T3T0GGUb43fyBGZiRheDgkJ8=; b=bMZr5jgD3WRbM2cllKm7+7M5/y9sboGCmqkRgUQ3lMqFvV3cMgipXUp0 gCzFpJWdT3AGfHe+RtkNvKZy1ZIIEgb1KhM2GWhHbw/CWuF3rYdLNJ+y9 ElAZWbPUylDOnViHROjdMCZiPVXyNhzCkRmX6+JYfjAdqk6z+Mm0NT1+b 9msegKullfL1S3Jjm8H3EnYnht/2FjZLpi6q8MOpA6YOIfpnpnFCPrp1q Zz9joi5dCHe5ZpntColTLModX22Xx17ea7wDnUnnOSEtivbeXT7i2eghk ifESHltoLfPgiiFR3nG0g6ncBYCwj8bOf2F+qd+y9NseMOMYMX2FMyLcQ g==; X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="307079225" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="307079225" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by orsmga102.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 17 Oct 2022 21:38:50 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="957608044" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="957608044" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga005.fm.intel.com with ESMTP; 17 Oct 2022 21:38:48 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ v3 3/4] profiles: Add initial code for mcp plugin Date: Tue, 18 Oct 2022 10:08:30 +0530 Message-Id: <20221018043831.342821-4-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> References: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org This adds initial code for mcp plugin which handles Media Control Profile and Generic Media Control Service for Client Role. The plugin exposes dbus methods for media control operations like play, pause which can be invoked using player menu in bluetoothctl. --- Makefile.plugins | 5 + configure.ac | 4 + profiles/audio/mcp.c | 430 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 439 insertions(+) create mode 100644 profiles/audio/mcp.c diff --git a/Makefile.plugins b/Makefile.plugins index a3654980f..20cac384e 100644 --- a/Makefile.plugins +++ b/Makefile.plugins @@ -122,6 +122,11 @@ builtin_modules += bap builtin_sources += profiles/audio/bap.c endif +if MCP +builtin_modules += mcp +builtin_sources += profiles/audio/mcp.c +endif + if VCP builtin_modules += vcp builtin_sources += profiles/audio/vcp.c diff --git a/configure.ac b/configure.ac index 79645e691..363a222a7 100644 --- a/configure.ac +++ b/configure.ac @@ -199,6 +199,10 @@ AC_ARG_ENABLE(bap, AS_HELP_STRING([--disable-bap], [disable BAP profile]), [enable_bap=${enableval}]) AM_CONDITIONAL(BAP, test "${enable_bap}" != "no") +AC_ARG_ENABLE(mcp, AS_HELP_STRING([--disable-mcp], + [disable MCP profile]), [enable_mcp=${enableval}]) +AM_CONDITIONAL(MCP, test "${enable_mcp}" != "no") + AC_ARG_ENABLE(vcp, AS_HELP_STRING([--disable-vcp], [disable VCP profile]), [enable_vcp=${enableval}]) AM_CONDITIONAL(VCP, test "${enable_vcp}" != "no") diff --git a/profiles/audio/mcp.c b/profiles/audio/mcp.c new file mode 100644 index 000000000..f3ea330f9 --- /dev/null +++ b/profiles/audio/mcp.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/dbus-common.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/gatt-server.h" +#include "src/shared/mcp.h" +#include "src/shared/mcs.h" + +#include "btio/btio.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/gatt-database.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" +#include "src/error.h" +#include "player.h" + +#define GMCS_UUID_STR "00001849-0000-1000-8000-00805f9b34fb" + +struct mcp_data { + struct btd_device *device; + struct btd_service *service; + struct bt_mcp *mcp; + unsigned int state_id; + + struct media_player *mp; +}; + +static void mcp_debug(const char *str, void *user_data) +{ + DBG_IDX(0xffff, "%s", str); +} + +static char *name2utf8(const uint8_t *name, uint16_t len) +{ + char utf8_name[HCI_MAX_NAME_LENGTH + 2]; + int i; + + if (g_utf8_validate((const char *) name, len, NULL)) + return g_strndup((char *) name, len); + + len = MIN(len, sizeof(utf8_name) - 1); + + memset(utf8_name, 0, sizeof(utf8_name)); + strncpy(utf8_name, (char *) name, len); + + /* Assume ASCII, and replace all non-ASCII with spaces */ + for (i = 0; utf8_name[i] != '\0'; i++) { + if (!isascii(utf8_name[i])) + utf8_name[i] = ' '; + } + + /* Remove leading and trailing whitespace characters */ + g_strstrip(utf8_name); + + return g_strdup(utf8_name); +} + +static const char *mcp_status_val_to_string(uint8_t status) +{ + switch (status) { + case BT_MCS_STATUS_PLAYING: + return "playing"; + case BT_MCS_STATUS_PAUSED: + return "paused"; + case BT_MCS_STATUS_INACTIVE: + return "stopped"; + case BT_MCS_STATUS_SEEKING: + /* TODO: find a way for fwd/rvs seeking, probably by storing + * control point operation sent before + */ + return "forward-seek"; + default: + return "error"; + } +} + +static struct mcp_data *mcp_data_new(struct btd_device *device) +{ + struct mcp_data *data; + + data = new0(struct mcp_data, 1); + data->device = device; + + return data; +} + +static void cb_player_name(struct bt_mcp *mcp, const uint8_t *value, + uint16_t length) +{ + char *name; + struct media_player *mp = bt_mcp_get_user_data(mcp); + + name = name2utf8(value, length); + DBG("Media Player Name %s", (const char *)name); + + media_player_set_name(mp, name); + + g_free(name); +} + +static void cb_track_changed(struct bt_mcp *mcp) +{ + DBG("Track Changed"); + /* Since track changed has happened + * track title notification is expected + */ +} + +static void cb_track_title(struct bt_mcp *mcp, const uint8_t *value, + uint16_t length) +{ + char *name; + uint16_t len; + struct media_player *mp = bt_mcp_get_user_data(mcp); + + name = name2utf8(value, length); + len = strlen(name); + + DBG("Track Title %s", (const char *)name); + + media_player_set_metadata(mp, NULL, "Title", name, len); + media_player_metadata_changed(mp); + + g_free(name); +} + +static void cb_track_duration(struct bt_mcp *mcp, int32_t duration) +{ + struct media_player *mp = bt_mcp_get_user_data(mcp); + unsigned char buf[10]; + + /* MCP defines duration is int32 but api takes it as uint32 */ + sprintf((char *)buf, "%d", duration); + media_player_set_metadata(mp, NULL, "Duration", buf, sizeof(buf)); + media_player_metadata_changed(mp); +} + +static void cb_track_position(struct bt_mcp *mcp, int32_t duration) +{ + struct media_player *mp = bt_mcp_get_user_data(mcp); + + /* MCP defines duration is int32 but api takes it as uint32 */ + media_player_set_position(mp, duration); +} + +static void cb_media_state(struct bt_mcp *mcp, uint8_t status) +{ + struct media_player *mp = bt_mcp_get_user_data(mcp); + + media_player_set_status(mp, mcp_status_val_to_string(status)); +} + +static const struct bt_mcp_event_callback cbs = { + .player_name = &cb_player_name, + .track_changed = &cb_track_changed, + .track_title = &cb_track_title, + .track_duration = &cb_track_duration, + .track_position = &cb_track_position, + .playback_speed = NULL, + .seeking_speed = NULL, + .play_order = NULL, + .play_order_supported = NULL, + .media_state = &cb_media_state, + .content_control_id = NULL, +}; + +static int ct_play(struct media_player *mp, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + return bt_mcp_play(mcp); +} + +static int ct_pause(struct media_player *mp, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + return bt_mcp_pause(mcp); +} + +static int ct_stop(struct media_player *mp, void *user_data) +{ + struct bt_mcp *mcp = user_data; + + return bt_mcp_stop(mcp); +} + +static const struct media_player_callback ct_cbs = { + .set_setting = NULL, + .play = &ct_play, + .pause = &ct_pause, + .stop = &ct_stop, + .next = NULL, + .previous = NULL, + .fast_forward = NULL, + .rewind = NULL, + .press = NULL, + .hold = NULL, + .release = NULL, + .list_items = NULL, + .change_folder = NULL, + .search = NULL, + .play_item = NULL, + .add_to_nowplaying = NULL, + .total_items = NULL, +}; + +static int mcp_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct btd_adapter *adapter = device_get_adapter(device); + struct btd_gatt_database *database = btd_adapter_get_database(adapter); + struct mcp_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + /* Ignore, if we were probed for this device already */ + if (data) { + error("Profile probed twice for the same device!"); + return -EINVAL; + } + + data = mcp_data_new(device); + data->service = service; + + data->mcp = bt_mcp_new(btd_gatt_database_get_db(database), + btd_device_get_gatt_db(device)); + + bt_mcp_set_debug(data->mcp, mcp_debug, NULL, NULL); + btd_service_set_user_data(service, data); + + return 0; +} + +static void mcp_data_free(struct mcp_data *data) +{ + DBG(""); + + if (data->service) { + btd_service_set_user_data(data->service, NULL); + bt_mcp_set_user_data(data->mcp, NULL); + } + + if (data->mp) { + media_player_destroy(data->mp); + data->mp = NULL; + } + + bt_mcp_unref(data->mcp); + free(data); +} + +static void mcp_data_remove(struct mcp_data *data) +{ + DBG("data %p", data); + + mcp_data_free(data); +} + +static void mcp_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct mcp_data *data; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + data = btd_service_get_user_data(service); + if (!data) { + error("MCP service not handled by profile"); + return; + } + + mcp_data_remove(data); +} + +static int mcp_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + struct mcp_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + bt_mcp_attach(data->mcp, client); + + data->mp = media_player_controller_create(device_get_path(device), 0); + if (data->mp == NULL) { + DBG("Unable to create Media Player"); + return -EINVAL; + } + + media_player_set_callbacks(data->mp, &ct_cbs, data->mcp); + + bt_mcp_set_user_data(data->mcp, data->mp); + bt_mcp_set_event_callbacks(data->mcp, &cbs, data->mp); + btd_service_connecting_complete(service, 0); + + return 0; +} + +static int mcp_connect(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + return 0; +} + +static int mcp_disconnect(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct mcp_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + if (data->mp) { + media_player_destroy(data->mp); + data->mp = NULL; + } + + bt_mcp_detach(data->mcp); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static int media_control_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct btd_gatt_database *database = btd_adapter_get_database(adapter); + + bt_mcp_register(btd_gatt_database_get_db(database)); + + return 0; +} + +static void media_control_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + +} + +static struct btd_profile mcp_profile = { + .name = "mcp", + .priority = BTD_PROFILE_PRIORITY_MEDIUM, + .remote_uuid = GMCS_UUID_STR, + .device_probe = mcp_probe, + .device_remove = mcp_remove, + .accept = mcp_accept, + .connect = mcp_connect, + .disconnect = mcp_disconnect, + + .adapter_probe = media_control_server_probe, + .adapter_remove = media_control_server_remove, +}; + +static int mcp_init(void) +{ + DBG(""); + + if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { + warn("D-Bus experimental not enabled"); + return -ENOTSUP; + } + + btd_profile_register(&mcp_profile); + return 0; +} + +static void mcp_exit(void) +{ + DBG(""); + + if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) + btd_profile_unregister(&mcp_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(mcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + mcp_init, mcp_exit) From patchwork Tue Oct 18 04:38:31 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 616128 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E36D2C4332F for ; Tue, 18 Oct 2022 04:38:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229725AbiJREiz (ORCPT ); Tue, 18 Oct 2022 00:38:55 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52518 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229895AbiJREix (ORCPT ); Tue, 18 Oct 2022 00:38:53 -0400 Received: from mga09.intel.com (mga09.intel.com [134.134.136.24]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 67553A02DF for ; Mon, 17 Oct 2022 21:38:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1666067932; x=1697603932; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=ic0z+lCR/WMmhpze3jCEnItcONdGcq+pyqBcAO8MJlI=; b=cpk3/h4BBAx0OtjQYNF3XIgngx6jnHOVdaRll6zG5xg/yFvpib3muWPQ +69iw2ZqeyZJhNJ+NQ2rC3FVIS8TyLv7tKjDX5DfkDS9ywaYD3N0vHUJr UcFhDHNQz2djjs2yFRLHWhc4xvEeLMvsU7oAUzU4cvysdvdiom5z1FXFV YgeN8jMjicnO5ymq3DBfVNpkjavRMPjcRahOJ02h8OR3wIsrxXVF1WP4c BGyK08egDo2IaiYpKSfTUhOT1QI5Mw7hPCa4pvLYYmepGuOvncCg+fNhK pLfvIcptkTiG3V1UXx/GVl9Mwl5YKif8ibEMsx/d7V52f9jCDmAAa48u8 g==; X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="307079226" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="307079226" Received: from fmsmga005.fm.intel.com ([10.253.24.32]) by orsmga102.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 17 Oct 2022 21:38:51 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10503"; a="957608048" X-IronPort-AV: E=Sophos;i="5.95,193,1661842800"; d="scan'208";a="957608048" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga005.fm.intel.com with ESMTP; 17 Oct 2022 21:38:50 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ v3 4/4] monitor/att: Add decoding support for GMCS Date: Tue, 18 Oct 2022 10:08:31 +0530 Message-Id: <20221018043831.342821-5-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> References: <20221018043831.342821-1-abhay.maheshbhai.maheta@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org This adds decoding support for GMCS attributes. < ACL Data TX: Handle 3585 flags 0x00 dlen 7 ATT: Read Request (0x0a) len 2 Handle: 0x0056 Type: Media Control Point Opcodes Supported (0x2ba5) > ACL Data RX: Handle 3585 flags 0x02 dlen 9 ATT: Read Response (0x0b) len 4 Value: 33180000 Handle: 0x0056 Type: Media Control Point Opcodes Supported (0x2ba5) Supported Opcodes: 0x00001833 Play (0x00000001) Pause (0x00000002) Stop (0x00000010) Move Relative (0x00000020) Previous Track (0x00000800) Next Track (0x00001000) --- monitor/att.c | 513 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 513 insertions(+) diff --git a/monitor/att.c b/monitor/att.c index f5fc32cb0..491f196bf 100644 --- a/monitor/att.c +++ b/monitor/att.c @@ -14,6 +14,7 @@ #endif #define _GNU_SOURCE +#include #include #include #include @@ -22,6 +23,8 @@ #include #include +#include + #include "lib/bluetooth.h" #include "lib/uuid.h" #include "lib/hci.h" @@ -1746,6 +1749,499 @@ static void vol_flag_notify(const struct l2cap_frame *frame) print_vcs_flag(frame); } +static char *name2utf8(const uint8_t *name, uint16_t len) +{ + char utf8_name[HCI_MAX_NAME_LENGTH + 2]; + int i; + + if (g_utf8_validate((const char *) name, len, NULL)) + return g_strndup((char *) name, len); + + len = MIN(len, sizeof(utf8_name) - 1); + + memset(utf8_name, 0, sizeof(utf8_name)); + strncpy(utf8_name, (char *) name, len); + + /* Assume ASCII, and replace all non-ASCII with spaces */ + for (i = 0; utf8_name[i] != '\0'; i++) { + if (!isascii(utf8_name[i])) + utf8_name[i] = ' '; + } + + /* Remove leading and trailing whitespace characters */ + g_strstrip(utf8_name); + + return g_strdup(utf8_name); +} + +static void print_mp_name(const struct l2cap_frame *frame) +{ + char *name; + + name = name2utf8((uint8_t *)frame->data, frame->size); + + print_field(" Media Player Name: %s", name); +} + +static void mp_name_read(const struct l2cap_frame *frame) +{ + print_mp_name(frame); +} + +static void mp_name_notify(const struct l2cap_frame *frame) +{ + print_mp_name(frame); +} + +static void print_track_changed(const struct l2cap_frame *frame) +{ + print_field(" Track Changed"); +} + +static void track_changed_notify(const struct l2cap_frame *frame) +{ + print_track_changed(frame); +} + +static void print_track_title(const struct l2cap_frame *frame) +{ + char *name; + + name = name2utf8((uint8_t *)frame->data, frame->size); + + print_field(" Track Title: %s", name); +} + +static void track_title_read(const struct l2cap_frame *frame) +{ + print_track_title(frame); +} + +static void track_title_notify(const struct l2cap_frame *frame) +{ + print_track_title(frame); +} + +static void print_track_duration(const struct l2cap_frame *frame) +{ + int32_t duration; + + if (!l2cap_frame_get_le32((void *)frame, (uint32_t *)&duration)) { + print_text(COLOR_ERROR, " Track Duration: invalid size"); + goto done; + } + + print_field(" Track Duration: %u", duration); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void track_duration_read(const struct l2cap_frame *frame) +{ + print_track_duration(frame); +} + +static void track_duration_notify(const struct l2cap_frame *frame) +{ + print_track_duration(frame); +} + +static void print_track_position(const struct l2cap_frame *frame) +{ + int32_t position; + + if (!l2cap_frame_get_le32((void *)frame, (uint32_t *)&position)) { + print_text(COLOR_ERROR, " Track Position: invalid size"); + goto done; + } + + print_field(" Track Position: %u", position); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void track_position_read(const struct l2cap_frame *frame) +{ + print_track_position(frame); +} + +static void track_position_write(const struct l2cap_frame *frame) +{ + print_track_position(frame); +} + +static void track_position_notify(const struct l2cap_frame *frame) +{ + print_track_position(frame); +} + +static void print_playback_speed(const struct l2cap_frame *frame) +{ + int8_t playback_speed; + + if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&playback_speed)) { + print_text(COLOR_ERROR, " Playback Speed: invalid size"); + goto done; + } + + print_field(" Playback Speed: %u", playback_speed); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void playback_speed_read(const struct l2cap_frame *frame) +{ + print_playback_speed(frame); +} + +static void playback_speed_write(const struct l2cap_frame *frame) +{ + print_playback_speed(frame); +} + +static void playback_speed_notify(const struct l2cap_frame *frame) +{ + print_playback_speed(frame); +} + +static void print_seeking_speed(const struct l2cap_frame *frame) +{ + int8_t seeking_speed; + + if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&seeking_speed)) { + print_text(COLOR_ERROR, " Seeking Speed: invalid size"); + goto done; + } + + print_field(" Seeking Speed: %u", seeking_speed); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void seeking_speed_read(const struct l2cap_frame *frame) +{ + print_seeking_speed(frame); +} + +static void seeking_speed_notify(const struct l2cap_frame *frame) +{ + print_seeking_speed(frame); +} + +static const char *play_order_str(uint8_t order) +{ + switch (order) { + case 0x01: + return "Single once"; + case 0x02: + return "Single repeat"; + case 0x03: + return "In order once"; + case 0x04: + return "In order repeat"; + case 0x05: + return "Oldest once"; + case 0x06: + return "Oldest repeat"; + case 0x07: + return "Newest once"; + case 0x08: + return "Newest repeat"; + case 0x09: + return "Shuffle once"; + case 0x0A: + return "Shuffle repeat"; + default: + return "RFU"; + } +} + +static void print_playing_order(const struct l2cap_frame *frame) +{ + int8_t playing_order; + + if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&playing_order)) { + print_text(COLOR_ERROR, " Playing Order: invalid size"); + goto done; + } + + print_field(" Playing Order: %s", play_order_str(playing_order)); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void playing_order_read(const struct l2cap_frame *frame) +{ + print_playing_order(frame); +} + +static void playing_order_write(const struct l2cap_frame *frame) +{ + print_playing_order(frame); +} + +static void playing_order_notify(const struct l2cap_frame *frame) +{ + print_playing_order(frame); +} + +static const struct bitfield_data playing_orders_table[] = { + { 0, "Single once (0x0001)" }, + { 1, "Single repeat (0x0002)" }, + { 2, "In order once (0x0004)" }, + { 3, "In Order Repeat (0x0008)" }, + { 4, "Oldest once (0x0010)" }, + { 5, "Oldest repeat (0x0020)" }, + { 6, "Newest once (0x0040)" }, + { 7, "Newest repeat (0x0080)" }, + { 8, "Shuffle once (0x0100)" }, + { 9, "Shuffle repeat (0x0200)" }, + { 10, "RFU (0x0400)" }, + { 11, "RFU (0x0800)" }, + { 12, "RFU (0x1000)" }, + { 13, "RFU (0x2000)" }, + { 14, "RFU (0x4000)" }, + { 15, "RFU (0x8000)" }, + { } +}; + +static void print_playing_orders_supported(const struct l2cap_frame *frame) +{ + uint16_t supported_orders; + uint16_t mask; + + if (!l2cap_frame_get_le16((void *)frame, &supported_orders)) { + print_text(COLOR_ERROR, + " Supported Playing Orders: invalid size"); + goto done; + } + + print_field(" Supported Playing Orders: 0x%4.4x", + supported_orders); + + mask = print_bitfield(8, supported_orders, playing_orders_table); + if (mask) + print_text(COLOR_WHITE_BG, " Unknown fields (0x%4.4x)", + mask); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void playing_orders_supported_read(const struct l2cap_frame *frame) +{ + print_playing_orders_supported(frame); +} + +static const char *media_state_str(uint8_t state) +{ + switch (state) { + case 0x00: + return "Inactive"; + case 0x01: + return "Playing"; + case 0x02: + return "Paused"; + case 0x03: + return "Seeking"; + default: + return "RFU"; + } +} + +static void print_media_state(const struct l2cap_frame *frame) +{ + int8_t state; + + if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&state)) { + print_text(COLOR_ERROR, " Media State: invalid size"); + goto done; + } + + print_field(" Media State: %s", media_state_str(state)); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void media_state_read(const struct l2cap_frame *frame) +{ + print_media_state(frame); +} + +static void media_state_notify(const struct l2cap_frame *frame) +{ + print_media_state(frame); +} + +struct media_cp_opcode { + uint8_t opcode; + const char *opcode_str; +} media_cp_opcode_table[] = { + {0x01, "Play"}, + {0x02, "Pause"}, + {0x03, "Fast Rewind"}, + {0x04, "Fast Forward"}, + {0x05, "Stop"}, + {0x10, "Move Relative"}, + {0x20, "Previous Segment"}, + {0x21, "Next Segment"}, + {0x22, "First Segment"}, + {0x23, "Last Segment"}, + {0x24, "Goto Segment"}, + {0x30, "Previous Track"}, + {0x31, "Next Track"}, + {0x32, "First Track"}, + {0x33, "Last Track"}, + {0x34, "Goto Track"}, + {0x40, "Previous Group"}, + {0x41, "Next Group"}, + {0x42, "First Group"}, + {0x43, "Last Group"}, + {0x44, "Goto Group"}, +}; + +static const char *cp_opcode_str(uint8_t opcode) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(media_cp_opcode_table); i++) { + const char *str = media_cp_opcode_table[i].opcode_str; + + if (opcode == media_cp_opcode_table[i].opcode) + return str; + } + + return "RFU"; +} + +static void print_media_cp(const struct l2cap_frame *frame) +{ + int8_t opcode; + + if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&opcode)) { + print_text(COLOR_ERROR, " Media Control Point: invalid size"); + goto done; + } + + print_field(" Media Control Point: %s", cp_opcode_str(opcode)); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void media_cp_write(const struct l2cap_frame *frame) +{ + print_media_cp(frame); +} + +static void media_cp_notify(const struct l2cap_frame *frame) +{ + print_media_cp(frame); +} + +static const struct bitfield_data supported_opcodes_table[] = { + {0, "Play (0x00000001)" }, + {1, "Pause (0x00000002)" }, + {2, "Fast Rewind (0x00000004)" }, + {3, "Fast Forward (0x00000008)" }, + {4, "Stop (0x00000010)" }, + {5, "Move Relative (0x00000020)" }, + {6, "Previous Segment (0x00000040)" }, + {7, "Next Segment (0x00000080)" }, + {8, "First Segment (0x00000100)" }, + {9, "Last Segment (0x00000200)" }, + {10, "Goto Segment (0x00000400)" }, + {11, "Previous Track (0x00000800)" }, + {12, "Next Track (0x00001000)" }, + {13, "First Track (0x00002000)" }, + {14, "Last Track (0x00004000)" }, + {15, "Goto Track (0x00008000)" }, + {16, "Previous Group (0x00010000)" }, + {17, "Next Group (0x00020000)" }, + {18, "First Group (0x00040000)" }, + {19, "Last Group (0x00080000)" }, + {20, "Goto Group (0x00100000)" }, + {21, "RFU (0x00200000)" }, + {22, "RFU (0x00400000)" }, + {23, "RFU (0x00800000)" }, + {24, "RFU (0x01000000)" }, + {25, "RFU (0x02000000)" }, + {26, "RFU (0x04000000)" }, + {27, "RFU (0x08000000)" }, + {28, "RFU (0x10000000)" }, + {29, "RFU (0x20000000)" }, + {30, "RFU (0x40000000)" }, + {31, "RFU (0x80000000)" }, + { } +}; + +static void print_media_cp_op_supported(const struct l2cap_frame *frame) +{ + uint32_t supported_opcodes; + uint32_t mask; + + if (!l2cap_frame_get_le32((void *)frame, &supported_opcodes)) { + print_text(COLOR_ERROR, " value: invalid size"); + goto done; + } + + print_field(" Supported Opcodes: 0x%8.8x", supported_opcodes); + + mask = print_bitfield(8, supported_opcodes, supported_opcodes_table); + if (mask) + print_text(COLOR_WHITE_BG, " Unknown fields (0x%4.4x)", + mask); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void media_cp_op_supported_read(const struct l2cap_frame *frame) +{ + print_media_cp_op_supported(frame); +} + +static void media_cp_op_supported_notify(const struct l2cap_frame *frame) +{ + print_media_cp_op_supported(frame); +} + +static void print_content_control_id(const struct l2cap_frame *frame) +{ + int8_t ccid; + + if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&ccid)) { + print_text(COLOR_ERROR, " Content Control ID: invalid size"); + goto done; + } + + print_field(" Content Control ID: 0x%2.2x", ccid); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void content_control_id_read(const struct l2cap_frame *frame) +{ + print_content_control_id(frame); +} + #define GATT_HANDLER(_uuid, _read, _write, _notify) \ { \ .uuid = { \ @@ -1776,6 +2272,23 @@ struct gatt_handler { GATT_HANDLER(0x2b7d, vol_state_read, NULL, vol_state_notify), GATT_HANDLER(0x2b7e, NULL, vol_cp_write, NULL), GATT_HANDLER(0x2b7f, vol_flag_read, NULL, vol_flag_notify), + GATT_HANDLER(0x2b93, mp_name_read, NULL, mp_name_notify), + GATT_HANDLER(0x2b96, NULL, NULL, track_changed_notify), + GATT_HANDLER(0x2b97, track_title_read, NULL, track_title_notify), + GATT_HANDLER(0x2b98, track_duration_read, NULL, track_duration_notify), + GATT_HANDLER(0x2b99, track_position_read, track_position_write, + track_position_notify), + GATT_HANDLER(0x2b9a, playback_speed_read, playback_speed_write, + playback_speed_notify), + GATT_HANDLER(0x2b9b, seeking_speed_read, NULL, seeking_speed_notify), + GATT_HANDLER(0x2ba1, playing_order_read, playing_order_write, + playing_order_notify), + GATT_HANDLER(0x2ba2, playing_orders_supported_read, NULL, NULL), + GATT_HANDLER(0x2ba3, media_state_read, NULL, media_state_notify), + GATT_HANDLER(0x2ba4, NULL, media_cp_write, media_cp_notify), + GATT_HANDLER(0x2ba5, media_cp_op_supported_read, NULL, + media_cp_op_supported_notify), + GATT_HANDLER(0x2bba, content_control_id_read, NULL, NULL), }; static struct gatt_handler *get_handler(struct gatt_db_attribute *attr)