xref: /linux/drivers/platform/x86/intel/pmt/crashlog.c (revision 66df9fa783aadc2a5ae8ca11ead0b13032d24e7e)
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 
68 /* Read, modify, write the control register, setting or clearing @bit based on @set */
69 static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
70 {
71 	struct intel_pmt_entry *entry = &crashlog->entry;
72 	u32 reg = readl(entry->disc_table + CONTROL_OFFSET);
73 
74 	reg &= ~CRASHLOG_FLAG_TRIGGER_MASK;
75 
76 	if (set)
77 		reg |= bit;
78 	else
79 		reg &= ~bit;
80 
81 	writel(reg, entry->disc_table + CONTROL_OFFSET);
82 }
83 
84 /* Read the status register and see if the specified @bit is set */
85 static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit)
86 {
87 	struct intel_pmt_entry *entry = &crashlog->entry;
88 	u32 reg = readl(entry->disc_table + CONTROL_OFFSET);
89 
90 	return !!(reg & bit);
91 }
92 
93 static bool pmt_crashlog_complete(struct crashlog_entry *crashlog)
94 {
95 	/* return current value of the crashlog complete flag */
96 	return pmt_crashlog_rc(crashlog, CRASHLOG_FLAG_TRIGGER_COMPLETE);
97 }
98 
99 static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog)
100 {
101 	/* return current value of the crashlog disabled flag */
102 	return pmt_crashlog_rc(crashlog, CRASHLOG_FLAG_DISABLE);
103 }
104 
105 static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
106 {
107 	u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
108 	u32 crash_type, version;
109 
110 	crash_type = GET_TYPE(discovery_header);
111 	version = GET_VERSION(discovery_header);
112 
113 	/*
114 	 * Currently we only recognize OOBMSM version 0 devices.
115 	 * We can ignore all other crashlog devices in the system.
116 	 */
117 	return crash_type == CRASH_TYPE_OOBMSM && version == 0;
118 }
119 
120 static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
121 				     bool disable)
122 {
123 	pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_DISABLE, disable);
124 }
125 
126 static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
127 {
128 	pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_TRIGGER_CLEAR, true);
129 }
130 
131 static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
132 {
133 	pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_TRIGGER_EXECUTE, true);
134 }
135 
136 /*
137  * sysfs
138  */
139 static ssize_t
140 enable_show(struct device *dev, struct device_attribute *attr, char *buf)
141 {
142 	struct crashlog_entry *crashlog = dev_get_drvdata(dev);
143 	bool enabled = !pmt_crashlog_disabled(crashlog);
144 
145 	return sprintf(buf, "%d\n", enabled);
146 }
147 
148 static ssize_t
149 enable_store(struct device *dev, struct device_attribute *attr,
150 	     const char *buf, size_t count)
151 {
152 	struct crashlog_entry *crashlog;
153 	bool enabled;
154 	int result;
155 
156 	crashlog = dev_get_drvdata(dev);
157 
158 	result = kstrtobool(buf, &enabled);
159 	if (result)
160 		return result;
161 
162 	guard(mutex)(&crashlog->control_mutex);
163 
164 	pmt_crashlog_set_disable(crashlog, !enabled);
165 
166 	return count;
167 }
168 static DEVICE_ATTR_RW(enable);
169 
170 static ssize_t
171 trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
172 {
173 	struct crashlog_entry *crashlog;
174 	bool trigger;
175 
176 	crashlog = dev_get_drvdata(dev);
177 	trigger = pmt_crashlog_complete(crashlog);
178 
179 	return sprintf(buf, "%d\n", trigger);
180 }
181 
182 static ssize_t
183 trigger_store(struct device *dev, struct device_attribute *attr,
184 	      const char *buf, size_t count)
185 {
186 	struct crashlog_entry *crashlog;
187 	bool trigger;
188 	int result;
189 
190 	crashlog = dev_get_drvdata(dev);
191 
192 	result = kstrtobool(buf, &trigger);
193 	if (result)
194 		return result;
195 
196 	guard(mutex)(&crashlog->control_mutex);
197 
198 	/* if device is currently disabled, return busy */
199 	if (pmt_crashlog_disabled(crashlog))
200 		return -EBUSY;
201 
202 	if (!trigger) {
203 		pmt_crashlog_set_clear(crashlog);
204 		return count;
205 	}
206 
207 	/* we cannot trigger a new crash if one is still pending */
208 	if (pmt_crashlog_complete(crashlog))
209 		return -EEXIST;
210 
211 	pmt_crashlog_set_execute(crashlog);
212 
213 	return count;
214 }
215 static DEVICE_ATTR_RW(trigger);
216 
217 static struct attribute *pmt_crashlog_attrs[] = {
218 	&dev_attr_enable.attr,
219 	&dev_attr_trigger.attr,
220 	NULL
221 };
222 
223 static const struct attribute_group pmt_crashlog_group = {
224 	.attrs	= pmt_crashlog_attrs,
225 };
226 
227 static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
228 				      struct device *dev)
229 {
230 	void __iomem *disc_table = entry->disc_table;
231 	struct intel_pmt_header *header = &entry->header;
232 	struct crashlog_entry *crashlog;
233 
234 	if (!pmt_crashlog_supported(entry))
235 		return 1;
236 
237 	/* initialize control mutex */
238 	crashlog = container_of(entry, struct crashlog_entry, entry);
239 	mutex_init(&crashlog->control_mutex);
240 
241 	header->access_type = GET_ACCESS(readl(disc_table));
242 	header->guid = readl(disc_table + GUID_OFFSET);
243 	header->base_offset = readl(disc_table + BASE_OFFSET);
244 
245 	/* Size is measured in DWORDS, but accessor returns bytes */
246 	header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
247 
248 	entry->attr_grp = &pmt_crashlog_group;
249 
250 	return 0;
251 }
252 
253 static DEFINE_XARRAY_ALLOC(crashlog_array);
254 static struct intel_pmt_namespace pmt_crashlog_ns = {
255 	.name = "crashlog",
256 	.xa = &crashlog_array,
257 	.pmt_header_decode = pmt_crashlog_header_decode,
258 };
259 
260 /*
261  * initialization
262  */
263 static void pmt_crashlog_remove(struct auxiliary_device *auxdev)
264 {
265 	struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev);
266 	int i;
267 
268 	for (i = 0; i < priv->num_entries; i++) {
269 		struct crashlog_entry *crashlog = &priv->entry[i];
270 
271 		intel_pmt_dev_destroy(&crashlog->entry, &pmt_crashlog_ns);
272 		mutex_destroy(&crashlog->control_mutex);
273 	}
274 }
275 
276 static int pmt_crashlog_probe(struct auxiliary_device *auxdev,
277 			      const struct auxiliary_device_id *id)
278 {
279 	struct intel_vsec_device *intel_vsec_dev = auxdev_to_ivdev(auxdev);
280 	struct pmt_crashlog_priv *priv;
281 	size_t size;
282 	int i, ret;
283 
284 	size = struct_size(priv, entry, intel_vsec_dev->num_resources);
285 	priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL);
286 	if (!priv)
287 		return -ENOMEM;
288 
289 	auxiliary_set_drvdata(auxdev, priv);
290 
291 	for (i = 0; i < intel_vsec_dev->num_resources; i++) {
292 		struct intel_pmt_entry *entry = &priv->entry[priv->num_entries].entry;
293 
294 		ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, intel_vsec_dev, i);
295 		if (ret < 0)
296 			goto abort_probe;
297 		if (ret)
298 			continue;
299 
300 		priv->num_entries++;
301 	}
302 
303 	return 0;
304 abort_probe:
305 	pmt_crashlog_remove(auxdev);
306 	return ret;
307 }
308 
309 static const struct auxiliary_device_id pmt_crashlog_id_table[] = {
310 	{ .name = "intel_vsec.crashlog" },
311 	{}
312 };
313 MODULE_DEVICE_TABLE(auxiliary, pmt_crashlog_id_table);
314 
315 static struct auxiliary_driver pmt_crashlog_aux_driver = {
316 	.id_table	= pmt_crashlog_id_table,
317 	.remove		= pmt_crashlog_remove,
318 	.probe		= pmt_crashlog_probe,
319 };
320 
321 static int __init pmt_crashlog_init(void)
322 {
323 	return auxiliary_driver_register(&pmt_crashlog_aux_driver);
324 }
325 
326 static void __exit pmt_crashlog_exit(void)
327 {
328 	auxiliary_driver_unregister(&pmt_crashlog_aux_driver);
329 	xa_destroy(&crashlog_array);
330 }
331 
332 module_init(pmt_crashlog_init);
333 module_exit(pmt_crashlog_exit);
334 
335 MODULE_AUTHOR("Alexander Duyck <alexander.h.duyck@linux.intel.com>");
336 MODULE_DESCRIPTION("Intel PMT Crashlog driver");
337 MODULE_LICENSE("GPL v2");
338 MODULE_IMPORT_NS("INTEL_PMT");
339