diff options
Diffstat (limited to 'src/libs/mynewt-nimble/apps/bttester/src/l2cap.c')
-rw-r--r-- | src/libs/mynewt-nimble/apps/bttester/src/l2cap.c | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/src/libs/mynewt-nimble/apps/bttester/src/l2cap.c b/src/libs/mynewt-nimble/apps/bttester/src/l2cap.c new file mode 100644 index 00000000..45b904a1 --- /dev/null +++ b/src/libs/mynewt-nimble/apps/bttester/src/l2cap.c @@ -0,0 +1,477 @@ +/* + * 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. + */ + +/* l2cap.c - Bluetooth L2CAP Tester */ + +/* + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "syscfg/syscfg.h" + +#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) + +#include "console/console.h" +#include "host/ble_gap.h" +#include "host/ble_l2cap.h" + +#include "bttester.h" + +#define CONTROLLER_INDEX 0 +#define CHANNELS MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) +#define TESTER_COC_MTU (230) +#define TESTER_COC_BUF_COUNT (3 * MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)) + +static os_membuf_t tester_sdu_coc_mem[ + OS_MEMPOOL_SIZE(TESTER_COC_BUF_COUNT, TESTER_COC_MTU) +]; + +struct os_mbuf_pool sdu_os_mbuf_pool; +static struct os_mempool sdu_coc_mbuf_mempool; + +static struct channel { + u8_t chan_id; /* Internal number that identifies L2CAP channel. */ + u8_t state; + struct ble_l2cap_chan *chan; +} channels[CHANNELS]; + +static u8_t recv_cb_buf[TESTER_COC_MTU + sizeof(struct l2cap_data_received_ev)]; + +struct channel *find_channel(struct ble_l2cap_chan *chan) { + int i; + + for (i = 0; i < CHANNELS; ++i) { + if (channels[i].chan == chan) { + return &channels[i]; + } + } + + return NULL; +} + +static void +tester_l2cap_coc_recv(struct ble_l2cap_chan *chan, struct os_mbuf *sdu) +{ + SYS_LOG_DBG("LE CoC SDU received, chan: 0x%08lx, data len %d", + (uint32_t) chan, OS_MBUF_PKTLEN(sdu)); + + os_mbuf_free_chain(sdu); + sdu = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + assert(sdu != NULL); + + ble_l2cap_recv_ready(chan, sdu); +} + +static void recv_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan, + struct os_mbuf *buf, void *arg) +{ + struct l2cap_data_received_ev *ev = (void *) recv_cb_buf; + struct channel *channel = arg; + + ev->chan_id = channel->chan_id; + ev->data_length = buf->om_len; + memcpy(ev->data, buf->om_data, buf->om_len); + + tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_EV_DATA_RECEIVED, + CONTROLLER_INDEX, recv_cb_buf, sizeof(*ev) + buf->om_len); + + tester_l2cap_coc_recv(chan, buf); +} + +static void unstalled_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan, + int status, void *arg) +{ + if (status) { + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA, + CONTROLLER_INDEX, BTP_STATUS_FAILED); + } else { + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA, + CONTROLLER_INDEX, BTP_STATUS_SUCCESS); + } +} + +static struct channel *get_free_channel(void) +{ + u8_t i; + struct channel *chan; + + for (i = 0; i < CHANNELS; i++) { + if (channels[i].state) { + continue; + } + + chan = &channels[i]; + chan->chan_id = i; + + return chan; + } + + return NULL; +} + +static void connected_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan, + void *arg) +{ + struct l2cap_connected_ev ev; + struct ble_gap_conn_desc desc; + struct channel *channel; + + channel = get_free_channel(); + if (!channel) { + assert(0); + } + + channel->chan = chan; + channel->state = 0; + + ev.chan_id = channel->chan_id; + channel->state = 1; + channel->chan = chan; + /* TODO: ev.psm */ + + if (!ble_gap_conn_find(conn_handle, &desc)) { + ev.address_type = desc.peer_ota_addr.type; + memcpy(ev.address, desc.peer_ota_addr.val, + sizeof(ev.address)); + } + + tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_EV_CONNECTED, CONTROLLER_INDEX, + (u8_t *) &ev, sizeof(ev)); +} + +static void disconnected_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan, + void *arg) +{ + struct l2cap_disconnected_ev ev; + struct ble_gap_conn_desc desc; + struct channel *channel; + + memset(&ev, 0, sizeof(struct l2cap_disconnected_ev)); + + channel = find_channel(chan); + if (channel != NULL) { + channel->state = 0; + channel->chan = chan; + + ev.chan_id = channel->chan_id; + /* TODO: ev.result */ + /* TODO: ev.psm */ + } + + if (!ble_gap_conn_find(conn_handle, &desc)) { + ev.address_type = desc.peer_ota_addr.type; + memcpy(ev.address, desc.peer_ota_addr.val, + sizeof(ev.address)); + } + + tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_EV_DISCONNECTED, + CONTROLLER_INDEX, (u8_t *) &ev, sizeof(ev)); +} + +static int accept_cb(uint16_t conn_handle, uint16_t peer_mtu, + struct ble_l2cap_chan *chan) +{ + struct os_mbuf *sdu_rx; + + SYS_LOG_DBG("LE CoC accepting, chan: 0x%08lx, peer_mtu %d", + (uint32_t) chan, peer_mtu); + + sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_rx) { + return BLE_HS_ENOMEM; + } + + ble_l2cap_recv_ready(chan, sdu_rx); + + return 0; +} + +static int +tester_l2cap_event(struct ble_l2cap_event *event, void *arg) +{ + struct ble_l2cap_chan_info chan_info; + + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: + if (event->connect.status) { + console_printf("LE COC error: %d\n", event->connect.status); + disconnected_cb(event->connect.conn_handle, + event->connect.chan, arg); + return 0; + } + + ble_l2cap_get_chan_info(event->connect.chan, &chan_info); + + console_printf("LE COC connected, conn: %d, chan: 0x%08lx, scid: 0x%04x, " + "dcid: 0x%04x, our_mtu: 0x%04x, peer_mtu: 0x%04x\n", + event->connect.conn_handle, + (uint32_t) event->connect.chan, + chan_info.scid, + chan_info.dcid, + chan_info.our_l2cap_mtu, + chan_info.peer_l2cap_mtu); + + connected_cb(event->connect.conn_handle, + event->connect.chan, arg); + + return 0; + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + console_printf("LE CoC disconnected, chan: 0x%08lx\n", + (uint32_t) event->disconnect.chan); + + disconnected_cb(event->disconnect.conn_handle, + event->disconnect.chan, arg); + return 0; + case BLE_L2CAP_EVENT_COC_ACCEPT: + console_printf("LE CoC accept, chan: 0x%08lx, handle: %u, sdu_size: %u\n", + (uint32_t) event->accept.chan, + event->accept.conn_handle, + event->accept.peer_sdu_size); + + return accept_cb(event->accept.conn_handle, + event->accept.peer_sdu_size, + event->accept.chan); + + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: + console_printf("LE CoC data received, chan: 0x%08lx, handle: %u, sdu_len: %u\n", + (uint32_t) event->receive.chan, + event->receive.conn_handle, + event->receive.sdu_rx->om_len); + recv_cb(event->receive.conn_handle, event->receive.chan, + event->receive.sdu_rx, arg); + return 0; + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: + console_printf("LE CoC tx unstalled, chan: 0x%08lx, handle: %u, status: %d\n", + (uint32_t) event->tx_unstalled.chan, + event->tx_unstalled.conn_handle, + event->tx_unstalled.status); + unstalled_cb(event->tx_unstalled.conn_handle, + event->tx_unstalled.chan, + event->tx_unstalled.status, arg); + return 0; + default: + return 0; + } +} + +static void connect(u8_t *data, u16_t len) +{ + const struct l2cap_connect_cmd *cmd = (void *) data; + struct l2cap_connect_rp rp; + struct ble_gap_conn_desc desc; + struct channel *chan; + struct os_mbuf *sdu_rx; + ble_addr_t *addr = (void *) data; + int rc; + + SYS_LOG_DBG("connect: type: %d addr: %s", addr->type, bt_hex(addr->val, 6)); + + rc = ble_gap_conn_find_by_addr(addr, &desc); + if (rc) { + SYS_LOG_ERR("GAP conn find failed"); + goto fail; + } + + chan = get_free_channel(); + if (!chan) { + SYS_LOG_ERR("No free channels"); + goto fail; + } + + sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (sdu_rx == NULL) { + SYS_LOG_ERR("Failed to alloc buf"); + goto fail; + } + + rc = ble_l2cap_connect(desc.conn_handle, htole16(cmd->psm), + TESTER_COC_MTU, sdu_rx, + tester_l2cap_event, chan); + if (rc) { + SYS_LOG_ERR("L2CAP connect failed\n"); + goto fail; + } + + rp.chan_id = chan->chan_id; + + tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_CONNECT, CONTROLLER_INDEX, + (u8_t *) &rp, sizeof(rp)); + + return; + +fail: + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_CONNECT, CONTROLLER_INDEX, + BTP_STATUS_FAILED); +} + +static void disconnect(u8_t *data, u16_t len) +{ + const struct l2cap_disconnect_cmd *cmd = (void *) data; + struct channel *chan; + u8_t status; + int err; + + SYS_LOG_DBG(""); + + chan = &channels[cmd->chan_id]; + + err = ble_l2cap_disconnect(chan->chan); + if (err) { + status = BTP_STATUS_FAILED; + goto rsp; + } + + status = BTP_STATUS_SUCCESS; + +rsp: + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_DISCONNECT, CONTROLLER_INDEX, + status); +} + +static void send_data(u8_t *data, u16_t len) +{ + const struct l2cap_send_data_cmd *cmd = (void *) data; + struct channel *chan = &channels[cmd->chan_id]; + struct os_mbuf *sdu_tx = NULL; + int rc; + u16_t data_len = sys_le16_to_cpu(cmd->data_len); + + SYS_LOG_DBG("cmd->chan_id=%d", cmd->chan_id); + + /* FIXME: For now, fail if data length exceeds buffer length */ + if (data_len > TESTER_COC_MTU) { + SYS_LOG_ERR("Data length exceeds buffer length"); + goto fail; + } + + sdu_tx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (sdu_tx == NULL) { + SYS_LOG_ERR("No memory in the test sdu pool\n"); + goto fail; + } + + os_mbuf_append(sdu_tx, cmd->data, data_len); + + /* ble_l2cap_send takes ownership of the sdu */ + rc = ble_l2cap_send(chan->chan, sdu_tx); + if (rc == 0 || rc == BLE_HS_ESTALLED) { + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA, CONTROLLER_INDEX, + BTP_STATUS_SUCCESS); + return; + } + + SYS_LOG_ERR("Unable to send data: %d", rc); + os_mbuf_free_chain(sdu_tx); + +fail: + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA, CONTROLLER_INDEX, + BTP_STATUS_FAILED); +} + +static void listen(u8_t *data, u16_t len) +{ + const struct l2cap_listen_cmd *cmd = (void *) data; + int rc; + + SYS_LOG_DBG(""); + + /* TODO: Handle cmd->transport flag */ + rc = ble_l2cap_create_server(cmd->psm, TESTER_COC_MTU, + tester_l2cap_event, NULL); + if (rc) { + goto fail; + } + + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_LISTEN, CONTROLLER_INDEX, + BTP_STATUS_SUCCESS); + return; + +fail: + tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_LISTEN, CONTROLLER_INDEX, + BTP_STATUS_FAILED); +} + +static void supported_commands(u8_t *data, u16_t len) +{ + u8_t cmds[1]; + struct l2cap_read_supported_commands_rp *rp = (void *) cmds; + + memset(cmds, 0, sizeof(cmds)); + + tester_set_bit(cmds, L2CAP_READ_SUPPORTED_COMMANDS); + tester_set_bit(cmds, L2CAP_CONNECT); + tester_set_bit(cmds, L2CAP_DISCONNECT); + tester_set_bit(cmds, L2CAP_LISTEN); + tester_set_bit(cmds, L2CAP_SEND_DATA); + + tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_READ_SUPPORTED_COMMANDS, + CONTROLLER_INDEX, (u8_t *) rp, sizeof(cmds)); +} + +void tester_handle_l2cap(u8_t opcode, u8_t index, u8_t *data, + u16_t len) +{ + switch (opcode) { + case L2CAP_READ_SUPPORTED_COMMANDS: + supported_commands(data, len); + return; + case L2CAP_CONNECT: + connect(data, len); + return; + case L2CAP_DISCONNECT: + disconnect(data, len); + return; + case L2CAP_SEND_DATA: + send_data(data, len); + return; + case L2CAP_LISTEN: + listen(data, len); + return; + default: + tester_rsp(BTP_SERVICE_ID_L2CAP, opcode, index, + BTP_STATUS_UNKNOWN_CMD); + return; + } +} + +u8_t tester_init_l2cap(void) +{ + int rc; + + /* For testing we want to support all the available channels */ + rc = os_mempool_init(&sdu_coc_mbuf_mempool, TESTER_COC_BUF_COUNT, + TESTER_COC_MTU, tester_sdu_coc_mem, + "tester_coc_sdu_pool"); + assert(rc == 0); + + rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mbuf_mempool, + TESTER_COC_MTU, TESTER_COC_BUF_COUNT); + assert(rc == 0); + + return BTP_STATUS_SUCCESS; +} + +u8_t tester_unregister_l2cap(void) +{ + return BTP_STATUS_SUCCESS; +} + +#endif |