blob: 437b1104bf84b5bfbc4a845920583a4fbfa806d7 [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 "host/ble_monitor.h"
#if BLE_MONITOR
#if MYNEWT_VAL(BLE_MONITOR_UART) && MYNEWT_VAL(BLE_MONITOR_RTT)
#error "Cannot enable monitor over UART and RTT at the same time!"
#endif
#include <stdarg.h>
#include <stdio.h>
#include <inttypes.h>
#include "os/os.h"
#include "log/log.h"
#if MYNEWT_VAL(BLE_MONITOR_UART)
#include "uart/uart.h"
#endif
#if MYNEWT_VAL(BLE_MONITOR_RTT)
#include "rtt/SEGGER_RTT.h"
#endif
#include "ble_hs_priv.h"
#include "ble_monitor_priv.h"
struct ble_npl_mutex lock;
#if MYNEWT_VAL(BLE_MONITOR_UART)
struct uart_dev *uart;
static uint8_t tx_ringbuf[MYNEWT_VAL(BLE_MONITOR_UART_BUFFER_SIZE)];
static uint8_t tx_ringbuf_head;
static uint8_t tx_ringbuf_tail;
#endif
#if MYNEWT_VAL(BLE_MONITOR_RTT)
static uint8_t rtt_buf[MYNEWT_VAL(BLE_MONITOR_RTT_BUFFER_SIZE)];
static int rtt_index;
#if MYNEWT_VAL(BLE_MONITOR_RTT_BUFFERED)
static uint8_t rtt_pktbuf[MYNEWT_VAL(BLE_MONITOR_RTT_BUFFER_SIZE)];
static size_t rtt_pktbuf_pos;
static struct {
bool dropped;
struct ble_npl_callout tmo;
struct ble_monitor_drops_hdr drops_hdr;
} rtt_drops;
#endif
#endif
#if MYNEWT_VAL(BLE_MONITOR_UART)
static inline int
inc_and_wrap(int i, int max)
{
return (i + 1) & (max - 1);
}
static int
monitor_uart_tx_char(void *arg)
{
uint8_t ch;
/* No more data */
if (tx_ringbuf_head == tx_ringbuf_tail) {
return -1;
}
ch = tx_ringbuf[tx_ringbuf_tail];
tx_ringbuf_tail = inc_and_wrap(tx_ringbuf_tail, sizeof(tx_ringbuf));
return ch;
}
static void
monitor_uart_queue_char(uint8_t ch)
{
int sr;
OS_ENTER_CRITICAL(sr);
/* We need to try flush some data from ringbuffer if full */
while (inc_and_wrap(tx_ringbuf_head, sizeof(tx_ringbuf)) ==
tx_ringbuf_tail) {
uart_start_tx(uart);
OS_EXIT_CRITICAL(sr);
if (os_started()) {
os_time_delay(1);
}
OS_ENTER_CRITICAL(sr);
}
tx_ringbuf[tx_ringbuf_head] = ch;
tx_ringbuf_head = inc_and_wrap(tx_ringbuf_head, sizeof(tx_ringbuf));
OS_EXIT_CRITICAL(sr);
}
static void
monitor_write(const void *buf, size_t len)
{
const uint8_t *ch = buf;
while (len--) {
monitor_uart_queue_char(*ch++);
}
uart_start_tx(uart);
}
#endif
#if MYNEWT_VAL(BLE_MONITOR_RTT)
#if MYNEWT_VAL(BLE_MONITOR_RTT_BUFFERED)
static void
update_drop_counters(struct ble_monitor_hdr *failed_hdr)
{
uint8_t *cnt;
rtt_drops.dropped = true;
switch (failed_hdr->opcode) {
case BLE_MONITOR_OPCODE_COMMAND_PKT:
cnt = &rtt_drops.drops_hdr.cmd;
break;
case BLE_MONITOR_OPCODE_EVENT_PKT:
cnt = &rtt_drops.drops_hdr.evt;
break;
case BLE_MONITOR_OPCODE_ACL_TX_PKT:
cnt = &rtt_drops.drops_hdr.acl_tx;
break;
case BLE_MONITOR_OPCODE_ACL_RX_PKT:
cnt = &rtt_drops.drops_hdr.acl_rx;
break;
default:
cnt = &rtt_drops.drops_hdr.other;
break;
}
if (*cnt < UINT8_MAX) {
(*cnt)++;
ble_npl_callout_reset(&rtt_drops.tmo, OS_TICKS_PER_SEC);
}
}
static void
reset_drop_counters(void)
{
rtt_drops.dropped = false;
rtt_drops.drops_hdr.cmd = 0;
rtt_drops.drops_hdr.evt = 0;
rtt_drops.drops_hdr.acl_tx = 0;
rtt_drops.drops_hdr.acl_rx = 0;
rtt_drops.drops_hdr.other = 0;
ble_npl_callout_stop(&rtt_drops.tmo);
}
#endif
static void
monitor_write(const void *buf, size_t len)
{
#if MYNEWT_VAL(BLE_MONITOR_RTT_BUFFERED)
struct ble_monitor_hdr *hdr = (struct ble_monitor_hdr *) rtt_pktbuf;
bool discard;
unsigned ret = 0;
/* We will discard any packet which exceeds length of intermediate buffer */
discard = rtt_pktbuf_pos + len > sizeof(rtt_pktbuf);
if (!discard) {
memcpy(rtt_pktbuf + rtt_pktbuf_pos, buf, len);
}
rtt_pktbuf_pos += len;
if (rtt_pktbuf_pos < sizeof(hdr->data_len) + hdr->data_len) {
return;
}
if (!discard) {
ret = SEGGER_RTT_WriteNoLock(rtt_index, rtt_pktbuf, rtt_pktbuf_pos);
}
if (ret > 0) {
reset_drop_counters();
} else {
update_drop_counters(hdr);
}
rtt_pktbuf_pos = 0;
#else
SEGGER_RTT_WriteNoLock(rtt_index, buf, len);
#endif
}
#endif
static void
monitor_write_header(uint16_t opcode, uint16_t len)
{
struct ble_monitor_hdr hdr;
struct ble_monitor_ts_hdr ts_hdr;
uint8_t hdr_len;
int64_t ts;
hdr_len = sizeof(ts_hdr);
#if MYNEWT_VAL(BLE_MONITOR_RTT) && MYNEWT_VAL(BLE_MONITOR_RTT_BUFFERED)
if (rtt_drops.dropped) {
hdr_len += sizeof(rtt_drops.drops_hdr);
}
#endif
hdr.data_len = htole16(4 + hdr_len + len);
hdr.hdr_len = hdr_len;
hdr.opcode = htole16(opcode);
hdr.flags = 0;
/* Use uptime for timestamp */
ts = os_get_uptime_usec();
/*
* btsnoop specification states that fields of extended header must be
* sorted in increasing order so we will send drops (if any) headers before
* timestamp header.
*/
monitor_write(&hdr, sizeof(hdr));
#if MYNEWT_VAL(BLE_MONITOR_RTT) && MYNEWT_VAL(BLE_MONITOR_RTT_BUFFERED)
if (rtt_drops.dropped) {
monitor_write(&rtt_drops.drops_hdr, sizeof(rtt_drops.drops_hdr));
}
#endif
ts_hdr.type = BLE_MONITOR_EXTHDR_TS32;
ts_hdr.ts32 = htole32(ts / 100);
monitor_write(&ts_hdr, sizeof(ts_hdr));
}
static size_t
btmon_write(FILE *instance, const char *bp, size_t n)
{
monitor_write(bp, n);
return n;
}
static FILE *btmon = (FILE *) &(struct File) {
.vmt = &(struct File_methods) {
.write = btmon_write,
},
};
#if MYNEWT_VAL(BLE_MONITOR_RTT) && MYNEWT_VAL(BLE_MONITOR_RTT_BUFFERED)
static void
drops_tmp_cb(struct ble_npl_event *ev)
{
ble_npl_mutex_pend(&lock, OS_TIMEOUT_NEVER);
/*
* There's no "nop" in btsnoop protocol so we just send empty system note
* to indicate drops.
*/
monitor_write_header(BLE_MONITOR_OPCODE_SYSTEM_NOTE, 1);
monitor_write("", 1);
ble_npl_mutex_release(&lock);
}
#endif
int
ble_monitor_init(void)
{
#if MYNEWT_VAL(BLE_MONITOR_UART)
struct uart_conf uc = {
.uc_speed = MYNEWT_VAL(BLE_MONITOR_UART_BAUDRATE),
.uc_databits = 8,
.uc_stopbits = 1,
.uc_parity = UART_PARITY_NONE,
.uc_flow_ctl = UART_FLOW_CTL_NONE,
.uc_tx_char = monitor_uart_tx_char,
.uc_rx_char = NULL,
.uc_cb_arg = NULL,
};
uart = (struct uart_dev *)os_dev_open(MYNEWT_VAL(BLE_MONITOR_UART_DEV),
OS_TIMEOUT_NEVER, &uc);
if (!uart) {
return -1;
}
#endif
#if MYNEWT_VAL(BLE_MONITOR_RTT)
#if MYNEWT_VAL(BLE_MONITOR_RTT_BUFFERED)
ble_npl_callout_init(&rtt_drops.tmo, ble_hs_evq_get(), drops_tmp_cb, NULL);
/* Initialize types in header (we won't touch them later) */
rtt_drops.drops_hdr.type_cmd = BLE_MONITOR_EXTHDR_COMMAND_DROPS;
rtt_drops.drops_hdr.type_evt = BLE_MONITOR_EXTHDR_EVENT_DROPS;
rtt_drops.drops_hdr.type_acl_tx = BLE_MONITOR_EXTHDR_ACL_TX_DROPS;
rtt_drops.drops_hdr.type_acl_rx = BLE_MONITOR_EXTHDR_ACL_RX_DROPS;
rtt_drops.drops_hdr.type_other = BLE_MONITOR_EXTHDR_OTHER_DROPS;
rtt_index = SEGGER_RTT_AllocUpBuffer(MYNEWT_VAL(BLE_MONITOR_RTT_BUFFER_NAME),
rtt_buf, sizeof(rtt_buf),
SEGGER_RTT_MODE_NO_BLOCK_SKIP);
#else
rtt_index = SEGGER_RTT_AllocUpBuffer(MYNEWT_VAL(BLE_MONITOR_RTT_BUFFER_NAME),
rtt_buf, sizeof(rtt_buf),
SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL);
#endif
if (rtt_index < 0) {
return -1;
}
#endif
ble_npl_mutex_init(&lock);
return 0;
}
int
ble_monitor_send(uint16_t opcode, const void *data, size_t len)
{
ble_npl_mutex_pend(&lock, OS_TIMEOUT_NEVER);
monitor_write_header(opcode, len);
monitor_write(data, len);
ble_npl_mutex_release(&lock);
return 0;
}
int
ble_monitor_send_om(uint16_t opcode, const struct os_mbuf *om)
{
const struct os_mbuf *om_tmp;
uint16_t length = 0;
om_tmp = om;
while (om_tmp) {
length += om_tmp->om_len;
om_tmp = SLIST_NEXT(om_tmp, om_next);
}
ble_npl_mutex_pend(&lock, OS_TIMEOUT_NEVER);
monitor_write_header(opcode, length);
while (om) {
monitor_write(om->om_data, om->om_len);
om = SLIST_NEXT(om, om_next);
}
ble_npl_mutex_release(&lock);
return 0;
}
int
ble_monitor_new_index(uint8_t bus, uint8_t *addr, const char *name)
{
struct ble_monitor_new_index pkt;
pkt.type = 0; /* Primary controller, we don't support other */
pkt.bus = bus;
memcpy(pkt.bdaddr, addr, 6);
strncpy(pkt.name, name, sizeof(pkt.name) - 1);
pkt.name[sizeof(pkt.name) - 1] = '\0';
ble_monitor_send(BLE_MONITOR_OPCODE_NEW_INDEX, &pkt, sizeof(pkt));
return 0;
}
int
ble_monitor_log(int level, const char *fmt, ...)
{
static const char id[] = "nimble";
struct ble_monitor_user_logging ulog;
va_list va;
int len;
va_start(va, fmt);
len = vsnprintf(NULL, 0, fmt, va);
va_end(va);
switch (level) {
case LOG_LEVEL_ERROR:
ulog.priority = 3;
break;
case LOG_LEVEL_WARN:
ulog.priority = 4;
break;
case LOG_LEVEL_INFO:
ulog.priority = 6;
break;
case LOG_LEVEL_DEBUG:
ulog.priority = 7;
break;
default:
ulog.priority = 8;
break;
}
ulog.ident_len = sizeof(id);
ble_npl_mutex_pend(&lock, OS_TIMEOUT_NEVER);
monitor_write_header(BLE_MONITOR_OPCODE_USER_LOGGING,
sizeof(ulog) + sizeof(id) + len + 1);
monitor_write(&ulog, sizeof(ulog));
monitor_write(id, sizeof(id));
va_start(va, fmt);
vfprintf(btmon, fmt, va);
va_end(va);
/* null-terminate string */
monitor_write("", 1);
ble_npl_mutex_release(&lock);
return 0;
}
int
ble_monitor_out(int c)
{
static char buf[MYNEWT_VAL(BLE_MONITOR_CONSOLE_BUFFER_SIZE)];
static size_t len;
if (c != '\n') {
buf[len++] = c;
if (len < sizeof(buf) - 1) {
return c;
}
}
buf[len++] = '\0';
ble_monitor_send(BLE_MONITOR_OPCODE_SYSTEM_NOTE, buf, len);
len = 0;
return c;
}
#endif