xref: /linux/arch/s390/pci/pci_report.c (revision 7fc2cd2e4b398c57c9cf961cfea05eadbf34c05c)
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