Commit d18e43dd authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'eth-fbnic-add-firmware-logging-support'

Lee Trager says:

====================
eth: fbnic: Add firmware logging support

Firmware running on fbnic generates device logs. These logs contain useful
information about the device which may or may not be related to the host.
Logs are stored in a ring buffer and accessible through DebugFS.
====================

Link: https://patch.msgid.link/20250702192207.697368-1-lee@trager.us


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 80b0dd1c 432407c8
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ fbnic-y := fbnic_csr.o \
	   fbnic_devlink.o \
	   fbnic_ethtool.o \
	   fbnic_fw.o \
	   fbnic_fw_log.o \
	   fbnic_hw_stats.o \
	   fbnic_hwmon.o \
	   fbnic_irq.o \
+3 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@

#include "fbnic_csr.h"
#include "fbnic_fw.h"
#include "fbnic_fw_log.h"
#include "fbnic_hw_stats.h"
#include "fbnic_mac.h"
#include "fbnic_rpc.h"
@@ -85,6 +86,8 @@ struct fbnic_dev {

	/* Lock protecting access to hw_stats */
	spinlock_t hw_stats_lock;

	struct fbnic_fw_log fw_log;
};

/* Reserve entry 0 in the MSI-X "others" array until we have filled all
+21 −6
Original line number Diff line number Diff line
@@ -12,13 +12,28 @@
#define DESC_BIT(nr)		BIT_ULL(nr)
#define DESC_GENMASK(h, l)	GENMASK_ULL(h, l)

#define FW_VER_CODE(_major, _minor, _patch, _build) (		      \
		FIELD_PREP(FBNIC_FW_CAP_RESP_VERSION_MAJOR, _major) | \
		FIELD_PREP(FBNIC_FW_CAP_RESP_VERSION_MINOR, _minor) | \
		FIELD_PREP(FBNIC_FW_CAP_RESP_VERSION_PATCH, _patch) | \
		FIELD_PREP(FBNIC_FW_CAP_RESP_VERSION_BUILD, _build))

/* Defines the minimum firmware version required by the driver */
#define MIN_FW_MAJOR_VERSION    0
#define MIN_FW_MINOR_VERSION    10
#define MIN_FW_BUILD_VERSION    6
#define MIN_FW_VERSION_CODE     (MIN_FW_MAJOR_VERSION * (1u << 24) + \
				 MIN_FW_MINOR_VERSION * (1u << 16) + \
				 MIN_FW_BUILD_VERSION)
#define MIN_FW_VER_CODE				FW_VER_CODE(0, 10, 6, 0)

/* Defines the minimum firmware version required for firmware logs */
#define MIN_FW_VER_CODE_LOG			FW_VER_CODE(0, 12, 9, 0)

/* Driver can request that firmware sends all cached logs in bulk. This
 * feature was enabled on older firmware however firmware has a bug
 * which attempted to send 30 messages per mbx message which caused an
 * overflow flooding the mailbox. This results in a kernel warning
 * related to corrupt mailbox messages.
 *
 * If firmware is new enough only request sending historical logs when
 * the log buffer is empty to prevent duplicate logs.
 */
#define MIN_FW_VER_CODE_HIST			FW_VER_CODE(25, 5, 7, 0)

#define PCI_DEVICE_ID_META_FBNIC_ASIC		0x0013

+29 −0
Original line number Diff line number Diff line
@@ -170,6 +170,33 @@ static int fbnic_dbg_ipo_dst_show(struct seq_file *s, void *v)
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ipo_dst);

static int fbnic_dbg_fw_log_show(struct seq_file *s, void *v)
{
	struct fbnic_dev *fbd = s->private;
	struct fbnic_fw_log_entry *entry;
	unsigned long flags;

	if (!fbnic_fw_log_ready(fbd))
		return -ENXIO;

	spin_lock_irqsave(&fbd->fw_log.lock, flags);

	list_for_each_entry_reverse(entry, &fbd->fw_log.entries, list) {
		seq_printf(s, FBNIC_FW_LOG_FMT, entry->index,
			   (entry->timestamp / (MSEC_PER_SEC * 60 * 60 * 24)),
			   (entry->timestamp / (MSEC_PER_SEC * 60 * 60)) % 24,
			   ((entry->timestamp / (MSEC_PER_SEC * 60) % 60)),
			   ((entry->timestamp / MSEC_PER_SEC) % 60),
			   (entry->timestamp % MSEC_PER_SEC),
			   entry->msg);
	}

	spin_unlock_irqrestore(&fbd->fw_log.lock, flags);

	return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_fw_log);

static int fbnic_dbg_pcie_stats_show(struct seq_file *s, void *v)
{
	struct fbnic_dev *fbd = s->private;
@@ -222,6 +249,8 @@ void fbnic_dbg_fbd_init(struct fbnic_dev *fbd)
			    &fbnic_dbg_ipo_src_fops);
	debugfs_create_file("ipo_dst", 0400, fbd->dbg_fbd, fbd,
			    &fbnic_dbg_ipo_dst_fops);
	debugfs_create_file("fw_log", 0400, fbd->dbg_fbd, fbd,
			    &fbnic_dbg_fw_log_fops);
}

void fbnic_dbg_fbd_exit(struct fbnic_dev *fbd)
+172 −7
Original line number Diff line number Diff line
@@ -573,16 +573,15 @@ static int fbnic_fw_parse_cap_resp(void *opaque, struct fbnic_tlv_msg **results)
	if (!fbd->fw_cap.running.mgmt.version)
		return -EINVAL;

	if (fbd->fw_cap.running.mgmt.version < MIN_FW_VERSION_CODE) {
	if (fbd->fw_cap.running.mgmt.version < MIN_FW_VER_CODE) {
		char required_ver[FBNIC_FW_VER_MAX_SIZE];
		char running_ver[FBNIC_FW_VER_MAX_SIZE];

		fbnic_mk_fw_ver_str(fbd->fw_cap.running.mgmt.version,
				    running_ver);
		dev_err(fbd->dev, "Device firmware version(%s) is older than minimum required version(%02d.%02d.%02d)\n",
			running_ver,
			MIN_FW_MAJOR_VERSION,
			MIN_FW_MINOR_VERSION,
			MIN_FW_BUILD_VERSION);
		fbnic_mk_fw_ver_str(MIN_FW_VER_CODE, required_ver);
		dev_err(fbd->dev, "Device firmware version(%s) is older than minimum required version(%s)\n",
			running_ver, required_ver);
		/* Disable TX mailbox to prevent card use until firmware is
		 * updated.
		 */
@@ -1035,6 +1034,169 @@ static int fbnic_fw_parse_tsene_read_resp(void *opaque,
	return err;
}

static const struct fbnic_tlv_index fbnic_fw_log_req_index[] = {
	FBNIC_TLV_ATTR_U32(FBNIC_FW_LOG_MSEC),
	FBNIC_TLV_ATTR_U64(FBNIC_FW_LOG_INDEX),
	FBNIC_TLV_ATTR_STRING(FBNIC_FW_LOG_MSG, FBNIC_FW_LOG_MAX_SIZE),
	FBNIC_TLV_ATTR_U32(FBNIC_FW_LOG_LENGTH),
	FBNIC_TLV_ATTR_ARRAY(FBNIC_FW_LOG_MSEC_ARRAY),
	FBNIC_TLV_ATTR_ARRAY(FBNIC_FW_LOG_INDEX_ARRAY),
	FBNIC_TLV_ATTR_ARRAY(FBNIC_FW_LOG_MSG_ARRAY),
	FBNIC_TLV_ATTR_LAST
};

static int fbnic_fw_process_log_array(struct fbnic_tlv_msg **results,
				      u16 length, u16 arr_type_idx,
				      u16 attr_type_idx,
				      struct fbnic_tlv_msg **tlv_array_out)
{
	struct fbnic_tlv_msg *attr;
	int attr_len;
	int err;

	if (!results[attr_type_idx])
		return -EINVAL;

	tlv_array_out[0] = results[attr_type_idx];

	if (!length)
		return 0;

	if (!results[arr_type_idx])
		return -EINVAL;

	attr = results[arr_type_idx];
	attr_len = le16_to_cpu(attr->hdr.len) / sizeof(u32) - 1;
	err = fbnic_tlv_attr_parse_array(&attr[1], attr_len, &tlv_array_out[1],
					 fbnic_fw_log_req_index,
					 attr_type_idx,
					 length);
	if (err)
		return err;

	return 0;
}

static int fbnic_fw_parse_logs(struct fbnic_dev *fbd,
			       struct fbnic_tlv_msg **msec_tlv,
			       struct fbnic_tlv_msg **index_tlv,
			       struct fbnic_tlv_msg **log_tlv,
			       int count)
{
	int i;

	for (i = 0; i < count; i++) {
		char log[FBNIC_FW_LOG_MAX_SIZE];
		ssize_t len;
		u64 index;
		u32 msec;
		int err;

		if (!msec_tlv[i] || !index_tlv[i] || !log_tlv[i]) {
			dev_warn(fbd->dev, "Received log message with missing attributes!\n");
			return -EINVAL;
		}

		index = fbnic_tlv_attr_get_signed(index_tlv[i], 0);
		msec = fbnic_tlv_attr_get_signed(msec_tlv[i], 0);
		len = fbnic_tlv_attr_get_string(log_tlv[i], log,
						FBNIC_FW_LOG_MAX_SIZE);
		if (len < 0)
			return len;

		err = fbnic_fw_log_write(fbd, index, msec, log);
		if (err)
			return err;
	}

	return 0;
}

static int fbnic_fw_parse_log_req(void *opaque,
				  struct fbnic_tlv_msg **results)
{
	struct fbnic_tlv_msg *index_tlv[FBNIC_FW_MAX_LOG_HISTORY];
	struct fbnic_tlv_msg *msec_tlv[FBNIC_FW_MAX_LOG_HISTORY];
	struct fbnic_tlv_msg *log_tlv[FBNIC_FW_MAX_LOG_HISTORY];
	struct fbnic_dev *fbd = opaque;
	u16 length;
	int err;

	length = fta_get_uint(results, FBNIC_FW_LOG_LENGTH);
	if (length >= FBNIC_FW_MAX_LOG_HISTORY)
		return -E2BIG;

	err = fbnic_fw_process_log_array(results, length,
					 FBNIC_FW_LOG_MSEC_ARRAY,
					 FBNIC_FW_LOG_MSEC, msec_tlv);
	if (err)
		return err;

	err = fbnic_fw_process_log_array(results, length,
					 FBNIC_FW_LOG_INDEX_ARRAY,
					 FBNIC_FW_LOG_INDEX, index_tlv);
	if (err)
		return err;

	err = fbnic_fw_process_log_array(results, length,
					 FBNIC_FW_LOG_MSG_ARRAY,
					 FBNIC_FW_LOG_MSG, log_tlv);
	if (err)
		return err;

	err = fbnic_fw_parse_logs(fbd, msec_tlv, index_tlv, log_tlv,
				  length + 1);
	if (err)
		return err;

	return 0;
}

int fbnic_fw_xmit_send_logs(struct fbnic_dev *fbd, bool enable,
			    bool send_log_history)
{
	struct fbnic_tlv_msg *msg;
	int err;

	if (fbd->fw_cap.running.mgmt.version < MIN_FW_VER_CODE_LOG) {
		dev_warn(fbd->dev, "Firmware version is too old to support firmware logs!\n");
		return -EOPNOTSUPP;
	}

	msg = fbnic_tlv_msg_alloc(FBNIC_TLV_MSG_ID_LOG_SEND_LOGS_REQ);
	if (!msg)
		return -ENOMEM;

	if (enable) {
		err = fbnic_tlv_attr_put_flag(msg, FBNIC_SEND_LOGS);
		if (err)
			goto free_message;

		/* Report request for version 1 of logs */
		err = fbnic_tlv_attr_put_int(msg, FBNIC_SEND_LOGS_VERSION,
					     FBNIC_FW_LOG_VERSION);
		if (err)
			goto free_message;

		if (send_log_history) {
			err = fbnic_tlv_attr_put_flag(msg,
						      FBNIC_SEND_LOGS_HISTORY);
			if (err)
				goto free_message;
		}
	}

	err = fbnic_mbx_map_tlv_msg(fbd, msg);
	if (err)
		goto free_message;

	return 0;

free_message:
	free_page((unsigned long)msg);
	return err;
}

static const struct fbnic_tlv_parser fbnic_fw_tlv_parser[] = {
	FBNIC_TLV_PARSER(FW_CAP_RESP, fbnic_fw_cap_resp_index,
			 fbnic_fw_parse_cap_resp),
@@ -1054,6 +1216,9 @@ static const struct fbnic_tlv_parser fbnic_fw_tlv_parser[] = {
	FBNIC_TLV_PARSER(TSENE_READ_RESP,
			 fbnic_tsene_read_resp_index,
			 fbnic_fw_parse_tsene_read_resp),
	FBNIC_TLV_PARSER(LOG_MSG_REQ,
			 fbnic_fw_log_req_index,
			 fbnic_fw_parse_log_req),
	FBNIC_TLV_MSG_ERROR
};

@@ -1167,7 +1332,7 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd)
	 * to indicate we entered the polling state waiting for a response
	 */
	for (fbd->fw_cap.running.mgmt.version = 1;
	     fbd->fw_cap.running.mgmt.version < MIN_FW_VERSION_CODE;) {
	     fbd->fw_cap.running.mgmt.version < MIN_FW_VER_CODE;) {
		if (!tx_mbx->ready)
			err = -ENODEV;
		if (err)
Loading