1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright IBM Corp. 2024 4 * 5 * Author(s): 6 * Niklas Schnelle <schnelle@linux.ibm.com> 7 * 8 */ 9 10 #define pr_fmt(fmt) "zpci: " fmt 11 12 #include <linux/kernel.h> 13 #include <linux/sprintf.h> 14 #include <linux/pci.h> 15 16 #include <asm/sclp.h> 17 #include <asm/debug.h> 18 #include <asm/pci_debug.h> 19 20 #include "pci_report.h" 21 22 #define ZPCI_ERR_LOG_ID_KERNEL_REPORT 0x4714 23 24 struct zpci_report_error_data { 25 u64 timestamp; 26 u64 err_log_id; 27 char log_data[]; 28 } __packed; 29 30 #define ZPCI_REPORT_SIZE (PAGE_SIZE - sizeof(struct err_notify_sccb)) 31 #define ZPCI_REPORT_DATA_SIZE (ZPCI_REPORT_SIZE - sizeof(struct zpci_report_error_data)) 32 33 struct zpci_report_error { 34 struct zpci_report_error_header header; 35 struct zpci_report_error_data data; 36 } __packed; 37 38 static const char *zpci_state_str(pci_channel_state_t state) 39 { 40 switch (state) { 41 case pci_channel_io_normal: 42 return "normal"; 43 case pci_channel_io_frozen: 44 return "frozen"; 45 case pci_channel_io_perm_failure: 46 return "permanent-failure"; 47 default: 48 return "invalid"; 49 }; 50 } 51 52 static int debug_log_header_fn(debug_info_t *id, struct debug_view *view, 53 int area, debug_entry_t *entry, char *out_buf, 54 size_t out_buf_size) 55 { 56 unsigned long sec, usec; 57 unsigned int level; 58 char *except_str; 59 int rc = 0; 60 61 level = entry->level; 62 sec = entry->clock; 63 usec = do_div(sec, USEC_PER_SEC); 64 65 if (entry->exception) 66 except_str = "*"; 67 else 68 except_str = "-"; 69 rc += scnprintf(out_buf, out_buf_size, "%011ld:%06lu %1u %1s %04u ", 70 sec, usec, level, except_str, 71 entry->cpu); 72 return rc; 73 } 74 75 static int debug_prolog_header(debug_info_t *id, struct debug_view *view, 76 char *out_buf, size_t out_buf_size) 77 { 78 return scnprintf(out_buf, out_buf_size, "sec:usec level except cpu msg\n"); 79 } 80 81 static struct debug_view debug_log_view = { 82 "pci_msg_log", 83 &debug_prolog_header, 84 &debug_log_header_fn, 85 &debug_sprintf_format_fn, 86 NULL, 87 NULL 88 }; 89 90 /** 91 * zpci_report_status - Report the status of operations on a PCI device 92 * @zdev: The PCI device for which to report status 93 * @operation: A string representing the operation reported 94 * @status: A string representing the status of the operation 95 * 96 * This function creates a human readable report about an operation such as 97 * PCI device recovery and forwards this to the platform using the SCLP Write 98 * Event Data mechanism. Besides the operation and status strings the report 99 * also contains additional information about the device deemed useful for 100 * debug such as the currently bound device driver, if any, and error state. 101 * Additionally a string representation of pci_debug_msg_id, or as much as fits, 102 * is also included. 103 * 104 * Return: 0 on success an error code < 0 otherwise. 105 */ 106 int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status) 107 { 108 struct zpci_report_error *report; 109 struct pci_driver *driver = NULL; 110 struct pci_dev *pdev = NULL; 111 char *buf, *end; 112 int ret; 113 114 if (!zdev || !zdev->zbus) 115 return -ENODEV; 116 117 /* Protected virtualization hosts get nothing from us */ 118 if (prot_virt_guest) 119 return -ENODATA; 120 121 report = (void *)get_zeroed_page(GFP_KERNEL); 122 if (!report) 123 return -ENOMEM; 124 if (zdev->zbus->bus) 125 pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); 126 if (pdev) 127 driver = to_pci_driver(pdev->dev.driver); 128 129 buf = report->data.log_data; 130 end = report->data.log_data + ZPCI_REPORT_DATA_SIZE; 131 buf += scnprintf(buf, end - buf, "report: %s\n", operation); 132 buf += scnprintf(buf, end - buf, "status: %s\n", status); 133 buf += scnprintf(buf, end - buf, "state: %s\n", 134 (pdev) ? zpci_state_str(pdev->error_state) : "n/a"); 135 buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); 136 ret = debug_dump(pci_debug_msg_id, &debug_log_view, buf, end - buf, true); 137 if (ret < 0) 138 pr_err("Reading PCI debug messages failed with code %d\n", ret); 139 else 140 buf += ret; 141 142 report->header.version = 1; 143 report->header.action = SCLP_ERRNOTIFY_AQ_INFO_LOG; 144 report->header.length = buf - (char *)&report->data; 145 report->data.timestamp = ktime_get_clocktai_seconds(); 146 report->data.err_log_id = ZPCI_ERR_LOG_ID_KERNEL_REPORT; 147 148 ret = sclp_pci_report(&report->header, zdev->fh, zdev->fid); 149 if (ret) 150 pr_err("Reporting PCI status failed with code %d\n", ret); 151 else 152 pr_info("Reported PCI device status\n"); 153 154 free_page((unsigned long)report); 155 156 return ret; 157 } 158