blob: 847412fb3fd8906773634348a19d802fdef6ee84 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2021 Google LLC
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "src/shared/util.h"
#include "display.h"
#include "packet.h"
#include "vendor.h"
#include "aosp.h"
#include "stats.h"
#define BLUETOOTH_CLOCK_MS(counter) 0.3125 * (counter)
#define BASEBAND_SLOT_MS(counter) 0.625 * (counter)
/*
* The statistics are calculated over a partial list of BQR subevent indices
* over which the statistics are of interest.
*/
static const uint8_t bqr_stats_subevt_list[] = {
TX_POWER_LEVEL,
RSSI,
SNR,
UNUSED_AFH_CHANNEL_COUNT,
AFH_SELECT_UNIDEAL_CHANNEL_COUNT,
RETRANSMISSION_COUNT,
NO_RX_COUNT,
NAK_COUNT,
FLOW_OFF_COUNT,
BUFFER_OVERFLOW_BYTES,
BUFFER_UNDERFLOW_BYTES,
};
/* --- xxxx_subevt functions called from packet.c --- */
static void bqr_id_subevt(const struct bqr *r, const char *subevt_name)
{
const char *str;
switch (r->quality_report_id) {
case 0x01:
str = "Quality reporting on the monitoring mode";
break;
case 0x02:
str = "Approaching LSTO";
break;
case 0x03:
str = "A2DP Audio Choppy";
break;
case 0x04:
str = "(e)SCO Voice Choppy";
break;
default:
str = "Unknown";
break;
}
print_field("%s: 0x%2.2x (%s)", subevt_name, r->quality_report_id, str);
}
/* packet types */
enum packet_types {
PACKET_ID = 0x01,
PACKET_NULL,
PACKET_POLL,
PACKET_FHS,
PACKET_HV1,
PACKET_HV2,
PACKET_HV3,
PACKET_DV,
PACKET_EV3,
PACKET_EV4,
PACKET_EV5,
PACKET_2EV3,
PACKET_2EV5,
PACKET_3EV3,
PACKET_3EV5,
PACKET_DH1 = 0x11,
PACKET_DM3,
PACKET_DH3,
PACKET_DM5,
PACKET_DH5,
PACKET_AUX1,
PACKET_2DH1,
PACKET_2DH3,
PACKET_2DH5,
PACKET_3DH1,
PACKET_3DH3,
PACKET_3DH5,
};
static inline const char *packet_type_to_string(
const enum packet_types packet_type)
{
switch (packet_type) {
case PACKET_ID:
return "ID";
case PACKET_NULL:
return "NULL";
case PACKET_POLL:
return "POLL";
case PACKET_FHS:
return "FHS";
case PACKET_HV1:
return "HV1";
case PACKET_HV2:
return "HV2";
case PACKET_HV3:
return "HV3";
case PACKET_DV:
return "DV";
case PACKET_EV3:
return "EV3";
case PACKET_EV4:
return "EV4";
case PACKET_EV5:
return "EV5";
case PACKET_2EV3:
return "2EV3";
case PACKET_2EV5:
return "2EV5";
case PACKET_3EV3:
return "3EV3";
case PACKET_3EV5:
return "3EV5";
case PACKET_DH1:
return "DH1";
case PACKET_DM3:
return "DM3";
case PACKET_DH3:
return "DH3";
case PACKET_DM5:
return "DM5";
case PACKET_DH5:
return "DH5";
case PACKET_AUX1:
return "AUX1";
case PACKET_2DH1:
return "2DH1";
case PACKET_2DH3:
return "2DH3";
case PACKET_2DH5:
return "2DH5";
case PACKET_3DH1:
return "3DH1";
case PACKET_3DH3:
return "3DH3";
case PACKET_3DH5:
return "3DH5";
}
return "Unknown";
}
static void packet_type_subevt(const struct bqr *r, const char *subevt_name)
{
const char *str;
str = packet_type_to_string(r->packet_type);
print_field("%s: 0x%2.2x (%s)", subevt_name, r->packet_type, str);
}
static void conn_handle_subevt(const struct bqr *r, const char *subevt_name)
{
uint16_t conn_handle = get_le16(&r->conn_handle);
print_field("%s: 0x%4.4x", subevt_name, conn_handle);
}
static void conn_role_subevt(const struct bqr *r, const char *subevt_name)
{
const char *str;
switch (r->conn_role) {
case 0x00:
str = "Central";
break;
case 0x01:
str = "Peripheral";
break;
default:
str = "Unknown";
break;
}
print_field("%s: 0x%2.2x (%s)", subevt_name, r->conn_role, str);
}
static void tx_power_level_subevt(const struct bqr *r, const char *subevt_name)
{
print_field("%s: %" PRId8 " (dbm)", subevt_name, r->tx_power_level);
}
static void rssi_subevt(const struct bqr *r, const char *subevt_name)
{
print_field("%s: % " PRId8 " (dbm)", subevt_name, r->rssi);
}
static void snr_subevt(const struct bqr *r, const char *subevt_name)
{
print_field("%s: %" PRIu8 " (db)", subevt_name, r->snr);
}
static void unused_afh_channel_count_subevt(const struct bqr *r,
const char *subevt_name)
{
print_field("%s: %" PRIu8, subevt_name, r->unused_afh_channel_count);
}
static void afh_select_unideal_channel_count_subevt(const struct bqr *r,
const char *subevt_name)
{
print_field("%s: %" PRIu8, subevt_name,
r->afh_select_unideal_channel_count);
}
static void lsto_subevt(const struct bqr *r, const char *subevt_name)
{
float lsto = BASEBAND_SLOT_MS(get_le16(&r->lsto));
print_field("%s: %.2f (ms)", subevt_name, lsto);
}
static void conn_piconet_clock_subevt(const struct bqr *r,
const char *subevt_name)
{
float conn_piconet_clock =
BLUETOOTH_CLOCK_MS(get_le32(&r->conn_piconet_clock));
print_field("%s: %.2f (ms)", subevt_name, conn_piconet_clock);
}
static void retransmission_count_subevt(const struct bqr *r,
const char *subevt_name)
{
uint32_t retransmission_count = get_le32(&r->retransmission_count);
print_field("%s: %" PRIu32, subevt_name, retransmission_count);
}
static void no_rx_count_subevt(const struct bqr *r, const char *subevt_name)
{
uint32_t no_rx_count = get_le32(&r->no_rx_count);
print_field("%s: %" PRIu32, subevt_name, no_rx_count);
}
static void nak_count_subevt(const struct bqr *r, const char *subevt_name)
{
uint32_t nak_count = get_le32(&r->nak_count);
print_field("%s: %" PRIu32, subevt_name, nak_count);
}
static void last_tx_ack_timestamp_subevt(const struct bqr *r,
const char *subevt_name)
{
float last_tx_ack_timestamp =
BLUETOOTH_CLOCK_MS(get_le32(&r->last_tx_ack_timestamp));
print_field("%s: %.2f (ms)", subevt_name, last_tx_ack_timestamp);
}
static void flow_off_count_subevt(const struct bqr *r, const char *subevt_name)
{
uint32_t flow_off_count = get_le32(&r->flow_off_count);
print_field("%s: %" PRIu32, subevt_name, flow_off_count);
}
static void last_flow_on_timestamp_subevt(const struct bqr *r,
const char *subevt_name)
{
float last_flow_on_timestamp =
BLUETOOTH_CLOCK_MS(get_le32(&r->last_flow_on_timestamp));
print_field("%s: %.2f (ms)", subevt_name, last_flow_on_timestamp);
}
static void buffer_overflow_bytes_subevt(const struct bqr *r,
const char *subevt_name)
{
uint32_t buffer_overflow_bytes = get_le32(&r->buffer_overflow_bytes);
print_field("%s: %" PRIu32 " (bytes)", subevt_name,
buffer_overflow_bytes);
}
static void buffer_underflow_bytes_subevt(const struct bqr *r,
const char *subevt_name)
{
uint32_t buffer_underflow_bytes = get_le32(&r->buffer_underflow_bytes);
print_field("%s: %" PRIu32 " (bytes)", subevt_name,
buffer_underflow_bytes);
}
static void vs_params_subevt(const void *data, uint8_t size)
{
print_field("Vendor Specific Parameters: %" PRIu8 " (octets)", size);
packet_hexdump(data, size);
}
/* --- ana_xxxx_subevt functions called from analyze.c --- */
static void ana_conn_handle_subevt(const struct bqr *r)
{
uint16_t conn_handle = get_le16(&r->conn_handle);
set_bqr_stats_current(conn_handle, bqr_stats_subevt_list,
ARRAY_SIZE(bqr_stats_subevt_list));
}
static void ana_tx_power_level_subevt(const struct bqr *r)
{
subevt_add(TX_POWER_LEVEL, &r->tx_power_level);
}
static void ana_rssi_subevt(const struct bqr *r)
{
subevt_add(RSSI, &r->rssi);
}
static void ana_snr_subevt(const struct bqr *r)
{
subevt_add(SNR, &r->snr);
}
static void ana_unused_afh_channel_count_subevt(const struct bqr *r)
{
subevt_add(UNUSED_AFH_CHANNEL_COUNT, &r->unused_afh_channel_count);
}
static void ana_afh_select_unideal_channel_count_subevt(const struct bqr *r)
{
subevt_add(AFH_SELECT_UNIDEAL_CHANNEL_COUNT,
&r->afh_select_unideal_channel_count);
}
static void ana_retransmission_count_subevt(const struct bqr *r)
{
uint32_t retransmission_count = get_le32(&r->retransmission_count);
subevt_add(RETRANSMISSION_COUNT, &retransmission_count);
}
static void ana_no_rx_count_subevt(const struct bqr *r)
{
uint32_t no_rx_count = get_le32(&r->no_rx_count);
subevt_add(NO_RX_COUNT, &no_rx_count);
}
static void ana_nak_count_subevt(const struct bqr *r)
{
uint32_t nak_count = get_le32(&r->nak_count);
subevt_add(NAK_COUNT, &nak_count);
}
static void ana_flow_off_count_subevt(const struct bqr *r)
{
uint32_t flow_off_count = get_le32(&r->flow_off_count);
subevt_add(FLOW_OFF_COUNT, &flow_off_count);
}
static void ana_buffer_overflow_bytes_subevt(const struct bqr *r)
{
uint32_t buffer_overflow_bytes = get_le32(&r->buffer_overflow_bytes);
subevt_add(BUFFER_OVERFLOW_BYTES, &buffer_overflow_bytes);
}
static void ana_buffer_underflow_bytes_subevt(const struct bqr *r)
{
uint32_t buffer_underflow_bytes = get_le32(&r->buffer_underflow_bytes);
subevt_add(BUFFER_UNDERFLOW_BYTES, &buffer_underflow_bytes);
}
static void ana_vs_params_subevt(const void *data, uint8_t size)
{
}
/* --- the subevent table for both xxxx_subevt and ana_xxxx_subevt --- */
struct bqr_subevt_data {
enum bqr_subevt_list subevt_idx;
const char *subevt_name;
void (*func)(const struct bqr *r, const char *subevt_name);
void (*ana_func)(const struct bqr *r);
enum int_types int_type;
};
static const struct bqr_subevt_data bqr_subevt_table[] = {
/* The sequence of subevents is fixed. There are no subevent codes. */
{ QUALITY_REPORT_ID, "Quality report id", bqr_id_subevt, NULL, UINT8 },
{ PACKET_TYPE, "Packet type", packet_type_subevt, NULL, UINT8 },
{ CONNECTION_HANDLE, "Connection handle",
conn_handle_subevt, ana_conn_handle_subevt, UINT16 },
{ CONNECTION_ROLE, "Connection role", conn_role_subevt, NULL, UINT8 },
{ TX_POWER_LEVEL, "Tx power level",
tx_power_level_subevt, ana_tx_power_level_subevt, INT8 },
{ RSSI, "RSSI", rssi_subevt, ana_rssi_subevt, INT8 },
{ SNR, "SNR", snr_subevt, ana_snr_subevt, UINT8 },
{ UNUSED_AFH_CHANNEL_COUNT, "Unused AFH channel count",
unused_afh_channel_count_subevt,
ana_unused_afh_channel_count_subevt, UINT8 },
{ AFH_SELECT_UNIDEAL_CHANNEL_COUNT, "AFH select unideal channel count",
afh_select_unideal_channel_count_subevt,
ana_afh_select_unideal_channel_count_subevt, UINT8 },
{ LSTO, "LSTO", lsto_subevt, NULL, UINT16 },
{ CONNECTION_PICONET_CLOCK, "Connection piconet clock",
conn_piconet_clock_subevt, NULL, UINT32 },
{ RETRANSMISSION_COUNT, "Retransmission count",
retransmission_count_subevt,
ana_retransmission_count_subevt, UINT32 },
{ NO_RX_COUNT, "No rx count",
no_rx_count_subevt, ana_no_rx_count_subevt, UINT32 },
{ NAK_COUNT, "NAK count",
nak_count_subevt, ana_nak_count_subevt, UINT32 },
{ LAST_TX_ACK_TIMESTAMP, "Last tx ack timestamp",
last_tx_ack_timestamp_subevt, NULL, UINT32 },
{ FLOW_OFF_COUNT, "Flow off count",
flow_off_count_subevt, ana_flow_off_count_subevt, UINT32 },
{ LAST_FLOW_ON_TIMESTAMP, "Last flow on timestamp",
last_flow_on_timestamp_subevt, NULL, UINT32 },
{ BUFFER_OVERFLOW_BYTES, "Buffer overflow bytes",
buffer_overflow_bytes_subevt,
ana_buffer_overflow_bytes_subevt, UINT32 },
{ BUFFER_UNDERFLOW_BYTES, "Buffer underflow bytes",
buffer_underflow_bytes_subevt,
ana_buffer_underflow_bytes_subevt, UINT32 },
{}
};
void aosp_get_subevt_info(uint8_t subevt_idx,
struct subevt_info_data *subevt_info)
{
const struct bqr_subevt_data *subevt = &bqr_subevt_table[subevt_idx];
subevt_info->name = subevt->subevt_name;
/* An AOSP quality subevent always contains exactly 1 slot. */
subevt_info->num_slots = 1;
subevt_info->int_type = subevt->int_type;
subevt_info->size = get_int_size(subevt->int_type);
}
static void aosp_bqr_evt(const void *data, uint8_t size)
{
const struct bqr *r = data;
int i;
if (!data)
return;
if (size < STRUCT_BQR_SIZE) {
print_field("quality report data too short (0x%02x)", size);
packet_hexdump(data, size);
return;
}
/* The report ids 0x01 ~ 0x04 are about link quality. */
if (r->quality_report_id < 0x01 || r->quality_report_id > 0x04) {
print_field("Unsupported quality report id (0x%02x)",
r->quality_report_id);
packet_hexdump(data, size);
return;
}
for (i = 0; bqr_subevt_table[i].subevt_name; i++) {
const struct bqr_subevt_data *subevt = &bqr_subevt_table[i];
if (is_packet_mode())
subevt->func(r, subevt->subevt_name);
else if (subevt->ana_func)
subevt->ana_func(r);
}
/* vs_params_subevt is optional */
if (is_packet_mode())
vs_params_subevt(data + STRUCT_BQR_SIZE,
size - STRUCT_BQR_SIZE);
else
ana_vs_params_subevt(data + STRUCT_BQR_SIZE,
size - STRUCT_BQR_SIZE);
bqr_stats_post();
}
/*
* AOSP vendor specific HCI events
* https://source.android.com/devices/bluetooth/hci_requirements
*/
static const struct vendor_evt vendor_evt_table[] = {
{ 0x58, "AOSP Bluetooth Quality Report",
aosp_bqr_evt, STRUCT_BQR_SIZE },
{ }
};
const struct vendor_evt *aosp_vendor_evt(uint8_t evt)
{
int i;
for (i = 0; vendor_evt_table[i].str; i++) {
if (vendor_evt_table[i].evt == evt)
return &vendor_evt_table[i];
}
return NULL;
}