diff mbox series

[BlueZ,1/1] bass: Move io handling out of src/shared

Message ID 20231030153903.4248-2-iulia.tanasescu@nxp.com
State New
Headers show
Series bass: Move io handling out of src/shared | expand

Commit Message

Iulia Tanasescu Oct. 30, 2023, 3:39 p.m. UTC
This moves IO handling from src/shared/bass.c to profiles/audio/bass.c.

The profiles/audio module registers io callbacks for listen and accept
with src/shared. These callbacks will be used to perform PA/BIG sync
and they will notify the result back to src/shared.

---
 profiles/audio/bass.c | 397 ++++++++++++++++++++++++++++++++++++++
 src/shared/bass.c     | 435 ++++++++++++++++--------------------------
 src/shared/bass.h     |  23 ++-
 3 files changed, 583 insertions(+), 272 deletions(-)
diff mbox series

Patch

diff --git a/profiles/audio/bass.c b/profiles/audio/bass.c
index 7952105c5..3984fca59 100644
--- a/profiles/audio/bass.c
+++ b/profiles/audio/bass.c
@@ -22,6 +22,7 @@ 
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <poll.h>
 
 #include <glib.h>
 
@@ -29,6 +30,9 @@ 
 
 #include "lib/bluetooth.h"
 #include "lib/uuid.h"
+#include "lib/iso.h"
+
+#include "btio/btio.h"
 
 #include "src/dbus-common.h"
 #include "src/shared/util.h"
@@ -54,10 +58,51 @@  struct bass_data {
 	struct btd_device *device;
 	struct btd_service *service;
 	struct bt_bass *bass;
+
+	unsigned int io_cb_id;
 };
 
 static struct queue *sessions;
 
+struct bt_bass_io {
+	GIOChannel *listen;
+	guint listen_io_id;
+	GIOChannel *pa;
+	guint pa_io_id;
+	struct queue *bises;
+};
+
+#define MAX_BIS_BITMASK_IDX		31
+
+#define DEFAULT_IO_QOS \
+{ \
+	.interval	= 10000, \
+	.latency	= 10, \
+	.sdu		= 40, \
+	.phy		= 0x02, \
+	.rtn		= 2, \
+}
+
+static struct bt_iso_qos default_qos = {
+	.bcast = {
+		.big			= BT_ISO_QOS_BIG_UNSET,
+		.bis			= BT_ISO_QOS_BIS_UNSET,
+		.sync_factor		= 0x07,
+		.packing		= 0x00,
+		.framing		= 0x00,
+		.in			= DEFAULT_IO_QOS,
+		.out			= DEFAULT_IO_QOS,
+		.encryption		= 0x00,
+		.bcode			= {0x00},
+		.options		= 0x00,
+		.skip			= 0x0000,
+		.sync_timeout		= 0x4000,
+		.sync_cte_type		= 0x00,
+		.mse			= 0x00,
+		.timeout		= 0x4000,
+	}
+};
+
 static void bass_debug(const char *str, void *user_data)
 {
 	DBG_IDX(0xffff, "%s", str);
@@ -148,6 +193,350 @@  static void bass_detached(struct bt_bass *bass, void *user_data)
 	bass_data_remove(data);
 }
 
+static gboolean check_io_err(GIOChannel *io)
+{
+	struct pollfd fds;
+
+	memset(&fds, 0, sizeof(fds));
+	fds.fd = g_io_channel_unix_get_fd(io);
+	fds.events = POLLERR;
+
+	if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLERR))
+		return TRUE;
+
+	return FALSE;
+}
+
+static gboolean pa_io_disconnect_cb(GIOChannel *io, GIOCondition cond,
+			gpointer data)
+{
+	struct bt_bcast_src *bcast_src = data;
+
+	DBG("PA sync io has been disconnected");
+
+	bcast_src->io->pa_io_id = 0;
+	g_io_channel_unref(bcast_src->io->pa);
+	bcast_src->io->pa = NULL;
+
+	return FALSE;
+}
+
+static void confirm_cb(GIOChannel *io, gpointer user_data)
+{
+	struct bt_bcast_src *bcast_src = user_data;
+	int sk, err;
+	socklen_t len;
+	struct bt_iso_qos qos;
+
+	if (!bcast_src || !bcast_src->bass)
+		return;
+
+	if (check_io_err(io)) {
+		DBG("PA sync failed");
+
+		/* Mark PA sync as failed and notify client */
+		bcast_src->sync_state = BT_BASS_FAILED_TO_SYNCHRONIZE_TO_PA;
+		goto notify;
+	}
+
+	bcast_src->sync_state = BT_BASS_SYNCHRONIZED_TO_PA;
+	bcast_src->io->pa = io;
+	g_io_channel_ref(bcast_src->io->pa);
+
+	bcast_src->io->pa_io_id = g_io_add_watch(io, G_IO_ERR |
+					G_IO_HUP | G_IO_NVAL,
+					(GIOFunc) pa_io_disconnect_cb,
+					bcast_src);
+
+	len = sizeof(qos);
+	memset(&qos, 0, len);
+
+	sk = g_io_channel_unix_get_fd(io);
+
+	err = getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len);
+	if (err < 0) {
+		DBG("Failed to get iso qos");
+		return;
+	}
+
+	if (!qos.bcast.encryption)
+		/* BIG is not encrypted. Try to synchronize */
+		bcast_src->enc = BT_BASS_BIG_ENC_STATE_NO_ENC;
+	else
+		/* BIG is encrypted. Wait for Client to provide the
+		 * Broadcast_Code
+		 */
+		bcast_src->enc = BT_BASS_BIG_ENC_STATE_BCODE_REQ;
+
+notify:
+	if (bcast_src->confirm_cb)
+		bcast_src->confirm_cb(bcast_src);
+}
+
+static gboolean listen_io_disconnect_cb(GIOChannel *io, GIOCondition cond,
+			gpointer data)
+{
+	struct bt_bcast_src *bcast_src = data;
+
+	DBG("Listen io has been disconnected");
+
+	bcast_src->io->listen_io_id = 0;
+	g_io_channel_unref(bcast_src->io->listen);
+	bcast_src->io->listen = NULL;
+
+	return FALSE;
+}
+
+static int bass_io_listen(struct bt_bcast_src *bcast_src,
+				const bdaddr_t *src)
+{
+	uint8_t addr_type;
+	GError *err = NULL;
+	struct bt_iso_qos iso_qos = default_qos;
+	uint8_t num_bis = 0;
+	uint8_t bis[ISO_MAX_NUM_BIS];
+
+	if (!bcast_src)
+		return -1;
+
+	if (!bcast_src->io) {
+		bcast_src->io = malloc(sizeof(*bcast_src->io));
+		if (!bcast_src->io)
+			return -1;
+
+		memset(bcast_src->io, 0, sizeof(*bcast_src->io));
+	}
+
+	memset(bis, 0, ISO_MAX_NUM_BIS);
+
+	for (int i = 0; i < bcast_src->num_subgroups; i++) {
+		struct bt_bass_subgroup_data *data =
+				&bcast_src->subgroup_data[i];
+
+		if (data->pending_bis_sync != BIS_SYNC_NO_PREF)
+			/* Iterate through the bis sync bitmask written
+			 * by the client and store the bis indexes that
+			 * the BASS server will try to synchronize to
+			 */
+			for (int bis_idx = 0; bis_idx < 31; bis_idx++) {
+				if (data->pending_bis_sync & (1 << bis_idx)) {
+					bis[num_bis] = bis_idx + 1;
+					num_bis++;
+				}
+			}
+	}
+
+	/* Convert to three-value type */
+	if (bcast_src->addr_type)
+		addr_type = BDADDR_LE_RANDOM;
+	else
+		addr_type = BDADDR_LE_PUBLIC;
+
+	/* Try to synchronize to the source */
+	bcast_src->io->listen = bt_io_listen(NULL, confirm_cb,
+				bcast_src, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR,
+				src,
+				BT_IO_OPT_DEST_BDADDR,
+				&bcast_src->addr,
+				BT_IO_OPT_DEST_TYPE,
+				addr_type,
+				BT_IO_OPT_MODE, BT_IO_MODE_ISO,
+				BT_IO_OPT_QOS, &iso_qos,
+				BT_IO_OPT_ISO_BC_SID, bcast_src->sid,
+				BT_IO_OPT_ISO_BC_NUM_BIS, num_bis,
+				BT_IO_OPT_ISO_BC_BIS, bis,
+				BT_IO_OPT_INVALID);
+
+	if (!bcast_src->io->listen) {
+		DBG("%s", err->message);
+		g_error_free(err);
+		return -1;
+	}
+
+	g_io_channel_ref(bcast_src->io->listen);
+
+	bcast_src->io->listen_io_id = g_io_add_watch(bcast_src->io->listen,
+					G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					(GIOFunc)listen_io_disconnect_cb,
+					bcast_src);
+
+	if (num_bis > 0 && !bcast_src->io->bises)
+		bcast_src->io->bises = queue_new();
+
+	return 0;
+}
+
+static void bass_bis_unref(void *data)
+{
+	GIOChannel *io = data;
+
+	g_io_channel_unref(io);
+}
+
+static void connect_cb(GIOChannel *io, GError *gerr,
+				gpointer user_data)
+{
+	struct bt_bcast_src *bcast_src = user_data;
+	int bis_idx;
+	int i;
+
+	if (!bcast_src || !bcast_src->bass)
+		return;
+
+	/* Keep io reference */
+	g_io_channel_ref(io);
+	queue_push_tail(bcast_src->io->bises, io);
+
+	for (i = 0; i < bcast_src->num_subgroups; i++) {
+		struct bt_bass_subgroup_data *data =
+				&bcast_src->subgroup_data[i];
+
+		for (bis_idx = 0; bis_idx < MAX_BIS_BITMASK_IDX; bis_idx++) {
+			if (data->pending_bis_sync & (1 << bis_idx)) {
+				data->bis_sync |= (1 << bis_idx);
+				data->pending_bis_sync &= ~(1 << bis_idx);
+				break;
+			}
+		}
+
+		if (bis_idx < MAX_BIS_BITMASK_IDX)
+			break;
+	}
+
+	for (i = 0; i < bcast_src->num_subgroups; i++) {
+		if (bcast_src->subgroup_data[i].pending_bis_sync)
+			break;
+	}
+
+	/* Wait until all BISes have been connected */
+	if (i != bcast_src->num_subgroups)
+		return;
+
+	if (check_io_err(io)) {
+		DBG("BIG sync failed");
+
+		queue_destroy(bcast_src->io->bises, bass_bis_unref);
+
+		bcast_src->io->bises = NULL;
+
+		/* Close listen io */
+		g_io_channel_shutdown(bcast_src->io->listen, TRUE, NULL);
+		g_io_channel_unref(bcast_src->io->listen);
+
+		bcast_src->io->listen = NULL;
+
+		if (bcast_src->io->listen_io_id > 0) {
+			g_source_remove(bcast_src->io->listen_io_id);
+			bcast_src->io->listen_io_id  = 0;
+		}
+
+		/* Close PA io */
+		g_io_channel_shutdown(bcast_src->io->pa, TRUE, NULL);
+		g_io_channel_unref(bcast_src->io->pa);
+
+		bcast_src->io->pa = NULL;
+
+		if (bcast_src->io->pa_io_id > 0) {
+			g_source_remove(bcast_src->io->pa_io_id);
+			bcast_src->io->pa_io_id  = 0;
+		}
+
+		for (i = 0; i < bcast_src->num_subgroups; i++)
+			bcast_src->subgroup_data[i].bis_sync =
+				BT_BASS_BIG_SYNC_FAILED_BITMASK;
+
+		/* If BIG sync failed because of an incorrect broadcast code,
+		 * inform client
+		 */
+		if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BCODE_REQ)
+			bcast_src->enc = BT_BASS_BIG_ENC_STATE_BAD_CODE;
+	} else {
+		if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BCODE_REQ)
+			bcast_src->enc = BT_BASS_BIG_ENC_STATE_DEC;
+	}
+
+	if (bcast_src->connect_cb)
+		bcast_src->connect_cb(bcast_src);
+}
+
+static int bass_io_accept(struct bt_bcast_src *bcast_src)
+{
+	int sk, err;
+	socklen_t len;
+	struct bt_iso_qos qos;
+	GError *gerr = NULL;
+
+	if (!bcast_src || !bcast_src->io || !bcast_src->io->pa)
+		return -1;
+
+	if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BCODE_REQ) {
+		/* Update socket QoS with Broadcast Code */
+		len = sizeof(qos);
+		memset(&qos, 0, len);
+
+		sk = g_io_channel_unix_get_fd(bcast_src->io->pa);
+
+		err = getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len);
+		if (err < 0) {
+			DBG("Failed to get iso qos");
+			return -1;
+		}
+
+		memcpy(qos.bcast.bcode, bcast_src->bcode,
+			BT_BASS_BCAST_CODE_SIZE);
+
+		if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos,
+					sizeof(qos)) < 0) {
+			DBG("Failed to set iso qos");
+			return -1;
+		}
+	}
+
+	if (!bt_io_bcast_accept(bcast_src->io->pa,
+		connect_cb, bcast_src, NULL, &gerr,
+		BT_IO_OPT_INVALID)) {
+		DBG("bt_io_accept: %s", gerr->message);
+		g_error_free(gerr);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void bass_io_destroy(struct bt_bcast_src *bcast_src)
+{
+	if (!bcast_src || !bcast_src->io)
+		return;
+
+	queue_destroy(bcast_src->io->bises, bass_bis_unref);
+	bcast_src->io->bises = NULL;
+
+	if (bcast_src->io->listen) {
+		g_io_channel_shutdown(bcast_src->io->listen, TRUE, NULL);
+		g_io_channel_unref(bcast_src->io->listen);
+		bcast_src->io->listen = NULL;
+	}
+
+	if (bcast_src->io->listen_io_id > 0) {
+		g_source_remove(bcast_src->io->listen_io_id);
+		bcast_src->io->listen_io_id  = 0;
+	}
+
+	if (bcast_src->io->pa) {
+		g_io_channel_shutdown(bcast_src->io->pa, TRUE, NULL);
+		g_io_channel_unref(bcast_src->io->pa);
+		bcast_src->io->pa = NULL;
+	}
+
+	if (bcast_src->io->pa_io_id > 0) {
+		g_source_remove(bcast_src->io->pa_io_id);
+		bcast_src->io->pa_io_id  = 0;
+	}
+
+	free(bcast_src->io);
+}
+
 static void bass_attached(struct bt_bass *bass, void *user_data)
 {
 	struct bass_data *data;
@@ -173,6 +562,10 @@  static void bass_attached(struct bt_bass *bass, void *user_data)
 	data = bass_data_new(device);
 	data->bass = bass;
 
+	/* Register io callbacks */
+	data->io_cb_id = bt_bass_io_cb_register(bass, bass_io_listen,
+			bass_io_accept, bass_io_destroy);
+
 	bass_data_add(data);
 }
 
@@ -205,6 +598,10 @@  static int bass_probe(struct btd_service *service)
 		return -EINVAL;
 	}
 
+	/* Register io callbacks */
+	data->io_cb_id = bt_bass_io_cb_register(data->bass, bass_io_listen,
+			bass_io_accept, bass_io_destroy);
+
 	bass_data_add(data);
 	bt_bass_set_user_data(data->bass, service);
 
diff --git a/src/shared/bass.c b/src/shared/bass.c
index ce8b239f3..8e410bf9f 100644
--- a/src/shared/bass.c
+++ b/src/shared/bass.c
@@ -14,13 +14,9 @@ 
 #include <stdbool.h>
 #include <unistd.h>
 #include <errno.h>
-#include <poll.h>
 
 #include "lib/bluetooth.h"
 #include "lib/uuid.h"
-#include "lib/iso.h"
-
-#include "btio/btio.h"
 
 #include "src/shared/queue.h"
 #include "src/shared/util.h"
@@ -29,8 +25,6 @@ 
 #include "src/shared/gatt-client.h"
 #include "src/shared/bass.h"
 
-#define MAX_BIS_BITMASK_IDX		31
-
 #define DBG(_bass, fmt, arg...) \
 	bass_debug(_bass, "%s:%s() " fmt, __FILE__, __func__, ## arg)
 
@@ -43,6 +37,13 @@  struct bt_bass_cb {
 	void *user_data;
 };
 
+struct bt_bass_io_cb {
+	unsigned int id;
+	bt_bass_listen_func_t listen;
+	bt_bass_accept_func_t accept;
+	bt_bass_io_destroy_func_t io_destroy;
+};
+
 struct bt_bcast_recv_state {
 	struct bt_bass_db *bdb;
 	struct gatt_db_attribute *attr;
@@ -64,6 +65,7 @@  struct bt_bass {
 	struct bt_bass_db *rdb;
 	struct bt_gatt_client *client;
 	struct bt_att *att;
+	unsigned int disconn_id;
 
 	struct queue *notify;
 
@@ -71,6 +73,8 @@  struct bt_bass {
 	bt_bass_destroy_func_t debug_destroy;
 	void *debug_data;
 
+	struct queue *io_cbs;
+
 	void *user_data;
 };
 
@@ -89,35 +93,6 @@  static struct queue *bass_db;
 static struct queue *bass_cbs;
 static struct queue *sessions;
 
-#define DEFAULT_IO_QOS \
-{ \
-	.interval	= 10000, \
-	.latency	= 10, \
-	.sdu		= 40, \
-	.phy		= 0x02, \
-	.rtn		= 2, \
-}
-
-static struct bt_iso_qos default_qos = {
-	.bcast = {
-		.big			= BT_ISO_QOS_BIG_UNSET,
-		.bis			= BT_ISO_QOS_BIS_UNSET,
-		.sync_factor		= 0x07,
-		.packing		= 0x00,
-		.framing		= 0x00,
-		.in			= DEFAULT_IO_QOS,
-		.out			= DEFAULT_IO_QOS,
-		.encryption		= 0x00,
-		.bcode			= {0x00},
-		.options		= 0x00,
-		.skip			= 0x0000,
-		.sync_timeout		= 0x4000,
-		.sync_cte_type		= 0x00,
-		.mse			= 0x00,
-		.timeout		= 0x4000,
-	}
-};
-
 static void bass_bcast_src_free(void *data);
 
 static void bass_debug(struct bt_bass *bass, const char *format, ...)
@@ -580,102 +555,11 @@  static bool bass_src_attr_match(const void *data, const void *match_data)
 	return (bcast_src->attr == attr);
 }
 
-static gboolean check_io_err(GIOChannel *io)
-{
-	struct pollfd fds;
-
-	memset(&fds, 0, sizeof(fds));
-	fds.fd = g_io_channel_unix_get_fd(io);
-	fds.events = POLLERR;
-
-	if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLERR))
-		return TRUE;
-
-	return FALSE;
-}
-
-static void bass_bis_unref(void *data)
-{
-	GIOChannel *io = data;
-
-	g_io_channel_unref(io);
-}
-
-static void connect_cb(GIOChannel *io, GError *gerr,
-				gpointer user_data)
+static void connect_cb(void *user_data)
 {
 	struct bt_bcast_src *bcast_src = user_data;
 	uint8_t *notify_data;
 	size_t notify_data_len;
-	int bis_idx;
-	int i;
-
-	/* Keep io reference */
-	g_io_channel_ref(io);
-	queue_push_tail(bcast_src->bises, io);
-
-	for (i = 0; i < bcast_src->num_subgroups; i++) {
-		struct bt_bass_subgroup_data *data =
-				&bcast_src->subgroup_data[i];
-
-		for (bis_idx = 0; bis_idx < MAX_BIS_BITMASK_IDX; bis_idx++) {
-			if (data->pending_bis_sync & (1 << bis_idx)) {
-				data->bis_sync |= (1 << bis_idx);
-				data->pending_bis_sync &= ~(1 << bis_idx);
-				break;
-			}
-		}
-
-		if (bis_idx < MAX_BIS_BITMASK_IDX)
-			break;
-	}
-
-	for (i = 0; i < bcast_src->num_subgroups; i++) {
-		if (bcast_src->subgroup_data[i].pending_bis_sync)
-			break;
-	}
-
-	/* If there are still pending bises, wait for their
-	 * notifications also before sending notification to
-	 * client
-	 */
-	if (i != bcast_src->num_subgroups)
-		return;
-
-	/* All connections have been notified */
-	if (check_io_err(io)) {
-		DBG(bcast_src->bass, "BIG sync failed");
-
-		/* Close all connected bises */
-		queue_destroy(bcast_src->bises, bass_bis_unref);
-		bcast_src->bises = NULL;
-
-		/* Close listen io */
-		g_io_channel_shutdown(bcast_src->listen_io, TRUE, NULL);
-		g_io_channel_unref(bcast_src->listen_io);
-		bcast_src->listen_io = NULL;
-
-		/* Close pa sync io */
-		if (bcast_src->pa_sync_io) {
-			g_io_channel_shutdown(bcast_src->pa_sync_io,
-					TRUE, NULL);
-			g_io_channel_unref(bcast_src->pa_sync_io);
-			bcast_src->pa_sync_io = NULL;
-		}
-
-		for (i = 0; i < bcast_src->num_subgroups; i++)
-			bcast_src->subgroup_data[i].bis_sync =
-				BT_BASS_BIG_SYNC_FAILED_BITMASK;
-
-		/* If BIG sync failed because of an incorrect broadcast code,
-		 * inform client
-		 */
-		if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BCODE_REQ)
-			bcast_src->enc = BT_BASS_BIG_ENC_STATE_BAD_CODE;
-	} else {
-		if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BCODE_REQ)
-			bcast_src->enc = BT_BASS_BIG_ENC_STATE_DEC;
-	}
 
 	/* Send notification to client */
 	notify_data = bass_build_notif_from_bcast_src(bcast_src,
@@ -703,62 +587,25 @@  static bool bass_trigger_big_sync(struct bt_bcast_src *bcast_src)
 	return false;
 }
 
-
-static void confirm_cb(GIOChannel *io, gpointer user_data)
+static void confirm_cb(void *user_data)
 {
 	struct bt_bcast_src *bcast_src = user_data;
-	int sk, err;
-	socklen_t len;
-	struct bt_iso_qos qos;
 	uint8_t *notify_data;
 	size_t notify_data_len;
-	GError *gerr = NULL;
-
-	if (check_io_err(io)) {
-		DBG(bcast_src->bass, "PA sync failed");
-
-		/* Mark PA sync as failed and notify client */
-		bcast_src->sync_state = BT_BASS_FAILED_TO_SYNCHRONIZE_TO_PA;
-		goto notify;
-	}
-
-	bcast_src->sync_state = BT_BASS_SYNCHRONIZED_TO_PA;
-	bcast_src->pa_sync_io = io;
-	g_io_channel_ref(bcast_src->pa_sync_io);
-
-	len = sizeof(qos);
-	memset(&qos, 0, len);
+	const struct queue_entry *entry;
 
-	sk = g_io_channel_unix_get_fd(io);
+	if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_NO_ENC &&
+		bass_trigger_big_sync(bcast_src)) {
+		for (entry = queue_get_entries(bcast_src->bass->io_cbs);
+						entry; entry = entry->next) {
+			struct bt_bass_io_cb *cb = entry->data;
 
-	err = getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len);
-	if (err < 0) {
-		DBG(bcast_src->bass, "Failed to get iso qos");
-		return;
-	}
-
-	if (!qos.bcast.encryption) {
-		/* BIG is not encrypted. Try to synchronize */
-		bcast_src->enc = BT_BASS_BIG_ENC_STATE_NO_ENC;
-
-		if (bass_trigger_big_sync(bcast_src)) {
-			if (!bt_io_bcast_accept(bcast_src->pa_sync_io,
-				connect_cb, bcast_src, NULL, &gerr,
-				BT_IO_OPT_INVALID)) {
-				DBG(bcast_src->bass, "bt_io_bcast_accept: %s",
-				gerr->message);
-				g_error_free(gerr);
-			}
-			return;
+			if (cb->accept && (cb->accept(bcast_src) < 0))
+				DBG(bcast_src->bass, "Unable to accept");
 		}
-
-		goto notify;
+		return;
 	}
 
-	/* BIG is encrypted. Wait for Client to provide the Broadcast_Code */
-	bcast_src->enc = BT_BASS_BIG_ENC_STATE_BCODE_REQ;
-
-notify:
 	notify_data = bass_build_notif_from_bcast_src(bcast_src,
 						&notify_data_len);
 
@@ -856,14 +703,9 @@  static void bass_handle_add_src_op(struct bt_bass *bass,
 	uint8_t src_id = 0;
 	struct gatt_db_attribute *attr;
 	uint8_t *pa_sync;
-	GIOChannel *io;
-	GError *err = NULL;
-	struct bt_iso_qos iso_qos = default_qos;
-	uint8_t num_bis = 0;
-	uint8_t bis[ISO_MAX_NUM_BIS];
 	uint8_t *notify_data;
 	size_t notify_data_len;
-	uint8_t addr_type;
+	const struct queue_entry *entry;
 
 	gatt_db_attribute_write_result(attrib, id, 0x00);
 
@@ -881,7 +723,6 @@  static void bass_handle_add_src_op(struct bt_bass *bass,
 	queue_push_tail(bass->ldb->bcast_srcs, bcast_src);
 
 	memset(bcast_src, 0, sizeof(*bcast_src));
-	memset(bis, 0, ISO_MAX_NUM_BIS);
 
 	bcast_src->bass = bass;
 
@@ -968,18 +809,6 @@  static void bass_handle_add_src_op(struct bt_bass *bass,
 
 		util_iov_pull_le32(iov, &data->pending_bis_sync);
 
-		if (data->pending_bis_sync != BIS_SYNC_NO_PREF)
-			/* Iterate through the bis sync bitmask written
-			 * by the client and store the bis indexes that
-			 * the BASS server will try to synchronize to
-			 */
-			for (int bis_idx = 0; bis_idx < 31; bis_idx++) {
-				if (data->pending_bis_sync & (1 << bis_idx)) {
-					bis[num_bis] = bis_idx + 1;
-					num_bis++;
-				}
-			}
-
 		data->meta_len = *(uint8_t *)util_iov_pull_mem(iov,
 						sizeof(data->meta_len));
 		if (!data->meta_len)
@@ -993,39 +822,20 @@  static void bass_handle_add_src_op(struct bt_bass *bass,
 					data->meta_len), data->meta_len);
 	}
 
-	if (pa_sync != PA_SYNC_NO_SYNC) {
-		/* Convert to three-value type */
-		if (bcast_src->addr_type)
-			addr_type = BDADDR_LE_RANDOM;
-		else
-			addr_type = BDADDR_LE_PUBLIC;
-
-		/* If requested by client, try to synchronize to the source */
-		io = bt_io_listen(NULL, confirm_cb, bcast_src, NULL, &err,
-					BT_IO_OPT_SOURCE_BDADDR,
-					&bass->ldb->adapter_bdaddr,
-					BT_IO_OPT_DEST_BDADDR,
-					&bcast_src->addr,
-					BT_IO_OPT_DEST_TYPE,
-					addr_type,
-					BT_IO_OPT_MODE, BT_IO_MODE_ISO,
-					BT_IO_OPT_QOS, &iso_qos,
-					BT_IO_OPT_ISO_BC_SID, bcast_src->sid,
-					BT_IO_OPT_ISO_BC_NUM_BIS, num_bis,
-					BT_IO_OPT_ISO_BC_BIS, bis,
-					BT_IO_OPT_INVALID);
-
-		if (!io) {
-			DBG(bass, "%s", err->message);
-			g_error_free(err);
-			goto err;
-		}
+	bcast_src->confirm_cb = confirm_cb;
+	bcast_src->connect_cb = connect_cb;
 
-		bcast_src->listen_io = io;
-		g_io_channel_ref(bcast_src->listen_io);
+	if (*pa_sync != PA_SYNC_NO_SYNC) {
+		for (entry = queue_get_entries(bass->io_cbs); entry;
+						entry = entry->next) {
+			struct bt_bass_io_cb *cb = entry->data;
 
-		if (num_bis > 0 && !bcast_src->bises)
-			bcast_src->bises = queue_new();
+			if (cb->listen && (cb->listen(bcast_src,
+				&bass->ldb->adapter_bdaddr) < 0)) {
+				DBG(bass, "Unable to listen for source");
+				goto err;
+			}
+		}
 	} else {
 		for (int i = 0; i < bcast_src->num_subgroups; i++)
 			bcast_src->subgroup_data[i].bis_sync =
@@ -1064,12 +874,9 @@  static void bass_handle_set_bcast_code_op(struct bt_bass *bass,
 {
 	struct bt_bass_set_bcast_code_params *params;
 	struct bt_bcast_src *bcast_src;
-	int sk, err;
-	socklen_t len;
-	struct bt_iso_qos qos;
-	GError *gerr = NULL;
 	uint8_t *notify_data;
 	size_t notify_data_len;
+	const struct queue_entry *entry;
 
 	/* Get Set Broadcast Code command parameters */
 	params = util_iov_pull_mem(iov, sizeof(*params));
@@ -1086,6 +893,9 @@  static void bass_handle_set_bcast_code_op(struct bt_bass *bass,
 		return;
 	}
 
+	if (!bcast_src->bass)
+		return;
+
 	gatt_db_attribute_write_result(attrib, id, 0x00);
 
 	if (!bass_trigger_big_sync(bcast_src)) {
@@ -1103,36 +913,15 @@  static void bass_handle_set_bcast_code_op(struct bt_bass *bass,
 		return;
 	}
 
-	/* Try to sync to the source using the
-	 * received broadcast code
-	 */
-	len = sizeof(qos);
-	memset(&qos, 0, len);
-
-	if (!bcast_src->pa_sync_io)
-		return;
+	/* Set Broadcast Code */
+	memcpy(bcast_src->bcode, params->bcast_code, BT_BASS_BCAST_CODE_SIZE);
 
-	sk = g_io_channel_unix_get_fd(bcast_src->pa_sync_io);
+	for (entry = queue_get_entries(bcast_src->bass->io_cbs); entry;
+							entry = entry->next) {
+		struct bt_bass_io_cb *cb = entry->data;
 
-	err = getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len);
-	if (err < 0) {
-		DBG(bcast_src->bass, "Failed to get iso qos");
-		return;
-	}
-
-	/* Update socket QoS with Broadcast Code */
-	memcpy(qos.bcast.bcode, params->bcast_code, BT_BASS_BCAST_CODE_SIZE);
-
-	if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos,
-				sizeof(qos)) < 0) {
-		DBG(bcast_src->bass, "Failed to set iso qos");
-		return;
-	}
-
-	if (!bt_io_bcast_accept(bcast_src->pa_sync_io, connect_cb,
-		bcast_src, NULL, &gerr,  BT_IO_OPT_INVALID)) {
-		DBG(bcast_src->bass, "bt_io_bcast_accept: %s", gerr->message);
-		g_error_free(gerr);
+		if (cb->accept && (cb->accept(bcast_src) < 0))
+			DBG(bass, "Unable to accept");
 	}
 }
 
@@ -1308,23 +1097,24 @@  static void bass_new(struct bt_bass_db *bdb)
 static void bass_bcast_src_free(void *data)
 {
 	struct bt_bcast_src *bcast_src = data;
+	const struct queue_entry *entry;
+
+	if (!bcast_src)
+		return;
 
 	for (int i = 0; i < bcast_src->num_subgroups; i++)
 		free(bcast_src->subgroup_data[i].meta);
 
 	free(bcast_src->subgroup_data);
 
-	if (bcast_src->listen_io) {
-		g_io_channel_shutdown(bcast_src->listen_io, TRUE, NULL);
-		g_io_channel_unref(bcast_src->listen_io);
-	}
-
-	if (bcast_src->pa_sync_io) {
-		g_io_channel_shutdown(bcast_src->pa_sync_io, TRUE, NULL);
-		g_io_channel_unref(bcast_src->pa_sync_io);
-	}
+	if (bcast_src->bass)
+		for (entry = queue_get_entries(bcast_src->bass->io_cbs);
+						entry; entry = entry->next) {
+			struct bt_bass_io_cb *cb = entry->data;
 
-	queue_destroy(bcast_src->bises, bass_bis_unref);
+			if (cb->io_destroy)
+				cb->io_destroy(bcast_src);
+		}
 
 	free(bcast_src);
 }
@@ -1335,6 +1125,9 @@  static void read_bcast_recv_state(bool success, uint8_t att_ecode,
 {
 	struct bt_bcast_src *bcast_src = user_data;
 
+	if (!bcast_src || !bcast_src->bass)
+		return;
+
 	if (!success) {
 		DBG(bcast_src->bass, "Unable to read "
 			"Broadcast Receive State: error 0x%02x",
@@ -1522,6 +1315,30 @@  static void bass_attached(void *data, void *user_data)
 	cb->attached(bass, cb->user_data);
 }
 
+static void bass_disconnected(int err, void *user_data)
+{
+	struct bt_bass *bass = user_data;
+
+	bass->disconn_id = 0;
+
+	DBG(bass, "bass %p disconnected err %d", bass, err);
+
+	bt_bass_detach(bass);
+}
+
+static void bass_attach_att(struct bt_bass *bass, struct bt_att *att)
+{
+	if (bass->disconn_id) {
+		if (att == bt_bass_get_att(bass))
+			return;
+		bt_att_unregister_disconnect(att, bass->disconn_id);
+	}
+
+	bass->disconn_id = bt_att_register_disconnect(att,
+							bass_disconnected,
+							bass, NULL);
+}
+
 bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client)
 {
 	bt_uuid_t uuid;
@@ -1533,8 +1350,10 @@  bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client)
 
 	queue_foreach(bass_cbs, bass_attached, bass);
 
-	if (!client)
+	if (!client) {
+		bass_attach_att(bass, bass->att);
 		return true;
+	}
 
 	if (bass->client)
 		return false;
@@ -1543,6 +1362,8 @@  bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client)
 	if (!bass->client)
 		return false;
 
+	bass_attach_att(bass, bt_gatt_client_get_att(client));
+
 	bt_uuid16_create(&uuid, BASS_UUID);
 	gatt_db_foreach_service(bass->rdb->db, &uuid, foreach_bass_service,
 				bass);
@@ -1572,8 +1393,16 @@  void bt_bass_detach(struct bt_bass *bass)
 	if (!queue_remove(sessions, bass))
 		return;
 
-	bt_gatt_client_unref(bass->client);
-	bass->client = NULL;
+	if (bass->client) {
+		bt_att_unregister_disconnect(
+				bt_gatt_client_get_att(bass->client),
+				bass->disconn_id);
+		bt_gatt_client_unref(bass->client);
+		bass->client = NULL;
+	} else {
+		bt_att_unregister_disconnect(bass->att, bass->disconn_id);
+		bass->att = NULL;
+	}
 
 	queue_foreach(bass_cbs, bass_detached, bass);
 }
@@ -1591,13 +1420,32 @@  static void bass_db_free(void *data)
 	free(bdb);
 }
 
+static void bass_io_cb_free(void *data)
+{
+	struct bt_bass_io_cb *cb = data;
+
+	free(cb);
+}
+
 static void bass_free(void *data)
 {
 	struct bt_bass *bass = data;
+	const struct queue_entry *entry;
 
 	bt_bass_detach(bass);
 	bass_db_free(bass->rdb);
+
+	if (bass->ldb)
+		for (entry = queue_get_entries(bass->ldb->bcast_srcs);
+						entry; entry = entry->next) {
+			struct bt_bcast_src *bcast_src = entry->data;
+
+			if (bcast_src && bcast_src->bass == bass)
+				bcast_src->bass = NULL;
+		}
+
 	queue_destroy(bass->notify, NULL);
+	queue_destroy(bass->io_cbs, bass_io_cb_free);
 
 	free(bass);
 }
@@ -1692,6 +1540,7 @@  struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb,
 	bass = new0(struct bt_bass, 1);
 	bass->ldb = db;
 	bass->notify = queue_new();
+	bass->io_cbs = queue_new();
 
 	if (!rdb)
 		goto done;
@@ -1782,3 +1631,51 @@  void bt_bass_add_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr)
 {
 	bass_db_new(db, adapter_bdaddr);
 }
+
+unsigned int bt_bass_io_cb_register(struct bt_bass *bass,
+				bt_bass_listen_func_t listen,
+				bt_bass_accept_func_t accept,
+				bt_bass_io_destroy_func_t io_destroy)
+{
+	struct bt_bass_io_cb *cb;
+	static unsigned int id;
+
+	if (!bass)
+		return 0;
+
+	cb = new0(struct bt_bass_io_cb, 1);
+	cb->id = ++id ? id : ++id;
+	cb->listen = listen;
+	cb->accept = accept;
+	cb->io_destroy = io_destroy;
+
+	queue_push_tail(bass->io_cbs, cb);
+
+	return cb->id;
+}
+
+static bool match_cb_id(const void *data, const void *match_data)
+{
+	const struct bt_bass_io_cb *cb = data;
+	unsigned int id = PTR_TO_UINT(match_data);
+
+	return (cb->id == id);
+}
+
+bool bt_bass_io_cb_unregister(struct bt_bass *bass,
+				unsigned int id)
+{
+	struct bt_bass_io_cb *cb;
+
+	if (!bass)
+		return false;
+
+	cb = queue_remove_if(bass->io_cbs, match_cb_id,
+						UINT_TO_PTR(id));
+	if (!cb)
+		return false;
+
+	free(cb);
+
+	return true;
+}
diff --git a/src/shared/bass.h b/src/shared/bass.h
index c4b5b76ba..db28628e7 100644
--- a/src/shared/bass.h
+++ b/src/shared/bass.h
@@ -8,6 +8,7 @@ 
  */
 
 struct bt_bass;
+struct bt_bass_io;
 
 #define NUM_BCAST_RECV_STATES				2
 #define BT_BASS_BCAST_CODE_SIZE				16
@@ -32,6 +33,9 @@  struct bt_bass;
 #define BT_BASS_BIG_ENC_STATE_DEC			0x02
 #define BT_BASS_BIG_ENC_STATE_BAD_CODE			0x03
 
+typedef void (*bt_bass_confirm_func_t)(void *user_data);
+typedef void (*bt_bass_connect_func_t)(void *user_data);
+
 /* BASS subgroup field of the Broadcast
  * Receive State characteristic
  */
@@ -53,12 +57,13 @@  struct bt_bcast_src {
 	uint32_t bid;
 	uint8_t sync_state;
 	uint8_t enc;
+	uint8_t bcode[BT_BASS_BCAST_CODE_SIZE];
 	uint8_t bad_code[BT_BASS_BCAST_CODE_SIZE];
 	uint8_t num_subgroups;
 	struct bt_bass_subgroup_data *subgroup_data;
-	GIOChannel *listen_io;
-	GIOChannel *pa_sync_io;
-	struct queue *bises;
+	struct bt_bass_io *io;
+	bt_bass_confirm_func_t confirm_cb;
+	bt_bass_connect_func_t connect_cb;
 };
 
 /* Broadcast Audio Scan Control Point
@@ -120,6 +125,11 @@  typedef void (*bt_bass_func_t)(struct bt_bass *bass, void *user_data);
 typedef void (*bt_bass_destroy_func_t)(void *user_data);
 typedef void (*bt_bass_debug_func_t)(const char *str, void *user_data);
 
+typedef int (*bt_bass_listen_func_t)(struct bt_bcast_src *bcast_src,
+				const bdaddr_t *src);
+typedef int (*bt_bass_accept_func_t)(struct bt_bcast_src *bcast_src);
+typedef void (*bt_bass_io_destroy_func_t)(struct bt_bcast_src *bcast_src);
+
 struct bt_att *bt_bass_get_att(struct bt_bass *bass);
 unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached,
 							void *user_data);
@@ -134,3 +144,10 @@  bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client);
 bool bt_bass_set_att(struct bt_bass *bass, struct bt_att *att);
 void bt_bass_detach(struct bt_bass *bass);
 void bt_bass_add_db(struct gatt_db *db, const bdaddr_t *adapter_bdaddr);
+
+unsigned int bt_bass_io_cb_register(struct bt_bass *bass,
+				bt_bass_listen_func_t listen,
+				bt_bass_accept_func_t accept,
+				bt_bass_io_destroy_func_t io_destroy);
+bool bt_bass_io_cb_unregister(struct bt_bass *bass,
+				unsigned int id);