All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Frédéric Danis" <frederic.danis@collabora.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v2 02/12] audio/hfp-hf: Add MediaEndpoint for HFP codecs
Date: Thu,  1 Oct 2026 23:10:26 +0200	[thread overview]
Message-ID: <20261001211036.1760396-3-frederic.danis@collabora.com> (raw)
In-Reply-To: <20261001211036.1760396-1-frederic.danis@collabora.com>

Register a MediaEndpoint1 (org.bluez.MediaEndpoint1) for each HFP
codec supported by the remote AG, exposing SCO audio through the
standard media transport API.

Adds a SCO listening/connecting socket in hfp-hf, endpoint
registration/unregistration tied to the HFP connection lifecycle,
and wires MediaTransport creation/configuration for the HFP_AG_UUID
endpoint in media.c.

Assisted-by: Claude:claude-sonnet-5
---
v1->v2: Fix make distcheck

 Makefile.plugins           |   6 +-
 profiles/audio/hfp-hf.c    | 415 +++++++++++++++++++++++++++++++++++++
 profiles/audio/hfp-hf.h    |  26 +++
 profiles/audio/media.c     |  76 +++++++
 profiles/audio/media.h     |   6 +
 profiles/audio/transport.c | 158 ++++++++++++++
 6 files changed, 686 insertions(+), 1 deletion(-)
 create mode 100644 profiles/audio/hfp-hf.h

diff --git a/Makefile.plugins b/Makefile.plugins
index a49278441..74b09cc64 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -153,5 +153,9 @@ endif
 
 if HFP
 builtin_modules += hfp
-builtin_sources += profiles/audio/telephony.h profiles/audio/telephony.c profiles/audio/hfp-hf.c
+builtin_sources += profiles/audio/telephony.h \
+			profiles/audio/telephony.c \
+			profiles/audio/hfp-hf.c \
+			profiles/audio/hfp-hf.h
+
 endif
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 102a8006f..dddd56261 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -44,8 +44,12 @@
 #include "src/shared/hfp.h"
 #include "src/shared/queue.h"
 
+#include "hfp-hf.h"
+#include "media.h"
 #include "telephony.h"
 
+#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
+
 #define HFP_HF_VERSION		0x0109
 #define HFP_HF_DEFAULT_CHANNEL	7
 
@@ -70,14 +74,23 @@ struct hfp_device {
 	struct telephony	*telephony;
 	uint16_t		version;
 	GIOChannel		*io;
+	GIOChannel		*sco_io;
+	uint16_t		imtu;
+	uint16_t		omtu;
 	struct hfp_hf		*hf;
 	struct queue		*calls;
+	uint8_t			codec;
+	unsigned int		resume_id;
+	hfp_hf_sco_closed_cb	sco_closed_cb;
+	void			*sco_closed_data;
 };
 
 struct hfp_server {
 	struct btd_adapter	*adapter;
 	GIOChannel		*io;
+	GIOChannel		*sco_io;
 	uint32_t		record_id;
+	GSList			*endpoints;
 };
 
 static GSList *servers;
@@ -87,6 +100,22 @@ static void hfp_hf_debug(const char *str, void *user_data)
 	DBG_IDX(0xffff, "%s", str);
 }
 
+static char *make_endpoint_path(struct telephony *telephony, uint8_t codec)
+{
+	char *path;
+	int err;
+
+	err = asprintf(&path, "%s/sep%u", telephony_get_path(telephony),
+								codec);
+	if (err < 0) {
+		error("Could not allocate path for remote %s",
+			device_get_path(telephony_get_device(telephony)));
+		return NULL;
+	}
+
+	return path;
+}
+
 static struct hfp_server *find_server(GSList *list, struct btd_adapter *a)
 {
 	for (; list; list = list->next) {
@@ -99,6 +128,20 @@ static struct hfp_server *find_server(GSList *list, struct btd_adapter *a)
 	return NULL;
 }
 
+static void unregister_endpoint(gpointer data, gpointer user_data)
+{
+	struct media_endpoint *ep = data;
+	struct telephony *telephony = user_data;
+	char *path;
+
+	path = make_endpoint_path(telephony, media_endpoint_get_codec(ep));
+	if (path) {
+		g_dbus_unregister_interface(btd_get_dbus_connection(),
+				path, MEDIA_ENDPOINT_INTERFACE);
+		free(path);
+	}
+}
+
 static enum call_state hfp_call_status_to_call_state(
 						enum hfp_call_status status)
 {
@@ -128,6 +171,8 @@ static bool call_id_cmp(const void *data, const void *match_data)
 
 static void device_destroy(struct hfp_device *dev)
 {
+	struct hfp_server *server;
+
 	DBG("%s", telephony_get_path(dev->telephony));
 
 	telephony_set_state(dev->telephony, DISCONNECTING);
@@ -137,11 +182,22 @@ static void device_destroy(struct hfp_device *dev)
 		dev->hf = NULL;
 	}
 
+	if (dev->sco_io) {
+		g_io_channel_unref(dev->sco_io);
+		dev->sco_io = NULL;
+	}
+
 	if (dev->io) {
 		g_io_channel_unref(dev->io);
 		dev->io = NULL;
 	}
 
+	server = find_server(servers,
+		device_get_adapter(telephony_get_device(dev->telephony)));
+	if (server)
+		g_slist_foreach(server->endpoints, unregister_endpoint,
+					dev->telephony);
+
 	telephony_unregister_interface(dev->telephony);
 }
 
@@ -296,10 +352,68 @@ static void hfp_disconnect_watch(void *user_data)
 	device_destroy(user_data);
 }
 
+static gboolean get_uuid(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	const char *uuid;
+
+	uuid = HFP_HS_UUID;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid);
+
+	return TRUE;
+}
+
+static gboolean get_device(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	struct hfp_device *dev = data;
+	const char *path;
+
+	path = device_get_path(telephony_get_device(dev->telephony));
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+	return TRUE;
+}
+
+static const GDBusMethodTable hfp_hf_ep_methods[] = {
+	{ },
+};
+
+static const GDBusPropertyTable hfp_hf_ep_properties[] = {
+	{ "UUID", "s", get_uuid, NULL, NULL,
+					G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
+	{ "Device", "o", get_device, NULL, NULL,
+					G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
+	{ }
+};
+
+static void register_endpoint(gpointer data, gpointer user_data)
+{
+	struct media_endpoint *ep = data;
+	struct hfp_device *dev = user_data;
+	char *path = NULL;
+
+	path = make_endpoint_path(dev->telephony,
+					media_endpoint_get_codec(ep));
+	if (path) {
+		if (g_dbus_register_interface(btd_get_dbus_connection(),
+					path, MEDIA_ENDPOINT_INTERFACE,
+					hfp_hf_ep_methods, NULL,
+					hfp_hf_ep_properties,
+					dev, NULL) == FALSE) {
+			error("Could not register remote ep %s", path);
+		}
+		free(path);
+	}
+}
+
 static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
 {
 	struct hfp_device *dev = user_data;
 	struct btd_service *service = telephony_get_service(dev->telephony);
+	struct hfp_server *server;
 
 	DBG("");
 
@@ -333,6 +447,10 @@ static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
 	telephony_set_state(dev->telephony, SESSION_CONNECTING);
 	btd_service_connecting_complete(service, 0);
 
+	server = find_server(servers,
+		device_get_adapter(telephony_get_device(dev->telephony)));
+	g_slist_foreach(server->endpoints, register_endpoint, dev);
+
 	return;
 
 failed:
@@ -507,6 +625,8 @@ static int hfp_probe(struct btd_service *service)
 		return -EINVAL;
 
 	dev->telephony = telephony_new(service, dev, &hfp_callbacks);
+	/* Use CVSD codec by default */
+	dev->codec = 1;
 	btd_service_set_user_data(service, dev);
 	telephony_add_uri_scheme(dev->telephony, URI);
 
@@ -670,6 +790,296 @@ static GIOChannel *server_socket(struct btd_adapter *adapter)
 	return io;
 }
 
+static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond, void *data)
+{
+	struct hfp_device *dev = data;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	DBG("sco connection released");
+	g_io_channel_shutdown(dev->sco_io, TRUE, NULL);
+	g_io_channel_unref(dev->sco_io);
+	dev->sco_io = NULL;
+
+	/* The remote end hung up the SCO connection instead of us
+	 * releasing it locally, e.g. via Suspend/Release on the media
+	 * transport. Notify the transport so it can update its state
+	 * and release/suspend accordingly.
+	 */
+	if (dev->sco_closed_cb)
+		dev->sco_closed_cb(dev, dev->sco_closed_data);
+
+	return FALSE;
+}
+
+struct connect_data {
+	struct hfp_device *dev;
+	void (*cb)(int status, void *data);
+	void *cb_user_data;
+};
+
+static void sco_connect_complete(struct connect_data *connect_data, int status)
+{
+	if (!connect_data || !connect_data->cb)
+		goto done;
+
+	connect_data->cb(status, connect_data->cb_user_data);
+
+done:
+	g_free(connect_data);
+}
+
+static void sco_connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+	struct connect_data *connect_data = user_data;
+	bdaddr_t src, dst;
+	char addr[18];
+	uint16_t handle;
+	struct btd_adapter *adapter;
+	struct btd_device *device;
+	struct btd_service *service;
+	struct hfp_device *dev;
+	struct hfp_server *server;
+	struct media_endpoint *endpoint = NULL;
+	GSList *l;
+	char *path = NULL;
+	uint16_t imtu, omtu;
+
+	if (err) {
+		error("Connecting failed: %s\n", err->message);
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+
+	if (!bt_io_get(io, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_DEST, addr,
+			BT_IO_OPT_HANDLE, &handle,
+			BT_IO_OPT_IMTU, &imtu,
+			BT_IO_OPT_OMTU, &omtu,
+			BT_IO_OPT_INVALID)) {
+		error("Unable to get destination address: %s\n", err->message);
+		g_clear_error(&err);
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+
+	DBG("Successfully connected to %s. handle=%u imtu=%u omtu=%u",
+						addr, handle, imtu, omtu);
+
+	adapter = adapter_find(&src);
+	if (!adapter) {
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+
+	device = btd_adapter_find_device(adapter, &dst, BDADDR_BREDR);
+	if (!device) {
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+
+	service = btd_device_get_service(device, HFP_AG_UUID);
+	if (!service) {
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+
+	dev = btd_service_get_user_data(service);
+
+	server = find_server(servers, adapter);
+	if (server == NULL) {
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+
+	for (l = server->endpoints; l; l = l->next) {
+		if (media_endpoint_get_codec(l->data) == dev->codec) {
+			endpoint = l->data;
+			break;
+		}
+	}
+	if (endpoint == NULL) {
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+
+	path = make_endpoint_path(dev->telephony, dev->codec);
+	if (path == NULL) {
+		error("Could not allocate path for remote %s",
+			device_get_path(telephony_get_device(dev->telephony)));
+		sco_connect_complete(connect_data, -ENOMEM);
+		return;
+	}
+
+	if (!hfp_hf_set_configuration(endpoint, path, NULL, dev, NULL)) {
+		free(path);
+		sco_connect_complete(connect_data, -EIO);
+		return;
+	}
+	free(path);
+
+	dev->imtu = imtu;
+	dev->omtu = omtu;
+	dev->sco_io = g_io_channel_ref(io);
+
+	g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+				(GIOFunc) sco_io_cb, dev);
+
+	sco_connect_complete(connect_data, 0);
+}
+
+bool hfp_hf_sco_listen(struct btd_adapter *adapter, void *endpoint)
+{
+	struct hfp_server *server;
+	GError *err = NULL;
+
+	DBG("path %s, codec %u", adapter_get_path(adapter),
+					media_endpoint_get_codec(endpoint));
+
+	server = find_server(servers, adapter);
+	if (server == NULL)
+		return false;
+
+	server->endpoints = g_slist_append(server->endpoints, endpoint);
+
+	if (server->sco_io)
+		return true;
+
+	server->sco_io = bt_io_listen(sco_connect_cb, NULL, NULL, NULL,
+				&err,
+				BT_IO_OPT_SOURCE_BDADDR,
+				btd_adapter_get_address(server->adapter),
+				BT_IO_OPT_INVALID);
+	if (server->sco_io) {
+		DBG("SCO server started");
+		return true;
+	}
+
+	server->endpoints = g_slist_remove(server->endpoints, endpoint);
+	error("%s", err->message);
+	g_error_free(err);
+
+	return false;
+}
+
+void hfp_hf_sco_remove(struct btd_adapter *adapter, void *endpoint)
+{
+	struct hfp_server *server;
+
+	DBG("path %s, codec %u", adapter_get_path(adapter),
+					media_endpoint_get_codec(endpoint));
+
+	server = find_server(servers, adapter);
+	if (server == NULL) {
+		error("No server for %s codec %u", adapter_get_path(adapter),
+					media_endpoint_get_codec(endpoint));
+		return;
+	}
+
+	server->endpoints = g_slist_remove(server->endpoints, endpoint);
+
+	if (server->sco_io && g_slist_length(server->endpoints) == 0) {
+		g_io_channel_shutdown(server->sco_io, TRUE, NULL);
+		g_io_channel_unref(server->sco_io);
+		server->sco_io = NULL;
+		DBG("SCO server stopped");
+	}
+}
+
+struct btd_device *hfp_hf_get_device(struct hfp_device *dev)
+{
+	return telephony_get_device(dev->telephony);
+}
+
+int hfp_hf_device_get_fd(struct hfp_device *dev)
+{
+	if (!dev->sco_io)
+		return -1;
+
+	return g_io_channel_unix_get_fd(dev->sco_io);
+}
+
+uint16_t hfp_hf_device_get_imtu(struct hfp_device *dev)
+{
+	return dev->imtu;
+}
+
+uint16_t hfp_hf_device_get_omtu(struct hfp_device *dev)
+{
+	return dev->omtu;
+}
+
+static gboolean sco_start_cb(gpointer data)
+{
+	struct connect_data *connect_data = data;
+
+	if (connect_data && connect_data->cb) {
+		connect_data->cb(0, connect_data->cb_user_data);
+		g_free(connect_data);
+	}
+
+	return FALSE;
+}
+
+unsigned int hfp_hf_sco_start(struct hfp_device *dev, void *cb, void *user_data)
+{
+	bdaddr_t src, dst;
+	struct connect_data *connect_data;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	DBG("codec %u", dev->codec);
+
+	connect_data = g_new0(struct connect_data, 1);
+	connect_data->dev = dev;
+	connect_data->cb = cb;
+	connect_data->cb_user_data = user_data;
+
+	src = telephony_get_src(dev->telephony);
+	dst = telephony_get_dst(dev->telephony);
+	if (!dev->sco_io) {
+		io = bt_io_connect(sco_connect_cb, connect_data, NULL, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+			BT_IO_OPT_INVALID);
+		if (!io) {
+			error("%s", err->message);
+			g_error_free(err);
+			g_free(connect_data);
+			return 0;
+		}
+	} else {
+		g_idle_add(sco_start_cb, connect_data);
+	}
+
+	return (++dev->resume_id);
+}
+
+unsigned int hfp_hf_sco_stop(struct hfp_device *dev)
+{
+	if (!dev->sco_io)
+		return 0;
+
+	DBG("codec %u", dev->codec);
+
+	g_io_channel_shutdown(dev->sco_io, TRUE, NULL);
+	g_io_channel_unref(dev->sco_io);
+	dev->sco_io = NULL;
+
+	return dev->resume_id;
+}
+
+void hfp_hf_set_sco_closed_cb(struct hfp_device *dev,
+				hfp_hf_sco_closed_cb cb, void *user_data)
+{
+	dev->sco_closed_cb = cb;
+	dev->sco_closed_data = user_data;
+}
+
 static int hfp_adapter_probe(struct btd_profile *p,
 				struct btd_adapter *adapter)
 {
@@ -724,6 +1134,11 @@ static void hfp_adapter_remove(struct btd_profile *p,
 	if (!server)
 		return;
 
+	if (server->sco_io) {
+		g_io_channel_shutdown(server->sco_io, TRUE, NULL);
+		g_io_channel_unref(server->sco_io);
+	}
+
 	if (server->io) {
 		g_io_channel_shutdown(server->io, TRUE, NULL);
 		g_io_channel_unref(server->io);
diff --git a/profiles/audio/hfp-hf.h b/profiles/audio/hfp-hf.h
new file mode 100644
index 000000000..6dfa3b12f
--- /dev/null
+++ b/profiles/audio/hfp-hf.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+struct hfp_device;
+
+typedef void (*hfp_hf_sco_closed_cb) (struct hfp_device *dev, void *user_data);
+
+bool hfp_hf_sco_listen(struct btd_adapter *adapter, void *endpoint);
+void hfp_hf_sco_remove(struct btd_adapter *adapter, void *endpoint);
+unsigned int hfp_hf_sco_start(struct hfp_device *dev, void *cb,
+							void *user_data);
+unsigned int hfp_hf_sco_stop(struct hfp_device *dev);
+void hfp_hf_set_sco_closed_cb(struct hfp_device *dev,
+				hfp_hf_sco_closed_cb cb, void *user_data);
+
+struct btd_device *hfp_hf_get_device(struct hfp_device *dev);
+int hfp_hf_device_get_fd(struct hfp_device *dev);
+uint16_t hfp_hf_device_get_imtu(struct hfp_device *dev);
+uint16_t hfp_hf_device_get_omtu(struct hfp_device *dev);
diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index dd1d4e571..e8418280e 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -66,6 +66,9 @@
 #ifdef HAVE_A2DP
 #include "a2dp.h"
 #endif
+#ifdef HAVE_HFP
+#include "hfp-hf.h"
+#endif
 
 #define MEDIA_INTERFACE "org.bluez.Media1"
 #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
@@ -119,6 +122,7 @@ struct media_endpoint {
 #endif
 	struct bt_bap_pac	*pac;
 	struct bt_asha_device	*asha;
+	bool			sco;
 	char			*sender;	/* Endpoint DBus bus id */
 	char			*path;		/* Endpoint object path */
 	char			*uuid;		/* Endpoint property UUID */
@@ -234,6 +238,11 @@ static void media_endpoint_destroy(struct media_endpoint *endpoint)
 		endpoint->pac = NULL;
 	}
 
+	if (endpoint->sco) {
+		hfp_hf_sco_remove(endpoint->adapter->btd_adapter, endpoint);
+		endpoint->sco = false;
+	}
+
 	g_dbus_remove_watch(btd_get_dbus_connection(), endpoint->watch);
 	g_free(endpoint->capabilities);
 	g_free(endpoint->metadata);
@@ -1430,6 +1439,64 @@ static bool endpoint_init_asha(struct media_endpoint *endpoint,
 	return true;
 }
 
+gboolean hfp_hf_set_configuration(struct media_endpoint *endpoint,
+					const char *ep_path,
+					media_endpoint_cb_t cb,
+					void *user_data,
+					GDestroyNotify destroy)
+{
+	struct hfp_device *hfp_dev = user_data;
+	struct btd_device *device = hfp_hf_get_device(hfp_dev);
+	DBusConnection *conn = btd_get_dbus_connection();
+	DBusMessage *msg;
+	DBusMessageIter iter;
+	struct media_transport *transport;
+	const char *path;
+
+	msg = dbus_message_new_method_call(endpoint->sender, endpoint->path,
+						MEDIA_ENDPOINT_INTERFACE,
+						"SetConfiguration");
+	if (msg == NULL) {
+		error("Couldn't allocate D-Bus message");
+		return FALSE;
+	}
+
+	transport = find_device_transport(endpoint, device);
+	if (transport == NULL) {
+		transport = media_transport_create(device, ep_path, NULL, 0,
+							endpoint, hfp_dev);
+		if (transport == NULL)
+			return FALSE;
+
+		endpoint->transports = g_slist_append(endpoint->transports,
+								transport);
+	}
+
+	dbus_message_iter_init_append(msg, &iter);
+
+	path = media_transport_get_path(transport);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+	g_dbus_get_properties(conn, path, "org.bluez.MediaTransport1", &iter);
+
+	return media_endpoint_async_call(msg, endpoint, transport,
+						cb, user_data, destroy, -1);
+}
+
+static bool endpoint_init_sco(struct media_endpoint *endpoint,
+						int *err)
+{
+	if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) {
+		DBG("D-Bus experimental not enabled");
+		*err = -ENOTSUP;
+		return false;
+	}
+
+	endpoint->sco = true;
+
+	return hfp_hf_sco_listen(endpoint->adapter->btd_adapter, endpoint);
+}
+
 static bool endpoint_properties_exists(const char *uuid,
 						struct btd_device *dev,
 						void *user_data)
@@ -1559,6 +1626,11 @@ static bool experimental_asha_supported(struct btd_adapter *adapter)
 	return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL;
 }
 
+static bool experimental_sco_supported(struct btd_adapter *adapter)
+{
+	return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL;
+}
+
 static const struct media_endpoint_init {
 	const char *uuid;
 	bool (*func)(struct media_endpoint *endpoint, int *err);
@@ -1580,6 +1652,10 @@ static const struct media_endpoint_init {
 			experimental_bcast_sink_ep_supported },
 	{ ASHA_PROFILE_UUID, endpoint_init_asha,
 			experimental_asha_supported },
+#ifdef HAVE_HFP
+	{ HFP_AG_UUID, endpoint_init_sco,
+			experimental_sco_supported },
+#endif
 };
 
 static struct media_endpoint *
diff --git a/profiles/audio/media.h b/profiles/audio/media.h
index 43a85b1d6..a84634fff 100644
--- a/profiles/audio/media.h
+++ b/profiles/audio/media.h
@@ -82,3 +82,9 @@ typedef void (*local_player_added_t)(struct local_player *lp, void *user_data);
 unsigned int local_player_register_watch(local_player_added_t cb,
 							void *user_data);
 void local_player_unregister_watch(unsigned int id);
+
+gboolean hfp_hf_set_configuration(struct media_endpoint *endpoint,
+					const char *ep_path,
+					media_endpoint_cb_t cb,
+					void *user_data,
+					GDestroyNotify destroy);
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index e3f3df50e..8cfc98a52 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -53,6 +53,10 @@
 #include "asha.h"
 #endif
 
+#ifdef HAVE_HFP
+#include "hfp-hf.h"
+#endif
+
 #include "media.h"
 #include "transport.h"
 #include "vcp.h"
@@ -1748,6 +1752,15 @@ static const GDBusPropertyTable transport_asha_properties[] = {
 };
 #endif /* HAVE_ASHA */
 
+static const GDBusPropertyTable transport_hfp_properties[] = {
+	{ "Device", "o", get_device },
+	{ "Endpoint", "o", get_endpoint, NULL, endpoint_exists },
+	{ "UUID", "s", get_uuid },
+	{ "Codec", "y", get_codec },
+	{ "State", "s", get_state },
+	{ }
+};
+
 #ifdef HAVE_A2DP
 static void transport_a2dp_destroy(void *data)
 {
@@ -2644,6 +2657,140 @@ static void *transport_asha_init(struct media_transport *transport, void *data)
 }
 #endif /* HAVE_ASHA */
 
+#ifdef HAVE_HFP
+static void hfp_transport_sco_closed(struct hfp_device *hfp_dev,
+							void *user_data)
+{
+	struct media_transport *transport = user_data;
+
+	DBG("%s", transport->path);
+
+	/* The remote hung up the SCO connection on its own, without us
+	 * requesting a Release/Suspend. Bring the transport back down so
+	 * its state reflects reality.
+	 */
+	if (transport->owner)
+		media_transport_remove_owner(transport);
+	else if (transport->state != TRANSPORT_STATE_IDLE)
+		transport_set_state(transport, TRANSPORT_STATE_IDLE);
+}
+
+static void *transport_hfp_init(struct media_transport *transport, void *data)
+{
+	/* We just store the struct hfp_device on the transport */
+	hfp_hf_set_sco_closed_cb(data, hfp_transport_sco_closed, transport);
+
+	return data;
+}
+
+static void transport_hfp_destroy(void *data)
+{
+	hfp_hf_set_sco_closed_cb(data, NULL, NULL);
+}
+
+static void hfp_transport_resume_cb(int status, void *user_data)
+{
+	struct media_owner *owner = user_data;
+	struct media_transport *transport = owner->transport;
+	struct hfp_device *hfp_dev = transport->data;
+	int fd;
+	uint16_t imtu, omtu;
+	gboolean ret;
+
+	DBG("");
+
+	if (!transport) {
+		DBG("Lost owner while connecting, bailing");
+		return;
+	}
+
+	fd = hfp_hf_device_get_fd(hfp_dev);
+	imtu = hfp_hf_device_get_imtu(hfp_dev);
+	omtu = hfp_hf_device_get_omtu(hfp_dev);
+
+	media_transport_set_fd(transport, fd, imtu, omtu);
+
+	owner->pending->id = 0;
+	ret = g_dbus_send_reply(btd_get_dbus_connection(),
+			owner->pending->msg,
+			DBUS_TYPE_UNIX_FD, &fd,
+			DBUS_TYPE_UINT16, &imtu,
+			DBUS_TYPE_UINT16, &omtu,
+			DBUS_TYPE_INVALID);
+	if (!ret) {
+		media_transport_remove_owner(transport);
+		return;
+	}
+
+	media_owner_remove(owner);
+
+	transport_set_state(transport, TRANSPORT_STATE_ACTIVE);
+}
+
+static guint transport_hfp_resume(struct media_transport *transport,
+						struct media_owner *owner)
+{
+	struct hfp_device *hfp_dev = transport->data;
+
+	return hfp_hf_sco_start(hfp_dev, hfp_transport_resume_cb, owner);
+}
+
+static gboolean hfp_transport_suspend_cb(void *user_data)
+{
+	struct media_owner *owner = user_data;
+	struct media_transport *transport = owner->transport;
+
+	/* Release always succeeds */
+	if (owner->pending) {
+		owner->pending->id = 0;
+		media_request_reply(owner->pending, 0);
+		media_owner_remove(owner);
+	}
+
+	media_transport_remove_owner(transport);
+	return FALSE;
+}
+
+static guint transport_hfp_suspend(struct media_transport *transport,
+						struct media_owner *owner)
+{
+	struct hfp_device *hfp_dev = transport->data;
+	guint ret = 0;
+
+	hfp_hf_sco_stop(hfp_dev);
+
+	if (owner) {
+		/* Return the g_idle_add() source id here, not
+		 * hfp_hf_sco_stop()'s return value (dev->resume_id). The
+		 * latter is unrelated and is 0 whenever dev->sco_io is
+		 * already NULL, e.g. right after the peer has hung up SCO.
+		 * release() below treats a 0 id as "no pending async
+		 * operation" and immediately, synchronously frees owner via
+		 * media_transport_remove_owner() -- while the idle callback
+		 * queued here still holds a pointer to it, causing a
+		 * use-after-free crash in hfp_transport_suspend_cb() on the
+		 * next mainloop iteration.
+		 */
+		ret = g_idle_add(hfp_transport_suspend_cb, owner);
+	} else {
+		/* We won't have a callback to set the final state */
+		transport_set_state(transport, TRANSPORT_STATE_IDLE);
+	}
+
+	return ret;
+}
+
+static void transport_hfp_cancel(struct media_transport *transport, guint id)
+{
+	/* id is the g_idle_add() source id returned by transport_hfp_suspend()
+	 * for its still-pending hfp_transport_suspend_cb() callback. Cancel it
+	 * so it can never fire with a dangling owner pointer if owner is torn
+	 * down through another path first.
+	 */
+	g_source_remove(id);
+}
+#endif /* HAVE_HFP */
+
 #define TRANSPORT_OPS(_uuid, _props, _set_owner, _remove_owner, _init, \
 		      _resume, _suspend, _cancel, _set_state, _get_stream, \
 		      _get_volume, _set_volume, _set_delay, _update_links, \
@@ -2704,6 +2851,14 @@ static void *transport_asha_init(struct media_transport *transport, void *data)
 			transport_asha_get_volume, transport_asha_set_volume, \
 			NULL, NULL, NULL, NULL)
 
+#define HFP_OPS(_uuid) \
+	TRANSPORT_OPS(_uuid, transport_hfp_properties, NULL, NULL, \
+			transport_hfp_init, \
+			transport_hfp_resume, transport_hfp_suspend, \
+			transport_hfp_cancel, NULL, NULL, \
+			NULL, NULL, \
+			NULL, NULL, transport_hfp_destroy, NULL)
+
 static const struct media_transport_ops transport_ops[] = {
 #ifdef HAVE_A2DP
 	A2DP_OPS(A2DP_SOURCE_UUID, transport_a2dp_src_init,
@@ -2730,6 +2885,9 @@ static const struct media_transport_ops transport_ops[] = {
 #ifdef HAVE_ASHA
 	ASHA_OPS(ASHA_PROFILE_UUID),
 #endif /* HAVE_ASHA */
+#ifdef HAVE_HFP
+	HFP_OPS(HFP_AG_UUID),
+#endif /* HAVE_HFP */
 };
 
 static const struct media_transport_ops *
-- 
2.43.0


  parent reply	other threads:[~2026-10-01 21:10 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 01/12] audio/hfp-hf: Add HFP HF server and SDP record Frédéric Danis
2026-10-02  0:53   ` hfp-hf: Enhance HFP Hands-Free profile support bluez.test.bot
2026-10-01 21:10 ` Frédéric Danis [this message]
2026-10-01 21:10 ` [PATCH BlueZ v2 03/12] client/player: Add MediaEndpoints for HFP HF codecs Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 04/12] shared/hfp: Add hangup all calls support Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 05/12] audio/hfp-hf: " Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 06/12] shared/hfp: Add send tones support Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 07/12] audio/hfp-hf: Add Send Tones support Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 08/12] client/telephony: Add Send Tones menu entry Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 09/12] audio/hfp-hf: Add multi calls support Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 10/12] client/telephony: " Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 11/12] shared/hfp: Add codecs support Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 12/12] audio/hfp-hf: " Frédéric Danis
2026-10-05 20:20 ` [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support patchwork-bot+bluetooth

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261001211036.1760396-3-frederic.danis@collabora.com \
    --to=frederic.danis@collabora.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.