* [PATCH BlueZ 1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER
@ 2025-09-14 12:07 Pauli Virtanen
2025-09-14 12:07 ` [PATCH BlueZ 2/2] avdtp: wait for L2CAP Disconnect Rsp before CLOSING->IDLE Pauli Virtanen
2025-09-14 13:33 ` [BlueZ,1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER bluez.test.bot
0 siblings, 2 replies; 3+ messages in thread
From: Pauli Virtanen @ 2025-09-14 12:07 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Add io_glib_shutdown_linger() for socket shutdown with wait for
remote ACK via SO_LINGER. E.g. wait for L2CAP Disconnect Rsp.
We don't want to block the main loop for the linger timeout, so call
shutdown() in a separate thread, as socket API seems to provide only the
blocking way to do it.
Implement it with Glib source API, as that's more convenient for the
AVDTP plugin that needs this.
---
src/shared/io-ell.c | 6 +++
src/shared/io-glib.c | 89 ++++++++++++++++++++++++++++++++++++++++
src/shared/io-mainloop.c | 6 +++
src/shared/io.h | 3 ++
4 files changed, 104 insertions(+)
diff --git a/src/shared/io-ell.c b/src/shared/io-ell.c
index 4d64cf3c5..e7baed0d3 100644
--- a/src/shared/io-ell.c
+++ b/src/shared/io-ell.c
@@ -315,3 +315,9 @@ unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func,
{
return 0;
}
+
+unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout,
+ io_destroy_func_t func, void *user_data)
+{
+ return 0;
+}
diff --git a/src/shared/io-glib.c b/src/shared/io-glib.c
index 81cd1122b..efb7b9f5c 100644
--- a/src/shared/io-glib.c
+++ b/src/shared/io-glib.c
@@ -461,3 +461,92 @@ unsigned int io_glib_add_err_watch(void *giochannel,
G_IO_ERR | G_IO_HUP | G_IO_NVAL,
err_watch_callback, data, g_free);
}
+
+/*
+ * shutdown() socket, enabling SO_LINGER to wait for close ACK, with
+ * asynchronous callback.
+ */
+
+struct shutdown_linger {
+ GSource source;
+ io_destroy_func_t func;
+ void *user_data;
+ int how;
+ GIOChannel *io;
+ GThread *thread;
+};
+
+static gpointer shutdown_linger_thread(gpointer data)
+{
+ struct shutdown_linger *source = data;
+
+ shutdown(g_io_channel_unix_get_fd(source->io), source->how);
+ g_source_set_ready_time(&source->source, 0);
+ g_source_unref(&source->source);
+ return NULL;
+}
+
+static gboolean shutdown_linger_dispatch(GSource *gsource, GSourceFunc callback,
+ gpointer user_data)
+{
+ struct shutdown_linger *source = (void *)gsource;
+
+ if (source->func)
+ source->func(source->user_data);
+ return FALSE;
+}
+
+static void shutdown_linger_finalize(GSource *gsource)
+{
+ struct shutdown_linger *source = (void *)gsource;
+
+ if (source->thread == g_thread_self())
+ g_thread_unref(source->thread);
+ else
+ g_thread_join(source->thread);
+
+ g_io_channel_unref(source->io);
+}
+
+unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout,
+ io_destroy_func_t func, void *user_data)
+{
+ static GSourceFuncs source_funcs = {
+ .dispatch = shutdown_linger_dispatch,
+ .finalize = shutdown_linger_finalize,
+ };
+ struct linger linger = {
+ .l_onoff = 1,
+ .l_linger = timeout,
+ };
+ GIOChannel *io = giochannel;
+ struct shutdown_linger *source;
+ guint id;
+ int fd;
+
+ if (!io)
+ return 0;
+
+ fd = g_io_channel_unix_get_fd(io);
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger))) {
+ shutdown(fd, how);
+ return 0;
+ }
+
+ source = (void *)g_source_new(&source_funcs, sizeof(*source));
+
+ g_source_set_name(&source->source, "shutdown_linger");
+ source->func = func;
+ source->user_data = user_data;
+ source->how = how;
+ source->io = g_io_channel_ref(io);
+
+ g_source_ref(&source->source); /* unref in thread */
+ source->thread = g_thread_new("shutdown_linger", shutdown_linger_thread,
+ source);
+
+ id = g_source_attach(&source->source, NULL);
+ g_source_unref(&source->source);
+
+ return id;
+}
diff --git a/src/shared/io-mainloop.c b/src/shared/io-mainloop.c
index 8fd49935e..abe76de1d 100644
--- a/src/shared/io-mainloop.c
+++ b/src/shared/io-mainloop.c
@@ -321,3 +321,9 @@ unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func,
{
return 0;
}
+
+unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout,
+ io_destroy_func_t func, void *user_data)
+{
+ return 0;
+}
diff --git a/src/shared/io.h b/src/shared/io.h
index 87c3c001c..7909b1707 100644
--- a/src/shared/io.h
+++ b/src/shared/io.h
@@ -37,3 +37,6 @@ bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback,
typedef void (*io_glib_err_func_t)(int cond, void *user_data);
unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func,
void *user_data);
+
+unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout,
+ io_destroy_func_t func, void *user_data);
--
2.51.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH BlueZ 2/2] avdtp: wait for L2CAP Disconnect Rsp before CLOSING->IDLE
2025-09-14 12:07 [PATCH BlueZ 1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER Pauli Virtanen
@ 2025-09-14 12:07 ` Pauli Virtanen
2025-09-14 13:33 ` [BlueZ,1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER bluez.test.bot
1 sibling, 0 replies; 3+ messages in thread
From: Pauli Virtanen @ 2025-09-14 12:07 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Pauli Virtanen
Delay CLOSING->IDLE until remote acknowledges L2CAP channel closure.
It is not explicitly stated in AVDTP v1.3 Sec. 6.13, but some devices
refuse commands sent immediately after L2CAP Disconnect Req, so wait
until Rsp.
Fails:
> ACL Data RX: Handle 6 flags 0x02 dlen 6
Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0}
AVDTP: Close (0x08) Response Accept (0x02) type 0x00 label 0 nosp 0
< ACL Data TX: Handle 6 flags 0x00 dlen 12
L2CAP: Disconnection Request (0x06) ident 16 len 4
Destination CID: 65
Source CID: 65
< ACL Data TX: Handle 6 flags 0x00 dlen 22
Channel: 64 len 18 [PSM 25 mode Basic (0x00)] {chan 0}
AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 1 nosp 0
ACP SEID: 7
INT SEID: 1
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: MPEG-2,4 AAC (0x02)
Object Type: MPEG-4 AAC LC (0x40)
Frequency: 44100 (0x100)
Channels: 2 (0x04)
Bitrate: 220000bps
VBR: No
Service Category: Delay Reporting (0x08)
> ACL Data RX: Handle 6 flags 0x02 dlen 12
L2CAP: Disconnection Response (0x07) ident 16 len 4
Destination CID: 65
Source CID: 65
> ACL Data RX: Handle 6 flags 0x02 dlen 8
Channel: 64 len 4 [PSM 25 mode Basic (0x00)] {chan 0}
AVDTP: Set Configuration (0x03) Response Reject (0x03) type 0x00 label 1 nosp 0
Service Category: Reserved (0x29)
Error code: UNSUPPORTED_CONFIGURATION (0x29)
Works:
> ACL Data RX: Handle 4 flags 0x02 dlen 6
Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0}
AVDTP: Close (0x08) Response Accept (0x02) type 0x00 label 12 nosp 0
< ACL Data TX: Handle 4 flags 0x00 dlen 12
L2CAP: Disconnection Request (0x06) ident 16 len 4
Destination CID: 65
Source CID: 65
> ACL Data RX: Handle 4 flags 0x02 dlen 12
L2CAP: Disconnection Response (0x07) ident 16 len 4
Destination CID: 65
Source CID: 65
< ACL Data TX: Handle 4 flags 0x00 dlen 22
Channel: 64 len 18 [PSM 25 mode Basic (0x00)] {chan 0}
AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 13 nosp 0
ACP SEID: 9
INT SEID: 2
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: MPEG-2,4 AAC (0x02)
Object Type: MPEG-4 AAC LC (0x40)
Frequency: 44100 (0x100)
Channels: 2 (0x04)
Bitrate: 220000bps
VBR: No
Service Category: Delay Reporting (0x08)
> ACL Data RX: Handle 4 flags 0x02 dlen 6
Channel: 64 len 2 [PSM 25 mode Basic (0x00)] {chan 0}
AVDTP: Set Configuration (0x03) Response Accept (0x02) type 0x00 label 13 nosp 0
Fixes: https://github.com/bluez/bluez/issues/1471
Fixes: aa118e965b ("a2dp: Don't wait to reconfigure")
---
profiles/audio/avdtp.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c
index 30648251f..3613dff2d 100644
--- a/profiles/audio/avdtp.c
+++ b/profiles/audio/avdtp.c
@@ -79,6 +79,7 @@
#define ABORT_TIMEOUT 2
#define DISCONNECT_TIMEOUT 1
#define START_TIMEOUT 1
+#define TRANSPORT_L2CAP_CLOSE_TIMEOUT 2
#if __BYTE_ORDER == __LITTLE_ENDIAN
@@ -752,6 +753,8 @@ static void transport_cb(int cond, void *data)
struct avdtp_stream *stream = data;
struct avdtp_local_sep *sep = stream->lsep;
+ DBG("");
+
if (stream->close_int && sep->cfm && sep->cfm->close)
sep->cfm->close(stream->session, sep, stream, NULL,
sep->user_data);
@@ -765,6 +768,26 @@ static void transport_cb(int cond, void *data)
avdtp_stream_set_state(stream, AVDTP_STATE_IDLE);
}
+static void close_stream_linger_finish(void *data)
+{
+ DBG("");
+
+ transport_cb(G_IO_HUP, data);
+}
+
+static void close_stream_linger(struct avdtp_stream *stream)
+{
+ /* Close and wait for L2CAP Disconnection Rsp via socket linger */
+ if (stream->io_id)
+ g_source_remove(stream->io_id);
+
+ stream->io_id = io_glib_shutdown_linger(stream->io, SHUT_RDWR,
+ TRANSPORT_L2CAP_CLOSE_TIMEOUT,
+ close_stream_linger_finish, stream);
+ if (!stream->io_id)
+ transport_cb(G_IO_HUP, stream);
+}
+
static int get_send_buffer_size(int sk)
{
int size;
@@ -2922,7 +2945,13 @@ static gboolean avdtp_close_resp(struct avdtp *session,
{
avdtp_stream_set_state(stream, AVDTP_STATE_CLOSING);
- close_stream(stream);
+ /* Delay CLOSING->IDLE until remote acknowledges L2CAP channel closure.
+ *
+ * It is not explicitly stated in AVDTP v1.3 Sec. 6.13, but some devices
+ * refuse commands sent immediately after L2CAP Disconnect Req, so wait
+ * until Rsp.
+ */
+ close_stream_linger(stream);
return TRUE;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* RE: [BlueZ,1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER
2025-09-14 12:07 [PATCH BlueZ 1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER Pauli Virtanen
2025-09-14 12:07 ` [PATCH BlueZ 2/2] avdtp: wait for L2CAP Disconnect Rsp before CLOSING->IDLE Pauli Virtanen
@ 2025-09-14 13:33 ` bluez.test.bot
1 sibling, 0 replies; 3+ messages in thread
From: bluez.test.bot @ 2025-09-14 13:33 UTC (permalink / raw)
To: linux-bluetooth, pav
[-- Attachment #1: Type: text/plain, Size: 1262 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1002158
---Test result---
Test Summary:
CheckPatch PENDING 0.23 seconds
GitLint PENDING 0.24 seconds
BuildEll PASS 20.07 seconds
BluezMake PASS 2567.36 seconds
MakeCheck PASS 20.18 seconds
MakeDistcheck PASS 183.89 seconds
CheckValgrind PASS 234.42 seconds
CheckSmatch PASS 305.43 seconds
bluezmakeextell PASS 128.41 seconds
IncrementalBuild PENDING 0.57 seconds
ScanBuild PASS 906.97 seconds
Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:
##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2025-09-14 13:33 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-14 12:07 [PATCH BlueZ 1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER Pauli Virtanen
2025-09-14 12:07 ` [PATCH BlueZ 2/2] avdtp: wait for L2CAP Disconnect Rsp before CLOSING->IDLE Pauli Virtanen
2025-09-14 13:33 ` [BlueZ,1/2] shared/io: add helper for asynchronous shutdown() with SO_LINGER 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