1 /*
2 * Firmware trace handling on the DHD side. Kernel thread reads the trace data and writes
3 * to the file and implements various utility functions.
4 *
5 * Broadcom Proprietary and Confidential. Copyright (C) 2020,
6 * All Rights Reserved.
7 *
8 * This is UNPUBLISHED PROPRIETARY SOURCE CODE of Broadcom;
9 * the contents of this file may not be disclosed to third parties,
10 * copied or duplicated in any form, in whole or in part, without
11 * the prior written permission of Broadcom.
12 *
13 *
14 * <<Broadcom-WL-IPTag/Proprietary:>>
15 *
16 * $Id$
17 */
18
19 #ifdef BCMINTERNAL
20
21 #ifdef DHD_FWTRACE
22
23 #include <typedefs.h>
24 #include <osl.h>
25
26 #include <bcmutils.h>
27 #include <bcmendian.h>
28
29 #include <dngl_stats.h>
30 #include <dhd.h>
31 #include <dhd_proto.h>
32 #include <dhd_dbg.h>
33 #include <dhd_debug.h>
34
35 #include <dhd_fwtrace.h>
36
37 static int fwtrace_write_to_file(uint8 *buf, uint16 buf_len, dhd_pub_t *dhdp);
38 static int fwtrace_close_file(dhd_pub_t *dhdp);
39 static int fwtrace_open_file(uint32 fw_trace_enabled, dhd_pub_t *dhdp);
40 static fwtrace_buf_t *fwtrace_get_trace_data_ptr(dhd_pub_t *dhdp);
41 static void fwtrace_free_trace_buf(dhd_pub_t *dhdp);
42
43 typedef struct fwtrace_info {
44 struct file *fw_trace_fp;
45 int file_index;
46 int part_index;
47 int trace_buf_index;
48 int trace_buf_count;
49 uint16 overflow_counter;
50
51 char trace_file[TRACE_FILE_NAME_LEN];
52
53 fwtrace_buf_t *trace_data_ptr;
54
55 uint16 prev_seq;
56
57 uint32 fwtrace_enable; /* Enable firmware tracing and the
58 * trace file management.
59 */
60 struct mutex fwtrace_lock; /* Synchronization between the
61 * ioctl and the kernel thread.
62 */
63 dhd_dma_buf_t fwtrace_buf; /* firmware trace buffer */
64 } fwtrace_info_t;
65
66 int
dhd_fwtrace_attach(dhd_pub_t * dhdp)67 dhd_fwtrace_attach(dhd_pub_t *dhdp)
68 {
69 fwtrace_info_t *fwtrace_info;
70
71 /* Allocate prot structure */
72 if (!(fwtrace_info = (fwtrace_info_t *)VMALLOCZ(dhdp->osh, sizeof(*fwtrace_info)))) {
73 DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__));
74 return (BCME_NOMEM);
75 }
76
77 bzero(fwtrace_info, sizeof(*fwtrace_info));
78 dhdp->fwtrace_info = fwtrace_info;
79
80 mutex_init(&dhdp->fwtrace_info->fwtrace_lock);
81
82 DHD_INFO(("allocated DHD fwtrace\n"));
83
84 return BCME_OK;
85 }
86
87 int
dhd_fwtrace_detach(dhd_pub_t * dhdp)88 dhd_fwtrace_detach(dhd_pub_t *dhdp)
89 {
90 fwtrace_info_t *fwtrace_info;
91
92 DHD_TRACE(("%s: %d\n", __FUNCTION__, __LINE__));
93
94 if (!dhdp) {
95 return BCME_OK;
96 }
97
98 if (!dhdp->fwtrace_info) {
99 return BCME_OK;
100 }
101
102 fwtrace_info = dhdp->fwtrace_info;
103
104 dhd_dma_buf_free(dhdp, &dhdp->fwtrace_info->fwtrace_buf);
105
106 /* close the file if valid */
107 if (!(IS_ERR_OR_NULL(dhdp->fwtrace_info->fw_trace_fp))) {
108 (void) filp_close(dhdp->fwtrace_info->fw_trace_fp, 0);
109 }
110
111 mutex_destroy(&dhdp->fwtrace_info->fwtrace_lock);
112
113 VMFREE(dhdp->osh, fwtrace_info, sizeof(*fwtrace_info));
114
115 dhdp->fwtrace_info = NULL;
116
117 DHD_INFO(("Deallocated DHD fwtrace_info\n"));
118
119 return (BCME_OK);
120 }
121
122 uint16
get_fw_trace_overflow_counter(dhd_pub_t * dhdp)123 get_fw_trace_overflow_counter(dhd_pub_t *dhdp)
124 {
125 return (dhdp->fwtrace_info->overflow_counter);
126 }
127
128 void
process_fw_trace_data(dhd_pub_t * dhdp)129 process_fw_trace_data(dhd_pub_t *dhdp)
130 {
131 fwtrace_info_t *fwtrace_info = dhdp->fwtrace_info;
132 uint16 length;
133 uint16 incoming_seq;
134 uint32 trace_buf_index = fwtrace_info->trace_buf_index;
135 fwtrace_buf_t * trace_buf;
136 fwtrace_buf_t * curr_buf;
137
138 mutex_lock(&fwtrace_info->fwtrace_lock);
139
140 if (fwtrace_info->fw_trace_fp == NULL) {
141 goto done;
142 }
143
144 if ((trace_buf = fwtrace_get_trace_data_ptr(dhdp)) == NULL) {
145 goto done;
146 }
147
148 do {
149 curr_buf = trace_buf + trace_buf_index;
150
151 length = curr_buf->info.length;
152 /* If the incoming length is 0, means nothing is updated by the firmware */
153 if (length == 0) {
154 break;
155 }
156
157 incoming_seq = curr_buf->info.seq_num;
158
159 if (((uint16)(fwtrace_info->prev_seq + 1) != incoming_seq) &&
160 length != sizeof(*curr_buf)) {
161 DHD_ERROR(("*** invalid trace len idx = %u, length = %u, "
162 "cur seq = %u, in-seq = %u \n",
163 trace_buf_index, length,
164 fwtrace_info->prev_seq, incoming_seq));
165 break;
166 }
167
168 DHD_TRACE(("*** TRACE BUS: IDX:%d, in-seq:%d(prev-%d), ptr:%p(%llu), len:%d\n",
169 trace_buf_index, incoming_seq, fwtrace_info->prev_seq,
170 curr_buf, (uint64)curr_buf, length));
171
172 /* Write trace data to a file */
173 if (fwtrace_write_to_file((uint8 *) curr_buf, length, dhdp) != BCME_OK) {
174 DHD_ERROR(("*** fwtrace_write_to_file has failed \n"));
175 break;
176 }
177
178 /* Reset length after consuming the fwtrace data */
179 curr_buf->info.length = 0;
180
181 if ((fwtrace_info->prev_seq + 1) != incoming_seq) {
182 DHD_ERROR(("*** seq mismatch, index = %u, length = %u, "
183 "cur seq = %u, in-seq = %u \n",
184 trace_buf_index, length,
185 fwtrace_info->prev_seq, incoming_seq));
186 }
187 fwtrace_info->prev_seq = incoming_seq;
188
189 trace_buf_index++;
190 trace_buf_index &= (fwtrace_info->trace_buf_count - 1u);
191 fwtrace_info->trace_buf_index = trace_buf_index;
192 } while (true);
193
194 done:
195 mutex_unlock(&fwtrace_info->fwtrace_lock);
196 return;
197 }
198
199 /*
200 * Write the incoming trace data to a file. The maximum file size is 1MB. After that
201 * the trace data is saved into a new file.
202 */
203 static int
fwtrace_write_to_file(uint8 * buf,uint16 buf_len,dhd_pub_t * dhdp)204 fwtrace_write_to_file(uint8 *buf, uint16 buf_len, dhd_pub_t *dhdp)
205 {
206 fwtrace_info_t *fwtrace_info = dhdp->fwtrace_info;
207 int ret_val = BCME_OK;
208 int ret_val_1 = 0;
209 mm_segment_t old_fs;
210 loff_t pos = 0;
211 struct kstat stat;
212 int error;
213
214 /* Change to KERNEL_DS address limit */
215 old_fs = get_fs();
216 set_fs(KERNEL_DS);
217
218 if (buf == NULL) {
219 ret_val = BCME_ERROR;
220 goto done;
221 }
222
223 if (IS_ERR_OR_NULL(fwtrace_info->fw_trace_fp)) {
224 ret_val = BCME_ERROR;
225 goto done;
226 }
227
228 //
229 // Get the file size
230 // if the size + buf_len > TRACE_FILE_SIZE, then write to a different file.
231 //
232 error = vfs_stat(fwtrace_info->trace_file, &stat);
233 if (error) {
234 DHD_ERROR(("vfs_stat has failed with error code = %d\n", error));
235 goto done;
236 }
237
238 if ((int) stat.size + buf_len > TRACE_FILE_SIZE) {
239 fwtrace_close_file(dhdp);
240 (fwtrace_info->part_index)++;
241 fwtrace_open_file(TRUE, dhdp);
242 }
243
244 pos = fwtrace_info->fw_trace_fp->f_pos;
245 /* Write buf to file */
246 ret_val_1 = vfs_write(fwtrace_info->fw_trace_fp,
247 (char *) buf, (uint32) buf_len, &pos);
248 if (ret_val_1 < 0) {
249 DHD_ERROR(("write file error, err = %d\n", ret_val_1));
250 ret_val = BCME_ERROR;
251 goto done;
252 }
253 fwtrace_info->fw_trace_fp->f_pos = pos;
254
255 /* Sync file from filesystem to physical media */
256 ret_val_1 = vfs_fsync(fwtrace_info->fw_trace_fp, 0);
257 if (ret_val_1 < 0) {
258 DHD_ERROR(("sync file error, error = %d\n", ret_val_1));
259 ret_val = BCME_ERROR;
260 goto done;
261 }
262
263 done:
264 /* restore previous address limit */
265 set_fs(old_fs);
266 return (ret_val);
267 }
268
269 /*
270 * Start the trace, gets called from the ioctl handler.
271 */
272 int
fw_trace_start(dhd_pub_t * dhdp,uint32 fw_trace_enabled)273 fw_trace_start(dhd_pub_t *dhdp, uint32 fw_trace_enabled)
274 {
275 int ret_val = BCME_OK;
276
277 (dhdp->fwtrace_info->file_index)++;
278 dhdp->fwtrace_info->part_index = 1;
279
280 dhdp->fwtrace_info->trace_buf_index = 0;
281
282 mutex_lock(&dhdp->fwtrace_info->fwtrace_lock);
283 ret_val = fwtrace_open_file(fw_trace_enabled, dhdp);
284 if (ret_val == BCME_OK) {
285 dhdp->fwtrace_info->fwtrace_enable = fw_trace_enabled;
286 }
287 mutex_unlock(&dhdp->fwtrace_info->fwtrace_lock);
288
289 return (ret_val);
290 }
291
292 /*
293 * Stop the trace collection and close the file descriptor.
294 */
295 int
fw_trace_stop(dhd_pub_t * dhdp)296 fw_trace_stop(dhd_pub_t *dhdp)
297 {
298 int ret_val = BCME_OK;
299
300 /* Check to see if there is any trace data */
301 process_fw_trace_data(dhdp);
302
303 mutex_lock(&dhdp->fwtrace_info->fwtrace_lock); /* acquire lock */
304 /* flush the trace buffer */
305 ret_val = fwtrace_close_file(dhdp);
306
307 /* free the trace buffer */
308 fwtrace_free_trace_buf(dhdp);
309 mutex_unlock(&dhdp->fwtrace_info->fwtrace_lock); /* release the lock */
310
311 return (ret_val);
312 }
313
314 /*
315 * The trace file format is: fw_trace_w_part_x_y_z
316 * where w is the file index, x is the part index,
317 * y is in seconds and z is in milliseconds
318 *
319 * fw_trace_1_part_1_1539298163209110
320 * fw_trace_1_part_2_1539298194739003 etc.
321 *
322 */
323 static int
fwtrace_open_file(uint32 fw_trace_enabled,dhd_pub_t * dhdp)324 fwtrace_open_file(uint32 fw_trace_enabled, dhd_pub_t *dhdp)
325 {
326 fwtrace_info_t *fwtrace_info = dhdp->fwtrace_info;
327 int ret_val = BCME_OK;
328 uint32 file_mode;
329 char ts_str[DEBUG_DUMP_TIME_BUF_LEN];
330
331 if (fw_trace_enabled) {
332 if (!(IS_ERR_OR_NULL(fwtrace_info->fw_trace_fp))) {
333 (void) filp_close(fwtrace_info->fw_trace_fp, 0);
334 }
335
336 DHD_INFO((" *** Creating the trace file \n"));
337
338 file_mode = O_CREAT | O_WRONLY | O_SYNC;
339 clear_debug_dump_time(ts_str);
340 get_debug_dump_time(ts_str);
341
342 snprintf(fwtrace_info->trace_file,
343 sizeof(fwtrace_info->trace_file),
344 "%sfw_trace_%d_part_%d_%x_%s",
345 DHD_COMMON_DUMP_PATH, fwtrace_info->file_index,
346 fwtrace_info->part_index,
347 dhd_bus_get_bp_base(dhdp),
348 ts_str);
349
350 fwtrace_info->fw_trace_fp =
351 filp_open(fwtrace_info->trace_file, file_mode, 0664);
352
353 if (IS_ERR(fwtrace_info->fw_trace_fp)) {
354 DHD_ERROR(("Unable to create the fw trace file file: %s\n",
355 fwtrace_info->trace_file));
356 ret_val = BCME_ERROR;
357 goto done;
358 }
359 }
360
361 done:
362 return (ret_val);
363 }
364
365 static int
fwtrace_close_file(dhd_pub_t * dhdp)366 fwtrace_close_file(dhd_pub_t *dhdp)
367 {
368 int ret_val = BCME_OK;
369
370 if (!(IS_ERR_OR_NULL(dhdp->fwtrace_info->fw_trace_fp))) {
371 (void) filp_close(dhdp->fwtrace_info->fw_trace_fp, 0);
372 }
373
374 dhdp->fwtrace_info->fw_trace_fp = NULL;
375
376 return (ret_val);
377 }
378
379 #define FWTRACE_HADDR_PARAMS_SIZE 256u
380 #define FW_TRACE_FLUSH 0x8u /* bit 3 */
381
382 static int send_fw_trace_val(dhd_pub_t *dhdp, int val);
383
384 /*
385 * Initialize FWTRACE.
386 * Allocate trace buffer and open trace file.
387 */
388 int
fwtrace_init(dhd_pub_t * dhdp)389 fwtrace_init(dhd_pub_t *dhdp)
390 {
391 int ret_val = BCME_OK;
392 fwtrace_hostaddr_info_t host_buf_info;
393
394 if (dhdp->fwtrace_info->fwtrace_buf.va != NULL) {
395 /* Already initialized */
396 goto done;
397 }
398
399 ret_val = fwtrace_get_haddr(dhdp, &host_buf_info);
400
401 if (ret_val != BCME_OK) {
402 goto done;
403 }
404
405 DHD_INFO(("dhd_get_trace_haddr: addr = %llx, len = %u\n",
406 host_buf_info.haddr.u64, host_buf_info.num_bufs));
407
408 /* Initialize and setup the file */
409 ret_val = fw_trace_start(dhdp, TRUE);
410
411 done:
412 return ret_val;
413 }
414
415 /*
416 * Process the fwtrace set command to enable/disable firmware tracing.
417 * Always, enable preceeds with disable.
418 */
419 int
handle_set_fwtrace(dhd_pub_t * dhdp,uint32 val)420 handle_set_fwtrace(dhd_pub_t *dhdp, uint32 val)
421 {
422 int ret, ret_val = BCME_OK;
423
424 /* On set, consider only lower two bytes for now */
425 dhdp->fwtrace_info->fwtrace_enable = (val & 0xFFFF);
426
427 if (val & FW_TRACE_FLUSH) { /* only flush the trace buffer */
428 if ((ret_val = send_fw_trace_val(dhdp, val)) != BCME_OK) {
429 goto done;
430 }
431 } else if (val == 0) { /* disable the tracing */
432 /* Disable the trace in the firmware */
433 if ((ret_val = send_fw_trace_val(dhdp, val)) != BCME_OK) {
434 goto done;
435 }
436
437 /* cleanup in the driver */
438 fw_trace_stop(dhdp);
439 } else { /* enable the tracing */
440 fwtrace_hostaddr_info_t haddr_info;
441
442 ret_val = fwtrace_init(dhdp);
443 if (ret_val != BCME_OK) {
444 goto done;
445 }
446
447 if ((ret_val = fwtrace_get_haddr(dhdp, &haddr_info)) != BCME_OK) {
448 DHD_ERROR(("%s: set dhd_iovar has failed for "
449 "fw_trace_haddr, "
450 "ret=%d\n", __FUNCTION__, ret_val));
451 goto done;
452 }
453
454 ret = dhd_iovar(dhdp, 0, "dngl:fwtrace_haddr",
455 (char *) &haddr_info, sizeof(haddr_info),
456 NULL, 0, TRUE);
457 if (ret < 0) {
458 DHD_ERROR(("%s: set dhd_iovar has failed for "
459 "fwtrace_haddr, "
460 "ret=%d\n", __FUNCTION__, ret));
461 ret_val = BCME_NOMEM;
462 goto done;
463 }
464
465 /* Finaly, enable the trace in the firmware */
466 if ((ret_val = send_fw_trace_val(dhdp, val)) != BCME_OK) {
467 goto done;
468 }
469 }
470 done:
471 return (ret_val);
472 }
473
474 /*
475 * Send dngl:fwtrace IOVAR to the firmware.
476 */
477
478 static int
send_fw_trace_val(dhd_pub_t * dhdp,int val)479 send_fw_trace_val(dhd_pub_t *dhdp, int val)
480 {
481 int ret_val = BCME_OK;
482
483 if ((ret_val = dhd_iovar(dhdp, 0, "dngl:fwtrace", (char *)&val, sizeof(val),
484 NULL, 0, TRUE)) < 0) {
485 DHD_ERROR(("%s: set dhd_iovar has failed fwtrace, "
486 "ret=%d\n", __FUNCTION__, ret_val));
487 }
488
489 return (ret_val);
490 }
491
492 /*
493 * Returns the virual address for the firmware trace buffer.
494 * DHD monitors this buffer for an update from the firmware.
495 */
496 static fwtrace_buf_t *
fwtrace_get_trace_data_ptr(dhd_pub_t * dhdp)497 fwtrace_get_trace_data_ptr(dhd_pub_t *dhdp)
498 {
499 return ((fwtrace_buf_t *) dhdp->fwtrace_info->fwtrace_buf.va);
500 }
501
502 int
fwtrace_get_haddr(dhd_pub_t * dhdp,fwtrace_hostaddr_info_t * haddr_info)503 fwtrace_get_haddr(dhd_pub_t *dhdp, fwtrace_hostaddr_info_t *haddr_info)
504 {
505 int ret_val = BCME_NOMEM;
506 int num_host_buffers = FWTRACE_NUM_HOST_BUFFERS;
507
508 if (haddr_info == NULL) {
509 ret_val = BCME_BADARG;
510 goto done;
511 }
512
513 if (dhdp->fwtrace_info->fwtrace_buf.va != NULL) {
514 /* Use the existing buffer and send to the firmware */
515 haddr_info->haddr.u64 = HTOL64(*(uint64 *)
516 &dhdp->fwtrace_info->fwtrace_buf.pa);
517 haddr_info->num_bufs = dhdp->fwtrace_info->trace_buf_count;
518 haddr_info->buf_len = sizeof(fwtrace_buf_t);
519 ret_val = BCME_OK;
520 goto done;
521 }
522
523 do {
524 /* Initialize firmware trace buffer */
525 if (dhd_dma_buf_alloc(dhdp, &dhdp->fwtrace_info->fwtrace_buf,
526 sizeof(fwtrace_buf_t) * num_host_buffers) == BCME_OK) {
527 dhdp->fwtrace_info->trace_buf_count = num_host_buffers;
528 ret_val = BCME_OK;
529 break;
530 }
531
532 DHD_ERROR(("%s: Allocing %d buffers of size %lu bytes failed\n",
533 __FUNCTION__, num_host_buffers,
534 sizeof(fwtrace_buf_t) * num_host_buffers));
535
536 /* Retry with smaller numbers */
537 num_host_buffers >>= 1;
538 } while (num_host_buffers > 0);
539
540 haddr_info->haddr.u64 = HTOL64(*(uint64 *)&dhdp->fwtrace_info->fwtrace_buf.pa);
541 haddr_info->num_bufs = num_host_buffers;
542 haddr_info->buf_len = sizeof(fwtrace_buf_t);
543
544 DHD_INFO(("Firmware trace buffer, host address = %llx, count = %u \n",
545 haddr_info->haddr.u64,
546 haddr_info->num_bufs));
547 done:
548 return (ret_val);
549 }
550
551 /*
552 * Frees the host buffer.
553 */
554 static void
fwtrace_free_trace_buf(dhd_pub_t * dhdp)555 fwtrace_free_trace_buf(dhd_pub_t *dhdp)
556 {
557 dhd_dma_buf_free(dhdp, &dhdp->fwtrace_info->fwtrace_buf);
558 return;
559 }
560
561 #endif /* DHD_FWTRACE */
562
563 #endif /* BCMINTERNAL */
564