blob: 5ca94e59fea776300110aa718881495d2d4b4e31 [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 <inttypes.h>
#include "syscfg/syscfg.h"
#include "ble_hs_mbuf_priv.h"
#if MYNEWT_VAL(BLE_ISO)
#include "os/os_mbuf.h"
#include "host/ble_hs_log.h"
#include "host/ble_hs.h"
#include "host/ble_iso.h"
#include "nimble/hci_common.h"
#include "sys/queue.h"
#include "ble_hs_hci_priv.h"
struct ble_iso_big {
SLIST_ENTRY(ble_iso_big) next;
uint8_t handle;
uint16_t max_pdu;
uint8_t num_bis;
uint16_t conn_handles[MYNEWT_VAL(BLE_MAX_BIS)];
ble_iso_event_fn *cb;
void *cb_arg;
};
static SLIST_HEAD(, ble_iso_big) ble_iso_bigs;
static struct os_mempool ble_iso_big_pool;
static os_membuf_t ble_iso_big_mem[
OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_MAX_BIG), sizeof (struct ble_iso_big))];
static int
ble_iso_big_handle_set(struct ble_iso_big *big)
{
static uint8_t free_handle;
uint8_t i;
/* Set next free handle */
for (i = BLE_HCI_ISO_BIG_HANDLE_MIN; i < BLE_HCI_ISO_BIG_HANDLE_MAX; i++) {
struct ble_iso_big *node = NULL;
if (free_handle > BLE_HCI_ISO_BIG_HANDLE_MAX) {
free_handle = BLE_HCI_ISO_BIG_HANDLE_MIN;
}
big->handle = free_handle++;
SLIST_FOREACH(node, &ble_iso_bigs, next) {
if (node->handle == big->handle) {
break;
}
}
if (node == NULL || node->handle != big->handle) {
return 0;
}
}
BLE_HS_DBG_ASSERT(0);
return BLE_HS_EOS;
}
static struct ble_iso_big *
ble_iso_big_alloc(void)
{
struct ble_iso_big *new_big;
int rc;
new_big = os_memblock_get(&ble_iso_big_pool);
if (new_big == NULL) {
BLE_HS_LOG_ERROR("No more memory in pool\n");
/* Out of memory. */
return NULL;
}
memset(new_big, 0, sizeof *new_big);
rc = ble_iso_big_handle_set(new_big);
if (rc != 0) {
os_memblock_put(&ble_iso_big_pool, new_big);
return NULL;
}
SLIST_INSERT_HEAD(&ble_iso_bigs, new_big, next);
return new_big;
}
static struct ble_iso_big *
ble_iso_big_find_by_handle(uint8_t big_handle)
{
struct ble_iso_big *big;
SLIST_FOREACH(big, &ble_iso_bigs, next) {
if (big->handle == big_handle) {
return big;
}
}
return NULL;
}
static int
ble_iso_big_free(struct ble_iso_big *big)
{
SLIST_REMOVE(&ble_iso_bigs, big, ble_iso_big, next);
os_memblock_put(&ble_iso_big_pool, big);
return 0;
}
int
ble_iso_create_big(const struct ble_iso_create_big_params *create_params,
const struct ble_iso_big_params *big_params)
{
struct ble_hci_le_create_big_cp cp = { 0 };
struct ble_iso_big *big;
cp.adv_handle = create_params->adv_handle;
if (create_params->bis_cnt > MYNEWT_VAL(BLE_MAX_BIS)) {
return BLE_HS_EINVAL;
}
big = ble_iso_big_alloc();
if (big == NULL) {
return BLE_HS_ENOMEM;
}
big->cb = create_params->cb;
big->cb_arg = create_params->cb_arg;
cp.num_bis = create_params->bis_cnt;
put_le24(cp.sdu_interval, big_params->sdu_interval);
cp.max_sdu = big_params->max_sdu;
cp.max_transport_latency = big_params->max_transport_latency;
cp.rtn = big_params->rtn;
cp.phy = big_params->phy;
cp.packing = big_params->packing;
cp.framing = big_params->framing;
cp.encryption = big_params->encryption;
if (big_params->encryption) {
memcpy(cp.broadcast_code, big_params->broadcast_code, 16);
}
return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
BLE_HCI_OCF_LE_CREATE_BIG),
&cp, sizeof(cp),NULL, 0);
}
int
ble_iso_terminate_big(uint8_t big_handle)
{
struct ble_hci_le_terminate_big_cp cp;
struct ble_iso_big *big;
int rc;
big = ble_iso_big_find_by_handle(big_handle);
if (big == NULL) {
BLE_HS_LOG_ERROR("No BIG with handle=%d\n", big_handle);
return BLE_HS_ENOENT;
}
cp.big_handle = big->handle;
cp.reason = BLE_ERR_CONN_TERM_LOCAL;
rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE,
BLE_HCI_OCF_LE_TERMINATE_BIG),
&cp, sizeof(cp),NULL, 0);
return rc;
}
int
ble_iso_init(void)
{
int rc;
SLIST_INIT(&ble_iso_bigs);
rc = os_mempool_init(&ble_iso_big_pool,
MYNEWT_VAL(BLE_MAX_BIG),
sizeof (struct ble_iso_big),
ble_iso_big_mem, "ble_iso_big_pool");
SYSINIT_PANIC_ASSERT(rc == 0);
return 0;
}
void
ble_iso_rx_create_big_complete(const struct ble_hci_ev_le_subev_create_big_complete *ev)
{
struct ble_iso_event event;
struct ble_iso_big *big;
int i;
big = ble_iso_big_find_by_handle(ev->big_handle);
big->num_bis = ev->num_bis;
for (i = 0; i < ev->num_bis; i++) {
big->conn_handles[i] = ev->conn_handle[i];
}
big->max_pdu = ev->max_pdu;
event.type = BLE_ISO_EVENT_BIG_CREATE_COMPLETE;
event.big_created.desc.big_handle = ev->big_handle;
event.big_created.desc.big_sync_delay = get_le24(ev->big_sync_delay);
event.big_created.desc.transport_latency_big =
get_le24(ev->transport_latency_big);
event.big_created.desc.phy = ev->phy;
event.big_created.desc.nse = ev->nse;
event.big_created.desc.bn = ev->bn;
event.big_created.desc.pto = ev->pto;
event.big_created.desc.irc = ev->irc;
event.big_created.desc.max_pdu = ev->max_pdu;
event.big_created.desc.iso_interval = ev->iso_interval;
event.big_created.desc.num_bis = ev->num_bis;
memcpy(event.big_created.desc.conn_handle, ev->conn_handle,
ev->num_bis * sizeof(uint16_t));
if (big->cb != NULL) {
big->cb(&event, big->cb_arg);
}
}
void
ble_iso_rx_terminate_big_complete(const struct ble_hci_ev_le_subev_terminate_big_complete *ev)
{
struct ble_iso_event event;
struct ble_iso_big *big;
big = ble_iso_big_find_by_handle(ev->big_handle);
event.type = BLE_ISO_EVENT_BIG_TERMINATE_COMPLETE;
event.big_terminated.big_handle = ev->big_handle;
event.big_terminated.reason = ev->reason;
if (big->cb != NULL) {
big->cb(&event, big->cb_arg);
}
ble_iso_big_free(big);
}
static int
ble_iso_tx_complete(uint16_t conn_handle, const uint8_t *data,
uint16_t data_len)
{
struct os_mbuf *om;
int rc;
om = ble_hs_mbuf_bare_pkt();
if (!om) {
return BLE_HS_ENOMEM;
}
os_mbuf_extend(om, 8);
/* Connection_Handle, PB_Flag, TS_Flag */
put_le16(&om->om_data[0],
BLE_HCI_ISO_HANDLE(conn_handle, BLE_HCI_ISO_PB_COMPLETE, 0));
/* Data_Total_Length = Data length + Packet_Sequence_Number placeholder */
put_le16(&om->om_data[2], data_len + 4);
/* Packet_Sequence_Number placeholder */
put_le16(&om->om_data[4], 0);
/* ISO_SDU_Length */
put_le16(&om->om_data[6], data_len);
rc = os_mbuf_append(om, data, data_len);
if (rc) {
return rc;
}
return ble_transport_to_ll_iso(om);
}
static int
ble_iso_tx_segmented(uint16_t conn_handle, const uint8_t *data,
uint16_t data_len)
{
struct os_mbuf *om;
uint16_t data_left = data_len;
uint16_t packet_len;
uint16_t offset = 0;
uint8_t pb;
int rc;
while (data_left) {
packet_len = min(MYNEWT_VAL(BLE_TRANSPORT_ISO_SIZE), data_left);
if (data_left == data_len) {
pb = BLE_HCI_ISO_PB_FIRST;
} else if (packet_len == data_left) {
pb = BLE_HCI_ISO_PB_LAST;
} else {
pb = BLE_HCI_ISO_PB_CONTINUATION;
}
om = ble_hs_mbuf_bare_pkt();
if (!om) {
return BLE_HS_ENOMEM;
}
os_mbuf_extend(om, pb == BLE_HCI_ISO_PB_FIRST ? 8: 4);
/* Connection_Handle, PB_Flag, TS_Flag */
put_le16(&om->om_data[0],
BLE_HCI_ISO_HANDLE(conn_handle, pb, 0));
if (pb == BLE_HCI_ISO_PB_FIRST) {
/* Data_Total_Length = Data length +
* Packet_Sequence_Number placeholder*/
put_le16(&om->om_data[2], packet_len + 4);
/* Packet_Sequence_Number placeholder */
put_le16(&om->om_data[8], 0);
/* ISO_SDU_Length */
put_le16(&om->om_data[10], packet_len);
} else {
put_le16(&om->om_data[2], packet_len);
}
rc = os_mbuf_append(om, data + offset, packet_len);
if (rc) {
return rc;
}
ble_transport_to_ll_iso(om);
offset += packet_len;
data_left -= packet_len;
}
return 0;
}
int
ble_iso_tx(uint16_t conn_handle, void *data, uint16_t data_len)
{
int rc;
if (data_len <= MYNEWT_VAL(BLE_TRANSPORT_ISO_SIZE)) {
rc = ble_iso_tx_complete(conn_handle, data, data_len);
} else {
rc = ble_iso_tx_segmented(conn_handle, data, data_len);
}
return rc;
}
#endif