public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH BlueZ v2 0/9] Add TMAP & GMAP information services
@ 2025-11-28 20:02 Pauli Virtanen
  2025-11-28 20:02 ` [PATCH BlueZ v2 1/9] shared/gmap: add GMAP Service Pauli Virtanen
                   ` (10 more replies)
  0 siblings, 11 replies; 17+ messages in thread
From: Pauli Virtanen @ 2025-11-28 20:02 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Pauli Virtanen

Add support for TMAP and GMAP services. They contain only device
audio capability bitmasks.

v2:
- Rework the service wait to be general mechanism that also determines
  the service autoconnect order.

  This is now slightly more involved, but this sort of "soft" ordering
  dependency must know which services the autoconnect mechanism is going
  to start.

  Sorting autoconnect services is one of the ways to do it, probably
  makes sense also otherwise, and we could insert service conflict
  handling at the same place.

- Make org.bluez.MediaEndpoint::SupportedFeatures per-uuid dict

---

Expose the values from remote devices in
org.bluez.MediaEndpoint->SupportedFeatures

This maybe could also be org.bluez.Device->SupportedFeatures instead,
but MediaEndpoint looks OK too.

Sound server can use theses to determine which mandatory capabilities
devices have.

TODO (maybe later): add way to configure advertised local service
values, e.g.  via config file.

Pauli Virtanen (9):
  shared/gmap: add GMAP Service
  test-gmap: add test for GMAP Service
  gmap: Add GMAP profile
  doc: org.bluez.MediaEndpoint: add SupportedFeatures
  bap: add SupportedFeatures for MediaEndpoints
  profile: add after_uuids for ordering profile startup
  device: use after_uuids in service autoconnect and sort also GATT
  service: add btd_profile::ready callback when after_uuids ready
  bap: have unicast client wait for VCS & TMAS & GMAP

 .gitignore                      |   1 +
 Makefile.am                     |   7 +
 Makefile.plugins                |   5 +
 configure.ac                    |   7 +
 doc/org.bluez.MediaEndpoint.rst | 108 +++++++
 lib/bluetooth/uuid.h            |   8 +
 profiles/audio/bap.c            | 163 ++++++++++-
 profiles/audio/gmap.c           | 200 +++++++++++++
 src/device.c                    |  74 ++++-
 src/profile.c                   |  89 ++++++
 src/profile.h                   |  16 ++
 src/service.c                   | 100 +++++++
 src/shared/gmap.c               | 401 ++++++++++++++++++++++++++
 src/shared/gmap.h               |  70 +++++
 unit/test-gmap.c                | 496 ++++++++++++++++++++++++++++++++
 15 files changed, 1730 insertions(+), 15 deletions(-)
 create mode 100644 profiles/audio/gmap.c
 create mode 100644 src/shared/gmap.c
 create mode 100644 src/shared/gmap.h
 create mode 100644 unit/test-gmap.c

-- 
2.51.1


^ permalink raw reply	[flat|nested] 17+ messages in thread
* [PATCH BlueZ 01/10] shared/tmap: add TMAP Service
@ 2025-11-23 16:17 Pauli Virtanen
  2025-11-23 17:24 ` Add TMAP & GMAP information services bluez.test.bot
  0 siblings, 1 reply; 17+ 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] 17+ messages in thread

end of thread, other threads:[~2025-12-01 17:57 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-28 20:02 [PATCH BlueZ v2 0/9] Add TMAP & GMAP information services Pauli Virtanen
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
2025-11-28 20:02 ` [PATCH BlueZ v2 2/9] test-gmap: add test for GMAP Service Pauli Virtanen
2025-11-28 20:02 ` [PATCH BlueZ v2 3/9] gmap: Add GMAP profile Pauli Virtanen
2025-11-28 20:02 ` [PATCH BlueZ v2 4/9] doc: org.bluez.MediaEndpoint: add SupportedFeatures Pauli Virtanen
2025-11-28 20:02 ` [PATCH BlueZ v2 5/9] bap: add SupportedFeatures for MediaEndpoints Pauli Virtanen
2025-11-28 20:02 ` [PATCH BlueZ v2 6/9] profile: add after_uuids for ordering profile startup Pauli Virtanen
2025-12-01 17:00   ` Luiz Augusto von Dentz
2025-12-01 17:32     ` Pauli Virtanen
2025-12-01 17:57       ` Luiz Augusto von Dentz
2025-11-28 20:02 ` [PATCH BlueZ v2 7/9] device: use after_uuids in service autoconnect and sort also GATT Pauli Virtanen
2025-11-28 20:02 ` [PATCH BlueZ v2 8/9] service: add btd_profile::ready callback when after_uuids ready Pauli Virtanen
2025-11-28 20:02 ` [PATCH BlueZ v2 9/9] bap: have unicast client wait for VCS & TMAS & GMAP Pauli Virtanen
2025-12-01 14:43 ` [PATCH BlueZ v2 0/9] Add TMAP & GMAP information services Luiz Augusto von Dentz
2025-12-01 16:50 ` patchwork-bot+bluetooth
  -- strict thread matches above, loose matches on Subject: below --
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox