Bluetooth: BAP: Add unicast client and server write long support

Add support for long writes for the unicast client and server.
This reuses the ATT buffer for long reads.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
diff --git a/samples/bluetooth/hap_ha/prj.conf b/samples/bluetooth/hap_ha/prj.conf
index 93384c4..eda9951 100644
--- a/samples/bluetooth/hap_ha/prj.conf
+++ b/samples/bluetooth/hap_ha/prj.conf
@@ -11,6 +11,9 @@
 # Appearance: Generic Hearing aid (0x0A40)
 CONFIG_BT_DEVICE_APPEARANCE=2624
 
+# Mandatory to support at least 1 for ASCS
+CONFIG_BT_ATT_PREPARE_COUNT=1
+
 CONFIG_BT_AUDIO=y
 CONFIG_BT_BAP_UNICAST_SERVER=y
 CONFIG_BT_ASCS_ASE_SNK_COUNT=1
diff --git a/samples/bluetooth/tmap_peripheral/prj.conf b/samples/bluetooth/tmap_peripheral/prj.conf
index 9750e0c..b821ba0 100644
--- a/samples/bluetooth/tmap_peripheral/prj.conf
+++ b/samples/bluetooth/tmap_peripheral/prj.conf
@@ -17,6 +17,8 @@
 
 # BAP support
 CONFIG_BT_BAP_UNICAST_SERVER=y
+# Mandatory to support at least 1 for ASCS
+CONFIG_BT_ATT_PREPARE_COUNT=1
 
 # VCP support
 CONFIG_BT_VCP_VOL_REND=y
diff --git a/samples/bluetooth/unicast_audio_server/prj.conf b/samples/bluetooth/unicast_audio_server/prj.conf
index e87b46b..7963da2 100644
--- a/samples/bluetooth/unicast_audio_server/prj.conf
+++ b/samples/bluetooth/unicast_audio_server/prj.conf
@@ -8,5 +8,8 @@
 # Support an ISO channel per ASE
 CONFIG_BT_ISO_MAX_CHAN=4
 
+# Mandatory to support at least 1 for ASCS
+CONFIG_BT_ATT_PREPARE_COUNT=1
+
 CONFIG_BT_EXT_ADV=y
 CONFIG_BT_DEVICE_NAME="Unicast Audio Server"
diff --git a/subsys/bluetooth/audio/ascs.c b/subsys/bluetooth/audio/ascs.c
index a41d118..39902a1 100644
--- a/subsys/bluetooth/audio/ascs.c
+++ b/subsys/bluetooth/audio/ascs.c
@@ -27,6 +27,8 @@
 #include "common/bt_str.h"
 #include "common/assert.h"
 
+#include "../host/att_internal.h"
+
 #include "audio_internal.h"
 #include "bap_iso.h"
 #include "bap_endpoint.h"
@@ -80,6 +82,19 @@
 			 MAX(MIN_CONFIG_STATE_SIZE + MAX_CODEC_CONFIG, \
 			     MIN_QOS_STATE_SIZE + MAX_METADATA))
 
+/* Verify that the prepare count is large enough to cover the maximum value we support a client
+ * writing
+ */
+BUILD_ASSERT(
+	BT_ATT_BUF_SIZE - 3 >= ASE_BUF_SIZE ||
+		DIV_ROUND_UP(ASE_BUF_SIZE, (BT_ATT_BUF_SIZE - 3)) <= CONFIG_BT_ATT_PREPARE_COUNT,
+	"CONFIG_BT_ATT_PREPARE_COUNT not large enough to cover the maximum supported ASCS value");
+
+/* It is mandatory to support long writes in ASCS unconditionally, and thus
+ * CONFIG_BT_ATT_PREPARE_COUNT must be at least 1 to support the feature
+ */
+BUILD_ASSERT(CONFIG_BT_ATT_PREPARE_COUNT > 0, "CONFIG_BT_ATT_PREPARE_COUNT shall be at least 1");
+
 static const struct bt_bap_unicast_server_cb *unicast_server_cb;
 
 static K_SEM_DEFINE(ase_buf_sem, 1, 1);
diff --git a/subsys/bluetooth/audio/bap_unicast_client.c b/subsys/bluetooth/audio/bap_unicast_client.c
index 07790ef..d66afe0 100644
--- a/subsys/bluetooth/audio/bap_unicast_client.c
+++ b/subsys/bluetooth/audio/bap_unicast_client.c
@@ -92,6 +92,7 @@
 	union {
 		struct bt_gatt_read_params read_params;
 		struct bt_gatt_discover_params disc_params;
+		struct bt_gatt_write_params write_params;
 	};
 
 	/* The att_buf needs to use the maximum ATT attribute size as a single
@@ -129,7 +130,11 @@
 	struct net_buf_simple *buf;
 	int err;
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_START_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(ep->stream->conn, BT_ASCS_START_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	req = net_buf_simple_add(buf, sizeof(*req));
 	req->num_ases = 1U;
@@ -1556,19 +1561,22 @@
 	}
 }
 
-NET_BUF_SIMPLE_DEFINE_STATIC(ep_buf, CONFIG_BT_L2CAP_TX_MTU);
-
-struct net_buf_simple *bt_bap_unicast_client_ep_create_pdu(uint8_t op)
+struct net_buf_simple *bt_bap_unicast_client_ep_create_pdu(struct bt_conn *conn, uint8_t op)
 {
+	struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)];
 	struct bt_ascs_ase_cp *hdr;
 
-	/* Reset buffer before using */
-	net_buf_simple_reset(&ep_buf);
+	if (client->busy) {
+		return NULL;
+	}
 
-	hdr = net_buf_simple_add(&ep_buf, sizeof(*hdr));
+	/* Reset buffer before using */
+	reset_att_buf(client);
+
+	hdr = net_buf_simple_add(&client->net_buf, sizeof(*hdr));
 	hdr->op = op;
 
-	return &ep_buf;
+	return &client->net_buf;
 }
 
 static int unicast_client_ep_config(struct bt_bap_ep *ep, struct net_buf_simple *buf,
@@ -1822,16 +1830,60 @@
 	return 0;
 }
 
+static void gatt_write_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
+{
+	struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)];
+
+	LOG_DBG("conn %p err %u", conn, err);
+
+	memset(params, 0, sizeof(*params));
+	client->busy = false;
+
+	/* TBD: Should we do anything in case of error here? */
+}
+
 int bt_bap_unicast_client_ep_send(struct bt_conn *conn, struct bt_bap_ep *ep,
 				  struct net_buf_simple *buf)
 {
+	const uint8_t att_write_header_size = 3; /* opcode (1) + handle (2) */
+	const uint16_t max_write_size = bt_gatt_get_mtu(conn) - att_write_header_size;
+	struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)];
 	struct bt_bap_unicast_client_ep *client_ep =
 		CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep);
+	int err;
 
 	LOG_DBG("conn %p ep %p buf %p len %u", conn, ep, buf, buf->len);
 
-	return bt_gatt_write_without_response(conn, client_ep->cp_handle, buf->data, buf->len,
-					      false);
+	if (buf->len > max_write_size) {
+		if (client->busy) {
+			LOG_DBG("Client connection is busy");
+			return -EBUSY;
+		}
+
+		client->write_params.func = gatt_write_cb;
+		client->write_params.handle = client_ep->cp_handle;
+		client->write_params.offset = 0U;
+		client->write_params.data = buf->data;
+		client->write_params.length = buf->len;
+#if defined(CONFIG_BT_EATT)
+		client->write_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
+#endif /* CONFIG_BT_EATT */
+
+		err = bt_gatt_write(conn, &client->write_params);
+		if (err != 0) {
+			LOG_DBG("bt_gatt_write failed: %d", err);
+		}
+
+		client->busy = true;
+	} else {
+		err = bt_gatt_write_without_response(conn, client_ep->cp_handle, buf->data,
+						     buf->len, false);
+		if (err != 0) {
+			LOG_DBG("bt_gatt_write_without_response failed: %d", err);
+		}
+	}
+
+	return err;
 }
 
 static void unicast_client_reset(struct bt_bap_ep *ep)
@@ -2523,7 +2575,11 @@
 	struct net_buf_simple *buf;
 	int err;
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_CONFIG_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_CONFIG_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	op = net_buf_simple_add(buf, sizeof(*op));
 	op->num_ases = 0x01;
@@ -2616,7 +2672,11 @@
 	}
 
 	/* Generate the control point write */
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_QOS_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(conn, BT_ASCS_QOS_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	op = net_buf_simple_add(buf, sizeof(*op));
 
@@ -2668,7 +2728,11 @@
 
 	LOG_DBG("stream %p", stream);
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_ENABLE_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_ENABLE_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	req = net_buf_simple_add(buf, sizeof(*req));
 	req->num_ases = 0x01;
@@ -2691,7 +2755,11 @@
 
 	LOG_DBG("stream %p", stream);
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_METADATA_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_METADATA_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	req = net_buf_simple_add(buf, sizeof(*req));
 	req->num_ases = 0x01;
@@ -2713,7 +2781,11 @@
 
 	LOG_DBG("stream %p", stream);
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_START_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_START_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	req = net_buf_simple_add(buf, sizeof(*req));
 	req->num_ases = 0x00;
@@ -2765,7 +2837,11 @@
 
 	LOG_DBG("stream %p", stream);
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_DISABLE_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_DISABLE_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	req = net_buf_simple_add(buf, sizeof(*req));
 	req->num_ases = 0x01;
@@ -2787,7 +2863,11 @@
 
 	LOG_DBG("stream %p", stream);
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_STOP_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_STOP_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	req = net_buf_simple_add(buf, sizeof(*req));
 	req->num_ases = 0x00;
@@ -2822,7 +2902,11 @@
 		return -ENOTCONN;
 	}
 
-	buf = bt_bap_unicast_client_ep_create_pdu(BT_ASCS_RELEASE_OP);
+	buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_RELEASE_OP);
+	if (buf == NULL) {
+		LOG_DBG("Could not create PDU");
+		return -EBUSY;
+	}
 
 	req = net_buf_simple_add(buf, sizeof(*req));
 	req->num_ases = 0x01;
diff --git a/subsys/bluetooth/audio/bap_unicast_client_internal.h b/subsys/bluetooth/audio/bap_unicast_client_internal.h
index db6b482..cb8f350 100644
--- a/subsys/bluetooth/audio/bap_unicast_client_internal.h
+++ b/subsys/bluetooth/audio/bap_unicast_client_internal.h
@@ -24,7 +24,7 @@
 
 int bt_bap_unicast_client_release(struct bt_bap_stream *stream);
 
-struct net_buf_simple *bt_bap_unicast_client_ep_create_pdu(uint8_t op);
+struct net_buf_simple *bt_bap_unicast_client_ep_create_pdu(struct bt_conn *conn, uint8_t op);
 
 int bt_bap_unicast_client_ep_qos(struct bt_bap_ep *ep, struct net_buf_simple *buf,
 				 struct bt_codec_qos *qos);
diff --git a/subsys/bluetooth/host/att_internal.h b/subsys/bluetooth/host/att_internal.h
index b3d052e..7f81b18 100644
--- a/subsys/bluetooth/host/att_internal.h
+++ b/subsys/bluetooth/host/att_internal.h
@@ -1,5 +1,7 @@
 /* att_internal.h - Attribute protocol handling */
 
+#include <zephyr/bluetooth/l2cap.h>
+
 /*
  * Copyright (c) 2015-2016 Intel Corporation
  *
diff --git a/tests/bluetooth/audio/ascs/prj.conf b/tests/bluetooth/audio/ascs/prj.conf
index ed2d370..d7a8a6e 100644
--- a/tests/bluetooth/audio/ascs/prj.conf
+++ b/tests/bluetooth/audio/ascs/prj.conf
@@ -12,6 +12,9 @@
 CONFIG_BT_BAP_UNICAST_SERVER=y
 CONFIG_BT_ISO_MAX_CHAN=2
 
+# Mandatory to support at least 1 for ASCS
+CONFIG_BT_ATT_PREPARE_COUNT=1
+
 CONFIG_BT_DEBUG_LOG=y
 CONFIG_BT_ASCS_LOG_LEVEL_DBG=y
 CONFIG_BT_BAP_ISO_LOG_LEVEL_DBG=y
diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf
index 2a76107..db9643b 100644
--- a/tests/bluetooth/shell/audio.conf
+++ b/tests/bluetooth/shell/audio.conf
@@ -16,7 +16,7 @@
 CONFIG_BT_EATT=y
 CONFIG_BT_SIGNING=y
 CONFIG_BT_FIXED_PASSKEY=y
-CONFIG_BT_ATT_PREPARE_COUNT=2
+CONFIG_BT_ATT_PREPARE_COUNT=5
 CONFIG_BT_SHELL=y
 CONFIG_BT_DEVICE_NAME="audio test shell"
 CONFIG_BT_DEVICE_NAME_DYNAMIC=y
diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf
index 664819d..15afe27 100644
--- a/tests/bsim/bluetooth/audio/prj.conf
+++ b/tests/bsim/bluetooth/audio/prj.conf
@@ -8,6 +8,7 @@
 CONFIG_BT_DEVICE_NAME="bsim_test_audio"
 # TBS Client may require up to 12 buffers
 CONFIG_BT_L2CAP_TX_BUF_COUNT=12
+CONFIG_BT_ATT_PREPARE_COUNT=5
 CONFIG_BT_MAX_CONN=5
 CONFIG_BT_MAX_PAIRED=5
 CONFIG_BT_GATT_DYNAMIC_DB=y