blob: 03a31d5fab793bfe8145306fe694f9cbf137481c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <stdint.h>
#include <syscfg/syscfg.h>
#include <nimble/hci_common.h>
#include <controller/ble_ll.h>
#include <controller/ble_ll_isoal.h>
#include <controller/ble_ll_iso.h>
#include <controller/ble_ll_tmr.h>
#if MYNEWT_VAL(BLE_LL_ISO)
STAILQ_HEAD(ble_ll_iso_conn_q, ble_ll_iso_conn);
struct ble_ll_iso_conn_q ll_iso_conn_q;
int
ble_ll_iso_setup_iso_data_path(const uint8_t *cmdbuf, uint8_t cmdlen,
uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_setup_iso_data_path_cp *cmd = (const void *)cmdbuf;
struct ble_hci_le_setup_iso_data_path_rp *rsp = (void *)rspbuf;
struct ble_ll_iso_conn *conn;
uint16_t conn_handle;
conn_handle = le16toh(cmd->conn_handle);
conn = ble_ll_iso_conn_find_by_handle(conn_handle);
if (!conn) {
return BLE_ERR_UNK_CONN_ID;
}
if (conn->mux.bn == 0) {
return BLE_ERR_UNSUPPORTED;
}
if (conn->data_path.enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
/* Only input for now since we only support BIS */
if (cmd->data_path_dir) {
return BLE_ERR_CMD_DISALLOWED;
}
/* We do not (yet) support any vendor-specific data path */
if (cmd->data_path_id) {
return BLE_ERR_CMD_DISALLOWED;
}
conn->data_path.enabled = 1;
conn->data_path.data_path_id = cmd->data_path_id;
rsp->conn_handle = cmd->conn_handle;
*rsplen = sizeof(*rsp);
return 0;
}
int
ble_ll_iso_remove_iso_data_path(const uint8_t *cmdbuf, uint8_t cmdlen,
uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_remove_iso_data_path_cp *cmd = (const void *)cmdbuf;
struct ble_hci_le_remove_iso_data_path_rp *rsp = (void *)rspbuf;
struct ble_ll_iso_conn *conn;
uint16_t conn_handle;
conn_handle = le16toh(cmd->conn_handle);
conn = ble_ll_iso_conn_find_by_handle(conn_handle);
if (!conn) {
return BLE_ERR_UNK_CONN_ID;
}
/* Only input for now since we only support BIS */
if (cmd->data_path_dir) {
return BLE_ERR_CMD_DISALLOWED;
}
conn->data_path.enabled = 0;
rsp->conn_handle = cmd->conn_handle;
*rsplen = sizeof(*rsp);
return 0;
}
int
ble_ll_iso_read_tx_sync(const uint8_t *cmdbuf, uint8_t cmdlen,
uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_read_iso_tx_sync_cp *cmd = (const void *)cmdbuf;
struct ble_hci_le_read_iso_tx_sync_rp *rsp = (void *)rspbuf;
struct ble_ll_iso_conn *iso_conn;
uint16_t handle;
handle = le16toh(cmd->conn_handle);
iso_conn = ble_ll_iso_conn_find_by_handle(handle);
if (!iso_conn) {
return BLE_ERR_UNK_CONN_ID;
}
rsp->conn_handle = cmd->conn_handle;
rsp->packet_seq_num = htole16(iso_conn->mux.last_tx_packet_seq_num);
rsp->tx_timestamp = htole32(iso_conn->mux.last_tx_timestamp);
put_le24(rsp->time_offset, 0);
*rsplen = sizeof(*rsp);
return 0;
}
int
ble_ll_iso_transmit_test(const uint8_t *cmdbuf, uint8_t cmdlen, uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_iso_transmit_test_cp *cmd = (const void *)cmdbuf;
struct ble_hci_le_iso_transmit_test_rp *rsp = (void *)rspbuf;
struct ble_ll_iso_conn *conn;
uint16_t handle;
handle = le16toh(cmd->conn_handle);
conn = ble_ll_iso_conn_find_by_handle(handle);
if (!conn) {
return BLE_ERR_UNK_CONN_ID;
}
if (conn->mux.bn == 0) {
return BLE_ERR_UNSUPPORTED;
}
if (conn->data_path.enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
if (cmd->payload_type > BLE_HCI_PAYLOAD_TYPE_MAXIMUM_LENGTH) {
return BLE_ERR_INV_LMP_LL_PARM;
}
conn->data_path.enabled = 1;
conn->data_path.data_path_id = BLE_HCI_ISO_DATA_PATH_ID_HCI;
conn->test_mode.transmit.enabled = 1;
conn->test_mode.transmit.payload_type = cmd->payload_type;
rsp->conn_handle = cmd->conn_handle;
*rsplen = sizeof(*rsp);
return 0;
}
int
ble_ll_iso_end_test(const uint8_t *cmdbuf, uint8_t len, uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_iso_test_end_cp *cmd = (const void *)cmdbuf;
struct ble_hci_le_iso_test_end_rp *rsp = (void *)rspbuf;
struct ble_ll_iso_conn *iso_conn;
uint16_t handle;
handle = le16toh(cmd->conn_handle);
iso_conn = ble_ll_iso_conn_find_by_handle(handle);
if (!iso_conn) {
return BLE_ERR_UNK_CONN_ID;
}
if (!iso_conn->test_mode.transmit.enabled) {
return BLE_ERR_UNSUPPORTED;
}
iso_conn->data_path.enabled = 0;
iso_conn->test_mode.transmit.enabled = 0;
rsp->conn_handle = cmd->conn_handle;
rsp->received_sdu_count = 0;
rsp->missed_sdu_count = 0;
rsp->failed_sdu_count = 0;
*rsplen = sizeof(*rsp);
return 0;
}
struct ble_ll_iso_conn *
ble_ll_iso_conn_find_by_handle(uint16_t conn_handle)
{
struct ble_ll_iso_conn *conn;
STAILQ_FOREACH(conn, &ll_iso_conn_q, iso_conn_q_next) {
if (conn_handle == conn->handle) {
return conn;
}
}
return NULL;
}
void
ble_ll_iso_init(void)
{
STAILQ_INIT(&ll_iso_conn_q);
ble_ll_isoal_init();
}
void
ble_ll_iso_reset(void)
{
STAILQ_INIT(&ll_iso_conn_q);
ble_ll_isoal_reset();
}
int
ble_ll_iso_data_in(struct os_mbuf *om)
{
struct ble_hci_iso *hci_iso;
struct ble_hci_iso_data *hci_iso_data;
struct ble_ll_iso_conn *conn;
struct ble_mbuf_hdr *blehdr;
uint16_t data_hdr_len;
uint16_t handle;
uint16_t conn_handle;
uint16_t length;
uint16_t pb_flag;
uint16_t ts_flag;
uint32_t timestamp = 0;
hci_iso = (void *)om->om_data;
handle = le16toh(hci_iso->handle);
conn_handle = BLE_HCI_ISO_CONN_HANDLE(handle);
pb_flag = BLE_HCI_ISO_PB_FLAG(handle);
ts_flag = BLE_HCI_ISO_TS_FLAG(handle);
length = BLE_HCI_ISO_LENGTH(le16toh(hci_iso->length));
conn = ble_ll_iso_conn_find_by_handle(conn_handle);
if (!conn) {
os_mbuf_free_chain(om);
return BLE_ERR_UNK_CONN_ID;
}
data_hdr_len = 0;
if ((pb_flag == BLE_HCI_ISO_PB_FIRST) ||
(pb_flag == BLE_HCI_ISO_PB_COMPLETE)) {
blehdr = BLE_MBUF_HDR_PTR(om);
blehdr->txiso.packet_seq_num = ++conn->mux.sdu_counter;
blehdr->txiso.cpu_timestamp = ble_ll_tmr_get();
if (ts_flag) {
timestamp = get_le32(om->om_data + sizeof(*hci_iso));
data_hdr_len += sizeof(uint32_t);
}
blehdr->txiso.hci_timestamp = timestamp;
hci_iso_data = (void *)(om->om_data + sizeof(*hci_iso) + data_hdr_len);
data_hdr_len += sizeof(*hci_iso_data);
}
os_mbuf_adj(om, sizeof(*hci_iso) + data_hdr_len);
if (OS_MBUF_PKTLEN(om) != length - data_hdr_len) {
os_mbuf_free_chain(om);
return BLE_ERR_MEM_CAPACITY;
}
switch (pb_flag) {
case BLE_HCI_ISO_PB_FIRST:
BLE_LL_ASSERT(!conn->frag);
conn->frag = om;
om = NULL;
break;
case BLE_HCI_ISO_PB_CONTINUATION:
BLE_LL_ASSERT(conn->frag);
os_mbuf_concat(conn->frag, om);
om = NULL;
break;
case BLE_HCI_ISO_PB_COMPLETE:
BLE_LL_ASSERT(!conn->frag);
break;
case BLE_HCI_ISO_PB_LAST:
BLE_LL_ASSERT(conn->frag);
os_mbuf_concat(conn->frag, om);
om = conn->frag;
conn->frag = NULL;
break;
default:
BLE_LL_ASSERT(0);
break;
}
if (om) {
ble_ll_isoal_mux_sdu_enqueue(&conn->mux, om);
}
return 0;
}
static int
ble_ll_iso_test_pdu_get(struct ble_ll_iso_conn *conn, uint8_t idx, uint32_t pkt_counter, uint8_t *llid, uint8_t *dptr)
{
uint32_t payload_len;
uint16_t rem_len;
uint8_t sdu_idx;
uint8_t pdu_idx;
int pdu_len;
BLE_LL_ASSERT(!conn->mux.framed);
sdu_idx = idx / conn->mux.pdu_per_sdu;
pdu_idx = idx - sdu_idx * conn->mux.pdu_per_sdu;
switch (conn->test_mode.transmit.payload_type) {
case BLE_HCI_PAYLOAD_TYPE_ZERO_LENGTH:
*llid = 0b00;
pdu_len = 0;
break;
case BLE_HCI_PAYLOAD_TYPE_VARIABLE_LENGTH:
payload_len = max(conn->test_mode.transmit.rand + (sdu_idx * pdu_idx), 4);
rem_len = payload_len - pdu_idx * conn->mux.max_pdu;
if (rem_len == 0) {
*llid = 0b01;
pdu_len = 0;
} else {
*llid = rem_len > conn->mux.max_pdu;
pdu_len = min(conn->mux.max_pdu, rem_len);
}
memset(dptr, 0, pdu_len);
if (payload_len == rem_len) {
put_le32(dptr, pkt_counter);
}
break;
case BLE_HCI_PAYLOAD_TYPE_MAXIMUM_LENGTH:
payload_len = conn->max_sdu;
rem_len = payload_len - pdu_idx * conn->mux.max_pdu;
if (rem_len == 0) {
*llid = 0b01;
pdu_len = 0;
} else {
*llid = rem_len > conn->mux.max_pdu;
pdu_len = min(conn->mux.max_pdu, rem_len);
}
memset(dptr, 0, pdu_len);
if (payload_len == rem_len) {
put_le32(dptr, pkt_counter);
}
break;
default:
BLE_LL_ASSERT(0);
}
return pdu_len;
}
int
ble_ll_iso_pdu_get(struct ble_ll_iso_conn *conn, uint8_t idx, uint32_t pkt_counter, uint8_t *llid, void *dptr)
{
if (conn->test_mode.transmit.enabled) {
return ble_ll_iso_test_pdu_get(conn, idx, pkt_counter, llid, dptr);
}
return ble_ll_isoal_mux_pdu_get(&conn->mux, idx, llid, dptr);
}
void
ble_ll_iso_conn_init(struct ble_ll_iso_conn *conn, struct ble_ll_iso_conn_init_param *param)
{
os_sr_t sr;
memset(conn, 0, sizeof(*conn));
conn->handle = param->conn_handle;
conn->max_sdu = param->max_sdu;
ble_ll_isoal_mux_init(&conn->mux, param->max_pdu, param->iso_interval_us, param->sdu_interval_us,
param->bn, param->pte, BLE_LL_ISOAL_MUX_IS_FRAMED(param->framing),
param->framing == BLE_HCI_ISO_FRAMING_FRAMED_UNSEGMENTED);
OS_ENTER_CRITICAL(sr);
STAILQ_INSERT_TAIL(&ll_iso_conn_q, conn, iso_conn_q_next);
OS_EXIT_CRITICAL(sr);
}
void
ble_ll_iso_conn_free(struct ble_ll_iso_conn *conn)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
STAILQ_REMOVE(&ll_iso_conn_q, conn, ble_ll_iso_conn, iso_conn_q_next);
OS_EXIT_CRITICAL(sr);
ble_ll_isoal_mux_free(&conn->mux);
}
int
ble_ll_iso_conn_event_start(struct ble_ll_iso_conn *conn, uint32_t timestamp)
{
if (conn->test_mode.transmit.enabled) {
conn->test_mode.transmit.rand = ble_ll_rand() % conn->max_sdu;
}
ble_ll_isoal_mux_event_start(&conn->mux, timestamp);
return 0;
}
int
ble_ll_iso_conn_event_done(struct ble_ll_iso_conn *conn)
{
conn->num_completed_pkt += ble_ll_isoal_mux_event_done(&conn->mux);
return conn->num_completed_pkt;
}
#endif /* BLE_LL_ISO */