diff options
| -rw-r--r-- | drivers/net/ethernet/broadcom/bnxt/bnxt_coredump.h | 66 | ||||
| -rw-r--r-- | drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 333 | ||||
| -rw-r--r-- | drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.h | 37 | 
3 files changed, 436 insertions, 0 deletions
| diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_coredump.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_coredump.h new file mode 100644 index 000000000000..09c22f8fe399 --- /dev/null +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_coredump.h @@ -0,0 +1,66 @@ +/* Broadcom NetXtreme-C/E network driver. + * + * Copyright (c) 2018 Broadcom Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation. + */ + +#ifndef BNXT_COREDUMP_H +#define BNXT_COREDUMP_H + +struct bnxt_coredump_segment_hdr { +	__u8 signature[4]; +	__le32 component_id; +	__le32 segment_id; +	__le32 flags; +	__u8 low_version; +	__u8 high_version; +	__le16 function_id; +	__le32 offset; +	__le32 length; +	__le32 status; +	__le32 duration; +	__le32 data_offset; +	__le32 instance; +	__le32 rsvd[5]; +}; + +struct bnxt_coredump_record { +	__u8 signature[4]; +	__le32 flags; +	__u8 low_version; +	__u8 high_version; +	__u8 asic_state; +	__u8 rsvd0[5]; +	char system_name[32]; +	__le16 year; +	__le16 month; +	__le16 day; +	__le16 hour; +	__le16 minute; +	__le16 second; +	__le16 utc_bias; +	__le16 rsvd1; +	char commandline[256]; +	__le32 total_segments; +	__le32 os_ver_major; +	__le32 os_ver_minor; +	__le32 rsvd2; +	char os_name[32]; +	__le16 end_year; +	__le16 end_month; +	__le16 end_day; +	__le16 end_hour; +	__le16 end_minute; +	__le16 end_second; +	__le16 end_utc_bias; +	__le32 asic_id1; +	__le32 asic_id2; +	__le32 coredump_status; +	__u8 ioctl_low_version; +	__u8 ioctl_high_version; +	__le16 rsvd3[313]; +}; +#endif diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index 9517633f947a..3fc7c741bdfe 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -16,12 +16,15 @@  #include <linux/etherdevice.h>  #include <linux/crc32.h>  #include <linux/firmware.h> +#include <linux/utsname.h> +#include <linux/time.h>  #include "bnxt_hsi.h"  #include "bnxt.h"  #include "bnxt_xdp.h"  #include "bnxt_ethtool.h"  #include "bnxt_nvm_defs.h"	/* NVRAM content constant and structure defs */  #include "bnxt_fw_hdr.h"	/* Firmware hdr constant and structure defs */ +#include "bnxt_coredump.h"  #define FLASH_NVRAM_TIMEOUT	((HWRM_CMD_TIMEOUT) * 100)  #define FLASH_PACKAGE_TIMEOUT	((HWRM_CMD_TIMEOUT) * 200)  #define INSTALL_PACKAGE_TIMEOUT	((HWRM_CMD_TIMEOUT) * 200) @@ -2685,6 +2688,334 @@ static int bnxt_reset(struct net_device *dev, u32 *flags)  	return rc;  } +static int bnxt_hwrm_dbg_dma_data(struct bnxt *bp, void *msg, int msg_len, +				  struct bnxt_hwrm_dbg_dma_info *info) +{ +	struct hwrm_dbg_cmn_output *cmn_resp = bp->hwrm_cmd_resp_addr; +	struct hwrm_dbg_cmn_input *cmn_req = msg; +	__le16 *seq_ptr = msg + info->seq_off; +	u16 seq = 0, len, segs_off; +	void *resp = cmn_resp; +	dma_addr_t dma_handle; +	int rc, off = 0; +	void *dma_buf; + +	dma_buf = dma_alloc_coherent(&bp->pdev->dev, info->dma_len, &dma_handle, +				     GFP_KERNEL); +	if (!dma_buf) +		return -ENOMEM; + +	segs_off = offsetof(struct hwrm_dbg_coredump_list_output, +			    total_segments); +	cmn_req->host_dest_addr = cpu_to_le64(dma_handle); +	cmn_req->host_buf_len = cpu_to_le32(info->dma_len); +	mutex_lock(&bp->hwrm_cmd_lock); +	while (1) { +		*seq_ptr = cpu_to_le16(seq); +		rc = _hwrm_send_message(bp, msg, msg_len, HWRM_CMD_TIMEOUT); +		if (rc) +			break; + +		len = le16_to_cpu(*((__le16 *)(resp + info->data_len_off))); +		if (!seq && +		    cmn_req->req_type == cpu_to_le16(HWRM_DBG_COREDUMP_LIST)) { +			info->segs = le16_to_cpu(*((__le16 *)(resp + +							      segs_off))); +			if (!info->segs) { +				rc = -EIO; +				break; +			} + +			info->dest_buf_size = info->segs * +					sizeof(struct coredump_segment_record); +			info->dest_buf = kmalloc(info->dest_buf_size, +						 GFP_KERNEL); +			if (!info->dest_buf) { +				rc = -ENOMEM; +				break; +			} +		} + +		if (info->dest_buf) +			memcpy(info->dest_buf + off, dma_buf, len); + +		if (cmn_req->req_type == +				cpu_to_le16(HWRM_DBG_COREDUMP_RETRIEVE)) +			info->dest_buf_size += len; + +		if (!(cmn_resp->flags & HWRM_DBG_CMN_FLAGS_MORE)) +			break; + +		seq++; +		off += len; +	} +	mutex_unlock(&bp->hwrm_cmd_lock); +	dma_free_coherent(&bp->pdev->dev, info->dma_len, dma_buf, dma_handle); +	return rc; +} + +static int bnxt_hwrm_dbg_coredump_list(struct bnxt *bp, +				       struct bnxt_coredump *coredump) +{ +	struct hwrm_dbg_coredump_list_input req = {0}; +	struct bnxt_hwrm_dbg_dma_info info = {NULL}; +	int rc; + +	bnxt_hwrm_cmd_hdr_init(bp, &req, HWRM_DBG_COREDUMP_LIST, -1, -1); + +	info.dma_len = COREDUMP_LIST_BUF_LEN; +	info.seq_off = offsetof(struct hwrm_dbg_coredump_list_input, seq_no); +	info.data_len_off = offsetof(struct hwrm_dbg_coredump_list_output, +				     data_len); + +	rc = bnxt_hwrm_dbg_dma_data(bp, &req, sizeof(req), &info); +	if (!rc) { +		coredump->data = info.dest_buf; +		coredump->data_size = info.dest_buf_size; +		coredump->total_segs = info.segs; +	} +	return rc; +} + +static int bnxt_hwrm_dbg_coredump_initiate(struct bnxt *bp, u16 component_id, +					   u16 segment_id) +{ +	struct hwrm_dbg_coredump_initiate_input req = {0}; + +	bnxt_hwrm_cmd_hdr_init(bp, &req, HWRM_DBG_COREDUMP_INITIATE, -1, -1); +	req.component_id = cpu_to_le16(component_id); +	req.segment_id = cpu_to_le16(segment_id); + +	return hwrm_send_message(bp, &req, sizeof(req), HWRM_CMD_TIMEOUT); +} + +static int bnxt_hwrm_dbg_coredump_retrieve(struct bnxt *bp, u16 component_id, +					   u16 segment_id, u32 *seg_len, +					   void *buf, u32 offset) +{ +	struct hwrm_dbg_coredump_retrieve_input req = {0}; +	struct bnxt_hwrm_dbg_dma_info info = {NULL}; +	int rc; + +	bnxt_hwrm_cmd_hdr_init(bp, &req, HWRM_DBG_COREDUMP_RETRIEVE, -1, -1); +	req.component_id = cpu_to_le16(component_id); +	req.segment_id = cpu_to_le16(segment_id); + +	info.dma_len = COREDUMP_RETRIEVE_BUF_LEN; +	info.seq_off = offsetof(struct hwrm_dbg_coredump_retrieve_input, +				seq_no); +	info.data_len_off = offsetof(struct hwrm_dbg_coredump_retrieve_output, +				     data_len); +	if (buf) +		info.dest_buf = buf + offset; + +	rc = bnxt_hwrm_dbg_dma_data(bp, &req, sizeof(req), &info); +	if (!rc) +		*seg_len = info.dest_buf_size; + +	return rc; +} + +static void +bnxt_fill_coredump_seg_hdr(struct bnxt *bp, +			   struct bnxt_coredump_segment_hdr *seg_hdr, +			   struct coredump_segment_record *seg_rec, u32 seg_len, +			   int status, u32 duration, u32 instance) +{ +	memset(seg_hdr, 0, sizeof(*seg_hdr)); +	strcpy(seg_hdr->signature, "sEgM"); +	if (seg_rec) { +		seg_hdr->component_id = (__force __le32)seg_rec->component_id; +		seg_hdr->segment_id = (__force __le32)seg_rec->segment_id; +		seg_hdr->low_version = seg_rec->version_low; +		seg_hdr->high_version = seg_rec->version_hi; +	} else { +		/* For hwrm_ver_get response Component id = 2 +		 * and Segment id = 0 +		 */ +		seg_hdr->component_id = cpu_to_le32(2); +		seg_hdr->segment_id = 0; +	} +	seg_hdr->function_id = cpu_to_le16(bp->pdev->devfn); +	seg_hdr->length = cpu_to_le32(seg_len); +	seg_hdr->status = cpu_to_le32(status); +	seg_hdr->duration = cpu_to_le32(duration); +	seg_hdr->data_offset = cpu_to_le32(sizeof(*seg_hdr)); +	seg_hdr->instance = cpu_to_le32(instance); +} + +static void +bnxt_fill_coredump_record(struct bnxt *bp, struct bnxt_coredump_record *record, +			  time64_t start, s16 start_utc, u16 total_segs, +			  int status) +{ +	time64_t end = ktime_get_real_seconds(); +	u32 os_ver_major = 0, os_ver_minor = 0; +	struct tm tm; + +	time64_to_tm(start, 0, &tm); +	memset(record, 0, sizeof(*record)); +	strcpy(record->signature, "cOrE"); +	record->flags = 0; +	record->low_version = 0; +	record->high_version = 1; +	record->asic_state = 0; +	strncpy(record->system_name, utsname()->nodename, +		strlen(utsname()->nodename)); +	record->year = cpu_to_le16(tm.tm_year); +	record->month = cpu_to_le16(tm.tm_mon); +	record->day = cpu_to_le16(tm.tm_mday); +	record->hour = cpu_to_le16(tm.tm_hour); +	record->minute = cpu_to_le16(tm.tm_min); +	record->second = cpu_to_le16(tm.tm_sec); +	record->utc_bias = cpu_to_le16(start_utc); +	strcpy(record->commandline, "ethtool -w"); +	record->total_segments = cpu_to_le32(total_segs); + +	sscanf(utsname()->release, "%u.%u", &os_ver_major, &os_ver_minor); +	record->os_ver_major = cpu_to_le32(os_ver_major); +	record->os_ver_minor = cpu_to_le32(os_ver_minor); + +	strcpy(record->os_name, utsname()->sysname); +	time64_to_tm(end, 0, &tm); +	record->end_year = cpu_to_le16(tm.tm_year + 1900); +	record->end_month = cpu_to_le16(tm.tm_mon + 1); +	record->end_day = cpu_to_le16(tm.tm_mday); +	record->end_hour = cpu_to_le16(tm.tm_hour); +	record->end_minute = cpu_to_le16(tm.tm_min); +	record->end_second = cpu_to_le16(tm.tm_sec); +	record->end_utc_bias = cpu_to_le16(sys_tz.tz_minuteswest * 60); +	record->asic_id1 = cpu_to_le32(bp->chip_num << 16 | +				       bp->ver_resp.chip_rev << 8 | +				       bp->ver_resp.chip_metal); +	record->asic_id2 = 0; +	record->coredump_status = cpu_to_le32(status); +	record->ioctl_low_version = 0; +	record->ioctl_high_version = 0; +} + +static int bnxt_get_coredump(struct bnxt *bp, void *buf, u32 *dump_len) +{ +	u32 ver_get_resp_len = sizeof(struct hwrm_ver_get_output); +	struct coredump_segment_record *seg_record = NULL; +	u32 offset = 0, seg_hdr_len, seg_record_len; +	struct bnxt_coredump_segment_hdr seg_hdr; +	struct bnxt_coredump_record coredump_rec; +	struct bnxt_coredump coredump = {NULL}; +	time64_t start_time; +	u16 start_utc; +	int rc = 0, i; + +	start_time = ktime_get_real_seconds(); +	start_utc = sys_tz.tz_minuteswest * 60; +	seg_hdr_len = sizeof(seg_hdr); + +	/* First segment should be hwrm_ver_get response */ +	*dump_len = seg_hdr_len + ver_get_resp_len; +	if (buf) { +		bnxt_fill_coredump_seg_hdr(bp, &seg_hdr, NULL, ver_get_resp_len, +					   0, 0, 0); +		memcpy(buf + offset, &seg_hdr, seg_hdr_len); +		offset += seg_hdr_len; +		memcpy(buf + offset, &bp->ver_resp, ver_get_resp_len); +		offset += ver_get_resp_len; +	} + +	rc = bnxt_hwrm_dbg_coredump_list(bp, &coredump); +	if (rc) { +		netdev_err(bp->dev, "Failed to get coredump segment list\n"); +		goto err; +	} + +	*dump_len += seg_hdr_len * coredump.total_segs; + +	seg_record = (struct coredump_segment_record *)coredump.data; +	seg_record_len = sizeof(*seg_record); + +	for (i = 0; i < coredump.total_segs; i++) { +		u16 comp_id = le16_to_cpu(seg_record->component_id); +		u16 seg_id = le16_to_cpu(seg_record->segment_id); +		u32 duration = 0, seg_len = 0; +		unsigned long start, end; + +		start = jiffies; + +		rc = bnxt_hwrm_dbg_coredump_initiate(bp, comp_id, seg_id); +		if (rc) { +			netdev_err(bp->dev, +				   "Failed to initiate coredump for seg = %d\n", +				   seg_record->segment_id); +			goto next_seg; +		} + +		/* Write segment data into the buffer */ +		rc = bnxt_hwrm_dbg_coredump_retrieve(bp, comp_id, seg_id, +						     &seg_len, buf, +						     offset + seg_hdr_len); +		if (rc) +			netdev_err(bp->dev, +				   "Failed to retrieve coredump for seg = %d\n", +				   seg_record->segment_id); + +next_seg: +		end = jiffies; +		duration = jiffies_to_msecs(end - start); +		bnxt_fill_coredump_seg_hdr(bp, &seg_hdr, seg_record, seg_len, +					   rc, duration, 0); + +		if (buf) { +			/* Write segment header into the buffer */ +			memcpy(buf + offset, &seg_hdr, seg_hdr_len); +			offset += seg_hdr_len + seg_len; +		} + +		*dump_len += seg_len; +		seg_record = +			(struct coredump_segment_record *)((u8 *)seg_record + +							   seg_record_len); +	} + +err: +	if (buf) { +		bnxt_fill_coredump_record(bp, &coredump_rec, start_time, +					  start_utc, coredump.total_segs + 1, +					  rc); +		memcpy(buf + offset, &coredump_rec, sizeof(coredump_rec)); +	} +	kfree(coredump.data); +	*dump_len += sizeof(coredump_rec); + +	return rc; +} + +static int bnxt_get_dump_flag(struct net_device *dev, struct ethtool_dump *dump) +{ +	struct bnxt *bp = netdev_priv(dev); + +	if (bp->hwrm_spec_code < 0x10801) +		return -EOPNOTSUPP; + +	dump->version = bp->ver_resp.hwrm_fw_maj_8b << 24 | +			bp->ver_resp.hwrm_fw_min_8b << 16 | +			bp->ver_resp.hwrm_fw_bld_8b << 8 | +			bp->ver_resp.hwrm_fw_rsvd_8b; + +	return bnxt_get_coredump(bp, NULL, &dump->len); +} + +static int bnxt_get_dump_data(struct net_device *dev, struct ethtool_dump *dump, +			      void *buf) +{ +	struct bnxt *bp = netdev_priv(dev); + +	if (bp->hwrm_spec_code < 0x10801) +		return -EOPNOTSUPP; + +	memset(buf, 0, dump->len); + +	return bnxt_get_coredump(bp, buf, &dump->len); +} +  void bnxt_ethtool_init(struct bnxt *bp)  {  	struct hwrm_selftest_qlist_output *resp = bp->hwrm_cmd_resp_addr; @@ -2788,4 +3119,6 @@ const struct ethtool_ops bnxt_ethtool_ops = {  	.set_phys_id		= bnxt_set_phys_id,  	.self_test		= bnxt_self_test,  	.reset			= bnxt_reset, +	.get_dump_flag		= bnxt_get_dump_flag, +	.get_dump_data		= bnxt_get_dump_data,  }; diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.h index 836ef682f24c..b5b65b3f8534 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.h @@ -22,6 +22,43 @@ struct bnxt_led_cfg {  	u8 rsvd;  }; +#define COREDUMP_LIST_BUF_LEN		2048 +#define COREDUMP_RETRIEVE_BUF_LEN	4096 + +struct bnxt_coredump { +	void		*data; +	int		data_size; +	u16		total_segs; +}; + +struct bnxt_hwrm_dbg_dma_info { +	void *dest_buf; +	int dest_buf_size; +	u16 dma_len; +	u16 seq_off; +	u16 data_len_off; +	u16 segs; +}; + +struct hwrm_dbg_cmn_input { +	__le16 req_type; +	__le16 cmpl_ring; +	__le16 seq_id; +	__le16 target_id; +	__le64 resp_addr; +	__le64 host_dest_addr; +	__le32 host_buf_len; +}; + +struct hwrm_dbg_cmn_output { +	__le16 error_code; +	__le16 req_type; +	__le16 seq_id; +	__le16 resp_len; +	u8 flags; +	#define HWRM_DBG_CMN_FLAGS_MORE	1 +}; +  #define BNXT_LED_DFLT_ENA				\  	(PORT_LED_CFG_REQ_ENABLES_LED0_ID |		\  	 PORT_LED_CFG_REQ_ENABLES_LED0_STATE |		\ | 
