diff --git a/extmod/nimble/hal/hal_uart.c b/extmod/nimble/hal/hal_uart.c index 6c17da0860..7713f75d8d 100644 --- a/extmod/nimble/hal/hal_uart.c +++ b/extmod/nimble/hal/hal_uart.c @@ -39,6 +39,9 @@ #endif #define HCI_TRACE (0) +#define COL_OFF "\033[0m" +#define COL_GREEN "\033[0;32m" +#define COL_BLUE "\033[0;34m" static hal_uart_tx_cb_t hal_uart_tx_cb; static void *hal_uart_tx_arg; @@ -71,11 +74,11 @@ void hal_uart_start_tx(uint32_t port) { } #if HCI_TRACE - printf("< [% 8d] %02x", (int)mp_hal_ticks_ms(), mp_bluetooth_hci_cmd_buf[0]); + printf(COL_GREEN "< [% 8d] %02x", (int)mp_hal_ticks_ms(), mp_bluetooth_hci_cmd_buf[0]); for (size_t i = 1; i < len; ++i) { printf(":%02x", mp_bluetooth_hci_cmd_buf[i]); } - printf("\n"); + printf(COL_OFF "\n"); #endif mp_bluetooth_hci_uart_write(mp_bluetooth_hci_cmd_buf, len); @@ -92,7 +95,7 @@ int hal_uart_close(uint32_t port) { STATIC void mp_bluetooth_hci_uart_char_cb(uint8_t chr) { #if HCI_TRACE - printf("> %02x\n", chr); + printf(COL_BLUE "> [% 8d] %02x" COL_OFF "\n", (int)mp_hal_ticks_ms(), chr); #endif hal_uart_rx_cb(hal_uart_rx_arg, chr); } diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index 14afbebcd7..8813ce147c 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -46,7 +46,11 @@ #include #define DEBUG_printf(...) // printf(__VA_ARGS__) -#define DEBUG_HCI_DUMP (0) + +#define HCI_TRACE (0) +#define COL_OFF "\033[0m" +#define COL_GREEN "\033[0;32m" +#define COL_BLUE "\033[0;34m" uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; @@ -234,8 +238,8 @@ int mp_bluetooth_hci_uart_readchar(void) { ssize_t bytes_read = read(uart_fd, &c, 1); if (bytes_read == 1) { - #if DEBUG_HCI_DUMP - printf("[% 8ld] RX: %02x\n", mp_hal_ticks_ms(), c); + #if HCI_TRACE + printf(COL_BLUE "> [% 8ld] RX: %02x" COL_OFF "\n", mp_hal_ticks_ms(), c); #endif return c; } else { @@ -250,12 +254,12 @@ int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { return 0; } - #if DEBUG_HCI_DUMP - printf("[% 8ld] TX: %02x", mp_hal_ticks_ms(), buf[0]); + #if HCI_TRACE + printf(COL_GREEN "< [% 8ld] TX: %02x", mp_hal_ticks_ms(), buf[0]); for (size_t i = 1; i < len; ++i) { printf(":%02x", buf[i]); } - printf("\n"); + printf(COL_OFF "\n"); #endif return write(uart_fd, buf, len); diff --git a/tools/hci_trace_to_pcap.py b/tools/hci_trace_to_pcap.py new file mode 100755 index 0000000000..d55df3fb61 --- /dev/null +++ b/tools/hci_trace_to_pcap.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2023 Jim Mussared +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# Simple script to take the output printed when HCI_TRACE is enabled in +# unix/mpbthciport.c, extmod/btstack/btstack_hci_uart.c, +# extmod/nimble/hal/hal_uart.c and turn it into a .pcap file suitable for use +# with Wireshark. + +import re +import sys +import struct + +# pcap file header: +# typedef struct pcap_hdr_s { +# guint32 magic_number; /* magic number */ +# guint16 version_major; /* major version number */ +# guint16 version_minor; /* minor version number */ +# gint32 thiszone; /* GMT to local correction */ +# guint32 sigfigs; /* accuracy of timestamps */ +# guint32 snaplen; /* max length of captured packets, in octets */ +# guint32 network; /* data link type */ +# } pcap_hdr_t; + +# pcap record header +# typedef struct pcaprec_hdr_s { +# guint32 ts_sec; /* timestamp seconds */ +# guint32 ts_usec; /* timestamp microseconds */ +# guint32 incl_len; /* number of octets of packet saved in file */ +# guint32 orig_len; /* actual length of packet */ +# } pcaprec_hdr_t; + +_LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR = 201 # "!I" direction, followed by data +sys.stdout.buffer.write( + struct.pack("!IHHiIII", 0xA1B2C3D4, 2, 4, 0, 0, 65535, _LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR) +) + +_DIR_CONTROLLER_TO_HOST = 1 +_DIR_HOST_TO_CONTROLLER = 0 + +reassemble_timestamp = 0 +reassemble_packet = bytearray() + +with open(sys.argv[1], "r") as f: + for line in f: + line = line.strip() + m = re.match("([<>]) \\[ *([0-9]+)\\] ([A-Fa-f0-9:]+)", line) + if not m: + continue + + timestamp = int(m.group(2)) + data = bytes.fromhex(m.group(3).replace(":", "")) + + if m.group(1) == "<": + # Host to controller. + # These are always complete. + sys.stdout.buffer.write( + struct.pack( + "!IIIII", + timestamp // 1000, + timestamp % 1000 * 1000, + len(data) + 4, + len(data) + 4, + _DIR_HOST_TO_CONTROLLER, + ) + ) + sys.stdout.buffer.write(data) + if m.group(1) == ">": + # Controller to host. + # Several of the sources print byte-at-a-time so need to reconstruct packets. + + if not reassemble_packet: + # Use the timestamp of the first fragment. + reassemble_timestamp = timestamp + + reassemble_packet.extend(data) + + if len(reassemble_packet) > 4: + plen = 0 + if reassemble_packet[0] == 1: + # command + plen = 3 + reassemble_packet[3] + elif reassemble_packet[0] == 2: + # acl + plen = 5 + reassemble_packet[3] + (reassemble_packet[4] << 8) + elif reassemble_packet[0] == 4: + # event + plen = 3 + reassemble_packet[2] + + if len(reassemble_packet) >= plen: + # Got a complete packet. + data = reassemble_packet[0:plen] + reassemble_packet = reassemble_packet[plen:] + reassemble_timestamp = timestamp + sys.stdout.buffer.write( + struct.pack( + "!IIIII", + reassemble_timestamp // 1000, + reassemble_timestamp % 1000 * 1000, + len(data) + 4, + len(data) + 4, + _DIR_CONTROLLER_TO_HOST, + ) + ) + sys.stdout.buffer.write(data) + +if reassemble_packet: + print( + "Error: Unknown byte in HCI stream. Remainder:", + reassemble_packet.hex(":"), + file=sys.stderr, + )