From patchwork Fri Aug 26 23:20:23 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: 600623 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 55252ECAAD5 for ; Fri, 26 Aug 2022 23:20:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345495AbiHZXUj (ORCPT ); Fri, 26 Aug 2022 19:20:39 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51670 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1344880AbiHZXUh (ORCPT ); Fri, 26 Aug 2022 19:20:37 -0400 Received: from mail-pg1-x529.google.com (mail-pg1-x529.google.com [IPv6:2607:f8b0:4864:20::529]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6E385B5A63 for ; Fri, 26 Aug 2022 16:20:36 -0700 (PDT) Received: by mail-pg1-x529.google.com with SMTP id 202so2653911pgc.8 for ; Fri, 26 Aug 2022 16:20:36 -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=TgPdfpW5b2iThVM3B8uZQxcudwAScvfnuxL/NC4HhMRwomeQaHhyulQ4cHFe8OvhES QTWax5sWqRf2lR/BZZIrtcef7v6VsQ4QYWhKPbHrByV6atqwjJfJsx3eH8el/SOAd4Ob U1uAKts5Y/4b31/TjlVS8WEG9RL+I/c+kgotV2MU4c0MuXv5EBwRzE0y8LHv3J6cZaz9 F4Vw2xdxWcAu4B4zBxYNF059B5QXnOw0sV5V1wX6G1XnGfVuQ2L+8KIUOqOjjkF++Y3z w/CEdLz1sPwpnGJYx4U7FVZL/bV2xd7uRcsYxcYfHPGy2uVVOCbk/jzbDWyJ6C1TPWLI 7icA== 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=45EfhmWlwgd5NWZJZF8hamBWITPViUrX6b99V0W2cPeV86LUQGyLFAo+MqJtMXzanv BlbIUqKpDm9qAbG626fZMcPkLM+Baz+ca3LHdFtal1iIEGztcE3diSf7DF9W2GqJVf6/ I8Ed2ySBQYp023AhV5uhBg9ef+TUNpXW2ujFMt+vy20NeVISEkA1/61AEx5gAFIiCkIL UYtj4WxMKCysXxOLgmYXJ5qX6NaEa6Nmz2dJ47lXii9RgANv1b9W9PNW6YrtMnRO85md o4UrkJOtxFAtOoeo1TTYW1rpNL1U5uw8vrG/VA9S1BvZ3lxzjB+sCdBGab1MN34jiHl8 U3Kw== X-Gm-Message-State: ACgBeo0rsmmxN5I6ZFAH41WCAloK+aiy3ivdE52dYDCThBQFcom14hF4 c/QpBL6/j8es6kM06qNFtK3T8TtKGYA= X-Google-Smtp-Source: AA6agR7j+4Mb6deeWC4PXW4Up0m2uQpwouqkkRrXbDBHW3E6jI0mdUbekpvC2jiCcAFx4mFFJYwIdw== X-Received: by 2002:a63:2265:0:b0:42b:b14:7b42 with SMTP id t37-20020a632265000000b0042b0b147b42mr4921697pgm.528.1661556035319; Fri, 26 Aug 2022 16:20:35 -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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.34 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:34 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 1/9] adapter: Add btd_adapter_find_device_by_fd Date: Fri, 26 Aug 2022 16:20:23 -0700 Message-Id: <20220826232031.20391-2-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20:24 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: 600413 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 0D0DAC0502A for ; Fri, 26 Aug 2022 23:20:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345498AbiHZXUk (ORCPT ); Fri, 26 Aug 2022 19:20:40 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51674 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345494AbiHZXUi (ORCPT ); Fri, 26 Aug 2022 19:20:38 -0400 Received: from mail-pf1-x433.google.com (mail-pf1-x433.google.com [IPv6:2607:f8b0:4864:20::433]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 727B4B6012 for ; Fri, 26 Aug 2022 16:20:37 -0700 (PDT) Received: by mail-pf1-x433.google.com with SMTP id 145so2544075pfw.4 for ; Fri, 26 Aug 2022 16:20:37 -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=G5cwwTDmLKgQ06i6iT3eCdA8rIblVxOtAFmpHxMA/bPZiE5iLd/aHbkjAR4GSptVZK ZgtVUIBgP8WZqunQXnl22zjF3YRtj6QvpFHNlqfVQo1wvg5BOSN5EDRUc0p3MDIg1YYA KrSXAa8aKqUJBbmfLxmyFjqinWgyzbw2fiMuJBwt6a3+binZjGa1OxaTx2+WK6XAyozF 05u0tR7nhnrsU8Qtb+FZo8ko9tIAgOXtA5o91R2wU6e/LX19IoZwGJX8rHDMWymewMLl ki42WxU4CSJI3XL4GdJ0IgncPdam+wNo64jVpv1MZJ6TeLnsCMjAW22cgeBXKVrhdanY 5c3Q== 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=eucxAGhLPGxVFO2Il5iBinE2QnnktuPE5xuOoJn6bcMI/myl1i7m7KTL9hD0mKuAwh Vn52OV2zqTQGEVBcKSjtrwVMqVRSU3azJfF9PMK3vRvEIdLO0Psw6I5swmBueMP2xSEx +uqmLD6O09iJAyJjyYzsJRdKbE9pQsUXmxyrWMXIf9HenOiC5Bt0vBwz9RwUc+KnSPUH dCyZJ/+A4wnoqEgdFN2ljH4daxuP/lz87wqu41zJsdWSBKuUL2nEQB3ZSAZQBe3nNuLo Bsvg4VaryfDWx4IWXDhCWuBahTLTiXRF9tiKy4MW2vPSvEKfcvPWwFGuSkOsVV5yRsKB xGFw== X-Gm-Message-State: ACgBeo1P1tabLZYAgAzovvFFRrv7OjPpi52GHGIBzBFdhmeZ4Mx86QLC DA92Q/N6aeTKFL9S4o0VTdC10zzFX/Q= X-Google-Smtp-Source: AA6agR4ucJ9r36NlEMXwpRJNZyP0lx4uPDHswnvPQDwPFRKfLnIJWmuBijQfExcMjQaGUUZPZP+eLw== X-Received: by 2002:a63:8048:0:b0:42b:73ef:ac8f with SMTP id j69-20020a638048000000b0042b73efac8fmr4332719pgd.446.1661556036443; Fri, 26 Aug 2022 16:20:36 -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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.35 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:35 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 2/9] lib/uuid: Add PACS/ASCS UUIDs Date: Fri, 26 Aug 2022 16:20:24 -0700 Message-Id: <20220826232031.20391-3-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20:25 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: 600619 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 E56B1ECAAD4 for ; Fri, 26 Aug 2022 23:20:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345511AbiHZXUt (ORCPT ); Fri, 26 Aug 2022 19:20:49 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51740 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345500AbiHZXUp (ORCPT ); Fri, 26 Aug 2022 19:20:45 -0400 Received: from mail-pj1-x1036.google.com (mail-pj1-x1036.google.com [IPv6:2607:f8b0:4864:20::1036]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D6615B4E85 for ; Fri, 26 Aug 2022 16:20:40 -0700 (PDT) Received: by mail-pj1-x1036.google.com with SMTP id z3-20020a17090abd8300b001fd803e34f1so820450pjr.1 for ; Fri, 26 Aug 2022 16:20:40 -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=XHW8VK8LD5YtoIRkFyKcCfNmORqREkPdirl5VsDonl2hhB+I2+rfd4vCSZndcDUFyX iXX59fEmtDTqBk+BHebpE/pMsQOEA/A/l+6DhBqdDc30Yz/xOE9jZmKUlz/MzdEpIR/N N2hbgpLjRAX1IgyywaBUaSbLFeSqvAJK6lRsyAaFQ23leSw3IXuAyfmw5Cws231cw8U6 Lx5/3uEusxl9T+/lNLShH6LBI/WRERLMiM2DXgq4cottYQ82PYU5ARF+MgGyFYRkdHl2 HqY9vLCBBp3ltQI6qX8PvNgCub4EffWPr+9YgymhXYb7JdsbwVOatkH/KhLqAifkz8yY 4scw== 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=ORFqIKw/etx/h39MjFNVKwMYQcq7BfLiRyQXq8BX5uwkXJ4yP4o0+ufCTgcbM8q0VE AuFmvhaeWOmkyh9BpxuFj3RLNxbuWYGsMsD1CTHQyAdWm69GFTSobGhC8hVPwxflFGQI 0M6GguM0HUzuyK3JTeat/9p9fJqd5j4rXtpD2KQQ/kEURLVi8tw6vs2ziyDCd2e9139m Omgh9Xf/f4BL8offRkhIOk8HT+0JW2GabaCMPZrHpr1uI+vVi5MSoyqaFfIaFmY3S1RY gYYUVsKlWJzRDLYSOJd/FYwChwvWbrbuT513R6R/IKACF4odGMSQ4AZ3C8h0PTjH+vqm bjXQ== X-Gm-Message-State: ACgBeo3ENhheGrYOfDcN+dWFHdFg8dtE/iLZosiInB5fJaeRP9pO+R/I 0cce7etl7lyuI/k+kJav+H3OggrOFJ0= X-Google-Smtp-Source: AA6agR7DYSnZuB5TrzfbI2otjIty/FvsWTYHfidR6UK9Ar1/c9ySFGjxLaXe73BZWXrgFeH5NvMVqQ== X-Received: by 2002:a17:902:710e:b0:170:8d34:9447 with SMTP id a14-20020a170902710e00b001708d349447mr5730656pll.126.1661556038108; Fri, 26 Aug 2022 16:20:38 -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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.36 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:37 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 3/9] shared/bap: Add initial code for handling BAP Date: Fri, 26 Aug 2022 16:20:25 -0700 Message-Id: <20220826232031.20391-4-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20:26 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: 600621 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 0F68AECAAD5 for ; Fri, 26 Aug 2022 23:20:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345506AbiHZXUq (ORCPT ); Fri, 26 Aug 2022 19:20:46 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51722 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345496AbiHZXUp (ORCPT ); Fri, 26 Aug 2022 19:20:45 -0400 Received: from mail-pl1-x633.google.com (mail-pl1-x633.google.com [IPv6:2607:f8b0:4864:20::633]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7DC60B6D7A for ; Fri, 26 Aug 2022 16:20:41 -0700 (PDT) Received: by mail-pl1-x633.google.com with SMTP id w2so2865232pld.0 for ; Fri, 26 Aug 2022 16:20:41 -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=ahYVAW4KTTDU4fbLla8+h8POrx//7IFQDcSM7Wxz0wUIgv5d7M7hTPs5Xwyv5rk5RQ V5gXlHwf6oE8IqwsLBi8ltHpY4KMtHbBlK3DyT/dW9k12LIFZY/W9nf8PEg0KCBtk22D 63r+fWzE20Zzy5qOfugENBVZiNMZ2OnIdqr0UreqtivuMVp/cYZ6UAZny4KJbg+CXUcE MlYmY7oQktRAVrwUZIHGxFsw4yhu11f2mJrevGCoZBeAKLLBhL4Z0hQ8AvINWOWGxq+6 HRH3cFFLiygvfwAIXHp0qHxNeHKYWPo1yqvGIP0UuJ2Kzre4+EhikdsL47OX93tasqQW 14iw== 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=UklnupLjLtuJIwmPoLB4pdrseIztG7Kgdfha1vKWdNt4ET6ZUbYIUvOOczruKDHTme arNt9z9J579FWbCGNixDKa5ih4jQTYRlq+JU8H8fJg6usqeQ/zwvqCeouP66s/Rg4wSx S42+5YWcMFlZ190NGT++nI20SI1dYs19lYdnxnZ80vVd8/1NE1S8aVjUDnlYYILOw/o/ 9PuX/7DExc1moasMzcwgOEKgmw57uWIEyij2M7Pipx1Ir1acTKSJ+Km4L44xehoo4yZJ a1+t7+CrRVHAZE711nP5RnKO3myn4bV1SmWyzqqNmiMDg2AKRwNkigxlkyH1ZPAPAHMC JOaQ== X-Gm-Message-State: ACgBeo0hQJW7BI8JlJKetEp4pRiPWe6MhdzPeCh3FLIgTYoMivd4zHXb Z6ICw+yQR55ANrirBIfx12ohS4w/cmg= X-Google-Smtp-Source: AA6agR6LnCAfYyABuQ3xP9hdD1Sfmo6ytgYKkbLlWfbTIJkXzxfPyHgh7NGchnniYsEdALx3c0SSRg== X-Received: by 2002:a17:903:245:b0:16f:85be:f33b with SMTP id j5-20020a170903024500b0016f85bef33bmr5756515plh.96.1661556039551; Fri, 26 Aug 2022 16:20:39 -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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.38 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:38 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 4/9] profiles: Add initial code for bap plugin Date: Fri, 26 Aug 2022 16:20:26 -0700 Message-Id: <20220826232031.20391-5-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20:27 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: 600412 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 CF79BC0502A for ; Fri, 26 Aug 2022 23:20:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345503AbiHZXUs (ORCPT ); Fri, 26 Aug 2022 19:20:48 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51742 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345501AbiHZXUp (ORCPT ); Fri, 26 Aug 2022 19:20:45 -0400 Received: from mail-pj1-x102c.google.com (mail-pj1-x102c.google.com [IPv6:2607:f8b0:4864:20::102c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 01222BA17C for ; Fri, 26 Aug 2022 16:20:42 -0700 (PDT) Received: by mail-pj1-x102c.google.com with SMTP id e19so2916310pju.1 for ; Fri, 26 Aug 2022 16:20:41 -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=aA6wgMg2QBqx//S3yt67zc7W17Us7+GgsXuoEcL3gfA8ejLy4zJSxWh0WHYVmJZ4Ty LCajq8N8Bpqn9mpkfDOYahLVMJTvRCBJsxVze9fDTeyZq56R1EcQhMGqUzNlKXlmYYVk GfHy6lC1il3WCYT0KxVL4qsDlgw95aZmeRCrx/4FdWFT+wg0osL1RuvA7BXXFbZ1PEIN E6ogICaby7LVdOqYDh/K70O6EUzQ8bAEOSse3WW4tAkyt7jwSCaXt+wqXNxGZmY9DHF/ sQR1+Odq8qp/PZFR/GeHPaY6dRdQCtxja7h+PiLTD8fk4u1AR5yJGn2X3b7ODYOq2AXz jnmQ== 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=Yq5cqTDUXWLqcd3vElT9SuDW65GP5geuPgbxfY2rNYOomnln1nf3gaQ+wMWu7R57Nj IZEBr2lY1dVeCFvM2Jn3/K6e4rQIyP0bN60V/LMsqaHxxWPhqiQGUHn4687PJyvOlnye Xjgd7WEdSI5DjTF3MTkkRaA7+yPswVbr16boXpQAZAjwE8zaKtu88Hm/3tACNpQNzEez /fAP5bsqq5hjON6VpLFiW/uPszF0lRHIJ4NMi947lxR+/v2A5e4UfrlE4XguhKOeOAw2 z+UrNlYkV/0s8X0faQRi9eJje7725NeGG2BuUXyNudNwfjQAbi+140ChSDnALQtzBwjA vKuQ== X-Gm-Message-State: ACgBeo1PUbsqz5DNeAbFtoy+rfh5ClCm9QVGNKUJIRaP1s6C/xWBU27k YnfM2qZ+g9+oB2aohqfRXt+K/TvlXeA= X-Google-Smtp-Source: AA6agR4oiJEM7TZfNGA5Hgb+e4GFBtfLDZ+5Lq0mY+qcy+6e2r/eOdryNXfOkblky4qQqR4pQrcAGQ== X-Received: by 2002:a17:903:1c8:b0:173:c58:dc6d with SMTP id e8-20020a17090301c800b001730c58dc6dmr5722380plh.105.1661556040938; Fri, 26 Aug 2022 16:20:40 -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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.39 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:40 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 5/9] shared: Add definition for LC3 codec Date: Fri, 26 Aug 2022 16:20:27 -0700 Message-Id: <20220826232031.20391-6-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20:28 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: 600411 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 8FC22C0502A for ; Fri, 26 Aug 2022 23:20:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345510AbiHZXUu (ORCPT ); Fri, 26 Aug 2022 19:20:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51750 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345502AbiHZXUp (ORCPT ); Fri, 26 Aug 2022 19:20:45 -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 46787BB002 for ; Fri, 26 Aug 2022 16:20:43 -0700 (PDT) Received: by mail-pj1-x102e.google.com with SMTP id e19so2916328pju.1 for ; Fri, 26 Aug 2022 16:20:43 -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=cMB6REfH7JLYAlPC/8lWPdxM/nu0xWrT11nnFPa7xA4is4GCJbeJGf0FN1e+k0ljbp /zc8uBsBs8pnvu8JVX/dnPNTZ19heQnMPthR2/oRmlDVm756q1Pjy6fwYnsnxL6ZNm5a +TfFljVMP5KnmS9ly+tSyVBQsUSU5rPOdyXqpboOg4UEw1+COgAKAtNAc/v9H3z8Ny+O y1PW4UxTY0Rwle0xc7TYZ6gd1WN0SF9JgManBunia40Z0LeuK31vh/xqCSDVl2GJ1OuY 0wkkbl9bfs25EM1h52jSxagXafyTUrDVrtaOLa4Lx4rnI9KlPhjkjho53rsZBbUo5hhV Z9IQ== 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=Q3oJykV8lMnPqhy2HH8NlSsKAYXaZ5epDrXbP50BnUkLqM3uS6D7WViiMPUQNrypzH USRrEMyKy5MzZIBxAqOZgyWZ3iYSNB6BgBw5AVWkzMBDYb3MSw+eJ9ofLTeqVeYnlrX8 +molFa23tYZh1uayd/1NpLx34htU8swGZ8BLHfl78DQIW3Tt4WrGf1w90BVIrC+rzewz NSLsnt7ZKZ0A3M4pMyIthDebjz78sZZIYOR6dPenbfzcMgpXGoXROJIFx8/R7Msm9JZL vlYtcxZ3benG/h8LbLz0mLmaTAzhrYEhxJUSPsw5L08iZCRSCg9uwQMQumwtIL2ikWHU MBXQ== X-Gm-Message-State: ACgBeo0stBIVkeL9dpdWWVaqNEtkKS+KLSZeUjBkVv0c35wGQ0uT1wh6 XFsHSO2lSu73uD5OuJk2nlTjJUUvQjs= X-Google-Smtp-Source: AA6agR4sQ8DGG2z07XEvQZUFlE2S4jB/faAI9JeWLRMPxRv51o/l3D/AQa3hELrJImhViBie4Ro3PQ== X-Received: by 2002:a17:90b:4b42:b0:1fb:3ea7:da with SMTP id mi2-20020a17090b4b4200b001fb3ea700damr6483493pjb.138.1661556042238; Fri, 26 Aug 2022 16:20:42 -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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.41 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:41 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 6/9] media-api: Add SelectProperties Date: Fri, 26 Aug 2022 16:20:28 -0700 Message-Id: <20220826232031.20391-7-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20:29 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: 600620 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 77E53C0502A for ; Fri, 26 Aug 2022 23:20:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345500AbiHZXUw (ORCPT ); Fri, 26 Aug 2022 19:20:52 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51752 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345208AbiHZXUp (ORCPT ); Fri, 26 Aug 2022 19:20:45 -0400 Received: from mail-pf1-x42c.google.com (mail-pf1-x42c.google.com [IPv6:2607:f8b0:4864:20::42c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7167EBC83D for ; Fri, 26 Aug 2022 16:20:44 -0700 (PDT) Received: by mail-pf1-x42c.google.com with SMTP id t129so2899375pfb.6 for ; Fri, 26 Aug 2022 16:20: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=vSA+HPWsuSD7qUqFUVcG0/msermoFukwlhbj3ey3Egc=; b=pc9Wut6zC16JuKQ7UQRlMekB0MUdz7AZCPUicoxXiBHwgbTIMrDnw6imCAZZCP/Usx 5FFYleB673IczW8z+us4vGTGJUj4BTKlD585x3k7/7BAQK6gsSPl8DNAvtXJ2vsnxdqe 5FqpQkftGwJ8u8UeODMVRad1bslqWQhKv4YkTyDqsR32mAVEVIF2EbSChGQKXM1MRc9A UIpUd/4MjygoARn/Rt8zX+7LH4L/4rUHia3f5R3COvMKruwjbmLyJwl1UjJRpO8b0g/X 8HnHDs64wLys1OdS1S4ugpdVSiEkCzGSAcNh4Jk26af80sOALXRCWCK3CHyW3f54qv+G 9+nA== 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=KO18jDsGjueru4gnoPDIA3Tat/msy6V/0X27nvgqEitcbsMqkhs2q5A84kWDV9uDeE yQBmrW174yTagshI6OX9qoYpXyrH6iVYcq1FDQJZyUdMU+/XRywLHYFLQt1gGpdwKEzd g9/KCcEf11oeTg0DF4nTg1YFx2/UP4jN1uNkFbi1ZCDm0Xqu5H2eim19u+EBTkJ8XjRb uTl805Z5jn3sKBCIoXx8bRM9ryLJp7gmGopUaxdxMmnW9QEJMdppqKROscu95g9Olt7T asfIAqDErNf9WRxvetMDYh1sA5F2pBLidYNOPWIcF/G5SC3qCFY9/OE0znNgh/gxunoG M/Bw== X-Gm-Message-State: ACgBeo3j3ZywUb6ekuruI9V7Wh+/JR9k6WTsleRQ1DjMol9OpNvVIRGD swaD1hThoMRghbLep0OI+7UGvuOjvnQ= X-Google-Smtp-Source: AA6agR5zajPu0GqAp3pseDCkOAus8zUTI+RGvHFD0TaWVfH2B99cNppiPEmpjE6AArYYg381G+ku1g== X-Received: by 2002:a05:6a00:1404:b0:537:2330:18d1 with SMTP id l4-20020a056a00140400b00537233018d1mr5977417pfu.39.1661556043394; Fri, 26 Aug 2022 16:20: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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.42 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:42 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 7/9] test/simple-endpoint: Add support for LC3 endpoints Date: Fri, 26 Aug 2022 16:20:29 -0700 Message-Id: <20220826232031.20391-8-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20: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: 600409 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 5444AECAAD5 for ; Fri, 26 Aug 2022 23:20:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345509AbiHZXUz (ORCPT ); Fri, 26 Aug 2022 19:20:55 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51750 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237451AbiHZXUt (ORCPT ); Fri, 26 Aug 2022 19:20:49 -0400 Received: from mail-pg1-x52a.google.com (mail-pg1-x52a.google.com [IPv6:2607:f8b0:4864:20::52a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2C986D3E55 for ; Fri, 26 Aug 2022 16:20:46 -0700 (PDT) Received: by mail-pg1-x52a.google.com with SMTP id c24so2644800pgg.11 for ; Fri, 26 Aug 2022 16:20: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=wxssbqhgwnyyI4qNQteNJ+xFtAj5pt6svn+tuRUJqHc=; b=MkbDBZG9dQw2NT+t6g2M7+XhfsTDlsm0jH8SODs63NJ6I93OMn50M7xRzWyRmrvp7x IQITLOoMCx4FtLttBQxIv27kJH3g8V4W+IpEZIvi2gWz1lAaF1SwyWlmruGoLMU030y3 N41lhlz7kE7xzxzKWQ9SGnPflqLOShtaQHLZD5LGqA+V3+Ne/dABw5J4WfBcbnxMwix8 fCwC+Fr3a+QczzikeYJtspyT5dgEaadN6Mx9gkjR84wpshOitLddSXK33Hi9hTK4PvC6 CbNJfpC6aE5/nZGBQgcUusqjefXEWyIrmDdji8U2dlIW2VlnJnX/9bSR2dGakR8hxC+b 2s+Q== 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=yK9jln9+gQJ5jLC5lnKwNV3VnDT7oSPEsJV24B1yZRukcoYQXhKiDvkRgicCFx3iDp EwKFsjXqo0i/CdJdYANp4ZAjg+zcnwUgZGGlD3yn4W4HUo/gLDyd9OFw5PnbOFTBnq/W TAfj79SPHVxeREmP3QcDpy55RAaJONUL0K2x5A4ztI966CcT++mBxijK8Je09QRgAZCu zI8osdJDL8jw61PkWtNCtuseLyBy31bFx89IYyo3LQLqgo9P4MQmHxtO4wuuDR/ugbwV zxUyiT/sYcH4h7ABrVqekpnOA5LYfVvhd7YTYjcyRD35P6DZEN6y2hGuSunDz7Ou0Wge xw4A== X-Gm-Message-State: ACgBeo2+gjgwZXpRn2cXfjwA1L24xjrrIUonByo0qKMX9VCcPcll63CV p8F848moiAzMqjPKFm6mUrJffSX6K4g= X-Google-Smtp-Source: AA6agR649gsn90IpWtlDzxSjzUtAKsTiXhrsm3xKOqmYrbtn+jRxx8YEasqI/RGwcn8lt3efIRHQmw== X-Received: by 2002:aa7:8881:0:b0:537:cc74:d197 with SMTP id z1-20020aa78881000000b00537cc74d197mr4189923pfe.19.1661556044628; Fri, 26 Aug 2022 16:20: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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.43 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:43 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 8/9] client/player: Add support for PACS endpoints Date: Fri, 26 Aug 2022 16:20:30 -0700 Message-Id: <20220826232031.20391-9-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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 Fri Aug 26 23:20: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: 600410 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 74370C0502C for ; Fri, 26 Aug 2022 23:20:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345504AbiHZXUy (ORCPT ); Fri, 26 Aug 2022 19:20:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51828 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345509AbiHZXUs (ORCPT ); Fri, 26 Aug 2022 19:20:48 -0400 Received: from mail-pf1-x42a.google.com (mail-pf1-x42a.google.com [IPv6:2607:f8b0:4864:20::42a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6D17BD8E04 for ; Fri, 26 Aug 2022 16:20:47 -0700 (PDT) Received: by mail-pf1-x42a.google.com with SMTP id 142so2893126pfu.10 for ; Fri, 26 Aug 2022 16:20:47 -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=JKiF2BKdma8LDzpetxmzfhxm9vuch3kjq15vo4jSwK+rk+FfYUXi1ZBy/9FUzu4aka oGml0Fa1rgky6lODvc+Ee0gkbSLL7KRyPtaozmZPaeJELYdKOcWynKj8mWZqKi7j1a+y R/ohjnYLquWlutuwbQdjEZ3V4Q8I//fC+wAb2gSaXC7a0AMjdo+cjncDjqEHze6nEsAz OA5SF5/leOL7TWcv1RBU7wtU16voLAPK3OYxz6WCZXO1SgZhaFW+R74MaTHZ336XD+vE w1svRqs7Xi3d1hYhDjq9eTYBCuT9ER1xrtmKgXCJAiBU+cFE6Iz/MAj3WUb3oEiSL6g8 9veA== 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=ACj2ScCfL+wheQLUDlIP0UsNyavkt/YFJV1YOursf3zqt30rtkYzg2H7mrq211YknO 5IpKUPl3ubdvMmhh+20jrGOCA0jLscIX4rV+BZavt98W5+9CeHqpn9AsPIS4urpIfXGO DfnmfJRqPjetP3FxjlL+J8azWJWCqGi0GypJ76pnajrkSt8Fi1ZicDC0cDVLTT9zuMsJ yJ5eQOvVlwEUayode2IXb8+QCnQzElWvPHwyQzbdbe1sZimAaMccj2QYz+QILvTvnifB sV+y6X9aUYGQ1y6Rc2xv9e9X+9jcvPphefoi2gimysgyvtL1fZWDcpaPIA4+O3KnCUzj b2Dg== X-Gm-Message-State: ACgBeo1S6bPE37rNfg1DXB0oT3cCghBP5ZXEe1hEPv7wTLB377w+4/g3 qWqSR++lq8NYsArrFwhITcnezq+Itkc= X-Google-Smtp-Source: AA6agR6Jv/gvnoSNionIjk01Zw98N9tV2Z/eIKVwtQG3l8z5tQUtkjUZLUJ6VgU8n2WHLMJ1w7jmDA== X-Received: by 2002:a63:931e:0:b0:42b:6394:f5b with SMTP id b30-20020a63931e000000b0042b63940f5bmr4954960pge.602.1661556046229; Fri, 26 Aug 2022 16:20: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 n15-20020aa7984f000000b0053645475a6dsm2312338pfq.66.2022.08.26.16.20.44 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Aug 2022 16:20:45 -0700 (PDT) From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH v2 9/9] client/player: Use QoS interval on transport.send Date: Fri, 26 Aug 2022 16:20:31 -0700 Message-Id: <20220826232031.20391-10-luiz.dentz@gmail.com> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220826232031.20391-1-luiz.dentz@gmail.com> References: <20220826232031.20391-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)