xref: /openbmc/linux/drivers/acpi/apei/erst.c (revision 382c5fec)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2a08f82d0SHuang Ying /*
3a08f82d0SHuang Ying  * APEI Error Record Serialization Table support
4a08f82d0SHuang Ying  *
5a08f82d0SHuang Ying  * ERST is a way provided by APEI to save and retrieve hardware error
658f87ed0SLucas De Marchi  * information to and from a persistent store.
7a08f82d0SHuang Ying  *
8a08f82d0SHuang Ying  * For more information about ERST, please refer to ACPI Specification
9a08f82d0SHuang Ying  * version 4.0, section 17.4.
10a08f82d0SHuang Ying  *
11a08f82d0SHuang Ying  * Copyright 2010 Intel Corp.
12a08f82d0SHuang Ying  *   Author: Huang Ying <ying.huang@intel.com>
13a08f82d0SHuang Ying  */
14a08f82d0SHuang Ying 
15a08f82d0SHuang Ying #include <linux/kernel.h>
16a08f82d0SHuang Ying #include <linux/module.h>
17a08f82d0SHuang Ying #include <linux/init.h>
18a08f82d0SHuang Ying #include <linux/delay.h>
19a08f82d0SHuang Ying #include <linux/io.h>
20a08f82d0SHuang Ying #include <linux/acpi.h>
21a08f82d0SHuang Ying #include <linux/uaccess.h>
22a08f82d0SHuang Ying #include <linux/cper.h>
23a08f82d0SHuang Ying #include <linux/nmi.h>
240a7992c9SThomas Gleixner #include <linux/hardirq.h>
250bb77c46STony Luck #include <linux/pstore.h>
26d6472302SStephen Rothwell #include <linux/vmalloc.h>
271d5cfdb0STetsuo Handa #include <linux/mm.h> /* kvfree() */
28a08f82d0SHuang Ying #include <acpi/apei.h>
29a08f82d0SHuang Ying 
30a08f82d0SHuang Ying #include "apei-internal.h"
31a08f82d0SHuang Ying 
32cb82a2e4SBorislav Petkov #undef pr_fmt
33cb82a2e4SBorislav Petkov #define pr_fmt(fmt) "ERST: " fmt
34a08f82d0SHuang Ying 
35a08f82d0SHuang Ying /* ERST command status */
36a08f82d0SHuang Ying #define ERST_STATUS_SUCCESS			0x0
37a08f82d0SHuang Ying #define ERST_STATUS_NOT_ENOUGH_SPACE		0x1
38a08f82d0SHuang Ying #define ERST_STATUS_HARDWARE_NOT_AVAILABLE	0x2
39a08f82d0SHuang Ying #define ERST_STATUS_FAILED			0x3
40a08f82d0SHuang Ying #define ERST_STATUS_RECORD_STORE_EMPTY		0x4
41a08f82d0SHuang Ying #define ERST_STATUS_RECORD_NOT_FOUND		0x5
42a08f82d0SHuang Ying 
43a08f82d0SHuang Ying #define ERST_TAB_ENTRY(tab)						\
44a08f82d0SHuang Ying 	((struct acpi_whea_header *)((char *)(tab) +			\
45a08f82d0SHuang Ying 				     sizeof(struct acpi_table_erst)))
46a08f82d0SHuang Ying 
47a08f82d0SHuang Ying #define SPIN_UNIT		100			/* 100ns */
48e8a8b252SStefan Weil /* Firmware should respond within 1 milliseconds */
49a08f82d0SHuang Ying #define FIRMWARE_TIMEOUT	(1 * NSEC_PER_MSEC)
50a08f82d0SHuang Ying #define FIRMWARE_MAX_STALL	50			/* 50us */
51a08f82d0SHuang Ying 
52a08f82d0SHuang Ying int erst_disable;
53a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_disable);
54a08f82d0SHuang Ying 
55a08f82d0SHuang Ying static struct acpi_table_erst *erst_tab;
56a08f82d0SHuang Ying 
57935ab850STom Saeger /* ERST Error Log Address Range attributes */
58a08f82d0SHuang Ying #define ERST_RANGE_RESERVED	0x0001
59a08f82d0SHuang Ying #define ERST_RANGE_NVRAM	0x0002
60a08f82d0SHuang Ying #define ERST_RANGE_SLOW		0x0004
61a08f82d0SHuang Ying 
62a08f82d0SHuang Ying /*
63a08f82d0SHuang Ying  * ERST Error Log Address Range, used as buffer for reading/writing
64a08f82d0SHuang Ying  * error records.
65a08f82d0SHuang Ying  */
66a08f82d0SHuang Ying static struct erst_erange {
67a08f82d0SHuang Ying 	u64 base;
68a08f82d0SHuang Ying 	u64 size;
69a08f82d0SHuang Ying 	void __iomem *vaddr;
70a08f82d0SHuang Ying 	u32 attr;
71a08f82d0SHuang Ying } erst_erange;
72a08f82d0SHuang Ying 
73a08f82d0SHuang Ying /*
74a08f82d0SHuang Ying  * Prevent ERST interpreter to run simultaneously, because the
75a08f82d0SHuang Ying  * corresponding firmware implementation may not work properly when
76a08f82d0SHuang Ying  * invoked simultaneously.
77a08f82d0SHuang Ying  *
78a08f82d0SHuang Ying  * It is used to provide exclusive accessing for ERST Error Log
79a08f82d0SHuang Ying  * Address Range too.
80a08f82d0SHuang Ying  */
813b38bb5fSHuang Ying static DEFINE_RAW_SPINLOCK(erst_lock);
82a08f82d0SHuang Ying 
erst_errno(int command_status)83a08f82d0SHuang Ying static inline int erst_errno(int command_status)
84a08f82d0SHuang Ying {
85a08f82d0SHuang Ying 	switch (command_status) {
86a08f82d0SHuang Ying 	case ERST_STATUS_SUCCESS:
87a08f82d0SHuang Ying 		return 0;
88a08f82d0SHuang Ying 	case ERST_STATUS_HARDWARE_NOT_AVAILABLE:
89a08f82d0SHuang Ying 		return -ENODEV;
90a08f82d0SHuang Ying 	case ERST_STATUS_NOT_ENOUGH_SPACE:
91a08f82d0SHuang Ying 		return -ENOSPC;
92a08f82d0SHuang Ying 	case ERST_STATUS_RECORD_STORE_EMPTY:
93a08f82d0SHuang Ying 	case ERST_STATUS_RECORD_NOT_FOUND:
94a08f82d0SHuang Ying 		return -ENOENT;
95a08f82d0SHuang Ying 	default:
96a08f82d0SHuang Ying 		return -EINVAL;
97a08f82d0SHuang Ying 	}
98a08f82d0SHuang Ying }
99a08f82d0SHuang Ying 
erst_timedout(u64 * t,u64 spin_unit)100a08f82d0SHuang Ying static int erst_timedout(u64 *t, u64 spin_unit)
101a08f82d0SHuang Ying {
102a08f82d0SHuang Ying 	if ((s64)*t < spin_unit) {
103cb82a2e4SBorislav Petkov 		pr_warn(FW_WARN "Firmware does not respond in time.\n");
104a08f82d0SHuang Ying 		return 1;
105a08f82d0SHuang Ying 	}
106a08f82d0SHuang Ying 	*t -= spin_unit;
107a08f82d0SHuang Ying 	ndelay(spin_unit);
108a08f82d0SHuang Ying 	touch_nmi_watchdog();
109a08f82d0SHuang Ying 	return 0;
110a08f82d0SHuang Ying }
111a08f82d0SHuang Ying 
erst_exec_load_var1(struct apei_exec_context * ctx,struct acpi_whea_header * entry)112a08f82d0SHuang Ying static int erst_exec_load_var1(struct apei_exec_context *ctx,
113a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
114a08f82d0SHuang Ying {
115a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->var1);
116a08f82d0SHuang Ying }
117a08f82d0SHuang Ying 
erst_exec_load_var2(struct apei_exec_context * ctx,struct acpi_whea_header * entry)118a08f82d0SHuang Ying static int erst_exec_load_var2(struct apei_exec_context *ctx,
119a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
120a08f82d0SHuang Ying {
121a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->var2);
122a08f82d0SHuang Ying }
123a08f82d0SHuang Ying 
erst_exec_store_var1(struct apei_exec_context * ctx,struct acpi_whea_header * entry)124a08f82d0SHuang Ying static int erst_exec_store_var1(struct apei_exec_context *ctx,
125a08f82d0SHuang Ying 				struct acpi_whea_header *entry)
126a08f82d0SHuang Ying {
127a08f82d0SHuang Ying 	return __apei_exec_write_register(entry, ctx->var1);
128a08f82d0SHuang Ying }
129a08f82d0SHuang Ying 
erst_exec_add(struct apei_exec_context * ctx,struct acpi_whea_header * entry)130a08f82d0SHuang Ying static int erst_exec_add(struct apei_exec_context *ctx,
131a08f82d0SHuang Ying 			 struct acpi_whea_header *entry)
132a08f82d0SHuang Ying {
133a08f82d0SHuang Ying 	ctx->var1 += ctx->var2;
134a08f82d0SHuang Ying 	return 0;
135a08f82d0SHuang Ying }
136a08f82d0SHuang Ying 
erst_exec_subtract(struct apei_exec_context * ctx,struct acpi_whea_header * entry)137a08f82d0SHuang Ying static int erst_exec_subtract(struct apei_exec_context *ctx,
138a08f82d0SHuang Ying 			      struct acpi_whea_header *entry)
139a08f82d0SHuang Ying {
140a08f82d0SHuang Ying 	ctx->var1 -= ctx->var2;
141a08f82d0SHuang Ying 	return 0;
142a08f82d0SHuang Ying }
143a08f82d0SHuang Ying 
erst_exec_add_value(struct apei_exec_context * ctx,struct acpi_whea_header * entry)144a08f82d0SHuang Ying static int erst_exec_add_value(struct apei_exec_context *ctx,
145a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
146a08f82d0SHuang Ying {
147a08f82d0SHuang Ying 	int rc;
148a08f82d0SHuang Ying 	u64 val;
149a08f82d0SHuang Ying 
150a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &val);
151a08f82d0SHuang Ying 	if (rc)
152a08f82d0SHuang Ying 		return rc;
153a08f82d0SHuang Ying 	val += ctx->value;
154a08f82d0SHuang Ying 	rc = __apei_exec_write_register(entry, val);
155a08f82d0SHuang Ying 	return rc;
156a08f82d0SHuang Ying }
157a08f82d0SHuang Ying 
erst_exec_subtract_value(struct apei_exec_context * ctx,struct acpi_whea_header * entry)158a08f82d0SHuang Ying static int erst_exec_subtract_value(struct apei_exec_context *ctx,
159a08f82d0SHuang Ying 				    struct acpi_whea_header *entry)
160a08f82d0SHuang Ying {
161a08f82d0SHuang Ying 	int rc;
162a08f82d0SHuang Ying 	u64 val;
163a08f82d0SHuang Ying 
164a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &val);
165a08f82d0SHuang Ying 	if (rc)
166a08f82d0SHuang Ying 		return rc;
167a08f82d0SHuang Ying 	val -= ctx->value;
168a08f82d0SHuang Ying 	rc = __apei_exec_write_register(entry, val);
169a08f82d0SHuang Ying 	return rc;
170a08f82d0SHuang Ying }
171a08f82d0SHuang Ying 
erst_exec_stall(struct apei_exec_context * ctx,struct acpi_whea_header * entry)172a08f82d0SHuang Ying static int erst_exec_stall(struct apei_exec_context *ctx,
173a08f82d0SHuang Ying 			   struct acpi_whea_header *entry)
174a08f82d0SHuang Ying {
175a08f82d0SHuang Ying 	u64 stall_time;
176a08f82d0SHuang Ying 
177a08f82d0SHuang Ying 	if (ctx->value > FIRMWARE_MAX_STALL) {
178a08f82d0SHuang Ying 		if (!in_nmi())
179cb82a2e4SBorislav Petkov 			pr_warn(FW_WARN
180cb82a2e4SBorislav Petkov 			"Too long stall time for stall instruction: 0x%llx.\n",
181a08f82d0SHuang Ying 				   ctx->value);
182a08f82d0SHuang Ying 		stall_time = FIRMWARE_MAX_STALL;
183a08f82d0SHuang Ying 	} else
184a08f82d0SHuang Ying 		stall_time = ctx->value;
185a08f82d0SHuang Ying 	udelay(stall_time);
186a08f82d0SHuang Ying 	return 0;
187a08f82d0SHuang Ying }
188a08f82d0SHuang Ying 
erst_exec_stall_while_true(struct apei_exec_context * ctx,struct acpi_whea_header * entry)189a08f82d0SHuang Ying static int erst_exec_stall_while_true(struct apei_exec_context *ctx,
190a08f82d0SHuang Ying 				      struct acpi_whea_header *entry)
191a08f82d0SHuang Ying {
192a08f82d0SHuang Ying 	int rc;
193a08f82d0SHuang Ying 	u64 val;
194a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
195a08f82d0SHuang Ying 	u64 stall_time;
196a08f82d0SHuang Ying 
197a08f82d0SHuang Ying 	if (ctx->var1 > FIRMWARE_MAX_STALL) {
198a08f82d0SHuang Ying 		if (!in_nmi())
199cb82a2e4SBorislav Petkov 			pr_warn(FW_WARN
200cb82a2e4SBorislav Petkov 		"Too long stall time for stall while true instruction: 0x%llx.\n",
201a08f82d0SHuang Ying 				   ctx->var1);
202a08f82d0SHuang Ying 		stall_time = FIRMWARE_MAX_STALL;
203a08f82d0SHuang Ying 	} else
204a08f82d0SHuang Ying 		stall_time = ctx->var1;
205a08f82d0SHuang Ying 
206a08f82d0SHuang Ying 	for (;;) {
207a08f82d0SHuang Ying 		rc = __apei_exec_read_register(entry, &val);
208a08f82d0SHuang Ying 		if (rc)
209a08f82d0SHuang Ying 			return rc;
210a08f82d0SHuang Ying 		if (val != ctx->value)
211a08f82d0SHuang Ying 			break;
212a08f82d0SHuang Ying 		if (erst_timedout(&timeout, stall_time * NSEC_PER_USEC))
213a08f82d0SHuang Ying 			return -EIO;
214a08f82d0SHuang Ying 	}
215a08f82d0SHuang Ying 	return 0;
216a08f82d0SHuang Ying }
217a08f82d0SHuang Ying 
erst_exec_skip_next_instruction_if_true(struct apei_exec_context * ctx,struct acpi_whea_header * entry)218a08f82d0SHuang Ying static int erst_exec_skip_next_instruction_if_true(
219a08f82d0SHuang Ying 	struct apei_exec_context *ctx,
220a08f82d0SHuang Ying 	struct acpi_whea_header *entry)
221a08f82d0SHuang Ying {
222a08f82d0SHuang Ying 	int rc;
223a08f82d0SHuang Ying 	u64 val;
224a08f82d0SHuang Ying 
225a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &val);
226a08f82d0SHuang Ying 	if (rc)
227a08f82d0SHuang Ying 		return rc;
228a08f82d0SHuang Ying 	if (val == ctx->value) {
229a08f82d0SHuang Ying 		ctx->ip += 2;
230a08f82d0SHuang Ying 		return APEI_EXEC_SET_IP;
231a08f82d0SHuang Ying 	}
232a08f82d0SHuang Ying 
233a08f82d0SHuang Ying 	return 0;
234a08f82d0SHuang Ying }
235a08f82d0SHuang Ying 
erst_exec_goto(struct apei_exec_context * ctx,struct acpi_whea_header * entry)236a08f82d0SHuang Ying static int erst_exec_goto(struct apei_exec_context *ctx,
237a08f82d0SHuang Ying 			  struct acpi_whea_header *entry)
238a08f82d0SHuang Ying {
239a08f82d0SHuang Ying 	ctx->ip = ctx->value;
240a08f82d0SHuang Ying 	return APEI_EXEC_SET_IP;
241a08f82d0SHuang Ying }
242a08f82d0SHuang Ying 
erst_exec_set_src_address_base(struct apei_exec_context * ctx,struct acpi_whea_header * entry)243a08f82d0SHuang Ying static int erst_exec_set_src_address_base(struct apei_exec_context *ctx,
244a08f82d0SHuang Ying 					  struct acpi_whea_header *entry)
245a08f82d0SHuang Ying {
246a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->src_base);
247a08f82d0SHuang Ying }
248a08f82d0SHuang Ying 
erst_exec_set_dst_address_base(struct apei_exec_context * ctx,struct acpi_whea_header * entry)249a08f82d0SHuang Ying static int erst_exec_set_dst_address_base(struct apei_exec_context *ctx,
250a08f82d0SHuang Ying 					  struct acpi_whea_header *entry)
251a08f82d0SHuang Ying {
252a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->dst_base);
253a08f82d0SHuang Ying }
254a08f82d0SHuang Ying 
erst_exec_move_data(struct apei_exec_context * ctx,struct acpi_whea_header * entry)255a08f82d0SHuang Ying static int erst_exec_move_data(struct apei_exec_context *ctx,
256a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
257a08f82d0SHuang Ying {
258a08f82d0SHuang Ying 	int rc;
259a08f82d0SHuang Ying 	u64 offset;
2600bbba38aSHuang Ying 	void *src, *dst;
2610bbba38aSHuang Ying 
2620bbba38aSHuang Ying 	/* ioremap does not work in interrupt context */
2630bbba38aSHuang Ying 	if (in_interrupt()) {
264cb82a2e4SBorislav Petkov 		pr_warn("MOVE_DATA can not be used in interrupt context.\n");
2650bbba38aSHuang Ying 		return -EBUSY;
2660bbba38aSHuang Ying 	}
267a08f82d0SHuang Ying 
268a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &offset);
269a08f82d0SHuang Ying 	if (rc)
270a08f82d0SHuang Ying 		return rc;
2710bbba38aSHuang Ying 
2720bbba38aSHuang Ying 	src = ioremap(ctx->src_base + offset, ctx->var2);
2730bbba38aSHuang Ying 	if (!src)
2740bbba38aSHuang Ying 		return -ENOMEM;
2750bbba38aSHuang Ying 	dst = ioremap(ctx->dst_base + offset, ctx->var2);
27608b326d0SWei Yongjun 	if (!dst) {
27708b326d0SWei Yongjun 		iounmap(src);
2780bbba38aSHuang Ying 		return -ENOMEM;
27908b326d0SWei Yongjun 	}
2800bbba38aSHuang Ying 
2810bbba38aSHuang Ying 	memmove(dst, src, ctx->var2);
2820bbba38aSHuang Ying 
2830bbba38aSHuang Ying 	iounmap(src);
2840bbba38aSHuang Ying 	iounmap(dst);
285a08f82d0SHuang Ying 
286a08f82d0SHuang Ying 	return 0;
287a08f82d0SHuang Ying }
288a08f82d0SHuang Ying 
289a08f82d0SHuang Ying static struct apei_exec_ins_type erst_ins_type[] = {
290a08f82d0SHuang Ying 	[ACPI_ERST_READ_REGISTER] = {
291a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
292a08f82d0SHuang Ying 		.run = apei_exec_read_register,
293a08f82d0SHuang Ying 	},
294a08f82d0SHuang Ying 	[ACPI_ERST_READ_REGISTER_VALUE] = {
295a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
296a08f82d0SHuang Ying 		.run = apei_exec_read_register_value,
297a08f82d0SHuang Ying 	},
298a08f82d0SHuang Ying 	[ACPI_ERST_WRITE_REGISTER] = {
299a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
300a08f82d0SHuang Ying 		.run = apei_exec_write_register,
301a08f82d0SHuang Ying 	},
302a08f82d0SHuang Ying 	[ACPI_ERST_WRITE_REGISTER_VALUE] = {
303a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
304a08f82d0SHuang Ying 		.run = apei_exec_write_register_value,
305a08f82d0SHuang Ying 	},
306a08f82d0SHuang Ying 	[ACPI_ERST_NOOP] = {
307a08f82d0SHuang Ying 		.flags = 0,
308a08f82d0SHuang Ying 		.run = apei_exec_noop,
309a08f82d0SHuang Ying 	},
310a08f82d0SHuang Ying 	[ACPI_ERST_LOAD_VAR1] = {
311a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
312a08f82d0SHuang Ying 		.run = erst_exec_load_var1,
313a08f82d0SHuang Ying 	},
314a08f82d0SHuang Ying 	[ACPI_ERST_LOAD_VAR2] = {
315a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
316a08f82d0SHuang Ying 		.run = erst_exec_load_var2,
317a08f82d0SHuang Ying 	},
318a08f82d0SHuang Ying 	[ACPI_ERST_STORE_VAR1] = {
319a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
320a08f82d0SHuang Ying 		.run = erst_exec_store_var1,
321a08f82d0SHuang Ying 	},
322a08f82d0SHuang Ying 	[ACPI_ERST_ADD] = {
323a08f82d0SHuang Ying 		.flags = 0,
324a08f82d0SHuang Ying 		.run = erst_exec_add,
325a08f82d0SHuang Ying 	},
326a08f82d0SHuang Ying 	[ACPI_ERST_SUBTRACT] = {
327a08f82d0SHuang Ying 		.flags = 0,
328a08f82d0SHuang Ying 		.run = erst_exec_subtract,
329a08f82d0SHuang Ying 	},
330a08f82d0SHuang Ying 	[ACPI_ERST_ADD_VALUE] = {
331a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
332a08f82d0SHuang Ying 		.run = erst_exec_add_value,
333a08f82d0SHuang Ying 	},
334a08f82d0SHuang Ying 	[ACPI_ERST_SUBTRACT_VALUE] = {
335a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
336a08f82d0SHuang Ying 		.run = erst_exec_subtract_value,
337a08f82d0SHuang Ying 	},
338a08f82d0SHuang Ying 	[ACPI_ERST_STALL] = {
339a08f82d0SHuang Ying 		.flags = 0,
340a08f82d0SHuang Ying 		.run = erst_exec_stall,
341a08f82d0SHuang Ying 	},
342a08f82d0SHuang Ying 	[ACPI_ERST_STALL_WHILE_TRUE] = {
343a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
344a08f82d0SHuang Ying 		.run = erst_exec_stall_while_true,
345a08f82d0SHuang Ying 	},
346a08f82d0SHuang Ying 	[ACPI_ERST_SKIP_NEXT_IF_TRUE] = {
347a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
348a08f82d0SHuang Ying 		.run = erst_exec_skip_next_instruction_if_true,
349a08f82d0SHuang Ying 	},
350a08f82d0SHuang Ying 	[ACPI_ERST_GOTO] = {
351a08f82d0SHuang Ying 		.flags = 0,
352a08f82d0SHuang Ying 		.run = erst_exec_goto,
353a08f82d0SHuang Ying 	},
354a08f82d0SHuang Ying 	[ACPI_ERST_SET_SRC_ADDRESS_BASE] = {
355a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
356a08f82d0SHuang Ying 		.run = erst_exec_set_src_address_base,
357a08f82d0SHuang Ying 	},
358a08f82d0SHuang Ying 	[ACPI_ERST_SET_DST_ADDRESS_BASE] = {
359a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
360a08f82d0SHuang Ying 		.run = erst_exec_set_dst_address_base,
361a08f82d0SHuang Ying 	},
362a08f82d0SHuang Ying 	[ACPI_ERST_MOVE_DATA] = {
363a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
364a08f82d0SHuang Ying 		.run = erst_exec_move_data,
365a08f82d0SHuang Ying 	},
366a08f82d0SHuang Ying };
367a08f82d0SHuang Ying 
erst_exec_ctx_init(struct apei_exec_context * ctx)368a08f82d0SHuang Ying static inline void erst_exec_ctx_init(struct apei_exec_context *ctx)
369a08f82d0SHuang Ying {
370a08f82d0SHuang Ying 	apei_exec_ctx_init(ctx, erst_ins_type, ARRAY_SIZE(erst_ins_type),
371a08f82d0SHuang Ying 			   ERST_TAB_ENTRY(erst_tab), erst_tab->entries);
372a08f82d0SHuang Ying }
373a08f82d0SHuang Ying 
erst_get_erange(struct erst_erange * range)374a08f82d0SHuang Ying static int erst_get_erange(struct erst_erange *range)
375a08f82d0SHuang Ying {
376a08f82d0SHuang Ying 	struct apei_exec_context ctx;
377a08f82d0SHuang Ying 	int rc;
378a08f82d0SHuang Ying 
379a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
380a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_RANGE);
381a08f82d0SHuang Ying 	if (rc)
382a08f82d0SHuang Ying 		return rc;
383a08f82d0SHuang Ying 	range->base = apei_exec_ctx_get_output(&ctx);
384a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_LENGTH);
385a08f82d0SHuang Ying 	if (rc)
386a08f82d0SHuang Ying 		return rc;
387a08f82d0SHuang Ying 	range->size = apei_exec_ctx_get_output(&ctx);
388a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_ATTRIBUTES);
389a08f82d0SHuang Ying 	if (rc)
390a08f82d0SHuang Ying 		return rc;
391a08f82d0SHuang Ying 	range->attr = apei_exec_ctx_get_output(&ctx);
392a08f82d0SHuang Ying 
393a08f82d0SHuang Ying 	return 0;
394a08f82d0SHuang Ying }
395a08f82d0SHuang Ying 
__erst_get_record_count(void)396a08f82d0SHuang Ying static ssize_t __erst_get_record_count(void)
397a08f82d0SHuang Ying {
398a08f82d0SHuang Ying 	struct apei_exec_context ctx;
399a08f82d0SHuang Ying 	int rc;
400a08f82d0SHuang Ying 
401a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
402a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_COUNT);
403a08f82d0SHuang Ying 	if (rc)
404a08f82d0SHuang Ying 		return rc;
405a08f82d0SHuang Ying 	return apei_exec_ctx_get_output(&ctx);
406a08f82d0SHuang Ying }
407a08f82d0SHuang Ying 
erst_get_record_count(void)408a08f82d0SHuang Ying ssize_t erst_get_record_count(void)
409a08f82d0SHuang Ying {
410a08f82d0SHuang Ying 	ssize_t count;
411a08f82d0SHuang Ying 	unsigned long flags;
412a08f82d0SHuang Ying 
413a08f82d0SHuang Ying 	if (erst_disable)
414a08f82d0SHuang Ying 		return -ENODEV;
415a08f82d0SHuang Ying 
4163b38bb5fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
417a08f82d0SHuang Ying 	count = __erst_get_record_count();
4183b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
419a08f82d0SHuang Ying 
420a08f82d0SHuang Ying 	return count;
421a08f82d0SHuang Ying }
422a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_count);
423a08f82d0SHuang Ying 
424885b976fSHuang Ying #define ERST_RECORD_ID_CACHE_SIZE_MIN	16
425885b976fSHuang Ying #define ERST_RECORD_ID_CACHE_SIZE_MAX	1024
426885b976fSHuang Ying 
427885b976fSHuang Ying struct erst_record_id_cache {
428885b976fSHuang Ying 	struct mutex lock;
429885b976fSHuang Ying 	u64 *entries;
430885b976fSHuang Ying 	int len;
431885b976fSHuang Ying 	int size;
432885b976fSHuang Ying 	int refcount;
433885b976fSHuang Ying };
434885b976fSHuang Ying 
435885b976fSHuang Ying static struct erst_record_id_cache erst_record_id_cache = {
436885b976fSHuang Ying 	.lock = __MUTEX_INITIALIZER(erst_record_id_cache.lock),
437885b976fSHuang Ying 	.refcount = 0,
438885b976fSHuang Ying };
439885b976fSHuang Ying 
__erst_get_next_record_id(u64 * record_id)440a08f82d0SHuang Ying static int __erst_get_next_record_id(u64 *record_id)
441a08f82d0SHuang Ying {
442a08f82d0SHuang Ying 	struct apei_exec_context ctx;
443a08f82d0SHuang Ying 	int rc;
444a08f82d0SHuang Ying 
445a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
446a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_ID);
447a08f82d0SHuang Ying 	if (rc)
448a08f82d0SHuang Ying 		return rc;
449a08f82d0SHuang Ying 	*record_id = apei_exec_ctx_get_output(&ctx);
450a08f82d0SHuang Ying 
451a08f82d0SHuang Ying 	return 0;
452a08f82d0SHuang Ying }
453a08f82d0SHuang Ying 
erst_get_record_id_begin(int * pos)454885b976fSHuang Ying int erst_get_record_id_begin(int *pos)
455885b976fSHuang Ying {
456885b976fSHuang Ying 	int rc;
457885b976fSHuang Ying 
458885b976fSHuang Ying 	if (erst_disable)
459885b976fSHuang Ying 		return -ENODEV;
460885b976fSHuang Ying 
461885b976fSHuang Ying 	rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
462885b976fSHuang Ying 	if (rc)
463885b976fSHuang Ying 		return rc;
464885b976fSHuang Ying 	erst_record_id_cache.refcount++;
465885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
466885b976fSHuang Ying 
467885b976fSHuang Ying 	*pos = 0;
468885b976fSHuang Ying 
469885b976fSHuang Ying 	return 0;
470885b976fSHuang Ying }
471885b976fSHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_id_begin);
472885b976fSHuang Ying 
473885b976fSHuang Ying /* erst_record_id_cache.lock must be held by caller */
__erst_record_id_cache_add_one(void)474885b976fSHuang Ying static int __erst_record_id_cache_add_one(void)
475885b976fSHuang Ying {
476885b976fSHuang Ying 	u64 id, prev_id, first_id;
477885b976fSHuang Ying 	int i, rc;
478885b976fSHuang Ying 	u64 *entries;
479885b976fSHuang Ying 	unsigned long flags;
480885b976fSHuang Ying 
481885b976fSHuang Ying 	id = prev_id = first_id = APEI_ERST_INVALID_RECORD_ID;
482885b976fSHuang Ying retry:
483885b976fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
484885b976fSHuang Ying 	rc = __erst_get_next_record_id(&id);
485885b976fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
486885b976fSHuang Ying 	if (rc == -ENOENT)
487885b976fSHuang Ying 		return 0;
488885b976fSHuang Ying 	if (rc)
489885b976fSHuang Ying 		return rc;
490885b976fSHuang Ying 	if (id == APEI_ERST_INVALID_RECORD_ID)
491885b976fSHuang Ying 		return 0;
492885b976fSHuang Ying 	/* can not skip current ID, or loop back to first ID */
493885b976fSHuang Ying 	if (id == prev_id || id == first_id)
494885b976fSHuang Ying 		return 0;
495885b976fSHuang Ying 	if (first_id == APEI_ERST_INVALID_RECORD_ID)
496885b976fSHuang Ying 		first_id = id;
497885b976fSHuang Ying 	prev_id = id;
498885b976fSHuang Ying 
499885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
500885b976fSHuang Ying 	for (i = 0; i < erst_record_id_cache.len; i++) {
501885b976fSHuang Ying 		if (entries[i] == id)
502885b976fSHuang Ying 			break;
503885b976fSHuang Ying 	}
504885b976fSHuang Ying 	/* record id already in cache, try next */
505885b976fSHuang Ying 	if (i < erst_record_id_cache.len)
506885b976fSHuang Ying 		goto retry;
507885b976fSHuang Ying 	if (erst_record_id_cache.len >= erst_record_id_cache.size) {
508752ade68SMichal Hocko 		int new_size;
509885b976fSHuang Ying 		u64 *new_entries;
510885b976fSHuang Ying 
511885b976fSHuang Ying 		new_size = erst_record_id_cache.size * 2;
512885b976fSHuang Ying 		new_size = clamp_val(new_size, ERST_RECORD_ID_CACHE_SIZE_MIN,
513885b976fSHuang Ying 				     ERST_RECORD_ID_CACHE_SIZE_MAX);
514885b976fSHuang Ying 		if (new_size <= erst_record_id_cache.size) {
515885b976fSHuang Ying 			if (printk_ratelimit())
516cb82a2e4SBorislav Petkov 				pr_warn(FW_WARN "too many record IDs!\n");
517885b976fSHuang Ying 			return 0;
518885b976fSHuang Ying 		}
519344476e1SKees Cook 		new_entries = kvmalloc_array(new_size, sizeof(entries[0]),
520344476e1SKees Cook 					     GFP_KERNEL);
521885b976fSHuang Ying 		if (!new_entries)
522885b976fSHuang Ying 			return -ENOMEM;
523885b976fSHuang Ying 		memcpy(new_entries, entries,
524885b976fSHuang Ying 		       erst_record_id_cache.len * sizeof(entries[0]));
5251d5cfdb0STetsuo Handa 		kvfree(entries);
526885b976fSHuang Ying 		erst_record_id_cache.entries = entries = new_entries;
527885b976fSHuang Ying 		erst_record_id_cache.size = new_size;
528885b976fSHuang Ying 	}
529885b976fSHuang Ying 	entries[i] = id;
530885b976fSHuang Ying 	erst_record_id_cache.len++;
531885b976fSHuang Ying 
532885b976fSHuang Ying 	return 1;
533885b976fSHuang Ying }
534885b976fSHuang Ying 
535a08f82d0SHuang Ying /*
536a08f82d0SHuang Ying  * Get the record ID of an existing error record on the persistent
537a08f82d0SHuang Ying  * storage. If there is no error record on the persistent storage, the
538a08f82d0SHuang Ying  * returned record_id is APEI_ERST_INVALID_RECORD_ID.
539a08f82d0SHuang Ying  */
erst_get_record_id_next(int * pos,u64 * record_id)540885b976fSHuang Ying int erst_get_record_id_next(int *pos, u64 *record_id)
541a08f82d0SHuang Ying {
542885b976fSHuang Ying 	int rc = 0;
543885b976fSHuang Ying 	u64 *entries;
544a08f82d0SHuang Ying 
545a08f82d0SHuang Ying 	if (erst_disable)
546a08f82d0SHuang Ying 		return -ENODEV;
547a08f82d0SHuang Ying 
548885b976fSHuang Ying 	/* must be enclosed by erst_get_record_id_begin/end */
549885b976fSHuang Ying 	BUG_ON(!erst_record_id_cache.refcount);
550885b976fSHuang Ying 	BUG_ON(*pos < 0 || *pos > erst_record_id_cache.len);
551885b976fSHuang Ying 
552885b976fSHuang Ying 	mutex_lock(&erst_record_id_cache.lock);
553885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
554885b976fSHuang Ying 	for (; *pos < erst_record_id_cache.len; (*pos)++)
555885b976fSHuang Ying 		if (entries[*pos] != APEI_ERST_INVALID_RECORD_ID)
556885b976fSHuang Ying 			break;
557885b976fSHuang Ying 	/* found next record id in cache */
558885b976fSHuang Ying 	if (*pos < erst_record_id_cache.len) {
559885b976fSHuang Ying 		*record_id = entries[*pos];
560885b976fSHuang Ying 		(*pos)++;
561885b976fSHuang Ying 		goto out_unlock;
562885b976fSHuang Ying 	}
563885b976fSHuang Ying 
564885b976fSHuang Ying 	/* Try to add one more record ID to cache */
565885b976fSHuang Ying 	rc = __erst_record_id_cache_add_one();
566885b976fSHuang Ying 	if (rc < 0)
567885b976fSHuang Ying 		goto out_unlock;
568885b976fSHuang Ying 	/* successfully add one new ID */
569885b976fSHuang Ying 	if (rc == 1) {
570885b976fSHuang Ying 		*record_id = erst_record_id_cache.entries[*pos];
571885b976fSHuang Ying 		(*pos)++;
572885b976fSHuang Ying 		rc = 0;
573885b976fSHuang Ying 	} else {
574885b976fSHuang Ying 		*pos = -1;
575885b976fSHuang Ying 		*record_id = APEI_ERST_INVALID_RECORD_ID;
576885b976fSHuang Ying 	}
577885b976fSHuang Ying out_unlock:
578885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
579a08f82d0SHuang Ying 
580a08f82d0SHuang Ying 	return rc;
581a08f82d0SHuang Ying }
582885b976fSHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_id_next);
583885b976fSHuang Ying 
584885b976fSHuang Ying /* erst_record_id_cache.lock must be held by caller */
__erst_record_id_cache_compact(void)585885b976fSHuang Ying static void __erst_record_id_cache_compact(void)
586885b976fSHuang Ying {
587885b976fSHuang Ying 	int i, wpos = 0;
588885b976fSHuang Ying 	u64 *entries;
589885b976fSHuang Ying 
590885b976fSHuang Ying 	if (erst_record_id_cache.refcount)
591885b976fSHuang Ying 		return;
592885b976fSHuang Ying 
593885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
594885b976fSHuang Ying 	for (i = 0; i < erst_record_id_cache.len; i++) {
595885b976fSHuang Ying 		if (entries[i] == APEI_ERST_INVALID_RECORD_ID)
596885b976fSHuang Ying 			continue;
597885b976fSHuang Ying 		if (wpos != i)
598d3ab3edcSChen, Gong 			entries[wpos] = entries[i];
599885b976fSHuang Ying 		wpos++;
600885b976fSHuang Ying 	}
601885b976fSHuang Ying 	erst_record_id_cache.len = wpos;
602885b976fSHuang Ying }
603885b976fSHuang Ying 
erst_get_record_id_end(void)604885b976fSHuang Ying void erst_get_record_id_end(void)
605885b976fSHuang Ying {
606885b976fSHuang Ying 	/*
607885b976fSHuang Ying 	 * erst_disable != 0 should be detected by invoker via the
608885b976fSHuang Ying 	 * return value of erst_get_record_id_begin/next, so this
609885b976fSHuang Ying 	 * function should not be called for erst_disable != 0.
610885b976fSHuang Ying 	 */
611885b976fSHuang Ying 	BUG_ON(erst_disable);
612885b976fSHuang Ying 
613885b976fSHuang Ying 	mutex_lock(&erst_record_id_cache.lock);
614885b976fSHuang Ying 	erst_record_id_cache.refcount--;
615885b976fSHuang Ying 	BUG_ON(erst_record_id_cache.refcount < 0);
616885b976fSHuang Ying 	__erst_record_id_cache_compact();
617885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
618885b976fSHuang Ying }
619885b976fSHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_id_end);
620a08f82d0SHuang Ying 
__erst_write_to_storage(u64 offset)621a08f82d0SHuang Ying static int __erst_write_to_storage(u64 offset)
622a08f82d0SHuang Ying {
623a08f82d0SHuang Ying 	struct apei_exec_context ctx;
624a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
625a08f82d0SHuang Ying 	u64 val;
626a08f82d0SHuang Ying 	int rc;
627a08f82d0SHuang Ying 
628a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
629392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_WRITE);
630a08f82d0SHuang Ying 	if (rc)
631a08f82d0SHuang Ying 		return rc;
632a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, offset);
633a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET);
634a08f82d0SHuang Ying 	if (rc)
635a08f82d0SHuang Ying 		return rc;
636a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
637a08f82d0SHuang Ying 	if (rc)
638a08f82d0SHuang Ying 		return rc;
639a08f82d0SHuang Ying 	for (;;) {
640a08f82d0SHuang Ying 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
641a08f82d0SHuang Ying 		if (rc)
642a08f82d0SHuang Ying 			return rc;
643a08f82d0SHuang Ying 		val = apei_exec_ctx_get_output(&ctx);
644a08f82d0SHuang Ying 		if (!val)
645a08f82d0SHuang Ying 			break;
646a08f82d0SHuang Ying 		if (erst_timedout(&timeout, SPIN_UNIT))
647a08f82d0SHuang Ying 			return -EIO;
648a08f82d0SHuang Ying 	}
649a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
650a08f82d0SHuang Ying 	if (rc)
651a08f82d0SHuang Ying 		return rc;
652a08f82d0SHuang Ying 	val = apei_exec_ctx_get_output(&ctx);
653392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
654a08f82d0SHuang Ying 	if (rc)
655a08f82d0SHuang Ying 		return rc;
656a08f82d0SHuang Ying 
657a08f82d0SHuang Ying 	return erst_errno(val);
658a08f82d0SHuang Ying }
659a08f82d0SHuang Ying 
__erst_read_from_storage(u64 record_id,u64 offset)660a08f82d0SHuang Ying static int __erst_read_from_storage(u64 record_id, u64 offset)
661a08f82d0SHuang Ying {
662a08f82d0SHuang Ying 	struct apei_exec_context ctx;
663a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
664a08f82d0SHuang Ying 	u64 val;
665a08f82d0SHuang Ying 	int rc;
666a08f82d0SHuang Ying 
667a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
668392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_READ);
669a08f82d0SHuang Ying 	if (rc)
670a08f82d0SHuang Ying 		return rc;
671a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, offset);
672a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET);
673a08f82d0SHuang Ying 	if (rc)
674a08f82d0SHuang Ying 		return rc;
675a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, record_id);
676a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID);
677a08f82d0SHuang Ying 	if (rc)
678a08f82d0SHuang Ying 		return rc;
679a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
680a08f82d0SHuang Ying 	if (rc)
681a08f82d0SHuang Ying 		return rc;
682a08f82d0SHuang Ying 	for (;;) {
683a08f82d0SHuang Ying 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
684a08f82d0SHuang Ying 		if (rc)
685a08f82d0SHuang Ying 			return rc;
686a08f82d0SHuang Ying 		val = apei_exec_ctx_get_output(&ctx);
687a08f82d0SHuang Ying 		if (!val)
688a08f82d0SHuang Ying 			break;
689a08f82d0SHuang Ying 		if (erst_timedout(&timeout, SPIN_UNIT))
690a08f82d0SHuang Ying 			return -EIO;
6914ffa84b8SYang Li 	}
692a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
693a08f82d0SHuang Ying 	if (rc)
694a08f82d0SHuang Ying 		return rc;
695a08f82d0SHuang Ying 	val = apei_exec_ctx_get_output(&ctx);
696392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
697a08f82d0SHuang Ying 	if (rc)
698a08f82d0SHuang Ying 		return rc;
699a08f82d0SHuang Ying 
700a08f82d0SHuang Ying 	return erst_errno(val);
701a08f82d0SHuang Ying }
702a08f82d0SHuang Ying 
__erst_clear_from_storage(u64 record_id)703a08f82d0SHuang Ying static int __erst_clear_from_storage(u64 record_id)
704a08f82d0SHuang Ying {
705a08f82d0SHuang Ying 	struct apei_exec_context ctx;
706a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
707a08f82d0SHuang Ying 	u64 val;
708a08f82d0SHuang Ying 	int rc;
709a08f82d0SHuang Ying 
710a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
711392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_CLEAR);
712a08f82d0SHuang Ying 	if (rc)
713a08f82d0SHuang Ying 		return rc;
714a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, record_id);
715a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID);
716a08f82d0SHuang Ying 	if (rc)
717a08f82d0SHuang Ying 		return rc;
718a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
719a08f82d0SHuang Ying 	if (rc)
720a08f82d0SHuang Ying 		return rc;
721a08f82d0SHuang Ying 	for (;;) {
722a08f82d0SHuang Ying 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
723a08f82d0SHuang Ying 		if (rc)
724a08f82d0SHuang Ying 			return rc;
725a08f82d0SHuang Ying 		val = apei_exec_ctx_get_output(&ctx);
726a08f82d0SHuang Ying 		if (!val)
727a08f82d0SHuang Ying 			break;
728a08f82d0SHuang Ying 		if (erst_timedout(&timeout, SPIN_UNIT))
729a08f82d0SHuang Ying 			return -EIO;
730a08f82d0SHuang Ying 	}
731a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
732a08f82d0SHuang Ying 	if (rc)
733a08f82d0SHuang Ying 		return rc;
734a08f82d0SHuang Ying 	val = apei_exec_ctx_get_output(&ctx);
735392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
736a08f82d0SHuang Ying 	if (rc)
737a08f82d0SHuang Ying 		return rc;
738a08f82d0SHuang Ying 
739a08f82d0SHuang Ying 	return erst_errno(val);
740a08f82d0SHuang Ying }
741a08f82d0SHuang Ying 
742a08f82d0SHuang Ying /* NVRAM ERST Error Log Address Range is not supported yet */
pr_unimpl_nvram(void)743a08f82d0SHuang Ying static void pr_unimpl_nvram(void)
744a08f82d0SHuang Ying {
745a08f82d0SHuang Ying 	if (printk_ratelimit())
746cb82a2e4SBorislav Petkov 		pr_warn("NVRAM ERST Log Address Range not implemented yet.\n");
747a08f82d0SHuang Ying }
748a08f82d0SHuang Ying 
__erst_write_to_nvram(const struct cper_record_header * record)749a08f82d0SHuang Ying static int __erst_write_to_nvram(const struct cper_record_header *record)
750a08f82d0SHuang Ying {
751a08f82d0SHuang Ying 	/* do not print message, because printk is not safe for NMI */
752a08f82d0SHuang Ying 	return -ENOSYS;
753a08f82d0SHuang Ying }
754a08f82d0SHuang Ying 
__erst_read_to_erange_from_nvram(u64 record_id,u64 * offset)755a08f82d0SHuang Ying static int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset)
756a08f82d0SHuang Ying {
757a08f82d0SHuang Ying 	pr_unimpl_nvram();
758a08f82d0SHuang Ying 	return -ENOSYS;
759a08f82d0SHuang Ying }
760a08f82d0SHuang Ying 
__erst_clear_from_nvram(u64 record_id)761a08f82d0SHuang Ying static int __erst_clear_from_nvram(u64 record_id)
762a08f82d0SHuang Ying {
763a08f82d0SHuang Ying 	pr_unimpl_nvram();
764a08f82d0SHuang Ying 	return -ENOSYS;
765a08f82d0SHuang Ying }
766a08f82d0SHuang Ying 
erst_write(const struct cper_record_header * record)767a08f82d0SHuang Ying int erst_write(const struct cper_record_header *record)
768a08f82d0SHuang Ying {
769a08f82d0SHuang Ying 	int rc;
770a08f82d0SHuang Ying 	unsigned long flags;
771a08f82d0SHuang Ying 	struct cper_record_header *rcd_erange;
772a08f82d0SHuang Ying 
773a08f82d0SHuang Ying 	if (erst_disable)
774a08f82d0SHuang Ying 		return -ENODEV;
775a08f82d0SHuang Ying 
776a08f82d0SHuang Ying 	if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE))
777a08f82d0SHuang Ying 		return -EINVAL;
778a08f82d0SHuang Ying 
779a08f82d0SHuang Ying 	if (erst_erange.attr & ERST_RANGE_NVRAM) {
7803b38bb5fSHuang Ying 		if (!raw_spin_trylock_irqsave(&erst_lock, flags))
781a08f82d0SHuang Ying 			return -EBUSY;
782a08f82d0SHuang Ying 		rc = __erst_write_to_nvram(record);
7833b38bb5fSHuang Ying 		raw_spin_unlock_irqrestore(&erst_lock, flags);
784a08f82d0SHuang Ying 		return rc;
785a08f82d0SHuang Ying 	}
786a08f82d0SHuang Ying 
787a08f82d0SHuang Ying 	if (record->record_length > erst_erange.size)
788a08f82d0SHuang Ying 		return -EINVAL;
789a08f82d0SHuang Ying 
7903b38bb5fSHuang Ying 	if (!raw_spin_trylock_irqsave(&erst_lock, flags))
791a08f82d0SHuang Ying 		return -EBUSY;
792a08f82d0SHuang Ying 	memcpy(erst_erange.vaddr, record, record->record_length);
793a08f82d0SHuang Ying 	rcd_erange = erst_erange.vaddr;
794a08f82d0SHuang Ying 	/* signature for serialization system */
795a08f82d0SHuang Ying 	memcpy(&rcd_erange->persistence_information, "ER", 2);
796a08f82d0SHuang Ying 
797a08f82d0SHuang Ying 	rc = __erst_write_to_storage(0);
7983b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
799a08f82d0SHuang Ying 
800a08f82d0SHuang Ying 	return rc;
801a08f82d0SHuang Ying }
802a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_write);
803a08f82d0SHuang Ying 
__erst_read_to_erange(u64 record_id,u64 * offset)804a08f82d0SHuang Ying static int __erst_read_to_erange(u64 record_id, u64 *offset)
805a08f82d0SHuang Ying {
806a08f82d0SHuang Ying 	int rc;
807a08f82d0SHuang Ying 
808a08f82d0SHuang Ying 	if (erst_erange.attr & ERST_RANGE_NVRAM)
809a08f82d0SHuang Ying 		return __erst_read_to_erange_from_nvram(
810a08f82d0SHuang Ying 			record_id, offset);
811a08f82d0SHuang Ying 
812a08f82d0SHuang Ying 	rc = __erst_read_from_storage(record_id, 0);
813a08f82d0SHuang Ying 	if (rc)
814a08f82d0SHuang Ying 		return rc;
815a08f82d0SHuang Ying 	*offset = 0;
816a08f82d0SHuang Ying 
817a08f82d0SHuang Ying 	return 0;
818a08f82d0SHuang Ying }
819a08f82d0SHuang Ying 
__erst_read(u64 record_id,struct cper_record_header * record,size_t buflen)820a08f82d0SHuang Ying static ssize_t __erst_read(u64 record_id, struct cper_record_header *record,
821a08f82d0SHuang Ying 			   size_t buflen)
822a08f82d0SHuang Ying {
823a08f82d0SHuang Ying 	int rc;
824a08f82d0SHuang Ying 	u64 offset, len = 0;
825a08f82d0SHuang Ying 	struct cper_record_header *rcd_tmp;
826a08f82d0SHuang Ying 
827a08f82d0SHuang Ying 	rc = __erst_read_to_erange(record_id, &offset);
828a08f82d0SHuang Ying 	if (rc)
829a08f82d0SHuang Ying 		return rc;
830a08f82d0SHuang Ying 	rcd_tmp = erst_erange.vaddr + offset;
831a08f82d0SHuang Ying 	len = rcd_tmp->record_length;
832a08f82d0SHuang Ying 	if (len <= buflen)
833a08f82d0SHuang Ying 		memcpy(record, rcd_tmp, len);
834a08f82d0SHuang Ying 
835a08f82d0SHuang Ying 	return len;
836a08f82d0SHuang Ying }
837a08f82d0SHuang Ying 
838a08f82d0SHuang Ying /*
839a08f82d0SHuang Ying  * If return value > buflen, the buffer size is not big enough,
840a08f82d0SHuang Ying  * else if return value < 0, something goes wrong,
841a08f82d0SHuang Ying  * else everything is OK, and return value is record length
842a08f82d0SHuang Ying  */
erst_read(u64 record_id,struct cper_record_header * record,size_t buflen)843a08f82d0SHuang Ying ssize_t erst_read(u64 record_id, struct cper_record_header *record,
844a08f82d0SHuang Ying 		  size_t buflen)
845a08f82d0SHuang Ying {
846a08f82d0SHuang Ying 	ssize_t len;
847a08f82d0SHuang Ying 	unsigned long flags;
848a08f82d0SHuang Ying 
849a08f82d0SHuang Ying 	if (erst_disable)
850a08f82d0SHuang Ying 		return -ENODEV;
851a08f82d0SHuang Ying 
8523b38bb5fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
853a08f82d0SHuang Ying 	len = __erst_read(record_id, record, buflen);
8543b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
855a08f82d0SHuang Ying 	return len;
856a08f82d0SHuang Ying }
857a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_read);
858a08f82d0SHuang Ying 
erst_clear_cache(u64 record_id)859a0909315SLiu Xinpeng static void erst_clear_cache(u64 record_id)
860a0909315SLiu Xinpeng {
861a0909315SLiu Xinpeng 	int i;
862a0909315SLiu Xinpeng 	u64 *entries;
863a0909315SLiu Xinpeng 
864a0909315SLiu Xinpeng 	mutex_lock(&erst_record_id_cache.lock);
865a0909315SLiu Xinpeng 
866a0909315SLiu Xinpeng 	entries = erst_record_id_cache.entries;
867a0909315SLiu Xinpeng 	for (i = 0; i < erst_record_id_cache.len; i++) {
868a0909315SLiu Xinpeng 		if (entries[i] == record_id)
869a0909315SLiu Xinpeng 			entries[i] = APEI_ERST_INVALID_RECORD_ID;
870a0909315SLiu Xinpeng 	}
871a0909315SLiu Xinpeng 	__erst_record_id_cache_compact();
872a0909315SLiu Xinpeng 
873a0909315SLiu Xinpeng 	mutex_unlock(&erst_record_id_cache.lock);
874a0909315SLiu Xinpeng }
875a0909315SLiu Xinpeng 
erst_read_record(u64 record_id,struct cper_record_header * record,size_t buflen,size_t recordlen,const guid_t * creatorid)876a0909315SLiu Xinpeng ssize_t erst_read_record(u64 record_id, struct cper_record_header *record,
877a0909315SLiu Xinpeng 		size_t buflen, size_t recordlen, const guid_t *creatorid)
878a0909315SLiu Xinpeng {
879a0909315SLiu Xinpeng 	ssize_t len;
880a0909315SLiu Xinpeng 
881a0909315SLiu Xinpeng 	/*
882a0909315SLiu Xinpeng 	 * if creatorid is NULL, read any record for erst-dbg module
883a0909315SLiu Xinpeng 	 */
884a0909315SLiu Xinpeng 	if (creatorid == NULL) {
885a0909315SLiu Xinpeng 		len = erst_read(record_id, record, buflen);
886a0909315SLiu Xinpeng 		if (len == -ENOENT)
887a0909315SLiu Xinpeng 			erst_clear_cache(record_id);
888a0909315SLiu Xinpeng 
889a0909315SLiu Xinpeng 		return len;
890a0909315SLiu Xinpeng 	}
891a0909315SLiu Xinpeng 
892a0909315SLiu Xinpeng 	len = erst_read(record_id, record, buflen);
893a0909315SLiu Xinpeng 	/*
894a0909315SLiu Xinpeng 	 * if erst_read return value is -ENOENT skip to next record_id,
895a0909315SLiu Xinpeng 	 * and clear the record_id cache.
896a0909315SLiu Xinpeng 	 */
897a0909315SLiu Xinpeng 	if (len == -ENOENT) {
898a0909315SLiu Xinpeng 		erst_clear_cache(record_id);
899a0909315SLiu Xinpeng 		goto out;
900a0909315SLiu Xinpeng 	}
901a0909315SLiu Xinpeng 
902a0909315SLiu Xinpeng 	if (len < 0)
903a0909315SLiu Xinpeng 		goto out;
904a0909315SLiu Xinpeng 
905a0909315SLiu Xinpeng 	/*
906a0909315SLiu Xinpeng 	 * if erst_read return value is less than record head length,
907a0909315SLiu Xinpeng 	 * consider it as -EIO, and clear the record_id cache.
908a0909315SLiu Xinpeng 	 */
909a0909315SLiu Xinpeng 	if (len < recordlen) {
910a0909315SLiu Xinpeng 		len = -EIO;
911a0909315SLiu Xinpeng 		erst_clear_cache(record_id);
912a0909315SLiu Xinpeng 		goto out;
913a0909315SLiu Xinpeng 	}
914a0909315SLiu Xinpeng 
915a0909315SLiu Xinpeng 	/*
916a0909315SLiu Xinpeng 	 * if creatorid is not wanted, consider it as not found,
917a0909315SLiu Xinpeng 	 * for skipping to next record_id.
918a0909315SLiu Xinpeng 	 */
919a0909315SLiu Xinpeng 	if (!guid_equal(&record->creator_id, creatorid))
920a0909315SLiu Xinpeng 		len = -ENOENT;
921a0909315SLiu Xinpeng 
922a0909315SLiu Xinpeng out:
923a0909315SLiu Xinpeng 	return len;
924a0909315SLiu Xinpeng }
925a0909315SLiu Xinpeng EXPORT_SYMBOL_GPL(erst_read_record);
926a0909315SLiu Xinpeng 
erst_clear(u64 record_id)927a08f82d0SHuang Ying int erst_clear(u64 record_id)
928a08f82d0SHuang Ying {
929885b976fSHuang Ying 	int rc, i;
930a08f82d0SHuang Ying 	unsigned long flags;
931885b976fSHuang Ying 	u64 *entries;
932a08f82d0SHuang Ying 
933a08f82d0SHuang Ying 	if (erst_disable)
934a08f82d0SHuang Ying 		return -ENODEV;
935a08f82d0SHuang Ying 
936885b976fSHuang Ying 	rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
937885b976fSHuang Ying 	if (rc)
938885b976fSHuang Ying 		return rc;
9393b38bb5fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
940a08f82d0SHuang Ying 	if (erst_erange.attr & ERST_RANGE_NVRAM)
941a08f82d0SHuang Ying 		rc = __erst_clear_from_nvram(record_id);
942a08f82d0SHuang Ying 	else
943a08f82d0SHuang Ying 		rc = __erst_clear_from_storage(record_id);
9443b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
945885b976fSHuang Ying 	if (rc)
946885b976fSHuang Ying 		goto out;
947885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
948885b976fSHuang Ying 	for (i = 0; i < erst_record_id_cache.len; i++) {
949885b976fSHuang Ying 		if (entries[i] == record_id)
950885b976fSHuang Ying 			entries[i] = APEI_ERST_INVALID_RECORD_ID;
951885b976fSHuang Ying 	}
952885b976fSHuang Ying 	__erst_record_id_cache_compact();
953885b976fSHuang Ying out:
954885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
955a08f82d0SHuang Ying 	return rc;
956a08f82d0SHuang Ying }
957a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_clear);
958a08f82d0SHuang Ying 
setup_erst_disable(char * str)959a08f82d0SHuang Ying static int __init setup_erst_disable(char *str)
960a08f82d0SHuang Ying {
961a08f82d0SHuang Ying 	erst_disable = 1;
962f3303ff6SRandy Dunlap 	return 1;
963a08f82d0SHuang Ying }
964a08f82d0SHuang Ying 
965a08f82d0SHuang Ying __setup("erst_disable", setup_erst_disable);
966a08f82d0SHuang Ying 
erst_check_table(struct acpi_table_erst * erst_tab)967a08f82d0SHuang Ying static int erst_check_table(struct acpi_table_erst *erst_tab)
968a08f82d0SHuang Ying {
9693a78f965SHuang Ying 	if ((erst_tab->header_length !=
9703a78f965SHuang Ying 	     (sizeof(struct acpi_table_erst) - sizeof(erst_tab->header)))
9717ed28f2eSJiang Liu 	    && (erst_tab->header_length != sizeof(struct acpi_table_erst)))
972a08f82d0SHuang Ying 		return -EINVAL;
973a08f82d0SHuang Ying 	if (erst_tab->header.length < sizeof(struct acpi_table_erst))
974a08f82d0SHuang Ying 		return -EINVAL;
975a08f82d0SHuang Ying 	if (erst_tab->entries !=
976a08f82d0SHuang Ying 	    (erst_tab->header.length - sizeof(struct acpi_table_erst)) /
977a08f82d0SHuang Ying 	    sizeof(struct acpi_erst_entry))
978a08f82d0SHuang Ying 		return -EINVAL;
979a08f82d0SHuang Ying 
980a08f82d0SHuang Ying 	return 0;
981a08f82d0SHuang Ying }
982a08f82d0SHuang Ying 
98306cf91b4SChen Gong static int erst_open_pstore(struct pstore_info *psi);
98406cf91b4SChen Gong static int erst_close_pstore(struct pstore_info *psi);
985125cc42bSKees Cook static ssize_t erst_reader(struct pstore_record *record);
98676cc9580SKees Cook static int erst_writer(struct pstore_record *record);
987a61072aaSKees Cook static int erst_clearer(struct pstore_record *record);
9880bb77c46STony Luck 
9890bb77c46STony Luck static struct pstore_info erst_info = {
9900bb77c46STony Luck 	.owner		= THIS_MODULE,
9910bb77c46STony Luck 	.name		= "erst",
992c950fd6fSNamhyung Kim 	.flags		= PSTORE_FLAGS_DMESG,
99306cf91b4SChen Gong 	.open		= erst_open_pstore,
99406cf91b4SChen Gong 	.close		= erst_close_pstore,
9950bb77c46STony Luck 	.read		= erst_reader,
9960bb77c46STony Luck 	.write		= erst_writer,
997638c1fd3SMatthew Garrett 	.erase		= erst_clearer
9980bb77c46STony Luck };
9990bb77c46STony Luck 
10000bb77c46STony Luck #define CPER_CREATOR_PSTORE						\
10016f9c104bSAndy Shevchenko 	GUID_INIT(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c,	\
10020bb77c46STony Luck 		  0x64, 0x90, 0xb8, 0x9d)
10030bb77c46STony Luck #define CPER_SECTION_TYPE_DMESG						\
10046f9c104bSAndy Shevchenko 	GUID_INIT(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54,	\
10050bb77c46STony Luck 		  0x94, 0x19, 0xeb, 0x12)
1006901037baSAruna Balakrishnaiah #define CPER_SECTION_TYPE_DMESG_Z					\
10076f9c104bSAndy Shevchenko 	GUID_INIT(0x4f118707, 0x04dd, 0x4055, 0xb5, 0xdd, 0x95, 0x6d,	\
1008901037baSAruna Balakrishnaiah 		  0x34, 0xdd, 0xfa, 0xc6)
10090bb77c46STony Luck #define CPER_SECTION_TYPE_MCE						\
10106f9c104bSAndy Shevchenko 	GUID_INIT(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96,	\
10110bb77c46STony Luck 		  0x04, 0x4a, 0x38, 0xfc)
10120bb77c46STony Luck 
10130bb77c46STony Luck struct cper_pstore_record {
10140bb77c46STony Luck 	struct cper_record_header hdr;
10150bb77c46STony Luck 	struct cper_section_descriptor sec_hdr;
10160bb77c46STony Luck 	char data[];
10170bb77c46STony Luck } __packed;
10180bb77c46STony Luck 
101906cf91b4SChen Gong static int reader_pos;
102006cf91b4SChen Gong 
erst_open_pstore(struct pstore_info * psi)102106cf91b4SChen Gong static int erst_open_pstore(struct pstore_info *psi)
102206cf91b4SChen Gong {
102306cf91b4SChen Gong 	if (erst_disable)
102406cf91b4SChen Gong 		return -ENODEV;
102506cf91b4SChen Gong 
1026*382c5fecSye xingchen 	return erst_get_record_id_begin(&reader_pos);
102706cf91b4SChen Gong }
102806cf91b4SChen Gong 
erst_close_pstore(struct pstore_info * psi)102906cf91b4SChen Gong static int erst_close_pstore(struct pstore_info *psi)
103006cf91b4SChen Gong {
103106cf91b4SChen Gong 	erst_get_record_id_end();
103206cf91b4SChen Gong 
103306cf91b4SChen Gong 	return 0;
103406cf91b4SChen Gong }
103506cf91b4SChen Gong 
erst_reader(struct pstore_record * record)1036125cc42bSKees Cook static ssize_t erst_reader(struct pstore_record *record)
10370bb77c46STony Luck {
10380bb77c46STony Luck 	int rc;
103906cf91b4SChen Gong 	ssize_t len = 0;
10400bb77c46STony Luck 	u64 record_id;
1041f6f82851SKees Cook 	struct cper_pstore_record *rcd;
1042f6f82851SKees Cook 	size_t rcd_len = sizeof(*rcd) + erst_info.bufsize;
10430bb77c46STony Luck 
10440bb77c46STony Luck 	if (erst_disable)
10450bb77c46STony Luck 		return -ENODEV;
10460bb77c46STony Luck 
1047f6f82851SKees Cook 	rcd = kmalloc(rcd_len, GFP_KERNEL);
1048f6f82851SKees Cook 	if (!rcd) {
1049f6f82851SKees Cook 		rc = -ENOMEM;
1050f6f82851SKees Cook 		goto out;
1051f6f82851SKees Cook 	}
10520bb77c46STony Luck skip:
105306cf91b4SChen Gong 	rc = erst_get_record_id_next(&reader_pos, &record_id);
105406cf91b4SChen Gong 	if (rc)
105506cf91b4SChen Gong 		goto out;
105606cf91b4SChen Gong 
10570bb77c46STony Luck 	/* no more record */
10580bb77c46STony Luck 	if (record_id == APEI_ERST_INVALID_RECORD_ID) {
1059f6f82851SKees Cook 		rc = -EINVAL;
106006cf91b4SChen Gong 		goto out;
10610bb77c46STony Luck 	}
10620bb77c46STony Luck 
1063a0909315SLiu Xinpeng 	len = erst_read_record(record_id, &rcd->hdr, rcd_len, sizeof(*rcd),
1064a0909315SLiu Xinpeng 			&CPER_CREATOR_PSTORE);
1065f5ec25deSChen Gong 	/* The record may be cleared by others, try read next record */
1066f5ec25deSChen Gong 	if (len == -ENOENT)
1067f5ec25deSChen Gong 		goto skip;
1068a0909315SLiu Xinpeng 	else if (len < 0)
1069f5ec25deSChen Gong 		goto out;
10700bb77c46STony Luck 
1071125cc42bSKees Cook 	record->buf = kmalloc(len, GFP_KERNEL);
1072125cc42bSKees Cook 	if (record->buf == NULL) {
1073f6f82851SKees Cook 		rc = -ENOMEM;
1074f6f82851SKees Cook 		goto out;
1075f6f82851SKees Cook 	}
1076125cc42bSKees Cook 	memcpy(record->buf, rcd->data, len - sizeof(*rcd));
1077125cc42bSKees Cook 	record->id = record_id;
1078125cc42bSKees Cook 	record->compressed = false;
1079125cc42bSKees Cook 	record->ecc_notice_size = 0;
10806f9c104bSAndy Shevchenko 	if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_DMESG_Z)) {
1081125cc42bSKees Cook 		record->type = PSTORE_TYPE_DMESG;
1082125cc42bSKees Cook 		record->compressed = true;
10836f9c104bSAndy Shevchenko 	} else if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_DMESG))
1084125cc42bSKees Cook 		record->type = PSTORE_TYPE_DMESG;
10856f9c104bSAndy Shevchenko 	else if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_MCE))
1086125cc42bSKees Cook 		record->type = PSTORE_TYPE_MCE;
10870bb77c46STony Luck 	else
1088f0f23e54SJoel Fernandes (Google) 		record->type = PSTORE_TYPE_MAX;
10890bb77c46STony Luck 
10900bb77c46STony Luck 	if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP)
1091125cc42bSKees Cook 		record->time.tv_sec = rcd->hdr.timestamp;
10920bb77c46STony Luck 	else
1093125cc42bSKees Cook 		record->time.tv_sec = 0;
1094125cc42bSKees Cook 	record->time.tv_nsec = 0;
10950bb77c46STony Luck 
109606cf91b4SChen Gong out:
1097f6f82851SKees Cook 	kfree(rcd);
109806cf91b4SChen Gong 	return (rc < 0) ? rc : (len - sizeof(*rcd));
10990bb77c46STony Luck }
11000bb77c46STony Luck 
erst_writer(struct pstore_record * record)110176cc9580SKees Cook static int erst_writer(struct pstore_record *record)
11020bb77c46STony Luck {
11030bb77c46STony Luck 	struct cper_pstore_record *rcd = (struct cper_pstore_record *)
11040bb77c46STony Luck 					(erst_info.buf - sizeof(*rcd));
1105b238b8faSChen Gong 	int ret;
11060bb77c46STony Luck 
11070bb77c46STony Luck 	memset(rcd, 0, sizeof(*rcd));
11080bb77c46STony Luck 	memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE);
11090bb77c46STony Luck 	rcd->hdr.revision = CPER_RECORD_REV;
11100bb77c46STony Luck 	rcd->hdr.signature_end = CPER_SIG_END;
11110bb77c46STony Luck 	rcd->hdr.section_count = 1;
11120bb77c46STony Luck 	rcd->hdr.error_severity = CPER_SEV_FATAL;
11130bb77c46STony Luck 	/* timestamp valid. platform_id, partition_id are invalid */
11140bb77c46STony Luck 	rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP;
1115c0041d40SArnd Bergmann 	rcd->hdr.timestamp = ktime_get_real_seconds();
111676cc9580SKees Cook 	rcd->hdr.record_length = sizeof(*rcd) + record->size;
11170bb77c46STony Luck 	rcd->hdr.creator_id = CPER_CREATOR_PSTORE;
11180bb77c46STony Luck 	rcd->hdr.notification_type = CPER_NOTIFY_MCE;
11190bb77c46STony Luck 	rcd->hdr.record_id = cper_next_record_id();
11200bb77c46STony Luck 	rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR;
11210bb77c46STony Luck 
11220bb77c46STony Luck 	rcd->sec_hdr.section_offset = sizeof(*rcd);
112376cc9580SKees Cook 	rcd->sec_hdr.section_length = record->size;
11240bb77c46STony Luck 	rcd->sec_hdr.revision = CPER_SEC_REV;
11250bb77c46STony Luck 	/* fru_id and fru_text is invalid */
11260bb77c46STony Luck 	rcd->sec_hdr.validation_bits = 0;
11270bb77c46STony Luck 	rcd->sec_hdr.flags = CPER_SEC_PRIMARY;
112876cc9580SKees Cook 	switch (record->type) {
11290bb77c46STony Luck 	case PSTORE_TYPE_DMESG:
113076cc9580SKees Cook 		if (record->compressed)
1131901037baSAruna Balakrishnaiah 			rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG_Z;
1132901037baSAruna Balakrishnaiah 		else
11330bb77c46STony Luck 			rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG;
11340bb77c46STony Luck 		break;
11350bb77c46STony Luck 	case PSTORE_TYPE_MCE:
11360bb77c46STony Luck 		rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE;
11370bb77c46STony Luck 		break;
11380bb77c46STony Luck 	default:
11390bb77c46STony Luck 		return -EINVAL;
11400bb77c46STony Luck 	}
11410bb77c46STony Luck 	rcd->sec_hdr.section_severity = CPER_SEV_FATAL;
11420bb77c46STony Luck 
1143b238b8faSChen Gong 	ret = erst_write(&rcd->hdr);
114476cc9580SKees Cook 	record->id = rcd->hdr.record_id;
11450bb77c46STony Luck 
1146b238b8faSChen Gong 	return ret;
11470bb77c46STony Luck }
11480bb77c46STony Luck 
erst_clearer(struct pstore_record * record)1149a61072aaSKees Cook static int erst_clearer(struct pstore_record *record)
1150638c1fd3SMatthew Garrett {
1151a61072aaSKees Cook 	return erst_clear(record->id);
1152638c1fd3SMatthew Garrett }
1153638c1fd3SMatthew Garrett 
erst_init(void)1154a08f82d0SHuang Ying static int __init erst_init(void)
1155a08f82d0SHuang Ying {
1156a08f82d0SHuang Ying 	int rc = 0;
1157a08f82d0SHuang Ying 	acpi_status status;
1158a08f82d0SHuang Ying 	struct apei_exec_context ctx;
1159a08f82d0SHuang Ying 	struct apei_resources erst_resources;
1160a08f82d0SHuang Ying 	struct resource *r;
11610bb77c46STony Luck 	char *buf;
1162a08f82d0SHuang Ying 
1163a08f82d0SHuang Ying 	if (acpi_disabled)
1164a08f82d0SHuang Ying 		goto err;
1165a08f82d0SHuang Ying 
1166a08f82d0SHuang Ying 	if (erst_disable) {
1167cb82a2e4SBorislav Petkov 		pr_info(
1168a08f82d0SHuang Ying 	"Error Record Serialization Table (ERST) support is disabled.\n");
1169a08f82d0SHuang Ying 		goto err;
1170a08f82d0SHuang Ying 	}
1171a08f82d0SHuang Ying 
1172a08f82d0SHuang Ying 	status = acpi_get_table(ACPI_SIG_ERST, 0,
1173a08f82d0SHuang Ying 				(struct acpi_table_header **)&erst_tab);
1174ad686154SHuang Ying 	if (status == AE_NOT_FOUND)
1175a08f82d0SHuang Ying 		goto err;
1176ad686154SHuang Ying 	else if (ACPI_FAILURE(status)) {
1177a08f82d0SHuang Ying 		const char *msg = acpi_format_exception(status);
1178cb82a2e4SBorislav Petkov 		pr_err("Failed to get table, %s\n", msg);
1179a08f82d0SHuang Ying 		rc = -EINVAL;
1180a08f82d0SHuang Ying 		goto err;
1181a08f82d0SHuang Ying 	}
1182a08f82d0SHuang Ying 
1183a08f82d0SHuang Ying 	rc = erst_check_table(erst_tab);
1184a08f82d0SHuang Ying 	if (rc) {
1185cb82a2e4SBorislav Petkov 		pr_err(FW_BUG "ERST table is invalid.\n");
118643f595dcSHanjun Guo 		goto err_put_erst_tab;
1187a08f82d0SHuang Ying 	}
1188a08f82d0SHuang Ying 
1189a08f82d0SHuang Ying 	apei_resources_init(&erst_resources);
1190a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
1191a08f82d0SHuang Ying 	rc = apei_exec_collect_resources(&ctx, &erst_resources);
1192a08f82d0SHuang Ying 	if (rc)
1193a08f82d0SHuang Ying 		goto err_fini;
1194a08f82d0SHuang Ying 	rc = apei_resources_request(&erst_resources, "APEI ERST");
1195a08f82d0SHuang Ying 	if (rc)
1196a08f82d0SHuang Ying 		goto err_fini;
1197a08f82d0SHuang Ying 	rc = apei_exec_pre_map_gars(&ctx);
1198a08f82d0SHuang Ying 	if (rc)
1199a08f82d0SHuang Ying 		goto err_release;
1200a08f82d0SHuang Ying 	rc = erst_get_erange(&erst_erange);
1201a08f82d0SHuang Ying 	if (rc) {
1202a08f82d0SHuang Ying 		if (rc == -ENODEV)
1203cb82a2e4SBorislav Petkov 			pr_info(
1204a08f82d0SHuang Ying 	"The corresponding hardware device or firmware implementation "
1205a08f82d0SHuang Ying 	"is not available.\n");
1206a08f82d0SHuang Ying 		else
1207cb82a2e4SBorislav Petkov 			pr_err("Failed to get Error Log Address Range.\n");
1208a08f82d0SHuang Ying 		goto err_unmap_reg;
1209a08f82d0SHuang Ying 	}
1210a08f82d0SHuang Ying 
1211a08f82d0SHuang Ying 	r = request_mem_region(erst_erange.base, erst_erange.size, "APEI ERST");
1212a08f82d0SHuang Ying 	if (!r) {
1213cb82a2e4SBorislav Petkov 		pr_err("Can not request [mem %#010llx-%#010llx] for ERST.\n",
1214a08f82d0SHuang Ying 		       (unsigned long long)erst_erange.base,
1215cb82a2e4SBorislav Petkov 		       (unsigned long long)erst_erange.base + erst_erange.size - 1);
1216a08f82d0SHuang Ying 		rc = -EIO;
1217a08f82d0SHuang Ying 		goto err_unmap_reg;
1218a08f82d0SHuang Ying 	}
1219a08f82d0SHuang Ying 	rc = -ENOMEM;
1220a08f82d0SHuang Ying 	erst_erange.vaddr = ioremap_cache(erst_erange.base,
1221a08f82d0SHuang Ying 					  erst_erange.size);
1222a08f82d0SHuang Ying 	if (!erst_erange.vaddr)
1223a08f82d0SHuang Ying 		goto err_release_erange;
1224a08f82d0SHuang Ying 
1225cb82a2e4SBorislav Petkov 	pr_info(
122674fd6c6fSLenny Szubowicz 	"Error Record Serialization Table (ERST) support is initialized.\n");
122774fd6c6fSLenny Szubowicz 
12280bb77c46STony Luck 	buf = kmalloc(erst_erange.size, GFP_KERNEL);
12290bb77c46STony Luck 	if (buf) {
12300bb77c46STony Luck 		erst_info.buf = buf + sizeof(struct cper_pstore_record);
12310bb77c46STony Luck 		erst_info.bufsize = erst_erange.size -
12320bb77c46STony Luck 				    sizeof(struct cper_pstore_record);
123374fd6c6fSLenny Szubowicz 		rc = pstore_register(&erst_info);
123474fd6c6fSLenny Szubowicz 		if (rc) {
123574fd6c6fSLenny Szubowicz 			if (rc != -EPERM)
1236cb82a2e4SBorislav Petkov 				pr_info(
1237cb82a2e4SBorislav Petkov 				"Could not register with persistent store.\n");
123874fd6c6fSLenny Szubowicz 			erst_info.buf = NULL;
123974fd6c6fSLenny Szubowicz 			erst_info.bufsize = 0;
12400bb77c46STony Luck 			kfree(buf);
12410bb77c46STony Luck 		}
124274fd6c6fSLenny Szubowicz 	} else
1243cb82a2e4SBorislav Petkov 		pr_err(
1244cb82a2e4SBorislav Petkov 		"Failed to allocate %lld bytes for persistent store error log.\n",
124574fd6c6fSLenny Szubowicz 		erst_erange.size);
1246a08f82d0SHuang Ying 
12473a03d126SJosh Hunt 	/* Cleanup ERST Resources */
12483a03d126SJosh Hunt 	apei_resources_fini(&erst_resources);
12493a03d126SJosh Hunt 
1250a08f82d0SHuang Ying 	return 0;
1251a08f82d0SHuang Ying 
1252a08f82d0SHuang Ying err_release_erange:
1253a08f82d0SHuang Ying 	release_mem_region(erst_erange.base, erst_erange.size);
1254a08f82d0SHuang Ying err_unmap_reg:
1255a08f82d0SHuang Ying 	apei_exec_post_unmap_gars(&ctx);
1256a08f82d0SHuang Ying err_release:
1257a08f82d0SHuang Ying 	apei_resources_release(&erst_resources);
1258a08f82d0SHuang Ying err_fini:
1259a08f82d0SHuang Ying 	apei_resources_fini(&erst_resources);
126043f595dcSHanjun Guo err_put_erst_tab:
126143f595dcSHanjun Guo 	acpi_put_table((struct acpi_table_header *)erst_tab);
1262a08f82d0SHuang Ying err:
1263a08f82d0SHuang Ying 	erst_disable = 1;
1264a08f82d0SHuang Ying 	return rc;
1265a08f82d0SHuang Ying }
1266a08f82d0SHuang Ying 
1267a08f82d0SHuang Ying device_initcall(erst_init);
1268