From: "Frédéric Danis" <frederic.danis@collabora.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ 02/12] audio/hfp-hf: Add MediaEndpoint for HFP codecs
Date: Fri, 25 Sep 2026 15:25:18 +0200 [thread overview]
Message-ID: <20260925132528.3361517-3-frederic.danis@collabora.com> (raw)
In-Reply-To: <20260925132528.3361517-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
---
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 ++++++++++++++
5 files changed, 681 insertions(+)
create mode 100644 profiles/audio/hfp-hf.h
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
next prev parent reply other threads:[~2026-09-25 13:25 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 13:25 [PATCH BlueZ 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 01/12] audio/hfp-hf: Add HFP HF server and SDP record Frédéric Danis
2026-09-25 15:37 ` hfp-hf: Enhance HFP Hands-Free profile support bluez.test.bot
2026-09-25 13:25 ` Frédéric Danis [this message]
2026-09-25 13:25 ` [PATCH BlueZ 03/12] client/player: Add MediaEndpoints for HFP HF codecs Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 04/12] shared/hfp: Add hangup all calls support Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 05/12] audio/hfp-hf: " Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 06/12] shared/hfp: Add send tones support Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 07/12] audio/hfp-hf: Add Send Tones support Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 08/12] client/telephony: Add Send Tones menu entry Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 09/12] audio/hfp-hf: Add multi calls support Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 10/12] client/telephony: " Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 11/12] shared/hfp: Add codecs support Frédéric Danis
2026-09-25 13:25 ` [PATCH BlueZ 12/12] audio/hfp-hf: " Frédéric Danis
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=20260925132528.3361517-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox