From patchwork Sat Aug 27 00:05:30 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600618 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 115E7ECAAD4 for ; Sat, 27 Aug 2022 00:05:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231769AbiH0AFs (ORCPT ); Fri, 26 Aug 2022 20:05:48 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54946 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230499AbiH0AFq (ORCPT ); Fri, 26 Aug 2022 20:05:46 -0400 Received: from mail-pg1-x52b.google.com (mail-pg1-x52b.google.com [IPv6:2607:f8b0:4864:20::52b]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B45DEBB906 for ; Fri, 26 Aug 2022 17:05:44 -0700 (PDT) Received: by mail-pg1-x52b.google.com with SMTP id r69so2741561pgr.2 for ; Fri, 26 Aug 2022 17:05:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=Y9UeP1LMUCj9M1cIMDaXSsm/TaUly8MjK/WdqL6cyXE=; b=X3XG/qd2SbIzw00ZpePIE71MCVtmIpRN/fasobZQZ6gPHUggus/XODQbcRu58jPjs8 N8ZasJuR0oBH2+VA0rF9k80/8Z2ok5d26Lq0LxEaAJabsByLq+CZrPgiwAWPw6Twwxt2 Hokcxqkm75DQPQxqie1yRmevV5X33HTSaoqGXo6HLJo/m1APbwXKfmfHq0yvMQi5d+qM ztnBy2VFzPKHLdHkZZeL7IIn32lfdtv2wPNnRQAG87GLp0rJyE2YU0lo6X9xWBsU+SnT yriU0+2WGyZClGZN5AL2zajWRWRZ2Kb6t4B+hI4uL/jhaQ537R6LpSNfI64o1HINQ2qq QVtQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=Y9UeP1LMUCj9M1cIMDaXSsm/TaUly8MjK/WdqL6cyXE=; b=2NxFgt4ZRW67SVXc5+UUdogdkRz/nR8iYnViqYs4dfpKXMfCdrGWxf/FgJtCcx6dxD gCCHzo2gYyHnscOEC91rCEQx2ftcGw9+EtiR9HGDZmc+BZrXE8q3lDbKYGViCIXlHm10 T2zxPQUCe9Ix+VxbP3mf12bE2CGyWZaRvnKeFWJ2vx9ojXEO2OvsJwO0YHay4/bS40cB cyr/SEc7SyhNg80936g1HSFVzgN5ViRde8NTRzrtzpBTherqfFRDhX8rqUGbydqsLYbg o2u/kGk4fvAav+bqS6O84ETM8OCGGtv2utQ+PtkfgyWCgg01VZXE7Mw2jV3QjRPzqWrH PYIw== X-Gm-Message-State: ACgBeo1D71joy5VgZXL+dQSl+tQmCIBYBB1FjsC643qyN4ZVJXJYy9Co yAkYUFOfHUX2L0fzLyHf3nL9tM46+ho= X-Google-Smtp-Source: AA6agR6+2P3PaQIzaw7IA0/CDzloM+oIqZihUCoAZS5ESG7rEoFEq5EXJbZp8qizSqNiXyjSuPMO5w== X-Received: by 2002:a65:4682:0:b0:41d:f44b:4227 with SMTP id h2-20020a654682000000b0041df44b4227mr4991193pgr.381.1661558743707; Fri, 26 Aug 2022 17:05:43 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.42 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:43 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 01/11] adapter: Add btd_adapter_find_device_by_fd Date: Fri, 26 Aug 2022 17:05:30 -0700 Message-Id: <20220827000540.113414-2-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds btd_adapter_find_device_by_fd that lookup a device by a fd socket destination address. --- src/adapter.c | 33 +++++++++++++++++++++++++++++++++ src/adapter.h | 1 + 2 files changed, 34 insertions(+) diff --git a/src/adapter.c b/src/adapter.c index b453e86a03c1..51b099daefdf 100644 --- a/src/adapter.c +++ b/src/adapter.c @@ -1383,6 +1383,39 @@ struct btd_device *btd_adapter_get_device(struct btd_adapter *adapter, return adapter_create_device(adapter, addr, addr_type); } +struct btd_device *btd_adapter_find_device_by_fd(int fd) +{ + bdaddr_t src, dst; + uint8_t dst_type; + GIOChannel *io = NULL; + GError *gerr = NULL; + struct btd_adapter *adapter; + + io = g_io_channel_unix_new(fd); + if (!io) + return NULL; + + bt_io_get(io, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST_TYPE, &dst_type, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + return NULL; + } + + g_io_channel_unref(io); + + adapter = adapter_find(&src); + if (!adapter) + return NULL; + + return btd_adapter_find_device(adapter, &dst, dst_type); +} + sdp_list_t *btd_adapter_get_services(struct btd_adapter *adapter) { return adapter->services; diff --git a/src/adapter.h b/src/adapter.h index b09044edda70..f38f473b79d7 100644 --- a/src/adapter.h +++ b/src/adapter.h @@ -86,6 +86,7 @@ struct btd_device *btd_adapter_find_device(struct btd_adapter *adapter, uint8_t dst_type); struct btd_device *btd_adapter_find_device_by_path(struct btd_adapter *adapter, const char *path); +struct btd_device *btd_adapter_find_device_by_fd(int fd); void btd_adapter_update_found_device(struct btd_adapter *adapter, const bdaddr_t *bdaddr, From patchwork Sat Aug 27 00:05:31 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600768 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 CAE84ECAAD5 for ; Sat, 27 Aug 2022 00:05:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233538AbiH0AFt (ORCPT ); Fri, 26 Aug 2022 20:05:49 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54952 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231629AbiH0AFq (ORCPT ); Fri, 26 Aug 2022 20:05:46 -0400 Received: from mail-pf1-x435.google.com (mail-pf1-x435.google.com [IPv6:2607:f8b0:4864:20::435]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 28A83BC13A for ; Fri, 26 Aug 2022 17:05:46 -0700 (PDT) Received: by mail-pf1-x435.google.com with SMTP id 199so2922269pfz.2 for ; Fri, 26 Aug 2022 17:05:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=+CslJruZhhH6jfT3eIwvKw88vFc+u4Wpz1EW4s1KON8=; b=DPXnc1YSe5yEu7hGOGjTpyaa+hKTjCjiIThnXDlU7hProxIW5/E4I6reJnkQDhwcGv bcFK9b9zlwmNBXOC65asRE6GMFcJRqdPFcG9EPdJCakU5WawXcYnjXLgqNt6r5O00m8l 7JELrYn2lZ1wjHtxn7NQ5smgMXmbAll0+5RL/oh3QWkwC3oMBoRqt2A3VMwJrkrF/5j4 GhT/5CssQjBUypkhJ3WTqkpkHuS/y17C6Mm+JGsSZtJp3jHRFRPsfZo6EmZwmr7MsuHS flX6ZqXCTqStUi9ZWJA6hYmWbSptYg09kWyNE9WUL38a214dQsEjRIf/3cTq+Ue6iLJr 0YFg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=+CslJruZhhH6jfT3eIwvKw88vFc+u4Wpz1EW4s1KON8=; b=5W7JhTCgTYCxrL6N6x6M7lXSh1VpsPw+cPH7Ej+2irkiTqi0Ijh7dfFDSJsqdaaQA3 Xjx49V45VaT1WC2CTspAg2H4RJy3yV1COrOlFE2v7r8TfETx5YdkBTccIlDe+W3or3dj 0Nw2U0kJt3+hMff/1QOUvDZEeB23T7F4O6t1MdUo2Di2V7mDG54ofE1Bc8YtYA2NQx8L X0Knqfvdgn6rn/c9ZSl9CGZMyURuuNagyKx8fAxKYiZcZ3XFN+zUxv/DhGaRZjq8SGP7 ROfN8vY0ArJNBnQ5NtnmKK5oH5HrC1VFeza8YPG6g2klG/ZWf6kXJmZ71LxLBAIAef4b qBHw== X-Gm-Message-State: ACgBeo1bam9FnURakClPnClv8L8LFyUWa2K9wZS7b4Qoy6MPNnSCoKXy 28KSoN1H0lRQhZQW8vH85LGK9ANwwgE= X-Google-Smtp-Source: AA6agR4edTkpqHBlsMvJdk0lR9y3WVQc1Qf3FJFOd9vIsV4Hhzmav3tjsJDtidpmlm9ORItlFYp6fw== X-Received: by 2002:a63:8542:0:b0:42b:4c9a:97cd with SMTP id u63-20020a638542000000b0042b4c9a97cdmr5140038pgd.221.1661558744903; Fri, 26 Aug 2022 17:05:44 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.43 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:44 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 02/11] lib/uuid: Add PACS/ASCS UUIDs Date: Fri, 26 Aug 2022 17:05:31 -0700 Message-Id: <20220827000540.113414-3-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds PACS/ASCS UUIDs which will be used by Basic Audio Profile. --- lib/uuid.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/uuid.h b/lib/uuid.h index 6236752a17a6..cb9294be8c6e 100644 --- a/lib/uuid.h +++ b/lib/uuid.h @@ -146,6 +146,24 @@ extern "C" { /* GATT Server Supported features */ #define GATT_CHARAC_SERVER_FEAT 0x2B3A +/* TODO: Update these on final UUID is given */ +#define PACS_UUID 0x1850 +#define PAC_SINK_CHRC_UUID 0x2bc9 +#define PAC_SINK_UUID "00002bc9-0000-1000-8000-00805f9b34fb" +#define PAC_SINK_LOC_CHRC_UUID 0x2bca + +#define PAC_SOURCE_CHRC_UUID 0x2bcb +#define PAC_SOURCE_UUID "00002bcb-0000-1000-8000-00805f9b34fb" +#define PAC_SOURCE_LOC_CHRC_UUID 0x2bcc + +#define PAC_CONTEXT 0x2bcd +#define PAC_SUPPORTED_CONTEXT 0x2bce + +#define ASCS_UUID 0x184e +#define ASE_SINK_UUID 0x2bc4 +#define ASE_SOURCE_UUID 0x2bc5 +#define ASE_CP_UUID 0x2bc6 + typedef struct { enum { BT_UUID_UNSPEC = 0, From patchwork Sat Aug 27 00:05:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600764 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 3798EECAAD4 for ; Sat, 27 Aug 2022 00:05:58 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1344902AbiH0AF5 (ORCPT ); Fri, 26 Aug 2022 20:05:57 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55300 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1344717AbiH0AFy (ORCPT ); Fri, 26 Aug 2022 20:05:54 -0400 Received: from mail-pf1-x434.google.com (mail-pf1-x434.google.com [IPv6:2607:f8b0:4864:20::434]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 71EB9BC13A for ; Fri, 26 Aug 2022 17:05:49 -0700 (PDT) Received: by mail-pf1-x434.google.com with SMTP id x19so1112003pfr.1 for ; Fri, 26 Aug 2022 17:05:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=jETu7KPHgZ9FTy9L0u4ynf2EZkX+GqH88OziTMV0ajs=; b=drLPquVK38rUNZqCZBpQGQfpp0ZcK5FEML25Z/RB33sLf/P3Sr71a6l8bCOSQdfJK5 zo+604LHr1uodw3R+dr5lS43VKIaugSR3wjZaz9eQFcGksLcRidL0orTnR6oS+zdtq6y 4yRnGq7ZNVi9M4hN+Qi+ADvF2Ob6PezTppQIPrUitMTHv6dCaRdLGxSPeGF5akQCEg24 zP908PrjQkTOZJxRUXFk+2F1Y/11iQ1e0Gdjfli38JkxtL3gT7laNbALZ2TAWlT1Ntm/ U2C0HYHhoWhQ9dLcqhTH9hn7eYnaFF7Cz6ipvj+3Ws6FYEcPnGDRZKZBdkRP2x5r/jF/ PTnA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=jETu7KPHgZ9FTy9L0u4ynf2EZkX+GqH88OziTMV0ajs=; b=3WobpeeHGxnv4SfmWLqHHhj1CZZwqkZeGncOgOD4E3XrDdutyiA7oOasiSrnSMXVN9 UaijiYW+ciYOkhXegbtV5uS9Ru6xeJSwbHMB8VFRLMgLWaH02Jm1gmsqk9VFA8MPi2sc RPewjZW2Pu0PuJB1eqXzoPx48MUhyxhSq8V8Ts+svmiCIi8TmWVwpJlkFxW1nsy22RBS D1OR4It5j/rI2MKuYGuydjpzW4JmBj7eXeCFouEZhGIFW3eodMS9BGmlu3FNjZvhJVZX qSkrPI54Cyf5mRJb2cC6jGIXiuEhbnBjoWG0eEPPTFPKy3K0E5yZXrMPaBQGb3Z4tZCS 5wOQ== X-Gm-Message-State: ACgBeo0KeP3q6xN6p/nmnx1pjsE3n80P0CjHB3uLzTL+50jSpNpA/919 WH8v4P6zm1FMo+/iJ+TRBQPzIoInC28= X-Google-Smtp-Source: AA6agR5ceZug5489Xc+XfpUw5rBYlzasRy2bO79uwGmMPk2cdtWvMayDB1xX5h1a93O3BM73PYNEUg== X-Received: by 2002:a63:c5:0:b0:40d:d290:24ef with SMTP id 188-20020a6300c5000000b0040dd29024efmr5120810pga.141.1661558746614; Fri, 26 Aug 2022 17:05:46 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.45 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:45 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 03/11] shared/bap: Add initial code for handling BAP Date: Fri, 26 Aug 2022 17:05:32 -0700 Message-Id: <20220827000540.113414-4-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds initial code for Basic Audio Profile. --- Makefile.am | 1 + src/device.c | 10 +- src/shared/ascs.h | 196 ++ src/shared/bap.c | 4776 +++++++++++++++++++++++++++++++++++++++++++++ src/shared/bap.h | 269 +++ 5 files changed, 5249 insertions(+), 3 deletions(-) create mode 100644 src/shared/ascs.h create mode 100644 src/shared/bap.c create mode 100644 src/shared/bap.h diff --git a/Makefile.am b/Makefile.am index ae317bf63b6b..92758ca55816 100644 --- a/Makefile.am +++ b/Makefile.am @@ -230,6 +230,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \ src/shared/gatt-db.h src/shared/gatt-db.c \ 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/tty.h if READLINE diff --git a/src/device.c b/src/device.c index 44b9033355ce..995d39f2ccee 100644 --- a/src/device.c +++ b/src/device.c @@ -3731,9 +3731,12 @@ static void device_add_gatt_services(struct btd_device *device) static void device_accept_gatt_profiles(struct btd_device *device) { GSList *l; + bool initiator = get_initiator(device); + + DBG("initiator %s", initiator ? "true" : "false"); for (l = device->services; l != NULL; l = g_slist_next(l)) - service_accept(l->data, get_initiator(device)); + service_accept(l->data, initiator); } static void device_remove_gatt_service(struct btd_device *device, @@ -5424,6 +5427,9 @@ int device_connect_le(struct btd_device *dev) DBG("Connection attempt to: %s", addr); + /* Set as initiator */ + dev->le_state.initiator = true; + if (dev->le_state.paired) sec_level = BT_IO_SEC_MEDIUM; else @@ -5461,8 +5467,6 @@ int device_connect_le(struct btd_device *dev) /* Keep this, so we can cancel the connection */ dev->att_io = io; - /* Set as initiator */ - dev->le_state.initiator = true; return 0; } diff --git a/src/shared/ascs.h b/src/shared/ascs.h new file mode 100644 index 000000000000..fc032048cd9d --- /dev/null +++ b/src/shared/ascs.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + */ + +/* Response Status Code */ +#define BT_ASCS_RSP_SUCCESS 0x00 +#define BT_ASCS_RSP_NOT_SUPPORTED 0x01 +#define BT_ASCS_RSP_TRUNCATED 0x02 +#define BT_ASCS_RSP_INVALID_ASE 0x03 +#define BT_ASCS_RSP_INVALID_ASE_STATE 0x04 +#define BT_ASCS_RSP_INVALID_DIR 0x05 +#define BT_ASCS_RSP_CAP_UNSUPPORTED 0x06 +#define BT_ASCS_RSP_CONF_UNSUPPORTED 0x07 +#define BT_ASCS_RSP_CONF_REJECTED 0x08 +#define BT_ASCS_RSP_CONF_INVALID 0x09 +#define BT_ASCS_RSP_METADATA_UNSUPPORTED 0x0a +#define BT_ASCS_RSP_METADATA_REJECTED 0x0b +#define BT_ASCS_RSP_METADATA_INVALID 0x0c +#define BT_ASCS_RSP_NO_MEM 0x0d +#define BT_ASCS_RSP_UNSPECIFIED 0x0e + +/* Response Reasons */ +#define BT_ASCS_REASON_NONE 0x00 +#define BT_ASCS_REASON_CODEC 0x01 +#define BT_ASCS_REASON_CODEC_DATA 0x02 +#define BT_ASCS_REASON_INTERVAL 0x03 +#define BT_ASCS_REASON_FRAMING 0x04 +#define BT_ASCS_REASON_PHY 0x05 +#define BT_ASCS_REASON_SDU 0x06 +#define BT_ASCS_REASON_RTN 0x07 +#define BT_ASCS_REASON_LATENCY 0x08 +#define BT_ASCS_REASON_PD 0x09 +#define BT_ASCS_REASON_CIS 0x0a + +/* Transport QoS Packing */ +#define BT_ASCS_QOS_PACKING_SEQ 0x00 +#define BT_ASCS_QOS_PACKING_INT 0x01 + +/* Transport QoS Framing */ +#define BT_ASCS_QOS_FRAMING_UNFRAMED 0x00 +#define BT_ASCS_QOS_FRAMING_FRAMED 0x01 + +/* ASE characteristic states */ +#define BT_ASCS_ASE_STATE_IDLE 0x00 +#define BT_ASCS_ASE_STATE_CONFIG 0x01 +#define BT_ASCS_ASE_STATE_QOS 0x02 +#define BT_ASCS_ASE_STATE_ENABLING 0x03 +#define BT_ASCS_ASE_STATE_STREAMING 0x04 +#define BT_ASCS_ASE_STATE_DISABLING 0x05 +#define BT_ASCS_ASE_STATE_RELEASING 0x06 + +struct bt_ascs_ase_rsp { + uint8_t ase; + uint8_t code; + uint8_t reason; +} __packed; + +struct bt_ascs_cp_rsp { + uint8_t op; + uint8_t num_ase; + struct bt_ascs_ase_rsp rsp[0]; +} __packed; + +struct bt_ascs_ase_status { + uint8_t id; + uint8_t state; + uint8_t params[0]; +} __packed; + +/* ASE_State = 0x01 (Codec Configured), defined in Table 4.7. */ +struct bt_ascs_ase_status_config { + uint8_t framing; + uint8_t phy; + uint8_t rtn; + uint16_t latency; + uint8_t pd_min[3]; + uint8_t pd_max[3]; + uint8_t ppd_min[3]; + uint8_t ppd_max[3]; + struct bt_bap_codec codec; + uint8_t cc_len; + /* LTV-formatted Codec-Specific Configuration */ + struct bt_ltv cc[0]; +} __packed; + +/* ASE_State = 0x02 (QoS Configured), defined in Table 4.8. */ +struct bt_ascs_ase_status_qos { + uint8_t cig_id; + uint8_t cis_id; + uint8_t interval[3]; + uint8_t framing; + uint8_t phy; + uint16_t sdu; + uint8_t rtn; + uint16_t latency; + uint8_t pd[3]; +} __packed; + +/* ASE_Status = 0x03 (Enabling), 0x04 (Streaming), or 0x05 (Disabling) + * defined in Table 4.9. + */ +struct bt_ascs_ase_status_metadata { + uint8_t cig_id; + uint8_t cis_id; + uint8_t len; + uint8_t data[0]; +} __packed; + +struct bt_ascs_ase_hdr { + uint8_t op; + uint8_t num; +} __packed; + +#define BT_ASCS_CONFIG 0x01 + +#define BT_ASCS_CONFIG_LATENCY_LOW 0x01 +#define BT_ASCS_CONFIG_LATENCY_MEDIUM 0x02 +#define BT_ASCS_CONFIG_LATENCY_HIGH 0x03 + +#define BT_ASCS_CONFIG_PHY_LE_1M 0x01 +#define BT_ASCS_CONFIG_PHY_LE_2M 0x02 +#define BT_ASCS_CONFIG_PHY_LE_CODED 0x03 + +struct bt_ascs_codec_config { + uint8_t len; + uint8_t type; + uint8_t data[0]; +} __packed; + +struct bt_ascs_config { + uint8_t ase; /* ASE ID */ + uint8_t latency; /* Target Latency */ + uint8_t phy; /* Target PHY */ + struct bt_bap_codec codec; /* Codec ID */ + uint8_t cc_len; /* Codec Specific Config Length */ + /* LTV-formatted Codec-Specific Configuration */ + struct bt_ascs_codec_config cc[0]; +} __packed; + +#define BT_ASCS_QOS 0x02 + +struct bt_ascs_qos { + uint8_t ase; /* ASE ID */ + uint8_t cig; /* CIG ID*/ + uint8_t cis; /* CIG ID*/ + uint8_t interval[3]; /* Frame interval */ + uint8_t framing; /* Frame framing */ + uint8_t phy; /* PHY */ + uint16_t sdu; /* Maximum SDU Size */ + uint8_t rtn; /* Retransmission Effort */ + uint16_t latency; /* Transport Latency */ + uint8_t pd[3]; /* Presentation Delay */ +} __packed; + +#define BT_ASCS_ENABLE 0x03 + +struct bt_ascs_metadata { + uint8_t ase; /* ASE ID */ + uint8_t len; /* Metadata length */ + uint8_t data[0]; /* LTV-formatted Metadata */ +} __packed; + +struct bt_ascs_enable { + struct bt_ascs_metadata meta; /* Metadata */ +} __packed; + +#define BT_ASCS_START 0x04 + +struct bt_ascs_start { + uint8_t ase; /* ASE ID */ +} __packed; + +#define BT_ASCS_DISABLE 0x05 + +struct bt_ascs_disable { + uint8_t ase; /* ASE ID */ +} __packed; + +#define BT_ASCS_STOP 0x06 + +struct bt_ascs_stop { + uint8_t ase; /* ASE ID */ +} __packed; + +#define BT_ASCS_METADATA 0x07 + +#define BT_ASCS_RELEASE 0x08 + +struct bt_ascs_release { + uint8_t ase; /* ASE ID */ +} __packed; diff --git a/src/shared/bap.c b/src/shared/bap.c new file mode 100644 index 000000000000..29b05db02702 --- /dev/null +++ b/src/shared/bap.c @@ -0,0 +1,4776 @@ +/* + * + * 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 "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/bap.h" +#include "src/shared/ascs.h" + +/* Maximum number of ASE(s) */ +#define NUM_SINKS 2 +#define NUM_SOURCE 2 +#define NUM_ASES (NUM_SINKS + NUM_SOURCE) +#define ASE_UUID(_id) (_id < NUM_SINKS ? ASE_SINK_UUID : ASE_SOURCE_UUID) +#define DBG(_bap, fmt, arg...) \ + bap_debug(_bap, "%s:%s() " fmt, __FILE__, __func__, ## arg) + +#define LTV(_type, _bytes...) \ + { \ + .len = 1 + sizeof((uint8_t []) { _bytes }), \ + .type = _type, \ + .data = { _bytes }, \ + } + +#define BAP_PROCESS_TIMEOUT 10 + +struct bt_bap_pac_changed { + bt_bap_pac_func_t added; + bt_bap_pac_func_t removed; + bt_bap_destroy_func_t destroy; + void *data; +}; + +struct bt_bap_ready { + unsigned int id; + bt_bap_ready_func_t func; + bt_bap_destroy_func_t destroy; + void *data; +}; + +struct bt_bap_state { + unsigned int id; + bt_bap_state_func_t func; + bt_bap_connecting_func_t connecting; + bt_bap_destroy_func_t destroy; + void *data; +}; + +struct bt_bap_cb { + unsigned int id; + bt_bap_func_t attached; + bt_bap_func_t detached; + void *user_data; +}; + +struct bt_pacs { + struct bt_bap_db *bdb; + struct gatt_db_attribute *service; + struct gatt_db_attribute *sink; + struct gatt_db_attribute *sink_ccc; + struct gatt_db_attribute *sink_loc; + struct gatt_db_attribute *sink_loc_ccc; + struct gatt_db_attribute *source; + struct gatt_db_attribute *source_ccc; + struct gatt_db_attribute *source_loc; + struct gatt_db_attribute *source_loc_ccc; + struct gatt_db_attribute *context; + struct gatt_db_attribute *context_ccc; + struct gatt_db_attribute *supported_context; + struct gatt_db_attribute *supported_context_ccc; +}; + +struct bt_ase { + struct bt_ascs *ascs; + uint8_t id; + struct gatt_db_attribute *attr; + struct gatt_db_attribute *ccc; +}; + +struct bt_ascs { + struct bt_bap_db *bdb; + struct gatt_db_attribute *service; + struct bt_ase *ase[NUM_ASES]; + struct gatt_db_attribute *ase_cp; + struct gatt_db_attribute *ase_cp_ccc; +}; + +struct bt_bap_db { + struct gatt_db *db; + struct bt_pacs *pacs; + struct bt_ascs *ascs; + struct queue *sinks; + struct queue *sources; + struct queue *endpoints; +}; + +struct bt_bap_req { + unsigned int id; + struct bt_bap_stream *stream; + uint8_t op; + struct queue *group; + struct iovec *iov; + size_t len; + bt_bap_stream_func_t func; + void *user_data; +}; + +typedef void (*bap_func_t)(struct bt_bap *bap, bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data); + +struct bt_bap_pending { + unsigned int id; + struct bt_bap *bap; + bap_func_t func; + void *user_data; +}; + +typedef void (*bap_notify_t)(struct bt_bap *bap, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data); + +struct bt_bap_notify { + unsigned int id; + struct bt_bap *bap; + bap_notify_t func; + void *user_data; +}; + +struct bt_bap { + int ref_count; + struct bt_bap_db *ldb; + struct bt_bap_db *rdb; + struct bt_gatt_client *client; + struct bt_att *att; + struct bt_bap_req *req; + unsigned int cp_id; + + unsigned int process_id; + struct queue *reqs; + struct queue *pending; + struct queue *notify; + struct queue *streams; + + struct queue *ready_cbs; + struct queue *state_cbs; + + bt_bap_debug_func_t debug_func; + bt_bap_destroy_func_t debug_destroy; + void *debug_data; + void *user_data; +}; + +struct bt_bap_pac { + struct bt_bap_db *bdb; + char *name; + uint8_t type; + uint32_t locations; + uint16_t contexts; + struct bt_bap_codec codec; + struct bt_bap_pac_qos qos; + struct iovec *data; + struct iovec *metadata; + struct bt_bap_pac_ops *ops; + void *user_data; +}; + +struct bt_bap_endpoint { + struct bt_bap_db *bdb; + struct bt_bap_stream *stream; + struct gatt_db_attribute *attr; + uint8_t id; + uint8_t dir; + uint8_t old_state; + uint8_t state; + unsigned int state_id; +}; + +struct bt_bap_stream_io { + struct bt_bap *bap; + int ref_count; + struct io *io; + bool connecting; +}; + +struct bt_bap_stream { + struct bt_bap *bap; + struct bt_bap_endpoint *ep; + struct queue *pacs; + struct bt_bap_pac *lpac; + struct bt_bap_pac *rpac; + struct iovec *cc; + struct iovec *meta; + struct bt_bap_qos qos; + struct queue *links; + struct bt_bap_stream_io *io; + bool client; + void *user_data; +}; + +/* TODO: Figure out the capabilities types */ +#define BT_CODEC_CAP_PARAMS 0x01 +#define BT_CODEC_CAP_DRM 0x0a +#define BT_CODEC_CAP_DRM_VALUE 0x0b + +struct bt_pac_metadata { + uint8_t len; + uint8_t data[0]; +} __packed; + +struct bt_pac { + struct bt_bap_codec codec; /* Codec ID */ + uint8_t cc_len; /* Codec Capabilities Length */ + struct bt_ltv cc[0]; /* Codec Specific Capabilities */ + struct bt_pac_metadata meta[0]; /* Metadata */ +} __packed; + +struct bt_pacs_read_rsp { + uint8_t num_pac; + struct bt_pac pac[0]; +} __packed; + +struct bt_pacs_context { + uint16_t snk; + uint16_t src; +} __packed; + +/* Contains local bt_bap_db */ +static struct queue *bap_db; +static struct queue *pac_cbs; +static struct queue *bap_cbs; +static struct queue *sessions; + +static bool bap_db_match(const void *data, const void *match_data) +{ + const struct bt_bap_db *bdb = data; + const struct gatt_db *db = match_data; + + return (bdb->db == db); +} + +static void *iov_add(struct iovec *iov, size_t len) +{ + void *data; + + data = iov->iov_base + iov->iov_len; + iov->iov_len += len; + + return data; +} + +static void *iov_add_mem(struct iovec *iov, size_t len, const void *d) +{ + void *data; + + data = iov->iov_base + iov->iov_len; + iov->iov_len += len; + + memcpy(data, d, len); + + return data; +} + +static void iov_free(void *data) +{ + struct iovec *iov = data; + + if (!iov) + return; + + free(iov->iov_base); + free(iov); +} + +static void iov_memcpy(struct iovec *iov, void *src, size_t len) +{ + iov->iov_base = realloc(iov->iov_base, len); + iov->iov_len = len; + memcpy(iov->iov_base, src, len); +} + +static int iov_memcmp(struct iovec *iov1, struct iovec *iov2) +{ + if (!iov1) + return 1; + + if (!iov2) + return -1; + + if (iov1->iov_len != iov2->iov_len) + return iov1->iov_len - iov2->iov_len; + + return memcmp(iov1->iov_base, iov2->iov_base, iov1->iov_len); +} + +static struct iovec *iov_dup(struct iovec *iov, size_t len) +{ + struct iovec *dup; + size_t i; + + if (!iov) + return NULL; + + dup = new0(struct iovec, len); + + for (i = 0; i < len; i++) + iov_memcpy(&dup[i], iov[i].iov_base, iov[i].iov_len); + + return dup; +} + +unsigned int bt_bap_pac_register(bt_bap_pac_func_t added, + bt_bap_pac_func_t removed, void *user_data, + bt_bap_destroy_func_t destroy) +{ + struct bt_bap_pac_changed *changed; + + changed = new0(struct bt_bap_pac_changed, 1); + changed->added = added; + changed->removed = removed; + changed->destroy = destroy; + changed->data = user_data; + + if (!pac_cbs) + pac_cbs = queue_new(); + + queue_push_tail(pac_cbs, changed); + + return queue_length(pac_cbs); +} + +static void pac_changed_free(void *data) +{ + struct bt_bap_pac_changed *changed = data; + + if (changed->destroy) + changed->destroy(changed->data); + + free(changed); +} + +struct match_pac_id { + unsigned int id; + unsigned int index; +}; + +static bool match_index(const void *data, const void *match_data) +{ + struct match_pac_id *match = (void *)match_data; + + match->index++; + + return match->id == match->index; +} + +bool bt_bap_pac_unregister(unsigned int id) +{ + struct bt_bap_pac_changed *changed; + struct match_pac_id match; + + memset(&match, 0, sizeof(match)); + match.id = id; + + changed = queue_remove_if(pac_cbs, match_index, &match); + if (!changed) + return false; + + pac_changed_free(changed); + + if (queue_isempty(pac_cbs)) { + queue_destroy(pac_cbs, NULL); + pac_cbs = NULL; + } + + return true; +} + +static void pac_foreach(void *data, void *user_data) +{ + struct bt_bap_pac *pac = data; + struct iovec *iov = user_data; + struct bt_pacs_read_rsp *rsp; + struct bt_pac *p; + struct bt_pac_metadata *meta; + + if (!iov->iov_len) { + rsp = iov_add(iov, sizeof(*rsp)); + rsp->num_pac = 0; + } else + rsp = iov->iov_base; + + rsp->num_pac++; + + p = iov_add(iov, sizeof(*p)); + p->codec.id = pac->codec.id; + + if (pac->data) { + p->cc_len = pac->data->iov_len; + iov_add_mem(iov, p->cc_len, pac->data->iov_base); + } else + p->cc_len = 0; + + meta = iov_add(iov, sizeof(*meta)); + + if (pac->metadata) { + meta->len = pac->metadata->iov_len; + iov_add_mem(iov, meta->len, pac->metadata->iov_base); + } else + meta->len = 0; +} + +static void pacs_sink_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_pacs *pacs = user_data; + struct bt_bap_db *bdb = pacs->bdb; + struct iovec iov; + uint8_t value[512]; + + memset(value, 0, sizeof(value)); + + iov.iov_base = value; + iov.iov_len = 0; + + queue_foreach(bdb->sinks, pac_foreach, &iov); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void pacs_sink_loc_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint32_t value = 0x00000003; + + gatt_db_attribute_read_result(attrib, id, 0, (void *) &value, + sizeof(value)); +} + +static void pacs_source_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_pacs *pacs = user_data; + struct bt_bap_db *bdb = pacs->bdb; + struct iovec iov; + uint8_t value[512]; + + memset(value, 0, sizeof(value)); + + iov.iov_base = value; + iov.iov_len = 0; + + queue_foreach(bdb->sources, pac_foreach, &iov); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void pacs_source_loc_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint32_t value = 0x00000001; + + gatt_db_attribute_read_result(attrib, id, 0, (void *) &value, + sizeof(value)); +} + +static void pacs_context_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_pacs_context ctx = { + .snk = 0x0fff, + .src = 0x000e + }; + + gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx, + sizeof(ctx)); +} + +static void pacs_supported_context_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_pacs_context ctx = { + .snk = 0x0fff, + .src = 0x000e + }; + + gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx, + sizeof(ctx)); +} + +static struct bt_pacs *pacs_new(struct gatt_db *db) +{ + struct bt_pacs *pacs; + bt_uuid_t uuid; + + if (!db) + return NULL; + + pacs = new0(struct bt_pacs, 1); + + /* Populate DB with PACS attributes */ + bt_uuid16_create(&uuid, PACS_UUID); + pacs->service = gatt_db_add_service(db, &uuid, true, 19); + + bt_uuid16_create(&uuid, PAC_SINK_CHRC_UUID); + pacs->sink = gatt_db_service_add_characteristic(pacs->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + pacs_sink_read, NULL, + pacs); + + pacs->sink_ccc = gatt_db_service_add_ccc(pacs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, PAC_SINK_LOC_CHRC_UUID); + pacs->sink_loc = gatt_db_service_add_characteristic(pacs->service, + &uuid, BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + pacs_sink_loc_read, NULL, + pacs); + + pacs->sink_loc_ccc = gatt_db_service_add_ccc(pacs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, PAC_SOURCE_CHRC_UUID); + pacs->sink = gatt_db_service_add_characteristic(pacs->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + pacs_source_read, NULL, + pacs); + + pacs->sink_ccc = gatt_db_service_add_ccc(pacs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, PAC_SOURCE_LOC_CHRC_UUID); + pacs->source_loc = gatt_db_service_add_characteristic(pacs->service, + &uuid, BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + pacs_source_loc_read, NULL, + pacs); + + pacs->source_loc_ccc = gatt_db_service_add_ccc(pacs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, PAC_CONTEXT); + pacs->context = gatt_db_service_add_characteristic(pacs->service, + &uuid, BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + pacs_context_read, NULL, pacs); + + pacs->context_ccc = gatt_db_service_add_ccc(pacs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, PAC_SUPPORTED_CONTEXT); + pacs->supported_context = + gatt_db_service_add_characteristic(pacs->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + pacs_supported_context_read, NULL, + pacs); + + pacs->supported_context_ccc = gatt_db_service_add_ccc(pacs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + gatt_db_service_set_active(pacs->service, true); + + return pacs; +} + +static void bap_debug(struct bt_bap *bap, const char *format, ...) +{ + va_list ap; + + if (!bap || !format || !bap->debug_func) + return; + + va_start(ap, format); + util_debug_va(bap->debug_func, bap->debug_data, format, ap); + va_end(ap); +} + +static void bap_disconnected(int err, void *user_data) +{ + struct bt_bap *bap = user_data; + + DBG(bap, "bap %p disconnected err %d", bap, err); + + bt_bap_detach(bap); +} + +static struct bt_bap *bap_get_session(struct bt_att *att, struct gatt_db *db) +{ + const struct queue_entry *entry; + struct bt_bap *bap; + + for (entry = queue_get_entries(sessions); entry; entry = entry->next) { + struct bt_bap *bap = entry->data; + + if (att == bt_bap_get_att(bap)) + return bap; + } + + bap = bt_bap_new(db, NULL); + bap->att = att; + + bt_att_register_disconnect(att, bap_disconnected, bap, NULL); + + bt_bap_attach(bap, NULL); + + return bap; +} + +static bool bap_endpoint_match(const void *data, const void *match_data) +{ + const struct bt_bap_endpoint *ep = data; + const struct gatt_db_attribute *attr = match_data; + + return (ep->attr == attr); +} + +static struct bt_bap_endpoint *bap_endpoint_new(struct bt_bap_db *bdb, + struct gatt_db_attribute *attr) +{ + struct bt_bap_endpoint *ep; + bt_uuid_t uuid, source, sink; + + if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, NULL, + &uuid)) + return NULL; + + ep = new0(struct bt_bap_endpoint, 1); + ep->bdb = bdb; + ep->attr = attr; + + bt_uuid16_create(&source, ASE_SOURCE_UUID); + bt_uuid16_create(&sink, ASE_SINK_UUID); + + if (!bt_uuid_cmp(&source, &uuid)) + ep->dir = BT_BAP_SOURCE; + else if (!bt_uuid_cmp(&sink, &uuid)) + ep->dir = BT_BAP_SINK; + + return ep; +} + +static struct bt_bap_endpoint *bap_get_endpoint(struct bt_bap_db *db, + struct gatt_db_attribute *attr) +{ + struct bt_bap_endpoint *ep; + + if (!db || !attr) + return NULL; + + ep = queue_find(db->endpoints, bap_endpoint_match, attr); + if (ep) + return ep; + + ep = bap_endpoint_new(db, attr); + if (!ep) + return NULL; + + queue_push_tail(db->endpoints, ep); + + return ep; +} + +static bool bap_endpoint_match_id(const void *data, const void *match_data) +{ + const struct bt_bap_endpoint *ep = data; + uint8_t id = PTR_TO_UINT(match_data); + + return (ep->id == id); +} + +static struct bt_bap_endpoint *bap_get_endpoint_id(struct bt_bap *bap, + struct bt_bap_db *db, + uint8_t id) +{ + struct bt_bap_endpoint *ep; + struct gatt_db_attribute *attr = NULL; + size_t i; + + if (!bap || !db) + return NULL; + + ep = queue_find(db->endpoints, bap_endpoint_match_id, UINT_TO_PTR(id)); + if (ep) + return ep; + + for (i = 0; i < ARRAY_SIZE(db->ascs->ase); i++) { + struct bt_ase *ase = db->ascs->ase[i]; + + if (id) { + if (ase->id != id) + continue; + attr = ase->attr; + break; + } + + ep = queue_find(db->endpoints, bap_endpoint_match, ase->attr); + if (!ep) { + attr = ase->attr; + break; + } + } + + if (!attr) + return NULL; + + ep = bap_endpoint_new(db, attr); + if (!ep) + return NULL; + + ep->id = id; + queue_push_tail(db->endpoints, ep); + + return ep; +} + +static void ascs_ase_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_ase *ase = user_data; + struct bt_bap *bap = bap_get_session(att, ase->ascs->bdb->db); + struct bt_bap_endpoint *ep = bap_get_endpoint(bap->ldb, attrib); + struct bt_ascs_ase_status rsp; + + if (!ase || !bap || !ep) { + gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, + NULL, 0); + return; + } + + memset(&rsp, 0, sizeof(rsp)); + + /* Initialize Endpoint ID with ASE ID */ + if (ase->id != ep->id) + ep->id = ase->id; + + rsp.id = ep->id; + rsp.state = ep->state; + + gatt_db_attribute_read_result(attrib, id, 0, (void *) &rsp, + sizeof(rsp)); +} + +static void ase_new(struct bt_ascs *ascs, int i) +{ + struct bt_ase *ase; + bt_uuid_t uuid; + + if (!ascs) + return; + + ase = new0(struct bt_ase, 1); + ase->ascs = ascs; + ase->id = i + 1; + + bt_uuid16_create(&uuid, ASE_UUID(i)); + ase->attr = gatt_db_service_add_characteristic(ascs->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + ascs_ase_read, NULL, + ase); + + ase->ccc = gatt_db_service_add_ccc(ascs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + ascs->ase[i] = ase; +} + +static void *iov_pull_mem(struct iovec *iov, size_t len) +{ + void *data = iov->iov_base; + + if (iov->iov_len < len) + return NULL; + + iov->iov_base += len; + iov->iov_len -= len; + + return data; +} + +static bool bap_codec_equal(const struct bt_bap_codec *c1, + const struct bt_bap_codec *c2) +{ + /* Compare CID and VID if id is 0xff */ + if (c1->id == 0xff) + return !memcmp(c1, c2, sizeof(*c1)); + + return c1->id == c2->id; +} + +static struct bt_bap_stream *bap_stream_new(struct bt_bap *bap, + struct bt_bap_endpoint *ep, + struct bt_bap_pac *lpac, + struct bt_bap_pac *rpac, + struct iovec *data, + bool client) +{ + struct bt_bap_stream *stream; + + stream = new0(struct bt_bap_stream, 1); + stream->bap = bap; + stream->ep = ep; + ep->stream = stream; + stream->lpac = lpac; + stream->rpac = rpac; + stream->cc = iov_dup(data, 1); + stream->client = client; + + queue_push_tail(bap->streams, stream); + + return stream; +} + +static void stream_notify_config(struct bt_bap_stream *stream) +{ + struct bt_bap_endpoint *ep = stream->ep; + struct bt_bap_pac *lpac = stream->lpac; + struct bt_ascs_ase_status *status; + struct bt_ascs_ase_status_config *config; + size_t len; + + DBG(stream->bap, "stream %p", stream); + + len = sizeof(*status) + sizeof(*config) + stream->cc->iov_len; + status = malloc(len); + + memset(status, 0, len); + status->id = ep->id; + status->state = ep->state; + + /* Initialize preffered settings if not set */ + if (!lpac->qos.phy) + lpac->qos.phy = 0x02; + + if (!lpac->qos.rtn) + lpac->qos.rtn = 0x05; + + if (!lpac->qos.latency) + lpac->qos.latency = 10; + + if (!lpac->qos.pd_min) + lpac->qos.pd_min = 20000; + + if (!lpac->qos.pd_max) + lpac->qos.pd_max = 40000; + + if (!lpac->qos.ppd_min) + lpac->qos.ppd_min = lpac->qos.pd_min; + + if (!lpac->qos.ppd_max) + lpac->qos.ppd_max = lpac->qos.pd_max; + + /* TODO:Add support for setting preffered settings on bt_bap_pac */ + config = (void *)status->params; + config->framing = lpac->qos.framing; + config->phy = lpac->qos.phy; + config->rtn = lpac->qos.rtn; + config->latency = cpu_to_le16(lpac->qos.latency); + put_le24(lpac->qos.pd_min, config->pd_min); + put_le24(lpac->qos.pd_max, config->pd_max); + put_le24(lpac->qos.ppd_min, config->ppd_min); + put_le24(lpac->qos.ppd_max, config->ppd_max); + config->codec = lpac->codec; + config->cc_len = stream->cc->iov_len; + memcpy(config->cc, stream->cc->iov_base, stream->cc->iov_len); + + gatt_db_attribute_notify(ep->attr, (void *) status, len, + bt_bap_get_att(stream->bap)); + + free(status); +} + +static void stream_notify_qos(struct bt_bap_stream *stream) +{ + struct bt_bap_endpoint *ep = stream->ep; + struct bt_ascs_ase_status *status; + struct bt_ascs_ase_status_qos *qos; + size_t len; + + DBG(stream->bap, "stream %p", stream); + + len = sizeof(*status) + sizeof(*qos); + status = malloc(len); + + memset(status, 0, len); + status->id = ep->id; + status->state = ep->state; + + qos = (void *)status->params; + qos->cis_id = stream->qos.cis_id; + qos->cig_id = stream->qos.cig_id; + put_le24(stream->qos.interval, qos->interval); + qos->framing = stream->qos.framing; + qos->phy = stream->qos.phy; + qos->sdu = cpu_to_le16(stream->qos.sdu); + qos->rtn = stream->qos.rtn; + qos->latency = cpu_to_le16(stream->qos.latency); + put_le24(stream->qos.delay, qos->pd); + + gatt_db_attribute_notify(ep->attr, (void *) status, len, + bt_bap_get_att(stream->bap)); + + free(status); +} + +static void stream_notify_metadata(struct bt_bap_stream *stream) +{ + struct bt_bap_endpoint *ep = stream->ep; + struct bt_ascs_ase_status *status; + struct bt_ascs_ase_status_metadata *meta; + size_t len; + + DBG(stream->bap, "stream %p", stream); + + len = sizeof(*status) + sizeof(*meta) + sizeof(stream->meta->iov_len); + status = malloc(len); + + memset(status, 0, len); + status->id = ep->id; + status->state = ep->state; + + meta = (void *)status->params; + meta->cis_id = stream->qos.cis_id; + meta->cig_id = stream->qos.cig_id; + + if (stream->meta) { + meta->len = stream->meta->iov_len; + memcpy(meta->data, stream->meta->iov_base, meta->len); + } + + gatt_db_attribute_notify(ep->attr, (void *) status, len, + bt_bap_get_att(stream->bap)); + + free(status); +} + +static void bap_stream_clear_cfm(struct bt_bap_stream *stream) +{ + if (!stream->lpac->ops || !stream->lpac->ops->clear) + return; + + stream->lpac->ops->clear(stream, stream->lpac->user_data); +} + +static int stream_io_get_fd(struct bt_bap_stream_io *io) +{ + if (!io) + return -1; + + return io_get_fd(io->io); +} + +static void stream_io_free(void *data) +{ + struct bt_bap_stream_io *io = data; + int fd; + + fd = stream_io_get_fd(io); + + DBG(io->bap, "fd %d", fd); + + io_destroy(io->io); + free(io); + + /* Shutdown using SHUT_WR as SHUT_RDWR cause the socket to HUP + * immediately instead of waiting for Disconnect Complete event. + */ + shutdown(fd, SHUT_WR); +} + +static void stream_io_unref(struct bt_bap_stream_io *io) +{ + if (!io) + return; + + if (__sync_sub_and_fetch(&io->ref_count, 1)) + return; + + stream_io_free(io); +} + +static void bap_stream_unlink(void *data, void *user_data) +{ + struct bt_bap_stream *link = data; + struct bt_bap_stream *stream = user_data; + + queue_remove(link->links, stream); +} + +static void bap_stream_free(void *data) +{ + struct bt_bap_stream *stream = data; + + if (stream->ep) + stream->ep->stream = NULL; + + queue_foreach(stream->links, bap_stream_unlink, stream); + queue_destroy(stream->links, NULL); + stream_io_unref(stream->io); + iov_free(stream->cc); + iov_free(stream->meta); + free(stream); +} + +static void bap_ep_detach(struct bt_bap_endpoint *ep) +{ + struct bt_bap_stream *stream = ep->stream; + + if (!stream) + return; + + queue_remove(stream->bap->streams, stream); + bap_stream_clear_cfm(stream); + + stream->ep = NULL; + ep->stream = NULL; + bap_stream_free(stream); +} + +static void bap_stream_io_link(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct bt_bap_stream *link = user_data; + + bt_bap_stream_io_link(stream, link); +} + +static void bap_stream_update_io_links(struct bt_bap_stream *stream) +{ + struct bt_bap *bap = stream->bap; + + DBG(bap, "stream %p", stream); + + queue_foreach(bap->streams, bap_stream_io_link, stream); +} + +static struct bt_bap_stream_io *stream_io_ref(struct bt_bap_stream_io *io) +{ + if (!io) + return NULL; + + __sync_fetch_and_add(&io->ref_count, 1); + + return io; +} + +static struct bt_bap_stream_io *stream_io_new(struct bt_bap *bap, int fd) +{ + struct io *io; + struct bt_bap_stream_io *sio; + + io = io_new(fd); + if (!io) + return NULL; + + DBG(bap, "fd %d", fd); + + sio = new0(struct bt_bap_stream_io, 1); + sio->bap = bap; + sio->io = io; + + return stream_io_ref(sio); +} + +static void stream_find_io(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct bt_bap_stream_io **io = user_data; + + if (*io) + return; + + *io = stream->io; +} + +static struct bt_bap_stream_io *stream_get_io(struct bt_bap_stream *stream) +{ + struct bt_bap_stream_io *io; + + if (!stream) + return NULL; + + if (stream->io) + return stream->io; + + io = NULL; + queue_foreach(stream->links, stream_find_io, &io); + + return io; +} + +static bool stream_io_disconnected(struct io *io, void *user_data); + +static bool bap_stream_io_attach(struct bt_bap_stream *stream, int fd, + bool connecting) +{ + struct bt_bap_stream_io *io; + + io = stream_get_io(stream); + if (io) { + if (fd == stream_io_get_fd(io)) { + if (!stream->io) + stream->io = stream_io_ref(io); + + io->connecting = connecting; + return true; + } + + DBG(stream->bap, "stream %p io already set", stream); + return false; + } + + DBG(stream->bap, "stream %p connecting %s", stream, + connecting ? "true" : "false"); + + io = stream_io_new(stream->bap, fd); + if (!io) + return false; + + io->connecting = connecting; + stream->io = io; + io_set_disconnect_handler(io->io, stream_io_disconnected, stream, NULL); + + return true; +} + +static bool match_stream_io(const void *data, const void *user_data) +{ + const struct bt_bap_stream *stream = data; + const struct bt_bap_stream_io *io = user_data; + + if (!stream->io) + return false; + + return stream->io == io; +} + +static bool bap_stream_io_detach(struct bt_bap_stream *stream) +{ + struct bt_bap_stream *link; + struct bt_bap_stream_io *io; + + if (!stream->io) + return false; + + DBG(stream->bap, "stream %p", stream); + + io = stream->io; + stream->io = NULL; + + link = queue_find(stream->links, match_stream_io, io); + if (link) { + /* Detach link if in QoS state */ + if (link->ep->state == BT_ASCS_ASE_STATE_QOS) + bap_stream_io_detach(link); + } + + stream_io_unref(io); + + return true; +} + +static void bap_stream_set_io(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + int fd = PTR_TO_INT(user_data); + bool ret; + + if (fd >= 0) + ret = bap_stream_io_attach(stream, fd, false); + else + ret = bap_stream_io_detach(stream); + + if (!ret) + return; + + switch (stream->ep->state) { + case BT_BAP_STREAM_STATE_ENABLING: + if (fd < 0) + bt_bap_stream_disable(stream, false, NULL, NULL); + else + bt_bap_stream_start(stream, NULL, NULL); + break; + case BT_BAP_STREAM_STATE_DISABLING: + if (fd < 0) + bt_bap_stream_stop(stream, NULL, NULL); + break; + } +} + +static void bap_stream_state_changed(struct bt_bap_stream *stream) +{ + struct bt_bap *bap = stream->bap; + const struct queue_entry *entry; + + DBG(bap, "stream %p dir 0x%02x: %s -> %s", stream, + bt_bap_stream_get_dir(stream), + bt_bap_stream_statestr(stream->ep->old_state), + bt_bap_stream_statestr(stream->ep->state)); + + bt_bap_ref(bap); + + /* Pre notification updates */ + switch (stream->ep->state) { + case BT_ASCS_ASE_STATE_IDLE: + break; + case BT_ASCS_ASE_STATE_CONFIG: + bap_stream_update_io_links(stream); + break; + case BT_ASCS_ASE_STATE_DISABLING: + bap_stream_io_detach(stream); + break; + case BT_ASCS_ASE_STATE_QOS: + if (stream->io && !stream->io->connecting) + bap_stream_io_detach(stream); + else + bap_stream_update_io_links(stream); + break; + case BT_ASCS_ASE_STATE_ENABLING: + case BT_ASCS_ASE_STATE_STREAMING: + break; + } + + for (entry = queue_get_entries(bap->state_cbs); entry; + entry = entry->next) { + struct bt_bap_state *state = entry->data; + + if (state->func) + state->func(stream, stream->ep->old_state, + stream->ep->state, state->data); + } + + /* Post notification updates */ + switch (stream->ep->state) { + case BT_ASCS_ASE_STATE_IDLE: + bap_ep_detach(stream->ep); + break; + case BT_ASCS_ASE_STATE_QOS: + break; + case BT_ASCS_ASE_STATE_ENABLING: + if (bt_bap_stream_get_io(stream)) + bt_bap_stream_start(stream, NULL, NULL); + break; + case BT_ASCS_ASE_STATE_DISABLING: + if (!bt_bap_stream_get_io(stream)) + bt_bap_stream_stop(stream, NULL, NULL); + break; + } + + bt_bap_unref(bap); +} + +static void stream_set_state(struct bt_bap_stream *stream, uint8_t state) +{ + struct bt_bap_endpoint *ep = stream->ep; + + ep->old_state = ep->state; + ep->state = state; + + if (stream->client) + goto done; + + switch (ep->state) { + case BT_ASCS_ASE_STATE_IDLE: + break; + case BT_ASCS_ASE_STATE_CONFIG: + stream_notify_config(stream); + break; + case BT_ASCS_ASE_STATE_QOS: + stream_notify_qos(stream); + break; + case BT_ASCS_ASE_STATE_ENABLING: + case BT_ASCS_ASE_STATE_STREAMING: + case BT_ASCS_ASE_STATE_DISABLING: + stream_notify_metadata(stream); + break; + } + +done: + bap_stream_state_changed(stream); +} + +static void ascs_ase_rsp_add(struct iovec *iov, uint8_t id, + uint8_t code, uint8_t reason) +{ + struct bt_ascs_cp_rsp *cp; + struct bt_ascs_ase_rsp *rsp; + + if (!iov) + return; + + cp = iov->iov_base; + + if (cp->num_ase == 0xff) + return; + + switch (code) { + /* If the Response_Code value is 0x01 or 0x02, Number_of_ASEs shall be + * set to 0xFF. + */ + case BT_ASCS_RSP_NOT_SUPPORTED: + case BT_ASCS_RSP_TRUNCATED: + cp->num_ase = 0xff; + break; + default: + cp->num_ase++; + break; + } + + iov->iov_len += sizeof(*rsp); + iov->iov_base = realloc(iov->iov_base, iov->iov_len); + + rsp = iov->iov_base + (iov->iov_len - sizeof(*rsp)); + rsp->ase = id; + rsp->code = code; + rsp->reason = reason; +} + +static void ascs_ase_rsp_add_errno(struct iovec *iov, uint8_t id, int err) +{ + struct bt_ascs_cp_rsp *rsp = iov->iov_base; + + switch (err) { + case -ENOBUFS: + case -ENOMEM: + return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_NO_MEM, + BT_ASCS_REASON_NONE); + case -EINVAL: + switch (rsp->op) { + case BT_ASCS_CONFIG: + /* Fallthrough */ + case BT_ASCS_QOS: + return ascs_ase_rsp_add(iov, id, + BT_ASCS_RSP_CONF_INVALID, + BT_ASCS_REASON_NONE); + case BT_ASCS_ENABLE: + /* Fallthrough */ + case BT_ASCS_METADATA: + return ascs_ase_rsp_add(iov, id, + BT_ASCS_RSP_METADATA_INVALID, + BT_ASCS_REASON_NONE); + default: + return ascs_ase_rsp_add(iov, id, + BT_ASCS_RSP_UNSPECIFIED, + BT_ASCS_REASON_NONE); + } + case -ENOTSUP: + switch (rsp->op) { + case BT_ASCS_CONFIG: + /* Fallthrough */ + case BT_ASCS_QOS: + return ascs_ase_rsp_add(iov, id, + BT_ASCS_RSP_CONF_UNSUPPORTED, + BT_ASCS_REASON_NONE); + case BT_ASCS_ENABLE: + /* Fallthrough */ + case BT_ASCS_METADATA: + return ascs_ase_rsp_add(iov, id, + BT_ASCS_RSP_METADATA_UNSUPPORTED, + BT_ASCS_REASON_NONE); + default: + return ascs_ase_rsp_add(iov, id, + BT_ASCS_RSP_NOT_SUPPORTED, + BT_ASCS_REASON_NONE); + } + case -EBADMSG: + return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + case -ENOMSG: + return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_TRUNCATED, + BT_ASCS_REASON_NONE); + default: + return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_UNSPECIFIED, + BT_ASCS_REASON_NONE); + } +} + +static void ascs_ase_rsp_success(struct iovec *iov, uint8_t id) +{ + return ascs_ase_rsp_add(iov, id, BT_ASCS_RSP_SUCCESS, + BT_ASCS_REASON_NONE); +} + +static void ep_config_cb(struct bt_bap_stream *stream, int err) +{ + if (err) + return; + + stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); +} + +static uint8_t stream_config(struct bt_bap_stream *stream, struct iovec *cc, + struct iovec *rsp) +{ + struct bt_bap_pac *pac = stream->lpac; + + DBG(stream->bap, "stream %p", stream); + + /* TODO: Wait for pac->ops response */ + ascs_ase_rsp_success(rsp, stream->ep->id); + + if (!iov_memcmp(stream->cc, cc)) { + stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); + return 0; + } + + iov_free(stream->cc); + stream->cc = iov_dup(cc, 1); + + if (pac->ops && pac->ops->config) + pac->ops->config(stream, cc, NULL, ep_config_cb, + pac->user_data); + + return 0; +} + +static uint8_t ep_config(struct bt_bap_endpoint *ep, struct bt_bap *bap, + struct bt_ascs_config *req, + struct iovec *iov, struct iovec *rsp) +{ + struct iovec cc; + const struct queue_entry *e; + + DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); + + switch (ep->state) { + /* Valid only if ASE_State field = 0x00 (Idle) */ + case BT_ASCS_ASE_STATE_IDLE: + /* or 0x01 (Codec Configured) */ + case BT_ASCS_ASE_STATE_CONFIG: + /* or 0x02 (QoS Configured) */ + case BT_ASCS_ASE_STATE_QOS: + break; + default: + DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + if (iov->iov_len < req->cc_len) + return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + + cc.iov_base = iov_pull_mem(iov, req->cc_len); + cc.iov_len = req->cc_len; + + if (!bap_print_cc(cc.iov_base, cc.iov_len, bap->debug_func, + bap->debug_data)) { + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_CONF_INVALID, + BT_ASCS_REASON_CODEC_DATA); + return 0; + } + + switch (ep->dir) { + case BT_BAP_SINK: + e = queue_get_entries(bap->ldb->sinks); + break; + case BT_BAP_SOURCE: + e = queue_get_entries(bap->ldb->sources); + break; + default: + e = NULL; + } + + for (; e; e = e->next) { + struct bt_bap_pac *pac = e->data; + + if (!bap_codec_equal(&req->codec, &pac->codec)) + continue; + + if (!ep->stream) + ep->stream = bap_stream_new(bap, ep, pac, NULL, NULL, + false); + + break; + } + + if (!e) { + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_CONF_INVALID, + BT_ASCS_REASON_CODEC); + return 0; + } + + return stream_config(ep->stream, &cc, rsp); +} + +static uint8_t ascs_config(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_config *req; + + req = iov_pull_mem(iov, sizeof(*req)); + + DBG(bap, "codec 0x%02x phy 0x%02x latency %u", req->codec.id, req->phy, + req->latency); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + if (!ep) { + DBG(bap, "Invalid ASE ID 0x%02x", req->ase); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + return ep_config(ep, bap, req, iov, rsp); +} + +static uint8_t stream_qos(struct bt_bap_stream *stream, struct bt_bap_qos *qos, + struct iovec *rsp) +{ + DBG(stream->bap, "stream %p", stream); + + ascs_ase_rsp_success(rsp, stream->ep->id); + + if (memcmp(&stream->qos, qos, sizeof(*qos))) + stream->qos = *qos; + + stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); + + return 0; +} + +static uint8_t ep_qos(struct bt_bap_endpoint *ep, struct bt_bap *bap, + struct bt_bap_qos *qos, struct iovec *rsp) +{ + DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); + + switch (ep->state) { + /* Valid only if ASE_State field = 0x01 (Codec Configured) */ + case BT_ASCS_ASE_STATE_CONFIG: + /* or 0x02 (QoS Configured) */ + case BT_ASCS_ASE_STATE_QOS: + break; + default: + DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + if (!ep->stream) { + DBG(bap, "No stream found"); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return stream_qos(ep->stream, qos, rsp); +} + +static uint8_t ascs_qos(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_qos *req; + struct bt_bap_qos qos; + + req = iov_pull_mem(iov, sizeof(*req)); + + memset(&qos, 0, sizeof(qos)); + + qos.cig_id = req->cig; + qos.cis_id = req->cis; + qos.interval = get_le24(req->interval); + qos.framing = req->framing; + qos.phy = req->phy; + qos.sdu = le16_to_cpu(req->sdu); + qos.rtn = req->rtn; + qos.latency = le16_to_cpu(req->latency); + qos.delay = get_le24(req->pd); + + DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x " + "phy 0x%02x SDU %u rtn %u latency %u pd %u", + req->cig, req->cis, qos.interval, qos.framing, qos.phy, + qos.sdu, qos.rtn, qos.latency, qos.delay); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + if (!ep) { + DBG(bap, "%s: Invalid ASE ID 0x%02x", req->ase); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + return ep_qos(ep, bap, &qos, rsp); +} + +static uint8_t stream_enable(struct bt_bap_stream *stream, struct iovec *meta, + struct iovec *rsp) +{ + DBG(stream->bap, "stream %p", stream); + + ascs_ase_rsp_success(rsp, stream->ep->id); + + iov_free(stream->meta); + stream->meta = iov_dup(meta, 1); + + stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING); + + /* Sink can autonomously for to Streaming state if io already exits */ + if (stream->io && stream->ep->dir == BT_BAP_SINK) + stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING); + + return 0; +} + +static bool bap_print_ltv(const char *label, void *data, size_t len, + util_debug_func_t func, void *user_data) +{ + struct iovec iov = { + .iov_base = data, + .iov_len = len, + }; + int i; + + util_debug(func, user_data, "Length %zu", iov.iov_len); + + for (i = 0; iov.iov_len > 1; i++) { + struct bt_ltv *ltv = iov_pull_mem(&iov, sizeof(*ltv)); + uint8_t *data; + + if (!ltv) { + util_debug(func, user_data, "Unable to parse %s", + label); + return false; + } + + util_debug(func, user_data, "%s #%u: len %u type %u", + label, i, ltv->len, ltv->type); + + data = iov_pull_mem(&iov, ltv->len - 1); + if (!data) { + util_debug(func, user_data, "Unable to parse %s", + label); + return false; + } + + util_hexdump(' ', ltv->value, ltv->len - 1, func, user_data); + } + + return true; +} + +static bool bap_print_metadata(void *data, size_t len, util_debug_func_t func, + void *user_data) +{ + return bap_print_ltv("Metadata", data, len, func, user_data); +} + +static uint8_t ep_enable(struct bt_bap_endpoint *ep, struct bt_bap *bap, + struct bt_ascs_enable *req, struct iovec *iov, + struct iovec *rsp) +{ + struct iovec meta; + + DBG(bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); + + switch (ep->state) { + /* Valid only if ASE_State field = 0x02 (QoS Configured) */ + case BT_ASCS_ASE_STATE_QOS: + break; + default: + DBG(bap, "Invalid state %s", bt_bap_stream_statestr(ep->state)); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + meta.iov_base = iov_pull_mem(iov, req->meta.len); + meta.iov_len = req->meta.len; + + if (!bap_print_metadata(meta.iov_base, meta.iov_len, bap->debug_func, + bap->debug_data)) { + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_METADATA_INVALID, + BT_ASCS_REASON_NONE); + return 0; + } + + if (!ep->stream) { + DBG(bap, "No stream found"); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return stream_enable(ep->stream, iov, rsp); +} + +static uint8_t ascs_enable(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_enable *req; + + req = iov_pull_mem(iov, sizeof(*req)); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->meta.ase); + if (!ep) { + DBG(bap, "Invalid ASE ID 0x%02x", req->meta.ase); + ascs_ase_rsp_add(rsp, req->meta.ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + return ep_enable(ep, bap, req, iov, rsp); +} + +static uint8_t stream_start(struct bt_bap_stream *stream, struct iovec *rsp) +{ + DBG(stream->bap, "stream %p", stream); + + ascs_ase_rsp_success(rsp, stream->ep->id); + + stream_set_state(stream, BT_BAP_STREAM_STATE_STREAMING); + + return 0; +} + +static uint8_t ep_start(struct bt_bap_endpoint *ep, struct iovec *rsp) +{ + struct bt_bap_stream *stream = ep->stream; + + DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); + + switch (ep->state) { + /* Valid only if ASE_State field = 0x03 (Enabling) */ + case BT_ASCS_ASE_STATE_ENABLING: + break; + default: + DBG(ep->stream->bap, "Invalid state %s", + bt_bap_stream_statestr(ep->state)); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + /* If the ASE_ID written by the client represents a Sink ASE, the + * server shall not accept the Receiver Start Ready operation for that + * ASE. The server shall send a notification of the ASE Control Point + * characteristic to the client, and the server shall set the + * Response_Code value for that ASE to 0x05 (Invalid ASE direction). + */ + if (ep->dir == BT_BAP_SINK) { + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE); + return 0; + } + + return stream_start(ep->stream, rsp); +} + +static uint8_t ascs_start(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_start *req; + + req = iov_pull_mem(iov, sizeof(*req)); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + if (!ep) { + DBG(bap, "Invalid ASE ID 0x%02x", req->ase); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + if (!ep->stream) { + DBG(bap, "No stream found for %p", ep); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return ep_start(ep, rsp); +} + +static uint8_t stream_disable(struct bt_bap_stream *stream, struct iovec *rsp) +{ + DBG(stream->bap, "stream %p", stream); + + if (!stream || stream->ep->state == BT_BAP_STREAM_STATE_QOS) + return 0; + + ascs_ase_rsp_success(rsp, stream->ep->id); + + /* Sink can autonomously transit to QOS while source needs to go to + * Disabling until BT_ASCS_STOP is received. + */ + if (stream->ep->dir == BT_BAP_SINK) + stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); + else + stream_set_state(stream, BT_BAP_STREAM_STATE_DISABLING); + + return 0; +} + +static uint8_t ep_disable(struct bt_bap_endpoint *ep, struct iovec *rsp) +{ + struct bt_bap_stream *stream = ep->stream; + + DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); + + switch (ep->state) { + /* Valid only if ASE_State field = 0x03 (Enabling) */ + case BT_ASCS_ASE_STATE_ENABLING: + /* or 0x04 (Streaming) */ + case BT_ASCS_ASE_STATE_STREAMING: + break; + default: + DBG(stream->bap, "Invalid state %s", + bt_bap_stream_statestr(ep->state)); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return stream_disable(ep->stream, rsp); +} + +static uint8_t ascs_disable(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_disable *req; + + req = iov_pull_mem(iov, sizeof(*req)); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + if (!ep) { + DBG(bap, "Invalid ASE ID 0x%02x", req->ase); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + if (!ep->stream) { + DBG(bap, "No stream found"); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return ep_disable(ep, rsp); +} + +static uint8_t stream_stop(struct bt_bap_stream *stream, struct iovec *rsp) +{ + DBG(stream->bap, "stream %p", stream); + + if (!stream) + return 0; + + ascs_ase_rsp_success(rsp, stream->ep->id); + + stream_set_state(stream, BT_BAP_STREAM_STATE_QOS); + + return 0; +} + +static uint8_t ep_stop(struct bt_bap_endpoint *ep, struct iovec *rsp) +{ + struct bt_bap_stream *stream = ep->stream; + + DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); + + switch (ep->state) { + /* Valid only if ASE_State field = 0x05 (Disabling) */ + case BT_ASCS_ASE_STATE_DISABLING: + break; + default: + DBG(stream->bap, "Invalid state %s", + bt_bap_stream_statestr(ep->state)); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + /* If the ASE_ID written by the client represents a Sink ASE, the + * server shall not accept the Receiver Stop Ready operation for that + * ASE. The server shall send a notification of the ASE Control Point + * characteristic to the client, and the server shall set the + * Response_Code value for that ASE to 0x05 (Invalid ASE direction). + */ + if (ep->dir == BT_BAP_SINK) { + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_DIR, BT_ASCS_REASON_NONE); + return 0; + } + + return stream_stop(ep->stream, rsp); +} + +static uint8_t ascs_stop(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_stop *req; + + req = iov_pull_mem(iov, sizeof(*req)); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + if (!ep) { + DBG(bap, "Invalid ASE ID 0x%02x", req->ase); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + if (!ep->stream) { + DBG(bap, "No stream found"); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return ep_stop(ep, rsp); +} + +static uint8_t stream_metadata(struct bt_bap_stream *stream, struct iovec *meta, + struct iovec *rsp) +{ + DBG(stream->bap, "stream %p", stream); + + ascs_ase_rsp_success(rsp, stream->ep->id); + + iov_free(stream->meta); + stream->meta = iov_dup(meta, 1); + + return 0; +} + +static uint8_t ep_metadata(struct bt_bap_endpoint *ep, struct iovec *meta, + struct iovec *rsp) +{ + struct bt_bap_stream *stream = ep->stream; + + DBG(stream->bap, "ep %p id 0x%02x dir 0x%02x", ep, ep->id, ep->dir); + + switch (ep->state) { + /* Valid only if ASE_State field = 0x03 (Enabling) */ + case BT_ASCS_ASE_STATE_ENABLING: + /* or 0x04 (Streaming) */ + case BT_ASCS_ASE_STATE_STREAMING: + break; + default: + DBG(stream->bap, "Invalid state %s", + bt_bap_stream_statestr(ep->state)); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return stream_metadata(ep->stream, meta, rsp); +} + +static uint8_t ascs_metadata(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_metadata *req; + + req = iov_pull_mem(iov, sizeof(*req)); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + if (!ep) { + DBG(bap, "Invalid ASE ID 0x%02x", req->ase); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + if (!ep->stream) { + DBG(bap, "No stream found"); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return ep_metadata(ep, iov, rsp); +} + +static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp) +{ + struct bt_bap_pac *pac; + + DBG(stream->bap, "stream %p", stream); + + ascs_ase_rsp_success(rsp, stream->ep->id); + + pac = stream->lpac; + if (pac->ops && pac->ops->clear) + pac->ops->clear(stream, pac->user_data); + + stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); + + return 0; +} + +static uint8_t ascs_release(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp) +{ + struct bt_bap_endpoint *ep; + struct bt_ascs_release *req; + + req = iov_pull_mem(iov, sizeof(*req)); + + ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + if (!ep) { + DBG(bap, "Invalid ASE ID 0x%02x", req->ase); + ascs_ase_rsp_add(rsp, req->ase, + BT_ASCS_RSP_INVALID_ASE, BT_ASCS_REASON_NONE); + return 0; + } + + if (!ep->stream) { + DBG(bap, "No stream found"); + ascs_ase_rsp_add(rsp, ep->id, + BT_ASCS_RSP_INVALID_ASE_STATE, + BT_ASCS_REASON_NONE); + return 0; + } + + return stream_release(ep->stream, rsp); +} + +#define ASCS_OP(_str, _op, _size, _func) \ + { \ + .str = _str, \ + .op = _op, \ + .size = _size, \ + .func = _func, \ + } + +struct ascs_op_handler { + const char *str; + uint8_t op; + size_t size; + uint8_t (*func)(struct bt_ascs *ascs, struct bt_bap *bap, + struct iovec *iov, struct iovec *rsp); +} handlers[] = { + ASCS_OP("Codec Config", BT_ASCS_CONFIG, + sizeof(struct bt_ascs_config), ascs_config), + ASCS_OP("QoS Config", BT_ASCS_QOS, + sizeof(struct bt_ascs_qos), ascs_qos), + ASCS_OP("Enable", BT_ASCS_ENABLE, sizeof(struct bt_ascs_enable), + ascs_enable), + ASCS_OP("Receiver Start Ready", BT_ASCS_START, + sizeof(struct bt_ascs_start), ascs_start), + ASCS_OP("Disable", BT_ASCS_DISABLE, + sizeof(struct bt_ascs_disable), ascs_disable), + ASCS_OP("Receiver Stop Ready", BT_ASCS_STOP, + sizeof(struct bt_ascs_stop), ascs_stop), + ASCS_OP("Update Metadata", BT_ASCS_METADATA, + sizeof(struct bt_ascs_metadata), ascs_metadata), + ASCS_OP("Release", BT_ASCS_RELEASE, + sizeof(struct bt_ascs_release), ascs_release), + {} +}; + +static struct iovec *ascs_ase_cp_rsp_new(uint8_t op) +{ + struct bt_ascs_cp_rsp *rsp; + struct iovec *iov; + + iov = new0(struct iovec, 1); + rsp = new0(struct bt_ascs_cp_rsp, 1); + rsp->op = op; + iov->iov_base = rsp; + iov->iov_len = sizeof(*rsp); + + return iov; +} + +static void ascs_ase_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) +{ + struct bt_ascs *ascs = user_data; + struct bt_bap *bap = bap_get_session(att, ascs->bdb->db); + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = len, + }; + struct bt_ascs_ase_hdr *hdr; + struct ascs_op_handler *handler; + uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + struct iovec *rsp; + + if (offset) { + DBG(bap, "invalid offset %u", offset); + gatt_db_attribute_write_result(attrib, id, + BT_ATT_ERROR_INVALID_OFFSET); + return; + } + + if (len < sizeof(*hdr)) { + DBG(bap, "invalid len %u < %u sizeof(*hdr)", len, + sizeof(*hdr)); + gatt_db_attribute_write_result(attrib, id, + BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN); + return; + } + + hdr = iov_pull_mem(&iov, sizeof(*hdr)); + rsp = ascs_ase_cp_rsp_new(hdr->op); + + for (handler = handlers; handler && handler->str; handler++) { + if (handler->op != hdr->op) + continue; + + if (iov.iov_len < hdr->num * handler->size) { + DBG(bap, "invalid len %u < %u " + "hdr->num * handler->size", len, + hdr->num * handler->size); + ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto respond; + } + + break; + } + + if (handler && handler->str) { + int i; + + DBG(bap, "%s", handler->str); + + for (i = 0; i < hdr->num; i++) + ret = handler->func(ascs, bap, &iov, rsp); + } else { + DBG(bap, "Unknown opcode 0x%02x", hdr->op); + ascs_ase_rsp_add_errno(rsp, 0x00, -ENOTSUP); + } + +respond: + if (ret == BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN) + ascs_ase_rsp_add_errno(rsp, 0x00, -ENOMSG); + + gatt_db_attribute_notify(attrib, rsp->iov_base, rsp->iov_len, att); + gatt_db_attribute_write_result(attrib, id, ret); + + iov_free(rsp); +} + +static struct bt_ascs *ascs_new(struct gatt_db *db) +{ + struct bt_ascs *ascs; + bt_uuid_t uuid; + int i; + + if (!db) + return NULL; + + ascs = new0(struct bt_ascs, 1); + + /* Populate DB with ASCS attributes */ + bt_uuid16_create(&uuid, ASCS_UUID); + ascs->service = gatt_db_add_service(db, &uuid, true, + 4 + (NUM_ASES * 3)); + + for (i = 0; i < NUM_ASES; i++) + ase_new(ascs, i); + + bt_uuid16_create(&uuid, ASE_CP_UUID); + ascs->ase_cp = gatt_db_service_add_characteristic(ascs->service, + &uuid, + BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE | + BT_GATT_CHRC_PROP_NOTIFY, + NULL, ascs_ase_cp_write, + ascs); + + ascs->ase_cp_ccc = gatt_db_service_add_ccc(ascs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + gatt_db_service_set_active(ascs->service, true); + + return ascs; +} + +static struct bt_bap_db *bap_db_new(struct gatt_db *db) +{ + struct bt_bap_db *bdb; + + if (!db) + return NULL; + + bdb = new0(struct bt_bap_db, 1); + bdb->db = gatt_db_ref(db); + bdb->sinks = queue_new(); + bdb->sources = queue_new(); + bdb->endpoints = queue_new(); + + if (!bap_db) + bap_db = queue_new(); + + bdb->pacs = pacs_new(db); + bdb->pacs->bdb = bdb; + + bdb->ascs = ascs_new(db); + bdb->ascs->bdb = bdb; + + queue_push_tail(bap_db, bdb); + + return bdb; +} + +static struct bt_bap_db *bap_get_db(struct gatt_db *db) +{ + struct bt_bap_db *bdb; + + bdb = queue_find(bap_db, bap_db_match, db); + if (bdb) + return bdb; + + return bap_db_new(db); +} + +static struct bt_pacs *bap_get_pacs(struct bt_bap *bap) +{ + if (!bap) + return NULL; + + if (bap->rdb->pacs) + return bap->rdb->pacs; + + bap->rdb->pacs = new0(struct bt_pacs, 1); + bap->rdb->pacs->bdb = bap->rdb; + + return bap->rdb->pacs; +} + +static struct bt_ascs *bap_get_ascs(struct bt_bap *bap) +{ + if (!bap) + return NULL; + + if (bap->rdb->ascs) + return bap->rdb->ascs; + + bap->rdb->ascs = new0(struct bt_ascs, 1); + bap->rdb->ascs->bdb = bap->rdb; + + return bap->rdb->ascs; +} + +static struct bt_bap_pac *bap_pac_new(struct bt_bap_db *bdb, const char *name, + uint8_t type, + struct bt_bap_codec *codec, + struct bt_bap_pac_qos *qos, + struct iovec *data, + struct iovec *metadata) +{ + struct bt_bap_pac *pac; + + pac = new0(struct bt_bap_pac, 1); + pac->bdb = bdb; + pac->name = name ? strdup(name) : NULL; + pac->type = type; + pac->codec = *codec; + pac->data = iov_dup(data, 1); + pac->metadata = iov_dup(metadata, 1); + + if (qos) + pac->qos = *qos; + + return pac; +} + +static void bap_pac_free(void *data) +{ + struct bt_bap_pac *pac = data; + + free(pac->name); + iov_free(pac->metadata); + iov_free(pac->data); + free(pac); +} + +static void bap_add_sink(struct bt_bap_pac *pac) +{ + struct iovec iov; + uint8_t value[512]; + + queue_push_tail(pac->bdb->sinks, pac); + + memset(value, 0, sizeof(value)); + + iov.iov_base = value; + iov.iov_len = 0; + + queue_foreach(pac->bdb->sinks, pac_foreach, &iov); + + gatt_db_attribute_notify(pac->bdb->pacs->sink, iov.iov_base, + iov.iov_len, NULL); +} + +static void bap_add_source(struct bt_bap_pac *pac) +{ + struct iovec iov; + uint8_t value[512]; + + queue_push_tail(pac->bdb->sources, pac); + + memset(value, 0, sizeof(value)); + + iov.iov_base = value; + iov.iov_len = 0; + + queue_foreach(pac->bdb->sinks, pac_foreach, &iov); + + gatt_db_attribute_notify(pac->bdb->pacs->source, iov.iov_base, + iov.iov_len, NULL); +} + +static void notify_pac_added(void *data, void *user_data) +{ + struct bt_bap_pac_changed *changed = data; + struct bt_bap_pac *pac = user_data; + + if (changed->added) + changed->added(pac, changed->data); +} + +struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db, + const char *name, uint8_t type, + uint8_t id, uint16_t cid, uint16_t vid, + struct bt_bap_pac_qos *qos, + struct iovec *data, + struct iovec *metadata) +{ + struct bt_bap_db *bdb; + struct bt_bap_pac *pac; + struct bt_bap_codec codec; + + if (!db) + return NULL; + + bdb = bap_get_db(db); + if (!bdb) + return NULL; + + codec.id = id; + codec.cid = cid; + codec.vid = vid; + + pac = bap_pac_new(bdb, name, type, &codec, qos, data, metadata); + + switch (type) { + case BT_BAP_SINK: + bap_add_sink(pac); + break; + case BT_BAP_SOURCE: + bap_add_source(pac); + break; + default: + bap_pac_free(pac); + return NULL; + } + + queue_foreach(pac_cbs, notify_pac_added, pac); + + return pac; +} + +struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name, + uint8_t type, uint8_t id, + struct bt_bap_pac_qos *qos, + struct iovec *data, + struct iovec *metadata) +{ + return bt_bap_add_vendor_pac(db, name, type, id, 0x0000, 0x0000, qos, + data, metadata); +} + +uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac) +{ + if (!pac) + return 0x00; + + return pac->type; +} + +static void notify_pac_removed(void *data, void *user_data) +{ + struct bt_bap_pac_changed *changed = data; + struct bt_bap_pac *pac = user_data; + + if (changed->removed) + changed->removed(pac, changed->data); +} + +bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops, + void *user_data) +{ + if (!pac) + return false; + + pac->ops = ops; + pac->user_data = user_data; + + return true; +} + +static bool match_stream_lpac(const void *data, const void *user_data) +{ + const struct bt_bap_stream *stream = data; + const struct bt_bap_pac *pac = user_data; + + return stream->lpac == pac; +} + +static void remove_streams(void *data, void *user_data) +{ + struct bt_bap *bap = data; + struct bt_bap_pac *pac = user_data; + struct bt_bap_stream *stream; + + stream = queue_remove_if(bap->streams, match_stream_lpac, pac); + if (stream) + bt_bap_stream_release(stream, NULL, NULL); +} + +bool bt_bap_remove_pac(struct bt_bap_pac *pac) +{ + if (!pac) + return false; + + if (queue_remove_if(pac->bdb->sinks, NULL, pac)) + goto found; + + if (queue_remove_if(pac->bdb->sources, NULL, pac)) + goto found; + + return false; + +found: + queue_foreach(sessions, remove_streams, pac); + queue_foreach(pac_cbs, notify_pac_removed, pac); + bap_pac_free(pac); + return true; +} + +static void bap_db_free(void *data) +{ + struct bt_bap_db *bdb = data; + + if (!bdb) + return; + + queue_destroy(bdb->sinks, bap_pac_free); + queue_destroy(bdb->sources, bap_pac_free); + queue_destroy(bdb->endpoints, free); + gatt_db_unref(bdb->db); + + free(bdb->pacs); + free(bdb->ascs); + free(bdb); +} + +static void bap_ready_free(void *data) +{ + struct bt_bap_ready *ready = data; + + if (ready->destroy) + ready->destroy(ready->data); + + free(ready); +} + +static void bap_state_free(void *data) +{ + struct bt_bap_state *state = data; + + if (state->destroy) + state->destroy(state->data); + + free(state); +} + +static void bap_req_free(void *data) +{ + struct bt_bap_req *req = data; + size_t i; + + queue_destroy(req->group, bap_req_free); + + for (i = 0; i < req->len; i++) + free(req->iov[i].iov_base); + + free(req->iov); + free(req); +} + +static void bap_detached(void *data, void *user_data) +{ + struct bt_bap_cb *cb = data; + struct bt_bap *bap = user_data; + + cb->detached(bap, cb->user_data); +} + +static void bap_free(void *data) +{ + struct bt_bap *bap = data; + + bt_bap_detach(bap); + + bap_db_free(bap->rdb); + + queue_destroy(bap->ready_cbs, bap_ready_free); + queue_destroy(bap->state_cbs, bap_state_free); + + queue_destroy(bap->reqs, bap_req_free); + queue_destroy(bap->pending, NULL); + queue_destroy(bap->notify, NULL); + queue_destroy(bap->streams, bap_stream_free); + + free(bap); +} + +unsigned int bt_bap_register(bt_bap_func_t attached, bt_bap_func_t detached, + void *user_data) +{ + struct bt_bap_cb *cb; + static unsigned int id; + + if (!attached && !detached) + return 0; + + if (!bap_cbs) + bap_cbs = queue_new(); + + cb = new0(struct bt_bap_cb, 1); + cb->id = ++id ? id : ++id; + cb->attached = attached; + cb->detached = detached; + cb->user_data = user_data; + + queue_push_tail(bap_cbs, cb); + + return cb->id; +} + +static bool match_id(const void *data, const void *match_data) +{ + const struct bt_bap_cb *cb = data; + unsigned int id = PTR_TO_UINT(match_data); + + return (cb->id == id); +} + +bool bt_bap_unregister(unsigned int id) +{ + struct bt_bap_cb *cb; + + cb = queue_remove_if(bap_cbs, match_id, UINT_TO_PTR(id)); + if (!cb) + return false; + + free(cb); + + return true; +} + +static void bap_attached(void *data, void *user_data) +{ + struct bt_bap_cb *cb = data; + struct bt_bap *bap = user_data; + + cb->attached(bap, cb->user_data); +} + +struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb) +{ + struct bt_bap *bap; + struct bt_bap_db *bdb; + + if (!ldb) + return NULL; + + bdb = bap_get_db(ldb); + if (!bdb) + return NULL; + + bap = new0(struct bt_bap, 1); + bap->ldb = bdb; + bap->reqs = queue_new(); + bap->pending = queue_new(); + bap->notify = queue_new(); + bap->ready_cbs = queue_new(); + bap->streams = queue_new(); + bap->state_cbs = queue_new(); + + if (!rdb) + goto done; + + bdb = new0(struct bt_bap_db, 1); + bdb->db = gatt_db_ref(rdb); + bdb->sinks = queue_new(); + bdb->sources = queue_new(); + bdb->endpoints = queue_new(); + + bap->rdb = bdb; + +done: + return bt_bap_ref(bap); +} + +bool bt_bap_set_user_data(struct bt_bap *bap, void *user_data) +{ + if (!bap) + return false; + + bap->user_data = user_data; + + return true; +} + +void *bt_bap_get_user_data(struct bt_bap *bap) +{ + if (!bap) + return NULL; + + return bap->user_data; +} + +struct bt_att *bt_bap_get_att(struct bt_bap *bap) +{ + if (!bap) + return NULL; + + if (bap->att) + return bap->att; + + return bt_gatt_client_get_att(bap->client); +} + +struct bt_bap *bt_bap_ref(struct bt_bap *bap) +{ + if (!bap) + return NULL; + + __sync_fetch_and_add(&bap->ref_count, 1); + + return bap; +} + +void bt_bap_unref(struct bt_bap *bap) +{ + if (!bap) + return; + + if (__sync_sub_and_fetch(&bap->ref_count, 1)) + return; + + bap_free(bap); +} + +static void bap_notify_ready(struct bt_bap *bap) +{ + const struct queue_entry *entry; + + if (!queue_isempty(bap->pending)) + return; + + bt_bap_ref(bap); + + for (entry = queue_get_entries(bap->ready_cbs); entry; + entry = entry->next) { + struct bt_bap_ready *ready = entry->data; + + ready->func(bap, ready->data); + } + + bt_bap_unref(bap); +} + +bool bap_print_cc(void *data, size_t len, util_debug_func_t func, + void *user_data) +{ + return bap_print_ltv("CC", data, len, func, user_data); +} + +static void bap_parse_pacs(struct bt_bap *bap, uint8_t type, + struct queue *queue, + const uint8_t *value, + uint16_t len) +{ + struct bt_pacs_read_rsp *rsp; + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = len, + }; + int i; + + rsp = iov_pull_mem(&iov, sizeof(*rsp)); + if (!rsp) { + DBG(bap, "Unable to parse PAC"); + return; + } + + DBG(bap, "PAC(s) %u", rsp->num_pac); + + for (i = 0; i < rsp->num_pac; i++) { + struct bt_bap_pac *pac; + struct bt_pac *p; + struct bt_ltv *cc; + struct bt_pac_metadata *meta; + struct iovec data, metadata; + + p = iov_pull_mem(&iov, sizeof(*p)); + if (!p) { + DBG(bap, "Unable to parse PAC"); + return; + } + + pac = NULL; + + if (!bap_print_cc(iov.iov_base, p->cc_len, bap->debug_func, + bap->debug_data)) + return; + + cc = iov_pull_mem(&iov, p->cc_len); + if (!cc) { + DBG(bap, "Unable to parse PAC codec capabilities"); + return; + } + + meta = iov_pull_mem(&iov, sizeof(*meta)); + if (!meta) { + DBG(bap, "Unable to parse PAC metadata"); + return; + } + + data.iov_len = p->cc_len; + data.iov_base = cc; + + metadata.iov_len = meta->len; + metadata.iov_base = meta->data; + + iov_pull_mem(&iov, meta->len); + + pac = bap_pac_new(bap->rdb, NULL, type, &p->codec, NULL, &data, + &metadata); + if (!pac) + continue; + + DBG(bap, "PAC #%u: type %u codec 0x%02x cc_len %u meta_len %u", + i, type, p->codec.id, p->cc_len, meta->len); + + queue_push_tail(queue, pac); + } +} + +static void read_source_pac(struct bt_bap *bap, bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + if (!success) { + DBG(bap, "Unable to read Source PAC: error 0x%02x", att_ecode); + return; + } + + bap_parse_pacs(bap, BT_BAP_SOURCE, bap->rdb->sources, value, length); +} + +static void read_sink_pac(struct bt_bap *bap, bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + if (!success) { + DBG(bap, "Unable to read Sink PAC: error 0x%02x", att_ecode); + return; + } + + bap_parse_pacs(bap, BT_BAP_SINK, bap->rdb->sinks, value, length); +} + +static void read_source_pac_loc(struct bt_bap *bap, bool success, + uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_pacs *pacs = bap_get_pacs(bap); + + if (!success) { + DBG(bap, "Unable to read Source PAC Location: error 0x%02x", + att_ecode); + return; + } + + gatt_db_attribute_write(pacs->source_loc, 0, value, length, 0, NULL, + NULL, NULL); +} + +static void read_sink_pac_loc(struct bt_bap *bap, bool success, + uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_pacs *pacs = bap_get_pacs(bap); + + if (!success) { + DBG(bap, "Unable to read Sink PAC Location: error 0x%02x", + att_ecode); + return; + } + + gatt_db_attribute_write(pacs->sink_loc, 0, value, length, 0, NULL, + NULL, NULL); +} + +static void read_pac_context(struct bt_bap *bap, bool success, + uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_pacs *pacs = bap_get_pacs(bap); + + if (!success) { + DBG(bap, "Unable to read PAC Context: error 0x%02x", att_ecode); + return; + } + + gatt_db_attribute_write(pacs->context, 0, value, length, 0, NULL, + NULL, NULL); +} + +static void read_pac_supported_context(struct bt_bap *bap, bool success, + uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_pacs *pacs = bap_get_pacs(bap); + + if (!success) { + DBG(bap, "Unable to read PAC Supproted Context: error 0x%02x", + att_ecode); + return; + } + + gatt_db_attribute_write(pacs->supported_context, 0, value, length, 0, + NULL, NULL, NULL); +} + +static void bap_pending_destroy(void *data) +{ + struct bt_bap_pending *pending = data; + struct bt_bap *bap = pending->bap; + + if (queue_remove_if(bap->pending, NULL, pending)) + free(pending); + + bap_notify_ready(bap); +} + +static void bap_pending_complete(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_bap_pending *pending = user_data; + + if (pending->func) + pending->func(pending->bap, success, att_ecode, value, length, + pending->user_data); +} + +static void bap_read_value(struct bt_bap *bap, uint16_t value_handle, + bap_func_t func, void *user_data) +{ + struct bt_bap_pending *pending; + + pending = new0(struct bt_bap_pending, 1); + pending->bap = bap; + pending->func = func; + pending->user_data = user_data; + + pending->id = bt_gatt_client_read_value(bap->client, value_handle, + bap_pending_complete, pending, + bap_pending_destroy); + if (!pending->id) { + DBG(bap, "Unable to send Read request"); + free(pending); + return; + } + + queue_push_tail(bap->pending, pending); +} + +static void foreach_pacs_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_bap *bap = user_data; + uint16_t value_handle; + bt_uuid_t uuid, uuid_sink, uuid_source, uuid_sink_loc, uuid_source_loc; + bt_uuid_t uuid_context, uuid_supported_context; + struct bt_pacs *pacs; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, + NULL, NULL, &uuid)) + return; + + bt_uuid16_create(&uuid_sink, PAC_SINK_CHRC_UUID); + bt_uuid16_create(&uuid_source, PAC_SOURCE_CHRC_UUID); + bt_uuid16_create(&uuid_sink_loc, PAC_SINK_LOC_CHRC_UUID); + bt_uuid16_create(&uuid_source_loc, PAC_SOURCE_LOC_CHRC_UUID); + bt_uuid16_create(&uuid_context, PAC_CONTEXT); + bt_uuid16_create(&uuid_supported_context, PAC_SUPPORTED_CONTEXT); + + if (!bt_uuid_cmp(&uuid, &uuid_sink)) { + DBG(bap, "Sink PAC found: handle 0x%04x", value_handle); + + pacs = bap_get_pacs(bap); + if (!pacs || pacs->sink) + return; + + pacs->sink = attr; + bap_read_value(bap, value_handle, read_sink_pac, bap); + } + + if (!bt_uuid_cmp(&uuid, &uuid_source)) { + DBG(bap, "Source PAC found: handle 0x%04x", value_handle); + + pacs = bap_get_pacs(bap); + if (!pacs || pacs->source) + return; + + pacs->source = attr; + bap_read_value(bap, value_handle, read_source_pac, NULL); + } + + if (!bt_uuid_cmp(&uuid, &uuid_sink_loc)) { + DBG(bap, "Sink PAC Location found: handle 0x%04x", + value_handle); + + pacs = bap_get_pacs(bap); + if (!pacs || pacs->sink_loc) + return; + + pacs->sink_loc = attr; + bap_read_value(bap, value_handle, read_sink_pac_loc, NULL); + } + + if (!bt_uuid_cmp(&uuid, &uuid_source_loc)) { + DBG(bap, "Source PAC Location found: handle 0x%04x", + value_handle); + + pacs = bap_get_pacs(bap); + if (!pacs || pacs->source_loc) + return; + + pacs->source_loc = attr; + bap_read_value(bap, value_handle, read_source_pac_loc, NULL); + } + + if (!bt_uuid_cmp(&uuid, &uuid_context)) { + DBG(bap, "PAC Context found: handle 0x%04x", value_handle); + + pacs = bap_get_pacs(bap); + if (!pacs || pacs->context) + return; + + pacs->context = attr; + bap_read_value(bap, value_handle, read_pac_context, NULL); + } + + if (!bt_uuid_cmp(&uuid, &uuid_supported_context)) { + DBG(bap, "PAC Supported Context found: handle 0x%04x", + value_handle); + + pacs = bap_get_pacs(bap); + if (!pacs || pacs->supported_context) + return; + + pacs->supported_context = attr; + bap_read_value(bap, value_handle, read_pac_supported_context, + NULL); + } +} + +static void foreach_pacs_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_bap *bap = user_data; + struct bt_pacs *pacs = bap_get_pacs(bap); + + pacs->service = attr; + + gatt_db_service_foreach_char(attr, foreach_pacs_char, bap); +} + +struct match_pac { + struct bt_bap_codec codec; + struct bt_bap_pac *lpac; + struct bt_bap_pac *rpac; + struct bt_bap_endpoint *ep; +}; + +static bool match_stream_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + void *user_data) +{ + struct match_pac *match = user_data; + + if (!bap_codec_equal(&match->codec, &lpac->codec)) + return true; + + match->lpac = lpac; + match->rpac = rpac; + + return false; +} + +static void ep_status_config(struct bt_bap *bap, struct bt_bap_endpoint *ep, + struct iovec *iov) +{ + struct bt_ascs_ase_status_config *cfg; + struct bt_ltv *cc; + uint32_t pd_min, pd_max, ppd_min, ppd_max; + int i; + + cfg = iov_pull_mem(iov, sizeof(*cfg)); + if (!cfg) { + DBG(bap, "Unable to parse Config Status"); + return; + } + + pd_min = get_le24(cfg->pd_min); + pd_max = get_le24(cfg->pd_max); + ppd_min = get_le24(cfg->ppd_min); + ppd_max = get_le24(cfg->ppd_max); + + DBG(bap, "codec 0x%02x framing 0x%02x phy 0x%02x rtn %u " + "latency %u pd %u - %u ppd %u - %u", cfg->codec.id, + cfg->framing, cfg->phy, cfg->rtn, + le16_to_cpu(cfg->latency), + pd_min, pd_max, ppd_min, ppd_max); + + if (iov->iov_len < cfg->cc_len) { + DBG(bap, "Unable to parse Config Status: len %zu < %u cc_len", + iov->iov_len, cfg->cc_len); + return; + } + + for (i = 0; iov->iov_len >= sizeof(*cc); i++) { + cc = iov_pull_mem(iov, sizeof(*cc)); + if (!cc) + break; + + DBG(bap, "Codec Config #%u: type 0x%02x len %u", i, + cc->type, cc->len); + + iov_pull_mem(iov, cc->len - 1); + } + + /* Any previously applied codec configuration may be cached by the + * server. + */ + if (!ep->stream) { + struct match_pac match; + + match.lpac = NULL; + match.rpac = NULL; + match.codec.id = cfg->codec.id; + match.codec.cid = le16_to_cpu(cfg->codec.cid); + match.codec.vid = le16_to_cpu(cfg->codec.vid); + + bt_bap_foreach_pac(bap, ep->dir, match_stream_pac, &match); + if (!match.lpac || !match.rpac) + return; + + bap_stream_new(bap, ep, match.lpac, match.rpac, NULL, true); + } + + if (!ep->stream) + return; + + /* Set preferred settings */ + if (ep->stream->rpac) { + ep->stream->rpac->qos.framing = cfg->framing; + ep->stream->rpac->qos.phy = cfg->phy; + ep->stream->rpac->qos.rtn = cfg->rtn; + ep->stream->rpac->qos.latency = le16_to_cpu(cfg->latency); + ep->stream->rpac->qos.pd_min = pd_min; + ep->stream->rpac->qos.pd_max = pd_max; + ep->stream->rpac->qos.ppd_min = ppd_min; + ep->stream->rpac->qos.ppd_max = ppd_max; + } + + if (!ep->stream->cc) + ep->stream->cc = new0(struct iovec, 1); + + iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len); +} + +static void bap_stream_config_cfm_cb(struct bt_bap_stream *stream, int err) +{ + struct bt_bap *bap = stream->bap; + + if (err) { + DBG(bap, "Config Confirmation failed: %d", err); + bt_bap_stream_release(stream, NULL, NULL); + return; + } +} + +static void bap_stream_config_cfm(struct bt_bap_stream *stream) +{ + int err; + + if (!stream->lpac->ops || !stream->lpac->ops->config) + return; + + err = stream->lpac->ops->config(stream, stream->cc, &stream->qos, + bap_stream_config_cfm_cb, + stream->lpac->user_data); + if (err < 0) { + DBG(stream->bap, "Unable to send Config Confirmation: %d", + err); + bt_bap_stream_release(stream, NULL, NULL); + } +} + +static void ep_status_qos(struct bt_bap *bap, struct bt_bap_endpoint *ep, + struct iovec *iov) +{ + struct bt_ascs_ase_status_qos *qos; + uint32_t interval; + uint32_t pd; + uint16_t sdu; + uint16_t latency; + + qos = iov_pull_mem(iov, sizeof(*qos)); + if (!qos) { + DBG(bap, "Unable to parse QoS Status"); + return; + } + + interval = get_le24(qos->interval); + pd = get_le24(qos->pd); + sdu = le16_to_cpu(qos->sdu); + latency = le16_to_cpu(qos->latency); + + DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x " + "phy 0x%02x rtn %u latency %u pd %u", qos->cig_id, + qos->cis_id, interval, qos->framing, qos->phy, + qos->rtn, latency, pd); + + if (!ep->stream) + return; + + ep->stream->qos.interval = interval; + ep->stream->qos.framing = qos->framing; + ep->stream->qos.phy = qos->phy; + ep->stream->qos.sdu = sdu; + ep->stream->qos.rtn = qos->rtn; + ep->stream->qos.latency = latency; + ep->stream->qos.delay = pd; + + if (ep->old_state == BT_ASCS_ASE_STATE_CONFIG) + bap_stream_config_cfm(ep->stream); +} + +static void ep_status_metadata(struct bt_bap *bap, struct bt_bap_endpoint *ep, + struct iovec *iov) +{ + struct bt_ascs_ase_status_metadata *meta; + + meta = iov_pull_mem(iov, sizeof(*meta)); + if (!meta) { + DBG(bap, "Unable to parse Metadata Status"); + return; + } + + DBG(bap, "CIS 0x%02x CIG 0x%02x metadata len %u", + meta->cis_id, meta->cig_id, meta->len); +} + +static void bap_ep_set_status(struct bt_bap *bap, struct bt_bap_endpoint *ep, + const uint8_t *value, uint16_t length) +{ + struct bt_ascs_ase_status *rsp; + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = length, + }; + + rsp = iov_pull_mem(&iov, sizeof(*rsp)); + if (!rsp) + return; + + ep->id = rsp->id; + ep->old_state = ep->state; + ep->state = rsp->state; + + DBG(bap, "ASE status: ep %p id 0x%02x handle 0x%04x state %s " + "len %zu", ep, ep->id, + gatt_db_attribute_get_handle(ep->attr), + bt_bap_stream_statestr(ep->state), iov.iov_len); + + switch (ep->state) { + case BT_ASCS_ASE_STATE_IDLE: + break; + case BT_ASCS_ASE_STATE_CONFIG: + ep_status_config(bap, ep, &iov); + break; + case BT_ASCS_ASE_STATE_QOS: + ep_status_qos(bap, ep, &iov); + break; + case BT_ASCS_ASE_STATE_ENABLING: + case BT_ASCS_ASE_STATE_STREAMING: + case BT_ASCS_ASE_STATE_DISABLING: + ep_status_metadata(bap, ep, &iov); + break; + case BT_ASCS_ASE_STATE_RELEASING: + break; + } + + /* Only notifify if there is a stream */ + if (!ep->stream) + return; + + bap_stream_state_changed(ep->stream); +} + +static void read_ase_status(struct bt_bap *bap, bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_bap_endpoint *ep = user_data; + + if (!success) + return; + + bap_ep_set_status(bap, ep, value, length); +} + +static void bap_register(uint16_t att_ecode, void *user_data) +{ + struct bt_bap_notify *notify = user_data; + + if (att_ecode) + DBG(notify->bap, "ASE register failed: 0x%04x", att_ecode); +} + +static void bap_endpoint_notify(struct bt_bap *bap, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_bap_endpoint *ep = user_data; + + bap_ep_set_status(bap, ep, value, length); +} + +static void bap_notify(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_bap_notify *notify = user_data; + + if (notify->func) + notify->func(notify->bap, value_handle, value, length, + notify->user_data); +} + +static void bap_notify_destroy(void *data) +{ + struct bt_bap_notify *notify = data; + struct bt_bap *bap = notify->bap; + + if (queue_remove_if(bap->notify, NULL, notify)) + free(notify); +} + +static unsigned int bap_register_notify(struct bt_bap *bap, + uint16_t value_handle, + bap_notify_t func, + void *user_data) +{ + struct bt_bap_notify *notify; + + notify = new0(struct bt_bap_notify, 1); + notify->bap = bap; + notify->func = func; + notify->user_data = user_data; + + notify->id = bt_gatt_client_register_notify(bap->client, + value_handle, bap_register, + bap_notify, notify, + bap_notify_destroy); + if (!notify->id) { + DBG(bap, "Unable to register for notifications"); + free(notify); + return 0; + } + + queue_push_tail(bap->notify, notify); + + return notify->id; +} + +static void bap_endpoint_attach(struct bt_bap *bap, struct bt_bap_endpoint *ep) +{ + uint16_t value_handle; + + if (!gatt_db_attribute_get_char_data(ep->attr, NULL, &value_handle, + NULL, NULL, NULL)) + return; + + DBG(bap, "ASE handle 0x%04x", value_handle); + + bap_read_value(bap, value_handle, read_ase_status, ep); + + ep->state_id = bap_register_notify(bap, value_handle, + bap_endpoint_notify, ep); +} + +static void append_group(void *data, void *user_data) +{ + struct bt_bap_req *req = data; + struct iovec *iov = user_data; + size_t i; + + for (i = 0; i < req->len; i++) + iov_add_mem(iov, req->iov[i].iov_len, req->iov[i].iov_base); +} + +static bool bap_send(struct bt_bap *bap, struct bt_bap_req *req) +{ + struct bt_ascs *ascs = bap_get_ascs(bap); + int ret; + uint16_t handle; + uint8_t buf[64]; + struct bt_ascs_ase_hdr hdr; + struct iovec iov = { + .iov_base = buf, + .iov_len = 0, + }; + size_t i; + + if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, &handle, + NULL, NULL, NULL)) + return false; + + hdr.op = req->op; + hdr.num = 1 + queue_length(req->group); + + iov_add_mem(&iov, sizeof(hdr), &hdr); + + for (i = 0; i < req->len; i++) + iov_add_mem(&iov, req->iov[i].iov_len, req->iov[i].iov_base); + + /* Append the request group with the same opcode */ + queue_foreach(req->group, append_group, &iov); + + ret = bt_gatt_client_write_without_response(bap->client, handle, + false, iov.iov_base, + iov.iov_len); + if (!ret) + return false; + + bap->req = req; + + return false; +} + +static bool bap_process_queue(void *data) +{ + struct bt_bap *bap = data; + struct bt_bap_req *req; + + if (bap->process_id) { + timeout_remove(bap->process_id); + bap->process_id = 0; + } + + while ((req = queue_pop_head(bap->reqs))) { + if (!bap_send(bap, req)) + break; + } + + return false; +} + +static bool match_req(const void *data, const void *match_data) +{ + const struct bt_bap_req *pend = data; + const struct bt_bap_req *req = match_data; + + return pend->op == req->op; +} + +static bool bap_queue_req(struct bt_bap *bap, struct bt_bap_req *req) +{ + struct bt_bap_req *pend; + struct queue *queue; + + pend = queue_find(bap->reqs, match_req, req); + if (pend) { + if (!pend->group) + pend->group = queue_new(); + /* Group requests with the same opcode */ + queue = pend->group; + } else { + queue = bap->reqs; + } + + DBG(bap, "req %p (op 0x%2.2x) queue %p", req, req->op, queue); + + if (!queue_push_tail(queue, req)) { + DBG(bap, "Unable to queue request"); + return false; + } + + /* Only attempot to process queue if there is no outstanding request + * and it has not been scheduled. + */ + if (!bap->req && !bap->process_id) + bap->process_id = timeout_add(BAP_PROCESS_TIMEOUT, + bap_process_queue, bap, NULL); + + return true; +} + +static void bap_req_complete(struct bt_bap_req *req, + const struct bt_ascs_ase_rsp *rsp) +{ + struct queue *group; + + if (!req->func) + goto done; + + if (rsp) + req->func(req->stream, rsp->code, rsp->reason, req->user_data); + else + req->func(req->stream, BT_ASCS_RSP_UNSPECIFIED, 0x00, + req->user_data); + +done: + /* Detach from request so it can be freed separately */ + group = req->group; + req->group = NULL; + + queue_foreach(group, (queue_foreach_func_t)bap_req_complete, + (void *)rsp); + + queue_destroy(group, NULL); + + bap_req_free(req); +} + +static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data) +{ + const struct bt_ascs_cp_rsp *rsp = (void *)value; + const struct bt_ascs_ase_rsp *ase_rsp = NULL; + struct bt_bap_req *req; + int i; + + if (!bap->req) + return; + + req = bap->req; + bap->req = NULL; + + if (length < sizeof(*rsp)) { + DBG(bap, "Invalid ASE CP notification: length %u < %zu", + length, sizeof(*rsp)); + goto done; + } + + if (rsp->op != req->op) { + DBG(bap, "Invalid ASE CP notification: op 0x%02x != 0x%02x", + rsp->op, req->op); + goto done; + } + + length -= sizeof(*rsp); + + if (rsp->num_ase == 0xff) { + ase_rsp = rsp->rsp; + goto done; + } + + for (i = 0; i < rsp->num_ase; i++) { + if (length < sizeof(*ase_rsp)) { + DBG(bap, "Invalid ASE CP notification: length %u < %zu", + length, sizeof(*ase_rsp)); + goto done; + } + + ase_rsp = &rsp->rsp[i]; + } + +done: + bap_req_complete(req, ase_rsp); + bap_process_queue(bap); +} + +static void bap_cp_attach(struct bt_bap *bap) +{ + uint16_t value_handle; + struct bt_ascs *ascs = bap_get_ascs(bap); + + if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, + &value_handle, + NULL, NULL, NULL)) + return; + + DBG(bap, "ASE CP handle 0x%04x", value_handle); + + bap->cp_id = bap_register_notify(bap, value_handle, bap_cp_notify, + NULL); +} + +static void foreach_ascs_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_bap *bap = user_data; + uint16_t value_handle; + bt_uuid_t uuid, uuid_sink, uuid_source, uuid_cp; + struct bt_ascs *ascs; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, + NULL, NULL, &uuid)) + return; + + bt_uuid16_create(&uuid_sink, ASE_SINK_UUID); + bt_uuid16_create(&uuid_source, ASE_SOURCE_UUID); + bt_uuid16_create(&uuid_cp, ASE_CP_UUID); + + if (!bt_uuid_cmp(&uuid, &uuid_sink) || + !bt_uuid_cmp(&uuid, &uuid_source)) { + struct bt_bap_endpoint *ep; + + ep = bap_get_endpoint(bap->rdb, attr); + if (!ep) + return; + + if (!bt_uuid_cmp(&uuid, &uuid_sink)) + DBG(bap, "ASE Sink found: handle 0x%04x", value_handle); + else + DBG(bap, "ASE Source found: handle 0x%04x", + value_handle); + + bap_endpoint_attach(bap, ep); + + return; + } + + if (!bt_uuid_cmp(&uuid, &uuid_cp)) { + ascs = bap_get_ascs(bap); + if (!ascs || ascs->ase_cp) + return; + + ascs->ase_cp = attr; + + DBG(bap, "ASE Control Point found: handle 0x%04x", + value_handle); + + bap_cp_attach(bap); + } +} + +static void foreach_ascs_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_bap *bap = user_data; + struct bt_ascs *ascs = bap_get_ascs(bap); + + ascs->service = attr; + + gatt_db_service_set_claimed(attr, true); + + gatt_db_service_foreach_char(attr, foreach_ascs_char, bap); +} + +static void bap_endpoint_foreach(void *data, void *user_data) +{ + struct bt_bap_endpoint *ep = data; + struct bt_bap *bap = user_data; + + bap_endpoint_attach(bap, ep); +} + +bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client) +{ + bt_uuid_t uuid; + + if (queue_find(sessions, NULL, bap)) { + /* If instance already been set but there is no client proceed + * to clone it otherwise considered it already attached. + */ + if (client && !bap->client) + goto clone; + return true; + } + + if (!sessions) + sessions = queue_new(); + + queue_push_tail(sessions, bap); + + queue_foreach(bap_cbs, bap_attached, bap); + + if (!client) + return true; + + if (bap->client) + return false; + +clone: + bap->client = bt_gatt_client_clone(client); + if (!bap->client) + return false; + + if (bap->rdb->pacs) { + uint16_t value_handle; + struct bt_pacs *pacs = bap->rdb->pacs; + + /* Resume reading sinks if supported */ + if (pacs->sink && queue_isempty(bap->rdb->sinks)) { + if (gatt_db_attribute_get_char_data(pacs->sink, + NULL, &value_handle, + NULL, NULL, NULL)) { + bap_read_value(bap, value_handle, + read_sink_pac, bap); + } + } + + /* Resume reading sources if supported */ + if (pacs->source && queue_isempty(bap->rdb->sources)) { + if (gatt_db_attribute_get_char_data(pacs->source, + NULL, &value_handle, + NULL, NULL, NULL)) { + bap_read_value(bap, value_handle, + read_source_pac, bap); + } + } + + queue_foreach(bap->rdb->endpoints, bap_endpoint_foreach, bap); + + bap_cp_attach(bap); + + bap_notify_ready(bap); + + return true; + } + + bt_uuid16_create(&uuid, PACS_UUID); + gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_pacs_service, bap); + + bt_uuid16_create(&uuid, ASCS_UUID); + gatt_db_foreach_service(bap->rdb->db, &uuid, foreach_ascs_service, bap); + + return true; +} + +static void stream_foreach_detach(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + + stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); +} + +void bt_bap_detach(struct bt_bap *bap) +{ + DBG(bap, "%p", bap); + + if (!queue_remove(sessions, bap)) + return; + + bt_gatt_client_unref(bap->client); + bap->client = NULL; + + queue_foreach(bap->streams, stream_foreach_detach, bap); + queue_foreach(bap_cbs, bap_detached, bap); +} + +bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t func, + void *user_data, bt_bap_destroy_func_t destroy) +{ + if (!bap) + return false; + + if (bap->debug_destroy) + bap->debug_destroy(bap->debug_data); + + bap->debug_func = func; + bap->debug_destroy = destroy; + bap->debug_data = user_data; + + return true; +} + +unsigned int bt_bap_ready_register(struct bt_bap *bap, + bt_bap_ready_func_t func, void *user_data, + bt_bap_destroy_func_t destroy) +{ + struct bt_bap_ready *ready; + static unsigned int id; + + if (!bap) + return 0; + + ready = new0(struct bt_bap_ready, 1); + ready->id = ++id ? id : ++id; + ready->func = func; + ready->destroy = destroy; + ready->data = user_data; + + queue_push_tail(bap->ready_cbs, ready); + + return ready->id; +} + +static bool match_ready_id(const void *data, const void *match_data) +{ + const struct bt_bap_ready *ready = data; + unsigned int id = PTR_TO_UINT(match_data); + + return (ready->id == id); +} + +bool bt_bap_ready_unregister(struct bt_bap *bap, unsigned int id) +{ + struct bt_bap_ready *ready; + + ready = queue_remove_if(bap->ready_cbs, match_ready_id, + UINT_TO_PTR(id)); + if (!ready) + return false; + + bap_ready_free(ready); + + return true; +} + +unsigned int bt_bap_state_register(struct bt_bap *bap, + bt_bap_state_func_t func, + bt_bap_connecting_func_t connecting, + void *user_data, bt_bap_destroy_func_t destroy) +{ + struct bt_bap_state *state; + static unsigned int id; + + if (!bap) + return 0; + + state = new0(struct bt_bap_state, 1); + state->id = ++id ? id : ++id; + state->func = func; + state->connecting = connecting; + state->destroy = destroy; + state->data = user_data; + + queue_push_tail(bap->state_cbs, state); + + return state->id; +} + +static bool match_state_id(const void *data, const void *match_data) +{ + const struct bt_bap_state *state = data; + unsigned int id = PTR_TO_UINT(match_data); + + return (state->id == id); +} + +bool bt_bap_state_unregister(struct bt_bap *bap, unsigned int id) +{ + struct bt_bap_state *state; + + if (!bap) + return false; + + state = queue_remove_if(bap->state_cbs, match_state_id, + UINT_TO_PTR(id)); + if (!state) + return false; + + bap_state_free(state); + + return false; +} + +const char *bt_bap_stream_statestr(uint8_t state) +{ + switch (state) { + case BT_BAP_STREAM_STATE_IDLE: + return "idle"; + case BT_BAP_STREAM_STATE_CONFIG: + return "config"; + case BT_BAP_STREAM_STATE_QOS: + return "qos"; + case BT_BAP_STREAM_STATE_ENABLING: + return "enabling"; + case BT_BAP_STREAM_STATE_STREAMING: + return "streaming"; + case BT_BAP_STREAM_STATE_DISABLING: + return "disabling"; + case BT_BAP_STREAM_STATE_RELEASING: + return "releasing"; + } + + return "unknown"; +} + +static void bap_foreach_pac(struct queue *l, struct queue *r, + bt_bap_pac_foreach_t func, void *user_data) +{ + const struct queue_entry *el; + + for (el = queue_get_entries(l); el; el = el->next) { + struct bt_bap_pac *lpac = el->data; + const struct queue_entry *er; + + for (er = queue_get_entries(r); er; er = er->next) { + struct bt_bap_pac *rpac = er->data; + + if (!bap_codec_equal(&lpac->codec, &rpac->codec)) + continue; + + if (!func(lpac, rpac, user_data)) + return; + } + } +} + +void bt_bap_foreach_pac(struct bt_bap *bap, uint8_t type, + bt_bap_pac_foreach_t func, void *user_data) +{ + if (!bap || !func || !bap->rdb || queue_isempty(bap_db)) + return; + + switch (type) { + case BT_BAP_SINK: + return bap_foreach_pac(bap->ldb->sources, bap->rdb->sinks, + func, user_data); + case BT_BAP_SOURCE: + return bap_foreach_pac(bap->ldb->sinks, bap->rdb->sources, + func, user_data); + } +} + +int bt_bap_pac_get_vendor_codec(struct bt_bap_pac *pac, uint8_t *id, + uint16_t *cid, uint16_t *vid, + struct iovec **data, struct iovec **metadata) +{ + if (!pac) + return -EINVAL; + + if (id) + *id = pac->codec.id; + + if (cid) + *cid = pac->codec.cid; + + if (vid) + *vid = pac->codec.cid; + + if (data) + *data = pac->data; + + if (metadata) + *metadata = pac->metadata; + + return 0; +} + +int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id, + struct iovec **data, struct iovec **metadata) +{ + return bt_bap_pac_get_vendor_codec(pac, id, NULL, NULL, data, metadata); +} + +static bool find_ep_unused(const void *data, const void *user_data) +{ + const struct bt_bap_endpoint *ep = data; + const struct match_pac *match = user_data; + + if (ep->stream) + return false; + + return ep->dir == match->rpac->type; +} + +static bool find_ep_pacs(const void *data, const void *user_data) +{ + const struct bt_bap_endpoint *ep = data; + const struct match_pac *match = user_data; + + if (!ep->stream) + return false; + + if (ep->stream->lpac != match->lpac) + return false; + + return ep->stream->rpac == match->rpac; +} + +static struct bt_bap_req *bap_req_new(struct bt_bap_stream *stream, + uint8_t op, struct iovec *iov, + size_t len, + bt_bap_stream_func_t func, + void *user_data) +{ + struct bt_bap_req *req; + static unsigned int id; + + req = new0(struct bt_bap_req, 1); + req->id = ++id; + req->stream = stream; + req->op = op; + req->iov = iov_dup(iov, len); + req->len = len; + req->func = func; + req->user_data = user_data; + + return req; +} + +static bool bap_stream_valid(struct bt_bap_stream *stream) +{ + if (!stream || !stream->bap) + return false; + + return queue_find(stream->bap->streams, NULL, stream); +} + +unsigned int bt_bap_stream_config(struct bt_bap_stream *stream, + struct bt_bap_qos *qos, + struct iovec *data, + bt_bap_stream_func_t func, + void *user_data) +{ + struct iovec iov[2]; + struct bt_ascs_config config; + uint8_t iovlen = 1; + struct bt_bap_req *req; + + if (!bap_stream_valid(stream)) + return 0; + + if (!stream->client) { + stream_config(stream, data, NULL); + return 0; + } + + memset(&config, 0, sizeof(config)); + + config.ase = stream->ep->id; + config.latency = qos->target_latency; + config.phy = qos->phy; + config.codec = stream->rpac->codec; + + iov[0].iov_base = &config; + iov[0].iov_len = sizeof(config); + + if (data) { + if (!bap_print_cc(data->iov_base, data->iov_len, + stream->bap->debug_func, + stream->bap->debug_data)) + return 0; + + config.cc_len = data->iov_len; + iov[1] = *data; + iovlen++; + } + + req = bap_req_new(stream, BT_ASCS_CONFIG, iov, iovlen, func, user_data); + + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } + + stream->qos = *qos; + + return req->id; +} + +static bool match_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + void *user_data) +{ + struct match_pac *match = user_data; + + if (match->lpac && match->lpac != lpac) + return true; + + if (match->rpac && match->rpac != rpac) + return true; + + match->lpac = lpac; + match->rpac = rpac; + + return false; +} + +int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + bt_bap_pac_select_t func, void *user_data) +{ + if (!lpac || !rpac || !func) + return -EINVAL; + + if (!lpac->ops || !lpac->ops->select) + return -EOPNOTSUPP; + + lpac->ops->select(lpac, &rpac->qos, rpac->data, rpac->metadata, + func, user_data, lpac->user_data); + + return 0; +} + +struct bt_bap_stream *bt_bap_config(struct bt_bap *bap, + struct bt_bap_pac *lpac, + struct bt_bap_pac *rpac, + struct bt_bap_qos *pqos, + struct iovec *data, + bt_bap_stream_func_t func, + void *user_data) +{ + struct bt_bap_stream *stream; + struct bt_bap_endpoint *ep; + struct match_pac match; + int id; + + if (!bap || !bap->rdb || queue_isempty(bap->rdb->endpoints)) + return NULL; + + if (lpac && rpac) { + if (!bap_codec_equal(&lpac->codec, &rpac->codec)) + return NULL; + } else { + uint8_t type; + + match.lpac = lpac; + match.rpac = rpac; + memset(&match.codec, 0, sizeof(match.codec)); + + if (rpac) + type = rpac->type; + else if (lpac) { + switch(lpac->type) { + case BT_BAP_SINK: + type = BT_BAP_SOURCE; + break; + case BT_BAP_SOURCE: + type = BT_BAP_SINK; + break; + default: + return NULL; + } + } else + return NULL; + + bt_bap_foreach_pac(bap, type, match_pac, &match); + if (!match.lpac || !match.rpac) + return NULL; + + lpac = match.lpac; + rpac = match.rpac; + } + + match.lpac = lpac; + match.rpac = rpac; + + /* Check for existing stream */ + ep = queue_find(bap->rdb->endpoints, find_ep_pacs, &match); + if (!ep) { + /* Check for unused ASE */ + ep = queue_find(bap->rdb->endpoints, find_ep_unused, &match); + if (!ep) { + DBG(bap, "Unable to find unused ASE"); + return NULL; + } + } + + stream = ep->stream; + if (!stream) + stream = bap_stream_new(bap, ep, lpac, rpac, data, true); + + id = bt_bap_stream_config(stream, pqos, data, func, user_data); + if (!id) { + DBG(bap, "Unable to config stream"); + queue_remove(bap->streams, stream); + ep->stream = NULL; + free(stream); + return NULL; + } + + return stream; +} + +struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream) +{ + if (!stream) + return NULL; + + return stream->bap; +} + +uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream) +{ + if (!stream) + return BT_BAP_STREAM_STATE_IDLE; + + return stream->ep->state; +} + +bool bt_bap_stream_set_user_data(struct bt_bap_stream *stream, void *user_data) +{ + if (!stream) + return false; + + stream->user_data = user_data; + + return true; +} + +void *bt_bap_stream_get_user_data(struct bt_bap_stream *stream) +{ + if (!stream) + return NULL; + + return stream->user_data; +} + +unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream, + struct bt_bap_qos *data, + bt_bap_stream_func_t func, + void *user_data) +{ + struct iovec iov; + struct bt_ascs_qos qos; + struct bt_bap_req *req; + + if (!bap_stream_valid(stream)) + return 0; + + if (!stream->client) { + stream_qos(stream, data, NULL); + return 0; + } + + memset(&qos, 0, sizeof(qos)); + + /* TODO: Figure out how to pass these values around */ + qos.ase = stream->ep->id; + qos.cig = data->cig_id; + qos.cis = data->cis_id; + put_le24(data->interval, qos.interval); + qos.framing = data->framing; + qos.phy = data->phy; + qos.sdu = cpu_to_le16(data->sdu); + qos.rtn = data->rtn; + qos.latency = cpu_to_le16(data->latency); + put_le24(data->delay, qos.pd); + + iov.iov_base = &qos; + iov.iov_len = sizeof(qos); + + req = bap_req_new(stream, BT_ASCS_QOS, &iov, 1, func, user_data); + + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } + + stream->qos = *data; + + return req->id; +} + +static int bap_stream_metadata(struct bt_bap_stream *stream, uint8_t op, + struct iovec *data, + bt_bap_stream_func_t func, + void *user_data) +{ + struct iovec iov[2]; + struct bt_ascs_metadata meta; + struct bt_bap_req *req; + struct metadata { + uint8_t len; + uint8_t type; + uint8_t data[2]; + } ctx = LTV(0x02, 0x01, 0x00); /* Context = Unspecified */ + + memset(&meta, 0, sizeof(meta)); + + meta.ase = stream->ep->id; + + iov[0].iov_base = &meta; + iov[0].iov_len = sizeof(meta); + + if (data) + iov[1] = *data; + else { + iov[1].iov_base = &ctx; + iov[1].iov_len = sizeof(ctx); + } + + meta.len = iov[1].iov_len; + + req = bap_req_new(stream, op, iov, 2, func, user_data); + + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } + + return req->id; +} + +static void bap_stream_enable_link(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct iovec *metadata = user_data; + + bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, NULL, NULL); +} + +unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream, + bool enable_links, + struct iovec *metadata, + bt_bap_stream_func_t func, + void *user_data) +{ + int ret; + + if (!bap_stream_valid(stream)) + return 0; + + if (!stream->client) { + stream_enable(stream, metadata, NULL); + return 0; + } + + ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, func, + user_data); + if (!ret || !enable_links) + return ret; + + queue_foreach(stream->links, bap_stream_enable_link, metadata); + + return ret; +} + +unsigned int bt_bap_stream_start(struct bt_bap_stream *stream, + bt_bap_stream_func_t func, + void *user_data) +{ + struct iovec iov; + struct bt_ascs_start start; + struct bt_bap_req *req; + + if (!bap_stream_valid(stream)) + return 0; + + if (!stream->client) { + if (stream->ep->dir == BT_BAP_SINK) + stream_start(stream, NULL); + return 0; + } + + if (stream->ep->dir == BT_BAP_SINK) + return 0; + + memset(&start, 0, sizeof(start)); + + start.ase = stream->ep->id; + + iov.iov_base = &start; + iov.iov_len = sizeof(start); + + req = bap_req_new(stream, BT_ASCS_START, &iov, 1, func, user_data); + + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } + + return req->id; +} + +static void bap_stream_disable_link(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct bt_bap_req *req; + struct iovec iov; + struct bt_ascs_disable disable; + + memset(&disable, 0, sizeof(disable)); + + disable.ase = stream->ep->id; + + iov.iov_base = &disable; + iov.iov_len = sizeof(disable); + + req = bap_req_new(stream, BT_ASCS_DISABLE, &iov, 1, NULL, NULL); + + if (!bap_queue_req(stream->bap, req)) + bap_req_free(req); +} + +unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream, + bool disable_links, + bt_bap_stream_func_t func, + void *user_data) +{ + struct iovec iov; + struct bt_ascs_disable disable; + struct bt_bap_req *req; + + if (!bap_stream_valid(stream)) + return 0; + + if (!stream->client) { + stream_disable(stream, NULL); + return 0; + } + + memset(&disable, 0, sizeof(disable)); + + disable.ase = stream->ep->id; + + iov.iov_base = &disable; + iov.iov_len = sizeof(disable); + + req = bap_req_new(stream, BT_ASCS_DISABLE, &iov, 1, func, user_data); + + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } + + if (disable_links) + queue_foreach(stream->links, bap_stream_disable_link, NULL); + + return req->id; +} + +unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream, + bt_bap_stream_func_t func, + void *user_data) +{ + struct iovec iov; + struct bt_ascs_stop stop; + struct bt_bap_req *req; + + if (!bap_stream_valid(stream)) + return 0; + + if (!stream->client) { + if (stream->ep->dir == BT_BAP_SINK) + stream_stop(stream, NULL); + return 0; + } + + if (stream->ep->dir == BT_BAP_SINK) + return 0; + + memset(&stop, 0, sizeof(stop)); + + stop.ase = stream->ep->id; + + iov.iov_base = &stop; + iov.iov_len = sizeof(stop); + + req = bap_req_new(stream, BT_ASCS_STOP, &iov, 1, func, user_data); + + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } + + return req->id; +} + +unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream, + struct iovec *metadata, + bt_bap_stream_func_t func, + void *user_data) +{ + if (!stream) + return 0; + + if (!stream->client) { + stream_metadata(stream, metadata, NULL); + return 0; + } + + return bap_stream_metadata(stream, BT_ASCS_METADATA, metadata, func, + user_data); +} + +unsigned int bt_bap_stream_release(struct bt_bap_stream *stream, + bt_bap_stream_func_t func, + void *user_data) +{ + struct iovec iov; + struct bt_ascs_release rel; + struct bt_bap_req *req; + + if (!stream) + return 0; + + if (!stream->client) { + stream_release(stream, NULL); + return 0; + } + + memset(&req, 0, sizeof(req)); + + rel.ase = stream->ep->id; + + iov.iov_base = &rel; + iov.iov_len = sizeof(rel); + + req = bap_req_new(stream, BT_ASCS_RELEASE, &iov, 1, func, user_data); + + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } + + return req->id; +} + +uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream) +{ + if (!stream) + return 0x00; + + return stream->ep->dir; +} + +uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream) +{ + struct bt_bap_pac *pac; + + if (!stream) + return 0x00000000; + + pac = stream->rpac ? stream->rpac : stream->lpac; + + return pac->locations; +} + +struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream) +{ + if (!stream) + return NULL; + + return stream->cc; +} + +struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream) +{ + if (!stream) + return NULL; + + return &stream->qos; +} + +struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream) +{ + if (!stream) + return NULL; + + return stream->meta; +} + +struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream) +{ + struct bt_bap_stream_io *io; + + io = stream_get_io(stream); + if (!io || io->connecting) + return NULL; + + return io->io; +} + +static bool stream_io_disconnected(struct io *io, void *user_data) +{ + struct bt_bap_stream *stream = user_data; + + DBG(stream->bap, "stream %p io disconnected", stream); + + bt_bap_stream_set_io(stream, -1); + + return false; +} + +bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd) +{ + if (!stream || (fd >= 0 && stream->io && !stream->io->connecting)) + return false; + + bap_stream_set_io(stream, INT_TO_PTR(fd)); + + queue_foreach(stream->links, bap_stream_set_io, INT_TO_PTR(fd)); + + return true; +} + +static bool match_req_id(const void *data, const void *match_data) +{ + const struct bt_bap_req *req = data; + unsigned int id = PTR_TO_UINT(match_data); + + return (req->id == id); +} + +int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id) +{ + struct bt_bap_req *req; + + if (!stream) + return -EINVAL; + + if (stream->bap->req && stream->bap->req->id == id) { + req = stream->bap->req; + stream->bap->req = NULL; + bap_req_free(req); + return 0; + } + + req = queue_remove_if(stream->bap->reqs, match_req_id, + UINT_TO_PTR(id)); + if (!req) + return 0; + + bap_req_free(req); + + return 0; +} + +int bt_bap_stream_io_link(struct bt_bap_stream *stream, + struct bt_bap_stream *link) +{ + struct bt_bap *bap = stream->bap; + + if (!stream || !link || stream == link) + return -EINVAL; + + if (queue_find(stream->links, NULL, link)) + return -EALREADY; + + if (stream->client != link->client || + stream->qos.cig_id != link->qos.cig_id || + stream->qos.cis_id != link->qos.cis_id) + return -EINVAL; + + if (!stream->links) + stream->links = queue_new(); + + if (!link->links) + link->links = queue_new(); + + queue_push_tail(stream->links, link); + queue_push_tail(link->links, stream); + + /* Link IOs if already set on stream/link */ + if (stream->io && !link->io) + link->io = stream_io_ref(stream->io); + else if (link->io && !stream->io) + stream->io = stream_io_ref(link->io); + + DBG(bap, "stream %p link %p", stream, link); + + return 0; +} + +struct queue *bt_bap_stream_io_get_links(struct bt_bap_stream *stream) +{ + if (!stream) + return NULL; + + return stream->links; +} + +static void bap_stream_get_in_qos(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct bt_bap_qos **qos = user_data; + + if (!qos || *qos || stream->ep->dir != BT_BAP_SOURCE || + !stream->qos.sdu) + return; + + *qos = &stream->qos; +} + +static void bap_stream_get_out_qos(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct bt_bap_qos **qos = user_data; + + if (!qos || *qos || stream->ep->dir != BT_BAP_SINK || !stream->qos.sdu) + return; + + *qos = &stream->qos; +} + +bool bt_bap_stream_io_get_qos(struct bt_bap_stream *stream, + struct bt_bap_qos **in, + struct bt_bap_qos **out) +{ + if (!stream || (!in && !out)) + return false; + + switch (stream->ep->dir) { + case BT_BAP_SOURCE: + bap_stream_get_in_qos(stream, in); + queue_foreach(stream->links, bap_stream_get_out_qos, out); + break; + case BT_BAP_SINK: + bap_stream_get_out_qos(stream, out); + queue_foreach(stream->links, bap_stream_get_in_qos, in); + break; + default: + return false; + } + + DBG(stream->bap, "in %p out %p", in ? *in : NULL, out ? *out : NULL); + + return in && out; +} + +static void bap_stream_get_dir(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + uint8_t *dir = user_data; + + *dir |= stream->ep->dir; +} + +uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream) +{ + uint8_t dir; + + if (!stream) + return 0x00; + + dir = stream->ep->dir; + + queue_foreach(stream->links, bap_stream_get_dir, &dir); + + return dir; +} + +static void bap_stream_io_connecting(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + int fd = PTR_TO_INT(user_data); + const struct queue_entry *entry; + + if (fd >= 0) + bap_stream_io_attach(stream, fd, true); + else + bap_stream_io_detach(stream); + + for (entry = queue_get_entries(stream->bap->state_cbs); entry; + entry = entry->next) { + struct bt_bap_state *state = entry->data; + + if (state->connecting) + state->connecting(stream, stream->io ? true : false, + fd, state->data); + } +} + +int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd) +{ + if (!stream) + return -EINVAL; + + bap_stream_io_connecting(stream, INT_TO_PTR(fd)); + + queue_foreach(stream->links, bap_stream_io_connecting, INT_TO_PTR(fd)); + + return 0; +} + +bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd) +{ + struct bt_bap_stream_io *io; + + if (!stream) + return false; + + io = stream_get_io(stream); + if (!io) + return false; + + if (fd) + *fd = stream_io_get_fd(io); + + return io->connecting; +} diff --git a/src/shared/bap.h b/src/shared/bap.h new file mode 100644 index 000000000000..f9b368deabd5 --- /dev/null +++ b/src/shared/bap.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + */ + +#include +#include + +#include "src/shared/io.h" + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#define BT_BAP_SINK 0x01 +#define BT_BAP_SOURCE 0x02 + +#define BT_BAP_STREAM_STATE_IDLE 0x00 +#define BT_BAP_STREAM_STATE_CONFIG 0x01 +#define BT_BAP_STREAM_STATE_QOS 0x02 +#define BT_BAP_STREAM_STATE_ENABLING 0x03 +#define BT_BAP_STREAM_STATE_STREAMING 0x04 +#define BT_BAP_STREAM_STATE_DISABLING 0x05 +#define BT_BAP_STREAM_STATE_RELEASING 0x06 + +#define BT_BAP_CONFIG_LATENCY_LOW 0x01 +#define BT_BAP_CONFIG_LATENCY_BALACED 0x02 +#define BT_BAP_CONFIG_LATENCY_HIGH 0x03 + +#define BT_BAP_CONFIG_PHY_1M 0x01 +#define BT_BAP_CONFIG_PHY_2M 0x02 +#define BT_BAP_CONFIG_PHY_CODEC 0x03 + +struct bt_bap; +struct bt_bap_pac; +struct bt_bap_stream; + +struct bt_bap_codec { + uint8_t id; + uint16_t vid; + uint16_t cid; +} __packed; + +struct bt_ltv { + uint8_t len; + uint8_t type; + uint8_t value[0]; +} __packed; + +struct bt_bap_qos { + uint8_t cig_id; + uint8_t cis_id; + uint32_t interval; /* Frame interval */ + uint8_t framing; /* Frame framing */ + uint8_t phy; /* PHY */ + uint16_t sdu; /* Maximum SDU Size */ + uint8_t rtn; /* Retransmission Effort */ + uint16_t latency; /* Transport Latency */ + uint32_t delay; /* Presentation Delay */ + uint8_t target_latency; /* Target Latency */ +}; + +typedef void (*bt_bap_ready_func_t)(struct bt_bap *bap, void *user_data); +typedef void (*bt_bap_destroy_func_t)(void *user_data); +typedef void (*bt_bap_debug_func_t)(const char *str, void *user_data); +typedef void (*bt_bap_pac_func_t)(struct bt_bap_pac *pac, void *user_data); +typedef bool (*bt_bap_pac_foreach_t)(struct bt_bap_pac *lpac, + struct bt_bap_pac *rpac, + void *user_data); +typedef void (*bt_bap_pac_select_t)(struct bt_bap_pac *pac, int err, + struct iovec *caps, + struct iovec *metadata, + struct bt_bap_qos *qos, + void *user_data); +typedef void (*bt_bap_pac_config_t)(struct bt_bap_stream *stream, int err); +typedef void (*bt_bap_state_func_t)(struct bt_bap_stream *stream, + uint8_t old_state, uint8_t new_state, + void *user_data); +typedef void (*bt_bap_connecting_func_t)(struct bt_bap_stream *stream, + bool state, int fd, + void *user_data); +typedef void (*bt_bap_stream_func_t)(struct bt_bap_stream *stream, + uint8_t code, uint8_t reason, + void *user_data); +typedef void (*bt_bap_func_t)(struct bt_bap *bap, void *user_data); + +/* Local PAC related functions */ + +unsigned int bt_bap_pac_register(bt_bap_pac_func_t added, + bt_bap_pac_func_t removed, void *user_data, + bt_bap_destroy_func_t destroy); +bool bt_bap_pac_unregister(unsigned int id); + +struct bt_bap_pac_qos { + uint8_t framing; + uint8_t phy; + uint8_t rtn; + uint16_t latency; + uint32_t pd_min; + uint32_t pd_max; + uint32_t ppd_min; + uint32_t ppd_max; +}; + +struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db, + const char *name, uint8_t type, + uint8_t id, uint16_t cid, uint16_t vid, + struct bt_bap_pac_qos *qos, + struct iovec *data, + struct iovec *metadata); + +struct bt_bap_pac *bt_bap_add_pac(struct gatt_db *db, const char *name, + uint8_t type, uint8_t id, + struct bt_bap_pac_qos *qos, + struct iovec *data, + struct iovec *metadata); + +struct bt_bap_pac_ops { + int (*select) (struct bt_bap_pac *pac, struct bt_bap_pac_qos *qos, + struct iovec *caps, struct iovec *metadata, + bt_bap_pac_select_t cb, void *cb_data, void *user_data); + int (*config) (struct bt_bap_stream *stream, struct iovec *cfg, + struct bt_bap_qos *qos, bt_bap_pac_config_t cb, + void *user_data); + void (*clear) (struct bt_bap_stream *stream, void *user_data); +}; + +bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops, + void *user_data); + +bool bt_bap_remove_pac(struct bt_bap_pac *pac); + +uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac); + +struct bt_bap_stream *bt_bap_pac_get_stream(struct bt_bap_pac *pac); + +/* Session related function */ +unsigned int bt_bap_register(bt_bap_func_t added, bt_bap_func_t removed, + void *user_data); +bool bt_bap_unregister(unsigned int id); + +struct bt_bap *bt_bap_new(struct gatt_db *ldb, struct gatt_db *rdb); + +bool bt_bap_set_user_data(struct bt_bap *bap, void *user_data); + +void *bt_bap_get_user_data(struct bt_bap *bap); + +struct bt_att *bt_bap_get_att(struct bt_bap *bap); + +struct bt_bap *bt_bap_ref(struct bt_bap *bap); +void bt_bap_unref(struct bt_bap *bap); + +bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client); +void bt_bap_detach(struct bt_bap *bap); + +bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t cb, + void *user_data, bt_bap_destroy_func_t destroy); + +bool bap_print_cc(void *data, size_t len, util_debug_func_t func, + void *user_data); + +unsigned int bt_bap_ready_register(struct bt_bap *bap, + bt_bap_ready_func_t func, void *user_data, + bt_bap_destroy_func_t destroy); +bool bt_bap_ready_unregister(struct bt_bap *bap, unsigned int id); + +unsigned int bt_bap_state_register(struct bt_bap *bap, + bt_bap_state_func_t func, + bt_bap_connecting_func_t connecting, + void *user_data, bt_bap_destroy_func_t destroy); +bool bt_bap_state_unregister(struct bt_bap *bap, unsigned int id); + +const char *bt_bap_stream_statestr(uint8_t state); + +void bt_bap_foreach_pac(struct bt_bap *bap, uint8_t type, + bt_bap_pac_foreach_t func, void *user_data); + +int bt_bap_pac_get_vendor_codec(struct bt_bap_pac *pac, uint8_t *id, + uint16_t *cid, uint16_t *vid, + struct iovec **data, struct iovec **metadata); + +int bt_bap_pac_get_codec(struct bt_bap_pac *pac, uint8_t *id, + struct iovec **data, struct iovec **metadata); + +/* Stream related functions */ +int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + bt_bap_pac_select_t func, void *user_data); + +struct bt_bap_stream *bt_bap_config(struct bt_bap *bap, + struct bt_bap_pac *lpac, + struct bt_bap_pac *rpac, + struct bt_bap_qos *pqos, + struct iovec *data, + bt_bap_stream_func_t func, + void *user_data); + +struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream); +uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream); + +bool bt_bap_stream_set_user_data(struct bt_bap_stream *stream, void *user_data); + +void *bt_bap_stream_get_user_data(struct bt_bap_stream *stream); + +unsigned int bt_bap_stream_config(struct bt_bap_stream *stream, + struct bt_bap_qos *pqos, + struct iovec *data, + bt_bap_stream_func_t func, + void *user_data); + +unsigned int bt_bap_stream_qos(struct bt_bap_stream *stream, + struct bt_bap_qos *qos, + bt_bap_stream_func_t func, + void *user_data); + +unsigned int bt_bap_stream_enable(struct bt_bap_stream *stream, + bool enable_links, + struct iovec *metadata, + bt_bap_stream_func_t func, + void *user_data); + +unsigned int bt_bap_stream_start(struct bt_bap_stream *stream, + bt_bap_stream_func_t func, + void *user_data); + +unsigned int bt_bap_stream_disable(struct bt_bap_stream *stream, + bool disable_links, + bt_bap_stream_func_t func, + void *user_data); + +unsigned int bt_bap_stream_stop(struct bt_bap_stream *stream, + bt_bap_stream_func_t func, + void *user_data); + +unsigned int bt_bap_stream_metadata(struct bt_bap_stream *stream, + struct iovec *metadata, + bt_bap_stream_func_t func, + void *user_data); + +unsigned int bt_bap_stream_release(struct bt_bap_stream *stream, + bt_bap_stream_func_t func, + void *user_data); + +uint8_t bt_bap_stream_get_dir(struct bt_bap_stream *stream); +uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream); +struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream); +struct bt_bap_qos *bt_bap_stream_get_qos(struct bt_bap_stream *stream); +struct iovec *bt_bap_stream_get_metadata(struct bt_bap_stream *stream); + +struct io *bt_bap_stream_get_io(struct bt_bap_stream *stream); + +bool bt_bap_stream_set_io(struct bt_bap_stream *stream, int fd); + +int bt_bap_stream_cancel(struct bt_bap_stream *stream, unsigned int id); + +int bt_bap_stream_io_link(struct bt_bap_stream *stream, + struct bt_bap_stream *link); +struct queue *bt_bap_stream_io_get_links(struct bt_bap_stream *stream); +bool bt_bap_stream_io_get_qos(struct bt_bap_stream *stream, + struct bt_bap_qos **in, + struct bt_bap_qos **out); + +uint8_t bt_bap_stream_io_dir(struct bt_bap_stream *stream); + +int bt_bap_stream_io_connecting(struct bt_bap_stream *stream, int fd); +bool bt_bap_stream_io_is_connecting(struct bt_bap_stream *stream, int *fd); From patchwork Sat Aug 27 00:05:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600615 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 6D308C0502C for ; Sat, 27 Aug 2022 00:05:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345096AbiH0AFz (ORCPT ); Fri, 26 Aug 2022 20:05:55 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55286 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231629AbiH0AFx (ORCPT ); Fri, 26 Aug 2022 20:05:53 -0400 Received: from mail-pf1-x435.google.com (mail-pf1-x435.google.com [IPv6:2607:f8b0:4864:20::435]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1B621E42ED for ; Fri, 26 Aug 2022 17:05:50 -0700 (PDT) Received: by mail-pf1-x435.google.com with SMTP id 199so2922380pfz.2 for ; Fri, 26 Aug 2022 17:05:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=Yoe4noJuNjEWlWGOqPX3R0F/FXc8Ptn+t64/XmAnJP8=; b=eGby75WMFGLrfPTT1iyG5M9fScqltZSbBjS5sAPeT6kQ5GCKkL549fXevh0ag31gJ0 G3qdvlG2Mx5L62U/g6UaFZInOb7Vd+UTnnYbAbl7BuOSNGVAFRDFyCNjoi8G2DY/b40u svdxklfvbvXDydjNFFVJcgVkmqL6n5wYKWUFIf1RJx85Cft/FmSP99juAc4YMa9H9RBn ytZPid0ogrw8DSFHe6/dG1zbOGHe0VSmj3ZT25lZxFyOHhYnCafKjOU8RXF16eYkjVWE tOVX0GTJcRg6kIPYhx5Cv2HYX/rBO5UISPC/6QtpJC/PhgtnWXEI98C36jeHe3RBGe0n RMHg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=Yoe4noJuNjEWlWGOqPX3R0F/FXc8Ptn+t64/XmAnJP8=; b=IJHRGGWMfQD30WPyAVOCgXZIxAys7kEuP97RcudW7BIMRFM09XYk4FIfU3urIRK0IE dlCQ/qDyWnXfRUQAeMWedIxfg88TpMT5vAKCOloqP407csGruBYd7w3X7SqZmDfwQfWz 1lkXurVzyFSfsDANjbNeXfFEAu501XSqN7LA3/wx+fCFs+nQS/qrBAkXjMAeX93AWgZK Y0/N2zZHZDZT0bCeN+fRRDLnHQsd3Ol4WXYhqUN1SSZHdnZ9PUMJqc9XR1T54I/n+liO ZRNZITBJ70voV1XxHCnvnig9qC4/S8ixQk7aypF2PsCRBCY7fkPz0ps6uMIffHI58Z/T 3BQw== X-Gm-Message-State: ACgBeo0hQwTDlrEQ6dQhwmrjqS1riALW9fV/+eX3BAs2585wEmjKJDKE BIqYIL8AVVgZynfuIHDz5qKwDESiq1I= X-Google-Smtp-Source: AA6agR5xsUUeUoLPzB1TRkdLecc87RHkF+qkxPSVbhReVNihzAWJLnyCGHJc995FYf0bmkp29DUeFw== X-Received: by 2002:a63:ee55:0:b0:429:88cf:78df with SMTP id n21-20020a63ee55000000b0042988cf78dfmr5075799pgk.301.1661558748082; Fri, 26 Aug 2022 17:05:48 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.46 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:47 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 04/11] profiles: Add initial code for bap plugin Date: Fri, 26 Aug 2022 17:05:33 -0700 Message-Id: <20220827000540.113414-5-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds initial code for bap plugin which handles Basic Audio Profile, Publish Audio Capabilities Service and Audio Stream Control Service. --- Makefile.plugins | 5 + configure.ac | 4 + profiles/audio/bap.c | 1324 ++++++++++++++++++++++++++++++++++++ profiles/audio/media.c | 678 ++++++++++++++++-- profiles/audio/transport.c | 552 ++++++++++++++- profiles/audio/transport.h | 3 +- 6 files changed, 2518 insertions(+), 48 deletions(-) create mode 100644 profiles/audio/bap.c diff --git a/Makefile.plugins b/Makefile.plugins index 7693c767f785..213ed99edf2d 100644 --- a/Makefile.plugins +++ b/Makefile.plugins @@ -116,3 +116,8 @@ plugins_sixaxis_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version plugins_sixaxis_la_LIBADD = $(UDEV_LIBS) plugins_sixaxis_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden endif + +if BAP +builtin_modules += bap +builtin_sources += profiles/audio/bap.c +endif diff --git a/configure.ac b/configure.ac index 91fd194116f4..1f76915b4349 100644 --- a/configure.ac +++ b/configure.ac @@ -195,6 +195,10 @@ AC_ARG_ENABLE(health, AS_HELP_STRING([--enable-health], [enable health profiles]), [enable_health=${enableval}]) AM_CONDITIONAL(HEALTH, test "${enable_health}" = "yes") +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(tools, AS_HELP_STRING([--disable-tools], [disable Bluetooth tools]), [enable_tools=${enableval}]) AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no") diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c new file mode 100644 index 000000000000..01ac03026259 --- /dev/null +++ b/profiles/audio/bap.c @@ -0,0 +1,1324 @@ +// 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 + +#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/bap.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" + +#define PACS_UUID_STR "00001850-0000-1000-8000-00805f9b34fb" +#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" + +struct bap_ep { + char *path; + struct bap_data *data; + struct bt_bap_pac *lpac; + struct bt_bap_pac *rpac; + struct bt_bap_stream *stream; + GIOChannel *io; + unsigned int io_id; + bool recreate; + struct iovec *caps; + struct iovec *metadata; + struct bt_bap_qos qos; + unsigned int id; + DBusMessage *msg; +}; + +struct bap_data { + struct btd_device *device; + struct btd_service *service; + struct bt_bap *bap; + unsigned int ready_id; + unsigned int state_id; + unsigned int pac_id; + struct queue *srcs; + struct queue *snks; + struct queue *streams; + GIOChannel *listen_io; +}; + +static struct queue *sessions; + +static void bap_debug(const char *str, void *user_data) +{ + DBG_IDX(0xffff, "%s", str); +} + +static void ep_unregister(void *data) +{ + struct bap_ep *ep = data; + + DBG("ep %p path %s", ep, ep->path); + + g_dbus_unregister_interface(btd_get_dbus_connection(), ep->path, + MEDIA_ENDPOINT_INTERFACE); +} + +static void bap_data_free(struct bap_data *data) +{ + if (data->listen_io) { + g_io_channel_shutdown(data->listen_io, TRUE, NULL); + g_io_channel_unref(data->listen_io); + } + + if (data->service) { + btd_service_set_user_data(data->service, NULL); + bt_bap_set_user_data(data->bap, NULL); + } + + queue_destroy(data->snks, ep_unregister); + queue_destroy(data->srcs, ep_unregister); + queue_destroy(data->streams, NULL); + bt_bap_ready_unregister(data->bap, data->ready_id); + bt_bap_state_unregister(data->bap, data->state_id); + bt_bap_pac_unregister(data->pac_id); + bt_bap_unref(data->bap); + free(data); +} + +static void bap_data_remove(struct bap_data *data) +{ + DBG("data %p", data); + + if (!queue_remove(sessions, data)) + return; + + bap_data_free(data); + + if (queue_isempty(sessions)) { + queue_destroy(sessions, NULL); + sessions = NULL; + } +} + +static void bap_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct bap_data *data; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + data = btd_service_get_user_data(service); + if (!data) { + error("BAP service not handled by profile"); + return; + } + + bap_data_remove(data); +} + +static gboolean get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct bap_ep *ep = data; + const char *uuid; + + if (queue_find(ep->data->snks, NULL, ep)) + uuid = PAC_SINK_UUID; + else + uuid = PAC_SOURCE_UUID; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + + return TRUE; +} + +static gboolean get_codec(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct bap_ep *ep = data; + uint8_t codec; + + bt_bap_pac_get_codec(ep->rpac, &codec, NULL, NULL); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &codec); + + return TRUE; +} + +static gboolean get_capabilities(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct bap_ep *ep = data; + DBusMessageIter array; + struct iovec *d; + + bt_bap_pac_get_codec(ep->rpac, NULL, &d, NULL); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &d->iov_base, d->iov_len); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean get_device(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct bap_ep *ep = data; + const char *path; + + path = device_get_path(ep->data->device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static const GDBusPropertyTable ep_properties[] = { + { "UUID", "s", get_uuid, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Codec", "y", get_codec, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Capabilities", "ay", get_capabilities, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Device", "o", get_device, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { } +}; + +static int parse_array(DBusMessageIter *iter, struct iovec **iov) +{ + DBusMessageIter array; + + if (!iov) + return 0; + + if (!(*iov)) + *iov = new0(struct iovec, 1); + + dbus_message_iter_recurse(iter, &array); + dbus_message_iter_get_fixed_array(&array, &(*iov)->iov_base, + (int *)&(*iov)->iov_len); + return 0; +} + +static int parse_properties(DBusMessageIter *props, struct iovec **caps, + struct iovec **metadata, struct bt_bap_qos *qos) +{ + const char *key; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + + if (!strcasecmp(key, "Capabilities")) { + if (var != DBUS_TYPE_ARRAY) + goto fail; + + if (parse_array(&value, caps)) + goto fail; + } else if (!strcasecmp(key, "Metadata")) { + if (var != DBUS_TYPE_ARRAY) + goto fail; + + if (parse_array(&value, metadata)) + goto fail; + } else if (!strcasecmp(key, "CIG")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->cig_id); + } else if (!strcasecmp(key, "CIS")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->cis_id); + } else if (!strcasecmp(key, "Interval")) { + if (var != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->interval); + } else if (!strcasecmp(key, "Framing")) { + dbus_bool_t val; + + if (var != DBUS_TYPE_BOOLEAN) + goto fail; + + dbus_message_iter_get_basic(&value, &val); + + qos->framing = val; + } else if (!strcasecmp(key, "PHY")) { + const char *str; + + if (var != DBUS_TYPE_STRING) + goto fail; + + dbus_message_iter_get_basic(&value, &str); + + if (!strcasecmp(str, "1M")) + qos->phy = 0x01; + else if (!strcasecmp(str, "2M")) + qos->phy = 0x02; + else + goto fail; + } else if (!strcasecmp(key, "SDU")) { + if (var != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->sdu); + } else if (!strcasecmp(key, "Retransmissions")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->rtn); + } else if (!strcasecmp(key, "Latency")) { + if (var != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->latency); + } else if (!strcasecmp(key, "Delay")) { + if (var != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->delay); + } else if (!strcasecmp(key, "TargetLatency")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->target_latency); + } + + dbus_message_iter_next(props); + } + + return 0; + +fail: + DBG("Failed parsing %s", key); + + if (*caps) { + free(*caps); + *caps = NULL; + } + + return -EINVAL; +} + +static void qos_cb(struct bt_bap_stream *stream, uint8_t code, uint8_t reason, + void *user_data) +{ + struct bap_ep *ep = user_data; + DBusMessage *reply; + + DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason); + + if (!ep->msg) + return; + + if (!code) + reply = dbus_message_new_method_return(ep->msg); + else + reply = btd_error_failed(ep->msg, "Unable to configure"); + + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(ep->msg); + ep->msg = NULL; +} + +static void config_cb(struct bt_bap_stream *stream, + uint8_t code, uint8_t reason, + void *user_data) +{ + struct bap_ep *ep = user_data; + DBusMessage *reply; + + DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason); + + ep->id = 0; + + if (!code) + return; + + if (!ep->msg) + return; + + reply = btd_error_failed(ep->msg, "Unable to configure"); + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(ep->msg); + ep->msg = NULL; +} + +static void bap_io_close(struct bap_ep *ep) +{ + int fd; + + if (ep->io_id) { + g_source_remove(ep->io_id); + ep->io_id = 0; + } + + if (!ep->io) + return; + + + DBG("ep %p", ep); + + fd = g_io_channel_unix_get_fd(ep->io); + close(fd); + + g_io_channel_unref(ep->io); + ep->io = NULL; +} + +static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct bap_ep *ep = data; + const char *path; + DBusMessageIter args, props; + + if (ep->msg) + return btd_error_busy(msg); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return btd_error_invalid_args(msg); + + /* Disconnect IO if connecting since QoS is going to be reconfigured */ + if (bt_bap_stream_io_is_connecting(ep->stream, NULL)) { + bap_io_close(ep); + bt_bap_stream_io_connecting(ep->stream, -1); + } + + /* Mark CIG and CIS to be auto assigned */ + ep->qos.cig_id = BT_ISO_QOS_CIG_UNSET; + ep->qos.cis_id = BT_ISO_QOS_CIS_UNSET; + + if (parse_properties(&props, &ep->caps, &ep->metadata, &ep->qos) < 0) { + DBG("Unable to parse properties"); + return btd_error_invalid_args(msg); + } + + /* TODO: Check if stream capabilities match add support for Latency + * and PHY. + */ + if (ep->stream) + ep->id = bt_bap_stream_config(ep->stream, &ep->qos, ep->caps, + config_cb, ep); + else + ep->stream = bt_bap_config(ep->data->bap, ep->lpac, ep->rpac, + &ep->qos, ep->caps, + config_cb, ep); + + if (!ep->stream) { + DBG("Unable to config stream"); + free(ep->caps); + ep->caps = NULL; + return btd_error_invalid_args(msg); + } + + bt_bap_stream_set_user_data(ep->stream, ep->path); + ep->msg = dbus_message_ref(msg); + + return NULL; +} + +static const GDBusMethodTable ep_methods[] = { + { GDBUS_EXPERIMENTAL_ASYNC_METHOD("SetConfiguration", + GDBUS_ARGS({ "endpoint", "o" }, + { "properties", "a{sv}" } ), + NULL, set_configuration) }, + { }, +}; + +static void ep_free(void *data) +{ + struct bap_ep *ep = data; + + if (ep->id) + bt_bap_stream_cancel(ep->stream, ep->id); + + bap_io_close(ep); + + free(ep->caps); + free(ep->path); + free(ep); +} + +static struct bap_ep *ep_register(struct btd_service *service, + struct bt_bap_pac *lpac, + struct bt_bap_pac *rpac) +{ + struct btd_device *device = btd_service_get_device(service); + struct bap_data *data = btd_service_get_user_data(service); + struct bap_ep *ep; + struct queue *queue; + int i, err; + const char *suffix; + + switch (bt_bap_pac_get_type(rpac)) { + case BT_BAP_SINK: + queue = data->snks; + i = queue_length(data->snks); + suffix = "sink"; + break; + case BT_BAP_SOURCE: + queue = data->srcs; + i = queue_length(data->srcs); + suffix = "source"; + break; + default: + return NULL; + } + + ep = new0(struct bap_ep, 1); + ep->data = data; + ep->lpac = lpac; + ep->rpac = rpac; + + err = asprintf(&ep->path, "%s/pac_%s%d", device_get_path(device), + suffix, i); + if (err < 0) { + error("Could not allocate path for remote pac %s/pac%d", + device_get_path(device), i); + free(ep); + return NULL; + } + + if (g_dbus_register_interface(btd_get_dbus_connection(), + ep->path, MEDIA_ENDPOINT_INTERFACE, + ep_methods, NULL, ep_properties, + ep, ep_free) == FALSE) { + error("Could not register remote ep %s", ep->path); + ep_free(ep); + return NULL; + } + + DBG("ep %p lpac %p rpac %p path %s", ep, ep->lpac, ep->rpac, ep->path); + + queue_push_tail(queue, ep); + + return ep; +} + +static void select_cb(struct bt_bap_pac *pac, int err, struct iovec *caps, + struct iovec *metadata, struct bt_bap_qos *qos, + void *user_data) +{ + struct bap_ep *ep = user_data; + + if (err) { + error("err %d", err); + return; + } + + ep->caps = caps; + ep->metadata = metadata; + ep->qos = *qos; + + /* TODO: Check if stream capabilities match add support for Latency + * and PHY. + */ + if (ep->stream) + ep->id = bt_bap_stream_config(ep->stream, &ep->qos, ep->caps, + config_cb, ep); + else + ep->stream = bt_bap_config(ep->data->bap, ep->lpac, ep->rpac, + &ep->qos, ep->caps, + config_cb, ep); + + if (!ep->stream) { + DBG("Unable to config stream"); + free(ep->caps); + ep->caps = NULL; + } + + bt_bap_stream_set_user_data(ep->stream, ep->path); +} + +static bool pac_found(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + void *user_data) +{ + struct btd_service *service = user_data; + struct bap_ep *ep; + + DBG("lpac %p rpac %p", lpac, rpac); + + ep = ep_register(service, lpac, rpac); + if (!ep) { + error("Unable to register endpoint for pac %p", rpac); + return true; + } + + /* TODO: Cache LRU? */ + if (btd_service_is_initiator(service)) + bt_bap_select(lpac, rpac, select_cb, ep); + + return true; +} + +static void bap_ready(struct bt_bap *bap, void *user_data) +{ + struct btd_service *service = user_data; + + DBG("bap %p", bap); + + bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_found, service); + bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_found, service); +} + +static bool match_ep_by_stream(const void *data, const void *user_data) +{ + const struct bap_ep *ep = data; + const struct bt_bap_stream *stream = user_data; + + return ep->stream == stream; +} + +static struct bap_ep *bap_find_ep_by_stream(struct bap_data *data, + struct bt_bap_stream *stream) +{ + struct bap_ep *ep; + + ep = queue_find(data->snks, match_ep_by_stream, stream); + if (ep) + return ep; + + return queue_find(data->srcs, match_ep_by_stream, stream); +} + +static void iso_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct bt_bap_stream *stream = user_data; + int fd; + + if (err) { + error("%s", err->message); + bt_bap_stream_set_io(stream, -1); + return; + } + + DBG("ISO connected"); + + fd = g_io_channel_unix_get_fd(chan); + + if (bt_bap_stream_set_io(stream, fd)) { + g_io_channel_set_close_on_unref(chan, FALSE); + return; + } + + error("Unable to set IO"); + bt_bap_stream_set_io(stream, -1); +} + +static void bap_iso_qos(struct bt_bap_qos *qos, struct bt_iso_io_qos *io) +{ + if (!qos) + return; + + io->interval = qos->interval; + io->latency = qos->latency; + io->sdu = qos->sdu; + io->phy = qos->phy; + io->rtn = qos->rtn; +} + +static bool match_stream_qos(const void *data, const void *user_data) +{ + const struct bt_bap_stream *stream = data; + const struct bt_iso_qos *iso_qos = user_data; + struct bt_bap_qos *qos; + + qos = bt_bap_stream_get_qos((void *)stream); + + if (iso_qos->cig != qos->cig_id) + return false; + + return iso_qos->cis == qos->cis_id; +} + +static void iso_confirm_cb(GIOChannel *io, void *user_data) +{ + struct bap_data *data = user_data; + struct bt_bap_stream *stream; + struct bt_iso_qos qos; + char address[18]; + GError *err = NULL; + + bt_io_get(io, &err, + BT_IO_OPT_DEST, address, + BT_IO_OPT_QOS, &qos, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + DBG("ISO: incoming connect from %s (CIG 0x%02x CIS 0x%02x)", + address, qos.cig, qos.cis); + + stream = queue_remove_if(data->streams, match_stream_qos, &qos); + if (!stream) { + error("No matching stream found"); + goto drop; + } + + if (!bt_io_accept(io, iso_connect_cb, stream, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(io, TRUE, NULL); +} + +static void bap_accept_io(struct bap_data *data, struct bt_bap_stream *stream, + int fd, int defer) +{ + char c; + struct pollfd pfd; + socklen_t len; + + if (fd < 0 || defer) + return; + + /* Check if socket has DEFER_SETUP set */ + len = sizeof(defer); + if (getsockopt(fd, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer, &len) < 0) + /* Ignore errors since the fd may be connected already */ + return; + + if (!defer) + return; + + DBG("stream %p fd %d defer %s", stream, fd, defer ? "true" : "false"); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + pfd.events = POLLOUT; + + if (poll(&pfd, 1, 0) < 0) { + error("poll: %s (%d)", strerror(errno), errno); + goto fail; + } + + if (!(pfd.revents & POLLOUT)) { + if (read(fd, &c, 1) < 0) { + error("read: %s (%d)", strerror(errno), errno); + goto fail; + } + } + + return; + +fail: + close(fd); +} + +static void bap_create_io(struct bap_data *data, struct bap_ep *ep, + struct bt_bap_stream *stream, int defer); + +static gboolean bap_io_recreate(void *user_data) +{ + struct bap_ep *ep = user_data; + + DBG("ep %p", ep); + + ep->io_id = 0; + + bap_create_io(ep->data, ep, ep->stream, true); + + return FALSE; +} + +static gboolean bap_io_disconnected(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct bap_ep *ep = user_data; + + DBG("ep %p recreate %s", ep, ep->recreate ? "true" : "false"); + + ep->io_id = 0; + + bap_io_close(ep); + + /* Check if connecting recreate IO */ + if (ep->recreate) { + ep->recreate = false; + ep->io_id = g_idle_add(bap_io_recreate, ep); + } + + return FALSE; +} + +static void bap_connect_io_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct bap_ep *ep = user_data; + + if (!ep->stream) + return; + + iso_connect_cb(chan, err, ep->stream); +} + +static void bap_connect_io(struct bap_data *data, struct bap_ep *ep, + struct bt_bap_stream *stream, + struct bt_iso_qos *qos, int defer) +{ + struct btd_adapter *adapter = device_get_adapter(data->device); + GIOChannel *io; + GError *err = NULL; + int fd; + + /* If IO already set skip creating it again */ + if (bt_bap_stream_get_io(stream)) + return; + + if (bt_bap_stream_io_is_connecting(stream, &fd)) { + bap_accept_io(data, stream, fd, defer); + return; + } + + /* If IO channel still up wait for it to be disconnected and then + * recreate. + */ + if (ep->io) { + ep->recreate = true; + return; + } + + if (ep->io_id) { + g_source_remove(ep->io_id); + ep->io_id = 0; + } + + DBG("ep %p stream %p defer %s", ep, stream, defer ? "true" : "false"); + + io = bt_io_connect(bap_connect_io_cb, ep, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_DEST_BDADDR, + device_get_address(ep->data->device), + BT_IO_OPT_DEST_TYPE, + device_get_le_address_type(ep->data->device), + BT_IO_OPT_MODE, BT_IO_MODE_ISO, + BT_IO_OPT_QOS, qos, + BT_IO_OPT_DEFER_TIMEOUT, defer, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return; + } + + ep->io_id = g_io_add_watch(io, G_IO_HUP | G_IO_ERR | G_IO_NVAL, + bap_io_disconnected, ep); + + ep->io = io; + + bt_bap_stream_io_connecting(stream, g_io_channel_unix_get_fd(io)); +} + +static void bap_listen_io(struct bap_data *data, struct bt_bap_stream *stream, + struct bt_iso_qos *qos) +{ + struct btd_adapter *adapter = device_get_adapter(data->device); + GIOChannel *io; + GError *err = NULL; + + DBG("stream %p", stream); + + /* If IO already set skip creating it again */ + if (bt_bap_stream_get_io(stream) || data->listen_io) + return; + + io = bt_io_listen(NULL, iso_confirm_cb, data, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_DEST_BDADDR, + device_get_address(data->device), + BT_IO_OPT_DEST_TYPE, + device_get_le_address_type(data->device), + BT_IO_OPT_MODE, BT_IO_MODE_ISO, + BT_IO_OPT_QOS, qos, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return; + } + + data->listen_io = io; +} + +static void bap_create_io(struct bap_data *data, struct bap_ep *ep, + struct bt_bap_stream *stream, int defer) +{ + struct bt_bap_qos *qos[2] = {}; + struct bt_iso_qos iso_qos; + + DBG("ep %p stream %p defer %s", ep, stream, defer ? "true" : "false"); + + if (!data->streams) + data->streams = queue_new(); + + if (!queue_find(data->streams, NULL, stream)) + queue_push_tail(data->streams, stream); + + if (!bt_bap_stream_io_get_qos(stream, &qos[0], &qos[1])) { + error("bt_bap_stream_get_qos_links: failed"); + return; + } + + memset(&iso_qos, 0, sizeof(iso_qos)); + iso_qos.cig = qos[0] ? qos[0]->cig_id : qos[1]->cig_id; + iso_qos.cis = qos[0] ? qos[0]->cis_id : qos[1]->cis_id; + + bap_iso_qos(qos[0], &iso_qos.in); + bap_iso_qos(qos[1], &iso_qos.out); + + if (ep) + bap_connect_io(data, ep, stream, &iso_qos, defer); + else + bap_listen_io(data, stream, &iso_qos); +} + +static void bap_state(struct bt_bap_stream *stream, uint8_t old_state, + uint8_t new_state, void *user_data) +{ + struct bap_data *data = user_data; + struct bap_ep *ep; + + DBG("stream %p: %s(%u) -> %s(%u)", stream, + bt_bap_stream_statestr(old_state), old_state, + bt_bap_stream_statestr(new_state), new_state); + + if (new_state == old_state) + return; + + ep = bap_find_ep_by_stream(data, stream); + + switch (new_state) { + case BT_BAP_STREAM_STATE_IDLE: + /* Release stream if idle */ + if (ep) + bap_io_close(ep); + else + queue_remove(data->streams, stream); + break; + case BT_BAP_STREAM_STATE_CONFIG: + if (ep && !ep->id) { + bap_create_io(data, ep, stream, true); + if (!ep->io) { + error("Unable to create io"); + bt_bap_stream_release(stream, NULL, NULL); + return; + } + + + /* Wait QoS response to respond */ + ep->id = bt_bap_stream_qos(stream, &ep->qos, qos_cb, + ep); + if (!ep->id) { + error("Failed to Configure QoS"); + bt_bap_stream_release(stream, NULL, NULL); + } + } + break; + case BT_BAP_STREAM_STATE_QOS: + bap_create_io(data, ep, stream, true); + break; + case BT_BAP_STREAM_STATE_ENABLING: + if (ep) + bap_create_io(data, ep, stream, false); + break; + } +} + +static void pac_added(struct bt_bap_pac *pac, void *user_data) +{ + struct btd_service *service = user_data; + struct bap_data *data; + + DBG("pac %p", pac); + + if (btd_service_get_state(service) != BTD_SERVICE_STATE_CONNECTED) + return; + + data = btd_service_get_user_data(service); + + bt_bap_foreach_pac(data->bap, BT_BAP_SOURCE, pac_found, service); + bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_found, service); +} + +static bool ep_match_rpac(const void *data, const void *match_data) +{ + const struct bap_ep *ep = data; + const struct bt_bap_pac *pac = match_data; + + return ep->rpac == pac; +} + +static void pac_removed(struct bt_bap_pac *pac, void *user_data) +{ + struct btd_service *service = user_data; + struct bap_data *data; + struct queue *queue; + struct bap_ep *ep; + + DBG("pac %p", pac); + + if (btd_service_get_state(service) != BTD_SERVICE_STATE_CONNECTED) + return; + + data = btd_service_get_user_data(service); + + switch (bt_bap_pac_get_type(pac)) { + case BT_BAP_SINK: + queue = data->srcs; + break; + case BT_BAP_SOURCE: + queue = data->snks; + break; + default: + return; + } + + ep = queue_remove_if(queue, ep_match_rpac, pac); + if (!ep) + return; + + ep_unregister(ep); +} + +static struct bap_data *bap_data_new(struct btd_device *device) +{ + struct bap_data *data; + + data = new0(struct bap_data, 1); + data->device = device; + data->srcs = queue_new(); + data->snks = queue_new(); + + return data; +} + +static void bap_data_add(struct bap_data *data) +{ + DBG("data %p", data); + + if (queue_find(sessions, NULL, data)) { + error("data %p already added", data); + return; + } + + bt_bap_set_debug(data->bap, bap_debug, NULL, NULL); + + if (!sessions) + sessions = queue_new(); + + queue_push_tail(sessions, data); + + if (data->service) + btd_service_set_user_data(data->service, data); +} + +static bool match_data(const void *data, const void *match_data) +{ + const struct bap_data *bdata = data; + const struct bt_bap *bap = match_data; + + return bdata->bap == bap; +} + +static void bap_connecting(struct bt_bap_stream *stream, bool state, int fd, + void *user_data) +{ + struct bap_data *data = user_data; + struct bap_ep *ep; + GIOChannel *io; + + if (!state) + return; + + ep = bap_find_ep_by_stream(data, stream); + if (!ep) + return; + + ep->recreate = false; + + if (!ep->io) { + io = g_io_channel_unix_new(fd); + ep->io = io; + } else + io = ep->io; + + g_io_channel_set_close_on_unref(io, FALSE); + + /* Attempt to get CIG/CIS if they have not been set */ + if (ep->qos.cig_id == BT_ISO_QOS_CIG_UNSET || + ep->qos.cis_id == BT_ISO_QOS_CIS_UNSET) { + struct bt_iso_qos qos; + GError *err = NULL; + + if (!bt_io_get(io, &err, BT_IO_OPT_QOS, &qos, + BT_IO_OPT_INVALID)) { + error("%s", err->message); + g_error_free(err); + g_io_channel_unref(io); + return; + } + + ep->qos.cig_id = qos.cig; + ep->qos.cis_id = qos.cis; + } + + DBG("stream %p fd %d: CIG 0x%02x CIS 0x%02x", stream, fd, + ep->qos.cig_id, ep->qos.cis_id); +} + +static void bap_attached(struct bt_bap *bap, void *user_data) +{ + struct bap_data *data; + struct bt_att *att; + struct btd_device *device; + + DBG("%p", bap); + + data = queue_find(sessions, match_data, bap); + if (data) + return; + + att = bt_bap_get_att(bap); + if (!att) + return; + + device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); + if (!device) { + error("Unable to find device"); + return; + } + + data = bap_data_new(device); + data->bap = bap; + + bap_data_add(data); + + data->state_id = bt_bap_state_register(data->bap, bap_state, + bap_connecting, data, NULL); +} + +static void bap_detached(struct bt_bap *bap, void *user_data) +{ + struct bap_data *data; + + DBG("%p", bap); + + data = queue_find(sessions, match_data, bap); + if (!data) { + error("Unable to find bap session"); + return; + } + + /* If there is a service it means there is PACS thus we can keep + * instance allocated. + */ + if (data->service) + return; + + bap_data_remove(data); +} + +static int bap_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 bap_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) { + error("BAP requires ISO Socket which is not enabled"); + return -ENOTSUP; + } + + /* Ignore, if we were probed for this device already */ + if (data) { + error("Profile probed twice for the same device!"); + return -EINVAL; + } + + data = bap_data_new(device); + data->service = service; + + data->bap = bt_bap_new(btd_gatt_database_get_db(database), + btd_device_get_gatt_db(device)); + if (!data->bap) { + error("Unable to create BAP instance"); + free(data); + return -EINVAL; + } + + bap_data_add(data); + + data->ready_id = bt_bap_ready_register(data->bap, bap_ready, service, + NULL); + data->state_id = bt_bap_state_register(data->bap, bap_state, + bap_connecting, data, NULL); + data->pac_id = bt_bap_pac_register(pac_added, pac_removed, service, + NULL); + + bt_bap_set_user_data(data->bap, service); + + return 0; +} + +static int bap_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 bap_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + if (!data) { + error("BAP service not handled by profile"); + return -EINVAL; + } + + if (!bt_bap_attach(data->bap, client)) { + error("BAP unable to attach"); + return -EINVAL; + } + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static bool ep_remove(const void *data, const void *match_data) +{ + ep_unregister((void *)data); + + return true; +} + +static int bap_disconnect(struct btd_service *service) +{ + struct bap_data *data = btd_service_get_user_data(service); + + queue_remove_all(data->snks, ep_remove, NULL, NULL); + queue_remove_all(data->srcs, ep_remove, NULL, NULL); + + bt_bap_detach(data->bap); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static struct btd_profile bap_profile = { + .name = "bap", + .priority = BTD_PROFILE_PRIORITY_MEDIUM, + .remote_uuid = PACS_UUID_STR, + .device_probe = bap_probe, + .device_remove = bap_remove, + .accept = bap_accept, + .disconnect = bap_disconnect, +}; + +static unsigned int bap_id = 0; + +static int bap_init(void) +{ + if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { + warn("D-Bus experimental not enabled"); + return -ENOTSUP; + } + + btd_profile_register(&bap_profile); + bap_id = bt_bap_register(bap_attached, bap_detached, NULL); + + return 0; +} + +static void bap_exit(void) +{ + if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) { + btd_profile_unregister(&bap_profile); + bt_bap_unregister(bap_id); + } +} + +BLUETOOTH_PLUGIN_DEFINE(bap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + bap_init, bap_exit) diff --git a/profiles/audio/media.c b/profiles/audio/media.c index c5d8ab14ef49..ff3fa197b284 100644 --- a/profiles/audio/media.c +++ b/profiles/audio/media.c @@ -31,12 +31,16 @@ #include "src/device.h" #include "src/dbus-common.h" #include "src/profile.h" +#include "src/service.h" #include "src/uuid-helper.h" #include "src/log.h" #include "src/error.h" +#include "src/gatt-database.h" #include "src/shared/util.h" #include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/bap.h" #include "avdtp.h" #include "media.h" @@ -81,10 +85,14 @@ struct endpoint_request { struct media_endpoint { struct a2dp_sep *sep; + struct bt_bap_pac *pac; + void *stream; char *sender; /* Endpoint DBus bus id */ char *path; /* Endpoint object path */ char *uuid; /* Endpoint property UUID */ uint8_t codec; /* Endpoint codec */ + bool delay_reporting;/* Endpoint delay_reporting */ + struct bt_bap_pac_qos qos; /* Endpoint qos */ uint8_t *capabilities; /* Endpoint property capabilities */ size_t size; /* Endpoint capabilities size */ guint hs_watch; @@ -161,6 +169,12 @@ static void media_endpoint_destroy(struct media_endpoint *endpoint) g_slist_free_full(endpoint->transports, (GDestroyNotify) media_transport_destroy); + endpoint->transports = NULL; + + if (endpoint->pac) { + bt_bap_remove_pac(endpoint->pac); + endpoint->pac = NULL; + } g_dbus_remove_watch(btd_get_dbus_connection(), endpoint->watch); g_free(endpoint->capabilities); @@ -286,6 +300,7 @@ static void endpoint_reply(DBusPendingCall *call, void *user_data) struct endpoint_request *request = user_data; struct media_endpoint *endpoint = request->endpoint; DBusMessage *reply; + DBusMessageIter args, props; DBusError err; gboolean value; void *ret = NULL; @@ -318,7 +333,7 @@ static void endpoint_reply(DBusPendingCall *call, void *user_data) } if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, - "SelectConfiguration")) { + "SelectConfiguration")) { DBusMessageIter args, array; uint8_t *configuration; @@ -330,7 +345,14 @@ static void endpoint_reply(DBusPendingCall *call, void *user_data) ret = configuration; goto done; - } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { + } else if (dbus_message_is_method_call(request->msg, + MEDIA_ENDPOINT_INTERFACE, + "SelectProperties")) { + dbus_message_iter_init(reply, &args); + dbus_message_iter_recurse(&args, &props); + ret = &props; + goto done; + } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); dbus_error_free(&err); goto done; @@ -496,7 +518,7 @@ static gboolean set_configuration(struct media_endpoint *endpoint, transport = media_transport_create(device, a2dp_setup_remote_path(data->setup), - configuration, size, endpoint); + configuration, size, endpoint, NULL); if (transport == NULL) return FALSE; @@ -671,32 +693,474 @@ static void a2dp_destroy_endpoint(void *user_data) release_endpoint(endpoint); } -static gboolean endpoint_init_a2dp_source(struct media_endpoint *endpoint, - gboolean delay_reporting, - int *err) +static bool endpoint_init_a2dp_source(struct media_endpoint *endpoint, int *err) { endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SOURCE, endpoint->codec, - delay_reporting, &a2dp_endpoint, - endpoint, a2dp_destroy_endpoint, err); + endpoint->delay_reporting, + &a2dp_endpoint, endpoint, + a2dp_destroy_endpoint, err); if (endpoint->sep == NULL) - return FALSE; + return false; - return TRUE; + return true; } -static gboolean endpoint_init_a2dp_sink(struct media_endpoint *endpoint, - gboolean delay_reporting, - int *err) +static bool endpoint_init_a2dp_sink(struct media_endpoint *endpoint, int *err) { endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SINK, endpoint->codec, - delay_reporting, &a2dp_endpoint, - endpoint, a2dp_destroy_endpoint, err); + endpoint->delay_reporting, + &a2dp_endpoint, endpoint, + a2dp_destroy_endpoint, err); if (endpoint->sep == NULL) - return FALSE; + return false; - return TRUE; + return true; +} + +struct pac_select_data { + struct bt_bap_pac *pac; + bt_bap_pac_select_t cb; + void *user_data; +}; + +static int parse_array(DBusMessageIter *iter, struct iovec **iov) +{ + DBusMessageIter array; + + if (!iov) + return 0; + + if (!(*iov)) + *iov = new0(struct iovec, 1); + + dbus_message_iter_recurse(iter, &array); + dbus_message_iter_get_fixed_array(&array, &(*iov)->iov_base, + (int *)&(*iov)->iov_len); + return 0; +} + +static int parse_select_properties(DBusMessageIter *props, struct iovec **caps, + struct iovec **metadata, + struct bt_bap_qos *qos) +{ + const char *key; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + + if (!strcasecmp(key, "Capabilities")) { + if (var != DBUS_TYPE_ARRAY) + goto fail; + + if (parse_array(&value, caps)) + goto fail; + } else if (!strcasecmp(key, "Metadata")) { + if (var != DBUS_TYPE_ARRAY) + goto fail; + + if (parse_array(&value, metadata)) + goto fail; + } else if (!strcasecmp(key, "CIG")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->cig_id); + } else if (!strcasecmp(key, "CIS")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->cis_id); + } else if (!strcasecmp(key, "Interval")) { + if (var != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->interval); + } else if (!strcasecmp(key, "Framing")) { + dbus_bool_t val; + + if (var != DBUS_TYPE_BOOLEAN) + goto fail; + + dbus_message_iter_get_basic(&value, &val); + + qos->framing = val; + } else if (!strcasecmp(key, "PHY")) { + const char *str; + + if (var != DBUS_TYPE_STRING) + goto fail; + + dbus_message_iter_get_basic(&value, &str); + + if (!strcasecmp(str, "1M")) + qos->phy = 0x01; + else if (!strcasecmp(str, "2M")) + qos->phy = 0x02; + else + goto fail; + } else if (!strcasecmp(key, "SDU")) { + if (var != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->sdu); + } else if (!strcasecmp(key, "Retransmissions")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->rtn); + } else if (!strcasecmp(key, "Latency")) { + if (var != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->latency); + } else if (!strcasecmp(key, "Delay")) { + if (var != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->delay); + } else if (!strcasecmp(key, "TargetLatency")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->target_latency); + } + + dbus_message_iter_next(props); + } + + return 0; + +fail: + DBG("Failed parsing %s", key); + + if (*caps) { + free(*caps); + *caps = NULL; + } + + return -EINVAL; +} + +static void pac_select_cb(struct media_endpoint *endpoint, void *ret, int size, + void *user_data) +{ + struct pac_select_data *data = user_data; + DBusMessageIter *iter = ret; + int err; + struct iovec *caps = NULL, *metadata = NULL; + struct bt_bap_qos qos; + + if (!ret) { + err = -EPERM; + goto done; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) { + DBG("Unexpected argument type: %c != %c", + dbus_message_iter_get_arg_type(iter), + DBUS_TYPE_DICT_ENTRY); + err = -EINVAL; + goto done; + } + + memset(&qos, 0, sizeof(qos)); + + /* Mark CIG and CIS to be auto assigned */ + qos.cig_id = BT_ISO_QOS_CIG_UNSET; + qos.cis_id = BT_ISO_QOS_CIS_UNSET; + + err = parse_select_properties(iter, &caps, &metadata, &qos); + if (err < 0) + DBG("Unable to parse properties"); + +done: + data->cb(data->pac, err, caps, metadata, &qos, data->user_data); +} + +static int pac_select(struct bt_bap_pac *pac, struct bt_bap_pac_qos *qos, + struct iovec *caps, struct iovec *metadata, + bt_bap_pac_select_t cb, void *cb_data, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + struct pac_select_data *data; + DBusMessage *msg; + DBusMessageIter iter, dict; + const char *key = "Capabilities"; + + if (!caps) + return -EINVAL; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SelectProperties"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + data = new0(struct pac_select_data, 1); + data->pac = pac; + data->cb = cb; + data->user_data = cb_data; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, + DBUS_TYPE_BYTE, &caps->iov_base, + caps->iov_len); + + if (metadata) { + key = "Metadata"; + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, + DBUS_TYPE_BYTE, + &metadata->iov_base, + metadata->iov_len); + } + + if (qos && qos->phy) { + g_dbus_dict_append_entry(&dict, "Framing", DBUS_TYPE_BYTE, + &qos->framing); + + g_dbus_dict_append_entry(&dict, "PHY", DBUS_TYPE_BYTE, + &qos->phy); + + g_dbus_dict_append_entry(&dict, "Latency", DBUS_TYPE_UINT16, + &qos->latency); + + g_dbus_dict_append_entry(&dict, "MinimumDelay", + DBUS_TYPE_UINT32, &qos->pd_min); + + g_dbus_dict_append_entry(&dict, "MaximumDelay", + DBUS_TYPE_UINT32, &qos->pd_max); + + g_dbus_dict_append_entry(&dict, "PreferredMinimumDelay", + DBUS_TYPE_UINT32, &qos->ppd_min); + + g_dbus_dict_append_entry(&dict, "PreferredMaximumDelay", + DBUS_TYPE_UINT32, &qos->ppd_min); + } + + dbus_message_iter_close_container(&iter, &dict); + + return media_endpoint_async_call(msg, endpoint, NULL, pac_select_cb, + data, free); +} + +struct pac_config_data { + struct bt_bap_stream *stream; + bt_bap_pac_config_t cb; + void *user_data; +}; + +static int transport_cmp(gconstpointer data, gconstpointer user_data) +{ + const struct media_transport *transport = data; + const char *path = user_data; + + if (g_str_has_prefix(media_transport_get_path((void *)transport), path)) + return 0; + + return -1; +} + +static struct media_transport *find_transport(struct media_endpoint *endpoint, + const char *path) +{ + GSList *match; + + if (!path) + return NULL; + + match = g_slist_find_custom(endpoint->transports, path, transport_cmp); + if (match == NULL) + return NULL; + + return match->data; +} + +static void pac_config_cb(struct media_endpoint *endpoint, void *ret, int size, + void *user_data) +{ + struct pac_config_data *data = user_data; + gboolean *ret_value = ret; + + if (ret_value) + endpoint->stream = data->stream; + + data->cb(data->stream, ret_value ? 0 : -EINVAL); +} + +static int pac_config(struct bt_bap_stream *stream, struct iovec *cfg, + struct bt_bap_qos *qos, bt_bap_pac_config_t cb, + void *user_data) +{ + struct media_endpoint *endpoint = user_data; + DBusConnection *conn = btd_get_dbus_connection(); + struct pac_config_data *data; + struct media_transport *transport; + DBusMessage *msg; + DBusMessageIter iter; + const char *path; + + path = bt_bap_stream_get_user_data(stream); + + DBG("endpoint %p path %s", endpoint, path); + + transport = find_transport(endpoint, path); + if (!transport) { + struct bt_bap *bap = bt_bap_stream_get_session(stream); + struct btd_service *service = bt_bap_get_user_data(bap); + struct btd_device *device; + + if (service) + device = btd_service_get_device(service); + else { + struct bt_att *att = bt_bap_get_att(bap); + int fd = bt_att_get_fd(att); + + device = btd_adapter_find_device_by_fd(fd); + } + + if (!device) { + error("Unable to find device"); + return -EINVAL; + } + + transport = media_transport_create(device, path, cfg->iov_base, + cfg->iov_len, endpoint, + stream); + if (!transport) + return -EINVAL; + + path = media_transport_get_path(transport); + bt_bap_stream_set_user_data(stream, (void *)path); + } + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SetConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + media_transport_destroy(transport); + return FALSE; + } + + data = new0(struct pac_config_data, 1); + data->stream = stream; + data->cb = cb; + data->user_data = user_data; + + endpoint->transports = g_slist_append(endpoint->transports, transport); + + dbus_message_iter_init_append(msg, &iter); + + path = media_transport_get_path(transport); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + + g_dbus_get_properties(conn, path, "org.bluez.MediaTransport1", &iter); + + return media_endpoint_async_call(msg, endpoint, transport, + pac_config_cb, data, free); +} + +static void pac_clear(struct bt_bap_stream *stream, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + endpoint->stream = NULL; + + while (endpoint->transports != NULL) + clear_configuration(endpoint, endpoint->transports->data); +} + +static struct bt_bap_pac_ops pac_ops = { + .select = pac_select, + .config = pac_config, + .clear = pac_clear, +}; + +static void bap_debug(const char *str, void *user_data) +{ + DBG("%s", str); +} + +static bool endpoint_init_pac(struct media_endpoint *endpoint, uint8_t type, + int *err) +{ + struct btd_gatt_database *database; + struct gatt_db *db; + struct iovec data; + char *name; + + if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { + warn("D-Bus experimental not enabled"); + *err = -ENOTSUP; + return false; + } + + database = btd_adapter_get_database(endpoint->adapter->btd_adapter); + if (!database) { + error("Adapter database not found"); + return false; + } + + if (!bap_print_cc(endpoint->capabilities, endpoint->size, bap_debug, + NULL)) { + error("Unable to parse endpoint capabilities"); + return false; + } + + db = btd_gatt_database_get_db(database); + + data.iov_base = endpoint->capabilities; + data.iov_len = endpoint->size; + + /* TODO: Add support for metadata */ + + if (asprintf(&name, "%s:%s", endpoint->sender, endpoint->path) < 0) { + error("Could not allocate name for pac %s:%s", + endpoint->sender, endpoint->path); + return false; + } + + endpoint->pac = bt_bap_add_pac(db, name, type, endpoint->codec, + &endpoint->qos, &data, NULL); + if (!endpoint->pac) { + error("Unable to create PAC"); + return false; + } + + bt_bap_pac_set_ops(endpoint->pac, &pac_ops, endpoint); + + DBG("PAC %s registered", name); + + free(name); + + return true; +} + +static bool endpoint_init_pac_sink(struct media_endpoint *endpoint, int *err) +{ + return endpoint_init_pac(endpoint, BT_BAP_SINK, err); +} + +static bool endpoint_init_pac_source(struct media_endpoint *endpoint, int *err) +{ + return endpoint_init_pac(endpoint, BT_BAP_SOURCE, err); } static bool endpoint_properties_exists(const char *uuid, @@ -781,24 +1245,55 @@ static bool endpoint_properties_get(const char *uuid, return true; } -static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter, +static bool endpoint_supported(void) +{ + return true; +} + +static bool experimental_endpoint_supported(void) +{ + return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL; +} + +static struct media_endpoint_init { + const char *uuid; + bool (*func)(struct media_endpoint *endpoint, int *err); + bool (*supported)(void); +} init_table[] = { + { A2DP_SOURCE_UUID, endpoint_init_a2dp_source, endpoint_supported }, + { A2DP_SINK_UUID, endpoint_init_a2dp_sink, endpoint_supported }, + { PAC_SINK_UUID, endpoint_init_pac_sink, + experimental_endpoint_supported }, + { PAC_SOURCE_UUID, endpoint_init_pac_source, + experimental_endpoint_supported }, +}; + +static struct media_endpoint * +media_endpoint_create(struct media_adapter *adapter, const char *sender, const char *path, const char *uuid, gboolean delay_reporting, uint8_t codec, + struct bt_bap_pac_qos *qos, uint8_t *capabilities, int size, int *err) { struct media_endpoint *endpoint; - gboolean succeeded; + struct media_endpoint_init *init; + size_t i; + bool succeeded = false; endpoint = g_new0(struct media_endpoint, 1); endpoint->sender = g_strdup(sender); endpoint->path = g_strdup(path); endpoint->uuid = g_strdup(uuid); endpoint->codec = codec; + endpoint->delay_reporting = delay_reporting; + + if (qos) + endpoint->qos = *qos; if (size > 0) { endpoint->capabilities = g_new(uint8_t, size); @@ -808,26 +1303,17 @@ static struct media_endpoint *media_endpoint_create(struct media_adapter *adapte endpoint->adapter = adapter; - if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) - succeeded = endpoint_init_a2dp_source(endpoint, - delay_reporting, err); - else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) - succeeded = endpoint_init_a2dp_sink(endpoint, - delay_reporting, err); - else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || - strcasecmp(uuid, HSP_AG_UUID) == 0) - succeeded = TRUE; - else if (strcasecmp(uuid, HFP_HS_UUID) == 0 || - strcasecmp(uuid, HSP_HS_UUID) == 0) - succeeded = TRUE; - else { - succeeded = FALSE; + for (i = 0; i < ARRAY_SIZE(init_table); i++) { + init = &init_table[i]; - if (err) - *err = -EINVAL; + if (!strcasecmp(init->uuid, uuid)) { + succeeded = init->func(endpoint, err); + break; + } } if (!succeeded) { + error("Unable initialize endpoint for UUID %s", uuid); media_endpoint_destroy(endpoint); return NULL; } @@ -853,6 +1339,7 @@ static struct media_endpoint *media_endpoint_create(struct media_adapter *adapte static int parse_properties(DBusMessageIter *props, const char **uuid, gboolean *delay_reporting, uint8_t *codec, + struct bt_bap_pac_qos *qos, uint8_t **capabilities, int *size) { gboolean has_uuid = FALSE; @@ -893,6 +1380,34 @@ static int parse_properties(DBusMessageIter *props, const char **uuid, dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, capabilities, size); + } else if (strcasecmp(key, "Framing") == 0) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + dbus_message_iter_get_basic(&value, &qos->framing); + } else if (strcasecmp(key, "PHY") == 0) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + dbus_message_iter_get_basic(&value, &qos->phy); + } else if (strcasecmp(key, "RTN") == 0) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + dbus_message_iter_get_basic(&value, &qos->rtn); + } else if (strcasecmp(key, "MinimumDelay") == 0) { + if (var != DBUS_TYPE_UINT16) + return -EINVAL; + dbus_message_iter_get_basic(&value, &qos->pd_min); + } else if (strcasecmp(key, "MaximumDelay") == 0) { + if (var != DBUS_TYPE_UINT16) + return -EINVAL; + dbus_message_iter_get_basic(&value, &qos->pd_max); + } else if (strcasecmp(key, "PreferredMinimumDelay") == 0) { + if (var != DBUS_TYPE_UINT16) + return -EINVAL; + dbus_message_iter_get_basic(&value, &qos->pd_min); + } else if (strcasecmp(key, "PreferredMaximumDelay") == 0) { + if (var != DBUS_TYPE_UINT16) + return -EINVAL; + dbus_message_iter_get_basic(&value, &qos->pd_max); } dbus_message_iter_next(props); @@ -908,7 +1423,8 @@ static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, DBusMessageIter args, props; const char *sender, *path, *uuid; gboolean delay_reporting = FALSE; - uint8_t codec; + uint8_t codec = 0; + struct bt_bap_pac_qos qos = {}; uint8_t *capabilities; int size = 0; int err; @@ -927,12 +1443,13 @@ static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); - if (parse_properties(&props, &uuid, &delay_reporting, &codec, + if (parse_properties(&props, &uuid, &delay_reporting, &codec, &qos, &capabilities, &size) < 0) return btd_error_invalid_args(msg); if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, - codec, capabilities, size, &err) == NULL) { + codec, &qos, capabilities, size, + &err) == NULL) { if (err == -EPROTONOSUPPORT) return btd_error_not_supported(msg); else @@ -1958,6 +2475,7 @@ static void app_register_endpoint(void *data, void *user_data) const char *uuid; gboolean delay_reporting = FALSE; uint8_t codec; + struct bt_bap_pac_qos qos; uint8_t *capabilities = NULL; int size = 0; DBusMessageIter iter, array; @@ -2002,9 +2520,60 @@ static void app_register_endpoint(void *data, void *user_data) dbus_message_iter_get_fixed_array(&array, &capabilities, &size); } + /* Parse QoS preferences */ + memset(&qos, 0, sizeof(qos)); + if (g_dbus_proxy_get_property(proxy, "Framing", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&iter, &qos.framing); + } + + if (g_dbus_proxy_get_property(proxy, "PHY", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&iter, &qos.phy); + } + + if (g_dbus_proxy_get_property(proxy, "Latency", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&iter, &qos.latency); + } + + if (g_dbus_proxy_get_property(proxy, "MinimumDelay", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&iter, &qos.pd_min); + } + + if (g_dbus_proxy_get_property(proxy, "MaximumDelay", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&iter, &qos.pd_max); + } + + if (g_dbus_proxy_get_property(proxy, "PreferredMinimumDelay", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&iter, &qos.ppd_min); + } + + if (g_dbus_proxy_get_property(proxy, "PreferredMaximumDelay", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&iter, &qos.ppd_min); + } + endpoint = media_endpoint_create(app->adapter, app->sender, path, uuid, - delay_reporting, codec, capabilities, - size, &app->err); + delay_reporting, codec, &qos, + capabilities, size, &app->err); if (!endpoint) { error("Unable to register endpoint %s:%s: %s", app->sender, path, strerror(-app->err)); @@ -2390,6 +2959,33 @@ static const GDBusMethodTable media_methods[] = { { }, }; +static gboolean supported_uuids(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + DBusMessageIter entry; + size_t i; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + + for (i = 0; i < ARRAY_SIZE(init_table); i++) { + struct media_endpoint_init *init = &init_table[i]; + + if (init->supported()) + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &init->uuid); + } + + dbus_message_iter_close_container(iter, &entry); + + return TRUE; +} + +static const GDBusPropertyTable media_properties[] = { + { "SupportedUUIDs", "as", supported_uuids }, + { } +}; + static void path_free(void *data) { struct media_adapter *adapter = data; @@ -2419,7 +3015,7 @@ int media_register(struct btd_adapter *btd_adapter) if (!g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(btd_adapter), MEDIA_INTERFACE, - media_methods, NULL, NULL, + media_methods, NULL, media_properties, adapter, path_free)) { error("D-Bus failed to register %s path", adapter_get_path(btd_adapter)); diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c index 5848e4019650..47db2a8026b2 100644 --- a/profiles/audio/transport.c +++ b/profiles/audio/transport.c @@ -23,6 +23,7 @@ #include "lib/uuid.h" #include "gdbus/gdbus.h" +#include "btio/btio.h" #include "src/adapter.h" #include "src/device.h" @@ -30,7 +31,9 @@ #include "src/log.h" #include "src/error.h" +#include "src/shared/util.h" #include "src/shared/queue.h" +#include "src/shared/bap.h" #include "avdtp.h" #include "media.h" @@ -76,6 +79,19 @@ struct a2dp_transport { int8_t volume; }; +struct bap_transport { + struct bt_bap_stream *stream; + unsigned int state_id; + bool linked; + uint32_t interval; + uint8_t framing; + uint8_t phy; + uint16_t sdu; + uint8_t rtn; + uint16_t latency; + uint32_t delay; +}; + struct media_transport { char *path; /* Transport object path */ struct btd_device *device; /* Transport device */ @@ -97,6 +113,8 @@ struct media_transport { struct media_owner *owner); void (*cancel) (struct media_transport *transport, guint id); + void (*set_state) (struct media_transport *transport, + transport_state_t state); GDestroyNotify destroy; void *data; }; @@ -134,6 +152,29 @@ static gboolean state_in_use(transport_state_t state) return FALSE; } +static struct media_transport * +find_transport_by_bap_stream(const struct bt_bap_stream *stream) +{ + GSList *l; + + for (l = transports; l; l = g_slist_next(l)) { + struct media_transport *transport = l->data; + const char *uuid = media_endpoint_get_uuid(transport->endpoint); + struct bap_transport *bap; + + if (strcasecmp(uuid, PAC_SINK_UUID) && + strcasecmp(uuid, PAC_SOURCE_UUID)) + continue; + + bap = transport->data; + + if (bap->stream == stream) + return transport; + } + + return NULL; +} + static void transport_set_state(struct media_transport *transport, transport_state_t state) { @@ -155,6 +196,10 @@ static void transport_set_state(struct media_transport *transport, transport->path, MEDIA_TRANSPORT_INTERFACE, "State"); + + /* Update transport specific data */ + if (transport->set_state) + transport->set_state(transport, state); } void media_transport_destroy(struct media_transport *transport) @@ -240,6 +285,9 @@ static void media_transport_remove_owner(struct media_transport *transport) { struct media_owner *owner = transport->owner; + if (!transport->owner) + return; + DBG("Transport %s Owner %s", transport->path, owner->name); /* Reply if owner has a pending request */ @@ -597,7 +645,8 @@ static gboolean get_state(const GDBusPropertyTable *property, return TRUE; } -static gboolean delay_exists(const GDBusPropertyTable *property, void *data) +static gboolean delay_reporting_exists(const GDBusPropertyTable *property, + void *data) { struct media_transport *transport = data; struct a2dp_transport *a2dp = transport->data; @@ -605,7 +654,7 @@ static gboolean delay_exists(const GDBusPropertyTable *property, void *data) return a2dp->delay != 0; } -static gboolean get_delay(const GDBusPropertyTable *property, +static gboolean get_delay_reporting(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; @@ -709,19 +758,181 @@ static const GDBusMethodTable transport_methods[] = { { }, }; -static const GDBusPropertyTable transport_properties[] = { +static const GDBusPropertyTable a2dp_properties[] = { { "Device", "o", get_device }, { "UUID", "s", get_uuid }, { "Codec", "y", get_codec }, { "Configuration", "ay", get_configuration }, { "State", "s", get_state }, - { "Delay", "q", get_delay, NULL, delay_exists }, + { "Delay", "q", get_delay_reporting, NULL, delay_reporting_exists }, { "Volume", "q", get_volume, set_volume, volume_exists }, { "Endpoint", "o", get_endpoint, NULL, endpoint_exists, G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, { } }; +static gboolean get_interval(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &bap->interval); + + return TRUE; +} + +static gboolean get_framing(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + dbus_bool_t val = bap->framing; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static gboolean get_sdu(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &bap->sdu); + + return TRUE; +} + +static gboolean get_retransmissions(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &bap->rtn); + + return TRUE; +} + +static gboolean get_latency(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &bap->latency); + + return TRUE; +} + +static gboolean get_delay(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &bap->delay); + + return TRUE; +} + +static gboolean get_location(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + uint32_t location = bt_bap_stream_get_location(bap->stream); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &location); + + return TRUE; +} + +static gboolean get_metadata(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + struct iovec *meta = bt_bap_stream_get_metadata(bap->stream); + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + if (meta) + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &meta->iov_base, + meta->iov_len); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean links_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + return bap->linked; +} + +static void append_links(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + DBusMessageIter *array = user_data; + struct media_transport *transport; + + transport = find_transport_by_bap_stream(stream); + if (!transport) { + error("Unable to find transport"); + return; + } + + dbus_message_iter_append_basic(array, DBUS_TYPE_OBJECT_PATH, + &transport->path); +} + +static gboolean get_links(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + struct queue *links = bt_bap_stream_io_get_links(bap->stream); + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, + &array); + + queue_foreach(links, append_links, &array); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static const GDBusPropertyTable bap_properties[] = { + { "Device", "o", get_device }, + { "UUID", "s", get_uuid }, + { "Codec", "y", get_codec }, + { "Configuration", "ay", get_configuration }, + { "State", "s", get_state }, + { "Interval", "u", get_interval }, + { "Framing", "b", get_framing }, + { "SDU", "q", get_sdu }, + { "Retransmissions", "y", get_retransmissions }, + { "Latency", "q", get_latency }, + { "Delay", "u", get_delay }, + { "Endpoint", "o", get_endpoint, NULL, endpoint_exists }, + { "Location", "u", get_location }, + { "Metadata", "ay", get_metadata }, + { "Links", "ao", get_links, NULL, links_exists }, + { } +}; + static void destroy_a2dp(void *data) { struct a2dp_transport *a2dp = data; @@ -842,15 +1053,337 @@ static int media_transport_init_sink(struct media_transport *transport) return 0; } +static void bap_enable_complete(struct bt_bap_stream *stream, + uint8_t code, uint8_t reason, + void *user_data) +{ + struct media_owner *owner = user_data; + + if (code) + media_transport_remove_owner(owner->transport); +} + +static gboolean resume_complete(void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner = transport->owner; + + if (!owner) + return FALSE; + + if (transport->fd < 0) { + media_transport_remove_owner(transport); + return FALSE; + } + + if (owner->pending) { + gboolean ret; + + ret = g_dbus_send_reply(btd_get_dbus_connection(), + owner->pending->msg, + DBUS_TYPE_UNIX_FD, &transport->fd, + DBUS_TYPE_UINT16, &transport->imtu, + DBUS_TYPE_UINT16, &transport->omtu, + DBUS_TYPE_INVALID); + if (!ret) { + media_transport_remove_owner(transport); + return FALSE; + } + } + + media_owner_remove(owner); + + transport_set_state(transport, TRANSPORT_STATE_ACTIVE); + + return FALSE; +} + +static void bap_update_links(const struct media_transport *transport); + +static bool match_link_transport(const void *data, const void *user_data) +{ + const struct bt_bap_stream *stream = data; + const struct media_transport *transport; + + transport = find_transport_by_bap_stream(stream); + if (!transport) + return false; + + bap_update_links(transport); + + return true; +} + +static void bap_update_links(const struct media_transport *transport) +{ + struct bap_transport *bap = transport->data; + struct queue *links = bt_bap_stream_io_get_links(bap->stream); + + if (bap->linked == !queue_isempty(links)) + return; + + bap->linked = !queue_isempty(links); + + /* Check if the links transport has been create yet */ + if (bap->linked && !queue_find(links, match_link_transport, NULL)) { + bap->linked = false; + return; + } + + g_dbus_emit_property_changed(btd_get_dbus_connection(), transport->path, + MEDIA_TRANSPORT_INTERFACE, + "Links"); + + DBG("stream %p linked %s", bap->stream, bap->linked ? "true" : "false"); +} + +static guint resume_bap(struct media_transport *transport, + struct media_owner *owner) +{ + struct bap_transport *bap = transport->data; + guint id; + + if (!bap->stream) + return 0; + + bap_update_links(transport); + + switch (bt_bap_stream_get_state(bap->stream)) { + case BT_BAP_STREAM_STATE_ENABLING: + bap_enable_complete(bap->stream, 0x00, 0x00, owner); + if (owner->pending) + return owner->pending->id; + return 0; + case BT_BAP_STREAM_STATE_STREAMING: + return g_idle_add(resume_complete, transport); + } + + id = bt_bap_stream_enable(bap->stream, bap->linked, NULL, + bap_enable_complete, owner); + if (!id) + return 0; + + if (transport->state == TRANSPORT_STATE_IDLE) + transport_set_state(transport, TRANSPORT_STATE_REQUESTING); + + return id; +} + +static void bap_stop_complete(struct bt_bap_stream *stream, + uint8_t code, uint8_t reason, + void *user_data) +{ + struct media_owner *owner = user_data; + struct media_request *req = owner->pending; + struct media_transport *transport = owner->transport; + + /* Release always succeeds */ + if (req) { + req->id = 0; + media_request_reply(req, 0); + media_owner_remove(owner); + } + + transport_set_state(transport, TRANSPORT_STATE_IDLE); + media_transport_remove_owner(transport); +} + +static void bap_disable_complete(struct bt_bap_stream *stream, + uint8_t code, uint8_t reason, + void *user_data) +{ + bap_stop_complete(stream, code, reason, user_data); +} + +static guint suspend_bap(struct media_transport *transport, + struct media_owner *owner) +{ + struct bap_transport *bap = transport->data; + bt_bap_stream_func_t func = NULL; + + if (!bap->stream) + return 0; + + if (owner) + func = bap_disable_complete; + else + transport_set_state(transport, TRANSPORT_STATE_IDLE); + + bap_update_links(transport); + + return bt_bap_stream_disable(bap->stream, bap->linked, func, owner); +} + +static void cancel_bap(struct media_transport *transport, guint id) +{ + struct bap_transport *bap = transport->data; + + if (!bap->stream) + return; + + bt_bap_stream_cancel(bap->stream, id); +} + +static void link_set_state(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + transport_state_t state = PTR_TO_UINT(user_data); + struct media_transport *transport; + + transport = find_transport_by_bap_stream(stream); + if (!transport) { + error("Unable to find transport"); + return; + } + + transport_set_state(transport, state); +} + +static void set_state_bap(struct media_transport *transport, + transport_state_t state) +{ + struct bap_transport *bap = transport->data; + + if (!bap->linked) + return; + + /* Update links */ + queue_foreach(bt_bap_stream_io_get_links(bap->stream), link_set_state, + UINT_TO_PTR(state)); +} + +static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, + uint8_t new_state, void *user_data) +{ + struct media_transport *transport = user_data; + struct bap_transport *bap = transport->data; + struct media_owner *owner = transport->owner; + struct io *io; + GIOChannel *chan; + GError *err = NULL; + int fd; + uint16_t imtu, omtu; + + if (bap->stream != stream) + return; + + DBG("stream %p: %s(%u) -> %s(%u)", stream, + bt_bap_stream_statestr(old_state), old_state, + bt_bap_stream_statestr(new_state), new_state); + + switch (new_state) { + case BT_BAP_STREAM_STATE_IDLE: + case BT_BAP_STREAM_STATE_CONFIG: + case BT_BAP_STREAM_STATE_QOS: + /* If a request is pending wait it to complete */ + if (owner && owner->pending) + return; + transport_update_playing(transport, FALSE); + return; + case BT_BAP_STREAM_STATE_DISABLING: + return; + case BT_BAP_STREAM_STATE_ENABLING: + if (!bt_bap_stream_get_io(stream)) + return; + break; + case BT_BAP_STREAM_STATE_STREAMING: + break; + } + + io = bt_bap_stream_get_io(stream); + if (!io) { + error("Unable to get stream IO"); + /* TODO: Fail if IO has not been established */ + goto done; + } + + fd = io_get_fd(io); + if (fd < 0) { + error("Unable to get IO fd"); + goto done; + } + + chan = g_io_channel_unix_new(fd); + + if (!bt_io_get(chan, &err, BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID)) { + error("%s", err->message); + goto done; + } + + g_io_channel_unref(chan); + + media_transport_set_fd(transport, fd, imtu, omtu); + transport_update_playing(transport, TRUE); + +done: + resume_complete(transport); +} + +static void bap_connecting(struct bt_bap_stream *stream, bool state, int fd, + void *user_data) +{ + struct media_transport *transport = user_data; + struct bap_transport *bap = transport->data; + + if (bap->stream != stream) + return; + + bap_update_links(transport); +} + +static void free_bap(void *data) +{ + struct bap_transport *bap = data; + + bt_bap_state_unregister(bt_bap_stream_get_session(bap->stream), + bap->state_id); + free(bap); +} + +static int media_transport_init_bap(struct media_transport *transport, + void *stream) +{ + struct bt_bap_qos *qos; + struct bap_transport *bap; + + qos = bt_bap_stream_get_qos(stream); + + bap = new0(struct bap_transport, 1); + bap->stream = stream; + bap->interval = qos->interval; + bap->framing = qos->framing; + bap->phy = qos->phy; + bap->rtn = qos->rtn; + bap->latency = qos->latency; + bap->delay = qos->delay; + bap->state_id = bt_bap_state_register(bt_bap_stream_get_session(stream), + bap_state_changed, + bap_connecting, + transport, NULL); + + transport->data = bap; + transport->resume = resume_bap; + transport->suspend = suspend_bap; + transport->cancel = cancel_bap; + transport->set_state = set_state_bap; + transport->destroy = free_bap; + + return 0; +} + struct media_transport *media_transport_create(struct btd_device *device, const char *remote_endpoint, uint8_t *configuration, - size_t size, void *data) + size_t size, void *data, + void *stream) { struct media_endpoint *endpoint = data; struct media_transport *transport; const char *uuid; static int fd = 0; + const GDBusPropertyTable *properties; transport = g_new0(struct media_transport, 1); transport->device = device; @@ -868,15 +1401,22 @@ struct media_transport *media_transport_create(struct btd_device *device, if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) { if (media_transport_init_source(transport) < 0) goto fail; + properties = a2dp_properties; } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) { if (media_transport_init_sink(transport) < 0) goto fail; + properties = a2dp_properties; + } else if (!strcasecmp(uuid, PAC_SINK_UUID) || + !strcasecmp(uuid, PAC_SOURCE_UUID)) { + if (media_transport_init_bap(transport, stream) < 0) + goto fail; + properties = bap_properties; } else goto fail; if (g_dbus_register_interface(btd_get_dbus_connection(), transport->path, MEDIA_TRANSPORT_INTERFACE, - transport_methods, NULL, transport_properties, + transport_methods, NULL, properties, transport, media_transport_free) == FALSE) { error("Could not register transport %s", transport->path); goto fail; diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h index 51a67ea74f46..102fc3cf1153 100644 --- a/profiles/audio/transport.h +++ b/profiles/audio/transport.h @@ -14,7 +14,8 @@ struct media_transport; struct media_transport *media_transport_create(struct btd_device *device, const char *remote_endpoint, uint8_t *configuration, - size_t size, void *data); + size_t size, void *data, + void *stream); void media_transport_destroy(struct media_transport *transport); const char *media_transport_get_path(struct media_transport *transport); From patchwork Sat Aug 27 00:05:34 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600617 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 7E343ECAAD5 for ; Sat, 27 Aug 2022 00:05:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238383AbiH0AFw (ORCPT ); Fri, 26 Aug 2022 20:05:52 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55230 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235359AbiH0AFv (ORCPT ); Fri, 26 Aug 2022 20:05:51 -0400 Received: from mail-pl1-x62d.google.com (mail-pl1-x62d.google.com [IPv6:2607:f8b0:4864:20::62d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B0083E42FA for ; Fri, 26 Aug 2022 17:05:50 -0700 (PDT) Received: by mail-pl1-x62d.google.com with SMTP id v23so2874489plo.9 for ; Fri, 26 Aug 2022 17:05:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=HcZbzWIaS3ZBAoKQ8WtTSxdAa3N97Uesw92CvXSMVJ0=; b=azaKRJgM4kyLUOEPJNdguDtTRRmqQcNzBbziPL3eYyQPWdgCNLSRORo+KsEQd16qk2 +3F4lDWBV9puq4mBrvP/tXUEnD3dR0nsJKk9YVYDuec1oeyR4CTCZJa5JVHK6tC9xKH6 sZQUCyU0uY44vn/mqY7TA9ZOU8c2dTBAwto54XejE35xLGcK2zgR1wBDxQCxMZJ6ngYH ojtLXuLzcl9PTJdNUhVR9RgOhVmTho+M8MKiv0EnJzoG0wwgo5hYfMHcbC3RWpFvMnmZ ln0Nw4GygE6x1lv2ckBg5DHRKkSeaAHxMBSk3IrS2loV0VjwjTQail7B9MmNEvRrUks9 xKSg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=HcZbzWIaS3ZBAoKQ8WtTSxdAa3N97Uesw92CvXSMVJ0=; b=MzvTOIh5CSEFr55PiATZk1es4Wz5nEAZnjr6OOIzMxd830IAscQtf0dcb9/kYYygbU 1TKNG5+RWEAEhFwnyGeeGxvVTsKEwMygs9QklE7k8eE7Vb+KPRX1vQYRA4DeYYw8MQ8y NRZ7eigxcWHgbbVgx71gzbzog+0ETF3vVqnAmrcvsXzuFnTfhf2U4fpl3IxfcduSiNGK FA+hyB2rjueHk65d8U1iXuVL2SSx47C/WxTmFJEWq3tkhnj16Ou58sbm7+S/KcFaP97G jgkQc0jdlhPKjPXszU8EQke7jfuYGU474Po81EyZME/COXl4Zj5zNrEXrUsLnwR+c7yr 0Zjg== X-Gm-Message-State: ACgBeo39gQ+iIsnFMYLFIR15qaErtuO4AAIr7NTMJdgBSiiXwh/ynWYV 13SRwyC8pr44IKkysYiJLJ8u3GpkxMk= X-Google-Smtp-Source: AA6agR7bstngbRbXWio/Hfhim+/aBsbsrHRPDWNUrhFIoli75V4r5eKkceFUUMXiBOXWygopdQv33A== X-Received: by 2002:a17:902:6b4c:b0:171:38ab:e762 with SMTP id g12-20020a1709026b4c00b0017138abe762mr5971286plt.42.1661558749618; Fri, 26 Aug 2022 17:05:49 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.48 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:49 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 05/11] shared: Add definition for LC3 codec Date: Fri, 26 Aug 2022 17:05:34 -0700 Message-Id: <20220827000540.113414-6-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds the definition for LC3 codec capabilities and configuration. --- Makefile.am | 2 +- src/shared/lc3.h | 112 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/shared/lc3.h diff --git a/Makefile.am b/Makefile.am index 92758ca55816..960bf21bc726 100644 --- a/Makefile.am +++ b/Makefile.am @@ -231,7 +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/tty.h + src/shared/lc3.h src/shared/tty.h if READLINE shared_sources += src/shared/shell.c src/shared/shell.h diff --git a/src/shared/lc3.h b/src/shared/lc3.h new file mode 100644 index 000000000000..33e8107e39e6 --- /dev/null +++ b/src/shared/lc3.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + */ + +#define LTV(_type, _bytes...) \ + { \ + .len = 1 + sizeof((uint8_t []) { _bytes }), \ + .type = _type, \ + .data = { _bytes }, \ + } + +#define LC3_ID 0x06 + +#define LC3_BASE 0x01 + +#define LC3_FREQ (LC3_BASE) +#define LC3_FREQ_8KHZ BIT(0) +#define LC3_FREQ_11KHZ BIT(1) +#define LC3_FREQ_16KHZ BIT(2) +#define LC3_FREQ_22KHZ BIT(3) +#define LC3_FREQ_24KHZ BIT(4) +#define LC3_FREQ_32KHZ BIT(5) +#define LC3_FREQ_44KHZ BIT(6) +#define LC3_FREQ_48KHZ BIT(7) +#define LC3_FREQ_ANY (LC3_FREQ_8KHZ | \ + LC3_FREQ_11KHZ | \ + LC3_FREQ_16KHZ | \ + LC3_FREQ_22KHZ | \ + LC3_FREQ_24KHZ | \ + LC3_FREQ_32KHZ | \ + LC3_FREQ_44KHZ | \ + LC3_FREQ_48KHZ) + +#define LC3_DURATION (LC3_BASE + 1) +#define LC3_DURATION_7_5 BIT(0) +#define LC3_DURATION_10 BIT(1) +#define LC3_DURATION_ANY (LC3_DURATION_7_5 | LC3_DURATION_10) +#define LC3_DURATION_PREFER_7_5 BIT(4) +#define LC3_DURATION_PREFER_10 BIT(5) + + +#define LC3_CHAN_COUNT (LC3_BASE + 2) +#define LC3_CHAN_COUNT_SUPPORT BIT(0) + +#define LC3_FRAME_LEN (LC3_BASE + 3) + +#define LC3_FRAME_COUNT (LC3_BASE + 4) + +#define LC3_CAPABILITIES(_freq, _duration, _chan_count, _len_min, _len_max) \ + { \ + LTV(LC3_FREQ, _freq), \ + LTV(LC3_DURATION, _duration), \ + LTV(LC3_CHAN_COUNT, _chan_count), \ + LTV(LC3_FRAME_LEN, _len_min, _len_min >> 8, \ + _len_max, _len_max >> 8), \ + } + +#define LC3_CONFIG_BASE 0x01 + +#define LC3_CONFIG_FREQ (LC3_CONFIG_BASE) +#define LC3_CONFIG_FREQ_8KHZ 0x01 +#define LC3_CONFIG_FREQ_11KHZ 0x02 +#define LC3_CONFIG_FREQ_16KHZ 0x03 +#define LC3_CONFIG_FREQ_22KHZ 0x04 +#define LC3_CONFIG_FREQ_24KHZ 0x05 +#define LC3_CONFIG_FREQ_32KHZ 0x06 +#define LC3_CONFIG_FREQ_44KHZ 0x07 +#define LC3_CONFIG_FREQ_48KHZ 0x08 + +#define LC3_CONFIG_DURATION (LC3_CONFIG_BASE + 1) +#define LC3_CONFIG_DURATION_7_5 0x00 +#define LC3_CONFIG_DURATION_10 0x01 + +#define LC3_CONFIG_CHAN_ALLOC (LC3_CONFIG_BASE + 2) + +#define LC3_CONFIG_FRAME_LEN (LC3_CONFIG_BASE + 3) + +#define LC3_CONFIG(_freq, _duration, _len) \ + { \ + LTV(LC3_CONFIG_FREQ, _freq), \ + LTV(LC3_CONFIG_DURATION, _duration), \ + LTV(LC3_CONFIG_FRAME_LEN, _len, _len >> 8), \ + } + +#define LC3_CONFIG_8KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_8KHZ, _duration, _len) + +#define LC3_CONFIG_11KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_11KHZ, _duration, _len) + +#define LC3_CONFIG_16KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_16KHZ, _duration, _len) + +#define LC3_CONFIG_22KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_22KHZ, _duration, _len) + +#define LC3_CONFIG_24KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_24KHZ, _duration, _len) + +#define LC3_CONFIG_32KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_32KHZ, _duration, _len) + +#define LC3_CONFIG_44KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_44KHZ, _duration, _len) + +#define LC3_CONFIG_48KHZ(_duration, _len) \ + LC3_CONFIG(LC3_CONFIG_FREQ_48KHZ, _duration, _len) From patchwork Sat Aug 27 00:05:35 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600616 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 65F27ECAAD4 for ; Sat, 27 Aug 2022 00:05:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1344959AbiH0AFy (ORCPT ); Fri, 26 Aug 2022 20:05:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55268 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241925AbiH0AFw (ORCPT ); Fri, 26 Aug 2022 20:05:52 -0400 Received: from mail-pl1-x62d.google.com (mail-pl1-x62d.google.com [IPv6:2607:f8b0:4864:20::62d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E3B5FE42FA for ; Fri, 26 Aug 2022 17:05:51 -0700 (PDT) Received: by mail-pl1-x62d.google.com with SMTP id v23so2874542plo.9 for ; Fri, 26 Aug 2022 17:05:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=LtLsDeU6W0S2tsIhTDpRd9Huw05LEotatD+3ogiUrJQ=; b=VGILEG4cZAI/7cdDCdUdSaz4jk+kvfvGlitmYfsK8O1e2FW05tF5Y1pizy3X221Ja5 q9iwhqwl+XDYv8Ajlw6b/m0DF1KoKJ0KOeJg78JM8TkwEFytZ2mYNxJw1G5JXosaTA6E viEPRYPUzVWuIFD4Fh/Dl6klY7WefXbwQVPC9x/myLMO5/vFn5v/N7XzVMuG3ZJrpVAv 0Mkd3DS8YBRDZchuYk/NkjaCOvBzy7Al5KKQwLZ+84UbRMAB1/+8SrsuRwIzU5pRKnrs Mi5DhwnrYWinCik+cMvayVt84CaFrquHV9kK/hub9WTC7sqcuGdgckTS0l9owUhMgTt/ 6Seg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=LtLsDeU6W0S2tsIhTDpRd9Huw05LEotatD+3ogiUrJQ=; b=Aw2Xyl/xFqbvZH6q3p5gqe3/Wxg8K6qqDZGdywHy6Lg8HCpy2mXdAeNAKFUh9GzWHB pM5Ywah6LI0Cp9nLpfzrwD1R2J8tsASy5b0OmEfgLhDAj5diZuh9zPmP2YIoJaaGTA5L Iz7esTu/KTwiLQij/KCDC8+D2ob9hKEJ7YjDjNk8C6/RwPUoSsEmFo0TouxZM/Ed6fVg A0PJW8jKQaoUBFRJ3tH/9K96K5VrBpwxLb8KGBYnsKg5G4UPgcqupRVrUX2s4b6kZdjL OxY8WvPd6/GoP+TRnbaw3fsXWsoUcZVhOGBOE7BRNgk846xD/CdBZJvHZHmFKbzrV8lz tM7g== X-Gm-Message-State: ACgBeo01qfSME1npjLqCZAdzprmEi8VGxvsEc9ney10FmQ6+SS5Wfji/ qK4ljl72hHeAXQRjAZ6b+IMKQyEerNw= X-Google-Smtp-Source: AA6agR7t8KQej81PXmEsrjNOHd0pVLUNqi0YUQ0mEZTvBgXoH+YsuPiI4ghEESZS7GSxEbRCq/YZ/Q== X-Received: by 2002:a17:90b:3d91:b0:1fb:49ed:a5c3 with SMTP id pq17-20020a17090b3d9100b001fb49eda5c3mr6727607pjb.187.1661558751038; Fri, 26 Aug 2022 17:05:51 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.49 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:50 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 06/11] media-api: Add SelectProperties Date: Fri, 26 Aug 2022 17:05:35 -0700 Message-Id: <20220827000540.113414-7-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds SelectProperties which is a more extensible version of SelectCapability since it takes a dictionary rather than a byte array and define new Properties for LE Audio. --- doc/media-api.txt | 88 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/doc/media-api.txt b/doc/media-api.txt index e98573157c60..9cd211355860 100644 --- a/doc/media-api.txt +++ b/doc/media-api.txt @@ -24,6 +24,9 @@ Methods void RegisterEndpoint(object endpoint, dict properties) UUID of the profile which the endpoint is for. + UUID must be in the list of + SupportedUUIDS. + byte Codec: Assigned number of codec that the @@ -87,6 +90,12 @@ Methods void RegisterEndpoint(object endpoint, dict properties) Possible errors: org.bluez.Error.InvalidArguments org.bluez.Error.DoesNotExist + +Properties array{string} SupportedUUIDs [readonly]: + + List of 128-bit UUIDs that represents the supported + Endpoint registration. + Media Control hierarchy ======================= @@ -564,7 +573,18 @@ Methods void SetConfiguration(object transport, dict properties) endpoint oject which will be configured and the properties must contain the following properties: - array{byte} Capabilities + array{byte} Capabilities [Mandatory] + array{byte} Metadata [ISO only] + byte CIG [ISO only] + byte CIS [ISO only] + uint32 Interval [ISO only] + bool Framing [ISO only] + string PHY [ISO only] + uint16 SDU [ISO only] + byte Retransmissions [ISO only] + uint16 Latency [ISO only] + uint32 Delay [ISO only] + uint8 TargetLatency [ISO Latency] array{byte} SelectConfiguration(array{byte} capabilities) @@ -578,6 +598,19 @@ Methods void SetConfiguration(object transport, dict properties) configuration since on success the configuration is send back as parameter of SetConfiguration. + dict SelectProperties(dict properties) + + Select preferable properties from the supported + properties. Refer to SetConfiguration for the list of + possible properties. + + Returns propeties which can be used to setup + a transport. + + Note: There is no need to cache the selected + properties since on success the configuration is + send back as parameter of SetConfiguration. + void ClearConfiguration(object transport) Clear transport configuration. @@ -613,6 +646,46 @@ Properties string UUID [readonly, optional]: Indicates if endpoint supports Delay Reporting. + byte Framing [ISO only] + + Indicates endpoint support framing. + + byte PHY [ISO only] + + Indicates endpoint supported PHY. + + uint16_t MaximumLatency [ISO only] + + Indicates endpoint maximum latency. + + uint32_t MinimumDelay [ISO only] + + Indicates endpoint minimum presentation delay. + + uint32_t MaximumDelay [ISO only] + + Indicates endpoint maximum presentation delay. + + uint32_t PreferredMinimumDelay [ISO only] + + Indicates endpoint preferred minimum presentation delay. + + uint32_t PreferredMinimumDelay [ISO only] + + Indicates endpoint preferred minimum presentation delay. + + uint32 Location [ISO only] + + Indicates endpoint supported locations. + + uint16 SupportedContext [ISO only] + + Indicates endpoint supported audio context. + + uint16 Context [ISO only] + + Indicates endpoint available audio context. + MediaTransport1 hierarchy ========================= @@ -689,3 +762,16 @@ Properties object Device [readonly] Endpoint object which the transport is associated with. + + uint32 Location [readonly, ISO only, experimental] + + Indicates transport Audio Location. + + array{byte} Metadata [ISO Only, experimental] + + Indicates transport Metadata. + + array{object} Links [readonly, optional, ISO only, experimental] + + Linked transport objects which the transport is + associated with. From patchwork Sat Aug 27 00:05:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600766 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 39D78C54EE9 for ; Sat, 27 Aug 2022 00:05:59 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345091AbiH0AF6 (ORCPT ); Fri, 26 Aug 2022 20:05:58 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55308 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231842AbiH0AFy (ORCPT ); Fri, 26 Aug 2022 20:05:54 -0400 Received: from mail-pf1-x435.google.com (mail-pf1-x435.google.com [IPv6:2607:f8b0:4864:20::435]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 357B7BCC05 for ; Fri, 26 Aug 2022 17:05:53 -0700 (PDT) Received: by mail-pf1-x435.google.com with SMTP id f17so2957195pfk.11 for ; Fri, 26 Aug 2022 17:05:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=vSA+HPWsuSD7qUqFUVcG0/msermoFukwlhbj3ey3Egc=; b=brJ9wZQMzMGT++H3iR6lGrIw9skW3+Qm8vLFeSuXyb0749Ni+gokHPfR+okMd3YRq7 rA6E2IgJ8F+HCc0PzxctYKRb1yMXBz3QO8lOuMmuuMEXZNtTv0uufPn+O2ZJgPXm9IfU Kb1GMYL5dYxKeILO6QLRHO4ZZ5ACHdUU0DHdcakZfuToOAVSK25oml6YEH+5UJkdaJBV a7kKQjb8LZWFsTzm0YSroqpMg6WFfp9gKdvhsES7hWBeYz9BamqDyC5VY38wasSMCNG7 H53RgAl04yc3T8kBEc5lurflYOnvDHa8LfeZRGuzXQADBAmXnc9cC6x9heriSIBAFIgP Gc1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=vSA+HPWsuSD7qUqFUVcG0/msermoFukwlhbj3ey3Egc=; b=fsEnMmI31RzlDsphqP2x2WRvNflM1PMzomy+pxIw2VLYmr4kXqxoNTiGNzc1lvTYBr yu1XJ7SvSNPdn2v5EqZ0lg0WchJZU8S9xR6v12SYI0kT5d9VNoLM1v4gayRaFOAQrvDM 7sVqCna5dgda3vjwqLRWrXTrnM42XgjOc4EYWeREWWLe4GHkRqCGoze09Uh242RCcDyj tekCGfY0ASNpTuHQQAHCOcrnGk2VyjftlehSXwxIADVGlMayiqNUbP8AMnBBNhZXszrN N8EP/1fDbsOqyB7rYsz0yxEJeBjzlXQnu2QGF9wzb3v2Q31YKMsB+gJ7A/32EdB2N6C0 lRSw== X-Gm-Message-State: ACgBeo0lNzdGZENriJKGr/tUVhhr+daeaZh430l3Hyb0G+2bH78jG7J5 WH66wH1PFFPsu0DsrgWbWoEd46HzUlc= X-Google-Smtp-Source: AA6agR64oOsT+zXrYp1QGDpmqWK4fJb6T8TKhQ7+jZz7YYDikDshlKqXIMdm3dVoAkpgOiQ07nftbg== X-Received: by 2002:a05:6a00:b52:b0:537:232f:9061 with SMTP id p18-20020a056a000b5200b00537232f9061mr6158054pfo.22.1661558752190; Fri, 26 Aug 2022 17:05:52 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.51 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:51 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 07/11] test/simple-endpoint: Add support for LC3 endpoints Date: Fri, 26 Aug 2022 17:05:36 -0700 Message-Id: <20220827000540.113414-8-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds support for LC3 sink/source endpoints. --- test/simple-endpoint | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/simple-endpoint b/test/simple-endpoint index 59ca189ce50e..463f124d1b6e 100755 --- a/test/simple-endpoint +++ b/test/simple-endpoint @@ -18,6 +18,8 @@ A2DP_SINK_UUID = "0000110B-0000-1000-8000-00805F9B34FB" HFP_AG_UUID = "0000111F-0000-1000-8000-00805F9B34FB" HFP_HF_UUID = "0000111E-0000-1000-8000-00805F9B34FB" HSP_AG_UUID = "00001112-0000-1000-8000-00805F9B34FB" +PAC_SINK_UUID = "00008f96-0000-1000-8000-00805F9B34FB" +PAC_SOURCE_UUID = "00008f98-0000-1000-8000-00805F9B34FB" SBC_CODEC = dbus.Byte(0x00) #Channel Modes: Mono DualChannel Stereo JointStereo @@ -41,6 +43,11 @@ MP3_CAPABILITIES = dbus.Array([dbus.Byte(0x3f), dbus.Byte(0x07), dbus.Byte(0xff) # JointStereo 44.1Khz Layer: 3 Bit Rate: VBR Format: RFC-2250 MP3_CONFIGURATION = dbus.Array([dbus.Byte(0x21), dbus.Byte(0x02), dbus.Byte(0x00), dbus.Byte(0x80)]) +LC3_CODEC = dbus.Byte(0x06) +#Bits per sample: 16 +#Bit Rate: 96kbps +LC3_CAPABILITIES = dbus.Array([dbus.Byte(16), dbus.Byte(96)]) + PCM_CODEC = dbus.Byte(0x00) PCM_CONFIGURATION = dbus.Array([], signature="ay") @@ -131,6 +138,16 @@ if __name__ == '__main__': "Codec" : CVSD_CODEC, "Capabilities" : PCM_CONFIGURATION }) endpoint.default_configuration(dbus.Array([])) + if sys.argv[2] == "lc3sink": + properties = dbus.Dictionary({ "UUID" : PAC_SINK_UUID, + "Codec" : LC3_CODEC, + "Capabilities" : + LC3_CAPABILITIES }) + if sys.argv[2] == "lc3source": + properties = dbus.Dictionary({ "UUID" : PAC_SOURCE_UUID, + "Codec" : LC3_CODEC, + "Capabilities" : + LC3_CAPABILITIES }) print(properties) From patchwork Sat Aug 27 00:05:37 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600765 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 31EE8C0502A for ; Sat, 27 Aug 2022 00:06:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345132AbiH0AF7 (ORCPT ); Fri, 26 Aug 2022 20:05:59 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55348 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1344716AbiH0AF4 (ORCPT ); Fri, 26 Aug 2022 20:05:56 -0400 Received: from mail-pl1-x62d.google.com (mail-pl1-x62d.google.com [IPv6:2607:f8b0:4864:20::62d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A2A53E68D9 for ; Fri, 26 Aug 2022 17:05:54 -0700 (PDT) Received: by mail-pl1-x62d.google.com with SMTP id f12so2721825plb.11 for ; Fri, 26 Aug 2022 17:05:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=wxssbqhgwnyyI4qNQteNJ+xFtAj5pt6svn+tuRUJqHc=; b=VcKMJZtw7i2DlkphGsb2DgtL4K2Tp2h6c6rmnCn7RGeM3BOTlGhQ0dD5pQ5F6V+fBx 1255kSmJt4MBdTzQFOPMDGoOd077qkK2oY4rV/XvQC7tc5Xarlsb/SjVK55pCcOJlT6K /NMaM+2C3CcbJrihlW+v07wl8wFy+gFVVMbjeVoGD4iaHucCiYkQove0qI2BYOluXMqN QfQJXABWTiLGuHOONN2aruECxz2LUpuf89jUu9lCHWiCznNBdLYgTWFuL9M7feUWUfd5 s5apaOzpY4cVfN1e3FvX05WQd+dr9plE35zUGYA3KJTcVwdZpS3+Pcg4hw9cchEqs3IK vtWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=wxssbqhgwnyyI4qNQteNJ+xFtAj5pt6svn+tuRUJqHc=; b=keJmJy/Hb1dPc4F7zoIOS93jRAw5HY115qi377zQxm1Fvu3//aoInRGKTzzP/ILCX9 KF3q5DKBUowcWOGZzELL9DNrMqHNYLMlgEiuFRbi09lfNLFFe5Vr+ueD2GRuJiH7o9Xj EX5KNHhT+osNP3Yb/JwG4uRIBE4DlmeXyQMqNq84TwoYSZWFi8ENhr2y5ojjXJ1uyQ3i Cm23vz6m0O9htq6NbnlzWC+ici09li730WmJlJGfQL/1CU3JDxwOKYnVYAUDqZ5v2G6G XhKUfSyS8TFxdHq8uZTiumfBWBkhUy/SCaGI9HGanVVl/Ro/m0vm5OsT7Rs/dJn52iSf fgxw== X-Gm-Message-State: ACgBeo1Bif+1VuNaJqEBczQtbKzR+h1R6bR+nMZ9CVzswByHKttQ2iJO Y6GFHx+r8x7MYMMVtLz3f6It1YPC7Yg= X-Google-Smtp-Source: AA6agR5V7Jzjt2DuNta9k2aQzygQ+65xnsfbFGB+CFYDzENp+f9oYI371PNYC++lD1HY+UV9NmljVA== X-Received: by 2002:a17:90b:19d8:b0:1fb:c116:a1e8 with SMTP id nm24-20020a17090b19d800b001fbc116a1e8mr6785252pjb.109.1661558753325; Fri, 26 Aug 2022 17:05:53 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.52 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:52 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 08/11] client/player: Add support for PACS endpoints Date: Fri, 26 Aug 2022 17:05:37 -0700 Message-Id: <20220827000540.113414-9-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This adds support for PAC_SINK and PAC_SOURCE endpoints as well as LC3 presets. --- client/player.c | 619 +++++++++++++++++++++++++++++++++------ tools/bluetooth-player.c | 2 +- 2 files changed, 532 insertions(+), 89 deletions(-) diff --git a/client/player.c b/client/player.c index 0c59db648ff1..4ba1a72ecfd5 100644 --- a/client/player.c +++ b/client/player.c @@ -33,6 +33,7 @@ #include "lib/uuid.h" #include "profiles/audio/a2dp-codecs.h" +#include "src/shared/lc3.h" #include "src/shared/util.h" #include "src/shared/shell.h" @@ -64,6 +65,8 @@ struct endpoint { uint8_t codec; struct iovec *caps; bool auto_accept; + uint8_t cig; + uint8_t cis; char *transport; DBusMessage *msg; }; @@ -1148,7 +1151,7 @@ struct codec_capabilities { #define data(args...) ((const unsigned char[]) { args }) -#define SBC_DATA(args...) \ +#define CODEC_DATA(args...) \ { \ .iov_base = (void *)data(args), \ .iov_len = sizeof(data(args)), \ @@ -1161,6 +1164,13 @@ struct codec_capabilities { .data = _data, \ } +#define LC3_DATA(_freq, _duration, _chan_count, _len_min, _len_max) \ + CODEC_DATA(0x03, LC3_FREQ, _freq, _freq >> 8, \ + 0x02, LC3_DURATION, _duration, \ + 0x02, LC3_CHAN_COUNT, _chan_count, \ + 0x05, LC3_FRAME_LEN, _len_min, _len_min >> 8, _len_max, \ + _len_max >> 8) + static const struct capabilities { const char *uuid; uint8_t codec_id; @@ -1175,7 +1185,7 @@ static const struct capabilities { * Bitpool Range: 2-64 */ CODEC_CAPABILITIES(A2DP_SOURCE_UUID, A2DP_CODEC_SBC, - SBC_DATA(0xff, 0xff, 2, 64)), + CODEC_DATA(0xff, 0xff, 2, 64)), /* A2DP SBC Sink: * * Channel Modes: Mono DualChannel Stereo JointStereo @@ -1185,13 +1195,45 @@ static const struct capabilities { * Bitpool Range: 2-64 */ CODEC_CAPABILITIES(A2DP_SINK_UUID, A2DP_CODEC_SBC, - SBC_DATA(0xff, 0xff, 2, 64)), + CODEC_DATA(0xff, 0xff, 2, 64)), + /* PAC LC3 Sink: + * + * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz + * Duration: 7.5 ms 10 ms + * Channel count: 3 + * Frame length: 30-240 + */ + CODEC_CAPABILITIES(PAC_SINK_UUID, LC3_ID, + LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, + 3u, 30, 240)), + /* PAC LC3 Source: + * + * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz + * Duration: 7.5 ms 10 ms + * Channel count: 3 + * Frame length: 30-240 + */ + CODEC_CAPABILITIES(PAC_SOURCE_UUID, LC3_ID, + LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, + 3u, 30, 240)), +}; + +struct codec_qos { + uint32_t interval; + uint8_t framing; + char *phy; + uint16_t sdu; + uint8_t rtn; + uint16_t latency; + uint32_t delay; }; struct codec_preset { const char *name; const struct iovec data; + const struct codec_qos qos; bool is_default; + uint8_t latency; }; #define SBC_PRESET(_name, _data) \ @@ -1216,32 +1258,212 @@ static struct codec_preset sbc_presets[] = { * mono, and 512kb/s for two-channel modes. */ SBC_PRESET("MQ_MONO_44_1", - SBC_DATA(0x28, 0x15, 2, SBC_BITPOOL_MQ_MONO_44100)), + CODEC_DATA(0x28, 0x15, 2, SBC_BITPOOL_MQ_MONO_44100)), SBC_PRESET("MQ_MONO_48", - SBC_DATA(0x18, 0x15, 2, SBC_BITPOOL_MQ_MONO_48000)), + CODEC_DATA(0x18, 0x15, 2, SBC_BITPOOL_MQ_MONO_48000)), SBC_PRESET("MQ_STEREO_44_1", - SBC_DATA(0x21, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_44100)), + CODEC_DATA(0x21, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_44100)), SBC_PRESET("MQ_STEREO_48", - SBC_DATA(0x11, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_48000)), + CODEC_DATA(0x11, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_48000)), SBC_PRESET("HQ_MONO_44_1", - SBC_DATA(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)), + CODEC_DATA(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)), SBC_PRESET("HQ_MONO_48", - SBC_DATA(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)), + CODEC_DATA(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)), SBC_DEFAULT_PRESET("HQ_STEREO_44_1", - SBC_DATA(0x21, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_44100)), + CODEC_DATA(0x21, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_44100)), SBC_PRESET("HQ_STEREO_48", - SBC_DATA(0x11, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_48000)), + CODEC_DATA(0x11, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_48000)), /* Higher bitrates not recommended by A2DP spec, it dual channel to * avoid going above 53 bitpool: * * https://habr.com/en/post/456476/ * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/1092 */ - SBC_PRESET("XQ_DUAL_44_1", SBC_DATA(0x24, 0x15, 2, 43)), - SBC_PRESET("XQ_DUAL_48", SBC_DATA(0x14, 0x15, 2, 39)), + SBC_PRESET("XQ_DUAL_44_1", CODEC_DATA(0x24, 0x15, 2, 43)), + SBC_PRESET("XQ_DUAL_48", CODEC_DATA(0x14, 0x15, 2, 39)), /* Ultra high bitpool that fits in 512 kbps mandatory bitrate */ - SBC_PRESET("UQ_STEREO_44_1", SBC_DATA(0x21, 0x15, 2, 64)), - SBC_PRESET("UQ_STEREO_48", SBC_DATA(0x11, 0x15, 2, 58)), + SBC_PRESET("UQ_STEREO_44_1", CODEC_DATA(0x21, 0x15, 2, 64)), + SBC_PRESET("UQ_STEREO_48", CODEC_DATA(0x11, 0x15, 2, 58)), +}; + +#define QOS_CONFIG(_interval, _framing, _phy, _sdu, _rtn, _latency, _delay) \ + { \ + .interval = _interval, \ + .framing = _framing, \ + .phy = _phy, \ + .sdu = _sdu, \ + .rtn = _rtn, \ + .latency = _latency, \ + .delay = _delay, \ + } + +#define QOS_UNFRAMED(_interval, _phy, _sdu, _rtn, _latency, _delay) \ + QOS_CONFIG(_interval, 0x00, _phy, _sdu, _rtn, _latency, _delay) + +#define QOS_FRAMED(_interval, _phy, _sdu, _rtn, _latency, _delay) \ + QOS_CONFIG(_interval, 0x01, _phy, _sdu, _rtn, _latency, _delay) + +#define QOS_UNFRAMED_1M(_interval, _sdu, _rtn, _latency, _delay) \ + QOS_UNFRAMED(_interval, "1M", _sdu, _rtn, _latency, _delay) \ + +#define QOS_FRAMED_1M(_interval, _sdu, _rtn, _latency, _delay) \ + QOS_FRAMED(_interval, "1M", _sdu, _rtn, _latency, _delay) \ + +#define QOS_UNFRAMED_2M(_interval, _sdu, _rtn, _latency, _delay) \ + QOS_UNFRAMED(_interval, "2M", _sdu, _rtn, _latency, _delay) \ + +#define QOS_FRAMED_2M(_interval, _sdu, _rtn, _latency, _delay) \ + QOS_FRAMED(_interval, "2M", _sdu, _rtn, _latency, _delay) \ + +#define LC3_7_5_UNFRAMED(_sdu, _rtn, _latency, _delay) \ + QOS_UNFRAMED(7500u, "2M", _sdu, _rtn, _latency, _delay) + +#define LC3_7_5_FRAMED(_sdu, _rtn, _latency, _delay) \ + QOS_FRAMED(7500u, "2M", _sdu, _rtn, _latency, _delay) + +#define LC3_10_UNFRAMED(_sdu, _rtn, _latency, _delay) \ + QOS_UNFRAMED_2M(10000u, _sdu, _rtn, _latency, _delay) + +#define LC3_10_FRAMED(_sdu, _rtn, _latency, _delay) \ + QOS_FRAMED_2M(10000u, _sdu, _rtn, _latency, _delay) + +#define LC3_PRESET_DATA(_freq, _duration, _len) \ + CODEC_DATA(0x02, LC3_CONFIG_FREQ, _freq, \ + 0x02, LC3_CONFIG_DURATION, _duration, \ + 0x03, LC3_CONFIG_FRAME_LEN, _len, _len >> 8) + +#define LC3_PRESET_8KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_8KHZ, _duration, _len) + +#define LC3_PRESET_11KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_11KHZ, _duration, _len) + +#define LC3_PRESET_16KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_16KHZ, _duration, _len) + +#define LC3_PRESET_22KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_22KHZ, _duration, _len) + +#define LC3_PRESET_24KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_24KHZ, _duration, _len) + +#define LC3_PRESET_32KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_32KHZ, _duration, _len) + +#define LC3_PRESET_44KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_44KHZ, _duration, _len) + +#define LC3_PRESET_48KHZ(_duration, _len) \ + LC3_PRESET_DATA(LC3_CONFIG_FREQ_48KHZ, _duration, _len) + +#define LC3_PRESET_LL(_name, _data, _qos) \ + { \ + .name = _name, \ + .data = _data, \ + .qos = _qos, \ + .latency = 0x01, \ + } + +#define LC3_PRESET(_name, _data, _qos) \ + { \ + .name = _name, \ + .data = _data, \ + .qos = _qos, \ + .latency = 0x02, \ + } + +#define LC3_PRESET_HR(_name, _data, _qos) \ + { \ + .name = _name, \ + .data = _data, \ + .qos = _qos, \ + .latency = 0x03, \ + } + +#define LC3_DEFAULT_PRESET(_name, _data, _qos) \ + { \ + .name = _name, \ + .data = _data, \ + .is_default = true, \ + .qos = _qos, \ + .latency = 0x02, \ + } + +static struct codec_preset lc3_presets[] = { + /* Table 4.43: QoS configuration support setting requirements */ + LC3_PRESET("8_1_1", + LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_7_5, 26u), + LC3_7_5_UNFRAMED(26u, 2u, 8u, 40000u)), + LC3_PRESET("8_2_1", + LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_10, 30u), + LC3_10_UNFRAMED(30u, 2u, 10u, 40000u)), + LC3_PRESET("16_1_1", + LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_7_5, 30u), + LC3_7_5_UNFRAMED(30u, 2u, 8u, 40000u)), + LC3_DEFAULT_PRESET("16_2_1", + LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_10, 40u), + LC3_10_UNFRAMED(40u, 2u, 10u, 40000u)), + LC3_PRESET("24_1_1", + LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_7_5, 45u), + LC3_7_5_UNFRAMED(45u, 2u, 8u, 40000u)), + LC3_PRESET("24_2_1", + LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_10, 60u), + LC3_10_UNFRAMED(60u, 2u, 10u, 40000u)), + LC3_PRESET("32_1_1", + LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_7_5, 60u), + LC3_7_5_UNFRAMED(60u, 2u, 8u, 40000u)), + LC3_PRESET("32_2_1", + LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_10, 80u), + LC3_10_UNFRAMED(80u, 2u, 10u, 40000u)), + LC3_PRESET("44_1_1", + LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_7_5, 98u), + QOS_FRAMED_2M(8163u, 98u, 5u, 24u, 40000u)), + LC3_PRESET("44_2_1", + LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_10, 130u), + QOS_FRAMED_2M(10884u, 130u, 5u, 31u, 40000u)), + LC3_PRESET("48_1_1", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 75u), + LC3_7_5_UNFRAMED(75u, 5u, 15u, 40000u)), + LC3_PRESET("48_2_1", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 100u), + LC3_10_UNFRAMED(100u, 5u, 20u, 40000u)), + LC3_PRESET("48_3_1", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 90u), + LC3_7_5_UNFRAMED(90u, 5u, 15u, 40000u)), + LC3_PRESET("48_4_1", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 120u), + LC3_10_UNFRAMED(120u, 5u, 20u, 40000u)), + LC3_PRESET("48_5_1", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 117u), + LC3_7_5_UNFRAMED(117u, 5u, 15u, 40000u)), + LC3_PRESET("48_6_1", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 155u), + LC3_10_UNFRAMED(155u, 5u, 20u, 40000u)), + /* QoS Configuration settings for high reliability audio data */ + LC3_PRESET_HR("44_1_2", + LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_7_5, 98u), + QOS_FRAMED_2M(8163u, 98u, 23u, 54u, 40000u)), + LC3_PRESET_HR("44_2_2", + LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_10, 130u), + QOS_FRAMED_2M(10884u, 130u, 23u, 71u, 40000u)), + LC3_PRESET_HR("48_1_2", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 75u), + LC3_7_5_UNFRAMED(75u, 23u, 45u, 40000u)), + LC3_PRESET_HR("48_2_2", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 100u), + LC3_10_UNFRAMED(100u, 23u, 60u, 40000u)), + LC3_PRESET_HR("48_3_2", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 90u), + LC3_7_5_UNFRAMED(90u, 23u, 45u, 40000u)), + LC3_PRESET_HR("48_4_2", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 120u), + LC3_10_UNFRAMED(120u, 23u, 60u, 40000u)), + LC3_PRESET_HR("48_5_2", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 117u), + LC3_7_5_UNFRAMED(117u, 23u, 45u, 40000u)), + LC3_PRESET_HR("48_6_2", + LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 155u), + LC3_10_UNFRAMED(155u, 23u, 60u, 40000u)), }; #define PRESET(_uuid, _presets) \ @@ -1258,6 +1480,8 @@ static const struct preset { } presets[] = { PRESET(A2DP_SOURCE_UUID, sbc_presets), PRESET(A2DP_SINK_UUID, sbc_presets), + PRESET(PAC_SINK_UUID, lc3_presets), + PRESET(PAC_SOURCE_UUID, lc3_presets), }; static struct codec_preset *find_preset(const char *uuid, const char *name) @@ -1400,10 +1624,7 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, } p = find_preset(ep->uuid, NULL); - if (!p) - NULL; - - if (p->data.iov_base) { + if (!p) { reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected", NULL); return reply; @@ -1419,6 +1640,190 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, return reply; } +struct endpoint_config { + GDBusProxy *proxy; + struct endpoint *ep; + struct iovec *caps; + uint8_t target_latency; + const struct codec_qos *qos; +}; + +static void append_properties(DBusMessageIter *iter, + struct endpoint_config *cfg) +{ + DBusMessageIter dict; + struct codec_qos *qos = (void *)cfg->qos; + const char *key = "Capabilities"; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + + bt_shell_printf("Capabilities: "); + bt_shell_hexdump(cfg->caps->iov_base, cfg->caps->iov_len); + + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, + DBUS_TYPE_BYTE, &cfg->caps->iov_base, + cfg->caps->iov_len); + + if (!qos) + goto done; + + if (cfg->target_latency) { + bt_shell_printf("TargetLatency 0x%02x\n", qos->interval); + g_dbus_dict_append_entry(&dict, "TargetLatency", + DBUS_TYPE_BYTE, &cfg->target_latency); + } + + if (cfg->ep->cig != BT_ISO_QOS_CIG_UNSET) { + bt_shell_printf("CIG 0x%2.2x\n", cfg->ep->cig); + g_dbus_dict_append_entry(&dict, "CIG", DBUS_TYPE_BYTE, + &cfg->ep->cig); + } + + if (cfg->ep->cis != BT_ISO_QOS_CIS_UNSET) { + bt_shell_printf("CIS 0x%2.2x\n", cfg->ep->cis); + g_dbus_dict_append_entry(&dict, "CIS", DBUS_TYPE_BYTE, + &cfg->ep->cis); + } + + bt_shell_printf("Interval %u\n", qos->interval); + + g_dbus_dict_append_entry(&dict, "Interval", DBUS_TYPE_UINT32, + &qos->interval); + + bt_shell_printf("Framing %s\n", qos->framing ? "true" : "false"); + + g_dbus_dict_append_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN, + &qos->framing); + + bt_shell_printf("PHY %s\n", qos->phy); + + g_dbus_dict_append_entry(&dict, "PHY", DBUS_TYPE_STRING, &qos->phy); + + bt_shell_printf("SDU %u\n", cfg->qos->sdu); + + g_dbus_dict_append_entry(&dict, "SDU", DBUS_TYPE_UINT16, &qos->sdu); + + bt_shell_printf("Retransmissions %u\n", qos->rtn); + + g_dbus_dict_append_entry(&dict, "Retransmissions", DBUS_TYPE_BYTE, + &qos->rtn); + + bt_shell_printf("Latency %u\n", qos->latency); + + g_dbus_dict_append_entry(&dict, "Latency", DBUS_TYPE_UINT16, + &qos->latency); + + bt_shell_printf("Delay %u\n", qos->delay); + + g_dbus_dict_append_entry(&dict, "Delay", DBUS_TYPE_UINT32, + &qos->delay); + +done: + dbus_message_iter_close_container(iter, &dict); +} + +static struct iovec *iov_append(struct iovec **iov, const void *data, + size_t len) +{ + if (!*iov) { + *iov = new0(struct iovec, 1); + (*iov)->iov_base = new0(uint8_t, UINT8_MAX); + } + + if (data && len) { + memcpy((*iov)->iov_base + (*iov)->iov_len, data, len); + (*iov)->iov_len += len; + } + + return *iov; +} + +static DBusMessage *endpoint_select_properties_reply(struct endpoint *ep, + DBusMessage *msg, + struct codec_preset *preset) +{ + DBusMessage *reply; + DBusMessageIter iter; + struct endpoint_config *cfg; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + cfg = new0(struct endpoint_config, 1); + cfg->ep = ep; + + /* Copy capabilities */ + iov_append(&cfg->caps, preset->data.iov_base, preset->data.iov_len); + cfg->target_latency = preset->latency; + + if (preset->qos.phy) + /* Set QoS parameters */ + cfg->qos = &preset->qos; + + dbus_message_iter_init_append(reply, &iter); + + append_properties(&iter, cfg); + + free(cfg); + + return reply; +} + +static void select_properties_response(const char *input, void *user_data) +{ + struct endpoint *ep = user_data; + struct codec_preset *p; + DBusMessage *reply; + + p = find_preset(ep->uuid, input); + if (p) { + reply = endpoint_select_properties_reply(ep, ep->msg, p); + goto done; + } + + bt_shell_printf("Preset %s not found\n", input); + reply = g_dbus_create_error(ep->msg, "org.bluez.Error.Rejected", NULL); + +done: + g_dbus_send_message(dbus_conn, reply); + dbus_message_unref(ep->msg); + ep->msg = NULL; +} + +static DBusMessage *endpoint_select_properties(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct endpoint *ep = user_data; + struct codec_preset *p; + DBusMessageIter args; + DBusMessage *reply; + + dbus_message_iter_init(msg, &args); + + bt_shell_printf("Endpoint: SelectProperties\n"); + print_iter("\t", "Properties", &args); + + if (!ep->auto_accept) { + ep->msg = dbus_message_ref(msg); + bt_shell_prompt_input("Endpoint", "Enter preset/configuration:", + select_properties_response, ep); + return NULL; + } + + p = find_preset(ep->uuid, NULL); + if (!p) + NULL; + + reply = endpoint_select_properties_reply(ep, msg, p); + if (!reply) + return NULL; + + bt_shell_printf("Auto Accepting using %s...\n", p->name); + + return reply; +} + static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessage *msg, void *user_data) { @@ -1478,7 +1883,12 @@ static const GDBusMethodTable endpoint_methods[] = { NULL, endpoint_set_configuration) }, { GDBUS_ASYNC_METHOD("SelectConfiguration", GDBUS_ARGS({ "caps", "ay" } ), - NULL, endpoint_select_configuration) }, + GDBUS_ARGS({ "cfg", "ay" } ), + endpoint_select_configuration) }, + { GDBUS_ASYNC_METHOD("SelectProperties", + GDBUS_ARGS({ "properties", "a{sv}" } ), + GDBUS_ARGS({ "properties", "a{sv}" } ), + endpoint_select_properties) }, { GDBUS_ASYNC_METHOD("ClearConfiguration", GDBUS_ARGS({ "transport", "o" } ), NULL, endpoint_clear_configuration) }, @@ -1625,18 +2035,64 @@ fail: } +static void endpoint_cis(const char *input, void *user_data) +{ + struct endpoint *ep = user_data; + char *endptr = NULL; + int value; + + if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) { + ep->cis = BT_ISO_QOS_CIS_UNSET; + } else { + value = strtol(input, &endptr, 0); + + if (!endptr || *endptr != '\0' || value > UINT8_MAX) { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ep->cis = value; + } + + endpoint_register(ep); +} + +static void endpoint_cig(const char *input, void *user_data) +{ + struct endpoint *ep = user_data; + char *endptr = NULL; + int value; + + if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) { + ep->cig = BT_ISO_QOS_CIG_UNSET; + } else { + value = strtol(input, &endptr, 0); + + if (!endptr || *endptr != '\0' || value > UINT8_MAX) { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ep->cig = value; + } + + bt_shell_prompt_input(ep->path, "CIS (auto/value):", endpoint_cis, ep); +} + static void endpoint_auto_accept(const char *input, void *user_data) { struct endpoint *ep = user_data; - if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) + if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) { ep->auto_accept = true; - else if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) + } else if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) { ep->auto_accept = false; - else + } else { bt_shell_printf("Invalid input for Auto Accept\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } - endpoint_register(ep); + bt_shell_prompt_input(ep->path, "CIG (auto/value):", endpoint_cig, ep); } static void endpoint_set_capabilities(const char *input, void *user_data) @@ -1694,22 +2150,6 @@ static const struct capabilities *find_capabilities(const char *uuid, return NULL; } -static struct iovec *iov_append(struct iovec **iov, const void *data, - size_t len) -{ - if (!*iov) { - *iov = new0(struct iovec, 1); - (*iov)->iov_base = new0(uint8_t, UINT8_MAX); - } - - if (data && len) { - memcpy((*iov)->iov_base + (*iov)->iov_len, data, len); - (*iov)->iov_len += len; - } - - return *iov; -} - static void cmd_register_endpoint(int argc, char *argv[]) { struct endpoint *ep; @@ -1799,31 +2239,14 @@ static void cmd_unregister_endpoint(int argc, char *argv[]) return bt_shell_noninteractive_quit(EXIT_SUCCESS); } -struct endpoint_config { - GDBusProxy *proxy; - struct endpoint *ep; - struct iovec *caps; -}; - static void config_endpoint_setup(DBusMessageIter *iter, void *user_data) { struct endpoint_config *cfg = user_data; - DBusMessageIter dict; - const char *key = "Capabilities"; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &cfg->ep->path); - dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); - - bt_shell_printf("Capabilities: "); - bt_shell_hexdump(cfg->caps->iov_base, cfg->caps->iov_len); - - g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, - DBUS_TYPE_BYTE, &cfg->caps->iov_base, - cfg->caps->iov_len); - - dbus_message_iter_close_container(iter, &dict); + append_properties(iter, cfg); } static void config_endpoint_reply(DBusMessage *message, void *user_data) @@ -1906,6 +2329,9 @@ static void cmd_config_endpoint(int argc, char *argv[]) iov_append(&cfg->caps, preset->data.iov_base, preset->data.iov_len); + /* Set QoS parameters */ + cfg->qos = &preset->qos; + endpoint_set_config(cfg); return; } @@ -2362,7 +2788,7 @@ static void transport_property_changed(GDBusProxy *proxy, const char *name, return; if (ep->auto_accept) { - bt_shell_printf("Auto Accepting...\n"); + bt_shell_printf("Auto Acquiring...\n"); if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL, acquire_reply, proxy, NULL)) bt_shell_printf("Failed acquire transport\n"); @@ -2431,6 +2857,15 @@ static void cmd_show_transport(int argc, char *argv[]) print_property(proxy, "Volume"); print_property(proxy, "Endpoint"); + print_property(proxy, "Interval"); + print_property(proxy, "Framing"); + print_property(proxy, "SDU"); + print_property(proxy, "Retransmissions"); + print_property(proxy, "Latency"); + print_property(proxy, "Location"); + print_property(proxy, "Metadata"); + print_property(proxy, "Links"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); } @@ -2450,23 +2885,27 @@ static struct transport *find_transport(GDBusProxy *proxy) static void cmd_acquire_transport(int argc, char *argv[]) { GDBusProxy *proxy; + int i; - proxy = g_dbus_proxy_lookup(transports, NULL, argv[1], + for (i = 1; i < argc; i++) { + proxy = g_dbus_proxy_lookup(transports, NULL, argv[i], BLUEZ_MEDIA_TRANSPORT_INTERFACE); - if (!proxy) { - bt_shell_printf("Transport %s not found\n", argv[1]); - return bt_shell_noninteractive_quit(EXIT_FAILURE); - } + if (!proxy) { + bt_shell_printf("Transport %s not found\n", argv[i]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } - if (find_transport(proxy)) { - bt_shell_printf("Transport %s already acquired\n", argv[1]); - return bt_shell_noninteractive_quit(EXIT_FAILURE); - } + if (find_transport(proxy)) { + bt_shell_printf("Transport %s already acquired\n", + argv[i]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } - if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL, - acquire_reply, proxy, NULL)) { - bt_shell_printf("Failed acquire transport\n"); - return bt_shell_noninteractive_quit(EXIT_FAILURE); + if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL, + acquire_reply, proxy, NULL)) { + bt_shell_printf("Failed acquire transport\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } } return bt_shell_noninteractive_quit(EXIT_SUCCESS); @@ -2496,25 +2935,29 @@ static void release_reply(DBusMessage *message, void *user_data) static void cmd_release_transport(int argc, char *argv[]) { GDBusProxy *proxy; - struct transport *transport; + int i; - proxy = g_dbus_proxy_lookup(transports, NULL, argv[1], + for (i = 1; i < argc; i++) { + struct transport *transport; + + proxy = g_dbus_proxy_lookup(transports, NULL, argv[i], BLUEZ_MEDIA_TRANSPORT_INTERFACE); - if (!proxy) { - bt_shell_printf("Transport %s not found\n", argv[1]); - return bt_shell_noninteractive_quit(EXIT_FAILURE); - } + if (!proxy) { + bt_shell_printf("Transport %s not found\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } - transport = find_transport(proxy); - if (!transport) { - bt_shell_printf("Transport %s not acquired\n", argv[1]); - return bt_shell_noninteractive_quit(EXIT_FAILURE); - } + transport = find_transport(proxy); + if (!transport) { + bt_shell_printf("Transport %s not acquired\n", argv[i]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } - if (!g_dbus_proxy_method_call(proxy, "Release", NULL, + if (!g_dbus_proxy_method_call(proxy, "Release", NULL, release_reply, transport, NULL)) { - bt_shell_printf("Failed release transport\n"); - return bt_shell_noninteractive_quit(EXIT_FAILURE); + bt_shell_printf("Failed release transport\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } } return bt_shell_noninteractive_quit(EXIT_SUCCESS); @@ -2707,10 +3150,10 @@ static const struct bt_shell_menu transport_menu = { { "show", "", cmd_show_transport, "Transport information", transport_generator }, - { "acquire", "", cmd_acquire_transport, + { "acquire", " [transport1...]", cmd_acquire_transport, "Acquire Transport", transport_generator }, - { "release", "", cmd_release_transport, + { "release", " [transport1...]", cmd_release_transport, "Release Transport", transport_generator }, { "send", " ", cmd_send_transport, diff --git a/tools/bluetooth-player.c b/tools/bluetooth-player.c index 497d975e9d7c..b6cdd161ee8c 100644 --- a/tools/bluetooth-player.c +++ b/tools/bluetooth-player.c @@ -26,7 +26,6 @@ #include #include "gdbus/gdbus.h" - #include "lib/bluetooth.h" #include "lib/uuid.h" @@ -37,6 +36,7 @@ #define PROMPT_ON COLOR_BLUE "[bluetooth]" COLOR_OFF "# " #define PROMPT_OFF "[bluetooth]# " + static DBusConnection *dbus_conn; static void connect_handler(DBusConnection *connection, void *user_data) From patchwork Sat Aug 27 00:05:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600614 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 88015ECAAD5 for ; Sat, 27 Aug 2022 00:06:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345143AbiH0AGB (ORCPT ); Fri, 26 Aug 2022 20:06:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55390 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345128AbiH0AF6 (ORCPT ); Fri, 26 Aug 2022 20:05:58 -0400 Received: from mail-pj1-x102b.google.com (mail-pj1-x102b.google.com [IPv6:2607:f8b0:4864:20::102b]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DE754E42FA for ; Fri, 26 Aug 2022 17:05:55 -0700 (PDT) Received: by mail-pj1-x102b.google.com with SMTP id c16-20020a17090aa61000b001fb3286d9f7so8452467pjq.1 for ; Fri, 26 Aug 2022 17:05:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=uKZN+WtU9c3IVIhReF07AEB77DEg2H9OvDD5FkV5cdU=; b=D65bEwoaCjVVkugPHJV49QE3p/11SvRRppn0inqH94FkMJLa0srtiKswBLXVY8aeo9 Qa+vOEHue+EPnvEAllx2wfFh7O/EhVPgIWBL8ZZYcMLgV5RXNaJUHMB9+x8A6AiEE3Wv YmqwNGikv1JV6ZQ+wJfoQaQ+tXKDj6aqvWG3SpC/S6uK3EkPiHudXXLeeQXzcLfLoDYU gkJJZAuzAbiokmCzJSZhOyz5xej9zWKzc3Dptw3MyBWI6W7iid3XkImv4OeGS/s8v98g Ca5q/+q6oiSLqwkZ7X9zMWKHKsZCJYjepHr0rSDVcpP0fcRsoLNq8FAu2bwgNaMcG4AI VRoQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=uKZN+WtU9c3IVIhReF07AEB77DEg2H9OvDD5FkV5cdU=; b=hb30nkSUV1ZIApS+9A4T/VrfEbdXFphNYx2eGtKeMiZf5rKrn0i08thhNGxzGg7IdG 0KFMCqXheJcqWuTd96IVvTMe47LzOq2pLJJ7lHQg+Bv2p2vXZoBeFX5q8pFjKeB8qEvq 3bsqjYyHPtUl2Ocwt0aZ8yu1THMs473Ch52kXm4f5IDpHkvqdUizQox4UBKE7uXUIq/g Qu6+R7c7lZZiZVKoeqca1QzLQwPBG+sjASlZJ9KWwEUym2TsLxVdbYiRiTk0ZSp7Uuwx Me8G0NmnPPAEizEYgUzoQT0qJQvbh0rOjmnWuWM0aRuBuPINgBSnuBal3atpn5ph+3Zh 79cw== X-Gm-Message-State: ACgBeo04WuSixfntiNuWVALo1Nk/FRwc9xzuIin0lxZaBwCPwZRmVbHz /Z8cNyeMmiyu8nJSw2vt3OXg6hT+2iE= X-Google-Smtp-Source: AA6agR5+9F7WPm1USe62ojFeSnfzFMZeUpMw7zj/pRwB6KMbF7WuLWXAg01L260/bMYZ3vQNNgDzig== X-Received: by 2002:a17:903:32d2:b0:172:f62a:2f33 with SMTP id i18-20020a17090332d200b00172f62a2f33mr5916748plr.16.1661558754807; Fri, 26 Aug 2022 17:05:54 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.53 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:53 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 09/11] client/player: Use QoS interval on transport.send Date: Fri, 26 Aug 2022 17:05:38 -0700 Message-Id: <20220827000540.113414-10-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Luiz Augusto von Dentz This makes use of QoS interval when sending a file. --- client/player.c | 69 ++++++++++++++++++++++++++++++++++++++-- tools/bluetooth-player.c | 1 - 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/client/player.c b/client/player.c index 4ba1a72ecfd5..99b036b8c3ec 100644 --- a/client/player.c +++ b/client/player.c @@ -2981,9 +2981,56 @@ static int open_file(const char *filename, int flags) return fd; } -static int transport_send(struct transport *transport, int fd) +#define NSEC_USEC(_t) (_t / 1000L) +#define SEC_USEC(_t) (_t * 1000000L) +#define TS_USEC(_ts) (SEC_USEC((_ts)->tv_sec) + NSEC_USEC((_ts)->tv_nsec)) + +static void send_wait(struct timespec *t_start, uint32_t us) { + struct timespec t_now; + struct timespec t_diff; + int64_t delta_us; + + /* Skip sleep at start */ + if (!us) + return; + + if (clock_gettime(CLOCK_MONOTONIC, &t_now) < 0) { + bt_shell_printf("clock_gettime: %s (%d)", strerror(errno), + errno); + return; + } + + t_diff.tv_sec = t_now.tv_sec - t_start->tv_sec; + t_diff.tv_nsec = t_now.tv_nsec - t_start->tv_nsec; + + delta_us = us - TS_USEC(&t_diff); + + if (delta_us < 0) { + bt_shell_printf("Send is behind: %zd us - skip sleep", + delta_us); + delta_us = 1000; + } + + usleep(delta_us); + + if (clock_gettime(CLOCK_MONOTONIC, t_start) < 0) + bt_shell_printf("clock_gettime: %s (%d)", strerror(errno), + errno); +} + +static int transport_send(struct transport *transport, int fd, + struct bt_iso_qos *qos) +{ + struct timespec t_start; uint8_t *buf; + uint32_t num = 0; + + if (qos && clock_gettime(CLOCK_MONOTONIC, &t_start) < 0) { + bt_shell_printf("clock_gettime: %s (%d)", strerror(errno), + errno); + return -errno; + } buf = malloc(transport->mtu[1]); if (!buf) { @@ -2991,6 +3038,10 @@ static int transport_send(struct transport *transport, int fd) return -ENOMEM; } + /* num of packets = latency (ms) / interval (us) */ + if (qos) + num = (qos->out.latency * 1000 / qos->out.interval); + for (transport->seq = 0; ; transport->seq++) { ssize_t ret; int queued; @@ -3016,6 +3067,11 @@ static int transport_send(struct transport *transport, int fd) bt_shell_printf("[seq %d] send: %zd bytes " "(TIOCOUTQ %d bytes)\n", transport->seq, ret, queued); + + if (qos) { + if (transport->seq && !((transport->seq + 1) % num)) + send_wait(&t_start, num * qos->out.interval); + } } free(buf); @@ -3026,6 +3082,8 @@ static void cmd_send_transport(int argc, char *argv[]) GDBusProxy *proxy; struct transport *transport; int fd, err; + struct bt_iso_qos qos; + socklen_t len; proxy = g_dbus_proxy_lookup(transports, NULL, argv[1], BLUEZ_MEDIA_TRANSPORT_INTERFACE); @@ -3049,7 +3107,14 @@ static void cmd_send_transport(int argc, char *argv[]) bt_shell_printf("Sending ...\n"); - err = transport_send(transport, fd); + /* Read QoS if available */ + memset(&qos, 0, sizeof(qos)); + len = sizeof(qos); + if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, + &len) < 0) + err = transport_send(transport, fd, NULL); + else + err = transport_send(transport, fd, &qos); close(fd); diff --git a/tools/bluetooth-player.c b/tools/bluetooth-player.c index b6cdd161ee8c..eba104d09fdb 100644 --- a/tools/bluetooth-player.c +++ b/tools/bluetooth-player.c @@ -36,7 +36,6 @@ #define PROMPT_ON COLOR_BLUE "[bluetooth]" COLOR_OFF "# " #define PROMPT_OFF "[bluetooth]# " - static DBusConnection *dbus_conn; static void connect_handler(DBusConnection *connection, void *user_data) From patchwork Sat Aug 27 00:05:39 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600613 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 31DEEECAAD5 for ; Sat, 27 Aug 2022 00:06:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345153AbiH0AGC (ORCPT ); Fri, 26 Aug 2022 20:06:02 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55412 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345111AbiH0AF7 (ORCPT ); Fri, 26 Aug 2022 20:05:59 -0400 Received: from mail-pl1-x62a.google.com (mail-pl1-x62a.google.com [IPv6:2607:f8b0:4864:20::62a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 136F2E8337 for ; Fri, 26 Aug 2022 17:05:57 -0700 (PDT) Received: by mail-pl1-x62a.google.com with SMTP id y1so1031259plb.2 for ; Fri, 26 Aug 2022 17:05:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=OewMMH2FhTMDlAG1e4dJh9VaKQQF/YVhC1cCT3PeBg8=; b=FwbS5FfxBJeXPqU+BpdMLFYjyhhzAPkqAiid5GolgEluI2GHOzsq0ve+KIdw17mSTD MoGcx7AihUUUo1HBkROBerHQJtuvsQbRtVw9eGyUwWInQtwX0ekdcOEUhvrNMZWkosal ltAn02/vA/beTe6+jR6No3gltTf/Q8bjL5KBX0TWr/eEqiVLv+gqfkAxvbQH3HnA7b78 iZWItAkGyzv4m4rG727dXEiHfz8DNioC5DL1MiltJjkmghHW++1hU7OZnqOiYHesoipJ 6IGkuDK0GN6Fa/VsBlEj1jG/8G6fQgaqDQkPiNVBesGMhnR+71uKffJQoMXoQ+yC9svU 1w1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=OewMMH2FhTMDlAG1e4dJh9VaKQQF/YVhC1cCT3PeBg8=; b=4nkXqK6qFGFcXPNNSe9dk1ullacxF3ngR+dos2+TvLAKDe3g/rQEPAmQYU/PyOont2 9UZyPWbHNtkaHYVq1eLm9a6mVAS1Nf3JJ8eNIKMXlmO7nKE1SwOCKS0MnbuuwZZcVz1j 06yXZ74vMXMuRXTPthkDxMGfC22lPEKd1zuPk6gZ4YLcpwEhlAW7qjiEV5NyP10ltIMF 7LD5SZv5NwIs0MJ6Nt7oEX7DYnG8CjPuDM255SzSBEnUrZVRP+kbciQtUHZoWzbtVPlj VYPfSbwNvHMHjTnRK+EY4ZG0qOnQ76dqdkurf93Ma19k+WFkY/5F2BdUdRJu0ejWFmYn t0ng== X-Gm-Message-State: ACgBeo1Tu3fZBCnZstM8g30vBGqV27JJhKZoMOr2Sro/kWkZq8//pOoP ydNawvfnMeGvsoVD66lrVZwWBVQjqCM= X-Google-Smtp-Source: AA6agR5mTA5TJTH3ymjGgOTZPXI5eZWC5HvaiXqEpSQfi2b59pE5QTZxh/BeHzz8OwV3fMlpZOQjPQ== X-Received: by 2002:a17:90b:3149:b0:1fb:71ad:256b with SMTP id ip9-20020a17090b314900b001fb71ad256bmr6735949pjb.18.1661558756164; Fri, 26 Aug 2022 17:05:56 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.55 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:55 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 10/11] profiles: Allow linked transport to release the fd Date: Fri, 26 Aug 2022 17:05:39 -0700 Message-Id: <20220827000540.113414-11-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Frédéric Danis Multiple transports can be linked when using LE Audio BAP. In this case only one transport is used to Acquire the file descriptor which will be shared by all linked transports. In the same way, any transport can Release the file descriptor. --- profiles/audio/transport.c | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c index 47db2a8026b2..f36e609cbd7a 100644 --- a/profiles/audio/transport.c +++ b/profiles/audio/transport.c @@ -281,9 +281,26 @@ static void media_owner_free(struct media_owner *owner) g_free(owner); } +static void linked_transport_remove_owner(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct media_owner *owner = user_data; + struct media_transport *transport; + + transport = find_transport_by_bap_stream(stream); + if (!transport) { + error("Unable to find transport"); + return; + } + + DBG("Transport %s Owner %s", transport->path, owner->name); + transport->owner = NULL; +} + static void media_transport_remove_owner(struct media_transport *transport) { struct media_owner *owner = transport->owner; + struct bap_transport *bap = transport->data; if (!transport->owner) return; @@ -295,6 +312,9 @@ static void media_transport_remove_owner(struct media_transport *transport) media_request_reply(owner->pending, EIO); transport->owner = NULL; + if (bap->linked) + queue_foreach(bt_bap_stream_io_get_links(bap->stream), + linked_transport_remove_owner, owner); if (owner->watch) g_dbus_remove_watch(btd_get_dbus_connection(), owner->watch); @@ -452,11 +472,34 @@ static void media_owner_exit(DBusConnection *connection, void *user_data) media_transport_remove_owner(owner->transport); } +static void linked_transport_set_owner(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct media_owner *owner = user_data; + struct media_transport *transport; + + transport = find_transport_by_bap_stream(stream); + if (!transport) { + error("Unable to find transport"); + return; + } + + DBG("Transport %s Owner %s", transport->path, owner->name); + transport->owner = owner; +} + static void media_transport_set_owner(struct media_transport *transport, struct media_owner *owner) { + struct bap_transport *bap = transport->data; + DBG("Transport %s Owner %s", transport->path, owner->name); transport->owner = owner; + + if (bap->linked) + queue_foreach(bt_bap_stream_io_get_links(bap->stream), + linked_transport_set_owner, owner); + owner->transport = transport; owner->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), owner->name, From patchwork Sat Aug 27 00:05:40 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Luiz Augusto von Dentz X-Patchwork-Id: 600763 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 05FE0ECAAD5 for ; Sat, 27 Aug 2022 00:06:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345128AbiH0AGG (ORCPT ); Fri, 26 Aug 2022 20:06:06 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55488 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345144AbiH0AGB (ORCPT ); Fri, 26 Aug 2022 20:06:01 -0400 Received: from mail-pj1-x102e.google.com (mail-pj1-x102e.google.com [IPv6:2607:f8b0:4864:20::102e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E92DFE8690 for ; Fri, 26 Aug 2022 17:05:58 -0700 (PDT) Received: by mail-pj1-x102e.google.com with SMTP id r15-20020a17090a1bcf00b001fabf42a11cso3356709pjr.3 for ; Fri, 26 Aug 2022 17:05:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc; bh=QSMUiJU2mV970rn7ifAB8SnNl65gwzqHfzVq99/Upio=; b=bUftI5hEYHLl3HG/p+kK9c4BBchEmjnrjjZSVrNRVl3SK773aLPtxgftoAFk4LEoWJ WhRJTOcqA5qVsLqhU2UEt6lAgpgnuNiYbwfjVt6qtEF2L6ajprC3GFD4nLQliz1YA+8R qpae+UKs5LlJbVqFMcRq7/rUsTBI1t9Ds562aIxSOVj1NbUYYc4QOk7YPp71U4WOBe8r +QGzAvWtdNJQzPNaDb2XLIznWYJkcNPRJtNGD++qdNfLX4LyBYZXaXfDCUu7SlyIRO6h xEPzsddfc/9Olckjb8ZgZmltBjTn6rb84SWBQaBw8ygQXwJTy3Pfz56JGDE+ztO33gPo ehNA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc; bh=QSMUiJU2mV970rn7ifAB8SnNl65gwzqHfzVq99/Upio=; b=rj/YA+SXpAdOiUmH2cPd+ONhY+TlwBu8KsyElXlHqw2nQTH6X04vF2fhkfcJ29Li9I /Z80xv6NBVdsllAzPv2WXGwYrsOmrlkXmteY95LH+CRKRlTaSPGzgCKMKlLEOLMGCIT7 6J02LlMP4gs5pbWS3Hm6SY24GG425tsvPFKIv98qdnekexBn9+P9PSshM7smHDGzmu7D 9J8IQtjMTxerSR57ilYSDEcWB8Vtv5LAXhABT1bekyeGjSFM3GrgY4JtsG3GY7qsaBut a5bHLPgaMojUPnL+izp1JhA718Zinc+BS/X6LTnEag2wJnHn10yNmH0KtICtd0rgzoCK Pr2w== X-Gm-Message-State: ACgBeo3bHcdQxN2z2Tm26scV2ukNDqy3DSSfSbA5awDmHFgop7++Tiob Sx3tsW/ihACb/WdymmWPCNr0FRfDo60= X-Google-Smtp-Source: AA6agR4M0ob/5HPD+Z8rV1WgtmyLEanE4OtPRDH67R+X5Bos7wY+PSE67uMc/djqYv5CKqvvQIrHTg== X-Received: by 2002:a17:902:e844:b0:16f:9d2:f4ff with SMTP id t4-20020a170902e84400b0016f09d2f4ffmr5899508plg.27.1661558757509; Fri, 26 Aug 2022 17:05:57 -0700 (PDT) Received: from lvondent-mobl4.. (c-71-56-157-77.hsd1.or.comcast.net. [71.56.157.77]) by smtp.gmail.com with ESMTPSA id o1-20020a17090a168100b001fa8b909ef0sm2249487pja.7.2022.08.26.17.05.56 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 17:05:56 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v3 11/11] profiles: Update transport Links property on state change to QoS Date: Fri, 26 Aug 2022 17:05:40 -0700 Message-Id: <20220827000540.113414-12-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220827000540.113414-1-luiz.dentz@gmail.com> References: <20220827000540.113414-1-luiz.dentz@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-bluetooth@vger.kernel.org From: Frédéric Danis Currently, the Links property is only sent after the first call to the Acquire method, this cmay result in a rejection if a linked transport tries to call the Acquire method before receiving the Links update. --- profiles/audio/transport.c | 1 + 1 file changed, 1 insertion(+) diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c index f36e609cbd7a..f233643a6a3f 100644 --- a/profiles/audio/transport.c +++ b/profiles/audio/transport.c @@ -1321,6 +1321,7 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, /* If a request is pending wait it to complete */ if (owner && owner->pending) return; + bap_update_links(transport); transport_update_playing(transport, FALSE); return; case BT_BAP_STREAM_STATE_DISABLING: