* RE: hfp-hf: Enhance HFP Hands-Free profile support
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 ` bluez.test.bot
0 siblings, 0 replies; 16+ messages in thread
From: bluez.test.bot @ 2026-09-25 15:37 UTC (permalink / raw)
To: linux-bluetooth, frederic.danis
[-- Attachment #1: Type: text/plain, Size: 1496 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/series/1173777/
---Test result---
Test Summary:
CheckPatch PASS 6.13 seconds
GitLint PASS 3.33 seconds
BuildEll PASS 15.61 seconds
BluezMake PASS 266.51 seconds
MakeCheck PASS 2.79 seconds
MakeDistcheck FAIL 35.64 seconds
CheckValgrind PASS 119.94 seconds
CheckSmatch PASS 196.93 seconds
bluezmakeextell PASS 76.02 seconds
TestFunctional PASS 879.40 seconds
IncrementalBuild PASS 320.00 seconds
ScanBuild PASS 773.86 seconds
Details
##############################
Test: MakeDistcheck - FAIL
Desc: Run Bluez Make Distcheck
Output:
../../profiles/audio/hfp-hf.c:47:10: fatal error: hfp-hf.h: No such file or directory
47 | #include "hfp-hf.h"
| ^~~~~~~~~~
compilation terminated.
make[2]: *** [Makefile:9451: profiles/audio/bluetoothd-hfp-hf.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [Makefile:4322: all] Error 2
make: *** [Makefile:11113: distcheck] Error 1
https://github.com/bluez/bluez/pull/2588
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support
@ 2026-10-01 21:10 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
` (12 more replies)
0 siblings, 13 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
This series enhanced the HFP Hands-Free (HF) role support in BlueZ:
- Registration:
- Register the HFP HF profile with an SDP record so BlueZ is
discoverable/connectable as a Hands-Free device.
- Audio path:
- Register a MediaEndpoint1 per supported HFP codec and wire up
MediaTransport creation/configuration so SCO audio is exposed
through the standard Media API instead of a bespoke interface.
- Extend bluetoothctl (client/player) with matching menu entries
to select/manage the HFP HF MediaEndpoints.
- Codec negotiation:
- Implement AT+BAC / +BCS handling in shared/hfp so the supported
codec list is advertised during SLC setup and codec selection
from the AG is honored (or renegotiated when unavailable).
- Multi-call handling:
- Implement SwapCalls, ReleaseAndAnswer, ReleaseAndSwap and
HoldAndAnswer on org.bluez.Telephony1, plus corresponding
bluetoothctl menu entries.
- Add support for releasing all calls except waiting ones
(including multiparty calls), both in shared/hfp and audio/hfp-hf.
- DTMF:
- Add AT+VTS-based Send Tones support in shared/hfp and audio/hfp-hf,
with a bluetoothctl menu entry to trigger it.
New related PipeWire BlueZ backend draft can be found at:
https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/2978
v1->v2: Fix make distcheck
Frédéric Danis (12):
audio/hfp-hf: Add HFP HF server and SDP record
audio/hfp-hf: Add MediaEndpoint for HFP codecs
client/player: Add MediaEndpoints for HFP HF codecs
shared/hfp: Add hangup all calls support
audio/hfp-hf: Add hangup all calls support
shared/hfp: Add send tones support
audio/hfp-hf: Add Send Tones support
client/telephony: Add Send Tones menu entry
audio/hfp-hf: Add multi calls support
client/telephony: Add multi calls support
shared/hfp: Add codecs support
audio/hfp-hf: Add codecs support
Makefile.plugins | 6 +-
client/player.c | 18 +-
client/telephony.c | 237 +++++++++
profiles/audio/hfp-hf.c | 979 +++++++++++++++++++++++++++++++++++++
profiles/audio/hfp-hf.h | 26 +
profiles/audio/media.c | 76 +++
profiles/audio/media.h | 6 +
profiles/audio/transport.c | 158 ++++++
src/shared/hfp.c | 229 ++++++++-
src/shared/hfp.h | 13 +
10 files changed, 1740 insertions(+), 8 deletions(-)
create mode 100644 profiles/audio/hfp-hf.h
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 01/12] audio/hfp-hf: Add HFP HF server and SDP record
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 ` 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 ` [PATCH BlueZ v2 02/12] audio/hfp-hf: Add MediaEndpoint for HFP codecs Frédéric Danis
` (11 subsequent siblings)
12 siblings, 1 reply; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
---
profiles/audio/hfp-hf.c | 253 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 253 insertions(+)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 8de2d7a62..102a8006f 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -46,6 +46,23 @@
#include "telephony.h"
+#define HFP_HF_VERSION 0x0109
+#define HFP_HF_DEFAULT_CHANNEL 7
+
+#define HFP_HF_SDP_ECNR 0x0001
+#define HFP_HF_SDP_3WAY 0x0002
+#define HFP_HF_SDP_CLIP 0x0004
+#define HFP_HF_SDP_VOICE_RECOGNITION 0x0008
+#define HFP_HF_SDP_REMOTE_VOLUME_CONTROL 0x0010
+#define HFP_HF_SDP_WIDE_BAND_SPEECH 0x0020
+#define HFP_HF_SDP_ENHANCED_VOICE_RECOGNITION_STATUS 0x0040
+#define HFP_HF_SDP_VOICE_RECOGNITION_TEXT 0x0080
+#define HFP_HF_SDP_SUPER_WIDE_BAND_SPEECH 0x0100
+
+#define HFP_HF_SDP_FEATURES (HFP_HF_SDP_ECNR | HFP_HF_SDP_3WAY |\
+ HFP_HF_SDP_CLIP |\
+ HFP_HF_SDP_REMOTE_VOLUME_CONTROL)
+
#define URI "tel"
#define URI_PREFIX URI ":"
@@ -57,11 +74,31 @@ struct hfp_device {
struct queue *calls;
};
+struct hfp_server {
+ struct btd_adapter *adapter;
+ GIOChannel *io;
+ uint32_t record_id;
+};
+
+static GSList *servers;
+
static void hfp_hf_debug(const char *str, void *user_data)
{
DBG_IDX(0xffff, "%s", str);
}
+static struct hfp_server *find_server(GSList *list, struct btd_adapter *a)
+{
+ for (; list; list = list->next) {
+ struct hfp_server *server = list->data;
+
+ if (server->adapter == a)
+ return server;
+ }
+
+ return NULL;
+}
+
static enum call_state hfp_call_status_to_call_state(
enum hfp_call_status status)
{
@@ -490,6 +527,219 @@ static void hfp_remove(struct btd_service *service)
g_free(dev);
}
+static sdp_record_t *hfp_record(void)
+{
+ sdp_record_t *record;
+ uuid_t root_uuid, hfphf, genericaudio, l2cap, rfcomm;
+ sdp_list_t *root, *svclass_id, *aproto, *proto[2], *apseq, *pfseq;
+ sdp_data_t *channel, *features;
+ uint8_t hf_channel = HFP_HF_DEFAULT_CHANNEL;
+ sdp_profile_desc_t profile;
+ uint16_t feat = HFP_HF_SDP_FEATURES;
+
+ record = sdp_record_alloc();
+ if (!record) {
+ error("Unable to allocate new service record");
+ return NULL;
+ }
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(NULL, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ /* Service Class ID List */
+ sdp_uuid16_create(&hfphf, HANDSFREE_SVCLASS_ID);
+ svclass_id = sdp_list_append(NULL, &hfphf);
+ sdp_uuid16_create(&genericaudio, GENERIC_AUDIO_SVCLASS_ID);
+ svclass_id = sdp_list_append(svclass_id, &genericaudio);
+ sdp_set_service_classes(record, svclass_id);
+
+ /* Protocol Descriptor List */
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto[0] = sdp_list_append(NULL, &l2cap);
+ apseq = sdp_list_append(NULL, proto[0]);
+
+ sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
+ proto[1] = sdp_list_append(NULL, &rfcomm);
+ channel = sdp_data_alloc(SDP_UINT8, &hf_channel);
+ proto[1] = sdp_list_append(proto[1], channel);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(NULL, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ /* Bluetooth Profile Descriptor List */
+ sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
+ profile.version = HFP_HF_VERSION;
+ pfseq = sdp_list_append(NULL, &profile);
+ sdp_set_profile_descs(record, pfseq);
+
+ sdp_set_info_attr(record, "Hands-Free unit", NULL, NULL);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ free(channel);
+ sdp_list_free(proto[0], NULL);
+ sdp_list_free(proto[1], NULL);
+ sdp_list_free(pfseq, NULL);
+ sdp_list_free(aproto, NULL);
+ sdp_list_free(apseq, NULL);
+ sdp_list_free(svclass_id, NULL);
+ sdp_list_free(root, NULL);
+
+ return record;
+}
+
+static void server_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+ uint8_t channel;
+ bdaddr_t src, dst;
+ char address[18];
+ GError *gerr = NULL;
+ struct btd_device *device;
+ struct btd_service *service;
+ struct hfp_device *dev;
+ const sdp_record_t *rec;
+ sdp_list_t *list;
+ sdp_profile_desc_t *desc;
+
+ if (err) {
+ error("%s", err->message);
+ return;
+ }
+
+ bt_io_get(chan, &gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_CHANNEL, &channel,
+ BT_IO_OPT_INVALID);
+ if (gerr) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ return;
+ }
+
+ ba2str(&dst, address);
+ DBG("Incoming connection from %s on Channel %d", address, channel);
+
+ device = btd_adapter_find_device(adapter_find(&src), &dst,
+ BDADDR_BREDR);
+ if (!device)
+ return;
+
+ service = btd_device_get_service(device, HFP_AG_UUID);
+ if (!service)
+ return;
+
+ dev = btd_service_get_user_data(service);
+
+ rec = btd_device_get_record(telephony_get_device(dev->telephony),
+ HFP_AG_UUID);
+ if (!rec)
+ return;
+
+ if (sdp_get_profile_descs(rec, &list) == 0) {
+ desc = list->data;
+ dev->version = desc->version;
+ }
+ sdp_list_free(list, free);
+
+ telephony_register_interface(dev->telephony);
+
+ connect_cb(chan, err, dev);
+}
+
+static GIOChannel *server_socket(struct btd_adapter *adapter)
+{
+ GIOChannel *io;
+ GError *err = NULL;
+
+ io = bt_io_listen(server_connect_cb, NULL, NULL, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR,
+ btd_adapter_get_address(adapter),
+ BT_IO_OPT_CHANNEL, HFP_HF_DEFAULT_CHANNEL,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ }
+
+ return io;
+}
+
+static int hfp_adapter_probe(struct btd_profile *p,
+ struct btd_adapter *adapter)
+{
+ struct hfp_server *server;
+ sdp_record_t *record;
+
+ DBG("path %s", adapter_get_path(adapter));
+
+ server = find_server(servers, adapter);
+ if (server != NULL)
+ goto done;
+
+ server = g_new0(struct hfp_server, 1);
+
+ server->io = server_socket(adapter);
+ if (!server->io) {
+ g_free(server);
+ return -1;
+ }
+
+done:
+ record = hfp_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ g_free(server);
+ return -1;
+ }
+
+ if (adapter_service_add(adapter, record) < 0) {
+ error("Unable to register HFP HF service record");
+ sdp_record_free(record);
+ g_free(server);
+ return -1;
+ }
+ server->record_id = record->handle;
+
+ server->adapter = btd_adapter_ref(adapter);
+
+ servers = g_slist_append(servers, server);
+
+ return 0;
+}
+
+static void hfp_adapter_remove(struct btd_profile *p,
+ struct btd_adapter *adapter)
+{
+ struct hfp_server *server;
+
+ DBG("path %s", adapter_get_path(adapter));
+
+ server = find_server(servers, adapter);
+ if (!server)
+ return;
+
+ if (server->io) {
+ g_io_channel_shutdown(server->io, TRUE, NULL);
+ g_io_channel_unref(server->io);
+ }
+
+ if (server->record_id != 0) {
+ adapter_service_remove(adapter, server->record_id);
+ server->record_id = 0;
+ }
+
+ servers = g_slist_remove(servers, server);
+
+ btd_adapter_unref(server->adapter);
+ g_free(server);
+}
+
static struct btd_profile hfp_hf_profile = {
.name = "hfp",
.priority = BTD_PROFILE_PRIORITY_MEDIUM,
@@ -502,6 +752,9 @@ static struct btd_profile hfp_hf_profile = {
.connect = hfp_connect,
.disconnect = hfp_disconnect,
+ .adapter_probe = hfp_adapter_probe,
+ .adapter_remove = hfp_adapter_remove,
+
.experimental = true,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 02/12] audio/hfp-hf: Add MediaEndpoint for HFP codecs
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-01 21:10 ` Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 03/12] client/player: Add MediaEndpoints for HFP HF codecs Frédéric Danis
` (10 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
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
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 03/12] client/player: Add MediaEndpoints for HFP HF codecs
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-01 21:10 ` [PATCH BlueZ v2 02/12] audio/hfp-hf: Add MediaEndpoint for HFP codecs Frédéric Danis
@ 2026-10-01 21:10 ` 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
` (9 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
Assisted-by: Claude:claude-sonnet-5
---
client/player.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/client/player.c b/client/player.c
index ae2ce66f0..88221076c 100644
--- a/client/player.c
+++ b/client/player.c
@@ -1363,6 +1363,18 @@ static const struct capabilities {
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26,
240),
UTIL_IOV_INIT()),
+
+ /* SCO CVSD:
+ */
+ CODEC_CAPABILITIES("hfp_ag/cvsd", HFP_AG_UUID, 1,
+ UTIL_IOV_INIT(),
+ UTIL_IOV_INIT()),
+
+ /* SCO mSBC:
+ */
+ CODEC_CAPABILITIES("hfp_ag/msbc", HFP_AG_UUID, 2,
+ UTIL_IOV_INIT(),
+ UTIL_IOV_INIT()),
};
struct codec_preset {
@@ -3069,8 +3081,10 @@ static void endpoint_free(void *data)
if (ep->msg)
dbus_message_unref(ep->msg);
- queue_destroy(ep->preset->custom, free);
- ep->preset->custom = NULL;
+ if (ep->preset) {
+ queue_destroy(ep->preset->custom, free);
+ ep->preset->custom = NULL;
+ }
if (ep->codec == 0xff)
free(ep->preset);
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 04/12] shared/hfp: Add hangup all calls support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (2 preceding siblings ...)
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 ` Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 05/12] audio/hfp-hf: " Frédéric Danis
` (8 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
This releases all calls except waiting calls. This includes
multiparty calls.
---
src/shared/hfp.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 3 +++
2 files changed, 50 insertions(+)
diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index 59e09f80e..f9fa53ae4 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -2970,6 +2970,53 @@ bool hfp_hf_swap_calls(struct hfp_hf *hfp,
return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+CHLD=2");
}
+bool hfp_hf_hangup_all(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data)
+{
+ bool found_active = false;
+ bool found_held = false;
+ const struct queue_entry *entry;
+
+ if (!hfp)
+ return false;
+
+ DBG(hfp, "");
+
+ for (entry = queue_get_entries(hfp->calls); entry;
+ entry = entry->next) {
+ struct hf_call *call = entry->data;
+
+ if (call_setup_match(call, NULL) ||
+ call_active_match(call, NULL)) {
+ found_active = true;
+ } else if (call_held_match(call, NULL)) {
+ found_held = true;
+ }
+ }
+
+ if (!found_active && !found_held)
+ return false;
+
+ if (found_held && (hfp->chlds & HFP_CHLD_0)) {
+ if (!hfp_hf_send_command(hfp, resp_cb, user_data,
+ "AT+CHLD=0")) {
+ DBG(hfp, "Failed to hangup held calls");
+ return false;
+ }
+ }
+
+ if (found_active) {
+ if (!hfp_hf_send_command(hfp, resp_cb, user_data,
+ "AT+CHUP")) {
+ DBG(hfp, "Failed to hangup active calls");
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id,
hfp_response_func_t resp_cb,
void *user_data)
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 201777605..77ada40b6 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -258,6 +258,9 @@ bool hfp_hf_release_and_accept(struct hfp_hf *hfp,
bool hfp_hf_swap_calls(struct hfp_hf *hfp,
hfp_response_func_t resp_cb,
void *user_data);
+bool hfp_hf_hangup_all(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data);
bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id,
hfp_response_func_t resp_cb,
void *user_data);
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 05/12] audio/hfp-hf: Add hangup all calls support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (3 preceding siblings ...)
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 ` Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 06/12] shared/hfp: Add send tones support Frédéric Danis
` (7 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
This releases all calls except waiting calls. This includes
multiparty calls.
---
profiles/audio/hfp-hf.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index dddd56261..287b79f1f 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -503,6 +503,20 @@ static DBusMessage *dial(DBusConnection *conn, DBusMessage *msg,
return NULL;
}
+static DBusMessage *hangup_all(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data)
+{
+ struct hfp_device *dev = profile_data;
+ bool ret;
+
+ ret = hfp_hf_hangup_all(dev->hf, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg, "Hang up all command failed");
+
+ return NULL;
+}
+
static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg,
void *call_data)
{
@@ -535,6 +549,7 @@ static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg,
struct telephony_callbacks hfp_callbacks = {
.dial = dial,
+ .hangup_all = hangup_all,
.call_answer = call_answer,
.call_hangup = call_hangup,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 06/12] shared/hfp: Add send tones support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (4 preceding siblings ...)
2026-10-01 21:10 ` [PATCH BlueZ v2 05/12] audio/hfp-hf: " Frédéric Danis
@ 2026-10-01 21:10 ` 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
` (6 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
---
src/shared/hfp.c | 18 ++++++++++++++++++
src/shared/hfp.h | 3 +++
2 files changed, 21 insertions(+)
diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index f9fa53ae4..dffad6fee 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -3017,6 +3017,24 @@ bool hfp_hf_hangup_all(struct hfp_hf *hfp,
return true;
}
+bool hfp_hf_send_tones(struct hfp_hf *hfp, const char *tones,
+ hfp_response_func_t resp_cb,
+ void *user_data)
+{
+ if (!hfp)
+ return false;
+
+ DBG(hfp, "");
+
+ if (!queue_find(hfp->calls, call_active_match, NULL)) {
+ DBG(hfp, "hf: No active call to send tones");
+ return false;
+ }
+
+ return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+VTS=%s",
+ tones);
+}
+
bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id,
hfp_response_func_t resp_cb,
void *user_data)
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 77ada40b6..11610018e 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -261,6 +261,9 @@ bool hfp_hf_swap_calls(struct hfp_hf *hfp,
bool hfp_hf_hangup_all(struct hfp_hf *hfp,
hfp_response_func_t resp_cb,
void *user_data);
+bool hfp_hf_send_tones(struct hfp_hf *hfp, const char *tones,
+ hfp_response_func_t resp_cb,
+ void *user_data);
bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id,
hfp_response_func_t resp_cb,
void *user_data);
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 07/12] audio/hfp-hf: Add Send Tones support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (5 preceding siblings ...)
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 ` 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
` (5 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
---
profiles/audio/hfp-hf.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 287b79f1f..1843b6758 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -517,6 +517,26 @@ static DBusMessage *hangup_all(DBusConnection *conn, DBusMessage *msg,
return NULL;
}
+static DBusMessage *send_tones(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data)
+{
+ struct hfp_device *dev = profile_data;
+ const char *tones;
+ bool ret;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &tones,
+ DBUS_TYPE_INVALID)) {
+ return btd_error_invalid_args(msg);
+ }
+
+ ret = hfp_hf_send_tones(dev->hf, tones, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg, "Send tones command failed");
+
+ return NULL;
+}
+
static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg,
void *call_data)
{
@@ -550,6 +570,7 @@ static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg,
struct telephony_callbacks hfp_callbacks = {
.dial = dial,
.hangup_all = hangup_all,
+ .send_tones = send_tones,
.call_answer = call_answer,
.call_hangup = call_hangup,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 08/12] client/telephony: Add Send Tones menu entry
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (6 preceding siblings ...)
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 ` 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
` (4 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
---
client/telephony.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/client/telephony.c b/client/telephony.c
index cc92fe33a..ab2a93430 100644
--- a/client/telephony.c
+++ b/client/telephony.c
@@ -277,6 +277,51 @@ static void cmd_hangupall(int argc, char *argv[])
bt_shell_printf("Attempting to hangup all calls\n");
}
+static void send_tones_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ bt_shell_printf("Failed to send tones: %s\n", error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Send tones successful\n");
+
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_send_tones(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ if (argc < 3) {
+ if (check_default_ag() == FALSE)
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+ proxy = default_ag;
+ } else {
+ proxy = g_dbus_proxy_lookup(ags, NULL, argv[2],
+ BLUEZ_TELEPHONY_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Audio gateway %s not available\n",
+ argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+ }
+
+ if (g_dbus_proxy_method_call(proxy, "SendTones", dial_setup,
+ send_tones_reply, argv[1], NULL) == FALSE) {
+ bt_shell_printf("Failed to send tones\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Attempting to send tones\n");
+}
+
static void cmd_list_calls(int argc, char *arg[])
{
g_list_foreach(calls, print_call, NULL);
@@ -507,6 +552,8 @@ static const struct bt_shell_menu telephony_menu = {
ag_generator},
{ "hangup-all", "[telephony]", cmd_hangupall, "Hangup all calls",
ag_generator},
+ { "send-tones", "<tones> [telephony]", cmd_send_tones, "Send tones",
+ ag_generator},
{ "list-calls", NULL, cmd_list_calls, "List calls" },
{ "show-call", "<call>", cmd_show_call, "Show call information",
call_generator},
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 09/12] audio/hfp-hf: Add multi calls support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (7 preceding siblings ...)
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 ` Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 10/12] client/telephony: " Frédéric Danis
` (3 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
This implements the SwapCalls, ReleaseAndAnswer, ReleaseAndSwap and
HoldAndAnswer methods from org.bluez.Telephony1.
---
profiles/audio/hfp-hf.c | 142 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 142 insertions(+)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 1843b6758..25dce517f 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -503,6 +503,144 @@ static DBusMessage *dial(DBusConnection *conn, DBusMessage *msg,
return NULL;
}
+static DBusMessage *swap_calls(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data)
+{
+ struct hfp_device *dev = profile_data;
+ const struct queue_entry *entry;
+ bool found_held = false;
+ bool ret;
+
+ for (entry = queue_get_entries(dev->calls); entry;
+ entry = entry->next) {
+ struct call *call = entry->data;
+
+ if (call->state == CALL_STATE_HELD) {
+ found_held = true;
+ break;
+ }
+ }
+
+ if (!found_held) {
+ return btd_error_failed(msg,
+ "Swap calls command failed: "
+ "no held calls");
+ }
+
+ ret = hfp_hf_swap_calls(dev->hf, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg, "Swap calls command failed");
+
+ return NULL;
+}
+
+static DBusMessage *release_and_answer(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data)
+{
+ struct hfp_device *dev = profile_data;
+ const struct queue_entry *entry;
+ bool found_active = false;
+ bool found_waiting = false;
+ bool ret;
+
+ for (entry = queue_get_entries(dev->calls); entry;
+ entry = entry->next) {
+ struct call *call = entry->data;
+
+ if (call->state == CALL_STATE_ACTIVE)
+ found_active = true;
+ else if (call->state == CALL_STATE_WAITING)
+ found_waiting = true;
+ }
+
+ if (!found_active || !found_waiting) {
+ return btd_error_failed(msg,
+ "Release and answer command failed: "
+ "no active and waiting calls");
+ }
+
+ ret = hfp_hf_release_and_accept(dev->hf, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg,
+ "Release and answer command failed");
+
+ return NULL;
+}
+
+static DBusMessage *release_and_swap(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data)
+{
+ struct hfp_device *dev = profile_data;
+ const struct queue_entry *entry;
+ bool found_active = false;
+ bool found_held = false;
+ bool ret;
+
+ for (entry = queue_get_entries(dev->calls); entry;
+ entry = entry->next) {
+ struct call *call = entry->data;
+
+ if (call->state == CALL_STATE_WAITING) {
+ return btd_error_failed(msg,
+ "Release and swap command failed: "
+ "waiting call exists");
+ } else if (call->state == CALL_STATE_ACTIVE)
+ found_active = true;
+ else if (call->state == CALL_STATE_HELD)
+ found_held = true;
+ }
+
+ if (!found_active || !found_held) {
+ return btd_error_failed(msg,
+ "Release and swap command failed: "
+ "no active and held calls");
+ }
+
+ ret = hfp_hf_release_and_accept(dev->hf, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg,
+ "Release and swap command failed");
+
+ return NULL;
+}
+
+static DBusMessage *hold_and_answer(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data)
+{
+ struct hfp_device *dev = profile_data;
+ const struct queue_entry *entry;
+ bool found_active = false;
+ bool found_waiting = false;
+ bool ret;
+
+ for (entry = queue_get_entries(dev->calls); entry;
+ entry = entry->next) {
+ struct call *call = entry->data;
+
+ if (call->state == CALL_STATE_ACTIVE)
+ found_active = true;
+ else if (call->state == CALL_STATE_WAITING)
+ found_waiting = true;
+ }
+
+ if (!found_active || !found_waiting) {
+ return btd_error_failed(msg,
+ "Hold and answer command failed: "
+ "no active and waiting calls");
+ }
+
+ ret = hfp_hf_swap_calls(dev->hf, cmd_complete,
+ dbus_message_ref(msg));
+ if (!ret)
+ return btd_error_failed(msg,
+ "Hold and answer command failed");
+
+ return NULL;
+}
+
static DBusMessage *hangup_all(DBusConnection *conn, DBusMessage *msg,
void *profile_data)
{
@@ -569,6 +707,10 @@ static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg,
struct telephony_callbacks hfp_callbacks = {
.dial = dial,
+ .swap_calls = swap_calls,
+ .release_and_answer = release_and_answer,
+ .release_and_swap = release_and_swap,
+ .hold_and_answer = hold_and_answer,
.hangup_all = hangup_all,
.send_tones = send_tones,
.call_answer = call_answer,
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 10/12] client/telephony: Add multi calls support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (8 preceding siblings ...)
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 ` Frédéric Danis
2026-10-01 21:10 ` [PATCH BlueZ v2 11/12] shared/hfp: Add codecs support Frédéric Danis
` (2 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
This adds menu entries for the SwapCalls, ReleaseAndAnswer,
ReleaseAndSwap and HoldAndAnswer methods from org.bluez.Telephony1.
---
client/telephony.c | 190 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 190 insertions(+)
diff --git a/client/telephony.c b/client/telephony.c
index ab2a93430..858c02c0b 100644
--- a/client/telephony.c
+++ b/client/telephony.c
@@ -232,6 +232,187 @@ static void cmd_dial(int argc, char *argv[])
bt_shell_printf("Attempting to dial\n");
}
+static void swap_calls_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ bt_shell_printf("Failed to swap calls: %s\n", error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Swap calls successful\n");
+
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_swap_calls(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ if (argc < 2) {
+ if (check_default_ag() == FALSE)
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+ proxy = default_ag;
+ } else {
+ proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+ BLUEZ_TELEPHONY_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Audio gateway %s not available\n",
+ argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+ }
+
+ if (g_dbus_proxy_method_call(proxy, "SwapCalls", NULL,
+ swap_calls_reply, NULL, NULL) == FALSE) {
+ bt_shell_printf("Failed to swap calls\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Attempting to swap calls\n");
+}
+
+static void release_answer_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ bt_shell_printf("Failed to release and answer: %s\n",
+ error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Release and answer successful\n");
+
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_release_answer(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ if (argc < 2) {
+ if (check_default_ag() == FALSE)
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+ proxy = default_ag;
+ } else {
+ proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+ BLUEZ_TELEPHONY_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Audio gateway %s not available\n",
+ argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+ }
+
+ if (g_dbus_proxy_method_call(proxy, "ReleaseAndAnswer", NULL,
+ release_answer_reply, NULL, NULL) == FALSE) {
+ bt_shell_printf("Failed to release and answer\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Attempting to release and answer\n");
+}
+
+static void release_swap_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ bt_shell_printf("Failed to release and swap: %s\n", error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Release and swap successful\n");
+
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_release_swap(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ if (argc < 2) {
+ if (check_default_ag() == FALSE)
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+ proxy = default_ag;
+ } else {
+ proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+ BLUEZ_TELEPHONY_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Audio gateway %s not available\n",
+ argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+ }
+
+ if (g_dbus_proxy_method_call(proxy, "ReleaseAndSwap", NULL,
+ release_swap_reply, NULL, NULL) == FALSE) {
+ bt_shell_printf("Failed to release and swap\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Attempting to release and swap\n");
+}
+
+static void hold_answer_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ bt_shell_printf("Failed to hold and answer: %s\n", error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Hold and answer successful\n");
+
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_hold_answer(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ if (argc < 2) {
+ if (check_default_ag() == FALSE)
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+ proxy = default_ag;
+ } else {
+ proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
+ BLUEZ_TELEPHONY_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Audio gateway %s not available\n",
+ argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+ }
+
+ if (g_dbus_proxy_method_call(proxy, "HoldAndAnswer", NULL,
+ hold_answer_reply, NULL, NULL) == FALSE) {
+ bt_shell_printf("Failed to hold and answer\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Attempting to hold and answer\n");
+}
+
static void hangupall_reply(DBusMessage *message, void *user_data)
{
DBusError error;
@@ -550,6 +731,15 @@ static const struct bt_shell_menu telephony_menu = {
ag_generator},
{ "dial", "<number> [telephony]", cmd_dial, "Dial number",
ag_generator},
+ { "swap-calls", "[telephony]", cmd_swap_calls, "Swap calls",
+ ag_generator},
+ { "release-answer", "[telephony]", cmd_release_answer,
+ "Release and answer",
+ ag_generator},
+ { "release-swap", "[telephony]", cmd_release_swap, "Release and swap",
+ ag_generator},
+ { "hold-answer", "[telephony]", cmd_hold_answer, "Hold and answer",
+ ag_generator},
{ "hangup-all", "[telephony]", cmd_hangupall, "Hangup all calls",
ag_generator},
{ "send-tones", "<tones> [telephony]", cmd_send_tones, "Send tones",
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 11/12] shared/hfp: Add codecs support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (9 preceding siblings ...)
2026-10-01 21:10 ` [PATCH BlueZ v2 10/12] client/telephony: " Frédéric Danis
@ 2026-10-01 21:10 ` 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
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
This send the AT+BAC command with the supported codec list during
SLC connection.
On reception of +BCS event to select a codec to use it replies with
the same codec if it is available, or re-send the AT+BAC to re-trigger
codec selection.
---
src/shared/hfp.c | 164 +++++++++++++++++++++++++++++++++++++++++++++--
src/shared/hfp.h | 7 ++
2 files changed, 166 insertions(+), 5 deletions(-)
diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index dffad6fee..cf12f0637 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -100,6 +100,7 @@ struct hfp_hf {
struct hfp_hf_callbacks *callbacks;
void *callbacks_data;
+ uint32_t hf_features;
uint32_t features;
struct indicator ag_ind[HFP_INDICATOR_LAST];
bool service;
@@ -2235,6 +2236,101 @@ static void clip_cb(struct hfp_context *context, void *user_data)
hfp->callbacks_data);
}
+static bool available_codecs_update(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data)
+{
+ uint8_t codecs[UINT8_MAX];
+ uint8_t len;
+ uint32_t codecs_str_len;
+ char *codecs_str, *ptr;
+
+ len = hfp->callbacks->get_codecs(codecs, UINT8_MAX,
+ hfp->callbacks_data);
+ if (!len) {
+ DBG(hfp, "hf: Failed to get supported codecs");
+ return false;
+ }
+
+ /* Each codec can be up to 3 digits + comma + null terminator */
+ codecs_str_len = len * 4 + 1;
+ codecs_str = malloc(codecs_str_len);
+ if (!codecs_str) {
+ DBG(hfp, "hf: Failed to allocate memory for codecs");
+ return false;
+ }
+
+ ptr = codecs_str;
+ for (uint8_t i = 0; i < len; i++) {
+ int ret;
+
+ ret = snprintf(ptr, codecs_str_len - (ptr - codecs_str),
+ "%u,", codecs[i]);
+ if (ret < 0 || ret >= codecs_str_len - (ptr - codecs_str)) {
+ DBG(hfp, "hf: Failed to format codecs string");
+ free(codecs_str);
+ return false;
+ }
+ ptr += ret;
+ }
+ /* Remove the trailing comma */
+ if (ptr != codecs_str) {
+ ptr--;
+ *ptr = '\0';
+ }
+
+ if (!hfp_hf_send_command(hfp, resp_cb, user_data, "AT+BAC=%s",
+ codecs_str)) {
+ DBG(hfp, "hf: Could not send AT+BAC=%s", codecs_str);
+ free(codecs_str);
+ return false;
+ }
+
+ free(codecs_str);
+
+ return true;
+}
+
+static void bac_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ if (result != HFP_RESULT_OK)
+ DBG(hfp, "hf: BAC error: %d", result);
+}
+
+static void bcs_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ if (result != HFP_RESULT_OK)
+ DBG(hfp, "hf: BCS error: %d", result);
+}
+
+static void bcs_cb(struct hfp_context *context, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+ unsigned int val;
+
+ if (!hfp_context_get_number(context, &val))
+ return;
+
+ if (!hfp->callbacks->select_codec(val, hfp->callbacks_data)) {
+ DBG(hfp, "hf: Codec selection failed: %d", val);
+
+ if (!available_codecs_update(hfp, bac_resp, hfp)) {
+ DBG(hfp, "hf: Could not re-trigger codec update");
+ return;
+ }
+ }
+
+ if (!hfp_hf_send_command(hfp, bcs_resp, hfp, "AT+BCS=%u", val)) {
+ DBG(hfp, "hf: Could not send AT+BCS=%u", val);
+ }
+}
+
static void nrec_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
@@ -2794,17 +2890,15 @@ static void slc_brsf_cb(struct hfp_context *context, void *user_data)
hfp->features = feat;
}
-static void slc_brsf_resp(enum hfp_result result, enum hfp_error cme_err,
+static void slc_bac_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
struct hfp_hf *hfp = user_data;
DBG(hfp, "");
- hfp_hf_unregister(hfp, "+BRSF");
-
if (result != HFP_RESULT_OK) {
- DBG(hfp, "BRSF error: %d", result);
+ DBG(hfp, "hf: BAC error: %d", result);
goto failed;
}
@@ -2829,6 +2923,46 @@ failed:
hfp->callbacks_data);
}
+static void slc_brsf_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ DBG(hfp, "");
+
+ hfp_hf_unregister(hfp, "+BRSF");
+
+ if (result != HFP_RESULT_OK) {
+ DBG(hfp, "BRSF error: %d", result);
+ goto failed;
+ }
+
+ /* Continue with SLC creation */
+ if (!(hfp->hf_features & HFP_HF_FEAT_CODEC_NEGOTIATION) ||
+ !(hfp->features & HFP_AG_FEAT_CODEC_NEGOTIATION)) {
+ /* Jump to next setup state */
+ slc_bac_resp(HFP_RESULT_OK, cme_err, user_data);
+ return;
+ }
+
+ if (!hfp_hf_register(hfp, bcs_cb, "+BCS", hfp, NULL)) {
+ DBG(hfp, "hf: Could not register for +BCS");
+ result = HFP_RESULT_ERROR;
+ goto failed;
+ }
+
+ if (!available_codecs_update(hfp, slc_bac_resp, hfp)) {
+ DBG(hfp, "hf: Could not send AT+BAC");
+ result = HFP_RESULT_ERROR;
+ goto failed;
+ }
+
+failed:
+ if (hfp->callbacks->session_ready)
+ hfp->callbacks->session_ready(result, cme_err,
+ hfp->callbacks_data);
+}
+
bool hfp_hf_session_register(struct hfp_hf *hfp,
struct hfp_hf_callbacks *callbacks,
void *callbacks_data)
@@ -2849,11 +2983,15 @@ bool hfp_hf_session(struct hfp_hf *hfp)
DBG(hfp, "");
+ hfp->hf_features = HFP_HF_FEATURES;
+ if (hfp->callbacks->get_codecs && hfp->callbacks->select_codec)
+ hfp->hf_features |= HFP_HF_FEAT_CODEC_NEGOTIATION;
+
if (!hfp_hf_register(hfp, slc_brsf_cb, "+BRSF", hfp, NULL))
return false;
return hfp_hf_send_command(hfp, slc_brsf_resp, hfp,
- "AT+BRSF=%u", HFP_HF_FEATURES);
+ "AT+BRSF=%u", hfp->hf_features);
}
const char *hfp_hf_call_get_number(struct hfp_hf *hfp, uint id)
@@ -3090,3 +3228,19 @@ bool hfp_hf_call_hangup(struct hfp_hf *hfp, uint id,
return false;
}
+
+bool hfp_hf_request_codec_connection(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data)
+{
+ if (!hfp)
+ return false;
+
+ if (!(hfp->hf_features & HFP_HF_FEAT_CODEC_NEGOTIATION) ||
+ !(hfp->features & HFP_AG_FEAT_CODEC_NEGOTIATION)) {
+ DBG(hfp, "hf: Codec negotiation not supported");
+ return false;
+ }
+
+ return hfp_hf_send_command(hfp, resp_cb, user_data, "AT+BCC");
+}
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 11610018e..a3b956cf5 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -220,6 +220,9 @@ struct hfp_hf_callbacks {
void (*call_line_id_updated)(uint id, const char *number, uint type,
void *user_data);
void (*call_mpty_updated)(uint id, bool mpty, void *user_data);
+ uint8_t (*get_codecs)(uint8_t *codecs, uint8_t max_codecs,
+ void *user_data);
+ bool (*select_codec)(uint8_t codec, void *user_data);
};
struct hfp_hf *hfp_hf_new(int fd);
@@ -270,3 +273,7 @@ bool hfp_hf_call_answer(struct hfp_hf *hfp, uint id,
bool hfp_hf_call_hangup(struct hfp_hf *hfp, uint id,
hfp_response_func_t resp_cb,
void *user_data);
+
+bool hfp_hf_request_codec_connection(struct hfp_hf *hfp,
+ hfp_response_func_t resp_cb,
+ void *user_data);
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH BlueZ v2 12/12] audio/hfp-hf: Add codecs support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (10 preceding siblings ...)
2026-10-01 21:10 ` [PATCH BlueZ v2 11/12] shared/hfp: Add codecs support Frédéric Danis
@ 2026-10-01 21:10 ` 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
12 siblings, 0 replies; 16+ messages in thread
From: Frédéric Danis @ 2026-10-01 21:10 UTC (permalink / raw)
To: linux-bluetooth
From: Frédéric Danis <frederic.danis.oss@gmail.com>
---
profiles/audio/hfp-hf.c | 171 +++++++++++++++++++++++++++++++++++-----
1 file changed, 152 insertions(+), 19 deletions(-)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 25dce517f..4e626653d 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -65,11 +65,19 @@
#define HFP_HF_SDP_FEATURES (HFP_HF_SDP_ECNR | HFP_HF_SDP_3WAY |\
HFP_HF_SDP_CLIP |\
- HFP_HF_SDP_REMOTE_VOLUME_CONTROL)
+ HFP_HF_SDP_REMOTE_VOLUME_CONTROL |\
+ HFP_HF_SDP_WIDE_BAND_SPEECH |\
+ HFP_HF_SDP_SUPER_WIDE_BAND_SPEECH)
#define URI "tel"
#define URI_PREFIX URI ":"
+struct connect_data {
+ struct hfp_device *dev;
+ void (*cb)(int status, void *data);
+ void *cb_user_data;
+};
+
struct hfp_device {
struct telephony *telephony;
uint16_t version;
@@ -81,6 +89,7 @@ struct hfp_device {
struct queue *calls;
uint8_t codec;
unsigned int resume_id;
+ struct connect_data *pending_connect;
hfp_hf_sco_closed_cb sco_closed_cb;
void *sco_closed_data;
};
@@ -169,6 +178,20 @@ static bool call_id_cmp(const void *data, const void *match_data)
return call->idx == id;
}
+static void sco_connect_complete(struct connect_data *connect_data, int status)
+{
+ if (!connect_data)
+ return;
+
+ if (connect_data->dev->pending_connect == connect_data)
+ connect_data->dev->pending_connect = NULL;
+
+ if (connect_data->cb)
+ connect_data->cb(status, connect_data->cb_user_data);
+
+ g_free(connect_data);
+}
+
static void device_destroy(struct hfp_device *dev)
{
struct hfp_server *server;
@@ -177,6 +200,9 @@ static void device_destroy(struct hfp_device *dev)
telephony_set_state(dev->telephony, DISCONNECTING);
+ if (dev->pending_connect)
+ sco_connect_complete(dev->pending_connect, -ENOTCONN);
+
if (dev->hf) {
hfp_hf_unref(dev->hf);
dev->hf = NULL;
@@ -329,6 +355,49 @@ static void hfp_hf_call_line_id_updated(uint id, const char *number,
telephony_call_set_line_id(call, number);
}
+static uint8_t hfp_hf_get_codecs(uint8_t *codecs, uint8_t max_codecs,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct btd_adapter *adapter;
+ struct hfp_server *server;
+ GSList *l;
+ uint8_t i = 0;
+
+ adapter = device_get_adapter(telephony_get_device(dev->telephony));
+ server = find_server(servers, adapter);
+
+ for (l = server->endpoints; l; l = l->next) {
+ codecs[i++] = media_endpoint_get_codec(l->data);
+ if (i >= max_codecs)
+ break;
+ }
+
+ return i;
+}
+
+static bool hfp_hf_select_codec(uint8_t codec, void *user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct btd_adapter *adapter;
+ struct hfp_server *server;
+ GSList *l;
+
+ DBG("Selecting codec: %u", codec);
+ adapter = device_get_adapter(telephony_get_device(dev->telephony));
+ server = find_server(servers, adapter);
+
+ for (l = server->endpoints; l; l = l->next) {
+ if (media_endpoint_get_codec(l->data) == codec) {
+ dev->codec = codec;
+ return true;
+ }
+ }
+
+ DBG("Unsupported codec: %u", codec);
+ return false;
+}
+
static struct hfp_hf_callbacks hf_session_callbacks = {
.session_ready = hfp_hf_session_ready_cb,
.update_indicator = hfp_hf_update_indicator,
@@ -338,6 +407,8 @@ static struct hfp_hf_callbacks hf_session_callbacks = {
.call_removed = hfp_hf_call_removed,
.call_status_updated = hfp_hf_call_status_updated,
.call_line_id_updated = hfp_hf_call_line_id_updated,
+ .get_codecs = hfp_hf_get_codecs,
+ .select_codec = hfp_hf_select_codec,
};
static void hfp_disconnect_watch(void *user_data)
@@ -991,23 +1062,6 @@ static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond, void *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;
@@ -1109,6 +1163,62 @@ static void sco_connect_cb(GIOChannel *io, GError *err, gpointer user_data)
sco_connect_complete(connect_data, 0);
}
+static void confirm_cb(GIOChannel *io, gpointer user_data)
+{
+ bdaddr_t src, dst;
+ char address[18];
+ struct btd_device *device;
+ struct btd_service *service;
+ struct hfp_device *dev = NULL;
+ int voice;
+ GError *err = NULL;
+
+ if (!bt_io_get(io, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_DEST, address,
+ BT_IO_OPT_INVALID)) {
+ error("Unable to get destination address: %s", err->message);
+ g_clear_error(&err);
+ goto drop;
+ }
+
+ DBG("Incoming SCO connection from %s", address);
+
+ device = btd_adapter_find_device(adapter_find(&src), &dst,
+ BDADDR_BREDR);
+ if (!device)
+ goto drop;
+
+ service = btd_device_get_service(device, HFP_AG_UUID);
+ if (!service)
+ goto drop;
+
+ dev = btd_service_get_user_data(service);
+ voice = dev->codec == 1 ? BT_VOICE_CVSD_16BIT : BT_VOICE_TRANSPARENT;
+
+ if (!bt_io_set(io, &err, BT_IO_OPT_VOICE, voice, BT_IO_OPT_INVALID)) {
+ error("Could not set voice settings on SCO IO: %s",
+ err->message);
+ g_error_free(err);
+ goto drop;
+ }
+
+ if (!bt_io_accept(io, sco_connect_cb, dev->pending_connect, NULL,
+ &err)) {
+ error("bt_io_accept() failed: %s", err->message);
+ g_error_free(err);
+ goto drop;
+ }
+
+ return;
+
+drop:
+ if (dev && dev->pending_connect)
+ sco_connect_complete(dev->pending_connect, -EIO);
+ g_io_channel_shutdown(io, TRUE, NULL);
+}
+
bool hfp_hf_sco_listen(struct btd_adapter *adapter, void *endpoint)
{
struct hfp_server *server;
@@ -1126,7 +1236,7 @@ bool hfp_hf_sco_listen(struct btd_adapter *adapter, void *endpoint)
if (server->sco_io)
return true;
- server->sco_io = bt_io_listen(sco_connect_cb, NULL, NULL, NULL,
+ server->sco_io = bt_io_listen(NULL, confirm_cb, NULL, NULL,
&err,
BT_IO_OPT_SOURCE_BDADDR,
btd_adapter_get_address(server->adapter),
@@ -1190,6 +1300,16 @@ uint16_t hfp_hf_device_get_omtu(struct hfp_device *dev)
return dev->omtu;
}
+static void req_codec_connection_complete(enum hfp_result res,
+ enum hfp_error cme_err,
+ void *user_data)
+{
+ struct connect_data *connect_data = user_data;
+
+ if (res != HFP_RESULT_OK)
+ sco_connect_complete(connect_data, -EIO);
+}
+
static gboolean sco_start_cb(gpointer data)
{
struct connect_data *connect_data = data;
@@ -1211,6 +1331,11 @@ unsigned int hfp_hf_sco_start(struct hfp_device *dev, void *cb, void *user_data)
DBG("codec %u", dev->codec);
+ if (dev->pending_connect) {
+ error("SCO connect already in progress");
+ return 0;
+ }
+
connect_data = g_new0(struct connect_data, 1);
connect_data->dev = dev;
connect_data->cb = cb;
@@ -1219,6 +1344,13 @@ unsigned int hfp_hf_sco_start(struct hfp_device *dev, void *cb, void *user_data)
src = telephony_get_src(dev->telephony);
dst = telephony_get_dst(dev->telephony);
if (!dev->sco_io) {
+ if (hfp_hf_request_codec_connection(dev->hf,
+ req_codec_connection_complete,
+ connect_data)) {
+ dev->pending_connect = connect_data;
+ goto done;
+ }
+
io = bt_io_connect(sco_connect_cb, connect_data, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &src,
BT_IO_OPT_DEST_BDADDR, &dst,
@@ -1234,6 +1366,7 @@ unsigned int hfp_hf_sco_start(struct hfp_device *dev, void *cb, void *user_data)
g_idle_add(sco_start_cb, connect_data);
}
+done:
return (++dev->resume_id);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* RE: hfp-hf: Enhance HFP Hands-Free profile support
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 ` bluez.test.bot
0 siblings, 0 replies; 16+ messages in thread
From: bluez.test.bot @ 2026-10-02 0:53 UTC (permalink / raw)
To: linux-bluetooth, frederic.danis
[-- Attachment #1: Type: text/plain, Size: 1019 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/series/1177946/
---Test result---
Test Summary:
CheckPatch PASS 6.04 seconds
GitLint PASS 5.44 seconds
BuildEll PASS 13.26 seconds
BluezMake PASS 226.43 seconds
MakeCheck PASS 12.83 seconds
MakeDistcheck PASS 98.56 seconds
CheckValgrind PASS 156.38 seconds
CheckSmatch PASS 162.70 seconds
bluezmakeextell PASS 59.86 seconds
TestFunctional PASS 795.43 seconds
IncrementalBuild PASS 297.94 seconds
ScanBuild PASS 622.94 seconds
https://github.com/bluez/bluez/pull/2620
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support
2026-10-01 21:10 [PATCH BlueZ v2 00/12] hfp-hf: Enhance HFP Hands-Free profile support Frédéric Danis
` (11 preceding siblings ...)
2026-10-01 21:10 ` [PATCH BlueZ v2 12/12] audio/hfp-hf: " Frédéric Danis
@ 2026-10-05 20:20 ` patchwork-bot+bluetooth
12 siblings, 0 replies; 16+ messages in thread
From: patchwork-bot+bluetooth @ 2026-10-05 20:20 UTC (permalink / raw)
To: =?utf-8?b?RnLDqWTDqXJpYyBEYW5pcyA8ZnJlZGVyaWMuZGFuaXNAY29sbGFib3JhLmNvbT4=?=
Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Thu, 1 Oct 2026 23:10:24 +0200 you wrote:
> This series enhanced the HFP Hands-Free (HF) role support in BlueZ:
>
> - Registration:
> - Register the HFP HF profile with an SDP record so BlueZ is
> discoverable/connectable as a Hands-Free device.
>
> - Audio path:
> - Register a MediaEndpoint1 per supported HFP codec and wire up
> MediaTransport creation/configuration so SCO audio is exposed
> through the standard Media API instead of a bespoke interface.
> - Extend bluetoothctl (client/player) with matching menu entries
> to select/manage the HFP HF MediaEndpoints.
>
> [...]
Here is the summary with links:
- [BlueZ,v2,01/12] audio/hfp-hf: Add HFP HF server and SDP record
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7878ae60a2e2
- [BlueZ,v2,02/12] audio/hfp-hf: Add MediaEndpoint for HFP codecs
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=89b91a4806fb
- [BlueZ,v2,03/12] client/player: Add MediaEndpoints for HFP HF codecs
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=9e2a7ddbb178
- [BlueZ,v2,04/12] shared/hfp: Add hangup all calls support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=f7fd0acef7ee
- [BlueZ,v2,05/12] audio/hfp-hf: Add hangup all calls support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=0a8dfeff2f79
- [BlueZ,v2,06/12] shared/hfp: Add send tones support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=67883db59611
- [BlueZ,v2,07/12] audio/hfp-hf: Add Send Tones support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=e9046f3fb78d
- [BlueZ,v2,08/12] client/telephony: Add Send Tones menu entry
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=a9b8a5ef6ec7
- [BlueZ,v2,09/12] audio/hfp-hf: Add multi calls support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=2a4adbb2a717
- [BlueZ,v2,10/12] client/telephony: Add multi calls support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=45384edcd1c7
- [BlueZ,v2,11/12] shared/hfp: Add codecs support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=003db6420008
- [BlueZ,v2,12/12] audio/hfp-hf: Add codecs support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7edaa6140339
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] 16+ messages in thread
end of thread, other threads:[~2026-10-05 20:20 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH BlueZ v2 02/12] audio/hfp-hf: Add MediaEndpoint for HFP codecs Frédéric Danis
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
-- strict thread matches above, loose matches on Subject: below --
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox