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:
parent
2a9b06b7c6
commit
da402c0830
12 changed files with 650 additions and 29 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue