xref: /btstack/src/hci_dump.c (revision 5fa31a9940804989b94f828b5982860dcb81c77d)
1 /*
2  * Copyright (C) 2014 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
24  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define __BTSTACK_FILE__ "hci_dump.c"
39 
40 /*
41  *  hci_dump.c
42  *
43  *  Dump HCI trace in various formats:
44  *
45  *  - BlueZ's hcidump format
46  *  - Apple's PacketLogger
47  *  - stdout hexdump
48  *
49  */
50 
51 #include "btstack_config.h"
52 
53 // enable POSIX functions (needed for -std=c99)
54 #define _POSIX_C_SOURCE 200809
55 
56 #include "hci_dump.h"
57 #include "hci.h"
58 #include "hci_transport.h"
59 #include "hci_cmd.h"
60 #include "btstack_run_loop.h"
61 #include <stdio.h>
62 
63 #ifdef HAVE_POSIX_FILE_IO
64 #include <fcntl.h>        // open
65 #include <unistd.h>       // write
66 #include <time.h>
67 #include <sys/time.h>     // for timestamps
68 #include <sys/stat.h>     // for mode flags
69 #endif
70 
71 #ifdef ENABLE_SEGGER_RTT
72 #include "SEGGER_RTT.h"
73 static char channel1_out[1024];
74 #endif
75 
76 // BLUEZ hcidump - struct not used directly, but left here as documentation
77 typedef struct {
78     uint16_t    len;
79     uint8_t     in;
80     uint8_t     pad;
81     uint32_t    ts_sec;
82     uint32_t    ts_usec;
83     uint8_t     packet_type;
84 }
85 hcidump_hdr;
86 #define HCIDUMP_HDR_SIZE 13
87 
88 // APPLE PacketLogger - struct not used directly, but left here as documentation
89 typedef struct {
90     uint32_t    len;
91     uint32_t    ts_sec;
92     uint32_t    ts_usec;
93     uint8_t     type;   // 0xfc for note
94 }
95 pktlog_hdr;
96 #define PKTLOG_HDR_SIZE 13
97 
98 static int dump_file = -1;
99 static int dump_format;
100 static union {
101     uint8_t header_bluez[HCIDUMP_HDR_SIZE];
102     uint8_t header_packetlogger[PKTLOG_HDR_SIZE];
103 } header;
104 #ifdef HAVE_POSIX_FILE_IO
105 static char time_string[40];
106 static int  max_nr_packets = -1;
107 static int  nr_packets = 0;
108 static char log_message_buffer[256];
109 #endif
110 
111 // levels: debug, info, error
112 static int log_level_enabled[3] = { 1, 1, 1};
113 
114 void hci_dump_open(const char *filename, hci_dump_format_t format){
115 
116     dump_format = format;
117 
118 #ifdef HAVE_POSIX_FILE_IO
119     if (dump_format == HCI_DUMP_STDOUT) {
120         dump_file = fileno(stdout);
121     } else {
122 
123         int oflags = O_WRONLY | O_CREAT | O_TRUNC;
124 #ifdef _WIN32
125         oflags |= O_BINARY;
126 #endif
127         dump_file = open(filename, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
128         if (dump_file < 0){
129             printf("hci_dump_open: failed to open file %s\n", filename);
130         }
131     }
132 #else
133 
134     UNUSED(filename);
135 
136 #ifdef ENABLE_SEGGER_RTT
137     switch (dump_format){
138         case HCI_DUMP_PACKETLOGGER:
139         case HCI_DUMP_BLUEZ:
140             // Configure up channel 1, options:
141             // - SEGGER_RTT_MODE_NO_BLOCK_SKIP
142             // - SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL
143             // Note: with SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL, firwmware will hang if RTT not supported/debug probe not connected
144             // Note: with SEGGER_RTT_MODE_NO_BLOCK_SKIP, there's a chance for log file corruption if second write (packet) is skipped
145             SEGGER_RTT_ConfigUpBuffer(1, "hci_dump", &channel1_out[0], sizeof(channel1_out), SEGGER_RTT_MODE_NO_BLOCK_SKIP) ;;
146             break;
147         default:
148             break;
149     }
150 #endif
151 
152     dump_file = 1;
153 #endif
154 }
155 
156 #ifdef HAVE_POSIX_FILE_IO
157 void hci_dump_set_max_packets(int packets){
158     max_nr_packets = packets;
159 }
160 #endif
161 
162 static void hci_dump_packetlogger_setup_header(uint8_t * buffer, uint32_t tv_sec, uint32_t tv_us, uint8_t packet_type, uint8_t in, uint16_t len){
163     big_endian_store_32( buffer, 0, PKTLOG_HDR_SIZE - 4 + len);
164     big_endian_store_32( buffer, 4, tv_sec);
165     big_endian_store_32( buffer, 8, tv_us);
166     uint8_t packet_logger_type = 0;
167     switch (packet_type){
168         case HCI_COMMAND_DATA_PACKET:
169             packet_logger_type = 0x00;
170             break;
171         case HCI_ACL_DATA_PACKET:
172             packet_logger_type = in ? 0x03 : 0x02;
173             break;
174         case HCI_SCO_DATA_PACKET:
175             packet_logger_type = in ? 0x09 : 0x08;
176             break;
177         case HCI_EVENT_PACKET:
178             packet_logger_type = 0x01;
179             break;
180         case LOG_MESSAGE_PACKET:
181             packet_logger_type = 0xfc;
182             break;
183         default:
184             return;
185     }
186     buffer[12] = packet_logger_type;
187 }
188 
189 static void hci_dump_bluez_setup_header(uint8_t * buffer, uint32_t tv_sec, uint32_t tv_us, uint8_t packet_type, uint8_t in, uint16_t len){
190     little_endian_store_16( buffer, 0, 1 + len);
191     buffer[2] = in;
192     buffer[3] = 0;
193     little_endian_store_32( buffer, 4, tv_sec);
194     little_endian_store_32( buffer, 8, tv_us);
195     buffer[12] = packet_type;
196 }
197 
198 static void printf_packet(uint8_t packet_type, uint8_t in, uint8_t * packet, uint16_t len){
199     switch (packet_type){
200         case HCI_COMMAND_DATA_PACKET:
201             printf("CMD => ");
202             break;
203         case HCI_EVENT_PACKET:
204             printf("EVT <= ");
205             break;
206         case HCI_ACL_DATA_PACKET:
207             if (in) {
208                 printf("ACL <= ");
209             } else {
210                 printf("ACL => ");
211             }
212             break;
213         case HCI_SCO_DATA_PACKET:
214             if (in) {
215                 printf("SCO <= ");
216             } else {
217                 printf("SCO => ");
218             }
219             break;
220         case LOG_MESSAGE_PACKET:
221             printf("LOG -- %s\n", (char*) packet);
222             return;
223         default:
224             return;
225     }
226     printf_hexdump(packet, len);
227 }
228 
229 static void printf_timestamp(void){
230 #ifdef HAVE_POSIX_FILE_IO
231     struct tm* ptm;
232     struct timeval curr_time;
233     gettimeofday(&curr_time, NULL);
234     time_t curr_time_secs = curr_time.tv_sec;
235     /* Obtain the time of day, and convert it to a tm struct. */
236     ptm = localtime (&curr_time_secs);
237     /* assert localtime was successful */
238     if (!ptm) return;
239     /* Format the date and time, down to a single second. */
240     strftime (time_string, sizeof (time_string), "[%Y-%m-%d %H:%M:%S", ptm);
241     /* Compute milliseconds from microseconds. */
242     uint16_t milliseconds = curr_time.tv_usec / 1000;
243     /* Print the formatted time, in seconds, followed by a decimal point and the milliseconds. */
244     printf ("%s.%03u] ", time_string, milliseconds);
245 #else
246     uint32_t time_ms = btstack_run_loop_get_time_ms();
247     int      seconds = time_ms / 1000;
248     int      minutes = seconds / 60;
249     unsigned int hours = minutes / 60;
250 
251     uint16_t p_ms      = time_ms - (seconds * 1000);
252     uint16_t p_seconds = seconds - (minutes * 60);
253     uint16_t p_minutes = minutes - (hours   * 60);
254     printf("[%02u:%02u:%02u.%03u] ", hours, p_minutes, p_seconds, p_ms);
255 #endif
256 }
257 
258 void hci_dump_packet(uint8_t packet_type, uint8_t in, uint8_t *packet, uint16_t len) {
259 
260     if (dump_file < 0) return; // not activated yet
261 
262 #ifdef HAVE_POSIX_FILE_IO
263     // don't grow bigger than max_nr_packets
264     if (dump_format != HCI_DUMP_STDOUT && max_nr_packets > 0){
265         if (nr_packets >= max_nr_packets){
266             lseek(dump_file, 0, SEEK_SET);
267             // avoid -Wunused-result
268             int res = ftruncate(dump_file, 0);
269             UNUSED(res);
270             nr_packets = 0;
271         }
272         nr_packets++;
273     }
274 #endif
275 
276     if (dump_format == HCI_DUMP_STDOUT){
277         printf_timestamp();
278         printf_packet(packet_type, in, packet, len);
279         return;
280     }
281 
282     uint32_t tv_sec = 0;
283     uint32_t tv_us  = 0;
284 
285     // get time
286 #ifdef HAVE_POSIX_FILE_IO
287     struct timeval curr_time;
288     gettimeofday(&curr_time, NULL);
289     tv_sec = curr_time.tv_sec;
290     tv_us  = curr_time.tv_usec;
291 #else
292     uint32_t time_ms = btstack_run_loop_get_time_ms();
293     tv_us   = time_ms * 1000;
294     tv_sec  = 946728000UL + (time_ms / 1000);
295 #endif
296 
297     uint16_t header_len = 0;
298     switch (dump_format){
299         case HCI_DUMP_BLUEZ:
300             hci_dump_bluez_setup_header(header.header_bluez, tv_sec, tv_us, packet_type, in, len);
301             header_len = HCIDUMP_HDR_SIZE;
302             break;
303         case HCI_DUMP_PACKETLOGGER:
304             hci_dump_packetlogger_setup_header(header.header_packetlogger, tv_sec, tv_us, packet_type, in, len);
305             header_len = PKTLOG_HDR_SIZE;
306             break;
307         default:
308             return;
309     }
310 
311 #ifdef HAVE_POSIX_FILE_IO
312     // avoid -Wunused-result
313     int res = 0;
314     res = write (dump_file, &header, header_len);
315     res = write (dump_file, packet, len );
316     UNUSED(res);
317 #endif
318 #ifdef ENABLE_SEGGER_RTT
319     SEGGER_RTT_Write(1, &header, header_len);
320     SEGGER_RTT_Write(1, packet, len);
321 #endif
322     UNUSED(header_len);
323 }
324 
325 static int hci_dump_log_level_active(int log_level){
326     if (log_level < HCI_DUMP_LOG_LEVEL_DEBUG) return 0;
327     if (log_level > HCI_DUMP_LOG_LEVEL_ERROR) return 0;
328     return log_level_enabled[log_level];
329 }
330 
331 void hci_dump_log_va_arg(int log_level, const char * format, va_list argptr){
332     if (!hci_dump_log_level_active(log_level)) return;
333 
334 #ifdef HAVE_POSIX_FILE_IO
335     if (dump_file >= 0){
336         int len = vsnprintf(log_message_buffer, sizeof(log_message_buffer), format, argptr);
337         hci_dump_packet(LOG_MESSAGE_PACKET, 0, (uint8_t*) log_message_buffer, len);
338         return;
339     }
340 #endif
341 
342     printf_timestamp();
343     printf("LOG -- ");
344     vprintf(format, argptr);
345     printf("\n");
346 }
347 
348 void hci_dump_log(int log_level, const char * format, ...){
349     va_list argptr;
350     va_start(argptr, format);
351     hci_dump_log_va_arg(log_level, format, argptr);
352     va_end(argptr);
353 }
354 
355 #ifdef __AVR__
356 void hci_dump_log_P(int log_level, PGM_P format, ...){
357     if (!hci_dump_log_level_active(log_level)) return;
358     va_list argptr;
359     va_start(argptr, format);
360     printf_P(PSTR("LOG -- "));
361     vfprintf_P(stdout, format, argptr);
362     printf_P(PSTR("\n"));
363     va_end(argptr);
364 }
365 #endif
366 
367 void hci_dump_close(void){
368 #ifdef HAVE_POSIX_FILE_IO
369     close(dump_file);
370 #endif
371     dump_file = -1;
372 }
373 
374 void hci_dump_enable_log_level(int log_level, int enable){
375     if (log_level < HCI_DUMP_LOG_LEVEL_DEBUG) return;
376     if (log_level > HCI_DUMP_LOG_LEVEL_ERROR) return;
377     log_level_enabled[log_level] = enable;
378 }
379 
380