// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) Meta Platforms, Inc. and affiliates. */ #include #include #include "fbnic.h" #include "fbnic_fw.h" #include "fbnic_fw_log.h" void fbnic_fw_log_enable(struct fbnic_dev *fbd, bool send_hist) { int err; if (!fbnic_fw_log_ready(fbd)) return; if (fbd->fw_cap.running.mgmt.version < MIN_FW_VER_CODE_HIST) send_hist = false; err = fbnic_fw_xmit_send_logs(fbd, true, send_hist); if (err && err != -EOPNOTSUPP) dev_warn(fbd->dev, "Unable to enable firmware logs: %d\n", err); } void fbnic_fw_log_disable(struct fbnic_dev *fbd) { int err; err = fbnic_fw_xmit_send_logs(fbd, false, false); if (err && err != -EOPNOTSUPP) dev_warn(fbd->dev, "Unable to disable firmware logs: %d\n", err); } 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; fbnic_fw_log_enable(fbd, true); 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; fbnic_fw_log_disable(fbd); 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_end = head->msg + head->len + 1; entry = PTR_ALIGN(entry_end, 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; }