From patchwork Thu Oct 6 14:33:40 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 613931 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 94992C433F5 for ; Thu, 6 Oct 2022 14:35:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230267AbiJFOfZ (ORCPT ); Thu, 6 Oct 2022 10:35:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58456 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230244AbiJFOfX (ORCPT ); Thu, 6 Oct 2022 10:35:23 -0400 Received: from mga03.intel.com (mga03.intel.com [134.134.136.65]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6F61B80BEE for ; Thu, 6 Oct 2022 07:35:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1665066922; x=1696602922; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=v4gQTTSH4nkFIZi1ot0slnrsUZrrSz9zYNk8jIRhslQ=; b=g/LUoXcYAR/oUpM0Ycs18rEkNv12eq3clfo7n3G092TuKtu3OM2GfzLn Lb5uCiGAdHWM1G7kqUyIk5eRIGYoWFUL1uz+gIcvfsecsLR4P8elDcOi+ 4kwvuPH8+Q9vhlHkHuyQqjvY2SZANgAvUrDVcHav7iVQsaRx3VBMbse82 rl0Q0nG19yYvkSpM/oYSKniEf/zYgzl5q0kezPQxwCh1yQW9lB59h2MS9 m93B6nVRHUmEoRBIkCfa+mhyaz76UYMFOjkoC2yBHvmxntiRJFqqkrEJT wNaXbAG9AKaOZ0xVLxQwHL0AaccpkMP7ZTKYf337B6vDhnoXAH+lLCkji Q==; X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="305039100" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="305039100" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by orsmga103.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Oct 2022 07:35:21 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="693373389" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="693373389" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga004.fm.intel.com with ESMTP; 06 Oct 2022 07:35:04 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ 1/4] lib/uuid: Add GMCS UUIDs Date: Thu, 6 Oct 2022 20:03:40 +0530 Message-Id: <20221006143343.199055-2-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221006143343.199055-1-abhay.maheshbhai.maheta@intel.com> References: <20221006143343.199055-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 Thu Oct 6 14:33:41 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 613004 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 45DF6C4332F for ; Thu, 6 Oct 2022 14:35:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230244AbiJFOfn (ORCPT ); Thu, 6 Oct 2022 10:35:43 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59076 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230463AbiJFOfj (ORCPT ); Thu, 6 Oct 2022 10:35:39 -0400 Received: from mga11.intel.com (mga11.intel.com [192.55.52.93]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B7E848306C for ; Thu, 6 Oct 2022 07:35:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1665066934; x=1696602934; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=LDu3EKDfytNQyPGsAQnOsz3WzjXgvH3YZ/plMcrZGWE=; b=nnnSFkra66AsA6ALCABQrrLAUMr9Mvi6mZNSJAvvcmXjE7v63q/7Thct TEGlY4eUA9/BYm9kaAR01qUBFiV5NIAw10/3wj2ygeRWASxhXL38yblwL vpzUpJyWVO59/Z7i6knCIzEoGtaiBcIUHzJDMddQzjVCRsf8SDk2q9Qgn pLBTfWVbR0E7WYjS/KtPgM8rcCly9TFzASTqo1sJPH3Il6VI8ovGeE/GP hxK0Ch9uj59tvxyaubb0nl93P2jTB6oYNEnO53vf6G7En1ySzWfeBeKfK Jsk+SM3ycjQIJeB03+mg0oHiMTHi8CTyIi12Y8l0/tn1zMi9xKnLtFzTC g==; X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="301059077" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="301059077" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Oct 2022 07:35:33 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="693373502" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="693373502" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga004.fm.intel.com with ESMTP; 06 Oct 2022 07:35:25 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ 2/4] shared/mcp: Add initial code for handling MCP Date: Thu, 6 Oct 2022 20:03:41 +0530 Message-Id: <20221006143343.199055-3-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221006143343.199055-1-abhay.maheshbhai.maheta@intel.com> References: <20221006143343.199055-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 | 1408 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/mcp.h | 60 ++ src/shared/mcs.h | 65 +++ 4 files changed, 1534 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..20f18d2c0 --- /dev/null +++ b/src/shared/mcp.c @@ -0,0 +1,1408 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#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; + struct event_callback *cb = mcp->cb; + + 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 opcodes 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 notification 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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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 Control Point Opecodes 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) { + uint16_t value_handle; + + 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..0a85631cf --- /dev/null +++ b/src/shared/mcp.h @@ -0,0 +1,60 @@ +/* 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..51c24d89c --- /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 Thu Oct 6 14:33:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 613005 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 27B25C433F5 for ; Thu, 6 Oct 2022 14:35:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230384AbiJFOfm (ORCPT ); Thu, 6 Oct 2022 10:35:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59080 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230435AbiJFOfj (ORCPT ); Thu, 6 Oct 2022 10:35:39 -0400 Received: from mga11.intel.com (mga11.intel.com [192.55.52.93]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3A70D8320A for ; Thu, 6 Oct 2022 07:35:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1665066936; x=1696602936; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=59MeVCuNwVwxBh9vrd4M6LR82MZKmCTIH3ypYURMu7I=; b=hkvPXdwASoIcaqI6QHVNAnLtVJnq2praqDBwH6ec8MWDlnOoxpGhmIUN K0Wrff/2WQ9QjSexKHMzL34Bai/HU/Bcx1uwg7NuTo61MXQhonQL4oGVg WbYKP9maCD3fCvNXjH5hwV0iJyOV7ZvdcRBPBDnqNM7aHurnq7445FQ4n zVa2RLjM6h2J5Kxse41JQNQRS3VcmxNQXjTdkEMKGtiZALWiKWwUkbm8W RJ5p+/fNTVvD4OwH/IWxUQCGO3wcDmdwwM09kWSuLmzIFlDHCq49vkkjX O7sr2wx8R+oQJQIfC4duvDt4bO3x9JDS/rmWA08+duRKf/YMlGtLGUasP g==; X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="301059123" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="301059123" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Oct 2022 07:35:36 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="693373563" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="693373563" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga004.fm.intel.com with ESMTP; 06 Oct 2022 07:35:31 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ 3/4] profiles: Add initial code for mcp plugin Date: Thu, 6 Oct 2022 20:03:42 +0530 Message-Id: <20221006143343.199055-4-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221006143343.199055-1-abhay.maheshbhai.maheta@intel.com> References: <20221006143343.199055-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. --- Makefile.plugins | 5 + configure.ac | 4 + profiles/audio/mcp.c | 429 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 438 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..0a8d83198 --- /dev/null +++ b/profiles/audio/mcp.c @@ -0,0 +1,429 @@ +// 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); +} + +void cb_track_changed(struct bt_mcp *mcp) +{ + DBG("Track Changed"); + /* Since track changed has happened + * track title notification is expected + */ +} + +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); +} + +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(buf, "%d", duration); + media_player_set_metadata(mp, NULL, "Duration", buf, sizeof(buf)); + media_player_metadata_changed(mp); +} + +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); +} + +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 Thu Oct 6 14:33:43 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Abhay Maheta X-Patchwork-Id: 613930 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 BAB03C43219 for ; Thu, 6 Oct 2022 14:35:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230336AbiJFOfo (ORCPT ); Thu, 6 Oct 2022 10:35:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59062 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230325AbiJFOfl (ORCPT ); Thu, 6 Oct 2022 10:35:41 -0400 Received: from mga11.intel.com (mga11.intel.com [192.55.52.93]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7870A83F19 for ; Thu, 6 Oct 2022 07:35:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1665066938; x=1696602938; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=c436BQecQNI2dks+MDUs/y6/usuVOpiOxHqKGGFJWYI=; b=dxPpHVxFWCD2kaivoZxJhFUTznC8IRQ2dddHa8uoJCFN6MGNZbXkqrrE qrFFCfVySvInp4Jg32rOTk738xic9M8clHC+n91fOR/vfmvykcuw2XL1G RJqdiBvikIVD9g6+eYQ13PWV5O9nyshrOonkWtTCVxEp1xaiYHow2ktX+ Mwma0o+Q0uEW14bUiOa8CRORBCPIUPfkywXRBJIKHpMSZDtogp1EOVALu 643Vsu06PAP5GT/ZqTYv4uM0vmcoeIqLYmHy04A8g6KPqV7T8PK2e7lEA qB/merPg86Xx+5bMh8a/QjNlEZKOdGYAB9UpuYO0lrRoOMrcbXC96tBIy w==; X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="301059148" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="301059148" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 06 Oct 2022 07:35:38 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10491"; a="693373601" X-IronPort-AV: E=Sophos;i="5.95,164,1661842800"; d="scan'208";a="693373601" Received: from bsbdt.iind.intel.com ([10.224.186.26]) by fmsmga004.fm.intel.com with ESMTP; 06 Oct 2022 07:35:32 -0700 From: Abhay Maheta To: linux-bluetooth@vger.kernel.org Cc: Abhay Maheta Subject: [PATCH BlueZ 4/4] monitor/att: Add decoding support for GMCS Date: Thu, 6 Oct 2022 20:03:43 +0530 Message-Id: <20221006143343.199055-5-abhay.maheshbhai.maheta@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221006143343.199055-1-abhay.maheshbhai.maheta@intel.com> References: <20221006143343.199055-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 | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 511 insertions(+) diff --git a/monitor/att.c b/monitor/att.c index f5fc32cb0..1bb9f58f6 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,497 @@ 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); +} + +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); +} + +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"}, +}; + +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 +2270,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)