summaryrefslogtreecommitdiffstats
path: root/arch/s390/pci/pci_report.c
blob: 1b494e5ecc4d848a053193f3477829c4e003f4b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright IBM Corp. 2024
 *
 * Author(s):
 *   Niklas Schnelle <schnelle@linux.ibm.com>
 *
 */

#define KMSG_COMPONENT "zpci"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

#include <linux/kernel.h>
#include <linux/sprintf.h>
#include <linux/pci.h>

#include <asm/sclp.h>
#include <asm/debug.h>
#include <asm/pci_debug.h>

#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;
}