Bluetooth: controller: Implements ISO Test Mode HCI commands

Implements the flollowing HCI commands:
- HCI_LE_ISO_Transmit_Test
- HCI_LE_ISO_Receive_Test
- HCI_LE_ISO_Read_Test_Counters
- HCI_LE_ISO_Test_End

Signed-off-by: Morten Priess <mtpr@oticon.com>
This commit is contained in:
Morten Priess 2022-06-30 16:00:00 +02:00 committed by Fabio Baltieri
commit da402c0830
12 changed files with 650 additions and 29 deletions

View file

@ -2008,6 +2008,10 @@ struct bt_hci_rp_le_remove_iso_path {
uint16_t handle;
} __packed;
#define BT_HCI_ISO_TEST_ZERO_SIZE_SDU 0
#define BT_HCI_ISO_TEST_VARIABLE_SIZE_SDU 1
#define BT_HCI_ISO_TEST_MAX_SIZE_SDU 2
#define BT_HCI_OP_LE_ISO_TRANSMIT_TEST BT_OP(BT_OGF_LE, 0x0070)
struct bt_hci_cp_le_iso_transmit_test {
uint16_t handle;

View file

@ -1060,6 +1060,11 @@ void isoal_source_disable(isoal_source_handle_t hdl)
isoal_global.source_state[hdl].pdu_production.mode = ISOAL_PRODUCTION_MODE_DISABLED;
}
struct isoal_source *isoal_source_get(isoal_source_handle_t hdl)
{
return &isoal_global.source_state[hdl];
}
/**
* @brief Disable and deallocate existing source
* @param hdl[in] Handle of existing instance

View file

@ -431,6 +431,8 @@ void isoal_source_enable(isoal_source_handle_t hdl);
void isoal_source_disable(isoal_source_handle_t hdl);
struct isoal_source *isoal_source_get(isoal_source_handle_t hdl);
void isoal_source_destroy(isoal_source_handle_t hdl);
isoal_status_t isoal_tx_sdu_fragment(isoal_source_handle_t source_hdl,

View file

@ -6387,9 +6387,6 @@ static uint8_t cis_req_recv(struct ll_conn *conn, memq_link_t *link,
void *node;
conn->llcp_cis.cig_id = req->cig_id;
conn->llcp_cis.c_max_sdu = (uint16_t)(req->c_max_sdu_packed[1] & 0x0F) << 8 |
req->c_max_sdu_packed[0];
conn->llcp_cis.p_max_sdu = (uint16_t)(req->p_max_sdu[1] & 0x0F) << 8 | req->p_max_sdu[0];
conn->llcp_cis.cis_offset_min = sys_get_le24(req->cis_offset_min);
conn->llcp_cis.cis_offset_max = sys_get_le24(req->cis_offset_max);
conn->llcp_cis.conn_event_count = sys_le16_to_cpu(req->conn_event_count);

View file

@ -25,6 +25,7 @@
#include "isoal.h"
#include "ull_iso_types.h"
#include "ull_iso_internal.h"
#include "ull_conn_types.h"
#include "lll_conn_iso.h"
#include "ull_conn_iso_types.h"
@ -109,8 +110,7 @@ struct ll_conn_iso_stream *ll_conn_iso_stream_acquire(void)
struct ll_conn_iso_stream *cis = mem_acquire(&cis_free);
if (cis) {
cis->hdr.datapath_in = NULL;
cis->hdr.datapath_out = NULL;
(void)memset(&cis->hdr, 0U, sizeof(cis->hdr));
}
return cis;
@ -752,3 +752,61 @@ static void disable(uint16_t handle)
cig->lll.handle = LLL_HANDLE_INVALID;
cig->lll.resume_cis = LLL_HANDLE_INVALID;
}
/* An ISO interval has elapsed for a Connected Isochronous Group */
void ull_conn_iso_transmit_test_cig_interval(uint16_t handle, uint32_t ticks_at_expire)
{
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
uint32_t sdu_interval;
uint32_t iso_interval;
uint16_t handle_iter;
uint64_t sdu_counter;
uint8_t tx_sdu_count;
cig = ll_conn_iso_group_get(handle);
LL_ASSERT(cig);
handle_iter = UINT16_MAX;
if (cig->lll.role) {
/* Peripheral */
sdu_interval = cig->p_sdu_interval;
} else {
/* Central */
sdu_interval = cig->c_sdu_interval;
}
iso_interval = cig->iso_interval * PERIODIC_INT_UNIT_US;
/* Handle ISO Transmit Test for all active CISes in the group */
for (uint8_t i = 0; i < cig->lll.num_cis; i++) {
cis = ll_conn_iso_stream_get_by_group(cig, &handle_iter);
LL_ASSERT(cis);
if (!cis->hdr.test_mode.tx_enabled || cis->lll.handle == LLL_HANDLE_INVALID) {
continue;
}
/* Calculate number of SDUs to transmit in the next ISO event. Ensure no overflow
* on 64-bit sdu_counter:
* (39 bits x 22 bits (4x10^6 us) = 61 bits / 8 bits (255 us) = 53 bits)
*/
sdu_counter = ceiling_fraction((cis->lll.event_count + 1U) * iso_interval,
sdu_interval);
if (cis->hdr.test_mode.tx_sdu_counter == 0U) {
/* First ISO event. Align SDU counter for next event */
cis->hdr.test_mode.tx_sdu_counter = sdu_counter;
tx_sdu_count = 0U;
} else {
/* Calculate number of SDUs to produce for next ISO event */
tx_sdu_count = sdu_counter - cis->hdr.test_mode.tx_sdu_counter;
}
/* Now process all SDUs due for next ISO event */
for (uint8_t sdu = 0; sdu < tx_sdu_count; sdu++) {
ll_iso_transmit_test_send_sdu(cis->lll.handle, ticks_at_expire);
}
}
}

View file

@ -34,3 +34,5 @@ void ull_conn_iso_resume_ticker_start(struct lll_event *resume_event,
uint16_t cis_handle,
uint32_t ticks_anchor,
uint32_t resume_timeout);
void ull_conn_iso_transmit_test_cig_interval(uint16_t handle,
uint32_t ticks_at_expire);

View file

@ -18,12 +18,14 @@ struct ll_conn_iso_stream {
uint8_t terminate_reason;
uint32_t offset; /* Offset of CIS from ACL event in us */
ll_iso_stream_released_cb_t released_cb; /* CIS release callback */
uint8_t framed:1;
uint8_t established:1; /* 0 if CIS has not yet been established.
uint16_t framed:1;
uint16_t established:1; /* 0 if CIS has not yet been established.
* 1 if CIS has been established and host
* notified.
*/
uint8_t teardown:1; /* 1 if CIS teardown has been initiated */
uint16_t teardown:1; /* 1 if CIS teardown has been initiated */
uint16_t p_max_sdu:12; /* Maximum SDU size P_To_C */
uint16_t c_max_sdu:12; /* Maximum SDU size C_To_P */
};
struct ll_conn_iso_group {

View file

@ -337,8 +337,6 @@ struct ll_conn {
uint8_t cig_id;
uint16_t cis_handle;
uint8_t cis_id;
uint32_t c_max_sdu:12;
uint32_t p_max_sdu:12;
uint32_t cis_offset_min;
uint32_t cis_offset_max;
uint16_t conn_event_count;

View file

@ -6,6 +6,7 @@
#include <zephyr/zephyr.h>
#include <soc.h>
#include <zephyr/sys/byteorder.h>
#include "hal/cpu.h"
#include "hal/ccm.h"
@ -69,7 +70,7 @@
static int init_reset(void);
#if defined(CONFIG_BT_CTLR_CONN_ISO)
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
static isoal_status_t ll_iso_pdu_alloc(struct isoal_pdu_buffer *pdu_buffer);
static isoal_status_t ll_iso_pdu_write(struct isoal_pdu_buffer *pdu_buffer,
const size_t offset,
@ -77,10 +78,12 @@ static isoal_status_t ll_iso_pdu_write(struct isoal_pdu_buffer *pdu_buffer,
const size_t consume_len);
static isoal_status_t ll_iso_pdu_emit(struct node_tx_iso *node_tx,
const uint16_t handle);
#if defined(CONFIG_BT_CTLR_CONN_ISO)
static isoal_status_t ll_iso_pdu_release(struct node_tx_iso *node_tx,
const uint16_t handle,
const isoal_status_t status);
#endif /* CONFIG_BT_CTLR_CONN_ISO */
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
/* Allocate data path pools for RX/TX directions for each stream */
#define BT_CTLR_ISO_STREAMS ((2 * (BT_CTLR_CONN_ISO_STREAMS)) + \
@ -114,11 +117,18 @@ static void iso_rx_demux(void *param);
#endif /* CONFIG_BT_CTLR_ISO_VENDOR_DATA_PATH */
#endif /* CONFIG_BT_CTLR_SYNC_ISO) || CONFIG_BT_CTLR_CONN_ISO */
#define ISO_TEST_PACKET_COUNTER_SIZE 4U
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
void ll_iso_link_tx_release(void *link);
void ll_iso_tx_mem_release(void *node_tx);
#define NODE_TX_BUFFER_SIZE MROUND(offsetof(struct node_tx_iso, pdu) + \
offsetof(struct pdu_iso, payload) + \
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE)
#define ISO_TEST_TX_BUFFER_SIZE 32U
static struct {
void *free;
uint8_t pool[NODE_TX_BUFFER_SIZE * CONFIG_BT_CTLR_ISO_TX_BUFFERS];
@ -579,24 +589,270 @@ uint8_t ll_remove_iso_path(uint16_t handle, uint8_t path_dir)
}
#if defined(CONFIG_BT_CTLR_SYNC_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
/* The sdu_alloc function is called before combining PDUs into an SDU. Here we
* store the paylaod number associated with the first PDU, for unframed usecase.
*/
static isoal_status_t ll_iso_test_sdu_alloc(const struct isoal_sink *sink_ctx,
const struct isoal_pdu_rx *valid_pdu,
struct isoal_sdu_buffer *sdu_buffer)
{
uint16_t handle;
handle = sink_ctx->session.handle;
if (IS_CIS_HANDLE(handle)) {
if (!sink_ctx->session.framed) {
struct ll_conn_iso_stream *cis;
cis = ll_iso_stream_connected_get(sink_ctx->session.handle);
LL_ASSERT(cis);
/* For unframed, SDU counter is the payload number */
cis->hdr.test_mode.rx_sdu_counter =
(uint32_t)valid_pdu->meta->payload_number;
}
} else if (IS_SYNC_ISO_HANDLE(handle)) {
/* FIXME: Implement for sync receiver */
LL_ASSERT(false);
}
return sink_sdu_alloc_hci(sink_ctx, valid_pdu, sdu_buffer);
}
/* The sdu_emit function is called whenever an SDU is combined and ready to be sent
* further in the data path. This injected implementation performs statistics on
* the SDU and then discards it.
*/
static isoal_status_t ll_iso_test_sdu_emit(const struct isoal_sink *sink_ctx,
const struct isoal_sdu_produced *valid_sdu)
{
isoal_status_t status;
struct net_buf *buf;
uint16_t handle;
handle = sink_ctx->session.handle;
buf = (struct net_buf *)valid_sdu->contents.dbuf;
if (IS_CIS_HANDLE(handle)) {
struct ll_conn_iso_stream *cis;
isoal_sdu_len_t length;
uint32_t sdu_counter;
uint8_t framed;
cis = ll_iso_stream_connected_get(sink_ctx->session.handle);
LL_ASSERT(cis);
length = sink_ctx->sdu_production.sdu_written;
framed = sink_ctx->session.framed;
/* In BT_HCI_ISO_TEST_ZERO_SIZE_SDU mode, all SDUs must have length 0 and there is
* no sdu_counter field. In the other modes, the first 4 bytes must contain a
* packet counter, which is used as SDU counter. The sdu_counter is extracted
* regardless of mode as a sanity check, unless the length does not allow it.
*/
if (length >= ISO_TEST_PACKET_COUNTER_SIZE) {
sdu_counter = sys_get_le32(buf->data);
} else {
sdu_counter = 0U;
}
switch (valid_sdu->status) {
case ISOAL_SDU_STATUS_VALID:
if (framed && cis->hdr.test_mode.rx_sdu_counter == 0U) {
/* BT 5.3, Vol 6, Part B, section 7.2:
* When using framed PDUs the expected value of the SDU counter
* shall be initialized with the value of the SDU counter of the
* first valid received SDU.
*/
cis->hdr.test_mode.rx_sdu_counter = sdu_counter;
}
switch (cis->hdr.test_mode.rx_payload_type) {
case BT_HCI_ISO_TEST_ZERO_SIZE_SDU:
if (length == 0) {
cis->hdr.test_mode.received_cnt++;
} else {
cis->hdr.test_mode.failed_cnt++;
}
break;
case BT_HCI_ISO_TEST_VARIABLE_SIZE_SDU:
if ((length >= ISO_TEST_PACKET_COUNTER_SIZE) &&
(length <= cis->c_max_sdu) &&
(sdu_counter == cis->hdr.test_mode.rx_sdu_counter)) {
cis->hdr.test_mode.received_cnt++;
} else {
cis->hdr.test_mode.failed_cnt++;
}
break;
case BT_HCI_ISO_TEST_MAX_SIZE_SDU:
if ((length == cis->c_max_sdu) &&
(sdu_counter == cis->hdr.test_mode.rx_sdu_counter)) {
cis->hdr.test_mode.received_cnt++;
} else {
cis->hdr.test_mode.failed_cnt++;
}
break;
default:
LL_ASSERT(0);
return ISOAL_STATUS_ERR_SDU_EMIT;
}
break;
case ISOAL_SDU_STATUS_ERRORS:
case ISOAL_SDU_STATUS_LOST_DATA:
cis->hdr.test_mode.missed_cnt++;
break;
}
if (framed) {
cis->hdr.test_mode.rx_sdu_counter++;
}
status = ISOAL_STATUS_OK;
} else if (IS_SYNC_ISO_HANDLE(handle)) {
/* FIXME: Implement for sync receiver */
status = ISOAL_STATUS_ERR_SDU_EMIT;
} else {
/* Handle is out of range */
status = ISOAL_STATUS_ERR_SDU_EMIT;
}
net_buf_unref(buf);
return status;
}
uint8_t ll_iso_receive_test(uint16_t handle, uint8_t payload_type)
{
ARG_UNUSED(handle);
ARG_UNUSED(payload_type);
isoal_sink_handle_t sink_handle;
struct ll_iso_datapath *dp;
uint32_t sdu_interval;
isoal_status_t err;
uint8_t status;
return BT_HCI_ERR_CMD_DISALLOWED;
status = BT_HCI_ERR_SUCCESS;
if (IS_CIS_HANDLE(handle)) {
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
cis = ll_iso_stream_connected_get(handle);
if (!cis) {
/* CIS is not connected */
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
if (cis->lll.rx.burst_number == 0) {
/* CIS is not configured for RX */
return BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL;
}
if (cis->hdr.datapath_out) {
/* Data path already set up */
return BT_HCI_ERR_CMD_DISALLOWED;
}
if (payload_type > BT_HCI_ISO_TEST_MAX_SIZE_SDU) {
return BT_HCI_ERR_INVALID_LL_PARAM;
}
/* Allocate and configure test datapath */
dp = mem_acquire(&datapath_free);
if (!dp) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
dp->path_dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST;
dp->path_id = BT_HCI_DATAPATH_ID_HCI;
cis->hdr.datapath_out = dp;
cig = cis->group;
if (cig->lll.role == BT_HCI_ROLE_PERIPHERAL) {
/* peripheral */
sdu_interval = cig->c_sdu_interval;
} else {
/* central */
sdu_interval = cig->p_sdu_interval;
}
err = isoal_sink_create(handle, cig->lll.role, cis->framed,
cis->lll.rx.burst_number, cis->lll.rx.flush_timeout,
sdu_interval, cig->iso_interval,
cis->sync_delay, cig->sync_delay,
ll_iso_test_sdu_alloc, ll_iso_test_sdu_emit,
sink_sdu_write_hci, &sink_handle);
if (err) {
/* Error creating test source - cleanup source and datapath */
isoal_sink_destroy(sink_handle);
ull_iso_datapath_release(dp);
cis->hdr.datapath_out = NULL;
return BT_HCI_ERR_CMD_DISALLOWED;
}
dp->sink_hdl = sink_handle;
isoal_sink_enable(sink_handle);
/* Enable Receive Test Mode */
cis->hdr.test_mode.rx_enabled = 1;
cis->hdr.test_mode.rx_payload_type = payload_type;
} else if (IS_SYNC_ISO_HANDLE(handle)) {
/* FIXME: Implement for sync receiver */
status = BT_HCI_ERR_CMD_DISALLOWED;
} else {
/* Handle is out of range */
status = BT_HCI_ERR_UNKNOWN_CONN_ID;
}
return status;
}
uint8_t ll_iso_read_test_counters(uint16_t handle, uint32_t *received_cnt,
uint32_t *missed_cnt,
uint32_t *failed_cnt)
{
ARG_UNUSED(handle);
ARG_UNUSED(received_cnt);
ARG_UNUSED(missed_cnt);
ARG_UNUSED(failed_cnt);
uint8_t status;
return BT_HCI_ERR_CMD_DISALLOWED;
*received_cnt = 0U;
*missed_cnt = 0U;
*failed_cnt = 0U;
status = BT_HCI_ERR_SUCCESS;
if (IS_CIS_HANDLE(handle)) {
struct ll_conn_iso_stream *cis;
cis = ll_iso_stream_connected_get(handle);
if (!cis) {
/* CIS is not connected */
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
if (!cis->hdr.test_mode.rx_enabled) {
/* ISO receive Test is not active */
return BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL;
}
/* Return SDU statistics */
*received_cnt = cis->hdr.test_mode.received_cnt;
*missed_cnt = cis->hdr.test_mode.missed_cnt;
*failed_cnt = cis->hdr.test_mode.failed_cnt;
} else if (IS_SYNC_ISO_HANDLE(handle)) {
/* FIXME: Implement for sync receiver */
status = BT_HCI_ERR_CMD_DISALLOWED;
} else {
/* Handle is out of range */
status = BT_HCI_ERR_UNKNOWN_CONN_ID;
}
return status;
}
#if defined(CONFIG_BT_CTLR_READ_ISO_LINK_QUALITY)
@ -625,24 +881,292 @@ uint8_t ll_read_iso_link_quality(uint16_t handle,
#endif /* CONFIG_BT_CTLR_SYNC_ISO || CONFIG_BT_CTLR_CONN_ISO */
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
static isoal_status_t ll_iso_test_pdu_release(struct node_tx_iso *node_tx,
const uint16_t handle,
const isoal_status_t status)
{
/* Release back to memory pool */
ll_iso_link_tx_release(node_tx->link);
ll_iso_tx_mem_release(node_tx);
return ISOAL_STATUS_OK;
}
#if defined(CONFIG_BT_CTLR_CONN_ISO)
void ll_iso_transmit_test_send_sdu(uint16_t handle, uint32_t ticks_at_expire)
{
isoal_source_handle_t source_handle;
struct isoal_sdu_tx sdu;
isoal_status_t err;
uint8_t tx_buffer[ISO_TEST_TX_BUFFER_SIZE];
uint16_t remaining_tx;
uint32_t sdu_counter;
if (IS_CIS_HANDLE(handle)) {
struct isoal_pdu_production *pdu_production;
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
struct isoal_source *source;
uint32_t rand_max_sdu;
uint8_t rand_8;
cis = ll_iso_stream_connected_get(handle);
LL_ASSERT(cis);
if (!cis->hdr.test_mode.tx_enabled) {
/* Transmit Test Mode not enabled */
return;
}
cig = cis->group;
source_handle = cis->hdr.datapath_in->source_hdl;
switch (cis->hdr.test_mode.tx_payload_type) {
case BT_HCI_ISO_TEST_ZERO_SIZE_SDU:
remaining_tx = 0;
break;
case BT_HCI_ISO_TEST_VARIABLE_SIZE_SDU:
/* Randomize the length [4..p_max_sdu] */
lll_rand_get(&rand_8, sizeof(rand_8));
rand_max_sdu = rand_8 * (cis->p_max_sdu - ISO_TEST_PACKET_COUNTER_SIZE);
remaining_tx = ISO_TEST_PACKET_COUNTER_SIZE + (rand_max_sdu >> 8);
break;
case BT_HCI_ISO_TEST_MAX_SIZE_SDU:
LL_ASSERT(cis->p_max_sdu > ISO_TEST_PACKET_COUNTER_SIZE);
remaining_tx = cis->p_max_sdu;
break;
default:
LL_ASSERT(0);
return;
}
if (remaining_tx > ISO_TEST_TX_BUFFER_SIZE) {
sdu.sdu_state = BT_ISO_START;
} else {
sdu.sdu_state = BT_ISO_SINGLE;
}
/* Configure SDU similarly to one delivered via HCI */
sdu.dbuf = tx_buffer;
sdu.cig_ref_point = cig->cig_ref_point;
sdu.target_event = cis->lll.event_count +
(cis->lll.tx.flush_timeout > 1U ? 0U : 1U);
sdu.iso_sdu_length = remaining_tx;
/* Send all SDU fragments */
do {
sdu.time_stamp = HAL_TICKER_TICKS_TO_US(ticks_at_expire);
sdu.size = MIN(remaining_tx, ISO_TEST_TX_BUFFER_SIZE);
memset(tx_buffer, 0, sdu.size);
/* If this is the first fragment of a framed SDU, inject the SDU
* counter.
*/
if ((sdu.size >= ISO_TEST_PACKET_COUNTER_SIZE) &&
((sdu.sdu_state == BT_ISO_START) || (sdu.sdu_state == BT_ISO_SINGLE))) {
if (cis->framed) {
sdu_counter = (uint32_t)cis->hdr.test_mode.tx_sdu_counter;
} else {
/* Unframed. Get the next payload counter.
*
* BT 5.3, Vol 6, Part B, Section 7.1:
* When using unframed PDUs, the SDU counter shall be equal
* to the payload counter.
*/
source = isoal_source_get(source_handle);
pdu_production = &source->pdu_production;
sdu_counter = MAX(pdu_production->payload_number,
(sdu.target_event *
cis->lll.tx.burst_number));
}
sys_put_le32(sdu_counter, tx_buffer);
}
/* Send to ISOAL */
err = isoal_tx_sdu_fragment(source_handle, &sdu);
LL_ASSERT(!err);
remaining_tx -= sdu.size;
if (remaining_tx > ISO_TEST_TX_BUFFER_SIZE) {
sdu.sdu_state = BT_ISO_CONT;
} else {
sdu.sdu_state = BT_ISO_END;
}
} while (remaining_tx);
cis->hdr.test_mode.tx_sdu_counter++;
} else if (IS_ADV_ISO_HANDLE(handle)) {
/* FIXME: Implement for broadcaster */
} else {
LL_ASSERT(0);
}
}
#endif /* CONFIG_BT_CTLR_CONN_ISO */
uint8_t ll_iso_transmit_test(uint16_t handle, uint8_t payload_type)
{
ARG_UNUSED(handle);
ARG_UNUSED(payload_type);
isoal_source_handle_t source_handle;
struct ll_iso_datapath *dp;
uint32_t sdu_interval;
isoal_status_t err;
uint8_t status;
return BT_HCI_ERR_CMD_DISALLOWED;
status = BT_HCI_ERR_SUCCESS;
if (IS_CIS_HANDLE(handle)) {
struct ll_conn_iso_stream *cis;
struct ll_conn_iso_group *cig;
cis = ll_iso_stream_connected_get(handle);
if (!cis) {
/* CIS is not connected */
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
if (cis->lll.tx.burst_number == 0U) {
/* CIS is not configured for TX */
return BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL;
}
if (cis->hdr.datapath_in) {
/* Data path already set up */
return BT_HCI_ERR_CMD_DISALLOWED;
}
if (payload_type > BT_HCI_ISO_TEST_MAX_SIZE_SDU) {
return BT_HCI_ERR_INVALID_LL_PARAM;
}
/* Allocate and configure test datapath */
dp = mem_acquire(&datapath_free);
if (!dp) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
dp->path_dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR;
dp->path_id = BT_HCI_DATAPATH_ID_HCI;
cis->hdr.datapath_in = dp;
cig = cis->group;
if (cig->lll.role == BT_HCI_ROLE_PERIPHERAL) {
/* peripheral */
sdu_interval = cig->c_sdu_interval;
} else {
/* central */
sdu_interval = cig->p_sdu_interval;
}
/* Setup the test source */
err = isoal_source_create(handle, cig->lll.role, cis->framed,
cis->lll.rx.burst_number, cis->lll.rx.flush_timeout,
cis->lll.rx.max_octets, sdu_interval, cig->iso_interval,
cis->sync_delay, cig->sync_delay,
ll_iso_pdu_alloc, ll_iso_pdu_write, ll_iso_pdu_emit,
ll_iso_test_pdu_release, &source_handle);
if (err) {
/* Error creating test source - cleanup source and datapath */
isoal_source_destroy(source_handle);
ull_iso_datapath_release(dp);
cis->hdr.datapath_in = NULL;
return BT_HCI_ERR_CMD_DISALLOWED;
}
dp->source_hdl = source_handle;
isoal_source_enable(source_handle);
/* Enable Transmit Test Mode */
cis->hdr.test_mode.tx_enabled = 1;
cis->hdr.test_mode.tx_payload_type = payload_type;
} else if (IS_ADV_ISO_HANDLE(handle)) {
struct lll_adv_iso_stream *stream;
stream = ull_adv_iso_stream_get(handle);
if (!stream) {
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
/* FIXME: Implement use of common header in stream to enable code sharing
* between CIS and BIS for test commands (and other places).
*/
status = BT_HCI_ERR_CMD_DISALLOWED;
} else {
/* Handle is out of range */
status = BT_HCI_ERR_UNKNOWN_CONN_ID;
}
return status;
}
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
uint8_t ll_iso_test_end(uint16_t handle, uint32_t *received_cnt,
uint32_t *missed_cnt, uint32_t *failed_cnt)
{
ARG_UNUSED(handle);
ARG_UNUSED(received_cnt);
ARG_UNUSED(missed_cnt);
ARG_UNUSED(failed_cnt);
uint8_t status;
return BT_HCI_ERR_CMD_DISALLOWED;
*received_cnt = 0U;
*missed_cnt = 0U;
*failed_cnt = 0U;
status = BT_HCI_ERR_SUCCESS;
if (IS_CIS_HANDLE(handle)) {
struct ll_conn_iso_stream *cis;
cis = ll_iso_stream_connected_get(handle);
if (!cis) {
/* CIS is not connected */
return BT_HCI_ERR_UNKNOWN_CONN_ID;
}
if (!cis->hdr.test_mode.rx_enabled && !cis->hdr.test_mode.tx_enabled) {
/* Test Mode is not active */
return BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL;
}
if (cis->hdr.test_mode.rx_enabled) {
isoal_sink_destroy(cis->hdr.datapath_out->sink_hdl);
ull_iso_datapath_release(cis->hdr.datapath_out);
cis->hdr.datapath_out = NULL;
/* Return SDU statistics */
*received_cnt = cis->hdr.test_mode.received_cnt;
*missed_cnt = cis->hdr.test_mode.missed_cnt;
*failed_cnt = cis->hdr.test_mode.failed_cnt;
}
if (cis->hdr.test_mode.tx_enabled) {
/* Tear down source and datapath */
isoal_source_destroy(cis->hdr.datapath_in->source_hdl);
ull_iso_datapath_release(cis->hdr.datapath_in);
cis->hdr.datapath_in = NULL;
}
/* Disable Test Mode */
(void)memset(&cis->hdr.test_mode, 0U, sizeof(cis->hdr.test_mode));
} else if (IS_ADV_ISO_HANDLE(handle)) {
/* FIXME: Implement for broadcaster */
status = BT_HCI_ERR_CMD_DISALLOWED;
} else if (IS_SYNC_ISO_HANDLE(handle)) {
/* FIXME: Implement for sync receiver */
status = BT_HCI_ERR_CMD_DISALLOWED;
} else {
/* Handle is out of range */
status = BT_HCI_ERR_UNKNOWN_CONN_ID;
}
return status;
}
#if defined(CONFIG_BT_CTLR_ADV_ISO) || defined(CONFIG_BT_CTLR_CONN_ISO)
@ -962,9 +1486,7 @@ void ll_iso_link_tx_release(void *link)
{
mem_release(link, &mem_link_iso_tx.free);
}
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
#if defined(CONFIG_BT_CTLR_CONN_ISO)
/**
* Allocate a PDU from the LL and store the details in the given buffer. Allocation
* is not expected to fail as there must always be sufficient PDU buffers. Any
@ -1051,6 +1573,7 @@ static isoal_status_t ll_iso_pdu_emit(struct node_tx_iso *node_tx,
return ISOAL_STATUS_OK;
}
#if defined(CONFIG_BT_CTLR_CONN_ISO)
/**
* Release the given payload back to the memory pool.
* @param node_tx TX node to release or forward
@ -1074,6 +1597,7 @@ static isoal_status_t ll_iso_pdu_release(struct node_tx_iso *node_tx,
return ISOAL_STATUS_OK;
}
#endif /* CONFIG_BT_CTLR_CONN_ISO */
#endif /* CONFIG_BT_CTLR_ADV_ISO || CONFIG_BT_CTLR_CONN_ISO */
static int init_reset(void)
{

View file

@ -10,3 +10,4 @@ void ull_iso_datapath_release(struct ll_iso_datapath *dp);
void ll_iso_rx_put(memq_link_t *link, void *rx);
void *ll_iso_rx_get(void);
void ll_iso_rx_dequeue(void);
void ll_iso_transmit_test_send_sdu(uint16_t handle, uint32_t ticks_at_expire);

View file

@ -34,10 +34,31 @@
#define IS_ADV_ISO_HANDLE(_handle) 0
#endif /* CONFIG_BT_CTLR_ADV_ISO */
#if defined(CONFIG_BT_CTLR_SYNC_ISO)
#define IS_SYNC_ISO_HANDLE(_handle) \
(((_handle) >= BT_CTLR_SYNC_ISO_STREAM_HANDLE_BASE) && \
((_handle) <= (BT_CTLR_SYNC_ISO_STREAM_HANDLE_BASE + BT_CTLR_SYNC_ISO_STREAM_MAX - 1U)))
#else
#define IS_SYNC_ISO_HANDLE(_handle) 0U
#endif /* CONFIG_BT_CTLR_SYNC_ISO */
struct ll_iso_test_mode_data {
uint32_t received_cnt;
uint32_t missed_cnt;
uint32_t failed_cnt;
uint32_t rx_sdu_counter;
uint64_t tx_sdu_counter:53; /* 39 + 22 - 8 */
uint64_t tx_enabled:1;
uint64_t tx_payload_type:4; /* Support up to 16 payload types (BT 5.3: 3, VS: 13) */
uint64_t rx_enabled:1;
uint64_t rx_payload_type:4;
};
/* Common members for ll_conn_iso_stream and ll_broadcast_iso_stream */
struct ll_iso_stream_hdr {
struct ll_iso_datapath *datapath_in;
struct ll_iso_datapath *datapath_out;
struct ll_iso_test_mode_data test_mode;
};
struct ll_iso_datapath {

View file

@ -194,6 +194,10 @@ uint8_t ull_peripheral_iso_acquire(struct ll_conn *acl,
cis->group = cig;
cis->teardown = 0;
cis->released_cb = NULL;
cis->c_max_sdu = (uint16_t)(req->c_max_sdu_packed[1] & 0x0F) << 8 |
req->c_max_sdu_packed[0];
cis->p_max_sdu = (uint16_t)(req->p_max_sdu[1] & 0x0F) << 8 |
req->p_max_sdu[0];
cis->lll.handle = 0xFFFF;
cis->lll.acl_handle = acl->lll.handle;
@ -335,6 +339,9 @@ static void ticker_cb(uint32_t ticks_at_expire, uint32_t ticks_drift,
err = mayfly_enqueue(TICKER_USER_ID_ULL_HIGH, TICKER_USER_ID_LLL,
0, &mfy);
LL_ASSERT(!err);
/* Handle ISO Transmit Test for this CIG */
ull_conn_iso_transmit_test_cig_interval(cig->lll.handle, ticks_at_expire);
}
static void ticker_op_cb(uint32_t status, void *param)