* [PATCH BlueZ 00/10] Add TMAP & GMAP information services
@ 2025-11-23 16:17 Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 01/10] shared/tmap: add TMAP Service Pauli Virtanen
` (10 more replies)
0 siblings, 11 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add support for TMAP and GMAP services. They contain only device
audio capability bitmasks.
Expose the values from remote devices in
org.bluez.MediaEndpoint->SupportedFeatures
Not sure if this should be org.bluez.Device->SupportedFeatures instead.
Sound server can use theses to determine which mandatory capabilities
devices have.
Some boilerplate maybe could be avoided by putting these into the BAP
service, but did them separately here.
TODO (maybe later): advertise local service values, these could be
configured either via config file or endpoint SupportedFeatures. Maybe
config file is better?
Pauli Virtanen (10):
shared/tmap: add TMAP Service
test-tmap: add test for TMAP Service
tmap: add TMAP profile
bap: have unicast client wait for VCS & TMAS
shared/gmap: add GMAP Service
test-gmap: add test for GMAP Service
gmap: Add GMAP profile
bap: unicast client wait for GMAP service
doc: org.bluez.MediaEndpoint: add SupportedFeatures
bap: add SupportedFeatures for MediaEndpoints
.gitignore | 2 +
Makefile.am | 14 +
Makefile.plugins | 10 +
configure.ac | 14 +
doc/org.bluez.MediaEndpoint.rst | 99 +++++++
lib/bluetooth/uuid.h | 12 +
profiles/audio/bap.c | 146 +++++++++-
profiles/audio/gmap.c | 203 +++++++++++++
profiles/audio/tmap.c | 203 +++++++++++++
src/shared/gmap.c | 402 ++++++++++++++++++++++++++
src/shared/gmap.h | 70 +++++
src/shared/tmap.c | 302 +++++++++++++++++++
src/shared/tmap.h | 39 +++
unit/test-gmap.c | 496 ++++++++++++++++++++++++++++++++
unit/test-tmap.c | 378 ++++++++++++++++++++++++
15 files changed, 2387 insertions(+), 3 deletions(-)
create mode 100644 profiles/audio/gmap.c
create mode 100644 profiles/audio/tmap.c
create mode 100644 src/shared/gmap.c
create mode 100644 src/shared/gmap.h
create mode 100644 src/shared/tmap.c
create mode 100644 src/shared/tmap.h
create mode 100644 unit/test-gmap.c
create mode 100644 unit/test-tmap.c
--
2.51.1
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH BlueZ 01/10] shared/tmap: add TMAP Service
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-23 17:24 ` Add TMAP & GMAP information services bluez.test.bot
2025-11-23 16:17 ` [PATCH BlueZ 02/10] test-tmap: add test for TMAP Service Pauli Virtanen
` (9 subsequent siblings)
10 siblings, 1 reply; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
TMAP Service is just a simple service with a Role bitmask.
The value can be used to figure out which TMAP roles the remote device
claims it supports (matters for available mandatory features). Also can
advertise the same for remote clients.
---
Makefile.am | 1 +
lib/bluetooth/uuid.h | 4 +
src/shared/tmap.c | 302 +++++++++++++++++++++++++++++++++++++++++++
src/shared/tmap.h | 39 ++++++
4 files changed, 346 insertions(+)
create mode 100644 src/shared/tmap.c
create mode 100644 src/shared/tmap.h
diff --git a/Makefile.am b/Makefile.am
index 9f06d60e6..d2e67012d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -242,6 +242,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
src/shared/csip.c src/shared/csip.h \
src/shared/bass.h src/shared/bass.c \
src/shared/ccp.h src/shared/ccp.c \
+ src/shared/tmap.c src/shared/tmap.h \
src/shared/lc3.h src/shared/tty.h \
src/shared/bap-defs.h \
src/shared/asha.h src/shared/asha.c \
diff --git a/lib/bluetooth/uuid.h b/lib/bluetooth/uuid.h
index 479986f06..771f7675d 100644
--- a/lib/bluetooth/uuid.h
+++ b/lib/bluetooth/uuid.h
@@ -212,6 +212,10 @@ extern "C" {
#define MEDIA_CP_OP_SUPPORTED_CHRC_UUID 0x2ba5
#define MEDIA_CONTENT_CONTROL_ID_CHRC_UUID 0x2bba
+/* Telephony and Media Audio Service */
+#define TMAS_UUID 0x1855
+#define TMAP_ROLE_CHRC_UUID 0x2b51
+
/* Coordinated Set Identification Profile(CSIP) */
#define CSIS_UUID 0x1846
#define CS_SIRK 0x2B84
diff --git a/src/shared/tmap.c b/src/shared/tmap.c
new file mode 100644
index 000000000..6fc96cd89
--- /dev/null
+++ b/src/shared/tmap.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/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/tmap.h"
+#include "src/shared/bap.h"
+
+#define DBG(_tmap, fmt, arg...) \
+ tmap_debug(_tmap, "%s:%s() " fmt, __FILE__, __func__, ## arg)
+
+struct bt_tmas_db {
+ struct gatt_db *db;
+ struct gatt_db_attribute *service;
+ struct gatt_db_attribute *role;
+ uint16_t role_value;
+};
+
+struct bt_tmap {
+ int ref_count;
+ struct bt_gatt_client *client;
+ struct bt_tmas_db db;
+
+ int idle_id;
+ bt_tmap_ready_func_t ready_func;
+ void *ready_data;
+
+ bt_tmap_debug_func_t debug_func;
+ bt_tmap_destroy_func_t debug_destroy;
+ void *debug_data;
+};
+
+static struct queue *instances;
+
+static void tmap_free(void *data)
+{
+ struct bt_tmap *tmap = data;
+
+ if (tmap->client) {
+ bt_gatt_client_idle_unregister(tmap->client, tmap->idle_id);
+ bt_gatt_client_unref(tmap->client);
+ } else {
+ gatt_db_remove_service(tmap->db.db, tmap->db.service);
+ gatt_db_unref(tmap->db.db);
+ }
+
+ queue_remove(instances, tmap);
+ if (queue_isempty(instances)) {
+ queue_destroy(instances, NULL);
+ instances = NULL;
+ }
+
+ free(tmap);
+}
+
+struct bt_tmap *bt_tmap_ref(struct bt_tmap *tmap)
+{
+ if (!tmap)
+ return NULL;
+
+ __sync_fetch_and_add(&tmap->ref_count, 1);
+
+ return tmap;
+}
+
+void bt_tmap_unref(struct bt_tmap *tmap)
+{
+ if (!tmap)
+ return;
+
+ if (__sync_sub_and_fetch(&tmap->ref_count, 1))
+ return;
+
+ tmap_free(tmap);
+}
+
+static void tmap_debug(struct bt_tmap *tmap, const char *format, ...)
+{
+ va_list ap;
+
+ if (!tmap || !format || !tmap->debug_func)
+ return;
+
+ va_start(ap, format);
+ util_debug_va(tmap->debug_func, tmap->debug_data, format, ap);
+ va_end(ap);
+}
+
+bool bt_tmap_set_debug(struct bt_tmap *tmap, bt_tmap_debug_func_t cb,
+ void *user_data, bt_tmap_destroy_func_t destroy)
+{
+ if (!tmap)
+ return false;
+
+ if (tmap->debug_destroy)
+ tmap->debug_destroy(tmap->debug_data);
+
+ tmap->debug_func = cb;
+ tmap->debug_destroy = destroy;
+ tmap->debug_data = user_data;
+
+ return true;
+}
+
+uint16_t bt_tmap_get_role(struct bt_tmap *tmap)
+{
+ if (!tmap)
+ return 0;
+
+ return tmap->db.role_value;
+}
+
+/*
+ * TMA Client
+ */
+
+static void tmap_role_read(bool success, uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct bt_tmap *tmap = user_data;
+ struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
+ uint16_t role;
+
+ if (!success) {
+ DBG(tmap, "Unable to read Role: error 0x%02x", att_ecode);
+ return;
+ }
+
+ if (!util_iov_pull_le16(&iov, &role)) {
+ DBG(tmap, "Invalid Role");
+ return;
+ }
+
+ DBG(tmap, "Role 0x%x", role);
+ tmap->db.role_value = role & BT_TMAP_ROLE_MASK;
+}
+
+static void foreach_tmap_char(struct gatt_db_attribute *attr, void *user_data)
+{
+ struct bt_tmap *tmap = user_data;
+ uint16_t value_handle;
+ bt_uuid_t uuid, uuid_role;
+
+ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
+ NULL, NULL, &uuid))
+ return;
+
+ bt_uuid16_create(&uuid_role, TMAP_ROLE_CHRC_UUID);
+
+ if (!bt_uuid_cmp(&uuid, &uuid_role)) {
+ DBG(tmap, "TMAS Role Char found: handle 0x%04x", value_handle);
+ bt_gatt_client_read_value(tmap->client, value_handle,
+ tmap_role_read, tmap, NULL);
+ return;
+ }
+}
+
+static void foreach_tmap_service(struct gatt_db_attribute *attr,
+ void *user_data)
+{
+ struct bt_tmap *tmap = user_data;
+
+ gatt_db_service_set_claimed(attr, true);
+ gatt_db_service_foreach_char(attr, foreach_tmap_char, tmap);
+}
+
+static void tmap_idle(void *data)
+{
+ struct bt_tmap *tmap = data;
+
+ tmap->idle_id = 0;
+
+ if (!instances)
+ instances = queue_new();
+ queue_push_tail(instances, tmap);
+
+ if (tmap->ready_func)
+ tmap->ready_func(tmap, tmap->ready_data);
+}
+
+struct bt_tmap *bt_tmap_attach(struct bt_gatt_client *client,
+ bt_tmap_ready_func_t ready, void *user_data)
+{
+ struct bt_tmap *tmap;
+ bt_uuid_t uuid;
+
+ if (!client)
+ return NULL;
+
+ client = bt_gatt_client_clone(client);
+ if (!client)
+ return NULL;
+
+ tmap = new0(struct bt_tmap, 1);
+ tmap->client = client;
+ tmap->ready_func = ready;
+ tmap->ready_data = user_data;
+ tmap->db.db = bt_gatt_client_get_db(tmap->client);
+
+ bt_uuid16_create(&uuid, TMAS_UUID);
+ gatt_db_foreach_service(tmap->db.db, &uuid, foreach_tmap_service, tmap);
+
+ tmap->idle_id = bt_gatt_client_idle_register(tmap->client, tmap_idle,
+ tmap, NULL);
+
+ return bt_tmap_ref(tmap);
+}
+
+/*
+ * TMAS Service
+ */
+
+static void tmas_role_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_tmas_db *db = user_data;
+ struct iovec iov = {
+ .iov_base = &db->role_value,
+ .iov_len = sizeof(db->role_value)
+ };
+
+ gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+ iov.iov_len);
+}
+
+static bool match_db(const void *data, const void *match_data)
+{
+ const struct bt_tmap *tmap = data;
+
+ return tmap->db.db == match_data;
+}
+
+struct bt_tmap *bt_tmap_find(struct gatt_db *db)
+{
+ return db ? queue_find(instances, match_db, db) : NULL;
+}
+
+struct bt_tmap *bt_tmap_add_db(struct gatt_db *db)
+{
+ struct bt_tmap *tmap;
+ bt_uuid_t uuid;
+
+ if (!db || queue_find(instances, match_db, db))
+ return NULL;
+
+ tmap = new0(struct bt_tmap, 1);
+ tmap->db.db = gatt_db_ref(db);
+
+ bt_uuid16_create(&uuid, TMAS_UUID);
+ tmap->db.service = gatt_db_add_service(db, &uuid, true, 3);
+
+ bt_uuid16_create(&uuid, TMAP_ROLE_CHRC_UUID);
+ tmap->db.role = gatt_db_service_add_characteristic(
+ tmap->db.service,
+ &uuid,
+ BT_ATT_PERM_READ,
+ BT_GATT_CHRC_PROP_READ,
+ tmas_role_read, NULL,
+ &tmap->db);
+
+ if (!instances)
+ instances = queue_new();
+ queue_push_tail(instances, tmap);
+
+ return bt_tmap_ref(tmap);
+}
+
+void bt_tmap_set_role(struct bt_tmap *tmap, uint16_t role)
+{
+ if (tmap->client)
+ return;
+
+ tmap->db.role_value = role & BT_TMAP_ROLE_MASK;
+
+ /* Expose value only when first set. Role does not have Notify. */
+ gatt_db_service_set_active(tmap->db.service, true);
+}
diff --git a/src/shared/tmap.h b/src/shared/tmap.h
new file mode 100644
index 000000000..9d872f0b6
--- /dev/null
+++ b/src/shared/tmap.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ *
+ */
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+#define BT_TMAP_ROLE_CG BIT(0)
+#define BT_TMAP_ROLE_CT BIT(1)
+#define BT_TMAP_ROLE_UMS BIT(2)
+#define BT_TMAP_ROLE_UMR BIT(3)
+#define BT_TMAP_ROLE_BMS BIT(4)
+#define BT_TMAP_ROLE_BMR BIT(5)
+#define BT_TMAP_ROLE_MASK (BIT(6) - 1)
+
+struct bt_tmap;
+
+typedef void (*bt_tmap_ready_func_t)(struct bt_tmap *tmap, void *user_data);
+typedef void (*bt_tmap_destroy_func_t)(void *user_data);
+typedef void (*bt_tmap_debug_func_t)(const char *str, void *user_data);
+
+struct bt_tmap *bt_tmap_ref(struct bt_tmap *tmap);
+void bt_tmap_unref(struct bt_tmap *tmap);
+
+struct bt_tmap *bt_tmap_attach(struct bt_gatt_client *client,
+ bt_tmap_ready_func_t ready, void *user_data);
+struct bt_tmap *bt_tmap_find(struct gatt_db *db);
+struct bt_tmap *bt_tmap_add_db(struct gatt_db *db);
+
+uint16_t bt_tmap_get_role(struct bt_tmap *tmap);
+void bt_tmap_set_role(struct bt_tmap *tmas, uint16_t role);
+
+bool bt_tmap_set_debug(struct bt_tmap *tmap, bt_tmap_debug_func_t cb,
+ void *user_data, bt_tmap_destroy_func_t destroy);
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 02/10] test-tmap: add test for TMAP Service
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 01/10] shared/tmap: add TMAP Service Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 03/10] tmap: add TMAP profile Pauli Virtanen
` (8 subsequent siblings)
10 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add tests on TMAP service for reading the role attribute.
---
.gitignore | 1 +
Makefile.am | 6 +
unit/test-tmap.c | 378 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 385 insertions(+)
create mode 100644 unit/test-tmap.c
diff --git a/.gitignore b/.gitignore
index c0aaafaea..48bf72a4a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -118,6 +118,7 @@ unit/test-hog
unit/test-bap
unit/test-bass
unit/test-battery
+unit/test-tmap
tools/mgmt-tester
tools/smp-tester
tools/gap-tester
diff --git a/Makefile.am b/Makefile.am
index d2e67012d..11e632c02 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -702,6 +702,12 @@ unit_test_vcp_SOURCES = unit/test-vcp.c $(btio_sources)
unit_test_vcp_LDADD = src/libshared-glib.la \
lib/libbluetooth-internal.la $(GLIB_LIBS)
+unit_tests += unit/test-tmap
+
+unit_test_tmap_SOURCES = unit/test-tmap.c $(btio_sources)
+unit_test_tmap_LDADD = src/libshared-glib.la \
+ lib/libbluetooth-internal.la $(GLIB_LIBS)
+
unit_tests += unit/test-battery
unit_test_battery_SOURCES = unit/test-battery.c
diff --git a/unit/test-tmap.c b/unit/test-tmap.c
new file mode 100644
index 000000000..481453112
--- /dev/null
+++ b/unit/test-tmap.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+
+#include <glib.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/uuid.h"
+#include "src/shared/util.h"
+#include "src/shared/tester.h"
+#include "src/shared/queue.h"
+#include "src/shared/att.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
+#include "src/shared/gatt-server.h"
+#include "src/shared/io.h"
+#include "src/shared/tmap.h"
+
+struct test_config {
+ uint16_t role;
+};
+
+struct test_data {
+ struct gatt_db *db;
+ struct bt_gatt_server *server;
+ struct bt_gatt_client *client;
+ struct bt_tmap *tmap;
+ size_t iovcnt;
+ struct iovec *iov;
+ const struct test_config *cfg;
+};
+
+#define iov_data(args...) ((const struct iovec[]) { args })
+
+#define define_test(name, setup, function, _cfg, args...) \
+ do { \
+ const struct iovec iov[] = { args }; \
+ static struct test_data data; \
+ data.iovcnt = ARRAY_SIZE(iov_data(args)); \
+ data.iov = util_iov_dup(iov, ARRAY_SIZE(iov_data(args))); \
+ data.cfg = _cfg; \
+ tester_add(name, &data, setup, function, \
+ test_teardown); \
+ } while (0)
+
+static void print_debug(const char *str, void *user_data)
+{
+ const char *prefix = user_data;
+
+ if (tester_use_debug())
+ tester_debug("%s%s", prefix, str);
+}
+
+static void test_teardown(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+
+ bt_gatt_client_unref(data->client);
+
+ bt_gatt_server_unref(data->server);
+ util_iov_free(data->iov, data->iovcnt);
+
+ gatt_db_unref(data->db);
+
+ bt_tmap_unref(data->tmap);
+ tester_teardown_complete();
+}
+
+/* ATT: Exchange MTU Response (0x03) len 2
+ * Server RX MTU: 64
+ * ATT: Exchange MTU Request (0x02) len 2
+ * Client RX MTU: 64
+ * ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute type: Server Supported Features (0x2b3a)
+ * ATT: Error Response (0x01) len 4
+ * Read By Type Request (0x08)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define TMAS_MTU_FEATURES \
+ IOV_DATA(0x02, 0x40, 0x00), \
+ IOV_DATA(0x03, 0x40, 0x00), \
+ IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x3a, 0x2b), \
+ IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+/* ATT: Read By Group Type Request (0x10) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute group type: Primary Service (0x2800)
+ * ATT: Read By Group Type Response (0x11) len 37
+ * Attribute data length: 6
+ * Attribute group list: 1 entries
+ * Handle range: 0x0001-0x0003
+ * UUID: Telephony and Media Audio (0x1855)
+ * ATT: Read By Group Type Request (0x10) len 6
+ * Handle range: 0x0004-0xffff
+ * Attribute group type: Primary Service (0x2800)
+ * ATT: Error Response (0x01) len 4
+ * Read By Group Type Request (0x10)
+ * Handle: 0x0004
+ * Error: Attribute Not Found (0x0a)
+ */
+#define TMAS_PRIMARY_SERVICE \
+ IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ IOV_DATA(0x11, 0x06, \
+ 0x01, 0x00, 0x03, 0x00, 0x55, 0x18), \
+ IOV_DATA(0x10, 0x04, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ IOV_DATA(0x01, 0x10, 0x04, 0x00, 0x0a)
+
+
+/* ATT: Read By Group Type Request (0x10) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute group type: Secondary Service (0x2801)
+ * ATT: Error Response (0x01) len 4
+ * Read By Group Type Request (0x10)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define TMAS_SECONDARY_SERVICE \
+ IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28), \
+ IOV_DATA(0x01, 0x10, 0x01, 0x00, 0x0a)
+
+/* ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0x0003
+ * Attribute group type: Include (0x2802)
+ * ATT: Error Response (0x01) len 4
+ * Read By Group Type Request (0x10)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define TMAS_INCLUDE \
+ IOV_DATA(0x08, 0x01, 0x00, 0x03, 0x00, 0x02, 0x28), \
+ IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+/* ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0x0003
+ * Attribute type: Characteristic (0x2803)
+ * ATT: Read By Type Response (0x09) len 57
+ * Attribute data length: 7
+ * Attribute data list: 8 entries
+ * Handle: 0x0002
+ * Value: 020300512b
+ * Properties: 0x02
+ * Read (0x02)
+ * Value Handle: 0x0003
+ * Value UUID: TMAP Role (0x2b51)
+ * ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0003-0x0004
+ * Attribute type: Characteristic (0x2803)
+ * ATT: Error Response (0x01) len 4
+ * Read By Type Request (0x08)
+ * Handle: 0x0022
+ * Error: Attribute Not Found (0x0a)
+ */
+#define TMAS_FIND_CHRC \
+ IOV_DATA(0x08, 0x01, 0x00, 0x03, 0x00, 0x03, 0x28), \
+ IOV_DATA(0x09, 0x07, \
+ 0x02, 0x00, 0x02, 0x03, 0x00, 0x51, 0x2b), \
+ IOV_DATA(0x08, 0x03, 0x00, 0x03, 0x00, 0x03, 0x28), \
+ IOV_DATA(0x01, 0x08, 0x03, 0x00, 0x0a)
+
+#define ROLE_HND 0x03, 0x00
+
+/* ACL Data TX: Handle 42 flags 0x00 dlen 11
+ * ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute type: Database Hash (0x2b2a)
+ * ATT: Error Response (0x01) len 4
+ * Read By Type Request (0x08)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define TMAS_DATABASE_HASH \
+ IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x2a, 0x2b), \
+ IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+/* GATT Discover All procedure */
+static const struct iovec setup_data[] = {
+ TMAS_MTU_FEATURES,
+ TMAS_PRIMARY_SERVICE,
+ TMAS_SECONDARY_SERVICE,
+ TMAS_INCLUDE,
+ TMAS_FIND_CHRC,
+ TMAS_DATABASE_HASH,
+};
+
+static void setup_complete_cb(const void *user_data)
+{
+ tester_setup_complete();
+}
+
+static void test_setup_server(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ struct bt_att *att;
+ struct gatt_db *db;
+ struct io *io;
+
+ io = tester_setup_io(setup_data, ARRAY_SIZE(setup_data));
+ g_assert(io);
+
+ tester_io_set_complete_func(setup_complete_cb);
+
+ db = gatt_db_new();
+ g_assert(db);
+
+ data->tmap = bt_tmap_add_db(db);
+ bt_tmap_set_debug(data->tmap, print_debug, "tmap:", NULL);
+
+ bt_tmap_set_role(data->tmap, data->cfg->role);
+
+ att = bt_att_new(io_get_fd(io), false);
+ g_assert(att);
+ bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL);
+
+ data->server = bt_gatt_server_new(db, att, 64, 0);
+ g_assert(data->server);
+ bt_gatt_server_set_debug(data->server, print_debug, "bt_gatt_server:",
+ NULL);
+
+ tester_io_send();
+
+ bt_att_unref(att);
+ gatt_db_unref(db);
+}
+
+static void test_complete_cb(const void *user_data)
+{
+ tester_test_passed();
+}
+
+static void test_server(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ struct io *io;
+
+ io = tester_setup_io(data->iov, data->iovcnt);
+ g_assert(io);
+
+ tester_io_set_complete_func(test_complete_cb);
+
+ tester_io_send();
+}
+
+static void setup_ready_cb(bool success, uint8_t att_ecode, void *user_data)
+{
+ if (!success)
+ tester_setup_failed();
+ else
+ tester_setup_complete();
+}
+
+static void test_setup(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ struct bt_att *att;
+ struct gatt_db *db;
+ struct io *io;
+
+ io = tester_setup_io(setup_data, ARRAY_SIZE(setup_data));
+ g_assert(io);
+
+ att = bt_att_new(io_get_fd(io), false);
+ g_assert(att);
+
+ bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL);
+
+ db = gatt_db_new();
+ g_assert(db);
+
+ data->client = bt_gatt_client_new(db, att, 64, 0);
+ g_assert(data->client);
+
+ bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
+ NULL);
+
+ bt_gatt_client_ready_register(data->client, setup_ready_cb, data,
+ NULL);
+
+ bt_att_unref(att);
+ gatt_db_unref(db);
+}
+
+static void client_ready_cb(struct bt_tmap *tmap, void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+
+ if (bt_tmap_get_role(tmap) != data->cfg->role) {
+ tester_test_failed();
+ return;
+ }
+
+ tester_test_passed();
+}
+
+static void test_client(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ struct io *io;
+
+ io = tester_setup_io(data->iov, data->iovcnt);
+ g_assert(io);
+
+ tester_io_set_complete_func(NULL);
+
+ data->tmap = bt_tmap_attach(data->client, client_ready_cb, data);
+ g_assert(data->tmap);
+
+ bt_tmap_set_debug(data->tmap, print_debug, "tmap:", NULL);
+}
+
+/* ATT: Read Request (0x0a) len 2
+ * Handle: 0x0003 Type: TMAP Role (0x2b51)
+ * ATT: Read Response (0x0b) len 24
+ * Value: _value
+ * Handle: 0x0003 Type: TMAP Role (0x2b51)
+ */
+#define READ_ROLE(value...) \
+ IOV_DATA(0x0a, ROLE_HND), \
+ IOV_DATA(0x0b, value)
+
+#define CGGIT_CHA \
+ /* Step 1. in CGGIT/CHA skipped, should be unnecessary */ \
+ READ_ROLE(0x24, 0x00)
+#define CGGIT_CHA_RFU \
+ /* Step 1. in SGGIT/CHA skipped, should be unnecessary */ \
+ READ_ROLE(0x24, 0xff)
+
+const struct test_config cfg_read_role = {
+ .role = BT_TMAP_ROLE_UMS | BT_TMAP_ROLE_BMR,
+};
+
+static void test_tmap_cl(void)
+{
+ /* Sec. 4.5.1 TMA Client */
+ define_test("TMAP/CL/CGGIT/CHA/BV-01-C [TMAP Role Read Characteristic, "
+ "Client]",
+ test_setup, test_client, &cfg_read_role, CGGIT_CHA);
+ define_test("TMAP/CL/TMAS/BI-01-C [Client Ignores RFU Bits in TMAP "
+ "Role Characteristic]",
+ test_setup, test_client, &cfg_read_role, CGGIT_CHA_RFU);
+}
+
+#define SGGIT_CHA \
+ /* Step 1. in CGGIT/CHA skipped, should be unnecessary */ \
+ READ_ROLE(0x24, 0x00)
+
+static void test_tmap_sr(void)
+{
+ /* Sec. 4.5.2 TMA Server */
+ define_test("TMAP/SR/SGGIT/CHA/BV-01-C [Characteristic GGIT - "
+ "TMAP Role]",
+ test_setup_server, test_server, &cfg_read_role,
+ SGGIT_CHA);
+}
+
+int main(int argc, char *argv[])
+{
+ tester_init(&argc, &argv);
+ test_tmap_cl();
+ test_tmap_sr();
+
+ return tester_run();
+}
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 03/10] tmap: add TMAP profile
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 01/10] shared/tmap: add TMAP Service Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 02/10] test-tmap: add test for TMAP Service Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 04/10] bap: have unicast client wait for VCS & TMAS Pauli Virtanen
` (7 subsequent siblings)
10 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Read TMAP Role from remote devices and enable advertising the value for
local services.
---
Makefile.plugins | 5 ++
configure.ac | 7 ++
profiles/audio/tmap.c | 203 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 215 insertions(+)
create mode 100644 profiles/audio/tmap.c
diff --git a/Makefile.plugins b/Makefile.plugins
index bae4363d0..c976c66f4 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -146,6 +146,11 @@ builtin_modules += csip
builtin_sources += profiles/audio/csip.c
endif
+if TMAP
+builtin_modules += tmap
+builtin_sources += profiles/audio/tmap.c
+endif
+
if ASHA
builtin_modules += asha
builtin_sources += profiles/audio/asha.h profiles/audio/asha.c
diff --git a/configure.ac b/configure.ac
index fa7ed185f..29a322cb5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -227,6 +227,13 @@ AC_ARG_ENABLE(csip, AS_HELP_STRING([--disable-csip],
[disable CSIP profile]), [enable_csip=${enableval}])
AM_CONDITIONAL(CSIP, test "${enable_csip}" != "no")
+AC_ARG_ENABLE(tmap, AS_HELP_STRING([--disable-tmap],
+ [disable TMAP profile]), [enable_tmap=${enableval}])
+AM_CONDITIONAL(TMAP, test "${enable_tmap}" != "no")
+if test "${enable_tmap}" != "no"; then
+ AC_DEFINE(HAVE_TMAP, 1, [Define to 1 if you have TMAP support.])
+fi
+
AC_ARG_ENABLE(asha, AS_HELP_STRING([--disable-asha],
[disable ASHA support]), [enable_asha=${enableval}])
AM_CONDITIONAL(ASHA, test "${enable_asha}" != "no")
diff --git a/profiles/audio/tmap.c b/profiles/audio/tmap.c
new file mode 100644
index 000000000..2525c33bf
--- /dev/null
+++ b/profiles/audio/tmap.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include "gdbus/gdbus.h"
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/hci.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/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/tmap.h"
+
+#include "btio/btio.h"
+#include "src/plugin.h"
+#include "src/adapter.h"
+#include "src/gatt-database.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "src/log.h"
+#include "src/error.h"
+
+#include "vcp.h"
+#include "transport.h"
+
+#define TMAS_UUID_STR "00001855-0000-1000-8000-00805f9b34fb"
+
+static void tmap_debug(const char *str, void *user_data)
+{
+ DBG_IDX(0xffff, "%s", str);
+}
+
+static void service_ready(struct bt_tmap *tmap, void *user_data)
+{
+ struct btd_service *service = user_data;
+
+ btd_service_connecting_complete(service, 0);
+}
+
+static struct bt_tmap *add_service(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 bt_tmap *tmap = btd_service_get_user_data(service);
+
+ if (tmap)
+ return tmap;
+
+ tmap = bt_tmap_attach(client, service_ready, service);
+ if (!tmap) {
+ error("TMAP client unable to attach");
+ return NULL;
+ }
+
+ bt_tmap_set_debug(tmap, tmap_debug, NULL, NULL);
+
+ btd_service_set_user_data(service, tmap);
+ return tmap;
+}
+
+static void remove_service(struct btd_service *service)
+{
+ struct bt_tmap *tmap = btd_service_get_user_data(service);
+
+ if (!tmap)
+ return;
+
+ btd_service_set_user_data(service, NULL);
+ bt_tmap_unref(tmap);
+}
+
+static int tmap_accept(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ struct bt_tmap *tmap;
+ char addr[18];
+
+ ba2str(device_get_address(device), addr);
+ DBG("%s", addr);
+
+ tmap = add_service(service);
+ if (!tmap)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int tmap_disconnect(struct btd_service *service)
+{
+ remove_service(service);
+
+ btd_service_disconnecting_complete(service, 0);
+ return 0;
+}
+
+static int tmap_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ char addr[18];
+
+ ba2str(device_get_address(device), addr);
+ DBG("%s", addr);
+ return 0;
+}
+
+static void tmap_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ char addr[18];
+
+ ba2str(device_get_address(device), addr);
+ DBG("%s", addr);
+}
+
+static int tmap_adapter_probe(struct btd_profile *p,
+ struct btd_adapter *adapter)
+{
+ struct btd_gatt_database *database = btd_adapter_get_database(adapter);
+ struct bt_tmap *tmap;
+
+ DBG("Add TMAP server %s", adapter_get_path(adapter));
+ tmap = bt_tmap_add_db(btd_gatt_database_get_db(database));
+
+ bt_tmap_set_debug(tmap, tmap_debug, NULL, NULL);
+ return 0;
+}
+
+static void tmap_adapter_remove(struct btd_profile *p,
+ struct btd_adapter *adapter)
+{
+ struct btd_gatt_database *database = btd_adapter_get_database(adapter);
+ struct bt_tmap *tmap;
+
+ DBG("Remove TMAP server %s", adapter_get_path(adapter));
+ tmap = bt_tmap_find(btd_gatt_database_get_db(database));
+ bt_tmap_unref(tmap);
+}
+
+static struct btd_profile tmap_profile = {
+ .name = "tmap",
+ .priority = BTD_PROFILE_PRIORITY_MEDIUM,
+ .remote_uuid = TMAS_UUID_STR,
+
+ .device_probe = tmap_probe,
+ .device_remove = tmap_remove,
+ .accept = tmap_accept,
+ .disconnect = tmap_disconnect,
+
+ .adapter_probe = tmap_adapter_probe,
+ .adapter_remove = tmap_adapter_remove,
+
+ .experimental = true,
+};
+
+static int tmap_init(void)
+{
+ int err;
+
+ err = btd_profile_register(&tmap_profile);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static void tmap_exit(void)
+{
+ btd_profile_unregister(&tmap_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(tmap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ tmap_init, tmap_exit)
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 04/10] bap: have unicast client wait for VCS & TMAS
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (2 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 03/10] tmap: add TMAP profile Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-24 16:25 ` Luiz Augusto von Dentz
2025-11-23 16:17 ` [PATCH BlueZ 05/10] shared/gmap: add GMAP Service Pauli Virtanen
` (6 subsequent siblings)
10 siblings, 1 reply; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Have unicast client to wait for VCS and TMAS before creating endpoints
and transports, so that their information is available at that point.
---
profiles/audio/bap.c | 69 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 66 insertions(+), 3 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index b07d65e68..73c5cfc74 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -127,6 +127,7 @@ struct bap_data {
struct btd_device *device;
struct btd_adapter *adapter;
struct btd_service *service;
+ struct queue *wait_services;
struct bt_bap *bap;
unsigned int ready_id;
unsigned int state_id;
@@ -139,6 +140,7 @@ struct bap_data {
GIOChannel *listen_io;
unsigned int io_id;
unsigned int cig_update_id;
+ unsigned int service_state_id;
};
static struct queue *sessions;
@@ -186,11 +188,15 @@ static void bap_data_free(struct bap_data *data)
queue_destroy(data->bcast, ep_unregister);
queue_destroy(data->server_streams, NULL);
queue_destroy(data->bcast_snks, setup_free);
+ queue_destroy(data->wait_services, NULL);
bt_bap_ready_unregister(data->bap, data->ready_id);
bt_bap_state_unregister(data->bap, data->state_id);
bt_bap_pac_unregister(data->bap, data->pac_id);
bt_bap_unref(data->bap);
+ if (data->service_state_id)
+ btd_service_remove_state_cb(data->service_state_id);
+
if (data->cig_update_id)
g_source_remove(data->cig_update_id);
@@ -2015,13 +2021,16 @@ static bool pac_found_bcast(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
return true;
}
-static void bap_ready(struct bt_bap *bap, void *user_data)
+static void bap_service_ready(struct bap_data *data)
{
- struct btd_service *service = user_data;
- struct bap_data *data = btd_service_get_user_data(service);
+ struct bt_bap *bap = data->bap;
+ struct btd_service *service = data->service;
DBG("bap %p", bap);
+ if (!queue_isempty(data->wait_services))
+ return;
+
/* Register all ep before selecting, so that sound server
* knows all.
*/
@@ -2031,6 +2040,15 @@ static void bap_ready(struct bt_bap *bap, void *user_data)
bap_select_all(data, false, NULL, NULL);
}
+static void bap_ready(struct bt_bap *bap, void *user_data)
+{
+ struct btd_service *service = user_data;
+ struct bap_data *data = btd_service_get_user_data(service);
+
+ queue_remove(data->wait_services, NULL);
+ bap_service_ready(data);
+}
+
static bool match_setup_stream(const void *data, const void *user_data)
{
const struct bap_setup *setup = data;
@@ -3740,6 +3758,44 @@ static int bap_probe(struct btd_service *service)
return 0;
}
+static void wait_service_cb(struct btd_service *service,
+ btd_service_state_t old_state,
+ btd_service_state_t new_state,
+ void *user_data)
+{
+ struct bap_data *data = user_data;
+
+ if (new_state == BTD_SERVICE_STATE_CONNECTING)
+ return;
+ if (!queue_remove(data->wait_services, service))
+ return;
+
+ DBG("%s", btd_service_get_profile(service)->name);
+ bap_service_ready(data);
+}
+
+static void wait_service_add(struct bap_data *data, uint16_t remote_uuid)
+{
+ struct btd_service *service;
+ bt_uuid_t uuid;
+ char uuid_str[64];
+
+ bt_uuid16_create(&uuid, remote_uuid);
+ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
+
+ service = btd_device_get_service(data->device, uuid_str);
+ if (!service)
+ return;
+ if (btd_service_get_state(service) != BTD_SERVICE_STATE_CONNECTING)
+ return;
+
+ queue_push_tail(data->wait_services, service);
+
+ if (!data->service_state_id)
+ data->service_state_id = btd_service_add_state_cb(
+ wait_service_cb, data);
+}
+
static int bap_accept(struct btd_service *service)
{
struct btd_device *device = btd_service_get_device(service);
@@ -3760,6 +3816,13 @@ static int bap_accept(struct btd_service *service)
return -EINVAL;
}
+ queue_destroy(data->wait_services, NULL);
+ data->wait_services = queue_new();
+
+ queue_push_tail(data->wait_services, NULL);
+ wait_service_add(data, TMAS_UUID);
+ wait_service_add(data, VCS_UUID);
+
btd_service_connecting_complete(service, 0);
return 0;
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 05/10] shared/gmap: add GMAP Service
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (3 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 04/10] bap: have unicast client wait for VCS & TMAS Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 06/10] test-gmap: add test for " Pauli Virtanen
` (5 subsequent siblings)
10 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
GMAP Service is just a simple service with bitmasks.
The values can be used to figure out which GMAP roles and features the
remote device claims it supports (matters for available mandatory
features). Also can advertise the same for remote clients.
---
Makefile.am | 1 +
lib/bluetooth/uuid.h | 8 +
src/shared/gmap.c | 402 +++++++++++++++++++++++++++++++++++++++++++
src/shared/gmap.h | 70 ++++++++
4 files changed, 481 insertions(+)
create mode 100644 src/shared/gmap.c
create mode 100644 src/shared/gmap.h
diff --git a/Makefile.am b/Makefile.am
index 11e632c02..14dccafaf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -243,6 +243,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
src/shared/bass.h src/shared/bass.c \
src/shared/ccp.h src/shared/ccp.c \
src/shared/tmap.c src/shared/tmap.h \
+ src/shared/gmap.c src/shared/gmap.h \
src/shared/lc3.h src/shared/tty.h \
src/shared/bap-defs.h \
src/shared/asha.h src/shared/asha.c \
diff --git a/lib/bluetooth/uuid.h b/lib/bluetooth/uuid.h
index 771f7675d..82e948a23 100644
--- a/lib/bluetooth/uuid.h
+++ b/lib/bluetooth/uuid.h
@@ -216,6 +216,14 @@ extern "C" {
#define TMAS_UUID 0x1855
#define TMAP_ROLE_CHRC_UUID 0x2b51
+/* Gaming Audio Service */
+#define GMAS_UUID 0x1858
+#define GMAP_ROLE_CHRC_UUID 0x2c00
+#define GMAP_UGG_CHRC_UUID 0x2c01
+#define GMAP_UGT_CHRC_UUID 0x2c02
+#define GMAP_BGS_CHRC_UUID 0x2c03
+#define GMAP_BGR_CHRC_UUID 0x2c04
+
/* Coordinated Set Identification Profile(CSIP) */
#define CSIS_UUID 0x1846
#define CS_SIRK 0x2B84
diff --git a/src/shared/gmap.c b/src/shared/gmap.c
new file mode 100644
index 000000000..f604147c9
--- /dev/null
+++ b/src/shared/gmap.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/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/gmap.h"
+#include "src/shared/bap.h"
+
+#define DBG(_gmap, fmt, arg...) \
+ gmap_debug(_gmap, "%s:%s() " fmt, __FILE__, __func__, ## arg)
+
+struct bt_gmas_attr {
+ struct bt_gmap *gmap;
+ const char *name;
+ struct gatt_db_attribute *attr;
+ uint8_t value;
+};
+
+struct bt_gmas_db {
+ struct gatt_db *db;
+ struct gatt_db_attribute *service;
+ struct bt_gmas_attr role;
+ struct bt_gmas_attr ugg;
+ struct bt_gmas_attr ugt;
+ struct bt_gmas_attr bgs;
+ struct bt_gmas_attr bgr;
+};
+
+struct bt_gmap {
+ int ref_count;
+ struct bt_gatt_client *client;
+ struct bt_gmas_db db;
+
+ int idle_id;
+ bt_gmap_ready_func_t ready_func;
+ void *ready_data;
+
+ bt_gmap_debug_func_t debug_func;
+ bt_gmap_destroy_func_t debug_destroy;
+ void *debug_data;
+};
+
+static struct queue *instances;
+
+static void gmap_free(void *data)
+{
+ struct bt_gmap *gmap = data;
+
+ if (gmap->client) {
+ bt_gatt_client_idle_unregister(gmap->client, gmap->idle_id);
+ bt_gatt_client_unref(gmap->client);
+ } else {
+ gatt_db_remove_service(gmap->db.db, gmap->db.service);
+ gatt_db_unref(gmap->db.db);
+ }
+
+ queue_remove(instances, gmap);
+ if (queue_isempty(instances)) {
+ queue_destroy(instances, NULL);
+ instances = NULL;
+ }
+
+ free(gmap);
+}
+
+struct bt_gmap *bt_gmap_ref(struct bt_gmap *gmap)
+{
+ if (!gmap)
+ return NULL;
+
+ __sync_fetch_and_add(&gmap->ref_count, 1);
+
+ return gmap;
+}
+
+void bt_gmap_unref(struct bt_gmap *gmap)
+{
+ if (!gmap)
+ return;
+
+ if (__sync_sub_and_fetch(&gmap->ref_count, 1))
+ return;
+
+ gmap_free(gmap);
+}
+
+static void gmap_debug(struct bt_gmap *gmap, const char *format, ...)
+{
+ va_list ap;
+
+ if (!gmap || !format || !gmap->debug_func)
+ return;
+
+ va_start(ap, format);
+ util_debug_va(gmap->debug_func, gmap->debug_data, format, ap);
+ va_end(ap);
+}
+
+bool bt_gmap_set_debug(struct bt_gmap *gmap, bt_gmap_debug_func_t cb,
+ void *user_data, bt_gmap_destroy_func_t destroy)
+{
+ if (!gmap)
+ return false;
+
+ if (gmap->debug_destroy)
+ gmap->debug_destroy(gmap->debug_data);
+
+ gmap->debug_func = cb;
+ gmap->debug_destroy = destroy;
+ gmap->debug_data = user_data;
+
+ return true;
+}
+
+uint8_t bt_gmap_get_role(struct bt_gmap *gmap)
+{
+ if (!gmap)
+ return 0;
+
+ return gmap->db.role.value & BT_GMAP_ROLE_MASK;
+}
+
+uint32_t bt_gmap_get_features(struct bt_gmap *gmap)
+{
+ if (!gmap)
+ return 0;
+
+ return (((uint32_t)gmap->db.ugg.value << BT_GMAP_UGG_FEATURE_SHIFT) |
+ ((uint32_t)gmap->db.ugt.value << BT_GMAP_UGT_FEATURE_SHIFT) |
+ ((uint32_t)gmap->db.bgs.value << BT_GMAP_BGS_FEATURE_SHIFT) |
+ ((uint32_t)gmap->db.bgr.value << BT_GMAP_BGR_FEATURE_SHIFT)) &
+ BT_GMAP_FEATURE_MASK;
+}
+
+/*
+ * GMA Client
+ */
+
+static void gmap_attr_read(bool success, uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct bt_gmas_attr *attr = user_data;
+ struct bt_gmap *gmap = attr->gmap;
+ struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
+ uint8_t v;
+
+ if (!success) {
+ DBG(gmap, "Unable to read %s: error 0x%02x",
+ attr->name, att_ecode);
+ return;
+ }
+
+ if (!util_iov_pull_u8(&iov, &v)) {
+ DBG(gmap, "Invalid %s", attr->name);
+ return;
+ }
+
+ DBG(gmap, "%s Value 0x%x", attr->name, v);
+ attr->value = v;
+}
+
+static void foreach_gmap_char(struct gatt_db_attribute *attr, void *user_data)
+{
+ struct bt_gmap *gmap = user_data;
+ uint16_t value_handle;
+ bt_uuid_t uuid, uuid_attr;
+ struct {
+ const uint32_t uuid;
+ struct bt_gmas_attr *attr;
+ const char *name;
+ } attrs[] = {
+ { GMAP_ROLE_CHRC_UUID, &gmap->db.role, "Role" },
+ { GMAP_UGG_CHRC_UUID, &gmap->db.ugg, "UGG Features" },
+ { GMAP_UGT_CHRC_UUID, &gmap->db.ugt, "UGT Features" },
+ { GMAP_BGS_CHRC_UUID, &gmap->db.bgs, "BGS Features" },
+ { GMAP_BGR_CHRC_UUID, &gmap->db.bgr, "BGR Features" },
+ };
+ unsigned int i;
+
+ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
+ NULL, NULL, &uuid))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(attrs); ++i) {
+ bt_uuid16_create(&uuid_attr, attrs[i].uuid);
+ if (bt_uuid_cmp(&uuid, &uuid_attr))
+ continue;
+
+ attrs[i].attr->gmap = gmap;
+ attrs[i].attr->name = attrs[i].name;
+
+ DBG(gmap, "GMAS %s Char found: handle 0x%04x",
+ attrs[i].name, value_handle);
+ bt_gatt_client_read_value(gmap->client, value_handle,
+ gmap_attr_read, attrs[i].attr,
+ NULL);
+ return;
+ }
+}
+
+static void foreach_gmap_service(struct gatt_db_attribute *attr,
+ void *user_data)
+{
+ struct bt_gmap *gmap = user_data;
+
+ gatt_db_service_set_claimed(attr, true);
+ gatt_db_service_foreach_char(attr, foreach_gmap_char, gmap);
+}
+
+static void gmap_idle(void *data)
+{
+ struct bt_gmap *gmap = data;
+
+ gmap->idle_id = 0;
+
+ if (!instances)
+ instances = queue_new();
+ queue_push_tail(instances, gmap);
+
+ if (gmap->ready_func)
+ gmap->ready_func(gmap, gmap->ready_data);
+}
+
+struct bt_gmap *bt_gmap_attach(struct bt_gatt_client *client,
+ bt_gmap_ready_func_t ready, void *user_data)
+{
+ struct bt_gmap *gmap;
+ bt_uuid_t uuid;
+
+ if (!client)
+ return NULL;
+
+ client = bt_gatt_client_clone(client);
+ if (!client)
+ return NULL;
+
+ gmap = new0(struct bt_gmap, 1);
+ gmap->client = client;
+ gmap->ready_func = ready;
+ gmap->ready_data = user_data;
+ gmap->db.db = bt_gatt_client_get_db(gmap->client);
+
+ bt_uuid16_create(&uuid, GMAS_UUID);
+ gatt_db_foreach_service(gmap->db.db, &uuid, foreach_gmap_service, gmap);
+
+ gmap->idle_id = bt_gatt_client_idle_register(gmap->client, gmap_idle,
+ gmap, NULL);
+
+ return bt_gmap_ref(gmap);
+}
+
+/*
+ * GMAS Service
+ */
+
+static void gmas_attr_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_gmas_attr *attr = user_data;
+ struct iovec iov = {
+ .iov_base = &attr->value,
+ .iov_len = sizeof(attr->value)
+ };
+
+ gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+ iov.iov_len);
+}
+
+static bool match_db(const void *data, const void *match_data)
+{
+ const struct bt_gmap *gmap = data;
+
+ return gmap->db.db == match_data;
+}
+
+struct bt_gmap *bt_gmap_find(struct gatt_db *db)
+{
+ return db ? queue_find(instances, match_db, db) : NULL;
+}
+
+static void gmap_update_chrc(struct bt_gmap *gmap)
+{
+ struct {
+ const uint32_t uuid;
+ uint8_t role;
+ struct bt_gmas_attr *attr;
+ const char *name;
+ } attrs[] = {
+ { GMAP_ROLE_CHRC_UUID, 0, &gmap->db.role, "Role" },
+ { GMAP_UGG_CHRC_UUID, BT_GMAP_ROLE_UGG, &gmap->db.ugg,
+ "UGG Features" },
+ { GMAP_UGT_CHRC_UUID, BT_GMAP_ROLE_UGT, &gmap->db.ugt,
+ "UGT Features" },
+ { GMAP_BGS_CHRC_UUID, BT_GMAP_ROLE_BGS, &gmap->db.bgs,
+ "BGS Features" },
+ { GMAP_BGR_CHRC_UUID, BT_GMAP_ROLE_BGR, &gmap->db.bgr,
+ "BGR Features" },
+ };
+ unsigned int i;
+ bt_uuid_t uuid;
+
+ for (i = 0; i < ARRAY_SIZE(attrs); ++i) {
+ if (attrs[i].attr->attr)
+ continue;
+
+ attrs[i].attr->gmap = gmap;
+ attrs[i].attr->name = attrs[i].name;
+
+ if (attrs[i].role && !(gmap->db.role.value & attrs[i].role))
+ continue;
+
+ bt_uuid16_create(&uuid, attrs[i].uuid);
+ attrs[i].attr->attr = gatt_db_service_add_characteristic(
+ gmap->db.service,
+ &uuid,
+ BT_ATT_PERM_READ,
+ BT_GATT_CHRC_PROP_READ,
+ gmas_attr_read, NULL,
+ attrs[i].attr);
+ gatt_db_attribute_set_fixed_length(attrs[i].attr->attr, 1);
+ }
+}
+
+struct bt_gmap *bt_gmap_add_db(struct gatt_db *db)
+{
+ struct bt_gmap *gmap;
+ bt_uuid_t uuid;
+
+ if (!db || queue_find(instances, match_db, db))
+ return NULL;
+
+ gmap = new0(struct bt_gmap, 1);
+ gmap->db.db = gatt_db_ref(db);
+
+ bt_uuid16_create(&uuid, GMAS_UUID);
+ gmap->db.service = gatt_db_add_service(db, &uuid, true, 5*3);
+
+ if (!instances)
+ instances = queue_new();
+ queue_push_tail(instances, gmap);
+
+ return bt_gmap_ref(gmap);
+}
+
+void bt_gmap_set_role(struct bt_gmap *gmap, uint8_t role)
+{
+ if (gmap->client)
+ return;
+
+ gmap->db.role.value = role & BT_GMAP_ROLE_MASK;
+
+ gmap_update_chrc(gmap);
+
+ /* Expose values only when first set */
+ gatt_db_service_set_active(gmap->db.service, true);
+}
+
+
+void bt_gmap_set_features(struct bt_gmap *gmap, uint32_t features)
+{
+ if (gmap->client)
+ return;
+
+ gmap->db.ugg.value = (features & BT_GMAP_UGG_FEATURE_MASK)
+ >> BT_GMAP_UGG_FEATURE_SHIFT;
+ gmap->db.ugt.value = (features & BT_GMAP_UGT_FEATURE_MASK)
+ >> BT_GMAP_UGT_FEATURE_SHIFT;
+ gmap->db.bgs.value = (features & BT_GMAP_BGS_FEATURE_MASK)
+ >> BT_GMAP_BGS_FEATURE_SHIFT;
+ gmap->db.bgr.value = (features & BT_GMAP_BGR_FEATURE_MASK)
+ >> BT_GMAP_BGR_FEATURE_SHIFT;
+
+ gmap_update_chrc(gmap);
+
+ /* Expose values only when first set */
+ gatt_db_service_set_active(gmap->db.service, true);
+}
diff --git a/src/shared/gmap.h b/src/shared/gmap.h
new file mode 100644
index 000000000..ef7d96e0a
--- /dev/null
+++ b/src/shared/gmap.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ *
+ */
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+#define BT_GMAP_ROLE_UGG BIT(0)
+#define BT_GMAP_ROLE_UGT BIT(1)
+#define BT_GMAP_ROLE_BGS BIT(2)
+#define BT_GMAP_ROLE_BGR BIT(3)
+#define BT_GMAP_ROLE_MASK (BIT(4) - 1)
+
+#define BT_GMAP_UGG_MULTIPLEX (BIT(0) << 0)
+#define BT_GMAP_UGG_96KBPS (BIT(1) << 0)
+#define BT_GMAP_UGG_MULTISINK (BIT(2) << 0)
+#define BT_GMAP_UGG_FEATURE_MASK ((BIT(3) - 1) << 0)
+#define BT_GMAP_UGG_FEATURE_SHIFT 0
+
+#define BT_GMAP_UGT_SOURCE (BIT(0) << 8)
+#define BT_GMAP_UGT_80KBPS_SOURCE (BIT(1) << 8)
+#define BT_GMAP_UGT_SINK (BIT(2) << 8)
+#define BT_GMAP_UGT_64KBPS_SINK (BIT(3) << 8)
+#define BT_GMAP_UGT_MULTIPLEX (BIT(4) << 8)
+#define BT_GMAP_UGT_MULTISINK (BIT(5) << 8)
+#define BT_GMAP_UGT_MULTISOURCE (BIT(6) << 8)
+#define BT_GMAP_UGT_FEATURE_MASK ((BIT(7) - 1) << 8)
+#define BT_GMAP_UGT_FEATURE_SHIFT 8
+
+#define BT_GMAP_BGS_96KBPS (BIT(0) << 16)
+#define BT_GMAP_BGS_FEATURE_MASK ((BIT(1) - 1) << 16)
+#define BT_GMAP_BGS_FEATURE_SHIFT 16
+
+#define BT_GMAP_BGR_MULTISINK (BIT(0) << 24)
+#define BT_GMAP_BGR_MULTIPLEX (BIT(1) << 24)
+#define BT_GMAP_BGR_FEATURE_MASK ((BIT(2) - 1) << 24)
+#define BT_GMAP_BGR_FEATURE_SHIFT 24
+
+#define BT_GMAP_FEATURE_MASK (BT_GMAP_UGG_FEATURE_MASK | \
+ BT_GMAP_UGT_FEATURE_MASK | \
+ BT_GMAP_BGS_FEATURE_MASK | \
+ BT_GMAP_BGR_FEATURE_MASK)
+
+struct bt_gmap;
+
+typedef void (*bt_gmap_ready_func_t)(struct bt_gmap *gmap, void *user_data);
+typedef void (*bt_gmap_destroy_func_t)(void *user_data);
+typedef void (*bt_gmap_debug_func_t)(const char *str, void *user_data);
+
+struct bt_gmap *bt_gmap_ref(struct bt_gmap *gmap);
+void bt_gmap_unref(struct bt_gmap *gmap);
+
+struct bt_gmap *bt_gmap_attach(struct bt_gatt_client *client,
+ bt_gmap_ready_func_t ready, void *user_data);
+struct bt_gmap *bt_gmap_find(struct gatt_db *db);
+struct bt_gmap *bt_gmap_add_db(struct gatt_db *db);
+
+uint8_t bt_gmap_get_role(struct bt_gmap *gmap);
+uint32_t bt_gmap_get_features(struct bt_gmap *gmap);
+
+void bt_gmap_set_role(struct bt_gmap *gmas, uint8_t role);
+void bt_gmap_set_features(struct bt_gmap *gmas, uint32_t features);
+
+bool bt_gmap_set_debug(struct bt_gmap *gmap, bt_gmap_debug_func_t cb,
+ void *user_data, bt_gmap_destroy_func_t destroy);
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 06/10] test-gmap: add test for GMAP Service
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (4 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 05/10] shared/gmap: add GMAP Service Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 07/10] gmap: Add GMAP profile Pauli Virtanen
` (4 subsequent siblings)
10 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add tests on GMAP service for reading the attributes.
---
.gitignore | 1 +
Makefile.am | 6 +
unit/test-gmap.c | 496 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 503 insertions(+)
create mode 100644 unit/test-gmap.c
diff --git a/.gitignore b/.gitignore
index 48bf72a4a..ba29d9d5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -119,6 +119,7 @@ unit/test-bap
unit/test-bass
unit/test-battery
unit/test-tmap
+unit/test-gmap
tools/mgmt-tester
tools/smp-tester
tools/gap-tester
diff --git a/Makefile.am b/Makefile.am
index 14dccafaf..7221e4cba 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -709,6 +709,12 @@ unit_test_tmap_SOURCES = unit/test-tmap.c $(btio_sources)
unit_test_tmap_LDADD = src/libshared-glib.la \
lib/libbluetooth-internal.la $(GLIB_LIBS)
+unit_tests += unit/test-gmap
+
+unit_test_gmap_SOURCES = unit/test-gmap.c $(btio_sources)
+unit_test_gmap_LDADD = src/libshared-glib.la \
+ lib/libbluetooth-internal.la $(GLIB_LIBS)
+
unit_tests += unit/test-battery
unit_test_battery_SOURCES = unit/test-battery.c
diff --git a/unit/test-gmap.c b/unit/test-gmap.c
new file mode 100644
index 000000000..6102ce65d
--- /dev/null
+++ b/unit/test-gmap.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+
+#include <glib.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/uuid.h"
+#include "src/shared/util.h"
+#include "src/shared/tester.h"
+#include "src/shared/queue.h"
+#include "src/shared/att.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
+#include "src/shared/gatt-server.h"
+#include "src/shared/io.h"
+#include "src/shared/gmap.h"
+
+struct test_config {
+ uint8_t role;
+ uint32_t features;
+ const struct iovec *setup_data;
+ const size_t setup_data_len;
+};
+
+struct test_data {
+ struct gatt_db *db;
+ struct bt_gatt_server *server;
+ struct bt_gatt_client *client;
+ struct bt_gmap *gmap;
+ size_t iovcnt;
+ struct iovec *iov;
+ const struct test_config *cfg;
+};
+
+#define iov_data(args...) ((const struct iovec[]) { args })
+
+#define define_test(name, setup, function, _cfg, args...) \
+ do { \
+ const struct iovec iov[] = { args }; \
+ static struct test_data data; \
+ data.iovcnt = ARRAY_SIZE(iov_data(args)); \
+ data.iov = util_iov_dup(iov, ARRAY_SIZE(iov_data(args))); \
+ data.cfg = _cfg; \
+ tester_add(name, &data, setup, function, \
+ test_teardown); \
+ } while (0)
+
+static void print_debug(const char *str, void *user_data)
+{
+ const char *prefix = user_data;
+
+ if (tester_use_debug())
+ tester_debug("%s%s", prefix, str);
+}
+
+static void test_teardown(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+
+ bt_gatt_client_unref(data->client);
+
+ bt_gatt_server_unref(data->server);
+ util_iov_free(data->iov, data->iovcnt);
+
+ gatt_db_unref(data->db);
+
+ bt_gmap_unref(data->gmap);
+ tester_teardown_complete();
+}
+
+/* ATT: Exchange MTU Response (0x03) len 2
+ * Server RX MTU: 64
+ * ATT: Exchange MTU Request (0x02) len 2
+ * Client RX MTU: 64
+ * ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute type: Server Supported Features (0x2b3a)
+ * ATT: Error Response (0x01) len 4
+ * Read By Type Request (0x08)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define GMAS_MTU_FEAT \
+ IOV_DATA(0x02, 0x40, 0x00), \
+ IOV_DATA(0x03, 0x40, 0x00), \
+ IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x3a, 0x2b), \
+ IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+/* ATT: Read By Group Type Request (0x10) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute group type: Primary Service (0x2800)
+ * ATT: Read By Group Type Response (0x11) len 37
+ * Attribute data length: 6
+ * Attribute group list: 1 entries
+ * Handle range: 0x0001-0x000f
+ * UUID: Gaming Audio Service (0x1858)
+ * ATT: Read By Group Type Request (0x10) len 6
+ * Handle range: 0x0006-0xffff
+ * Attribute group type: Primary Service (0x2800)
+ * ATT: Error Response (0x01) len 4
+ * Read By Group Type Request (0x10)
+ * Handle: 0x0004
+ * Error: Attribute Not Found (0x0a)
+ */
+#define GMAS_PRIMARY_SERVICE \
+ IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ IOV_DATA(0x11, 0x06, \
+ 0x01, 0x00, 0x0f, 0x00, 0x58, 0x18), \
+ IOV_DATA(0x10, 0x10, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ IOV_DATA(0x01, 0x10, 0x10, 0x00, 0x0a)
+
+
+/* ATT: Read By Group Type Request (0x10) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute group type: Secondary Service (0x2801)
+ * ATT: Error Response (0x01) len 4
+ * Read By Group Type Request (0x10)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define GMAS_SECONDARY_SERVICE \
+ IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28), \
+ IOV_DATA(0x01, 0x10, 0x01, 0x00, 0x0a)
+
+/* ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0x0005
+ * Attribute group type: Include (0x2802)
+ * ATT: Error Response (0x01) len 4
+ * Read By Group Type Request (0x10)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define GMAS_INCLUDE \
+ IOV_DATA(0x08, 0x01, 0x00, 0x0f, 0x00, 0x02, 0x28), \
+ IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+/* ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0x0003
+ * Attribute type: Characteristic (0x2803)
+ * ATT: Read By Type Response (0x09) len 57
+ * Attribute data length: 7
+ * Attribute data list: 8 entries
+ * Handle: 0x0002
+ * Value: 020300512b
+ * Properties: 0x02
+ * Read (0x02)
+ * Value Handle: 0x0003
+ * Value UUID: GMAP Role (0x2c00)
+ * ATT: Read By Type Response (0x09) len 57
+ * Attribute data length: 7
+ * Attribute data list: 8 entries
+ * Handle: 0x0004
+ * Value: 020300512b
+ * Properties: 0x02
+ * Read (0x02)
+ * Value Handle: 0x0005
+ * Value UUID: GMAP Features ({uuid})
+ * ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0003-0x0004
+ * Attribute type: Characteristic (0x2803)
+ * ATT: Error Response (0x01) len 4
+ * Read By Type Request (0x08)
+ * Handle: 0x0022
+ * Error: Attribute Not Found (0x0a)
+ * ATT: Find Information Request (0x04)
+ * ATT: Error Response
+ */
+#define IOV_CONTENT(data...) data
+
+#define GMAS_FIND_CHRC(uuid) \
+ IOV_DATA(0x08, 0x01, 0x00, 0x0f, 0x00, 0x03, 0x28), \
+ IOV_DATA(0x09, 0x07, \
+ 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x2c, \
+ 0x04, 0x00, 0x02, 0x05, 0x00, uuid), \
+ IOV_DATA(0x08, 0x05, 0x00, 0x0f, 0x00, 0x03, 0x28), \
+ IOV_DATA(0x01, 0x08, 0x05, 0x00, 0x0a), \
+ IOV_DATA(0x04, 0x06, 0x00, 0x0f, 0x00), \
+ IOV_DATA(0x01, 0x04, 0x06, 0x00, 0x0a)
+
+#define UGG_UUID 0x01, 0x2c
+#define UGT_UUID 0x02, 0x2c
+#define BGS_UUID 0x03, 0x2c
+#define BGR_UUID 0x04, 0x2c
+
+#define ROLE_HND 0x03, 0x00
+#define FEAT_HND 0x05, 0x00
+
+/* ACL Data TX: Handle 42 flags 0x00 dlen 11
+ * ATT: Read By Type Request (0x08) len 6
+ * Handle range: 0x0001-0xffff
+ * Attribute type: Database Hash (0x2b2a)
+ * ATT: Error Response (0x01) len 4
+ * Read By Type Request (0x08)
+ * Handle: 0x0001
+ * Error: Attribute Not Found (0x0a)
+ */
+#define GMAS_DATABASE_HASH \
+ IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x2a, 0x2b), \
+ IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a)
+
+
+#define GMAS_SETUP(uuid) \
+ GMAS_MTU_FEAT, \
+ GMAS_PRIMARY_SERVICE, \
+ GMAS_SECONDARY_SERVICE, \
+ GMAS_INCLUDE, \
+ GMAS_FIND_CHRC(IOV_CONTENT(uuid)), \
+ GMAS_DATABASE_HASH
+
+/* GATT Discover All procedure */
+static const struct iovec setup_data_ugg[] = { GMAS_SETUP(UGG_UUID) };
+static const struct iovec setup_data_ugt[] = { GMAS_SETUP(UGT_UUID) };
+static const struct iovec setup_data_bgs[] = { GMAS_SETUP(BGS_UUID) };
+static const struct iovec setup_data_bgr[] = { GMAS_SETUP(BGR_UUID) };
+
+static void setup_complete_cb(const void *user_data)
+{
+ tester_setup_complete();
+}
+
+static void test_setup_server(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ const struct test_config *cfg = data->cfg;
+ struct bt_att *att;
+ struct gatt_db *db;
+ struct io *io;
+
+ io = tester_setup_io(cfg->setup_data, cfg->setup_data_len);
+ g_assert(io);
+
+ tester_io_set_complete_func(setup_complete_cb);
+
+ db = gatt_db_new();
+ g_assert(db);
+
+ data->gmap = bt_gmap_add_db(db);
+ bt_gmap_set_debug(data->gmap, print_debug, "gmap:", NULL);
+
+ bt_gmap_set_role(data->gmap, data->cfg->role);
+ bt_gmap_set_features(data->gmap, data->cfg->features);
+
+ att = bt_att_new(io_get_fd(io), false);
+ g_assert(att);
+ bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL);
+
+ data->server = bt_gatt_server_new(db, att, 64, 0);
+ g_assert(data->server);
+ bt_gatt_server_set_debug(data->server, print_debug, "bt_gatt_server:",
+ NULL);
+
+ tester_io_send();
+
+ bt_att_unref(att);
+ gatt_db_unref(db);
+}
+
+static void test_complete_cb(const void *user_data)
+{
+ tester_test_passed();
+}
+
+static void test_server(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ struct io *io;
+
+ io = tester_setup_io(data->iov, data->iovcnt);
+ g_assert(io);
+
+ tester_io_set_complete_func(test_complete_cb);
+
+ tester_io_send();
+}
+
+static void setup_ready_cb(bool success, uint8_t att_ecode, void *user_data)
+{
+ if (!success)
+ tester_setup_failed();
+ else
+ tester_setup_complete();
+}
+
+static void test_setup(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ const struct test_config *cfg = data->cfg;
+ struct bt_att *att;
+ struct gatt_db *db;
+ struct io *io;
+
+ io = tester_setup_io(cfg->setup_data, cfg->setup_data_len);
+ g_assert(io);
+
+ att = bt_att_new(io_get_fd(io), false);
+ g_assert(att);
+
+ bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL);
+
+ db = gatt_db_new();
+ g_assert(db);
+
+ data->client = bt_gatt_client_new(db, att, 64, 0);
+ g_assert(data->client);
+
+ bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
+ NULL);
+
+ bt_gatt_client_ready_register(data->client, setup_ready_cb, data,
+ NULL);
+
+ bt_att_unref(att);
+ gatt_db_unref(db);
+}
+
+static void client_ready_cb(struct bt_gmap *gmap, void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+
+ if (bt_gmap_get_role(gmap) != data->cfg->role) {
+ tester_test_failed();
+ return;
+ }
+
+ if (bt_gmap_get_features(gmap) != data->cfg->features) {
+ tester_test_failed();
+ return;
+ }
+
+ tester_test_passed();
+}
+
+static void test_client(const void *user_data)
+{
+ struct test_data *data = (void *)user_data;
+ struct io *io;
+
+ io = tester_setup_io(data->iov, data->iovcnt);
+ g_assert(io);
+
+ tester_io_set_complete_func(NULL);
+
+ data->gmap = bt_gmap_attach(data->client, client_ready_cb, data);
+ g_assert(data->gmap);
+
+ bt_gmap_set_debug(data->gmap, print_debug, "gmap:", NULL);
+}
+
+/* ATT: Read Request (0x0a) len 2
+ * Handle: 0x0003 Type: GMAP Role (0x2c00)
+ * ATT: Read Response (0x0b) len 24
+ * Value: _value
+ * Handle: 0x0003 Type: GMAP Role (0x2c00)
+ */
+
+#define READ_CHRC(hnd, value...) \
+ IOV_DATA(0x0a, hnd), \
+ IOV_DATA(0x0b, value)
+
+#define READ_ROLE(value...) READ_CHRC(IOV_CONTENT(ROLE_HND), value)
+#define READ_FEAT(value...) READ_CHRC(IOV_CONTENT(FEAT_HND), value)
+
+#define CGGIT_CHA(role, value) READ_ROLE(role), READ_FEAT(value)
+
+#define CGGIT_ROLE CGGIT_CHA(0x01, 0x00)
+#define CGGIT_ROLE_RFU CGGIT_CHA(0xf1, 0x00)
+
+const struct test_config cfg_read_role = {
+ .role = BT_GMAP_ROLE_UGG,
+ .setup_data = setup_data_ugg,
+ .setup_data_len = ARRAY_SIZE(setup_data_ugg),
+};
+
+#define CGGIT_UGG CGGIT_CHA(0x01, 0x01)
+#define CGGIT_UGG_RFU CGGIT_CHA(0x01, 0xf1)
+
+const struct test_config cfg_read_ugg = {
+ .role = BT_GMAP_ROLE_UGG,
+ .features = BT_GMAP_UGG_MULTIPLEX,
+ .setup_data = setup_data_ugg,
+ .setup_data_len = ARRAY_SIZE(setup_data_ugg),
+};
+
+#define CGGIT_UGT CGGIT_CHA(0x02, 0x01)
+#define CGGIT_UGT_RFU CGGIT_CHA(0x02, 0x81)
+
+const struct test_config cfg_read_ugt = {
+ .role = BT_GMAP_ROLE_UGT,
+ .features = BT_GMAP_UGT_SOURCE,
+ .setup_data = setup_data_ugt,
+ .setup_data_len = ARRAY_SIZE(setup_data_ugt),
+};
+
+#define CGGIT_BGS CGGIT_CHA(0x04, 0x01)
+#define CGGIT_BGS_RFU CGGIT_CHA(0x04, 0x81)
+
+const struct test_config cfg_read_bgs = {
+ .role = BT_GMAP_ROLE_BGS,
+ .features = BT_GMAP_BGS_96KBPS,
+ .setup_data = setup_data_bgs,
+ .setup_data_len = ARRAY_SIZE(setup_data_bgs),
+};
+
+#define CGGIT_BGR CGGIT_CHA(0x08, 0x01)
+#define CGGIT_BGR_RFU CGGIT_CHA(0x08, 0x81)
+
+const struct test_config cfg_read_bgr = {
+ .role = BT_GMAP_ROLE_BGR,
+ .features = BT_GMAP_BGR_MULTISINK,
+ .setup_data = setup_data_bgr,
+ .setup_data_len = ARRAY_SIZE(setup_data_bgr),
+};
+
+static void test_gmap_cl(void)
+{
+ /* Sec. 4.5.1 TMA Client */
+ define_test("GMAP/CL/CGGIT/CHA/BV-01-C [GMAP Role Read Characteristic, "
+ "Client]",
+ test_setup, test_client, &cfg_read_role, CGGIT_ROLE);
+ define_test("GMAP/CL/CGGIT/CHA/BV-03-C [UGG Features Read "
+ "Characteristic, Client]",
+ test_setup, test_client, &cfg_read_ugg, CGGIT_UGG);
+ define_test("GMAP/CL/CGGIT/CHA/BV-02-C [UGT Features Read "
+ "Characteristic, Client]",
+ test_setup, test_client, &cfg_read_ugt, CGGIT_UGT);
+ define_test("GMAP/CL/CGGIT/CHA/BV-04-C [BGS Features Read "
+ "Characteristic, Client]",
+ test_setup, test_client, &cfg_read_bgs, CGGIT_BGS);
+ define_test("GMAP/CL/CGGIT/CHA/BV-05-C [BGR Features Read "
+ "Characteristic, Client]",
+ test_setup, test_client, &cfg_read_bgr, CGGIT_BGR);
+
+ define_test("GMAP/CL/GMAS/BI-01-C [Client Ignores RFU Bits in GMAP "
+ "Role Characteristic]",
+ test_setup, test_client, &cfg_read_role, CGGIT_ROLE_RFU);
+ define_test("GMAP/CL/GMAS/BI-03-C [Client Ignores RFU Bits in UGG "
+ "Features Characteristic]",
+ test_setup, test_client, &cfg_read_ugg, CGGIT_UGG_RFU);
+ define_test("GMAP/CL/GMAS/BI-02-C [Client Ignores RFU Bit in UGT "
+ "Features Characteristic]",
+ test_setup, test_client, &cfg_read_ugt, CGGIT_UGT_RFU);
+ define_test("GMAP/CL/GMAS/BI-04-C [Client Ignores RFU Bits in BGS "
+ "Features Characteristic]",
+ test_setup, test_client, &cfg_read_bgs, CGGIT_BGS_RFU);
+ define_test("GMAP/CL/GMAS/BI-05-C [Client Ignores RFU Bits in BGR "
+ "Features Characteristic]",
+ test_setup, test_client, &cfg_read_bgr, CGGIT_BGR_RFU);
+}
+
+/* Step 1. in CGGIT/CHA skipped, should be unnecessary */
+#define SGGIT_CHA_ROLE READ_ROLE(0x01)
+#define SGGIT_CHA_FEAT READ_FEAT(0x01)
+
+static void test_gmap_sr(void)
+{
+ /* Sec. 4.6.2 GMA Server */
+ define_test("GMAP/SR/SGGIT/CHA/BV-01-C [Characteristic GGIT - GMAP "
+ "Role]",
+ test_setup_server, test_server, &cfg_read_role, SGGIT_CHA_ROLE);
+ define_test("GMAP/SR/SGGIT/CHA/BV-03-C [Characteristic GGIT - UGG "
+ "Features]",
+ test_setup_server, test_server, &cfg_read_ugg, SGGIT_CHA_FEAT);
+ define_test("GMAP/SR/SGGIT/CHA/BV-02-C [Characteristic GGIT - UGT "
+ "Features]",
+ test_setup_server, test_server, &cfg_read_ugt, SGGIT_CHA_FEAT);
+ define_test("GMAP/SR/SGGIT/CHA/BV-04-C [Characteristic GGIT - BGS "
+ "Features]",
+ test_setup_server, test_server, &cfg_read_bgs, SGGIT_CHA_FEAT);
+ define_test("GMAP/SR/SGGIT/CHA/BV-05-C [Characteristic GGIT - BGR "
+ "Features]",
+ test_setup_server, test_server, &cfg_read_bgr, SGGIT_CHA_FEAT);
+}
+
+int main(int argc, char *argv[])
+{
+ tester_init(&argc, &argv);
+ test_gmap_cl();
+ test_gmap_sr();
+
+ return tester_run();
+}
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 07/10] gmap: Add GMAP profile
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (5 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 06/10] test-gmap: add test for " Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 08/10] bap: unicast client wait for GMAP service Pauli Virtanen
` (3 subsequent siblings)
10 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Read GMAP properties from remote devices and enable advertising the
values for local services.
---
Makefile.plugins | 5 ++
configure.ac | 7 ++
profiles/audio/gmap.c | 203 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 215 insertions(+)
create mode 100644 profiles/audio/gmap.c
diff --git a/Makefile.plugins b/Makefile.plugins
index c976c66f4..3572ee845 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -151,6 +151,11 @@ builtin_modules += tmap
builtin_sources += profiles/audio/tmap.c
endif
+if GMAP
+builtin_modules += gmap
+builtin_sources += profiles/audio/gmap.c
+endif
+
if ASHA
builtin_modules += asha
builtin_sources += profiles/audio/asha.h profiles/audio/asha.c
diff --git a/configure.ac b/configure.ac
index 29a322cb5..16b81aca3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -234,6 +234,13 @@ if test "${enable_tmap}" != "no"; then
AC_DEFINE(HAVE_TMAP, 1, [Define to 1 if you have TMAP support.])
fi
+AC_ARG_ENABLE(gmap, AS_HELP_STRING([--disable-gmap],
+ [disable GMAP profile]), [enable_gmap=${enableval}])
+AM_CONDITIONAL(GMAP, test "${enable_gmap}" != "no")
+if test "${enable_gmap}" != "no"; then
+ AC_DEFINE(HAVE_GMAP, 1, [Define to 1 if you have GMAP support.])
+fi
+
AC_ARG_ENABLE(asha, AS_HELP_STRING([--disable-asha],
[disable ASHA support]), [enable_asha=${enableval}])
AM_CONDITIONAL(ASHA, test "${enable_asha}" != "no")
diff --git a/profiles/audio/gmap.c b/profiles/audio/gmap.c
new file mode 100644
index 000000000..eb3f82f9c
--- /dev/null
+++ b/profiles/audio/gmap.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Pauli Virtanen. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include "gdbus/gdbus.h"
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/hci.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/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/gmap.h"
+
+#include "btio/btio.h"
+#include "src/plugin.h"
+#include "src/adapter.h"
+#include "src/gatt-database.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "src/log.h"
+#include "src/error.h"
+
+#include "vcp.h"
+#include "transport.h"
+
+#define GMAS_UUID_STR "00001858-0000-1000-8000-00805f9b34fb"
+
+static void gmap_debug(const char *str, void *user_data)
+{
+ DBG_IDX(0xffff, "%s", str);
+}
+
+static void service_ready(struct bt_gmap *gmap, void *user_data)
+{
+ struct btd_service *service = user_data;
+
+ btd_service_connecting_complete(service, 0);
+}
+
+static struct bt_gmap *add_service(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 bt_gmap *gmap = btd_service_get_user_data(service);
+
+ if (gmap)
+ return gmap;
+
+ gmap = bt_gmap_attach(client, service_ready, service);
+ if (!gmap) {
+ error("GMAP client unable to attach");
+ return NULL;
+ }
+
+ bt_gmap_set_debug(gmap, gmap_debug, NULL, NULL);
+
+ btd_service_set_user_data(service, gmap);
+ return gmap;
+}
+
+static void remove_service(struct btd_service *service)
+{
+ struct bt_gmap *gmap = btd_service_get_user_data(service);
+
+ if (!gmap)
+ return;
+
+ btd_service_set_user_data(service, NULL);
+ bt_gmap_unref(gmap);
+}
+
+static int gmap_accept(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ struct bt_gmap *gmap;
+ char addr[18];
+
+ ba2str(device_get_address(device), addr);
+ DBG("%s", addr);
+
+ gmap = add_service(service);
+ if (!gmap)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int gmap_disconnect(struct btd_service *service)
+{
+ remove_service(service);
+
+ btd_service_disconnecting_complete(service, 0);
+ return 0;
+}
+
+static int gmap_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ char addr[18];
+
+ ba2str(device_get_address(device), addr);
+ DBG("%s", addr);
+ return 0;
+}
+
+static void gmap_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ char addr[18];
+
+ ba2str(device_get_address(device), addr);
+ DBG("%s", addr);
+}
+
+static int gmap_adapter_probe(struct btd_profile *p,
+ struct btd_adapter *adapter)
+{
+ struct btd_gatt_database *database = btd_adapter_get_database(adapter);
+ struct bt_gmap *gmap;
+
+ DBG("Add GMAP server %s", adapter_get_path(adapter));
+ gmap = bt_gmap_add_db(btd_gatt_database_get_db(database));
+
+ bt_gmap_set_debug(gmap, gmap_debug, NULL, NULL);
+ return 0;
+}
+
+static void gmap_adapter_remove(struct btd_profile *p,
+ struct btd_adapter *adapter)
+{
+ struct btd_gatt_database *database = btd_adapter_get_database(adapter);
+ struct bt_gmap *gmap;
+
+ DBG("Remove GMAP server %s", adapter_get_path(adapter));
+ gmap = bt_gmap_find(btd_gatt_database_get_db(database));
+ bt_gmap_unref(gmap);
+}
+
+static struct btd_profile gmap_profile = {
+ .name = "gmap",
+ .priority = BTD_PROFILE_PRIORITY_MEDIUM,
+ .remote_uuid = GMAS_UUID_STR,
+
+ .device_probe = gmap_probe,
+ .device_remove = gmap_remove,
+ .accept = gmap_accept,
+ .disconnect = gmap_disconnect,
+
+ .adapter_probe = gmap_adapter_probe,
+ .adapter_remove = gmap_adapter_remove,
+
+ .experimental = true,
+};
+
+static int gmap_init(void)
+{
+ int err;
+
+ err = btd_profile_register(&gmap_profile);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static void gmap_exit(void)
+{
+ btd_profile_unregister(&gmap_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(gmap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ gmap_init, gmap_exit)
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 08/10] bap: unicast client wait for GMAP service
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (6 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 07/10] gmap: Add GMAP profile Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures Pauli Virtanen
` (2 subsequent siblings)
10 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Wait until GMAP properties (if any) are read before configuring
endpoints and transports as BAP Client.
---
profiles/audio/bap.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 73c5cfc74..17a040a71 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -3821,6 +3821,7 @@ static int bap_accept(struct btd_service *service)
queue_push_tail(data->wait_services, NULL);
wait_service_add(data, TMAS_UUID);
+ wait_service_add(data, GMAS_UUID);
wait_service_add(data, VCS_UUID);
btd_service_connecting_complete(service, 0);
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (7 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 08/10] bap: unicast client wait for GMAP service Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-24 16:20 ` Luiz Augusto von Dentz
2025-11-23 16:17 ` [PATCH BlueZ 10/10] bap: add SupportedFeatures for MediaEndpoints Pauli Virtanen
2025-11-26 16:20 ` [PATCH BlueZ 00/10] Add TMAP & GMAP information services patchwork-bot+bluetooth
10 siblings, 1 reply; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add SupportedFeatures field for indicating TMAP & GMAP roles and
features.
---
doc/org.bluez.MediaEndpoint.rst | 99 +++++++++++++++++++++++++++++++++
1 file changed, 99 insertions(+)
diff --git a/doc/org.bluez.MediaEndpoint.rst b/doc/org.bluez.MediaEndpoint.rst
index c1ce1d562..dd4f03e0d 100644
--- a/doc/org.bluez.MediaEndpoint.rst
+++ b/doc/org.bluez.MediaEndpoint.rst
@@ -299,3 +299,102 @@ Indicates QoS capabilities.
:uint32 PreferredMaximumDelay:
Indicates endpoint preferred maximum presentation delay.
+
+array{string} SupportedFeatures [readonly, ISO only, experimental]
+``````````````````````````````````````````````````````````````````
+
+List of strings that represent supported features.
+
+Possible values:
+
+:"tmap-cg":
+
+ TMAP Call Gateway
+
+:"tmap-ct":
+
+ TMAP Call Terminal
+
+:"tmap-ums":
+
+ TMAP Unicast Media Sender
+
+:"tmap-umr":
+
+ TMAP Unicast Media Receiver
+
+:"tmap-bms":
+
+ TMAP Broadcast Media Sender
+
+:"tmap-bmr":
+
+ TMAP Broadcast Media Receiver
+
+:"gmap-ugg":
+
+ GMAP Unicast Game Gateway
+
+:"gmap-ugt":
+
+ GMAP Unicast Game Terminal
+
+:"gmap-bgs":
+
+ GMAP Broadcast Game Sender
+
+:"gmap-bgr":
+
+ GMAP Broadcast Game Receiver
+
+:"gmap-ugg-multiplex":
+
+ GMAP UGG Multiplex feature support
+
+:"gmap-ugg-96kbps-source":
+
+ GMAP UGG 96 kbps Source feature support
+
+:"gmap-ugg-multisink":
+
+ GMAP UGG Multisink feature support
+
+:"gmap-ugt-source":
+
+ GMAP UGT Source feature support
+
+:"gmap-ugt-80kbps-souce":
+
+ GMAP UGT 80 kbps Source feature support
+
+:"gmap-ugt-sink":
+
+ GMAP UGT Sink feature support
+
+:"gmap-ugt-64kbps-sink":
+
+ GMAP UGT 64 kbps Sink feature support
+
+:"gmap-ugt-multiplex":
+
+ GMAP UGT Multiplex feature support
+
+:"gmap-ugt-multisink":
+
+ GMAP UGT Multisink feature support
+
+:"gmap-ugt-multisource":
+
+ GMAP UGT Multisource feature support
+
+:"gmap-bgs-96kbps":
+
+ GMAP BGS 96 kbps feature support
+
+:"gmap-bgr-multisink":
+
+ GMAP BGR Multisink feature support
+
+:"gmap-bgr-multiplex":
+
+ GMAP BGR Multiplex feature support
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH BlueZ 10/10] bap: add SupportedFeatures for MediaEndpoints
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (8 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures Pauli Virtanen
@ 2025-11-23 16:17 ` Pauli Virtanen
2025-11-26 16:20 ` [PATCH BlueZ 00/10] Add TMAP & GMAP information services patchwork-bot+bluetooth
10 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-23 16:17 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Indicate TMAP & GMAP capabilities in SupportedFeatures.
---
profiles/audio/bap.c | 76 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index 17a040a71..ec75fb5ce 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -46,6 +46,8 @@
#include "src/shared/gatt-client.h"
#include "src/shared/gatt-server.h"
#include "src/shared/bap.h"
+#include "src/shared/tmap.h"
+#include "src/shared/gmap.h"
#include "btio/btio.h"
#include "src/plugin.h"
@@ -444,6 +446,78 @@ static gboolean get_qos(const GDBusPropertyTable *property,
return TRUE;
}
+static bool probe_tmap_role(struct bap_ep *ep, uint32_t data)
+{
+ struct gatt_db *db = bt_bap_get_db(ep->data->bap, true);
+
+ return bt_tmap_get_role(bt_tmap_find(db)) & data;
+}
+
+static bool probe_gmap_role(struct bap_ep *ep, uint32_t data)
+{
+ struct gatt_db *db = bt_bap_get_db(ep->data->bap, true);
+
+ return bt_gmap_get_role(bt_gmap_find(db)) & data;
+}
+
+static bool probe_gmap_feature(struct bap_ep *ep, uint32_t data)
+{
+ struct gatt_db *db = bt_bap_get_db(ep->data->bap, true);
+
+ return bt_gmap_get_features(bt_gmap_find(db)) & data;
+}
+
+static const struct {
+ const char *name;
+ bool (*probe)(struct bap_ep *ep, uint32_t data);
+ uint32_t data;
+} features[] = {
+ { "tmap-cg", probe_tmap_role, BT_TMAP_ROLE_CG },
+ { "tmap-ct", probe_tmap_role, BT_TMAP_ROLE_CT },
+ { "tmap-ums", probe_tmap_role, BT_TMAP_ROLE_UMS },
+ { "tmap-umr", probe_tmap_role, BT_TMAP_ROLE_UMR },
+ { "tmap-bms", probe_tmap_role, BT_TMAP_ROLE_BMS },
+ { "tmap-bmr", probe_tmap_role, BT_TMAP_ROLE_BMR },
+ { "gmap-ugg", probe_gmap_role, BT_GMAP_ROLE_UGG },
+ { "gmap-ugt", probe_gmap_role, BT_GMAP_ROLE_UGT },
+ { "gmap-bgs", probe_gmap_role, BT_GMAP_ROLE_BGS },
+ { "gmap-bgr", probe_gmap_role, BT_GMAP_ROLE_BGR },
+ { "gmap-ugg-multiplex", probe_gmap_feature, BT_GMAP_UGG_MULTIPLEX },
+ { "gmap-ugg-96kbps-source", probe_gmap_feature, BT_GMAP_UGG_96KBPS },
+ { "gmap-ugg-multisink", probe_gmap_feature, BT_GMAP_UGG_MULTISINK },
+ { "gmap-ugt-source", probe_gmap_feature, BT_GMAP_UGT_SOURCE },
+ { "gmap-ugt-80kbps-source", probe_gmap_feature,
+ BT_GMAP_UGT_80KBPS_SOURCE },
+ { "gmap-ugt-sink", probe_gmap_feature, BT_GMAP_UGT_SINK },
+ { "gmap-ugt-64kbps-sink", probe_gmap_feature, BT_GMAP_UGT_64KBPS_SINK },
+ { "gmap-ugt-multiplex", probe_gmap_feature, BT_GMAP_UGT_MULTIPLEX },
+ { "gmap-ugt-multisink", probe_gmap_feature, BT_GMAP_UGT_MULTISINK },
+ { "gmap-ugt-multisource", probe_gmap_feature, BT_GMAP_UGT_MULTISOURCE },
+ { "gmap-bgs-96kbps", probe_gmap_feature, BT_GMAP_BGS_96KBPS },
+ { "gmap-bgr-multisink", probe_gmap_feature, BT_GMAP_BGR_MULTISINK },
+ { "gmap-bgr-multiplex", probe_gmap_feature, BT_GMAP_BGR_MULTIPLEX },
+};
+
+static gboolean supported_features(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct bap_ep *ep = 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(features); ++i)
+ if (features[i].probe(ep, features[i].data))
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+ &features[i].name);
+
+ dbus_message_iter_close_container(iter, &entry);
+
+ return TRUE;
+}
+
static const GDBusPropertyTable ep_properties[] = {
{ "UUID", "s", get_uuid, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
@@ -463,6 +537,8 @@ static const GDBusPropertyTable ep_properties[] = {
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "QoS", "a{sv}", get_qos, NULL, qos_exists,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
+ { "SupportedFeatures", "as", supported_features, NULL, NULL,
+ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ }
};
--
2.51.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* RE: Add TMAP & GMAP information services
2025-11-23 16:17 ` [PATCH BlueZ 01/10] shared/tmap: add TMAP Service Pauli Virtanen
@ 2025-11-23 17:24 ` bluez.test.bot
0 siblings, 0 replies; 18+ messages in thread
From: bluez.test.bot @ 2025-11-23 17:24 UTC (permalink / raw)
To: linux-bluetooth, pav
[-- Attachment #1: Type: text/plain, Size: 1261 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1026711
---Test result---
Test Summary:
CheckPatch PENDING 0.36 seconds
GitLint PENDING 0.33 seconds
BuildEll PASS 20.28 seconds
BluezMake PASS 632.28 seconds
MakeCheck PASS 22.34 seconds
MakeDistcheck PASS 240.84 seconds
CheckValgrind PASS 296.97 seconds
CheckSmatch PASS 345.77 seconds
bluezmakeextell PASS 182.29 seconds
IncrementalBuild PENDING 0.34 seconds
ScanBuild PASS 992.33 seconds
Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:
##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH BlueZ 09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures
2025-11-23 16:17 ` [PATCH BlueZ 09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures Pauli Virtanen
@ 2025-11-24 16:20 ` Luiz Augusto von Dentz
2025-11-24 16:55 ` Pauli Virtanen
0 siblings, 1 reply; 18+ messages in thread
From: Luiz Augusto von Dentz @ 2025-11-24 16:20 UTC (permalink / raw)
To: Pauli Virtanen; +Cc: linux-bluetooth
Hi Pauli,
On Sun, Nov 23, 2025 at 11:18 AM Pauli Virtanen <pav@iki.fi> wrote:
>
> Add SupportedFeatures field for indicating TMAP & GMAP roles and
> features.
> ---
> doc/org.bluez.MediaEndpoint.rst | 99 +++++++++++++++++++++++++++++++++
> 1 file changed, 99 insertions(+)
>
> diff --git a/doc/org.bluez.MediaEndpoint.rst b/doc/org.bluez.MediaEndpoint.rst
> index c1ce1d562..dd4f03e0d 100644
> --- a/doc/org.bluez.MediaEndpoint.rst
> +++ b/doc/org.bluez.MediaEndpoint.rst
> @@ -299,3 +299,102 @@ Indicates QoS capabilities.
> :uint32 PreferredMaximumDelay:
>
> Indicates endpoint preferred maximum presentation delay.
> +
> +array{string} SupportedFeatures [readonly, ISO only, experimental]
> +``````````````````````````````````````````````````````````````````
> +
> +List of strings that represent supported features.
> +
> +Possible values:
> +
> +:"tmap-cg":
> +
> + TMAP Call Gateway
> +
> +:"tmap-ct":
> +
> + TMAP Call Terminal
> +
> +:"tmap-ums":
> +
> + TMAP Unicast Media Sender
> +
> +:"tmap-umr":
> +
> + TMAP Unicast Media Receiver
> +
> +:"tmap-bms":
> +
> + TMAP Broadcast Media Sender
> +
> +:"tmap-bmr":
> +
> + TMAP Broadcast Media Receiver
> +
> +:"gmap-ugg":
> +
> + GMAP Unicast Game Gateway
> +
> +:"gmap-ugt":
> +
> + GMAP Unicast Game Terminal
> +
> +:"gmap-bgs":
> +
> + GMAP Broadcast Game Sender
> +
> +:"gmap-bgr":
> +
> + GMAP Broadcast Game Receiver
> +
> +:"gmap-ugg-multiplex":
> +
> + GMAP UGG Multiplex feature support
> +
> +:"gmap-ugg-96kbps-source":
> +
> + GMAP UGG 96 kbps Source feature support
> +
> +:"gmap-ugg-multisink":
> +
> + GMAP UGG Multisink feature support
> +
> +:"gmap-ugt-source":
> +
> + GMAP UGT Source feature support
> +
> +:"gmap-ugt-80kbps-souce":
> +
> + GMAP UGT 80 kbps Source feature support
> +
> +:"gmap-ugt-sink":
> +
> + GMAP UGT Sink feature support
> +
> +:"gmap-ugt-64kbps-sink":
> +
> + GMAP UGT 64 kbps Sink feature support
> +
> +:"gmap-ugt-multiplex":
> +
> + GMAP UGT Multiplex feature support
> +
> +:"gmap-ugt-multisink":
> +
> + GMAP UGT Multisink feature support
> +
> +:"gmap-ugt-multisource":
> +
> + GMAP UGT Multisource feature support
> +
> +:"gmap-bgs-96kbps":
> +
> + GMAP BGS 96 kbps feature support
> +
> +:"gmap-bgr-multisink":
> +
> + GMAP BGR Multisink feature support
> +
> +:"gmap-bgr-multiplex":
> +
> + GMAP BGR Multiplex feature support
> --
> 2.51.1
I wonder if it would make sense to add a sublevel as UUID followed by
the feature set, this is a little bit more streamline since there is
just one level though since you are prefixing the profile friendly
name, that said I think it having a multilevel is probably going to be
easier to maintain since the value domain would be per UUID rather
than global.
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH BlueZ 04/10] bap: have unicast client wait for VCS & TMAS
2025-11-23 16:17 ` [PATCH BlueZ 04/10] bap: have unicast client wait for VCS & TMAS Pauli Virtanen
@ 2025-11-24 16:25 ` Luiz Augusto von Dentz
2025-11-24 16:58 ` Pauli Virtanen
0 siblings, 1 reply; 18+ messages in thread
From: Luiz Augusto von Dentz @ 2025-11-24 16:25 UTC (permalink / raw)
To: Pauli Virtanen; +Cc: linux-bluetooth
Hi Pauli,
On Sun, Nov 23, 2025 at 11:17 AM Pauli Virtanen <pav@iki.fi> wrote:
>
> Have unicast client to wait for VCS and TMAS before creating endpoints
> and transports, so that their information is available at that point.
> ---
> profiles/audio/bap.c | 69 ++++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 66 insertions(+), 3 deletions(-)
>
> diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
> index b07d65e68..73c5cfc74 100644
> --- a/profiles/audio/bap.c
> +++ b/profiles/audio/bap.c
> @@ -127,6 +127,7 @@ struct bap_data {
> struct btd_device *device;
> struct btd_adapter *adapter;
> struct btd_service *service;
> + struct queue *wait_services;
> struct bt_bap *bap;
> unsigned int ready_id;
> unsigned int state_id;
> @@ -139,6 +140,7 @@ struct bap_data {
> GIOChannel *listen_io;
> unsigned int io_id;
> unsigned int cig_update_id;
> + unsigned int service_state_id;
> };
>
> static struct queue *sessions;
> @@ -186,11 +188,15 @@ static void bap_data_free(struct bap_data *data)
> queue_destroy(data->bcast, ep_unregister);
> queue_destroy(data->server_streams, NULL);
> queue_destroy(data->bcast_snks, setup_free);
> + queue_destroy(data->wait_services, NULL);
> bt_bap_ready_unregister(data->bap, data->ready_id);
> bt_bap_state_unregister(data->bap, data->state_id);
> bt_bap_pac_unregister(data->bap, data->pac_id);
> bt_bap_unref(data->bap);
>
> + if (data->service_state_id)
> + btd_service_remove_state_cb(data->service_state_id);
> +
> if (data->cig_update_id)
> g_source_remove(data->cig_update_id);
>
> @@ -2015,13 +2021,16 @@ static bool pac_found_bcast(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
> return true;
> }
>
> -static void bap_ready(struct bt_bap *bap, void *user_data)
> +static void bap_service_ready(struct bap_data *data)
> {
> - struct btd_service *service = user_data;
> - struct bap_data *data = btd_service_get_user_data(service);
> + struct bt_bap *bap = data->bap;
> + struct btd_service *service = data->service;
>
> DBG("bap %p", bap);
>
> + if (!queue_isempty(data->wait_services))
> + return;
> +
> /* Register all ep before selecting, so that sound server
> * knows all.
> */
> @@ -2031,6 +2040,15 @@ static void bap_ready(struct bt_bap *bap, void *user_data)
> bap_select_all(data, false, NULL, NULL);
> }
>
> +static void bap_ready(struct bt_bap *bap, void *user_data)
> +{
> + struct btd_service *service = user_data;
> + struct bap_data *data = btd_service_get_user_data(service);
> +
> + queue_remove(data->wait_services, NULL);
> + bap_service_ready(data);
> +}
> +
> static bool match_setup_stream(const void *data, const void *user_data)
> {
> const struct bap_setup *setup = data;
> @@ -3740,6 +3758,44 @@ static int bap_probe(struct btd_service *service)
> return 0;
> }
>
> +static void wait_service_cb(struct btd_service *service,
> + btd_service_state_t old_state,
> + btd_service_state_t new_state,
> + void *user_data)
> +{
> + struct bap_data *data = user_data;
> +
> + if (new_state == BTD_SERVICE_STATE_CONNECTING)
> + return;
> + if (!queue_remove(data->wait_services, service))
> + return;
> +
> + DBG("%s", btd_service_get_profile(service)->name);
> + bap_service_ready(data);
> +}
> +
> +static void wait_service_add(struct bap_data *data, uint16_t remote_uuid)
> +{
> + struct btd_service *service;
> + bt_uuid_t uuid;
> + char uuid_str[64];
> +
> + bt_uuid16_create(&uuid, remote_uuid);
> + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
> +
> + service = btd_device_get_service(data->device, uuid_str);
> + if (!service)
> + return;
> + if (btd_service_get_state(service) != BTD_SERVICE_STATE_CONNECTING)
> + return;
> +
> + queue_push_tail(data->wait_services, service);
> +
> + if (!data->service_state_id)
> + data->service_state_id = btd_service_add_state_cb(
> + wait_service_cb, data);
> +}
> +
> static int bap_accept(struct btd_service *service)
> {
> struct btd_device *device = btd_service_get_device(service);
> @@ -3760,6 +3816,13 @@ static int bap_accept(struct btd_service *service)
> return -EINVAL;
> }
>
> + queue_destroy(data->wait_services, NULL);
> + data->wait_services = queue_new();
> +
> + queue_push_tail(data->wait_services, NULL);
Why are you pushing NULL above?
> + wait_service_add(data, TMAS_UUID);
> + wait_service_add(data, VCS_UUID);
I wonder if we couldn't add this sort of logic directly in service.c
so in case other plugins need to do some dependency handling like
above we don't need to code it internally in the plugin itself
creating duplicate handling.
> btd_service_connecting_complete(service, 0);
>
> return 0;
> --
> 2.51.1
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH BlueZ 09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures
2025-11-24 16:20 ` Luiz Augusto von Dentz
@ 2025-11-24 16:55 ` Pauli Virtanen
0 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-24 16:55 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hi,
24. marraskuuta 2025 16.20.28 UTC Luiz Augusto von Dentz <luiz.dentz@gmail.com> kirjoitti:
>Hi Pauli,
>
>On Sun, Nov 23, 2025 at 11:18 AM Pauli Virtanen <pav@iki.fi> wrote:
>>
>> Add SupportedFeatures field for indicating TMAP & GMAP roles and
>> features.
>> ---
>> doc/org.bluez.MediaEndpoint.rst | 99 +++++++++++++++++++++++++++++++++
>> 1 file changed, 99 insertions(+)
>>
>> diff --git a/doc/org.bluez.MediaEndpoint.rst b/doc/org.bluez.MediaEndpoint.rst
>> index c1ce1d562..dd4f03e0d 100644
>> --- a/doc/org.bluez.MediaEndpoint.rst
>> +++ b/doc/org.bluez.MediaEndpoint.rst
>> @@ -299,3 +299,102 @@ Indicates QoS capabilities.
>> :uint32 PreferredMaximumDelay:
>>
>> Indicates endpoint preferred maximum presentation delay.
>> +
>> +array{string} SupportedFeatures [readonly, ISO only, experimental]
>> +``````````````````````````````````````````````````````````````````
>> +
>> +List of strings that represent supported features.
>> +
>> +Possible values:
>> +
>> +:"tmap-cg":
>> +
>> + TMAP Call Gateway
>> +
>> +:"tmap-ct":
>> +
>> + TMAP Call Terminal
>> +
>> +:"tmap-ums":
>> +
>> + TMAP Unicast Media Sender
>> +
>> +:"tmap-umr":
>> +
>> + TMAP Unicast Media Receiver
>> +
>> +:"tmap-bms":
>> +
>> + TMAP Broadcast Media Sender
>> +
>> +:"tmap-bmr":
>> +
>> + TMAP Broadcast Media Receiver
>> +
>> +:"gmap-ugg":
>> +
>> + GMAP Unicast Game Gateway
>> +
>> +:"gmap-ugt":
>> +
>> + GMAP Unicast Game Terminal
>> +
>> +:"gmap-bgs":
>> +
>> + GMAP Broadcast Game Sender
>> +
>> +:"gmap-bgr":
>> +
>> + GMAP Broadcast Game Receiver
>> +
>> +:"gmap-ugg-multiplex":
>> +
>> + GMAP UGG Multiplex feature support
>> +
>> +:"gmap-ugg-96kbps-source":
>> +
>> + GMAP UGG 96 kbps Source feature support
>> +
>> +:"gmap-ugg-multisink":
>> +
>> + GMAP UGG Multisink feature support
>> +
>> +:"gmap-ugt-source":
>> +
>> + GMAP UGT Source feature support
>> +
>> +:"gmap-ugt-80kbps-souce":
>> +
>> + GMAP UGT 80 kbps Source feature support
>> +
>> +:"gmap-ugt-sink":
>> +
>> + GMAP UGT Sink feature support
>> +
>> +:"gmap-ugt-64kbps-sink":
>> +
>> + GMAP UGT 64 kbps Sink feature support
>> +
>> +:"gmap-ugt-multiplex":
>> +
>> + GMAP UGT Multiplex feature support
>> +
>> +:"gmap-ugt-multisink":
>> +
>> + GMAP UGT Multisink feature support
>> +
>> +:"gmap-ugt-multisource":
>> +
>> + GMAP UGT Multisource feature support
>> +
>> +:"gmap-bgs-96kbps":
>> +
>> + GMAP BGS 96 kbps feature support
>> +
>> +:"gmap-bgr-multisink":
>> +
>> + GMAP BGR Multisink feature support
>> +
>> +:"gmap-bgr-multiplex":
>> +
>> + GMAP BGR Multiplex feature support
>> --
>> 2.51.1
>
>I wonder if it would make sense to add a sublevel as UUID followed by
>the feature set, this is a little bit more streamline since there is
>just one level though since you are prefixing the profile friendly
>name, that said I think it having a multilevel is probably going to be
>easier to maintain since the value domain would be per UUID rather
>than global.
>
dict uuid -> array of strings should work to group them per service
String arrays may be a bit simpler for clients to parse though, but I guess can get more messy if list grows bigger
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH BlueZ 04/10] bap: have unicast client wait for VCS & TMAS
2025-11-24 16:25 ` Luiz Augusto von Dentz
@ 2025-11-24 16:58 ` Pauli Virtanen
0 siblings, 0 replies; 18+ messages in thread
From: Pauli Virtanen @ 2025-11-24 16:58 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hi,
24. marraskuuta 2025 16.25.49 UTC Luiz Augusto von Dentz <luiz.dentz@gmail.com> kirjoitti:
>Hi Pauli,
>
>On Sun, Nov 23, 2025 at 11:17 AM Pauli Virtanen <pav@iki.fi> wrote:
>>
>> Have unicast client to wait for VCS and TMAS before creating endpoints
>> and transports, so that their information is available at that point.
>> ---
>> profiles/audio/bap.c | 69 ++++++++++++++++++++++++++++++++++++++++++--
>> 1 file changed, 66 insertions(+), 3 deletions(-)
>>
>> diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
>> index b07d65e68..73c5cfc74 100644
>> --- a/profiles/audio/bap.c
>> +++ b/profiles/audio/bap.c
>> @@ -127,6 +127,7 @@ struct bap_data {
>> struct btd_device *device;
>> struct btd_adapter *adapter;
>> struct btd_service *service;
>> + struct queue *wait_services;
>> struct bt_bap *bap;
>> unsigned int ready_id;
>> unsigned int state_id;
>> @@ -139,6 +140,7 @@ struct bap_data {
>> GIOChannel *listen_io;
>> unsigned int io_id;
>> unsigned int cig_update_id;
>> + unsigned int service_state_id;
>> };
>>
>> static struct queue *sessions;
>> @@ -186,11 +188,15 @@ static void bap_data_free(struct bap_data *data)
>> queue_destroy(data->bcast, ep_unregister);
>> queue_destroy(data->server_streams, NULL);
>> queue_destroy(data->bcast_snks, setup_free);
>> + queue_destroy(data->wait_services, NULL);
>> bt_bap_ready_unregister(data->bap, data->ready_id);
>> bt_bap_state_unregister(data->bap, data->state_id);
>> bt_bap_pac_unregister(data->bap, data->pac_id);
>> bt_bap_unref(data->bap);
>>
>> + if (data->service_state_id)
>> + btd_service_remove_state_cb(data->service_state_id);
>> +
>> if (data->cig_update_id)
>> g_source_remove(data->cig_update_id);
>>
>> @@ -2015,13 +2021,16 @@ static bool pac_found_bcast(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
>> return true;
>> }
>>
>> -static void bap_ready(struct bt_bap *bap, void *user_data)
>> +static void bap_service_ready(struct bap_data *data)
>> {
>> - struct btd_service *service = user_data;
>> - struct bap_data *data = btd_service_get_user_data(service);
>> + struct bt_bap *bap = data->bap;
>> + struct btd_service *service = data->service;
>>
>> DBG("bap %p", bap);
>>
>> + if (!queue_isempty(data->wait_services))
>> + return;
>> +
>> /* Register all ep before selecting, so that sound server
>> * knows all.
>> */
>> @@ -2031,6 +2040,15 @@ static void bap_ready(struct bt_bap *bap, void *user_data)
>> bap_select_all(data, false, NULL, NULL);
>> }
>>
>> +static void bap_ready(struct bt_bap *bap, void *user_data)
>> +{
>> + struct btd_service *service = user_data;
>> + struct bap_data *data = btd_service_get_user_data(service);
>> +
>> + queue_remove(data->wait_services, NULL);
>> + bap_service_ready(data);
>> +}
>> +
>> static bool match_setup_stream(const void *data, const void *user_data)
>> {
>> const struct bap_setup *setup = data;
>> @@ -3740,6 +3758,44 @@ static int bap_probe(struct btd_service *service)
>> return 0;
>> }
>>
>> +static void wait_service_cb(struct btd_service *service,
>> + btd_service_state_t old_state,
>> + btd_service_state_t new_state,
>> + void *user_data)
>> +{
>> + struct bap_data *data = user_data;
>> +
>> + if (new_state == BTD_SERVICE_STATE_CONNECTING)
>> + return;
>> + if (!queue_remove(data->wait_services, service))
>> + return;
>> +
>> + DBG("%s", btd_service_get_profile(service)->name);
>> + bap_service_ready(data);
>> +}
>> +
>> +static void wait_service_add(struct bap_data *data, uint16_t remote_uuid)
>> +{
>> + struct btd_service *service;
>> + bt_uuid_t uuid;
>> + char uuid_str[64];
>> +
>> + bt_uuid16_create(&uuid, remote_uuid);
>> + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
>> +
>> + service = btd_device_get_service(data->device, uuid_str);
>> + if (!service)
>> + return;
>> + if (btd_service_get_state(service) != BTD_SERVICE_STATE_CONNECTING)
>> + return;
>> +
>> + queue_push_tail(data->wait_services, service);
>> +
>> + if (!data->service_state_id)
>> + data->service_state_id = btd_service_add_state_cb(
>> + wait_service_cb, data);
>> +}
>> +
>> static int bap_accept(struct btd_service *service)
>> {
>> struct btd_device *device = btd_service_get_device(service);
>> @@ -3760,6 +3816,13 @@ static int bap_accept(struct btd_service *service)
>> return -EINVAL;
>> }
>>
>> + queue_destroy(data->wait_services, NULL);
>> + data->wait_services = queue_new();
>> +
>> + queue_push_tail(data->wait_services, NULL);
>
>Why are you pushing NULL above?
It's for waiting for bap_ready
>
>> + wait_service_add(data, TMAS_UUID);
>> + wait_service_add(data, VCS_UUID);
>
>I wonder if we couldn't add this sort of logic directly in service.c
>so in case other plugins need to do some dependency handling like
>above we don't need to code it internally in the plugin itself
>creating duplicate handling.
Could be changed to be generic yes. We also might want to also do conflicts, eg BAP and AVRPC/AVDTP at the same time sometimes happens in practice and devices usually don't like that.
>
>> btd_service_connecting_complete(service, 0);
>>
>> return 0;
>> --
>> 2.51.1
>>
>>
>
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH BlueZ 00/10] Add TMAP & GMAP information services
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
` (9 preceding siblings ...)
2025-11-23 16:17 ` [PATCH BlueZ 10/10] bap: add SupportedFeatures for MediaEndpoints Pauli Virtanen
@ 2025-11-26 16:20 ` patchwork-bot+bluetooth
10 siblings, 0 replies; 18+ messages in thread
From: patchwork-bot+bluetooth @ 2025-11-26 16:20 UTC (permalink / raw)
To: Pauli Virtanen; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Sun, 23 Nov 2025 18:17:05 +0200 you wrote:
> Add support for TMAP and GMAP services. They contain only device
> audio capability bitmasks.
>
> Expose the values from remote devices in
> org.bluez.MediaEndpoint->SupportedFeatures
>
> Not sure if this should be org.bluez.Device->SupportedFeatures instead.
>
> [...]
Here is the summary with links:
- [BlueZ,01/10] shared/tmap: add TMAP Service
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=12e32efac2e4
- [BlueZ,02/10] test-tmap: add test for TMAP Service
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7b93dae26a57
- [BlueZ,03/10] tmap: add TMAP profile
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=0f56b20f9196
- [BlueZ,04/10] bap: have unicast client wait for VCS & TMAS
(no matching commit)
- [BlueZ,05/10] shared/gmap: add GMAP Service
(no matching commit)
- [BlueZ,06/10] test-gmap: add test for GMAP Service
(no matching commit)
- [BlueZ,07/10] gmap: Add GMAP profile
(no matching commit)
- [BlueZ,08/10] bap: unicast client wait for GMAP service
(no matching commit)
- [BlueZ,09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures
(no matching commit)
- [BlueZ,10/10] bap: add SupportedFeatures for MediaEndpoints
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 18+ messages in thread
* RE: Add TMAP & GMAP information services
2025-11-28 20:02 [PATCH BlueZ v2 1/9] shared/gmap: add GMAP Service Pauli Virtanen
@ 2025-11-28 20:58 ` bluez.test.bot
0 siblings, 0 replies; 18+ messages in thread
From: bluez.test.bot @ 2025-11-28 20:58 UTC (permalink / raw)
To: linux-bluetooth, pav
[-- Attachment #1: Type: text/plain, Size: 1261 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1028785
---Test result---
Test Summary:
CheckPatch PENDING 0.33 seconds
GitLint PENDING 0.30 seconds
BuildEll PASS 20.19 seconds
BluezMake PASS 637.42 seconds
MakeCheck PASS 21.70 seconds
MakeDistcheck PASS 239.12 seconds
CheckValgrind PASS 297.87 seconds
CheckSmatch PASS 344.65 seconds
bluezmakeextell PASS 180.66 seconds
IncrementalBuild PENDING 0.25 seconds
ScanBuild PASS 976.58 seconds
Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:
##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2025-11-28 20:58 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-23 16:17 [PATCH BlueZ 00/10] Add TMAP & GMAP information services Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 01/10] shared/tmap: add TMAP Service Pauli Virtanen
2025-11-23 17:24 ` Add TMAP & GMAP information services bluez.test.bot
2025-11-23 16:17 ` [PATCH BlueZ 02/10] test-tmap: add test for TMAP Service Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 03/10] tmap: add TMAP profile Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 04/10] bap: have unicast client wait for VCS & TMAS Pauli Virtanen
2025-11-24 16:25 ` Luiz Augusto von Dentz
2025-11-24 16:58 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 05/10] shared/gmap: add GMAP Service Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 06/10] test-gmap: add test for " Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 07/10] gmap: Add GMAP profile Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 08/10] bap: unicast client wait for GMAP service Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 09/10] doc: org.bluez.MediaEndpoint: add SupportedFeatures Pauli Virtanen
2025-11-24 16:20 ` Luiz Augusto von Dentz
2025-11-24 16:55 ` Pauli Virtanen
2025-11-23 16:17 ` [PATCH BlueZ 10/10] bap: add SupportedFeatures for MediaEndpoints Pauli Virtanen
2025-11-26 16:20 ` [PATCH BlueZ 00/10] Add TMAP & GMAP information services patchwork-bot+bluetooth
-- strict thread matches above, loose matches on Subject: below --
2025-11-28 20:02 [PATCH BlueZ v2 1/9] shared/gmap: add GMAP Service Pauli Virtanen
2025-11-28 20:58 ` Add TMAP & GMAP information services bluez.test.bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox