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