Bluetooth: Audio: ASCS: Make ASE Allocation Dynamic

Decoupled the ASEs from the ASCS Session and made them dynamically
allocated instead. A Kconfig option was added to set the maximum
number of active ASEs at a single time. The intent here is to allow
the developer greater control over memory usage; this fix addresses
one of the largest ram usages in le audio.

Signed-off-by: Fredrik Danebjer <fredrik@danebjer.com>
This commit is contained in:
Fredrik Danebjer 2022-12-14 08:50:42 +01:00 committed by Carles Cufí
commit 18a889fb8b
3 changed files with 160 additions and 84 deletions

View file

@ -35,4 +35,13 @@ config BT_ASCS_ASE_SNK
config BT_ASCS_ASE_SRC
def_bool BT_ASCS_ASE_SRC_COUNT > 0
select BT_PAC_SRC
config BT_ASCS_MAX_ACTIVE_ASES
int "Number of simultaneously supported ASE sessions"
default BT_ISO_MAX_CHAN
range 1 65535
help
The number of simultanesouly supported active ASEs, in particular
meaning the number of ASEs that are allowed to be in a non-idle state at
a single time.
endif # BT_ASCS

View file

@ -39,6 +39,14 @@ LOG_MODULE_REGISTER(bt_ascs, CONFIG_BT_ASCS_LOG_LEVEL);
#include "pacs_internal.h"
#include "cap_internal.h"
#define MAX_ASES_SESSIONS CONFIG_BT_MAX_CONN * \
(CONFIG_BT_ASCS_ASE_SNK_COUNT + \
CONFIG_BT_ASCS_ASE_SRC_COUNT)
BUILD_ASSERT(CONFIG_BT_ASCS_MAX_ACTIVE_ASES <= MAX(MAX_ASES_SESSIONS,
CONFIG_BT_ISO_MAX_CHAN),
"Max active ASEs are set to more than actual number of ASEs or ISOs");
#if defined(CONFIG_BT_AUDIO_UNICAST_SERVER)
#define ASE_ID(_ase) ase->ep.status.id
@ -52,17 +60,43 @@ struct bt_ascs_ase {
struct bt_ascs *ascs;
struct bt_audio_ep ep;
const struct bt_gatt_attr *attr;
sys_snode_t node;
};
struct bt_ascs {
struct bt_conn *conn;
struct bt_ascs_ase ases[ASE_COUNT];
sys_slist_t ases;
};
K_MEM_SLAB_DEFINE(ase_slab, sizeof(struct bt_ascs_ase),
CONFIG_BT_ASCS_MAX_ACTIVE_ASES,
__alignof__(struct bt_ascs_ase));
static struct bt_ascs sessions[CONFIG_BT_MAX_CONN];
static int control_point_notify(struct bt_conn *conn, const void *data, uint16_t len);
static void bt_ascs_ase_return_to_slab(struct bt_ascs_ase *ase)
{
__ASSERT(ase && ase->ascs, "Non-existing ASE or ASCS");
LOG_DBG("Returning ase %p to slab", ase);
sys_slist_find_and_remove(&ase->ascs->ases, &ase->node);
k_mem_slab_free(&ase_slab, (void **)&ase);
}
static struct bt_ascs_ase *bt_ascs_ase_get_from_slab(void)
{
struct bt_ascs_ase *ase = NULL;
if (k_mem_slab_alloc(&ase_slab, (void **)&ase, K_NO_WAIT) < 0) {
LOG_DBG("Could not get ASE from slab, out of memory");
}
return ase;
}
static void ase_status_changed(struct bt_audio_ep *ep, uint8_t old_state,
uint8_t state)
{
@ -105,6 +139,10 @@ void ascs_ep_set_state(struct bt_audio_ep *ep, uint8_t state)
if (ops->released != NULL) {
ops->released(stream);
}
struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep);
/* Return the ase to slab */
bt_ascs_ase_return_to_slab(ase);
break;
case BT_AUDIO_EP_STATE_CODEC_CONFIGURED:
@ -353,6 +391,26 @@ static void ascs_ep_get_status_enable(struct bt_audio_ep *ep,
LOG_DBG("dir 0x%02x cig 0x%02x cis 0x%02x", ep->dir, ep->cig_id, ep->cis_id);
}
static int ascs_ep_get_status_idle(uint8_t ase_id, struct net_buf_simple *buf)
{
struct bt_ascs_ase_status *status;
if (!buf || ase_id > ASE_COUNT) {
return -EINVAL;
}
net_buf_simple_reset(buf);
status = net_buf_simple_add(buf, sizeof(*status));
status->id = ase_id;
status->state = BT_AUDIO_EP_STATE_IDLE;
LOG_DBG("id 0x%02x state %s", ase_id,
bt_audio_ep_state_str(status->state));
return 0;
}
static int ascs_ep_get_status(struct bt_audio_ep *ep,
struct net_buf_simple *buf)
{
@ -823,12 +881,19 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
return;
}
for (size_t i = 0; i < ARRAY_SIZE(session->ases); i++) {
struct bt_ascs_ase *ase = &session->ases[i];
struct bt_audio_stream *stream = ase->ep.stream;
sys_snode_t *ase_node, *s;
SYS_SLIST_FOR_EACH_NODE_SAFE(&session->ases, ase_node, s) {
struct bt_audio_stream *stream;
struct bt_ascs_ase *ase;
ase = CONTAINER_OF(ase_node, struct bt_ascs_ase, node);
stream = ase->ep.stream;
if (ase->ep.status.state != BT_AUDIO_EP_STATE_IDLE) {
/* ase_process will handle the final state transition into idle state */
/* ase_process will handle the final state transition into idle
* state, where the ase finally will be deallocated
*/
ase_release(ase);
}
@ -997,7 +1062,6 @@ void ascs_ep_init(struct bt_audio_ep *ep, uint8_t id)
static void ase_init(struct bt_ascs_ase *ase, uint8_t id)
{
memset(ase, 0, sizeof(*ase));
ascs_ep_init(&ase->ep, id);
/* Lookup ASE characteristic */
@ -1009,30 +1073,19 @@ static void ase_init(struct bt_ascs_ase *ase, uint8_t id)
static struct bt_ascs_ase *ase_new(struct bt_ascs *ascs, uint8_t id)
{
struct bt_ascs_ase *ase;
int i;
if (id) {
if (id > ASE_COUNT) {
return NULL;
}
i = id;
ase = &ascs->ases[i - 1];
goto done;
if (!id || id > ASE_COUNT) {
return NULL;
}
for (i = 0; i < ASE_COUNT; i++) {
ase = &ascs->ases[i];
if (!ase->ep.status.id) {
i++;
goto done;
}
ase = bt_ascs_ase_get_from_slab();
if (!ase) {
return NULL;
}
return NULL;
sys_slist_append(&ascs->ases, &ase->node);
done:
ase_init(ase, i);
ase_init(ase, id);
ase->ascs = ascs;
return ase;
@ -1040,15 +1093,14 @@ done:
static struct bt_ascs_ase *ase_find(struct bt_ascs *ascs, uint8_t id)
{
struct bt_ascs_ase *ase;
sys_snode_t *ase_node;
if (!id || id > ASE_COUNT) {
return NULL;
}
SYS_SLIST_FOR_EACH_NODE(&ascs->ases, ase_node) {
struct bt_ascs_ase *ase = CONTAINER_OF(ase_node, struct bt_ascs_ase, node);
ase = &ascs->ases[id - 1];
if (ase->ep.status.id == id) {
return ase;
if (ase->ep.status.id == id) {
return ase;
}
}
return NULL;
@ -1072,16 +1124,25 @@ static ssize_t ascs_ase_read(struct bt_conn *conn,
{
struct bt_ascs *ascs = ascs_get(conn);
struct bt_ascs_ase *ase;
uint8_t ase_id;
LOG_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len, offset);
ase = ase_get(ascs, POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr)));
if (!ase) {
LOG_ERR("Unable to get ASE");
ase_id = POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr));
if (ase_id > ASE_COUNT) {
LOG_ERR("Unable to get ASE, id out of range");
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
ascs_ep_get_status(&ase->ep, &ase_buf);
ase = ase_find(ascs, ase_id);
/* If NULL, we haven't assigned an ASE, this also means that we are currently in IDLE */
if (!ase) {
ascs_ep_get_status_idle(ase_id, &ase_buf);
} else {
ascs_ep_get_status(&ase->ep, &ase_buf);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, ase_buf.data,
ase_buf.len);
@ -1367,16 +1428,19 @@ static ssize_t ascs_config(struct bt_ascs *ascs, struct net_buf_simple *buf)
LOG_DBG("ase 0x%02x cc_len %u", cfg->ase, cfg->cc_len);
if (cfg->ase) {
ase = ase_get(ascs, cfg->ase);
if (!cfg->ase || cfg->ase > ASE_COUNT) {
LOG_WRN("Invalid ASE ID: %u", cfg->ase);
ascs_cp_rsp_add(cfg->ase, BT_ASCS_CONFIG_OP,
BT_ASCS_RSP_INVALID_ASE, 0x00);
continue;
} else {
ase = ase_new(ascs, 0);
ase = ase_get(ascs, cfg->ase);
}
if (!ase) {
ascs_cp_rsp_add(cfg->ase, BT_ASCS_CONFIG_OP,
BT_ASCS_RSP_INVALID_ASE, 0x00);
LOG_WRN("Unknown ase 0x%02x", cfg->ase);
BT_ASCS_RSP_NO_MEM, 0x00);
LOG_WRN("No free ASE found for config ASE ID 0x%02x", cfg->ase);
continue;
}

View file

@ -57,8 +57,11 @@ static const struct bt_uuid *ase_snk_uuid = BT_UUID_ASCS_ASE_SNK;
static const struct bt_uuid *ase_src_uuid = BT_UUID_ASCS_ASE_SRC;
static const struct bt_uuid *cp_uuid = BT_UUID_ASCS_ASE_CP;
static struct bt_unicast_client_ep snks[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SNK_COUNT];
static struct bt_unicast_client_ep srcs[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SRC_COUNT];
static struct bt_unicast_client_ep snks[CONFIG_BT_MAX_CONN]
[CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SNK_COUNT];
static struct bt_unicast_client_ep srcs[CONFIG_BT_MAX_CONN]
[CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SRC_COUNT];
static struct bt_gatt_subscribe_params cp_subscribe[CONFIG_BT_MAX_CONN];
static struct bt_gatt_subscribe_params snk_loc_subscribe[CONFIG_BT_MAX_CONN];
@ -235,14 +238,14 @@ static struct bt_iso_chan_ops unicast_client_iso_ops = {
static void unicast_client_ep_init(struct bt_audio_ep *ep, uint16_t handle,
uint8_t dir)
{
struct bt_unicast_client_ep *client;
struct bt_unicast_client_ep *client_ep;
LOG_DBG("ep %p dir 0x%02x handle 0x%04x", ep, dir, handle);
client = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
client_ep = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
(void)memset(ep, 0, sizeof(*ep));
client->handle = handle;
client_ep->handle = handle;
ep->status.id = 0U;
ep->dir = dir;
}
@ -256,20 +259,20 @@ static struct bt_audio_ep *unicast_client_ep_find(struct bt_conn *conn,
index = bt_conn_index(conn);
for (i = 0; i < ARRAY_SIZE(snks[index]); i++) {
struct bt_unicast_client_ep *client = &snks[index][i];
struct bt_unicast_client_ep *client_ep = &snks[index][i];
if ((handle && client->handle == handle) ||
(!handle && client->handle)) {
return &client->ep;
if ((handle && client_ep->handle == handle) ||
(!handle && client_ep->handle)) {
return &client_ep->ep;
}
}
for (i = 0; i < ARRAY_SIZE(srcs[index]); i++) {
struct bt_unicast_client_ep *client = &srcs[index][i];
struct bt_unicast_client_ep *client_ep = &srcs[index][i];
if ((handle && client->handle == handle) ||
(!handle && client->handle)) {
return &client->ep;
if ((handle && client_ep->handle == handle) ||
(!handle && client_ep->handle)) {
return &client_ep->ep;
}
}
@ -316,11 +319,11 @@ static struct bt_audio_ep *unicast_client_ep_new(struct bt_conn *conn,
}
for (i = 0; i < size; i++) {
struct bt_unicast_client_ep *client = &cache[i];
struct bt_unicast_client_ep *client_ep = &cache[i];
if (!client->handle) {
unicast_client_ep_init(&client->ep, handle, dir);
return &client->ep;
if (!client_ep->handle) {
unicast_client_ep_init(&client_ep->ep, handle, dir);
return &client_ep->ep;
}
}
@ -678,7 +681,7 @@ static void unicast_client_ep_set_status(struct bt_audio_ep *ep,
struct net_buf_simple *buf)
{
struct bt_ascs_ase_status *status;
struct bt_unicast_client_ep *client;
struct bt_unicast_client_ep *client_ep;
bool state_changed;
uint8_t old_state;
@ -686,7 +689,7 @@ static void unicast_client_ep_set_status(struct bt_audio_ep *ep,
return;
}
client = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
client_ep = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
status = net_buf_simple_pull_mem(buf, sizeof(*status));
@ -694,7 +697,7 @@ static void unicast_client_ep_set_status(struct bt_audio_ep *ep,
ep->status = *status;
state_changed = old_state != ep->status.state;
LOG_DBG("ep %p handle 0x%04x id 0x%02x dir %u state %s -> %s", ep, client->handle,
LOG_DBG("ep %p handle 0x%04x id 0x%02x dir %u state %s -> %s", ep, client_ep->handle,
status->id, ep->dir, bt_audio_ep_state_str(old_state),
bt_audio_ep_state_str(status->state));
@ -1083,11 +1086,11 @@ static uint8_t unicast_client_ep_notify(struct bt_conn *conn,
const void *data, uint16_t length)
{
struct net_buf_simple buf;
struct bt_unicast_client_ep *client;
struct bt_unicast_client_ep *client_ep;
client = CONTAINER_OF(params, struct bt_unicast_client_ep, subscribe);
client_ep = CONTAINER_OF(params, struct bt_unicast_client_ep, subscribe);
LOG_DBG("conn %p ep %p len %u", conn, &client->ep, length);
LOG_DBG("conn %p ep %p len %u", conn, &client_ep->ep, length);
if (!data) {
LOG_DBG("Unsubscribed");
@ -1102,7 +1105,7 @@ static uint8_t unicast_client_ep_notify(struct bt_conn *conn,
return BT_GATT_ITER_STOP;
}
unicast_client_ep_set_status(&client->ep, &buf);
unicast_client_ep_set_status(&client_ep->ep, &buf);
return BT_GATT_ITER_CONTINUE;
}
@ -1110,25 +1113,25 @@ static uint8_t unicast_client_ep_notify(struct bt_conn *conn,
static int unicast_client_ep_subscribe(struct bt_conn *conn,
struct bt_audio_ep *ep)
{
struct bt_unicast_client_ep *client;
struct bt_unicast_client_ep *client_ep;
client = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
client_ep = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
LOG_DBG("ep %p handle 0x%02x", ep, client->handle);
LOG_DBG("ep %p handle 0x%02x", ep, client_ep->handle);
if (client->subscribe.value_handle) {
if (client_ep->subscribe.value_handle) {
return 0;
}
client->subscribe.value_handle = client->handle;
client->subscribe.ccc_handle = 0x0000;
client->subscribe.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
client->subscribe.disc_params = &client->discover;
client->subscribe.notify = unicast_client_ep_notify;
client->subscribe.value = BT_GATT_CCC_NOTIFY;
atomic_set_bit(client->subscribe.flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE);
client_ep->subscribe.value_handle = client_ep->handle;
client_ep->subscribe.ccc_handle = 0x0000;
client_ep->subscribe.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
client_ep->subscribe.disc_params = &client_ep->discover;
client_ep->subscribe.notify = unicast_client_ep_notify;
client_ep->subscribe.value = BT_GATT_CCC_NOTIFY;
atomic_set_bit(client_ep->subscribe.flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE);
return bt_gatt_subscribe(conn, &client->subscribe);
return bt_gatt_subscribe(conn, &client_ep->subscribe);
}
static void unicast_client_ep_set_cp(struct bt_conn *conn, uint16_t handle)
@ -1154,18 +1157,18 @@ static void unicast_client_ep_set_cp(struct bt_conn *conn, uint16_t handle)
}
for (i = 0; i < ARRAY_SIZE(snks[index]); i++) {
struct bt_unicast_client_ep *client = &snks[index][i];
struct bt_unicast_client_ep *client_ep = &snks[index][i];
if (client->handle) {
client->cp_handle = handle;
if (client_ep->handle) {
client_ep->cp_handle = handle;
}
}
for (i = 0; i < ARRAY_SIZE(srcs[index]); i++) {
struct bt_unicast_client_ep *client = &srcs[index][i];
struct bt_unicast_client_ep *client_ep = &srcs[index][i];
if (client->handle) {
client->cp_handle = handle;
if (client_ep->handle) {
client_ep->cp_handle = handle;
}
}
}
@ -1449,11 +1452,11 @@ static int unicast_client_ep_release(struct bt_audio_ep *ep,
int bt_unicast_client_ep_send(struct bt_conn *conn, struct bt_audio_ep *ep,
struct net_buf_simple *buf)
{
struct bt_unicast_client_ep *client = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
struct bt_unicast_client_ep *client_ep = CONTAINER_OF(ep, struct bt_unicast_client_ep, ep);
LOG_DBG("conn %p ep %p buf %p len %u", conn, ep, buf, buf->len);
return bt_gatt_write_without_response(conn, client->cp_handle,
return bt_gatt_write_without_response(conn, client_ep->cp_handle,
buf->data, buf->len, false);
}