// SPDX-License-Identifier: GPL-2.0 /* * Copyright IBM Corp. 2024 * * Author(s): * Niklas Schnelle * */ #define KMSG_COMPONENT "zpci" #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt #include #include #include #include #include #include #include "pci_report.h" #define ZPCI_ERR_LOG_ID_KERNEL_REPORT 0x4714 struct zpci_report_error_data { u64 timestamp; u64 err_log_id; char log_data[]; } __packed; #define ZPCI_REPORT_SIZE (PAGE_SIZE - sizeof(struct err_notify_sccb)) #define ZPCI_REPORT_DATA_SIZE (ZPCI_REPORT_SIZE - sizeof(struct zpci_report_error_data)) struct zpci_report_error { struct zpci_report_error_header header; struct zpci_report_error_data data; } __packed; static const char *zpci_state_str(pci_channel_state_t state) { switch (state) { case pci_channel_io_normal: return "normal"; case pci_channel_io_frozen: return "frozen"; case pci_channel_io_perm_failure: return "permanent-failure"; default: return "invalid"; }; } static int debug_log_header_fn(debug_info_t *id, struct debug_view *view, int area, debug_entry_t *entry, char *out_buf, size_t out_buf_size) { unsigned long sec, usec; unsigned int level; char *except_str; int rc = 0; level = entry->level; sec = entry->clock; usec = do_div(sec, USEC_PER_SEC); if (entry->exception) except_str = "*"; else except_str = "-"; rc += scnprintf(out_buf, out_buf_size, "%011ld:%06lu %1u %1s %04u ", sec, usec, level, except_str, entry->cpu); return rc; } static int debug_prolog_header(debug_info_t *id, struct debug_view *view, char *out_buf, size_t out_buf_size) { return scnprintf(out_buf, out_buf_size, "sec:usec level except cpu msg\n"); } static struct debug_view debug_log_view = { "pci_msg_log", &debug_prolog_header, &debug_log_header_fn, &debug_sprintf_format_fn, NULL, NULL }; /** * zpci_report_status - Report the status of operations on a PCI device * @zdev: The PCI device for which to report status * @operation: A string representing the operation reported * @status: A string representing the status of the operation * * This function creates a human readable report about an operation such as * PCI device recovery and forwards this to the platform using the SCLP Write * Event Data mechanism. Besides the operation and status strings the report * also contains additional information about the device deemed useful for * debug such as the currently bound device driver, if any, and error state. * Additionally a string representation of pci_debug_msg_id, or as much as fits, * is also included. * * Return: 0 on success an error code < 0 otherwise. */ int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status) { struct zpci_report_error *report; struct pci_driver *driver = NULL; struct pci_dev *pdev = NULL; char *buf, *end; int ret; if (!zdev || !zdev->zbus) return -ENODEV; /* Protected virtualization hosts get nothing from us */ if (prot_virt_guest) return -ENODATA; report = (void *)get_zeroed_page(GFP_KERNEL); if (!report) return -ENOMEM; if (zdev->zbus->bus) pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); if (pdev) driver = to_pci_driver(pdev->dev.driver); buf = report->data.log_data; end = report->data.log_data + ZPCI_REPORT_DATA_SIZE; buf += scnprintf(buf, end - buf, "report: %s\n", operation); buf += scnprintf(buf, end - buf, "status: %s\n", status); buf += scnprintf(buf, end - buf, "state: %s\n", (pdev) ? zpci_state_str(pdev->error_state) : "n/a"); buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); ret = debug_dump(pci_debug_msg_id, &debug_log_view, buf, end - buf, true); if (ret < 0) pr_err("Reading PCI debug messages failed with code %d\n", ret); else buf += ret; report->header.version = 1; report->header.action = SCLP_ERRNOTIFY_AQ_INFO_LOG; report->header.length = buf - (char *)&report->data; report->data.timestamp = ktime_get_clocktai_seconds(); report->data.err_log_id = ZPCI_ERR_LOG_ID_KERNEL_REPORT; ret = sclp_pci_report(&report->header, zdev->fh, zdev->fid); if (ret) pr_err("Reporting PCI status failed with code %d\n", ret); else pr_info("Reported PCI device status\n"); free_page((unsigned long)report); return ret; }