bnxt_coredump.c (12217B)
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 18static 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, msg, info->dma_len, &dma_handle); 30 if (!dma_buf) { 31 hwrm_req_drop(bp, msg); 32 return -ENOMEM; 33 } 34 35 hwrm_req_timeout(bp, msg, bp->hwrm_cmd_max_timeout); 36 cmn_resp = hwrm_req_hold(bp, 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, 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(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, msg); 90 return rc; 91} 92 93static 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, req, &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 118static 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, 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 135static 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, req, &info); 162 if (!rc) 163 *seg_len = info.dest_buf_size; 164 165 return rc; 166} 167 168static void 169bnxt_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 197static 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(record->commandline, 206 (char __user *)mm->arg_start, 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 221static void 222bnxt_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(start, 0, &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(bp->dev, "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(end, 0, &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 272static 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, NULL, ver_get_resp_len, 294 0, 0, 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); 302 if (rc) { 303 netdev_err(bp->dev, "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, comp_id, seg_id); 327 if (rc) { 328 netdev_err(bp->dev, 329 "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, comp_id, seg_id, 336 &seg_len, buf, buf_len, 337 offset + seg_hdr_len); 338 if (rc && rc == -ENOBUFS) 339 goto err; 340 else if (rc) 341 netdev_err(bp->dev, 342 "Failed to retrieve coredump for seg = %d\n", 343 seg_record->segment_id); 344 345next_seg: 346 end = jiffies; 347 duration = jiffies_to_msecs(end - start); 348 bnxt_fill_coredump_seg_hdr(bp, &seg_hdr, seg_record, seg_len, 349 rc, duration, 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 363err: 364 if (buf) 365 bnxt_fill_coredump_record(bp, buf + offset, start_time, 366 start_utc, coredump.total_segs + 1, 367 rc); 368 kfree(coredump.data); 369 *dump_len += sizeof(struct bnxt_coredump_record); 370 if (rc == -ENOBUFS) 371 netdev_err(bp->dev, "Firmware returned large coredump buffer\n"); 372 return rc; 373} 374 375int 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, 0, *dump_len); 380#else 381 return -EOPNOTSUPP; 382#endif 383 } else { 384 return __bnxt_get_coredump(bp, buf, dump_len); 385 } 386} 387 388static 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 428get_dump_len_exit: 429 hwrm_req_drop(bp, req); 430 return rc; 431} 432 433u32 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, &len)) { 438 if (dump_type == BNXT_DUMP_CRASH) 439 len = BNXT_CRASH_DUMP_LEN; 440 else 441 __bnxt_get_coredump(bp, NULL, &len); 442 } 443 return len; 444}