xref: /linux/drivers/platform/x86/intel/pmt/crashlog.c (revision 5c7bfa1088274bc3820eb2083f8091ff8ad397ec)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Intel Platform Monitoring Technology Crashlog driver
4  *
5  * Copyright (c) 2020, Intel Corporation.
6  * All Rights Reserved.
7  *
8  * Author: "Alexander Duyck" <alexander.h.duyck@linux.intel.com>
9  */
10 
11 #include <linux/auxiliary_bus.h>
12 #include <linux/cleanup.h>
13 #include <linux/intel_vsec.h>
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/mutex.h>
17 #include <linux/pci.h>
18 #include <linux/slab.h>
19 #include <linux/uaccess.h>
20 #include <linux/overflow.h>
21 
22 #include "class.h"
23 
24 /* Crashlog discovery header types */
25 #define CRASH_TYPE_OOBMSM	1
26 
27 /* Control Flags */
28 #define CRASHLOG_FLAG_DISABLE		BIT(28)
29 
30 /*
31  * Bits 29 and 30 control the state of bit 31.
32  *
33  * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
34  * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
35  * Bit 31 is the read-only status with a 1 indicating log is complete.
36  */
37 #define CRASHLOG_FLAG_TRIGGER_CLEAR	BIT(29)
38 #define CRASHLOG_FLAG_TRIGGER_EXECUTE	BIT(30)
39 #define CRASHLOG_FLAG_TRIGGER_COMPLETE	BIT(31)
40 #define CRASHLOG_FLAG_TRIGGER_MASK	GENMASK(31, 28)
41 
42 /* Crashlog Discovery Header */
43 #define CONTROL_OFFSET		0x0
44 #define GUID_OFFSET		0x4
45 #define BASE_OFFSET		0x8
46 #define SIZE_OFFSET		0xC
47 #define GET_ACCESS(v)		((v) & GENMASK(3, 0))
48 #define GET_TYPE(v)		(((v) & GENMASK(7, 4)) >> 4)
49 #define GET_VERSION(v)		(((v) & GENMASK(19, 16)) >> 16)
50 /* size is in bytes */
51 #define GET_SIZE(v)		((v) * sizeof(u32))
52 
53 struct crashlog_entry {
54 	/* entry must be first member of struct */
55 	struct intel_pmt_entry		entry;
56 	struct mutex			control_mutex;
57 };
58 
59 struct pmt_crashlog_priv {
60 	int			num_entries;
61 	struct crashlog_entry	entry[];
62 };
63 
64 /*
65  * I/O
66  */
67 static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
68 {
69 	u32 control = readl(entry->disc_table + CONTROL_OFFSET);
70 
71 	/* return current value of the crashlog complete flag */
72 	return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
73 }
74 
75 static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
76 {
77 	u32 control = readl(entry->disc_table + CONTROL_OFFSET);
78 
79 	/* return current value of the crashlog disabled flag */
80 	return !!(control & CRASHLOG_FLAG_DISABLE);
81 }
82 
83 static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
84 {
85 	u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
86 	u32 crash_type, version;
87 
88 	crash_type = GET_TYPE(discovery_header);
89 	version = GET_VERSION(discovery_header);
90 
91 	/*
92 	 * Currently we only recognize OOBMSM version 0 devices.
93 	 * We can ignore all other crashlog devices in the system.
94 	 */
95 	return crash_type == CRASH_TYPE_OOBMSM && version == 0;
96 }
97 
98 static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
99 				     bool disable)
100 {
101 	u32 control = readl(entry->disc_table + CONTROL_OFFSET);
102 
103 	/* clear trigger bits so we are only modifying disable flag */
104 	control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
105 
106 	if (disable)
107 		control |= CRASHLOG_FLAG_DISABLE;
108 	else
109 		control &= ~CRASHLOG_FLAG_DISABLE;
110 
111 	writel(control, entry->disc_table + CONTROL_OFFSET);
112 }
113 
114 static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
115 {
116 	u32 control = readl(entry->disc_table + CONTROL_OFFSET);
117 
118 	control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
119 	control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
120 
121 	writel(control, entry->disc_table + CONTROL_OFFSET);
122 }
123 
124 static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
125 {
126 	u32 control = readl(entry->disc_table + CONTROL_OFFSET);
127 
128 	control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
129 	control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
130 
131 	writel(control, entry->disc_table + CONTROL_OFFSET);
132 }
133 
134 /*
135  * sysfs
136  */
137 static ssize_t
138 enable_show(struct device *dev, struct device_attribute *attr, char *buf)
139 {
140 	struct intel_pmt_entry *entry = dev_get_drvdata(dev);
141 	bool enabled = !pmt_crashlog_disabled(entry);
142 
143 	return sprintf(buf, "%d\n", enabled);
144 }
145 
146 static ssize_t
147 enable_store(struct device *dev, struct device_attribute *attr,
148 	     const char *buf, size_t count)
149 {
150 	struct crashlog_entry *entry;
151 	bool enabled;
152 	int result;
153 
154 	entry = dev_get_drvdata(dev);
155 
156 	result = kstrtobool(buf, &enabled);
157 	if (result)
158 		return result;
159 
160 	guard(mutex)(&entry->control_mutex);
161 
162 	pmt_crashlog_set_disable(&entry->entry, !enabled);
163 
164 	return count;
165 }
166 static DEVICE_ATTR_RW(enable);
167 
168 static ssize_t
169 trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
170 {
171 	struct intel_pmt_entry *entry;
172 	bool trigger;
173 
174 	entry = dev_get_drvdata(dev);
175 	trigger = pmt_crashlog_complete(entry);
176 
177 	return sprintf(buf, "%d\n", trigger);
178 }
179 
180 static ssize_t
181 trigger_store(struct device *dev, struct device_attribute *attr,
182 	      const char *buf, size_t count)
183 {
184 	struct crashlog_entry *entry;
185 	bool trigger;
186 	int result;
187 
188 	entry = dev_get_drvdata(dev);
189 
190 	result = kstrtobool(buf, &trigger);
191 	if (result)
192 		return result;
193 
194 	guard(mutex)(&entry->control_mutex);
195 
196 	/* if device is currently disabled, return busy */
197 	if (pmt_crashlog_disabled(&entry->entry))
198 		return -EBUSY;
199 
200 	if (!trigger) {
201 		pmt_crashlog_set_clear(&entry->entry);
202 		return count;
203 	}
204 
205 	/* we cannot trigger a new crash if one is still pending */
206 	if (pmt_crashlog_complete(&entry->entry))
207 		return -EEXIST;
208 
209 	pmt_crashlog_set_execute(&entry->entry);
210 
211 	return count;
212 }
213 static DEVICE_ATTR_RW(trigger);
214 
215 static struct attribute *pmt_crashlog_attrs[] = {
216 	&dev_attr_enable.attr,
217 	&dev_attr_trigger.attr,
218 	NULL
219 };
220 
221 static const struct attribute_group pmt_crashlog_group = {
222 	.attrs	= pmt_crashlog_attrs,
223 };
224 
225 static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
226 				      struct device *dev)
227 {
228 	void __iomem *disc_table = entry->disc_table;
229 	struct intel_pmt_header *header = &entry->header;
230 	struct crashlog_entry *crashlog;
231 
232 	if (!pmt_crashlog_supported(entry))
233 		return 1;
234 
235 	/* initialize control mutex */
236 	crashlog = container_of(entry, struct crashlog_entry, entry);
237 	mutex_init(&crashlog->control_mutex);
238 
239 	header->access_type = GET_ACCESS(readl(disc_table));
240 	header->guid = readl(disc_table + GUID_OFFSET);
241 	header->base_offset = readl(disc_table + BASE_OFFSET);
242 
243 	/* Size is measured in DWORDS, but accessor returns bytes */
244 	header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
245 
246 	return 0;
247 }
248 
249 static DEFINE_XARRAY_ALLOC(crashlog_array);
250 static struct intel_pmt_namespace pmt_crashlog_ns = {
251 	.name = "crashlog",
252 	.xa = &crashlog_array,
253 	.attr_grp = &pmt_crashlog_group,
254 	.pmt_header_decode = pmt_crashlog_header_decode,
255 };
256 
257 /*
258  * initialization
259  */
260 static void pmt_crashlog_remove(struct auxiliary_device *auxdev)
261 {
262 	struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev);
263 	int i;
264 
265 	for (i = 0; i < priv->num_entries; i++) {
266 		struct crashlog_entry *crashlog = &priv->entry[i];
267 
268 		intel_pmt_dev_destroy(&crashlog->entry, &pmt_crashlog_ns);
269 		mutex_destroy(&crashlog->control_mutex);
270 	}
271 }
272 
273 static int pmt_crashlog_probe(struct auxiliary_device *auxdev,
274 			      const struct auxiliary_device_id *id)
275 {
276 	struct intel_vsec_device *intel_vsec_dev = auxdev_to_ivdev(auxdev);
277 	struct pmt_crashlog_priv *priv;
278 	size_t size;
279 	int i, ret;
280 
281 	size = struct_size(priv, entry, intel_vsec_dev->num_resources);
282 	priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL);
283 	if (!priv)
284 		return -ENOMEM;
285 
286 	auxiliary_set_drvdata(auxdev, priv);
287 
288 	for (i = 0; i < intel_vsec_dev->num_resources; i++) {
289 		struct intel_pmt_entry *entry = &priv->entry[priv->num_entries].entry;
290 
291 		ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, intel_vsec_dev, i);
292 		if (ret < 0)
293 			goto abort_probe;
294 		if (ret)
295 			continue;
296 
297 		priv->num_entries++;
298 	}
299 
300 	return 0;
301 abort_probe:
302 	pmt_crashlog_remove(auxdev);
303 	return ret;
304 }
305 
306 static const struct auxiliary_device_id pmt_crashlog_id_table[] = {
307 	{ .name = "intel_vsec.crashlog" },
308 	{}
309 };
310 MODULE_DEVICE_TABLE(auxiliary, pmt_crashlog_id_table);
311 
312 static struct auxiliary_driver pmt_crashlog_aux_driver = {
313 	.id_table	= pmt_crashlog_id_table,
314 	.remove		= pmt_crashlog_remove,
315 	.probe		= pmt_crashlog_probe,
316 };
317 
318 static int __init pmt_crashlog_init(void)
319 {
320 	return auxiliary_driver_register(&pmt_crashlog_aux_driver);
321 }
322 
323 static void __exit pmt_crashlog_exit(void)
324 {
325 	auxiliary_driver_unregister(&pmt_crashlog_aux_driver);
326 	xa_destroy(&crashlog_array);
327 }
328 
329 module_init(pmt_crashlog_init);
330 module_exit(pmt_crashlog_exit);
331 
332 MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
333 MODULE_DESCRIPTION("Intel PMT Crashlog driver");
334 MODULE_LICENSE("GPL v2");
335 MODULE_IMPORT_NS("INTEL_PMT");
336