xref: /linux/drivers/net/ethernet/meta/fbnic/fbnic_fw_log.c (revision 8be4d31cb8aaeea27bde4b7ddb26e28a89062ebf)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) Meta Platforms, Inc. and affiliates. */
3 
4 #include <linux/spinlock.h>
5 #include <linux/vmalloc.h>
6 
7 #include "fbnic.h"
8 #include "fbnic_fw.h"
9 #include "fbnic_fw_log.h"
10 
fbnic_fw_log_enable(struct fbnic_dev * fbd,bool send_hist)11 void fbnic_fw_log_enable(struct fbnic_dev *fbd, bool send_hist)
12 {
13 	int err;
14 
15 	if (!fbnic_fw_log_ready(fbd))
16 		return;
17 
18 	if (fbd->fw_cap.running.mgmt.version < MIN_FW_VER_CODE_HIST)
19 		send_hist = false;
20 
21 	err = fbnic_fw_xmit_send_logs(fbd, true, send_hist);
22 	if (err && err != -EOPNOTSUPP)
23 		dev_warn(fbd->dev, "Unable to enable firmware logs: %d\n", err);
24 }
25 
fbnic_fw_log_disable(struct fbnic_dev * fbd)26 void fbnic_fw_log_disable(struct fbnic_dev *fbd)
27 {
28 	int err;
29 
30 	err = fbnic_fw_xmit_send_logs(fbd, false, false);
31 	if (err && err != -EOPNOTSUPP)
32 		dev_warn(fbd->dev, "Unable to disable firmware logs: %d\n",
33 			 err);
34 }
35 
fbnic_fw_log_init(struct fbnic_dev * fbd)36 int fbnic_fw_log_init(struct fbnic_dev *fbd)
37 {
38 	struct fbnic_fw_log *log = &fbd->fw_log;
39 	void *data;
40 
41 	if (WARN_ON_ONCE(fbnic_fw_log_ready(fbd)))
42 		return -EEXIST;
43 
44 	data = vmalloc(FBNIC_FW_LOG_SIZE);
45 	if (!data)
46 		return -ENOMEM;
47 
48 	spin_lock_init(&fbd->fw_log.lock);
49 	INIT_LIST_HEAD(&log->entries);
50 	log->size = FBNIC_FW_LOG_SIZE;
51 	log->data_start = data;
52 	log->data_end = data + FBNIC_FW_LOG_SIZE;
53 
54 	fbnic_fw_log_enable(fbd, true);
55 
56 	return 0;
57 }
58 
fbnic_fw_log_free(struct fbnic_dev * fbd)59 void fbnic_fw_log_free(struct fbnic_dev *fbd)
60 {
61 	struct fbnic_fw_log *log = &fbd->fw_log;
62 
63 	if (!fbnic_fw_log_ready(fbd))
64 		return;
65 
66 	fbnic_fw_log_disable(fbd);
67 	INIT_LIST_HEAD(&log->entries);
68 	log->size = 0;
69 	vfree(log->data_start);
70 	log->data_start = NULL;
71 	log->data_end = NULL;
72 }
73 
fbnic_fw_log_write(struct fbnic_dev * fbd,u64 index,u32 timestamp,char * msg)74 int fbnic_fw_log_write(struct fbnic_dev *fbd, u64 index, u32 timestamp,
75 		       char *msg)
76 {
77 	struct fbnic_fw_log_entry *entry, *head, *tail, *next;
78 	struct fbnic_fw_log *log = &fbd->fw_log;
79 	size_t msg_len = strlen(msg) + 1;
80 	unsigned long flags;
81 	void *entry_end;
82 
83 	if (!fbnic_fw_log_ready(fbd)) {
84 		dev_err(fbd->dev, "Firmware sent log entry without being requested!\n");
85 		return -ENOSPC;
86 	}
87 
88 	spin_lock_irqsave(&log->lock, flags);
89 
90 	if (list_empty(&log->entries)) {
91 		entry = log->data_start;
92 	} else {
93 		head = list_first_entry(&log->entries, typeof(*head), list);
94 		entry_end = head->msg + head->len + 1;
95 		entry = PTR_ALIGN(entry_end, 8);
96 	}
97 
98 	entry_end = entry->msg + msg_len + 1;
99 
100 	/* We've reached the end of the buffer, wrap around */
101 	if (entry_end > log->data_end) {
102 		entry = log->data_start;
103 		entry_end = entry->msg + msg_len + 1;
104 	}
105 
106 	/* Make room for entry by removing from tail. */
107 	list_for_each_entry_safe_reverse(tail, next, &log->entries, list) {
108 		if (entry <= tail && entry_end > (void *)tail)
109 			list_del(&tail->list);
110 		else
111 			break;
112 	}
113 
114 	entry->index = index;
115 	entry->timestamp = timestamp;
116 	entry->len = msg_len;
117 	strscpy(entry->msg, msg, entry->len);
118 	list_add(&entry->list, &log->entries);
119 
120 	spin_unlock_irqrestore(&log->lock, flags);
121 
122 	return 0;
123 }
124