blob: de9c23603a213469bb86c8668d875e152a4a376c [file]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2011-2014 Intel Corporation
* Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include "bluetooth/bluetooth.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
#include "src/shared/btsnoop.h"
#include "monitor/bt.h"
#include "monitor/display.h"
#include "monitor/packet.h"
#include "monitor/analyze.h"
#define TIMEVAL_MSEC(_tv) \
(long long)((_tv)->tv_sec * 1000 + (_tv)->tv_usec / 1000)
struct hci_dev {
uint16_t index;
uint8_t type;
uint8_t bdaddr[6];
struct timeval time_added;
struct timeval time_removed;
unsigned long num_hci;
unsigned long num_cmd;
unsigned long num_evt;
unsigned long num_acl;
unsigned long num_sco;
unsigned long num_iso;
unsigned long vendor_diag;
unsigned long system_note;
unsigned long user_log;
unsigned long ctrl_msg;
unsigned long unknown;
uint16_t manufacturer;
struct queue *conn_list;
};
struct hci_stats {
size_t bytes;
size_t num;
size_t num_comp;
struct packet_latency latency;
struct queue *plot;
uint16_t min;
uint16_t max;
/* Wall-clock throughput tracking */
struct timeval first_ts;
struct timeval last_ts;
/* Windowed throughput (1-second windows) */
struct timeval window_start;
size_t window_bytes;
long long speed_min; /* Kb/s, 0 = not set */
long long speed_max; /* Kb/s */
};
struct hci_conn {
uint16_t handle;
uint16_t link;
uint8_t type;
uint8_t bdaddr[6];
uint8_t bdaddr_type;
bool setup_seen;
bool terminated;
uint8_t disconnect_reason;
unsigned long frame_connected;
unsigned long frame_disconnected;
struct queue *tx_queue;
struct timeval last_rx;
struct queue *chan_list;
struct hci_stats rx;
struct hci_stats tx;
};
struct hci_conn_tx {
struct timeval tv;
struct l2cap_chan *chan;
};
struct plot {
long long x_msec;
size_t y_count;
};
struct l2cap_chan {
uint16_t cid;
uint16_t psm;
uint16_t mtu;
uint16_t mps;
uint8_t mode;
bool out;
struct timeval last_rx;
struct hci_stats rx;
struct hci_stats tx;
};
static struct queue *dev_list;
static void tmp_write(void *data, void *user_data)
{
struct plot *plot = data;
FILE *tmp = user_data;
fprintf(tmp, "%lld %zu\n", plot->x_msec, plot->y_count);
}
static void plot_draw(struct queue *queue, const char *title)
{
FILE *gplot;
if (queue_length(queue) < 2)
return;
gplot = popen("gnuplot", "w");
if (!gplot)
return;
fprintf(gplot, "$data << EOD\n");
queue_foreach(queue, tmp_write, gplot);
fprintf(gplot, "EOD\n");
fprintf(gplot, "set terminal dumb enhanced ansi\n");
fprintf(gplot, "set xlabel 'Latency (ms)'\n");
fprintf(gplot, "set tics out nomirror\n");
fprintf(gplot, "set log y\n");
fprintf(gplot, "set yrange [0.5:*]\n");
fprintf(gplot, "plot $data using 1:2 t '%s' w impulses\n", title);
fflush(gplot);
pclose(gplot);
}
static void print_stats(struct hci_stats *stats, const char *label)
{
long long duration_ms;
if (!stats->num)
return;
print_field("%s packets: %zu/%zu", label, stats->num, stats->num_comp);
print_field("%s Latency: %lld-%lld msec (~%lld msec)", label,
TV_MSEC(stats->latency.min),
TV_MSEC(stats->latency.max),
TV_MSEC(stats->latency.med));
print_field("%s size: %u-%u octets (~%zd octets)", label,
stats->min, stats->max, stats->bytes / stats->num);
/* Compute wall-clock speed from first/last packet timestamps */
duration_ms = TV_MSEC(stats->last_ts) - TV_MSEC(stats->first_ts);
if (duration_ms > 0) {
long long avg_speed = stats->bytes * 8 / duration_ms;
/* Close the last window for min/max if it has data */
if (stats->window_bytes > 0 && stats->num > 1) {
struct timeval delta;
long long last_win_ms;
timersub(&stats->last_ts, &stats->window_start,
&delta);
last_win_ms = TV_MSEC(delta);
if (last_win_ms > 0) {
long long speed;
speed = stats->window_bytes * 8 /
last_win_ms;
if (!stats->speed_min ||
speed < stats->speed_min)
stats->speed_min = speed;
if (speed > stats->speed_max)
stats->speed_max = speed;
}
}
if (stats->speed_min && stats->speed_max)
print_field("%s speed: ~%lld Kb/s "
"(min ~%lld Kb/s max ~%lld Kb/s)",
label, avg_speed,
stats->speed_min, stats->speed_max);
else
print_field("%s speed: ~%lld Kb/s", label,
avg_speed);
}
plot_draw(stats->plot, label);
}
static const char *fixed_channel_name(uint16_t cid)
{
switch (cid) {
case 0x0001:
return "L2CAP Signaling (BR/EDR)";
case 0x0002:
return "Connectionless";
case 0x0003:
return "AMP Manager";
case 0x0004:
return "ATT";
case 0x0005:
return "L2CAP Signaling (LE)";
case 0x0006:
return "SMP (LE)";
case 0x0007:
return "SMP (BR/EDR)";
default:
return NULL;
}
}
static const char *l2cap_mode_name(uint8_t mode)
{
switch (mode) {
case 0x00:
return "Basic";
case 0x01:
return "Retransmission";
case 0x02:
return "Flow Control";
case 0x03:
return "ERTM";
case 0x04:
return "Streaming";
case 0x80:
return "LE Credit";
case 0x81:
return "Enhanced Credit";
default:
return "Unknown";
}
}
static void chan_destroy(void *data)
{
struct l2cap_chan *chan = data;
const char *fixed;
if (!chan->rx.num && !chan->tx.num)
goto done;
fixed = fixed_channel_name(chan->cid);
if (fixed)
printf(" Found %s L2CAP channel with CID %u (%s)\n",
chan->out ? "TX" : "RX", chan->cid,
fixed);
else
printf(" Found %s L2CAP channel with CID %u\n",
chan->out ? "TX" : "RX", chan->cid);
if (chan->psm)
print_field("PSM %u (0x%04x)", chan->psm, chan->psm);
if (!fixed) {
print_field("Mode: %s", l2cap_mode_name(chan->mode));
if (chan->mtu)
print_field("MTU: %u", chan->mtu);
if (chan->mps)
print_field("MPS: %u", chan->mps);
}
print_stats(&chan->rx, "RX");
print_stats(&chan->tx, "TX");
done:
free(chan);
}
static struct l2cap_chan *chan_alloc(struct hci_conn *conn, uint16_t cid,
bool out)
{
struct l2cap_chan *chan;
chan = new0(struct l2cap_chan, 1);
chan->cid = cid;
chan->out = out;
chan->rx.plot = queue_new();
chan->tx.plot = queue_new();
return chan;
}
static bool chan_match_cid(const void *a, const void *b)
{
const struct l2cap_chan *chan = a;
uint32_t val = PTR_TO_UINT(b);
uint16_t cid = val & 0xffff;
bool out = val & 0x10000;
return chan->cid == cid && chan->out == out;
}
static struct l2cap_chan *chan_lookup(struct hci_conn *conn, uint16_t cid,
bool out)
{
struct l2cap_chan *chan;
uint32_t val = cid | (out ? 0x10000 : 0);
chan = queue_find(conn->chan_list, chan_match_cid, UINT_TO_PTR(val));
if (!chan) {
chan = chan_alloc(conn, cid, out);
queue_push_tail(conn->chan_list, chan);
}
return chan;
}
static void conn_destroy(void *data)
{
struct hci_conn *conn = data;
const char *str;
switch (conn->type) {
case BTMON_CONN_ACL:
str = "BR-ACL";
break;
case BTMON_CONN_LE:
str = "LE-ACL";
break;
case BTMON_CONN_SCO:
str = "BR-SCO";
break;
case BTMON_CONN_ESCO:
str = "BR-ESCO";
break;
case BTMON_CONN_CIS:
str = "LE-CIS";
break;
case BTMON_CONN_BIS:
str = "LE-BIS";
break;
default:
str = "unknown";
break;
}
printf(" Found %s connection with handle %u\n", str, conn->handle);
packet_print_addr("Address", conn->bdaddr, conn->bdaddr_type);
if (!conn->setup_seen)
print_field("Connection setup missing");
print_stats(&conn->rx, "RX");
print_stats(&conn->tx, "TX");
if (conn->setup_seen) {
print_field("Connected: #%lu", conn->frame_connected);
if (conn->terminated) {
print_field("Disconnected: #%lu",
conn->frame_disconnected);
print_field("Disconnect Reason: 0x%02x",
conn->disconnect_reason);
}
}
queue_destroy(conn->rx.plot, free);
queue_destroy(conn->tx.plot, free);
queue_destroy(conn->chan_list, chan_destroy);
queue_destroy(conn->tx_queue, free);
free(conn);
}
static struct hci_conn *conn_alloc(struct hci_dev *dev, uint16_t handle,
uint8_t type)
{
struct hci_conn *conn;
conn = new0(struct hci_conn, 1);
conn->handle = handle;
conn->type = type;
conn->tx_queue = queue_new();
conn->tx.plot = queue_new();
conn->rx.plot = queue_new();
conn->chan_list = queue_new();
return conn;
}
static bool conn_match_handle(const void *a, const void *b)
{
const struct hci_conn *conn = a;
uint16_t handle = PTR_TO_UINT(b);
return (conn->handle == handle && !conn->terminated);
}
static struct hci_conn *conn_lookup(struct hci_dev *dev, uint16_t handle)
{
return queue_find(dev->conn_list, conn_match_handle,
UINT_TO_PTR(handle));
}
static bool link_match_handle(const void *a, const void *b)
{
const struct hci_conn *conn = a;
uint16_t handle = PTR_TO_UINT(b);
return (conn->link == handle && !conn->terminated);
}
static struct hci_conn *link_lookup(struct hci_dev *dev, uint16_t handle)
{
return queue_find(dev->conn_list, link_match_handle,
UINT_TO_PTR(handle));
}
static struct hci_conn *conn_lookup_type(struct hci_dev *dev, uint16_t handle,
uint8_t type)
{
struct hci_conn *conn;
conn = queue_find(dev->conn_list, conn_match_handle,
UINT_TO_PTR(handle));
if (!conn || (type && conn->type != type)) {
conn = conn_alloc(dev, handle, type);
queue_push_tail(dev->conn_list, conn);
}
return conn;
}
static void dev_destroy(void *data)
{
struct hci_dev *dev = data;
const char *str;
switch (dev->type) {
case 0x00:
str = "BR/EDR";
break;
case 0x01:
str = "AMP";
break;
default:
str = "unknown";
break;
}
printf("Found %s controller with index %u\n", str, dev->index);
printf(" BD_ADDR %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
dev->bdaddr[5], dev->bdaddr[4], dev->bdaddr[3],
dev->bdaddr[2], dev->bdaddr[1], dev->bdaddr[0]);
if (dev->manufacturer != 0xffff)
printf(" (%s)", bt_compidtostr(dev->manufacturer));
printf("\n");
printf(" %lu commands\n", dev->num_cmd);
printf(" %lu events\n", dev->num_evt);
printf(" %lu ACL packets\n", dev->num_acl);
printf(" %lu SCO packets\n", dev->num_sco);
printf(" %lu ISO packets\n", dev->num_iso);
printf(" %lu vendor diagnostics\n", dev->vendor_diag);
printf(" %lu system notes\n", dev->system_note);
printf(" %lu user logs\n", dev->user_log);
printf(" %lu control messages \n", dev->ctrl_msg);
printf(" %lu unknown opcodes\n", dev->unknown);
queue_destroy(dev->conn_list, conn_destroy);
printf("\n");
free(dev);
}
static struct hci_dev *dev_alloc(uint16_t index)
{
struct hci_dev *dev;
dev = new0(struct hci_dev, 1);
dev->index = index;
dev->manufacturer = 0xffff;
dev->conn_list = queue_new();
return dev;
}
static bool dev_match_index(const void *a, const void *b)
{
const struct hci_dev *dev = a;
uint16_t index = PTR_TO_UINT(b);
return dev->index == index;
}
static struct hci_dev *dev_lookup(uint16_t index)
{
struct hci_dev *dev;
dev = queue_find(dev_list, dev_match_index, UINT_TO_PTR(index));
if (!dev) {
dev = dev_alloc(index);
queue_push_tail(dev_list, dev);
}
return dev;
}
static void l2cap_sig(struct hci_conn *conn, bool out,
const void *data, uint16_t size)
{
const struct bt_l2cap_hdr_sig *hdr = data;
struct l2cap_chan *chan;
uint16_t psm, scid, dcid;
switch (hdr->code) {
case BT_L2CAP_PDU_CONN_REQ:
psm = get_le16(data + 4);
scid = get_le16(data + 6);
chan = chan_lookup(conn, scid, out);
if (chan)
chan->psm = psm;
break;
case BT_L2CAP_PDU_CONN_RSP:
dcid = get_le16(data + 4);
scid = get_le16(data + 6);
chan = chan_lookup(conn, scid, !out);
if (chan) {
psm = chan->psm;
chan = chan_lookup(conn, dcid, out);
if (chan)
chan->psm = psm;
}
break;
case BT_L2CAP_PDU_CONFIG_REQ:
{
const struct bt_l2cap_pdu_config_req *pdu = data + 4;
const uint8_t *opts;
uint16_t opts_len;
dcid = le16_to_cpu(pdu->dcid);
/* Options start after the 4-byte config req header */
opts = data + 4 + sizeof(*pdu);
opts_len = size - 4 - sizeof(*pdu);
chan = chan_lookup(conn, dcid, !out);
if (!chan)
break;
while (opts_len >= 2) {
uint8_t type = opts[0];
uint8_t len = opts[1];
if (opts_len < (uint16_t)(2 + len))
break;
switch (type) {
case 0x01: /* MTU */
if (len >= 2)
chan->mtu = get_le16(opts + 2);
break;
case 0x04: /* Retransmission and Flow Control */
if (len >= 1)
chan->mode = opts[2];
break;
}
opts += 2 + len;
opts_len -= 2 + len;
}
break;
}
case BT_L2CAP_PDU_CONFIG_RSP:
{
const struct bt_l2cap_pdu_config_rsp *pdu = data + 4;
const uint8_t *opts;
uint16_t opts_len;
scid = le16_to_cpu(pdu->scid);
/* Options start after the 6-byte config rsp header */
opts = data + 4 + sizeof(*pdu);
opts_len = size - 4 - sizeof(*pdu);
chan = chan_lookup(conn, scid, out);
if (!chan)
break;
while (opts_len >= 2) {
uint8_t type = opts[0];
uint8_t len = opts[1];
if (opts_len < (uint16_t)(2 + len))
break;
switch (type) {
case 0x01: /* MTU */
if (len >= 2)
chan->mtu = get_le16(opts + 2);
break;
case 0x04: /* Retransmission and Flow Control */
if (len >= 1)
chan->mode = opts[2];
break;
}
opts += 2 + len;
opts_len -= 2 + len;
}
break;
}
}
}
static void l2cap_le_sig(struct hci_conn *conn, bool out,
const void *data, uint16_t size)
{
const struct bt_l2cap_hdr_sig *hdr = data;
struct l2cap_chan *chan;
uint16_t psm, scid, dcid;
switch (hdr->code) {
case BT_L2CAP_PDU_LE_CONN_REQ:
{
const struct bt_l2cap_pdu_le_conn_req *pdu = data + 4;
psm = le16_to_cpu(pdu->psm);
scid = le16_to_cpu(pdu->scid);
chan = chan_lookup(conn, scid, out);
if (chan) {
chan->psm = psm;
chan->mtu = le16_to_cpu(pdu->mtu);
chan->mps = le16_to_cpu(pdu->mps);
chan->mode = 0x80; /* LE Credit */
}
break;
}
case BT_L2CAP_PDU_LE_CONN_RSP:
{
const struct bt_l2cap_pdu_le_conn_rsp *pdu = data + 4;
dcid = le16_to_cpu(pdu->dcid);
/* The response's dcid is the responder's CID. Its MTU/MPS
* belong to the responder, so set them on the channel for
* that direction (out). Also propagate PSM from the
* requester's channel (!out) if available.
*/
chan = chan_lookup(conn, dcid, out);
if (chan) {
struct l2cap_chan *req_chan;
chan->mtu = le16_to_cpu(pdu->mtu);
chan->mps = le16_to_cpu(pdu->mps);
chan->mode = 0x80; /* LE Credit */
/* Propagate PSM from the request channel */
req_chan = queue_find(conn->chan_list,
chan_match_cid,
UINT_TO_PTR(dcid |
(!out ? 0x10000 : 0)));
if (req_chan && req_chan->psm)
chan->psm = req_chan->psm;
}
break;
}
case BT_L2CAP_PDU_ECRED_CONN_REQ:
{
const struct bt_l2cap_pdu_ecred_conn_req *pdu = data + 4;
uint16_t req_len = le16_to_cpu(hdr->len);
int num_cids;
int i;
psm = le16_to_cpu(pdu->psm);
num_cids = (req_len - sizeof(*pdu)) / sizeof(uint16_t);
for (i = 0; i < num_cids; i++) {
scid = le16_to_cpu(pdu->scid[i]);
chan = chan_lookup(conn, scid, out);
if (chan) {
chan->psm = psm;
chan->mtu = le16_to_cpu(pdu->mtu);
chan->mps = le16_to_cpu(pdu->mps);
chan->mode = 0x81; /* Enhanced Credit */
}
}
break;
}
}
}
static void new_index(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
const struct btsnoop_opcode_new_index *ni = data;
struct hci_dev *dev;
dev = dev_alloc(index);
dev->type = ni->type;
memcpy(dev->bdaddr, ni->bdaddr, 6);
queue_push_tail(dev_list, dev);
}
static void del_index(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
struct hci_dev *dev;
dev = queue_remove_if(dev_list, dev_match_index, UINT_TO_PTR(index));
if (!dev) {
fprintf(stderr, "Remove for an unexisting device\n");
return;
}
dev_destroy(dev);
}
static void command_pkt(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->num_hci++;
dev->num_cmd++;
}
static void evt_conn_complete(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
const void *data, uint16_t size)
{
const struct bt_hci_evt_conn_complete *evt = data;
struct hci_conn *conn;
if (evt->status)
return;
conn = conn_lookup_type(dev, le16_to_cpu(evt->handle), BTMON_CONN_ACL);
if (!conn)
return;
memcpy(conn->bdaddr, evt->bdaddr, 6);
conn->frame_connected = frame;
conn->setup_seen = true;
}
static void evt_disconnect_complete(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
const void *data, uint16_t size)
{
const struct bt_hci_evt_disconnect_complete *evt = data;
struct hci_conn *conn;
if (evt->status)
return;
conn = conn_lookup(dev, le16_to_cpu(evt->handle));
if (!conn)
return;
conn->frame_disconnected = frame;
conn->disconnect_reason = evt->reason;
conn->terminated = true;
}
static void rsp_read_bd_addr(struct hci_dev *dev, struct timeval *tv,
const void *data, uint16_t size)
{
const struct bt_hci_rsp_read_bd_addr *rsp = data;
if (rsp->status)
return;
memcpy(dev->bdaddr, rsp->bdaddr, 6);
}
static void evt_cmd_complete(struct hci_dev *dev, struct timeval *tv,
const void *data, uint16_t size)
{
const struct bt_hci_evt_cmd_complete *evt = data;
uint16_t opcode;
data += sizeof(*evt);
size -= sizeof(*evt);
opcode = le16_to_cpu(evt->opcode);
switch (opcode) {
case BT_HCI_CMD_READ_BD_ADDR:
rsp_read_bd_addr(dev, tv, data, size);
break;
}
}
static bool match_plot_latency(const void *data, const void *user_data)
{
const struct plot *plot = data;
const struct timeval *latency = user_data;
return TIMEVAL_MSEC(latency) == plot->x_msec;
}
static void plot_add(struct queue *queue, struct timeval *latency,
uint16_t count)
{
struct plot *plot;
/* Use LRU ordering */
plot = queue_remove_if(queue, match_plot_latency, latency);
if (plot) {
plot->y_count += count;
queue_push_head(queue, plot);
return;
}
plot = new0(struct plot, 1);
plot->x_msec = TIMEVAL_MSEC(latency);
plot->y_count = count;
queue_push_tail(queue, plot);
}
static void evt_le_conn_complete(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
struct iovec *iov)
{
const struct bt_hci_evt_le_conn_complete *evt;
struct hci_conn *conn;
evt = util_iov_pull_mem(iov, sizeof(*evt));
if (!evt || evt->status)
return;
conn = conn_lookup_type(dev, le16_to_cpu(evt->handle), BTMON_CONN_LE);
if (!conn)
return;
memcpy(conn->bdaddr, evt->peer_addr, 6);
conn->bdaddr_type = evt->peer_addr_type;
conn->frame_connected = frame;
conn->setup_seen = true;
}
static void evt_le_enh_conn_complete(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
struct iovec *iov)
{
const struct bt_hci_evt_le_enhanced_conn_complete *evt;
struct hci_conn *conn;
evt = util_iov_pull_mem(iov, sizeof(*evt));
if (!evt || evt->status)
return;
conn = conn_lookup_type(dev, le16_to_cpu(evt->handle), BTMON_CONN_LE);
if (!conn)
return;
memcpy(conn->bdaddr, evt->peer_addr, 6);
conn->bdaddr_type = evt->peer_addr_type;
conn->frame_connected = frame;
conn->setup_seen = true;
}
static void evt_num_completed_packets(struct hci_dev *dev, struct timeval *tv,
const void *data, uint16_t size)
{
struct iovec iov = { .iov_base = (void *)data, .iov_len = size };
uint8_t num_handles;
int i;
if (!util_iov_pull_u8(&iov, &num_handles))
return;
for (i = 0; i < num_handles; i++) {
uint16_t handle, count;
struct hci_conn *conn;
struct timeval res;
struct hci_conn_tx *last_tx;
int j;
if (!util_iov_pull_le16(&iov, &handle))
return;
if (!util_iov_pull_le16(&iov, &count))
return;
conn = conn_lookup(dev, handle);
if (!conn)
continue;
conn->tx.num_comp += count;
for (j = 0; j < count; j++) {
last_tx = queue_pop_head(conn->tx_queue);
if (last_tx) {
struct l2cap_chan *chan = last_tx->chan;
timersub(tv, &last_tx->tv, &res);
packet_latency_add(&conn->tx.latency, &res);
plot_add(conn->tx.plot, &res, 1);
if (chan) {
chan->tx.num_comp += count;
packet_latency_add(&chan->tx.latency,
&res);
plot_add(chan->tx.plot, &res, 1);
}
free(last_tx);
}
}
}
}
static void evt_sync_conn_complete(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
const void *data, uint16_t size)
{
const struct bt_hci_evt_sync_conn_complete *evt = data;
struct hci_conn *conn;
if (evt->status)
return;
conn = conn_lookup_type(dev, le16_to_cpu(evt->handle), evt->link_type);
if (!conn)
return;
memcpy(conn->bdaddr, evt->bdaddr, 6);
conn->frame_connected = frame;
conn->setup_seen = true;
}
static void evt_le_cis_established(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
struct iovec *iov)
{
const struct bt_hci_evt_le_cis_established *evt;
struct hci_conn *conn, *link;
evt = util_iov_pull_mem(iov, sizeof(*evt));
if (!evt || evt->status)
return;
conn = conn_lookup_type(dev, le16_to_cpu(evt->conn_handle),
BTMON_CONN_CIS);
if (!conn)
return;
conn->frame_connected = frame;
conn->setup_seen = true;
link = link_lookup(dev, conn->handle);
if (link)
memcpy(conn->bdaddr, link->bdaddr, 6);
}
static void evt_le_cis_req(struct hci_dev *dev, struct timeval *tv,
struct iovec *iov)
{
const struct bt_hci_evt_le_cis_req *evt;
struct hci_conn *conn;
evt = util_iov_pull_mem(iov, sizeof(*evt));
if (!evt)
return;
conn = conn_lookup(dev, le16_to_cpu(evt->acl_handle));
if (!conn)
return;
conn->link = le16_to_cpu(evt->cis_handle);
}
static void evt_le_big_complete(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
struct iovec *iov)
{
const struct bt_hci_evt_le_big_complete *evt;
int i;
evt = util_iov_pull_mem(iov, sizeof(*evt));
if (!evt || evt->status)
return;
for (i = 0; i < evt->num_bis; i++) {
struct hci_conn *conn;
uint16_t handle;
if (!util_iov_pull_le16(iov, &handle))
return;
conn = conn_lookup_type(dev, handle, BTMON_CONN_BIS);
if (conn) {
conn->setup_seen = true;
conn->frame_connected = frame;
}
}
}
static void evt_le_big_sync_established(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
struct iovec *iov)
{
const struct bt_hci_evt_le_big_sync_estabilished *evt;
int i;
evt = util_iov_pull_mem(iov, sizeof(*evt));
if (!evt || evt->status)
return;
for (i = 0; i < evt->num_bis; i++) {
struct hci_conn *conn;
uint16_t handle;
if (!util_iov_pull_le16(iov, &handle))
return;
conn = conn_lookup_type(dev, handle, BTMON_CONN_BIS);
if (conn) {
conn->setup_seen = true;
conn->frame_connected = frame;
}
}
}
static void evt_le_meta_event(struct hci_dev *dev, struct timeval *tv,
unsigned long frame,
const void *data, uint16_t size)
{
struct iovec iov = {
.iov_base = (void *)data,
.iov_len = size,
};
uint8_t subevt;
if (!util_iov_pull_u8(&iov, &subevt))
return;
switch (subevt) {
case BT_HCI_EVT_LE_CONN_COMPLETE:
evt_le_conn_complete(dev, tv, frame, &iov);
break;
case BT_HCI_EVT_LE_ENHANCED_CONN_COMPLETE:
evt_le_enh_conn_complete(dev, tv, frame, &iov);
break;
case BT_HCI_EVT_LE_CIS_ESTABLISHED:
evt_le_cis_established(dev, tv, frame, &iov);
break;
case BT_HCI_EVT_LE_CIS_REQ:
evt_le_cis_req(dev, tv, &iov);
break;
case BT_HCI_EVT_LE_BIG_COMPLETE:
evt_le_big_complete(dev, tv, frame, &iov);
break;
case BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED:
evt_le_big_sync_established(dev, tv, frame, &iov);
break;
}
}
static void event_pkt(struct timeval *tv, uint16_t index,
unsigned long frame,
const void *data, uint16_t size)
{
const struct bt_hci_evt_hdr *hdr = data;
struct hci_dev *dev;
data += sizeof(*hdr);
size -= sizeof(*hdr);
dev = dev_lookup(index);
if (!dev)
return;
dev->num_hci++;
dev->num_evt++;
switch (hdr->evt) {
case BT_HCI_EVT_CONN_COMPLETE:
evt_conn_complete(dev, tv, frame, data, size);
break;
case BT_HCI_EVT_DISCONNECT_COMPLETE:
evt_disconnect_complete(dev, tv, frame, data, size);
break;
case BT_HCI_EVT_CMD_COMPLETE:
evt_cmd_complete(dev, tv, data, size);
break;
case BT_HCI_EVT_NUM_COMPLETED_PACKETS:
evt_num_completed_packets(dev, tv, data, size);
break;
case BT_HCI_EVT_SYNC_CONN_COMPLETE:
evt_sync_conn_complete(dev, tv, frame, data, size);
break;
case BT_HCI_EVT_LE_META_EVENT:
evt_le_meta_event(dev, tv, frame, data, size);
break;
}
}
static void stats_add(struct hci_stats *stats, struct timeval *tv,
uint16_t size)
{
stats->num++;
stats->bytes += size;
if (!stats->min || size < stats->min)
stats->min = size;
if (!stats->max || size > stats->max)
stats->max = size;
/* Wall-clock timestamp tracking */
if (!timerisset(&stats->first_ts))
stats->first_ts = *tv;
stats->last_ts = *tv;
/* Windowed throughput: 1-second windows */
if (!timerisset(&stats->window_start)) {
stats->window_start = *tv;
stats->window_bytes = size;
} else {
struct timeval delta;
timersub(tv, &stats->window_start, &delta);
if (TV_MSEC(delta) >= 1000) {
/* Close current window, compute speed */
long long speed;
speed = stats->window_bytes * 8 /
TV_MSEC(delta);
if (!stats->speed_min || speed < stats->speed_min)
stats->speed_min = speed;
if (speed > stats->speed_max)
stats->speed_max = speed;
/* Start new window */
stats->window_start = *tv;
stats->window_bytes = size;
} else {
stats->window_bytes += size;
}
}
}
static void conn_pkt_tx(struct hci_conn *conn, struct timeval *tv,
uint16_t size, struct l2cap_chan *chan)
{
struct hci_conn_tx *last_tx;
last_tx = new0(struct hci_conn_tx, 1);
memcpy(last_tx, tv, sizeof(*tv));
last_tx->chan = chan;
queue_push_tail(conn->tx_queue, last_tx);
stats_add(&conn->tx, tv, size);
if (chan)
stats_add(&chan->tx, tv, size);
}
static void conn_pkt_rx(struct hci_conn *conn, struct timeval *tv,
uint16_t size, struct l2cap_chan *chan)
{
struct timeval res;
if (timerisset(&conn->last_rx)) {
timersub(tv, &conn->last_rx, &res);
packet_latency_add(&conn->rx.latency, &res);
plot_add(conn->rx.plot, &res, 1);
}
conn->last_rx = *tv;
stats_add(&conn->rx, tv, size);
conn->rx.num_comp++;
if (chan) {
if (timerisset(&chan->last_rx)) {
timersub(tv, &chan->last_rx, &res);
packet_latency_add(&chan->rx.latency, &res);
plot_add(chan->rx.plot, &res, 1);
}
chan->last_rx = *tv;
stats_add(&chan->rx, tv, size);
chan->rx.num_comp++;
}
}
static void acl_pkt(struct timeval *tv, uint16_t index, bool out,
const void *data, uint16_t size)
{
const struct bt_hci_acl_hdr *hdr = data;
struct hci_dev *dev;
struct hci_conn *conn;
struct l2cap_chan *chan = NULL;
uint16_t cid;
data += sizeof(*hdr);
size -= sizeof(*hdr);
dev = dev_lookup(index);
if (!dev)
return;
dev->num_hci++;
dev->num_acl++;
conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff, 0x00);
if (!conn)
return;
switch (le16_to_cpu(hdr->handle) >> 12) {
case 0x00:
case 0x02:
cid = get_le16(data + 2);
chan = chan_lookup(conn, cid, out);
if (cid == 1)
l2cap_sig(conn, out, data + 4, size - 4);
else if (cid == 5)
l2cap_le_sig(conn, out, data + 4, size - 4);
break;
}
if (out) {
conn_pkt_tx(conn, tv, size, chan);
} else {
conn_pkt_rx(conn, tv, size, chan);
}
}
static void sco_pkt(struct timeval *tv, uint16_t index, bool out,
const void *data, uint16_t size)
{
const struct bt_hci_acl_hdr *hdr = data;
struct hci_dev *dev;
struct hci_conn *conn;
dev = dev_lookup(index);
if (!dev)
return;
dev->num_hci++;
dev->num_sco++;
conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
BTMON_CONN_SCO);
if (!conn) {
conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
BTMON_CONN_ESCO);
if (!conn)
return;
}
if (out) {
conn_pkt_tx(conn, tv, size - sizeof(*hdr), NULL);
} else {
conn_pkt_rx(conn, tv, size - sizeof(*hdr), NULL);
}
}
static void info_index(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
const struct btsnoop_opcode_index_info *hdr = data;
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->manufacturer = hdr->manufacturer;
}
static void vendor_diag(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->vendor_diag++;
}
static void system_note(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->system_note++;
}
static void user_log(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->user_log++;
}
static void ctrl_msg(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->ctrl_msg++;
}
static void iso_pkt(struct timeval *tv, uint16_t index, bool out,
const void *data, uint16_t size)
{
const struct bt_hci_iso_hdr *hdr = data;
struct hci_conn *conn;
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->num_hci++;
dev->num_iso++;
conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
BTMON_CONN_CIS);
if (!conn) {
conn = conn_lookup_type(dev, le16_to_cpu(hdr->handle) & 0x0fff,
BTMON_CONN_BIS);
if (!conn)
return;
}
if (out) {
conn_pkt_tx(conn, tv, size - sizeof(*hdr), NULL);
} else {
conn_pkt_rx(conn, tv, size - sizeof(*hdr), NULL);
}
}
static void unknown_opcode(struct timeval *tv, uint16_t index,
const void *data, uint16_t size)
{
struct hci_dev *dev;
dev = dev_lookup(index);
if (!dev)
return;
dev->unknown++;
}
void analyze_trace(const char *path)
{
struct btsnoop *btsnoop_file;
unsigned long num_packets = 0;
unsigned long num_frames = 0;
uint32_t format;
btsnoop_file = btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT);
if (!btsnoop_file)
return;
format = btsnoop_get_format(btsnoop_file);
switch (format) {
case BTSNOOP_FORMAT_HCI:
case BTSNOOP_FORMAT_UART:
case BTSNOOP_FORMAT_MONITOR:
break;
default:
fprintf(stderr, "Unsupported packet format\n");
goto done;
}
dev_list = queue_new();
while (1) {
unsigned char buf[BTSNOOP_MAX_PACKET_SIZE];
struct timeval tv;
uint16_t index, opcode, pktlen;
if (!btsnoop_read_hci(btsnoop_file, &tv, &index, &opcode,
buf, &pktlen))
break;
switch (opcode) {
case BTSNOOP_OPCODE_NEW_INDEX:
new_index(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_DEL_INDEX:
del_index(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_COMMAND_PKT:
num_frames++;
command_pkt(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_EVENT_PKT:
num_frames++;
event_pkt(&tv, index, num_frames, buf, pktlen);
break;
case BTSNOOP_OPCODE_ACL_TX_PKT:
num_frames++;
acl_pkt(&tv, index, true, buf, pktlen);
break;
case BTSNOOP_OPCODE_ACL_RX_PKT:
num_frames++;
acl_pkt(&tv, index, false, buf, pktlen);
break;
case BTSNOOP_OPCODE_SCO_TX_PKT:
num_frames++;
sco_pkt(&tv, index, true, buf, pktlen);
break;
case BTSNOOP_OPCODE_SCO_RX_PKT:
num_frames++;
sco_pkt(&tv, index, false, buf, pktlen);
break;
case BTSNOOP_OPCODE_OPEN_INDEX:
case BTSNOOP_OPCODE_CLOSE_INDEX:
break;
case BTSNOOP_OPCODE_INDEX_INFO:
info_index(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_VENDOR_DIAG:
vendor_diag(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_SYSTEM_NOTE:
system_note(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_USER_LOGGING:
user_log(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_CTRL_OPEN:
case BTSNOOP_OPCODE_CTRL_CLOSE:
case BTSNOOP_OPCODE_CTRL_COMMAND:
case BTSNOOP_OPCODE_CTRL_EVENT:
ctrl_msg(&tv, index, buf, pktlen);
break;
case BTSNOOP_OPCODE_ISO_TX_PKT:
num_frames++;
iso_pkt(&tv, index, true, buf, pktlen);
break;
case BTSNOOP_OPCODE_ISO_RX_PKT:
num_frames++;
iso_pkt(&tv, index, false, buf, pktlen);
break;
default:
unknown_opcode(&tv, index, buf, pktlen);
break;
}
num_packets++;
}
printf("Trace contains %lu packets\n\n", num_packets);
queue_destroy(dev_list, dev_destroy);
done:
btsnoop_unref(btsnoop_file);
}