summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/intel/iwlwifi/fw/dump.c
blob: 358fc2d999d588339251299c05d1395c7c10b7fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2012-2014, 2018-2021 Intel Corporation
 * Copyright (C) 2013-2014 Intel Mobile Communications GmbH
 * Copyright (C) 2015-2017 Intel Deutschland GmbH
 */
#include <linux/devcoredump.h>
#include "iwl-drv.h"
#include "runtime.h"
#include "dbg.h"
#include "debugfs.h"
#include "iwl-io.h"
#include "iwl-prph.h"
#include "iwl-csr.h"

/*
 * Note: This structure is read from the device with IO accesses,
 * and the reading already does the endian conversion. As it is
 * read with u32-sized accesses, any members with a different size
 * need to be ordered correctly though!
 */
struct iwl_error_event_table_v1 {
	u32 valid;		/* (nonzero) valid, (0) log is empty */
	u32 error_id;		/* type of error */
	u32 pc;			/* program counter */
	u32 blink1;		/* branch link */
	u32 blink2;		/* branch link */
	u32 ilink1;		/* interrupt link */
	u32 ilink2;		/* interrupt link */
	u32 data1;		/* error-specific data */
	u32 data2;		/* error-specific data */
	u32 data3;		/* error-specific data */
	u32 bcon_time;		/* beacon timer */
	u32 tsf_low;		/* network timestamp function timer */
	u32 tsf_hi;		/* network timestamp function timer */
	u32 gp1;		/* GP1 timer register */
	u32 gp2;		/* GP2 timer register */
	u32 gp3;		/* GP3 timer register */
	u32 ucode_ver;		/* uCode version */
	u32 hw_ver;		/* HW Silicon version */
	u32 brd_ver;		/* HW board version */
	u32 log_pc;		/* log program counter */
	u32 frame_ptr;		/* frame pointer */
	u32 stack_ptr;		/* stack pointer */
	u32 hcmd;		/* last host command header */
	u32 isr0;		/* isr status register LMPM_NIC_ISR0:
				 * rxtx_flag */
	u32 isr1;		/* isr status register LMPM_NIC_ISR1:
				 * host_flag */
	u32 isr2;		/* isr status register LMPM_NIC_ISR2:
				 * enc_flag */
	u32 isr3;		/* isr status register LMPM_NIC_ISR3:
				 * time_flag */
	u32 isr4;		/* isr status register LMPM_NIC_ISR4:
				 * wico interrupt */
	u32 isr_pref;		/* isr status register LMPM_NIC_PREF_STAT */
	u32 wait_event;		/* wait event() caller address */
	u32 l2p_control;	/* L2pControlField */
	u32 l2p_duration;	/* L2pDurationField */
	u32 l2p_mhvalid;	/* L2pMhValidBits */
	u32 l2p_addr_match;	/* L2pAddrMatchStat */
	u32 lmpm_pmg_sel;	/* indicate which clocks are turned on
				 * (LMPM_PMG_SEL) */
	u32 u_timestamp;	/* indicate when the date and time of the
				 * compilation */
	u32 flow_handler;	/* FH read/write pointers, RX credit */
} __packed /* LOG_ERROR_TABLE_API_S_VER_1 */;

struct iwl_error_event_table {
	u32 valid;		/* (nonzero) valid, (0) log is empty */
	u32 error_id;		/* type of error */
	u32 trm_hw_status0;	/* TRM HW status */
	u32 trm_hw_status1;	/* TRM HW status */
	u32 blink2;		/* branch link */
	u32 ilink1;		/* interrupt link */
	u32 ilink2;		/* interrupt link */
	u32 data1;		/* error-specific data */
	u32 data2;		/* error-specific data */
	u32 data3;		/* error-specific data */
	u32 bcon_time;		/* beacon timer */
	u32 tsf_low;		/* network timestamp function timer */
	u32 tsf_hi;		/* network timestamp function timer */
	u32 gp1;		/* GP1 timer register */
	u32 gp2;		/* GP2 timer register */
	u32 fw_rev_type;	/* firmware revision type */
	u32 major;		/* uCode version major */
	u32 minor;		/* uCode version minor */
	u32 hw_ver;		/* HW Silicon version */
	u32 brd_ver;		/* HW board version */
	u32 log_pc;		/* log program counter */
	u32 frame_ptr;		/* frame pointer */
	u32 stack_ptr;		/* stack pointer */
	u32 hcmd;		/* last host command header */
	u32 isr0;		/* isr status register LMPM_NIC_ISR0:
				 * rxtx_flag */
	u32 isr1;		/* isr status register LMPM_NIC_ISR1:
				 * host_flag */
	u32 isr2;		/* isr status register LMPM_NIC_ISR2:
				 * enc_flag */
	u32 isr3;		/* isr status register LMPM_NIC_ISR3:
				 * time_flag */
	u32 isr4;		/* isr status register LMPM_NIC_ISR4:
				 * wico interrupt */
	u32 last_cmd_id;	/* last HCMD id handled by the firmware */
	u32 wait_event;		/* wait event() caller address */
	u32 l2p_control;	/* L2pControlField */
	u32 l2p_duration;	/* L2pDurationField */
	u32 l2p_mhvalid;	/* L2pMhValidBits */
	u32 l2p_addr_match;	/* L2pAddrMatchStat */
	u32 lmpm_pmg_sel;	/* indicate which clocks are turned on
				 * (LMPM_PMG_SEL) */
	u32 u_timestamp;	/* indicate when the date and time of the
				 * compilation */
	u32 flow_handler;	/* FH read/write pointers, RX credit */
} __packed /* LOG_ERROR_TABLE_API_S_VER_3 */;

/*
 * UMAC error struct - relevant starting from family 8000 chip.
 * Note: This structure is read from the device with IO accesses,
 * and the reading already does the endian conversion. As it is
 * read with u32-sized accesses, any members with a different size
 * need to be ordered correctly though!
 */
struct iwl_umac_error_event_table {
	u32 valid;		/* (nonzero) valid, (0) log is empty */
	u32 error_id;		/* type of error */
	u32 blink1;		/* branch link */
	u32 blink2;		/* branch link */
	u32 ilink1;		/* interrupt link */
	u32 ilink2;		/* interrupt link */
	u32 data1;		/* error-specific data */
	u32 data2;		/* error-specific data */
	u32 data3;		/* error-specific data */
	u32 umac_major;
	u32 umac_minor;
	u32 frame_pointer;	/* core register 27*/
	u32 stack_pointer;	/* core register 28 */
	u32 cmd_header;		/* latest host cmd sent to UMAC */
	u32 nic_isr_pref;	/* ISR status register */
} __packed;

#define ERROR_START_OFFSET  (1 * sizeof(u32))
#define ERROR_ELEM_SIZE     (7 * sizeof(u32))

static void iwl_fwrt_dump_umac_error_log(struct iwl_fw_runtime *fwrt)
{
	struct iwl_trans *trans = fwrt->trans;
	struct iwl_umac_error_event_table table = {};
	u32 base = fwrt->trans->dbg.umac_error_event_table;

	if (!base &&
	    !(fwrt->trans->dbg.error_event_table_tlv_status &
	      IWL_ERROR_EVENT_TABLE_UMAC))
		return;

	iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table));

	if (table.valid)
		fwrt->dump.umac_err_id = table.error_id;

	if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) {
		IWL_ERR(trans, "Start IWL Error Log Dump:\n");
		IWL_ERR(trans, "Transport status: 0x%08lX, valid: %d\n",
			fwrt->trans->status, table.valid);
	}

	IWL_ERR(fwrt, "0x%08X | %s\n", table.error_id,
		iwl_fw_lookup_assert_desc(table.error_id));
	IWL_ERR(fwrt, "0x%08X | umac branchlink1\n", table.blink1);
	IWL_ERR(fwrt, "0x%08X | umac branchlink2\n", table.blink2);
	IWL_ERR(fwrt, "0x%08X | umac interruptlink1\n", table.ilink1);
	IWL_ERR(fwrt, "0x%08X | umac interruptlink2\n", table.ilink2);
	IWL_ERR(fwrt, "0x%08X | umac data1\n", table.data1);
	IWL_ERR(fwrt, "0x%08X | umac data2\n", table.data2);
	IWL_ERR(fwrt, "0x%08X | umac data3\n", table.data3);
	IWL_ERR(fwrt, "0x%08X | umac major\n", table.umac_major);
	IWL_ERR(fwrt, "0x%08X | umac minor\n", table.umac_minor);
	IWL_ERR(fwrt, "0x%08X | frame pointer\n", table.frame_pointer);
	IWL_ERR(fwrt, "0x%08X | stack pointer\n", table.stack_pointer);
	IWL_ERR(fwrt, "0x%08X | last host cmd\n", table.cmd_header);
	IWL_ERR(fwrt, "0x%08X | isr status reg\n", table.nic_isr_pref);
}

static void iwl_fwrt_dump_lmac_error_log(struct iwl_fw_runtime *fwrt, u8 lmac_num)
{
	struct iwl_trans *trans = fwrt->trans;
	struct iwl_error_event_table table = {};
	u32 val, base = fwrt->trans->dbg.lmac_error_event_table[lmac_num];

	if (fwrt->cur_fw_img == IWL_UCODE_INIT) {
		if (!base)
			base = fwrt->fw->init_errlog_ptr;
	} else {
		if (!base)
			base = fwrt->fw->inst_errlog_ptr;
	}

	if (base < 0x400000) {
		IWL_ERR(fwrt,
			"Not valid error log pointer 0x%08X for %s uCode\n",
			base,
			(fwrt->cur_fw_img == IWL_UCODE_INIT)
			? "Init" : "RT");
		return;
	}

	/* check if there is a HW error */
	val = iwl_trans_read_mem32(trans, base);
	if (((val & ~0xf) == 0xa5a5a5a0) || ((val & ~0xf) == 0x5a5a5a50)) {
		int err;

		IWL_ERR(trans, "HW error, resetting before reading\n");

		/* reset the device */
		err = iwl_trans_sw_reset(trans, true);
		if (err)
			return;

		err = iwl_finish_nic_init(trans);
		if (err)
			return;
	}

	iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table));

	if (table.valid)
		fwrt->dump.lmac_err_id[lmac_num] = table.error_id;

	if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) {
		IWL_ERR(trans, "Start IWL Error Log Dump:\n");
		IWL_ERR(trans, "Transport status: 0x%08lX, valid: %d\n",
			fwrt->trans->status, table.valid);
	}

	/* Do not change this output - scripts rely on it */

	IWL_ERR(fwrt, "Loaded firmware version: %s\n", fwrt->fw->fw_version);

	IWL_ERR(fwrt, "0x%08X | %-28s\n", table.error_id,
		iwl_fw_lookup_assert_desc(table.error_id));
	IWL_ERR(fwrt, "0x%08X | trm_hw_status0\n", table.trm_hw_status0);
	IWL_ERR(fwrt, "0x%08X | trm_hw_status1\n", table.trm_hw_status1);
	IWL_ERR(fwrt, "0x%08X | branchlink2\n", table.blink2);
	IWL_ERR(fwrt, "0x%08X | interruptlink1\n", table.ilink1);
	IWL_ERR(fwrt, "0x%08X | interruptlink2\n", table.ilink2);
	IWL_ERR(fwrt, "0x%08X | data1\n", table.data1);
	IWL_ERR(fwrt, "0x%08X | data2\n", table.data2);
	IWL_ERR(fwrt, "0x%08X | data3\n", table.data3);
	IWL_ERR(fwrt, "0x%08X | beacon time\n", table.bcon_time);
	IWL_ERR(fwrt, "0x%08X | tsf low\n", table.tsf_low);
	IWL_ERR(fwrt, "0x%08X | tsf hi\n", table.tsf_hi);
	IWL_ERR(fwrt, "0x%08X | time gp1\n", table.gp1);
	IWL_ERR(fwrt, "0x%08X | time gp2\n", table.gp2);
	IWL_ERR(fwrt, "0x%08X | uCode revision type\n", table.fw_rev_type);
	IWL_ERR(fwrt, "0x%08X | uCode version major\n", table.major);
	IWL_ERR(fwrt, "0x%08X | uCode version minor\n", table.minor);
	IWL_ERR(fwrt, "0x%08X | hw version\n", table.hw_ver);
	IWL_ERR(fwrt, "0x%08X | board version\n", table.brd_ver);
	IWL_ERR(fwrt, "0x%08X | hcmd\n", table.hcmd);
	IWL_ERR(fwrt, "0x%08X | isr0\n", table.isr0);
	IWL_ERR(fwrt, "0x%08X | isr1\n", table.isr1);
	IWL_ERR(fwrt, "0x%08X | isr2\n", table.isr2);
	IWL_ERR(fwrt, "0x%08X | isr3\n", table.isr3);
	IWL_ERR(fwrt, "0x%08X | isr4\n", table.isr4);
	IWL_ERR(fwrt, "0x%08X | last cmd Id\n", table.last_cmd_id);
	IWL_ERR(fwrt, "0x%08X | wait_event\n", table.wait_event);
	IWL_ERR(fwrt, "0x%08X | l2p_control\n", table.l2p_control);
	IWL_ERR(fwrt, "0x%08X | l2p_duration\n", table.l2p_duration);
	IWL_ERR(fwrt, "0x%08X | l2p_mhvalid\n", table.l2p_mhvalid);
	IWL_ERR(fwrt, "0x%08X | l2p_addr_match\n", table.l2p_addr_match);
	IWL_ERR(fwrt, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel);
	IWL_ERR(fwrt, "0x%08X | timestamp\n", table.u_timestamp);
	IWL_ERR(fwrt, "0x%08X | flow_handler\n", table.flow_handler);
}

/*
 * TCM error struct.
 * Note: This structure is read from the device with IO accesses,
 * and the reading already does the endian conversion. As it is
 * read with u32-sized accesses, any members with a different size
 * need to be ordered correctly though!
 */
struct iwl_tcm_error_event_table {
	u32 valid;
	u32 error_id;
	u32 blink2;
	u32 ilink1;
	u32 ilink2;
	u32 data1, data2, data3;
	u32 logpc;
	u32 frame_pointer;
	u32 stack_pointer;
	u32 msgid;
	u32 isr;
	u32 hw_status[5];
	u32 sw_status[1];
	u32 reserved[4];
} __packed; /* TCM_LOG_ERROR_TABLE_API_S_VER_1 */

static void iwl_fwrt_dump_tcm_error_log(struct iwl_fw_runtime *fwrt)
{
	struct iwl_trans *trans = fwrt->trans;
	struct iwl_tcm_error_event_table table = {};
	u32 base = fwrt->trans->dbg.tcm_error_event_table;
	int i;

	if (!base ||
	    !(fwrt->trans->dbg.error_event_table_tlv_status &
	      IWL_ERROR_EVENT_TABLE_TCM))
		return;

	iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table));

	IWL_ERR(fwrt, "TCM status:\n");
	IWL_ERR(fwrt, "0x%08X | error ID\n", table.error_id);
	IWL_ERR(fwrt, "0x%08X | tcm branchlink2\n", table.blink2);
	IWL_ERR(fwrt, "0x%08X | tcm interruptlink1\n", table.ilink1);
	IWL_ERR(fwrt, "0x%08X | tcm interruptlink2\n", table.ilink2);
	IWL_ERR(fwrt, "0x%08X | tcm data1\n", table.data1);
	IWL_ERR(fwrt, "0x%08X | tcm data2\n", table.data2);
	IWL_ERR(fwrt, "0x%08X | tcm data3\n", table.data3);
	IWL_ERR(fwrt, "0x%08X | tcm log PC\n", table.logpc);
	IWL_ERR(fwrt, "0x%08X | tcm frame pointer\n", table.frame_pointer);
	IWL_ERR(fwrt, "0x%08X | tcm stack pointer\n", table.stack_pointer);
	IWL_ERR(fwrt, "0x%08X | tcm msg ID\n", table.msgid);
	IWL_ERR(fwrt, "0x%08X | tcm ISR status\n", table.isr);
	for (i = 0; i < ARRAY_SIZE(table.hw_status); i++)
		IWL_ERR(fwrt, "0x%08X | tcm HW status[%d]\n",
			table.hw_status[i], i);
	for (i = 0; i < ARRAY_SIZE(table.sw_status); i++)
		IWL_ERR(fwrt, "0x%08X | tcm SW status[%d]\n",
			table.sw_status[i], i);

	if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) {
		u32 scratch = iwl_read32(trans, CSR_FUNC_SCRATCH);

		IWL_ERR(fwrt, "Function Scratch status:\n");
		IWL_ERR(fwrt, "0x%08X | Func Scratch\n", scratch);
	}
}

static void iwl_fwrt_dump_iml_error_log(struct iwl_fw_runtime *fwrt)
{
	struct iwl_trans *trans = fwrt->trans;
	u32 error, data1;

	if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) {
		error = UMAG_SB_CPU_2_STATUS;
		data1 = UMAG_SB_CPU_1_STATUS;
	} else if (fwrt->trans->trans_cfg->device_family >=
		   IWL_DEVICE_FAMILY_8000) {
		error = SB_CPU_2_STATUS;
		data1 = SB_CPU_1_STATUS;
	} else {
		return;
	}

	error = iwl_read_umac_prph(trans, UMAG_SB_CPU_2_STATUS);

	IWL_ERR(trans, "IML/ROM dump:\n");

	if (error & 0xFFFF0000)
		IWL_ERR(trans, "0x%04X | IML/ROM SYSASSERT\n", error >> 16);

	IWL_ERR(fwrt, "0x%08X | IML/ROM error/state\n", error);
	IWL_ERR(fwrt, "0x%08X | IML/ROM data1\n",
		iwl_read_umac_prph(trans, data1));

	if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000)
		IWL_ERR(fwrt, "0x%08X | IML/ROM WFPM_AUTH_KEY_0\n",
			iwl_read_umac_prph(trans, SB_MODIFY_CFG_FLAG));
}

#define FSEQ_REG(x) { .addr = (x), .str = #x, }

static void iwl_fwrt_dump_fseq_regs(struct iwl_fw_runtime *fwrt)
{
	struct iwl_trans *trans = fwrt->trans;
	int i;
	struct {
		u32 addr;
		const char *str;
	} fseq_regs[] = {
		FSEQ_REG(FSEQ_ERROR_CODE),
		FSEQ_REG(FSEQ_TOP_INIT_VERSION),
		FSEQ_REG(FSEQ_CNVIO_INIT_VERSION),
		FSEQ_REG(FSEQ_OTP_VERSION),
		FSEQ_REG(FSEQ_TOP_CONTENT_VERSION),
		FSEQ_REG(FSEQ_ALIVE_TOKEN),
		FSEQ_REG(FSEQ_CNVI_ID),
		FSEQ_REG(FSEQ_CNVR_ID),
		FSEQ_REG(CNVI_AUX_MISC_CHIP),
		FSEQ_REG(CNVR_AUX_MISC_CHIP),
		FSEQ_REG(CNVR_SCU_SD_REGS_SD_REG_DIG_DCDC_VTRIM),
		FSEQ_REG(CNVR_SCU_SD_REGS_SD_REG_ACTIVE_VDIG_MIRROR),
	};

	if (!iwl_trans_grab_nic_access(trans))
		return;

	IWL_ERR(fwrt, "Fseq Registers:\n");

	for (i = 0; i < ARRAY_SIZE(fseq_regs); i++)
		IWL_ERR(fwrt, "0x%08X | %s\n",
			iwl_read_prph_no_grab(trans, fseq_regs[i].addr),
			fseq_regs[i].str);

	iwl_trans_release_nic_access(trans);
}

void iwl_fwrt_dump_error_logs(struct iwl_fw_runtime *fwrt)
{
	if (!test_bit(STATUS_DEVICE_ENABLED, &fwrt->trans->status)) {
		IWL_ERR(fwrt,
			"DEVICE_ENABLED bit is not set. Aborting dump.\n");
		return;
	}

	iwl_fwrt_dump_lmac_error_log(fwrt, 0);
	if (fwrt->trans->dbg.lmac_error_event_table[1])
		iwl_fwrt_dump_lmac_error_log(fwrt, 1);
	iwl_fwrt_dump_umac_error_log(fwrt);
	iwl_fwrt_dump_tcm_error_log(fwrt);
	iwl_fwrt_dump_iml_error_log(fwrt);
	iwl_fwrt_dump_fseq_regs(fwrt);
}
IWL_EXPORT_SYMBOL(iwl_fwrt_dump_error_logs);