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