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 return 0;
55 }
56
fbnic_fw_log_free(struct fbnic_dev * fbd)57 void fbnic_fw_log_free(struct fbnic_dev *fbd)
58 {
59 struct fbnic_fw_log *log = &fbd->fw_log;
60
61 if (!fbnic_fw_log_ready(fbd))
62 return;
63
64 INIT_LIST_HEAD(&log->entries);
65 log->size = 0;
66 vfree(log->data_start);
67 log->data_start = NULL;
68 log->data_end = NULL;
69 }
70
fbnic_fw_log_write(struct fbnic_dev * fbd,u64 index,u32 timestamp,const char * msg)71 int fbnic_fw_log_write(struct fbnic_dev *fbd, u64 index, u32 timestamp,
72 const char *msg)
73 {
74 struct fbnic_fw_log_entry *entry, *head, *tail, *next;
75 struct fbnic_fw_log *log = &fbd->fw_log;
76 size_t msg_len = strlen(msg) + 1;
77 unsigned long flags;
78 void *entry_end;
79
80 if (!fbnic_fw_log_ready(fbd)) {
81 dev_err(fbd->dev, "Firmware sent log entry without being requested!\n");
82 return -ENOSPC;
83 }
84
85 spin_lock_irqsave(&log->lock, flags);
86
87 if (list_empty(&log->entries)) {
88 entry = log->data_start;
89 } else {
90 head = list_first_entry(&log->entries, typeof(*head), list);
91 entry_end = head->msg + head->len + 1;
92 entry = PTR_ALIGN(entry_end, 8);
93 }
94
95 entry_end = entry->msg + msg_len + 1;
96
97 /* We've reached the end of the buffer, wrap around */
98 if (entry_end > log->data_end) {
99 entry = log->data_start;
100 entry_end = entry->msg + msg_len + 1;
101 }
102
103 /* Make room for entry by removing from tail. */
104 list_for_each_entry_safe_reverse(tail, next, &log->entries, list) {
105 if (entry <= tail && entry_end > (void *)tail)
106 list_del(&tail->list);
107 else
108 break;
109 }
110
111 entry->index = index;
112 entry->timestamp = timestamp;
113 entry->len = msg_len;
114 strscpy(entry->msg, msg, entry->len);
115 list_add(&entry->list, &log->entries);
116
117 spin_unlock_irqrestore(&log->lock, flags);
118
119 return 0;
120 }
121