From: Luiz Augusto von Dentz <[email protected]>
iThis set add initial support for BAP (Basic Audio Profile) which is
an essential part of LE Audio responsible for stream control.
The plugin is considered experimental and depends on ISO socket in order
to work so the following setting needs to be changed in order to enable it:
# Enables D-Bus experimental interfaces
# Possible values: true or false
+Experimental = true
# Enables kernel experimental features, alternatively a list of UUIDs
# can be given.
@@ -126,7 +126,7 @@
# a6695ace-ee7f-4fb9-881a-5fac66c629af (BlueZ Experimental Offload Codecs)
# 6fbaf188-05e0-496a-9885-d6ddfdb4e03e (BlueZ Experimental ISO socket)
# Defaults to false.
+KernelExperimental = 6fbaf188-05e0-496a-9885-d6ddfdb4e03e
While proper support to the likes of PulseAudio and Pipewire are still
in progress it is possible to test using bluetoothctl with the following
commands:
[Server/Peripheral]
[bluetooth]# power on
[bluetooth]# advertise on
[bluetooth]# endpoint.register 00002bc9-0000-1000-8000-00805f9b34fb 0x06
[/local/endpoint/ep0] Auto Accept (yes/no): y
[/local/endpoint/ep0] CIG (auto/value): a
[/local/endpoint/ep0] CIS (auto/value): a
Capabilities:
03 01 ff 00 02 02 03 02 03 03 05 04 1e 00 f0 00 ................
Endpoint /local/endpoint/ep0 registered
[bluetooth]# endpoint.register 00002bcb-0000-1000-8000-00805f9b34fb 0x06
[/local/endpoint/ep1] Auto Accept (yes/no): y
[/local/endpoint/ep1] CIG (auto/value): a
[/local/endpoint/ep1] CIS (auto/value): a
Capabilities:
03 01 ff 00 02 02 03 02 03 03 05 04 1e 00 f0 00 ................
Endpoint /local/endpoint/ep1 registered
[Client/Central]
[bluetooth]# power on
[bluetooth]# endpoint.register 00002bc9-0000-1000-8000-00805f9b34fb 0x06
[/local/endpoint/ep0] Auto Accept (yes/no): y
[/local/endpoint/ep0] CIG (auto/value): a
[/local/endpoint/ep0] CIS (auto/value): a
Capabilities:
03 01 ff 00 02 02 03 02 03 03 05 04 1e 00 f0 00 ................
Endpoint /local/endpoint/ep0 registered
[bluetooth]# endpoint.register 00002bcb-0000-1000-8000-00805f9b34fb 0x06
[/local/endpoint/ep1] Auto Accept (yes/no): y
[/local/endpoint/ep1] CIG (auto/value): a
[/local/endpoint/ep1] CIS (auto/value): a
Capabilities:
03 01 ff 00 02 02 03 02 03 03 05 04 1e 00 f0 00 ................
Endpoint /local/endpoint/ep1 registered
[bluetooth]# scan on
[bluetooth]# scan off
[bluetooth]# connect <bdaddr>
[NEW] Transport /org/bluez/hci0/dev_00_AA_01_01_00_02/pac_source0/fd0
Endpoint: SetConfiguration
Transport /org/bluez/hci0/dev_00_AA_01_01_00_02/pac_source0/fd0
Device: /org/bluez/hci0/dev_00_AA_01_01_00_02
Auto Accepting...
[NEW] Transport /org/bluez/hci0/dev_00_AA_01_01_00_02/pac_sink0/fd1
Endpoint: SetConfiguration
Transport /org/bluez/hci0/dev_00_AA_01_01_00_02/pac_sink0/fd1
Device: /org/bluez/hci0/dev_00_AA_01_01_00_02
Auto Accepting...
[bluetooth]# transport.acquire /org/bluez/hci0/dev_00_AA_01_01_00_02/pac_sink0/fd1
Frédéric Danis (2):
profiles: Allow linked transport to release the fd
profiles: Update transport Links property on state change to QoS
Luiz Augusto von Dentz (9):
adapter: Add btd_adapter_find_device_by_fd
lib/uuid: Add PACS/ASCS UUIDs
shared/bap: Add initial code for handling BAP
profiles: Add initial code for bap plugin
shared: Add definition for LC3 codec
media-api: Add SelectProperties
test/simple-endpoint: Add support for LC3 endpoints
client/player: Add support for PACS endpoints
client/player: Use QoS interval on transport.send
Makefile.am | 3 +-
Makefile.plugins | 5 +
client/player.c | 688 +++++-
configure.ac | 4 +
doc/media-api.txt | 88 +-
lib/uuid.h | 18 +
profiles/audio/bap.c | 1324 ++++++++++
profiles/audio/media.c | 678 ++++-
profiles/audio/transport.c | 596 ++++-
profiles/audio/transport.h | 3 +-
src/adapter.c | 33 +
src/adapter.h | 1 +
src/device.c | 10 +-
src/shared/ascs.h | 196 ++
src/shared/bap.c | 4776 ++++++++++++++++++++++++++++++++++++
src/shared/bap.h | 269 ++
src/shared/lc3.h | 112 +
test/simple-endpoint | 17 +
tools/bluetooth-player.c | 1 -
19 files changed, 8678 insertions(+), 144 deletions(-)
create mode 100644 profiles/audio/bap.c
create mode 100644 src/shared/ascs.h
create mode 100644 src/shared/bap.c
create mode 100644 src/shared/bap.h
create mode 100644 src/shared/lc3.h
--
2.37.2
From: Luiz Augusto von Dentz <[email protected]>
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)
--
2.37.2
From: Luiz Augusto von Dentz <[email protected]>
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,
--
2.37.2
From: Luiz Augusto von Dentz <[email protected]>
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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#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);
--
2.37.2
From: Luiz Augusto von Dentz <[email protected]>
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 <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#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 <stdbool.h>
+#include <inttypes.h>
+
+#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);
--
2.37.2
From: Frédéric Danis <[email protected]>
Currently, the Links property is only sent after the first call to the
Acquire method, this cmay result in a rejection if a linked transport tries
to call the Acquire method before receiving the Links update.
---
profiles/audio/transport.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index f36e609cbd7a..f233643a6a3f 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -1321,6 +1321,7 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state,
/* If a request is pending wait it to complete */
if (owner && owner->pending)
return;
+ bap_update_links(transport);
transport_update_playing(transport, FALSE);
return;
case BT_BAP_STREAM_STATE_DISABLING:
--
2.37.2
From: Frédéric Danis <[email protected]>
Multiple transports can be linked when using LE Audio BAP.
In this case only one transport is used to Acquire the file descriptor
which will be shared by all linked transports.
In the same way, any transport can Release the file descriptor.
---
profiles/audio/transport.c | 43 ++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 47db2a8026b2..f36e609cbd7a 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -281,9 +281,26 @@ static void media_owner_free(struct media_owner *owner)
g_free(owner);
}
+static void linked_transport_remove_owner(void *data, void *user_data)
+{
+ struct bt_bap_stream *stream = data;
+ struct media_owner *owner = user_data;
+ struct media_transport *transport;
+
+ transport = find_transport_by_bap_stream(stream);
+ if (!transport) {
+ error("Unable to find transport");
+ return;
+ }
+
+ DBG("Transport %s Owner %s", transport->path, owner->name);
+ transport->owner = NULL;
+}
+
static void media_transport_remove_owner(struct media_transport *transport)
{
struct media_owner *owner = transport->owner;
+ struct bap_transport *bap = transport->data;
if (!transport->owner)
return;
@@ -295,6 +312,9 @@ static void media_transport_remove_owner(struct media_transport *transport)
media_request_reply(owner->pending, EIO);
transport->owner = NULL;
+ if (bap->linked)
+ queue_foreach(bt_bap_stream_io_get_links(bap->stream),
+ linked_transport_remove_owner, owner);
if (owner->watch)
g_dbus_remove_watch(btd_get_dbus_connection(), owner->watch);
@@ -452,11 +472,34 @@ static void media_owner_exit(DBusConnection *connection, void *user_data)
media_transport_remove_owner(owner->transport);
}
+static void linked_transport_set_owner(void *data, void *user_data)
+{
+ struct bt_bap_stream *stream = data;
+ struct media_owner *owner = user_data;
+ struct media_transport *transport;
+
+ transport = find_transport_by_bap_stream(stream);
+ if (!transport) {
+ error("Unable to find transport");
+ return;
+ }
+
+ DBG("Transport %s Owner %s", transport->path, owner->name);
+ transport->owner = owner;
+}
+
static void media_transport_set_owner(struct media_transport *transport,
struct media_owner *owner)
{
+ struct bap_transport *bap = transport->data;
+
DBG("Transport %s Owner %s", transport->path, owner->name);
transport->owner = owner;
+
+ if (bap->linked)
+ queue_foreach(bt_bap_stream_io_get_links(bap->stream),
+ linked_transport_set_owner, owner);
+
owner->transport = transport;
owner->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(),
owner->name,
--
2.37.2
From: Luiz Augusto von Dentz <[email protected]>
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)
--
2.37.2
From: Luiz Augusto von Dentz <[email protected]>
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", "<transport>", cmd_show_transport,
"Transport information",
transport_generator },
- { "acquire", "<transport>", cmd_acquire_transport,
+ { "acquire", "<transport> [transport1...]", cmd_acquire_transport,
"Acquire Transport",
transport_generator },
- { "release", "<transport>", cmd_release_transport,
+ { "release", "<transport> [transport1...]", cmd_release_transport,
"Release Transport",
transport_generator },
{ "send", "<transport> <filename>", 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 <glib.h>
#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)
--
2.37.2
From: Luiz Augusto von Dentz <[email protected]>
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)
--
2.37.2
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <[email protected]>:
On Fri, 26 Aug 2022 17:05:29 -0700 you wrote:
> From: Luiz Augusto von Dentz <[email protected]>
>
> iThis set add initial support for BAP (Basic Audio Profile) which is
> an essential part of LE Audio responsible for stream control.
>
> The plugin is considered experimental and depends on ISO socket in order
> to work so the following setting needs to be changed in order to enable it:
>
> [...]
Here is the summary with links:
- [v3,01/11] adapter: Add btd_adapter_find_device_by_fd
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=67dfe38b660d
- [v3,02/11] lib/uuid: Add PACS/ASCS UUIDs
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=d7b78586afdd
- [v3,03/11] shared/bap: Add initial code for handling BAP
(no matching commit)
- [v3,04/11] profiles: Add initial code for bap plugin
(no matching commit)
- [v3,05/11] shared: Add definition for LC3 codec
(no matching commit)
- [v3,06/11] media-api: Add SelectProperties
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=3579cf8bf6a7
- [v3,07/11] test/simple-endpoint: Add support for LC3 endpoints
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=f081ac884591
- [v3,08/11] client/player: Add support for PACS endpoints
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=dc61ec4419eb
- [v3,09/11] client/player: Use QoS interval on transport.send
(no matching commit)
- [v3,10/11] profiles: Allow linked transport to release the fd
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=34e8c20d488f
- [v3,11/11] profiles: Update transport Links property on state change to QoS
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=b843a5c5b4b5
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html