1 | /* Broadcom NetXtreme-C/E network driver. |
2 | * |
3 | * Copyright (c) 2021 Broadcom Limited |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation. |
8 | */ |
9 | |
10 | #include <linux/types.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/pci.h> |
13 | #include "bnxt_hsi.h" |
14 | #include "bnxt.h" |
15 | #include "bnxt_hwrm.h" |
16 | #include "bnxt_coredump.h" |
17 | |
18 | static int bnxt_hwrm_dbg_dma_data(struct bnxt *bp, void *msg, |
19 | struct bnxt_hwrm_dbg_dma_info *info) |
20 | { |
21 | struct hwrm_dbg_cmn_input *cmn_req = msg; |
22 | __le16 *seq_ptr = msg + info->seq_off; |
23 | struct hwrm_dbg_cmn_output *cmn_resp; |
24 | u16 seq = 0, len, segs_off; |
25 | dma_addr_t dma_handle; |
26 | void *dma_buf, *resp; |
27 | int rc, off = 0; |
28 | |
29 | dma_buf = hwrm_req_dma_slice(bp, req: msg, size: info->dma_len, dma: &dma_handle); |
30 | if (!dma_buf) { |
31 | hwrm_req_drop(bp, req: msg); |
32 | return -ENOMEM; |
33 | } |
34 | |
35 | hwrm_req_timeout(bp, req: msg, timeout: bp->hwrm_cmd_max_timeout); |
36 | cmn_resp = hwrm_req_hold(bp, req: msg); |
37 | resp = cmn_resp; |
38 | |
39 | segs_off = offsetof(struct hwrm_dbg_coredump_list_output, |
40 | total_segments); |
41 | cmn_req->host_dest_addr = cpu_to_le64(dma_handle); |
42 | cmn_req->host_buf_len = cpu_to_le32(info->dma_len); |
43 | while (1) { |
44 | *seq_ptr = cpu_to_le16(seq); |
45 | rc = hwrm_req_send(bp, req: msg); |
46 | if (rc) |
47 | break; |
48 | |
49 | len = le16_to_cpu(*((__le16 *)(resp + info->data_len_off))); |
50 | if (!seq && |
51 | cmn_req->req_type == cpu_to_le16(HWRM_DBG_COREDUMP_LIST)) { |
52 | info->segs = le16_to_cpu(*((__le16 *)(resp + |
53 | segs_off))); |
54 | if (!info->segs) { |
55 | rc = -EIO; |
56 | break; |
57 | } |
58 | |
59 | info->dest_buf_size = info->segs * |
60 | sizeof(struct coredump_segment_record); |
61 | info->dest_buf = kmalloc(size: info->dest_buf_size, |
62 | GFP_KERNEL); |
63 | if (!info->dest_buf) { |
64 | rc = -ENOMEM; |
65 | break; |
66 | } |
67 | } |
68 | |
69 | if (info->dest_buf) { |
70 | if ((info->seg_start + off + len) <= |
71 | BNXT_COREDUMP_BUF_LEN(info->buf_len)) { |
72 | memcpy(info->dest_buf + off, dma_buf, len); |
73 | } else { |
74 | rc = -ENOBUFS; |
75 | break; |
76 | } |
77 | } |
78 | |
79 | if (cmn_req->req_type == |
80 | cpu_to_le16(HWRM_DBG_COREDUMP_RETRIEVE)) |
81 | info->dest_buf_size += len; |
82 | |
83 | if (!(cmn_resp->flags & HWRM_DBG_CMN_FLAGS_MORE)) |
84 | break; |
85 | |
86 | seq++; |
87 | off += len; |
88 | } |
89 | hwrm_req_drop(bp, req: msg); |
90 | return rc; |
91 | } |
92 | |
93 | static int bnxt_hwrm_dbg_coredump_list(struct bnxt *bp, |
94 | struct bnxt_coredump *coredump) |
95 | { |
96 | struct bnxt_hwrm_dbg_dma_info info = {NULL}; |
97 | struct hwrm_dbg_coredump_list_input *req; |
98 | int rc; |
99 | |
100 | rc = hwrm_req_init(bp, req, HWRM_DBG_COREDUMP_LIST); |
101 | if (rc) |
102 | return rc; |
103 | |
104 | info.dma_len = COREDUMP_LIST_BUF_LEN; |
105 | info.seq_off = offsetof(struct hwrm_dbg_coredump_list_input, seq_no); |
106 | info.data_len_off = offsetof(struct hwrm_dbg_coredump_list_output, |
107 | data_len); |
108 | |
109 | rc = bnxt_hwrm_dbg_dma_data(bp, msg: req, info: &info); |
110 | if (!rc) { |
111 | coredump->data = info.dest_buf; |
112 | coredump->data_size = info.dest_buf_size; |
113 | coredump->total_segs = info.segs; |
114 | } |
115 | return rc; |
116 | } |
117 | |
118 | static int bnxt_hwrm_dbg_coredump_initiate(struct bnxt *bp, u16 component_id, |
119 | u16 segment_id) |
120 | { |
121 | struct hwrm_dbg_coredump_initiate_input *req; |
122 | int rc; |
123 | |
124 | rc = hwrm_req_init(bp, req, HWRM_DBG_COREDUMP_INITIATE); |
125 | if (rc) |
126 | return rc; |
127 | |
128 | hwrm_req_timeout(bp, req, timeout: bp->hwrm_cmd_max_timeout); |
129 | req->component_id = cpu_to_le16(component_id); |
130 | req->segment_id = cpu_to_le16(segment_id); |
131 | |
132 | return hwrm_req_send(bp, req); |
133 | } |
134 | |
135 | static int bnxt_hwrm_dbg_coredump_retrieve(struct bnxt *bp, u16 component_id, |
136 | u16 segment_id, u32 *seg_len, |
137 | void *buf, u32 buf_len, u32 offset) |
138 | { |
139 | struct hwrm_dbg_coredump_retrieve_input *req; |
140 | struct bnxt_hwrm_dbg_dma_info info = {NULL}; |
141 | int rc; |
142 | |
143 | rc = hwrm_req_init(bp, req, HWRM_DBG_COREDUMP_RETRIEVE); |
144 | if (rc) |
145 | return rc; |
146 | |
147 | req->component_id = cpu_to_le16(component_id); |
148 | req->segment_id = cpu_to_le16(segment_id); |
149 | |
150 | info.dma_len = COREDUMP_RETRIEVE_BUF_LEN; |
151 | info.seq_off = offsetof(struct hwrm_dbg_coredump_retrieve_input, |
152 | seq_no); |
153 | info.data_len_off = offsetof(struct hwrm_dbg_coredump_retrieve_output, |
154 | data_len); |
155 | if (buf) { |
156 | info.dest_buf = buf + offset; |
157 | info.buf_len = buf_len; |
158 | info.seg_start = offset; |
159 | } |
160 | |
161 | rc = bnxt_hwrm_dbg_dma_data(bp, msg: req, info: &info); |
162 | if (!rc) |
163 | *seg_len = info.dest_buf_size; |
164 | |
165 | return rc; |
166 | } |
167 | |
168 | static void |
169 | bnxt_fill_coredump_seg_hdr(struct bnxt *bp, |
170 | struct bnxt_coredump_segment_hdr *seg_hdr, |
171 | struct coredump_segment_record *seg_rec, u32 seg_len, |
172 | int status, u32 duration, u32 instance) |
173 | { |
174 | memset(seg_hdr, 0, sizeof(*seg_hdr)); |
175 | memcpy(seg_hdr->signature, "sEgM" , 4); |
176 | if (seg_rec) { |
177 | seg_hdr->component_id = (__force __le32)seg_rec->component_id; |
178 | seg_hdr->segment_id = (__force __le32)seg_rec->segment_id; |
179 | seg_hdr->low_version = seg_rec->version_low; |
180 | seg_hdr->high_version = seg_rec->version_hi; |
181 | seg_hdr->flags = cpu_to_le32(seg_rec->compress_flags); |
182 | } else { |
183 | /* For hwrm_ver_get response Component id = 2 |
184 | * and Segment id = 0 |
185 | */ |
186 | seg_hdr->component_id = cpu_to_le32(2); |
187 | seg_hdr->segment_id = 0; |
188 | } |
189 | seg_hdr->function_id = cpu_to_le16(bp->pdev->devfn); |
190 | seg_hdr->length = cpu_to_le32(seg_len); |
191 | seg_hdr->status = cpu_to_le32(status); |
192 | seg_hdr->duration = cpu_to_le32(duration); |
193 | seg_hdr->data_offset = cpu_to_le32(sizeof(*seg_hdr)); |
194 | seg_hdr->instance = cpu_to_le32(instance); |
195 | } |
196 | |
197 | static void bnxt_fill_cmdline(struct bnxt_coredump_record *record) |
198 | { |
199 | struct mm_struct *mm = current->mm; |
200 | int i, len, last = 0; |
201 | |
202 | if (mm) { |
203 | len = min_t(int, mm->arg_end - mm->arg_start, |
204 | sizeof(record->commandline) - 1); |
205 | if (len && !copy_from_user(to: record->commandline, |
206 | from: (char __user *)mm->arg_start, n: len)) { |
207 | for (i = 0; i < len; i++) { |
208 | if (record->commandline[i]) |
209 | last = i; |
210 | else |
211 | record->commandline[i] = ' '; |
212 | } |
213 | record->commandline[last + 1] = 0; |
214 | return; |
215 | } |
216 | } |
217 | |
218 | strscpy(record->commandline, current->comm, TASK_COMM_LEN); |
219 | } |
220 | |
221 | static void |
222 | bnxt_fill_coredump_record(struct bnxt *bp, struct bnxt_coredump_record *record, |
223 | time64_t start, s16 start_utc, u16 total_segs, |
224 | int status) |
225 | { |
226 | time64_t end = ktime_get_real_seconds(); |
227 | u32 os_ver_major = 0, os_ver_minor = 0; |
228 | struct tm tm; |
229 | |
230 | time64_to_tm(totalsecs: start, offset: 0, result: &tm); |
231 | memset(record, 0, sizeof(*record)); |
232 | memcpy(record->signature, "cOrE" , 4); |
233 | record->flags = 0; |
234 | record->low_version = 0; |
235 | record->high_version = 1; |
236 | record->asic_state = 0; |
237 | strscpy(record->system_name, utsname()->nodename, |
238 | sizeof(record->system_name)); |
239 | record->year = cpu_to_le16(tm.tm_year + 1900); |
240 | record->month = cpu_to_le16(tm.tm_mon + 1); |
241 | record->day = cpu_to_le16(tm.tm_mday); |
242 | record->hour = cpu_to_le16(tm.tm_hour); |
243 | record->minute = cpu_to_le16(tm.tm_min); |
244 | record->second = cpu_to_le16(tm.tm_sec); |
245 | record->utc_bias = cpu_to_le16(start_utc); |
246 | bnxt_fill_cmdline(record); |
247 | record->total_segments = cpu_to_le32(total_segs); |
248 | |
249 | if (sscanf(utsname()->release, "%u.%u" , &os_ver_major, &os_ver_minor) != 2) |
250 | netdev_warn(dev: bp->dev, format: "Unknown OS release in coredump\n" ); |
251 | record->os_ver_major = cpu_to_le32(os_ver_major); |
252 | record->os_ver_minor = cpu_to_le32(os_ver_minor); |
253 | |
254 | strscpy(record->os_name, utsname()->sysname, sizeof(record->os_name)); |
255 | time64_to_tm(totalsecs: end, offset: 0, result: &tm); |
256 | record->end_year = cpu_to_le16(tm.tm_year + 1900); |
257 | record->end_month = cpu_to_le16(tm.tm_mon + 1); |
258 | record->end_day = cpu_to_le16(tm.tm_mday); |
259 | record->end_hour = cpu_to_le16(tm.tm_hour); |
260 | record->end_minute = cpu_to_le16(tm.tm_min); |
261 | record->end_second = cpu_to_le16(tm.tm_sec); |
262 | record->end_utc_bias = cpu_to_le16(sys_tz.tz_minuteswest * 60); |
263 | record->asic_id1 = cpu_to_le32(bp->chip_num << 16 | |
264 | bp->ver_resp.chip_rev << 8 | |
265 | bp->ver_resp.chip_metal); |
266 | record->asic_id2 = 0; |
267 | record->coredump_status = cpu_to_le32(status); |
268 | record->ioctl_low_version = 0; |
269 | record->ioctl_high_version = 0; |
270 | } |
271 | |
272 | static int __bnxt_get_coredump(struct bnxt *bp, void *buf, u32 *dump_len) |
273 | { |
274 | u32 ver_get_resp_len = sizeof(struct hwrm_ver_get_output); |
275 | u32 offset = 0, seg_hdr_len, seg_record_len, buf_len = 0; |
276 | struct coredump_segment_record *seg_record = NULL; |
277 | struct bnxt_coredump_segment_hdr seg_hdr; |
278 | struct bnxt_coredump coredump = {NULL}; |
279 | time64_t start_time; |
280 | u16 start_utc; |
281 | int rc = 0, i; |
282 | |
283 | if (buf) |
284 | buf_len = *dump_len; |
285 | |
286 | start_time = ktime_get_real_seconds(); |
287 | start_utc = sys_tz.tz_minuteswest * 60; |
288 | seg_hdr_len = sizeof(seg_hdr); |
289 | |
290 | /* First segment should be hwrm_ver_get response */ |
291 | *dump_len = seg_hdr_len + ver_get_resp_len; |
292 | if (buf) { |
293 | bnxt_fill_coredump_seg_hdr(bp, seg_hdr: &seg_hdr, NULL, seg_len: ver_get_resp_len, |
294 | status: 0, duration: 0, instance: 0); |
295 | memcpy(buf + offset, &seg_hdr, seg_hdr_len); |
296 | offset += seg_hdr_len; |
297 | memcpy(buf + offset, &bp->ver_resp, ver_get_resp_len); |
298 | offset += ver_get_resp_len; |
299 | } |
300 | |
301 | rc = bnxt_hwrm_dbg_coredump_list(bp, coredump: &coredump); |
302 | if (rc) { |
303 | netdev_err(dev: bp->dev, format: "Failed to get coredump segment list\n" ); |
304 | goto err; |
305 | } |
306 | |
307 | *dump_len += seg_hdr_len * coredump.total_segs; |
308 | |
309 | seg_record = (struct coredump_segment_record *)coredump.data; |
310 | seg_record_len = sizeof(*seg_record); |
311 | |
312 | for (i = 0; i < coredump.total_segs; i++) { |
313 | u16 comp_id = le16_to_cpu(seg_record->component_id); |
314 | u16 seg_id = le16_to_cpu(seg_record->segment_id); |
315 | u32 duration = 0, seg_len = 0; |
316 | unsigned long start, end; |
317 | |
318 | if (buf && ((offset + seg_hdr_len) > |
319 | BNXT_COREDUMP_BUF_LEN(buf_len))) { |
320 | rc = -ENOBUFS; |
321 | goto err; |
322 | } |
323 | |
324 | start = jiffies; |
325 | |
326 | rc = bnxt_hwrm_dbg_coredump_initiate(bp, component_id: comp_id, segment_id: seg_id); |
327 | if (rc) { |
328 | netdev_err(dev: bp->dev, |
329 | format: "Failed to initiate coredump for seg = %d\n" , |
330 | seg_record->segment_id); |
331 | goto next_seg; |
332 | } |
333 | |
334 | /* Write segment data into the buffer */ |
335 | rc = bnxt_hwrm_dbg_coredump_retrieve(bp, component_id: comp_id, segment_id: seg_id, |
336 | seg_len: &seg_len, buf, buf_len, |
337 | offset: offset + seg_hdr_len); |
338 | if (rc && rc == -ENOBUFS) |
339 | goto err; |
340 | else if (rc) |
341 | netdev_err(dev: bp->dev, |
342 | format: "Failed to retrieve coredump for seg = %d\n" , |
343 | seg_record->segment_id); |
344 | |
345 | next_seg: |
346 | end = jiffies; |
347 | duration = jiffies_to_msecs(j: end - start); |
348 | bnxt_fill_coredump_seg_hdr(bp, seg_hdr: &seg_hdr, seg_rec: seg_record, seg_len, |
349 | status: rc, duration, instance: 0); |
350 | |
351 | if (buf) { |
352 | /* Write segment header into the buffer */ |
353 | memcpy(buf + offset, &seg_hdr, seg_hdr_len); |
354 | offset += seg_hdr_len + seg_len; |
355 | } |
356 | |
357 | *dump_len += seg_len; |
358 | seg_record = |
359 | (struct coredump_segment_record *)((u8 *)seg_record + |
360 | seg_record_len); |
361 | } |
362 | |
363 | err: |
364 | if (buf) |
365 | bnxt_fill_coredump_record(bp, record: buf + offset, start: start_time, |
366 | start_utc, total_segs: coredump.total_segs + 1, |
367 | status: rc); |
368 | kfree(objp: coredump.data); |
369 | *dump_len += sizeof(struct bnxt_coredump_record); |
370 | if (rc == -ENOBUFS) |
371 | netdev_err(dev: bp->dev, format: "Firmware returned large coredump buffer\n" ); |
372 | return rc; |
373 | } |
374 | |
375 | int bnxt_get_coredump(struct bnxt *bp, u16 dump_type, void *buf, u32 *dump_len) |
376 | { |
377 | if (dump_type == BNXT_DUMP_CRASH) { |
378 | #ifdef CONFIG_TEE_BNXT_FW |
379 | return tee_bnxt_copy_coredump(buf, offset: 0, size: *dump_len); |
380 | #else |
381 | return -EOPNOTSUPP; |
382 | #endif |
383 | } else { |
384 | return __bnxt_get_coredump(bp, buf, dump_len); |
385 | } |
386 | } |
387 | |
388 | static int bnxt_hwrm_get_dump_len(struct bnxt *bp, u16 dump_type, u32 *dump_len) |
389 | { |
390 | struct hwrm_dbg_qcfg_output *resp; |
391 | struct hwrm_dbg_qcfg_input *req; |
392 | int rc, hdr_len = 0; |
393 | |
394 | if (!(bp->fw_cap & BNXT_FW_CAP_DBG_QCAPS)) |
395 | return -EOPNOTSUPP; |
396 | |
397 | if (dump_type == BNXT_DUMP_CRASH && |
398 | !(bp->fw_dbg_cap & DBG_QCAPS_RESP_FLAGS_CRASHDUMP_SOC_DDR)) |
399 | return -EOPNOTSUPP; |
400 | |
401 | rc = hwrm_req_init(bp, req, HWRM_DBG_QCFG); |
402 | if (rc) |
403 | return rc; |
404 | |
405 | req->fid = cpu_to_le16(0xffff); |
406 | if (dump_type == BNXT_DUMP_CRASH) |
407 | req->flags = cpu_to_le16(DBG_QCFG_REQ_FLAGS_CRASHDUMP_SIZE_FOR_DEST_DEST_SOC_DDR); |
408 | |
409 | resp = hwrm_req_hold(bp, req); |
410 | rc = hwrm_req_send(bp, req); |
411 | if (rc) |
412 | goto get_dump_len_exit; |
413 | |
414 | if (dump_type == BNXT_DUMP_CRASH) { |
415 | *dump_len = le32_to_cpu(resp->crashdump_size); |
416 | } else { |
417 | /* Driver adds coredump header and "HWRM_VER_GET response" |
418 | * segment additionally to coredump. |
419 | */ |
420 | hdr_len = sizeof(struct bnxt_coredump_segment_hdr) + |
421 | sizeof(struct hwrm_ver_get_output) + |
422 | sizeof(struct bnxt_coredump_record); |
423 | *dump_len = le32_to_cpu(resp->coredump_size) + hdr_len; |
424 | } |
425 | if (*dump_len <= hdr_len) |
426 | rc = -EINVAL; |
427 | |
428 | get_dump_len_exit: |
429 | hwrm_req_drop(bp, req); |
430 | return rc; |
431 | } |
432 | |
433 | u32 bnxt_get_coredump_length(struct bnxt *bp, u16 dump_type) |
434 | { |
435 | u32 len = 0; |
436 | |
437 | if (bnxt_hwrm_get_dump_len(bp, dump_type, dump_len: &len)) { |
438 | if (dump_type == BNXT_DUMP_CRASH) |
439 | len = BNXT_CRASH_DUMP_LEN; |
440 | else |
441 | __bnxt_get_coredump(bp, NULL, dump_len: &len); |
442 | } |
443 | return len; |
444 | } |
445 | |