Commit c2b93d6b authored by Lee Trager's avatar Lee Trager Committed by Jakub Kicinski
Browse files

eth: fbnic: Create ring buffer for firmware logs



When enabled, firmware may send logs messages which are specific to the
device and not the host. Create a ring buffer to store these messages
which are read by a user through DebugFS. Buffer access is protected by
a spinlock.

Signed-off-by: default avatarLee Trager <lee@trager.us>
Reviewed-by: default avatarJacob Keller <jacob.e.keller@intel.com>
Link: https://patch.msgid.link/20250702192207.697368-4-lee@trager.us


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent e48f6620
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
+95 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */

#include <linux/spinlock.h>
#include <linux/vmalloc.h>

#include "fbnic.h"
#include "fbnic_fw.h"
#include "fbnic_fw_log.h"

int fbnic_fw_log_init(struct fbnic_dev *fbd)
{
	struct fbnic_fw_log *log = &fbd->fw_log;
	void *data;

	if (WARN_ON_ONCE(fbnic_fw_log_ready(fbd)))
		return -EEXIST;

	data = vmalloc(FBNIC_FW_LOG_SIZE);
	if (!data)
		return -ENOMEM;

	spin_lock_init(&fbd->fw_log.lock);
	INIT_LIST_HEAD(&log->entries);
	log->size = FBNIC_FW_LOG_SIZE;
	log->data_start = data;
	log->data_end = data + FBNIC_FW_LOG_SIZE;

	return 0;
}

void fbnic_fw_log_free(struct fbnic_dev *fbd)
{
	struct fbnic_fw_log *log = &fbd->fw_log;

	if (!fbnic_fw_log_ready(fbd))
		return;

	INIT_LIST_HEAD(&log->entries);
	log->size = 0;
	vfree(log->data_start);
	log->data_start = NULL;
	log->data_end = NULL;
}

int fbnic_fw_log_write(struct fbnic_dev *fbd, u64 index, u32 timestamp,
		       char *msg)
{
	struct fbnic_fw_log_entry *entry, *head, *tail, *next;
	struct fbnic_fw_log *log = &fbd->fw_log;
	size_t msg_len = strlen(msg) + 1;
	unsigned long flags;
	void *entry_end;

	if (!fbnic_fw_log_ready(fbd)) {
		dev_err(fbd->dev, "Firmware sent log entry without being requested!\n");
		return -ENOSPC;
	}

	spin_lock_irqsave(&log->lock, flags);

	if (list_empty(&log->entries)) {
		entry = log->data_start;
	} else {
		head = list_first_entry(&log->entries, typeof(*head), list);
		entry = (struct fbnic_fw_log_entry *)&head->msg[head->len + 1];
		entry = PTR_ALIGN(entry, 8);
	}

	entry_end = &entry->msg[msg_len + 1];

	/* We've reached the end of the buffer, wrap around */
	if (entry_end > log->data_end) {
		entry = log->data_start;
		entry_end = &entry->msg[msg_len + 1];
	}

	/* Make room for entry by removing from tail. */
	list_for_each_entry_safe_reverse(tail, next, &log->entries, list) {
		if (entry <= tail && entry_end > (void *)tail)
			list_del(&tail->list);
		else
			break;
	}

	entry->index = index;
	entry->timestamp = timestamp;
	entry->len = msg_len;
	strscpy(entry->msg, msg, entry->len);
	list_add(&entry->list, &log->entries);

	spin_unlock_irqrestore(&log->lock, flags);

	return 0;
}
+43 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */

#ifndef _FBNIC_FW_LOG_H_
#define _FBNIC_FW_LOG_H_

#include <linux/spinlock.h>
#include <linux/types.h>

/* A 512K log buffer was chosen fairly arbitrarily */
#define FBNIC_FW_LOG_SIZE	(512 * 1024) /* bytes */

/* Firmware log output is prepended with log index followed by a timestamp.
 * The timestamp is similar to Zephyr's format DD:HH:MM:SS.MMM
 */
#define FBNIC_FW_LOG_FMT	"[%5lld] [%02ld:%02ld:%02ld:%02ld.%03ld] %s\n"

struct fbnic_dev;

struct fbnic_fw_log_entry {
	struct list_head	list;
	u64			index;
	u32			timestamp;
	u16			len;
	char			msg[] __counted_by(len);
};

struct fbnic_fw_log {
	void			*data_start;
	void			*data_end;
	size_t			size;
	struct list_head	entries;
	/* Spin lock for accessing or modifying entries */
	spinlock_t		lock;
};

#define fbnic_fw_log_ready(_fbd)	(!!(_fbd)->fw_log.data_start)

int fbnic_fw_log_init(struct fbnic_dev *fbd);
void fbnic_fw_log_free(struct fbnic_dev *fbd);
int fbnic_fw_log_write(struct fbnic_dev *fbd, u64 index, u32 timestamp,
		       char *msg);
#endif /* _FBNIC_FW_LOG_H_ */