xref: /openbmc/linux/drivers/acpi/apei/erst.c (revision ad686154)
1a08f82d0SHuang Ying /*
2a08f82d0SHuang Ying  * APEI Error Record Serialization Table support
3a08f82d0SHuang Ying  *
4a08f82d0SHuang Ying  * ERST is a way provided by APEI to save and retrieve hardware error
558f87ed0SLucas De Marchi  * information to and from a persistent store.
6a08f82d0SHuang Ying  *
7a08f82d0SHuang Ying  * For more information about ERST, please refer to ACPI Specification
8a08f82d0SHuang Ying  * version 4.0, section 17.4.
9a08f82d0SHuang Ying  *
10a08f82d0SHuang Ying  * Copyright 2010 Intel Corp.
11a08f82d0SHuang Ying  *   Author: Huang Ying <ying.huang@intel.com>
12a08f82d0SHuang Ying  *
13a08f82d0SHuang Ying  * This program is free software; you can redistribute it and/or
14a08f82d0SHuang Ying  * modify it under the terms of the GNU General Public License version
15a08f82d0SHuang Ying  * 2 as published by the Free Software Foundation.
16a08f82d0SHuang Ying  *
17a08f82d0SHuang Ying  * This program is distributed in the hope that it will be useful,
18a08f82d0SHuang Ying  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19a08f82d0SHuang Ying  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20a08f82d0SHuang Ying  * GNU General Public License for more details.
21a08f82d0SHuang Ying  *
22a08f82d0SHuang Ying  * You should have received a copy of the GNU General Public License
23a08f82d0SHuang Ying  * along with this program; if not, write to the Free Software
24a08f82d0SHuang Ying  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25a08f82d0SHuang Ying  */
26a08f82d0SHuang Ying 
27a08f82d0SHuang Ying #include <linux/kernel.h>
28a08f82d0SHuang Ying #include <linux/module.h>
29a08f82d0SHuang Ying #include <linux/init.h>
30a08f82d0SHuang Ying #include <linux/delay.h>
31a08f82d0SHuang Ying #include <linux/io.h>
32a08f82d0SHuang Ying #include <linux/acpi.h>
33a08f82d0SHuang Ying #include <linux/uaccess.h>
34a08f82d0SHuang Ying #include <linux/cper.h>
35a08f82d0SHuang Ying #include <linux/nmi.h>
360a7992c9SThomas Gleixner #include <linux/hardirq.h>
370bb77c46STony Luck #include <linux/pstore.h>
38a08f82d0SHuang Ying #include <acpi/apei.h>
39a08f82d0SHuang Ying 
40a08f82d0SHuang Ying #include "apei-internal.h"
41a08f82d0SHuang Ying 
42a08f82d0SHuang Ying #define ERST_PFX "ERST: "
43a08f82d0SHuang Ying 
44a08f82d0SHuang Ying /* ERST command status */
45a08f82d0SHuang Ying #define ERST_STATUS_SUCCESS			0x0
46a08f82d0SHuang Ying #define ERST_STATUS_NOT_ENOUGH_SPACE		0x1
47a08f82d0SHuang Ying #define ERST_STATUS_HARDWARE_NOT_AVAILABLE	0x2
48a08f82d0SHuang Ying #define ERST_STATUS_FAILED			0x3
49a08f82d0SHuang Ying #define ERST_STATUS_RECORD_STORE_EMPTY		0x4
50a08f82d0SHuang Ying #define ERST_STATUS_RECORD_NOT_FOUND		0x5
51a08f82d0SHuang Ying 
52a08f82d0SHuang Ying #define ERST_TAB_ENTRY(tab)						\
53a08f82d0SHuang Ying 	((struct acpi_whea_header *)((char *)(tab) +			\
54a08f82d0SHuang Ying 				     sizeof(struct acpi_table_erst)))
55a08f82d0SHuang Ying 
56a08f82d0SHuang Ying #define SPIN_UNIT		100			/* 100ns */
57e8a8b252SStefan Weil /* Firmware should respond within 1 milliseconds */
58a08f82d0SHuang Ying #define FIRMWARE_TIMEOUT	(1 * NSEC_PER_MSEC)
59a08f82d0SHuang Ying #define FIRMWARE_MAX_STALL	50			/* 50us */
60a08f82d0SHuang Ying 
61a08f82d0SHuang Ying int erst_disable;
62a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_disable);
63a08f82d0SHuang Ying 
64a08f82d0SHuang Ying static struct acpi_table_erst *erst_tab;
65a08f82d0SHuang Ying 
66a08f82d0SHuang Ying /* ERST Error Log Address Range atrributes */
67a08f82d0SHuang Ying #define ERST_RANGE_RESERVED	0x0001
68a08f82d0SHuang Ying #define ERST_RANGE_NVRAM	0x0002
69a08f82d0SHuang Ying #define ERST_RANGE_SLOW		0x0004
70a08f82d0SHuang Ying 
71a08f82d0SHuang Ying /*
72a08f82d0SHuang Ying  * ERST Error Log Address Range, used as buffer for reading/writing
73a08f82d0SHuang Ying  * error records.
74a08f82d0SHuang Ying  */
75a08f82d0SHuang Ying static struct erst_erange {
76a08f82d0SHuang Ying 	u64 base;
77a08f82d0SHuang Ying 	u64 size;
78a08f82d0SHuang Ying 	void __iomem *vaddr;
79a08f82d0SHuang Ying 	u32 attr;
80a08f82d0SHuang Ying } erst_erange;
81a08f82d0SHuang Ying 
82a08f82d0SHuang Ying /*
83a08f82d0SHuang Ying  * Prevent ERST interpreter to run simultaneously, because the
84a08f82d0SHuang Ying  * corresponding firmware implementation may not work properly when
85a08f82d0SHuang Ying  * invoked simultaneously.
86a08f82d0SHuang Ying  *
87a08f82d0SHuang Ying  * It is used to provide exclusive accessing for ERST Error Log
88a08f82d0SHuang Ying  * Address Range too.
89a08f82d0SHuang Ying  */
903b38bb5fSHuang Ying static DEFINE_RAW_SPINLOCK(erst_lock);
91a08f82d0SHuang Ying 
92a08f82d0SHuang Ying static inline int erst_errno(int command_status)
93a08f82d0SHuang Ying {
94a08f82d0SHuang Ying 	switch (command_status) {
95a08f82d0SHuang Ying 	case ERST_STATUS_SUCCESS:
96a08f82d0SHuang Ying 		return 0;
97a08f82d0SHuang Ying 	case ERST_STATUS_HARDWARE_NOT_AVAILABLE:
98a08f82d0SHuang Ying 		return -ENODEV;
99a08f82d0SHuang Ying 	case ERST_STATUS_NOT_ENOUGH_SPACE:
100a08f82d0SHuang Ying 		return -ENOSPC;
101a08f82d0SHuang Ying 	case ERST_STATUS_RECORD_STORE_EMPTY:
102a08f82d0SHuang Ying 	case ERST_STATUS_RECORD_NOT_FOUND:
103a08f82d0SHuang Ying 		return -ENOENT;
104a08f82d0SHuang Ying 	default:
105a08f82d0SHuang Ying 		return -EINVAL;
106a08f82d0SHuang Ying 	}
107a08f82d0SHuang Ying }
108a08f82d0SHuang Ying 
109a08f82d0SHuang Ying static int erst_timedout(u64 *t, u64 spin_unit)
110a08f82d0SHuang Ying {
111a08f82d0SHuang Ying 	if ((s64)*t < spin_unit) {
112a08f82d0SHuang Ying 		pr_warning(FW_WARN ERST_PFX
113a08f82d0SHuang Ying 			   "Firmware does not respond in time\n");
114a08f82d0SHuang Ying 		return 1;
115a08f82d0SHuang Ying 	}
116a08f82d0SHuang Ying 	*t -= spin_unit;
117a08f82d0SHuang Ying 	ndelay(spin_unit);
118a08f82d0SHuang Ying 	touch_nmi_watchdog();
119a08f82d0SHuang Ying 	return 0;
120a08f82d0SHuang Ying }
121a08f82d0SHuang Ying 
122a08f82d0SHuang Ying static int erst_exec_load_var1(struct apei_exec_context *ctx,
123a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
124a08f82d0SHuang Ying {
125a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->var1);
126a08f82d0SHuang Ying }
127a08f82d0SHuang Ying 
128a08f82d0SHuang Ying static int erst_exec_load_var2(struct apei_exec_context *ctx,
129a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
130a08f82d0SHuang Ying {
131a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->var2);
132a08f82d0SHuang Ying }
133a08f82d0SHuang Ying 
134a08f82d0SHuang Ying static int erst_exec_store_var1(struct apei_exec_context *ctx,
135a08f82d0SHuang Ying 				struct acpi_whea_header *entry)
136a08f82d0SHuang Ying {
137a08f82d0SHuang Ying 	return __apei_exec_write_register(entry, ctx->var1);
138a08f82d0SHuang Ying }
139a08f82d0SHuang Ying 
140a08f82d0SHuang Ying static int erst_exec_add(struct apei_exec_context *ctx,
141a08f82d0SHuang Ying 			 struct acpi_whea_header *entry)
142a08f82d0SHuang Ying {
143a08f82d0SHuang Ying 	ctx->var1 += ctx->var2;
144a08f82d0SHuang Ying 	return 0;
145a08f82d0SHuang Ying }
146a08f82d0SHuang Ying 
147a08f82d0SHuang Ying static int erst_exec_subtract(struct apei_exec_context *ctx,
148a08f82d0SHuang Ying 			      struct acpi_whea_header *entry)
149a08f82d0SHuang Ying {
150a08f82d0SHuang Ying 	ctx->var1 -= ctx->var2;
151a08f82d0SHuang Ying 	return 0;
152a08f82d0SHuang Ying }
153a08f82d0SHuang Ying 
154a08f82d0SHuang Ying static int erst_exec_add_value(struct apei_exec_context *ctx,
155a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
156a08f82d0SHuang Ying {
157a08f82d0SHuang Ying 	int rc;
158a08f82d0SHuang Ying 	u64 val;
159a08f82d0SHuang Ying 
160a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &val);
161a08f82d0SHuang Ying 	if (rc)
162a08f82d0SHuang Ying 		return rc;
163a08f82d0SHuang Ying 	val += ctx->value;
164a08f82d0SHuang Ying 	rc = __apei_exec_write_register(entry, val);
165a08f82d0SHuang Ying 	return rc;
166a08f82d0SHuang Ying }
167a08f82d0SHuang Ying 
168a08f82d0SHuang Ying static int erst_exec_subtract_value(struct apei_exec_context *ctx,
169a08f82d0SHuang Ying 				    struct acpi_whea_header *entry)
170a08f82d0SHuang Ying {
171a08f82d0SHuang Ying 	int rc;
172a08f82d0SHuang Ying 	u64 val;
173a08f82d0SHuang Ying 
174a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &val);
175a08f82d0SHuang Ying 	if (rc)
176a08f82d0SHuang Ying 		return rc;
177a08f82d0SHuang Ying 	val -= ctx->value;
178a08f82d0SHuang Ying 	rc = __apei_exec_write_register(entry, val);
179a08f82d0SHuang Ying 	return rc;
180a08f82d0SHuang Ying }
181a08f82d0SHuang Ying 
182a08f82d0SHuang Ying static int erst_exec_stall(struct apei_exec_context *ctx,
183a08f82d0SHuang Ying 			   struct acpi_whea_header *entry)
184a08f82d0SHuang Ying {
185a08f82d0SHuang Ying 	u64 stall_time;
186a08f82d0SHuang Ying 
187a08f82d0SHuang Ying 	if (ctx->value > FIRMWARE_MAX_STALL) {
188a08f82d0SHuang Ying 		if (!in_nmi())
189a08f82d0SHuang Ying 			pr_warning(FW_WARN ERST_PFX
190a08f82d0SHuang Ying 			"Too long stall time for stall instruction: %llx.\n",
191a08f82d0SHuang Ying 				   ctx->value);
192a08f82d0SHuang Ying 		stall_time = FIRMWARE_MAX_STALL;
193a08f82d0SHuang Ying 	} else
194a08f82d0SHuang Ying 		stall_time = ctx->value;
195a08f82d0SHuang Ying 	udelay(stall_time);
196a08f82d0SHuang Ying 	return 0;
197a08f82d0SHuang Ying }
198a08f82d0SHuang Ying 
199a08f82d0SHuang Ying static int erst_exec_stall_while_true(struct apei_exec_context *ctx,
200a08f82d0SHuang Ying 				      struct acpi_whea_header *entry)
201a08f82d0SHuang Ying {
202a08f82d0SHuang Ying 	int rc;
203a08f82d0SHuang Ying 	u64 val;
204a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
205a08f82d0SHuang Ying 	u64 stall_time;
206a08f82d0SHuang Ying 
207a08f82d0SHuang Ying 	if (ctx->var1 > FIRMWARE_MAX_STALL) {
208a08f82d0SHuang Ying 		if (!in_nmi())
209a08f82d0SHuang Ying 			pr_warning(FW_WARN ERST_PFX
210a08f82d0SHuang Ying 		"Too long stall time for stall while true instruction: %llx.\n",
211a08f82d0SHuang Ying 				   ctx->var1);
212a08f82d0SHuang Ying 		stall_time = FIRMWARE_MAX_STALL;
213a08f82d0SHuang Ying 	} else
214a08f82d0SHuang Ying 		stall_time = ctx->var1;
215a08f82d0SHuang Ying 
216a08f82d0SHuang Ying 	for (;;) {
217a08f82d0SHuang Ying 		rc = __apei_exec_read_register(entry, &val);
218a08f82d0SHuang Ying 		if (rc)
219a08f82d0SHuang Ying 			return rc;
220a08f82d0SHuang Ying 		if (val != ctx->value)
221a08f82d0SHuang Ying 			break;
222a08f82d0SHuang Ying 		if (erst_timedout(&timeout, stall_time * NSEC_PER_USEC))
223a08f82d0SHuang Ying 			return -EIO;
224a08f82d0SHuang Ying 	}
225a08f82d0SHuang Ying 	return 0;
226a08f82d0SHuang Ying }
227a08f82d0SHuang Ying 
228a08f82d0SHuang Ying static int erst_exec_skip_next_instruction_if_true(
229a08f82d0SHuang Ying 	struct apei_exec_context *ctx,
230a08f82d0SHuang Ying 	struct acpi_whea_header *entry)
231a08f82d0SHuang Ying {
232a08f82d0SHuang Ying 	int rc;
233a08f82d0SHuang Ying 	u64 val;
234a08f82d0SHuang Ying 
235a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &val);
236a08f82d0SHuang Ying 	if (rc)
237a08f82d0SHuang Ying 		return rc;
238a08f82d0SHuang Ying 	if (val == ctx->value) {
239a08f82d0SHuang Ying 		ctx->ip += 2;
240a08f82d0SHuang Ying 		return APEI_EXEC_SET_IP;
241a08f82d0SHuang Ying 	}
242a08f82d0SHuang Ying 
243a08f82d0SHuang Ying 	return 0;
244a08f82d0SHuang Ying }
245a08f82d0SHuang Ying 
246a08f82d0SHuang Ying static int erst_exec_goto(struct apei_exec_context *ctx,
247a08f82d0SHuang Ying 			  struct acpi_whea_header *entry)
248a08f82d0SHuang Ying {
249a08f82d0SHuang Ying 	ctx->ip = ctx->value;
250a08f82d0SHuang Ying 	return APEI_EXEC_SET_IP;
251a08f82d0SHuang Ying }
252a08f82d0SHuang Ying 
253a08f82d0SHuang Ying static int erst_exec_set_src_address_base(struct apei_exec_context *ctx,
254a08f82d0SHuang Ying 					  struct acpi_whea_header *entry)
255a08f82d0SHuang Ying {
256a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->src_base);
257a08f82d0SHuang Ying }
258a08f82d0SHuang Ying 
259a08f82d0SHuang Ying static int erst_exec_set_dst_address_base(struct apei_exec_context *ctx,
260a08f82d0SHuang Ying 					  struct acpi_whea_header *entry)
261a08f82d0SHuang Ying {
262a08f82d0SHuang Ying 	return __apei_exec_read_register(entry, &ctx->dst_base);
263a08f82d0SHuang Ying }
264a08f82d0SHuang Ying 
265a08f82d0SHuang Ying static int erst_exec_move_data(struct apei_exec_context *ctx,
266a08f82d0SHuang Ying 			       struct acpi_whea_header *entry)
267a08f82d0SHuang Ying {
268a08f82d0SHuang Ying 	int rc;
269a08f82d0SHuang Ying 	u64 offset;
2700bbba38aSHuang Ying 	void *src, *dst;
2710bbba38aSHuang Ying 
2720bbba38aSHuang Ying 	/* ioremap does not work in interrupt context */
2730bbba38aSHuang Ying 	if (in_interrupt()) {
2740bbba38aSHuang Ying 		pr_warning(ERST_PFX
2750bbba38aSHuang Ying 			   "MOVE_DATA can not be used in interrupt context");
2760bbba38aSHuang Ying 		return -EBUSY;
2770bbba38aSHuang Ying 	}
278a08f82d0SHuang Ying 
279a08f82d0SHuang Ying 	rc = __apei_exec_read_register(entry, &offset);
280a08f82d0SHuang Ying 	if (rc)
281a08f82d0SHuang Ying 		return rc;
2820bbba38aSHuang Ying 
2830bbba38aSHuang Ying 	src = ioremap(ctx->src_base + offset, ctx->var2);
2840bbba38aSHuang Ying 	if (!src)
2850bbba38aSHuang Ying 		return -ENOMEM;
2860bbba38aSHuang Ying 	dst = ioremap(ctx->dst_base + offset, ctx->var2);
2870bbba38aSHuang Ying 	if (!dst)
2880bbba38aSHuang Ying 		return -ENOMEM;
2890bbba38aSHuang Ying 
2900bbba38aSHuang Ying 	memmove(dst, src, ctx->var2);
2910bbba38aSHuang Ying 
2920bbba38aSHuang Ying 	iounmap(src);
2930bbba38aSHuang Ying 	iounmap(dst);
294a08f82d0SHuang Ying 
295a08f82d0SHuang Ying 	return 0;
296a08f82d0SHuang Ying }
297a08f82d0SHuang Ying 
298a08f82d0SHuang Ying static struct apei_exec_ins_type erst_ins_type[] = {
299a08f82d0SHuang Ying 	[ACPI_ERST_READ_REGISTER] = {
300a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
301a08f82d0SHuang Ying 		.run = apei_exec_read_register,
302a08f82d0SHuang Ying 	},
303a08f82d0SHuang Ying 	[ACPI_ERST_READ_REGISTER_VALUE] = {
304a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
305a08f82d0SHuang Ying 		.run = apei_exec_read_register_value,
306a08f82d0SHuang Ying 	},
307a08f82d0SHuang Ying 	[ACPI_ERST_WRITE_REGISTER] = {
308a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
309a08f82d0SHuang Ying 		.run = apei_exec_write_register,
310a08f82d0SHuang Ying 	},
311a08f82d0SHuang Ying 	[ACPI_ERST_WRITE_REGISTER_VALUE] = {
312a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
313a08f82d0SHuang Ying 		.run = apei_exec_write_register_value,
314a08f82d0SHuang Ying 	},
315a08f82d0SHuang Ying 	[ACPI_ERST_NOOP] = {
316a08f82d0SHuang Ying 		.flags = 0,
317a08f82d0SHuang Ying 		.run = apei_exec_noop,
318a08f82d0SHuang Ying 	},
319a08f82d0SHuang Ying 	[ACPI_ERST_LOAD_VAR1] = {
320a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
321a08f82d0SHuang Ying 		.run = erst_exec_load_var1,
322a08f82d0SHuang Ying 	},
323a08f82d0SHuang Ying 	[ACPI_ERST_LOAD_VAR2] = {
324a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
325a08f82d0SHuang Ying 		.run = erst_exec_load_var2,
326a08f82d0SHuang Ying 	},
327a08f82d0SHuang Ying 	[ACPI_ERST_STORE_VAR1] = {
328a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
329a08f82d0SHuang Ying 		.run = erst_exec_store_var1,
330a08f82d0SHuang Ying 	},
331a08f82d0SHuang Ying 	[ACPI_ERST_ADD] = {
332a08f82d0SHuang Ying 		.flags = 0,
333a08f82d0SHuang Ying 		.run = erst_exec_add,
334a08f82d0SHuang Ying 	},
335a08f82d0SHuang Ying 	[ACPI_ERST_SUBTRACT] = {
336a08f82d0SHuang Ying 		.flags = 0,
337a08f82d0SHuang Ying 		.run = erst_exec_subtract,
338a08f82d0SHuang Ying 	},
339a08f82d0SHuang Ying 	[ACPI_ERST_ADD_VALUE] = {
340a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
341a08f82d0SHuang Ying 		.run = erst_exec_add_value,
342a08f82d0SHuang Ying 	},
343a08f82d0SHuang Ying 	[ACPI_ERST_SUBTRACT_VALUE] = {
344a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
345a08f82d0SHuang Ying 		.run = erst_exec_subtract_value,
346a08f82d0SHuang Ying 	},
347a08f82d0SHuang Ying 	[ACPI_ERST_STALL] = {
348a08f82d0SHuang Ying 		.flags = 0,
349a08f82d0SHuang Ying 		.run = erst_exec_stall,
350a08f82d0SHuang Ying 	},
351a08f82d0SHuang Ying 	[ACPI_ERST_STALL_WHILE_TRUE] = {
352a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
353a08f82d0SHuang Ying 		.run = erst_exec_stall_while_true,
354a08f82d0SHuang Ying 	},
355a08f82d0SHuang Ying 	[ACPI_ERST_SKIP_NEXT_IF_TRUE] = {
356a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
357a08f82d0SHuang Ying 		.run = erst_exec_skip_next_instruction_if_true,
358a08f82d0SHuang Ying 	},
359a08f82d0SHuang Ying 	[ACPI_ERST_GOTO] = {
360a08f82d0SHuang Ying 		.flags = 0,
361a08f82d0SHuang Ying 		.run = erst_exec_goto,
362a08f82d0SHuang Ying 	},
363a08f82d0SHuang Ying 	[ACPI_ERST_SET_SRC_ADDRESS_BASE] = {
364a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
365a08f82d0SHuang Ying 		.run = erst_exec_set_src_address_base,
366a08f82d0SHuang Ying 	},
367a08f82d0SHuang Ying 	[ACPI_ERST_SET_DST_ADDRESS_BASE] = {
368a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
369a08f82d0SHuang Ying 		.run = erst_exec_set_dst_address_base,
370a08f82d0SHuang Ying 	},
371a08f82d0SHuang Ying 	[ACPI_ERST_MOVE_DATA] = {
372a08f82d0SHuang Ying 		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
373a08f82d0SHuang Ying 		.run = erst_exec_move_data,
374a08f82d0SHuang Ying 	},
375a08f82d0SHuang Ying };
376a08f82d0SHuang Ying 
377a08f82d0SHuang Ying static inline void erst_exec_ctx_init(struct apei_exec_context *ctx)
378a08f82d0SHuang Ying {
379a08f82d0SHuang Ying 	apei_exec_ctx_init(ctx, erst_ins_type, ARRAY_SIZE(erst_ins_type),
380a08f82d0SHuang Ying 			   ERST_TAB_ENTRY(erst_tab), erst_tab->entries);
381a08f82d0SHuang Ying }
382a08f82d0SHuang Ying 
383a08f82d0SHuang Ying static int erst_get_erange(struct erst_erange *range)
384a08f82d0SHuang Ying {
385a08f82d0SHuang Ying 	struct apei_exec_context ctx;
386a08f82d0SHuang Ying 	int rc;
387a08f82d0SHuang Ying 
388a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
389a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_RANGE);
390a08f82d0SHuang Ying 	if (rc)
391a08f82d0SHuang Ying 		return rc;
392a08f82d0SHuang Ying 	range->base = apei_exec_ctx_get_output(&ctx);
393a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_LENGTH);
394a08f82d0SHuang Ying 	if (rc)
395a08f82d0SHuang Ying 		return rc;
396a08f82d0SHuang Ying 	range->size = apei_exec_ctx_get_output(&ctx);
397a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_ATTRIBUTES);
398a08f82d0SHuang Ying 	if (rc)
399a08f82d0SHuang Ying 		return rc;
400a08f82d0SHuang Ying 	range->attr = apei_exec_ctx_get_output(&ctx);
401a08f82d0SHuang Ying 
402a08f82d0SHuang Ying 	return 0;
403a08f82d0SHuang Ying }
404a08f82d0SHuang Ying 
405a08f82d0SHuang Ying static ssize_t __erst_get_record_count(void)
406a08f82d0SHuang Ying {
407a08f82d0SHuang Ying 	struct apei_exec_context ctx;
408a08f82d0SHuang Ying 	int rc;
409a08f82d0SHuang Ying 
410a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
411a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_COUNT);
412a08f82d0SHuang Ying 	if (rc)
413a08f82d0SHuang Ying 		return rc;
414a08f82d0SHuang Ying 	return apei_exec_ctx_get_output(&ctx);
415a08f82d0SHuang Ying }
416a08f82d0SHuang Ying 
417a08f82d0SHuang Ying ssize_t erst_get_record_count(void)
418a08f82d0SHuang Ying {
419a08f82d0SHuang Ying 	ssize_t count;
420a08f82d0SHuang Ying 	unsigned long flags;
421a08f82d0SHuang Ying 
422a08f82d0SHuang Ying 	if (erst_disable)
423a08f82d0SHuang Ying 		return -ENODEV;
424a08f82d0SHuang Ying 
4253b38bb5fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
426a08f82d0SHuang Ying 	count = __erst_get_record_count();
4273b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
428a08f82d0SHuang Ying 
429a08f82d0SHuang Ying 	return count;
430a08f82d0SHuang Ying }
431a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_count);
432a08f82d0SHuang Ying 
433885b976fSHuang Ying #define ERST_RECORD_ID_CACHE_SIZE_MIN	16
434885b976fSHuang Ying #define ERST_RECORD_ID_CACHE_SIZE_MAX	1024
435885b976fSHuang Ying 
436885b976fSHuang Ying struct erst_record_id_cache {
437885b976fSHuang Ying 	struct mutex lock;
438885b976fSHuang Ying 	u64 *entries;
439885b976fSHuang Ying 	int len;
440885b976fSHuang Ying 	int size;
441885b976fSHuang Ying 	int refcount;
442885b976fSHuang Ying };
443885b976fSHuang Ying 
444885b976fSHuang Ying static struct erst_record_id_cache erst_record_id_cache = {
445885b976fSHuang Ying 	.lock = __MUTEX_INITIALIZER(erst_record_id_cache.lock),
446885b976fSHuang Ying 	.refcount = 0,
447885b976fSHuang Ying };
448885b976fSHuang Ying 
449a08f82d0SHuang Ying static int __erst_get_next_record_id(u64 *record_id)
450a08f82d0SHuang Ying {
451a08f82d0SHuang Ying 	struct apei_exec_context ctx;
452a08f82d0SHuang Ying 	int rc;
453a08f82d0SHuang Ying 
454a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
455a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_ID);
456a08f82d0SHuang Ying 	if (rc)
457a08f82d0SHuang Ying 		return rc;
458a08f82d0SHuang Ying 	*record_id = apei_exec_ctx_get_output(&ctx);
459a08f82d0SHuang Ying 
460a08f82d0SHuang Ying 	return 0;
461a08f82d0SHuang Ying }
462a08f82d0SHuang Ying 
463885b976fSHuang Ying int erst_get_record_id_begin(int *pos)
464885b976fSHuang Ying {
465885b976fSHuang Ying 	int rc;
466885b976fSHuang Ying 
467885b976fSHuang Ying 	if (erst_disable)
468885b976fSHuang Ying 		return -ENODEV;
469885b976fSHuang Ying 
470885b976fSHuang Ying 	rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
471885b976fSHuang Ying 	if (rc)
472885b976fSHuang Ying 		return rc;
473885b976fSHuang Ying 	erst_record_id_cache.refcount++;
474885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
475885b976fSHuang Ying 
476885b976fSHuang Ying 	*pos = 0;
477885b976fSHuang Ying 
478885b976fSHuang Ying 	return 0;
479885b976fSHuang Ying }
480885b976fSHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_id_begin);
481885b976fSHuang Ying 
482885b976fSHuang Ying /* erst_record_id_cache.lock must be held by caller */
483885b976fSHuang Ying static int __erst_record_id_cache_add_one(void)
484885b976fSHuang Ying {
485885b976fSHuang Ying 	u64 id, prev_id, first_id;
486885b976fSHuang Ying 	int i, rc;
487885b976fSHuang Ying 	u64 *entries;
488885b976fSHuang Ying 	unsigned long flags;
489885b976fSHuang Ying 
490885b976fSHuang Ying 	id = prev_id = first_id = APEI_ERST_INVALID_RECORD_ID;
491885b976fSHuang Ying retry:
492885b976fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
493885b976fSHuang Ying 	rc = __erst_get_next_record_id(&id);
494885b976fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
495885b976fSHuang Ying 	if (rc == -ENOENT)
496885b976fSHuang Ying 		return 0;
497885b976fSHuang Ying 	if (rc)
498885b976fSHuang Ying 		return rc;
499885b976fSHuang Ying 	if (id == APEI_ERST_INVALID_RECORD_ID)
500885b976fSHuang Ying 		return 0;
501885b976fSHuang Ying 	/* can not skip current ID, or loop back to first ID */
502885b976fSHuang Ying 	if (id == prev_id || id == first_id)
503885b976fSHuang Ying 		return 0;
504885b976fSHuang Ying 	if (first_id == APEI_ERST_INVALID_RECORD_ID)
505885b976fSHuang Ying 		first_id = id;
506885b976fSHuang Ying 	prev_id = id;
507885b976fSHuang Ying 
508885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
509885b976fSHuang Ying 	for (i = 0; i < erst_record_id_cache.len; i++) {
510885b976fSHuang Ying 		if (entries[i] == id)
511885b976fSHuang Ying 			break;
512885b976fSHuang Ying 	}
513885b976fSHuang Ying 	/* record id already in cache, try next */
514885b976fSHuang Ying 	if (i < erst_record_id_cache.len)
515885b976fSHuang Ying 		goto retry;
516885b976fSHuang Ying 	if (erst_record_id_cache.len >= erst_record_id_cache.size) {
517885b976fSHuang Ying 		int new_size, alloc_size;
518885b976fSHuang Ying 		u64 *new_entries;
519885b976fSHuang Ying 
520885b976fSHuang Ying 		new_size = erst_record_id_cache.size * 2;
521885b976fSHuang Ying 		new_size = clamp_val(new_size, ERST_RECORD_ID_CACHE_SIZE_MIN,
522885b976fSHuang Ying 				     ERST_RECORD_ID_CACHE_SIZE_MAX);
523885b976fSHuang Ying 		if (new_size <= erst_record_id_cache.size) {
524885b976fSHuang Ying 			if (printk_ratelimit())
525885b976fSHuang Ying 				pr_warning(FW_WARN ERST_PFX
526885b976fSHuang Ying 					   "too many record ID!\n");
527885b976fSHuang Ying 			return 0;
528885b976fSHuang Ying 		}
529885b976fSHuang Ying 		alloc_size = new_size * sizeof(entries[0]);
530885b976fSHuang Ying 		if (alloc_size < PAGE_SIZE)
531885b976fSHuang Ying 			new_entries = kmalloc(alloc_size, GFP_KERNEL);
532885b976fSHuang Ying 		else
533885b976fSHuang Ying 			new_entries = vmalloc(alloc_size);
534885b976fSHuang Ying 		if (!new_entries)
535885b976fSHuang Ying 			return -ENOMEM;
536885b976fSHuang Ying 		memcpy(new_entries, entries,
537885b976fSHuang Ying 		       erst_record_id_cache.len * sizeof(entries[0]));
538885b976fSHuang Ying 		if (erst_record_id_cache.size < PAGE_SIZE)
539885b976fSHuang Ying 			kfree(entries);
540885b976fSHuang Ying 		else
541885b976fSHuang Ying 			vfree(entries);
542885b976fSHuang Ying 		erst_record_id_cache.entries = entries = new_entries;
543885b976fSHuang Ying 		erst_record_id_cache.size = new_size;
544885b976fSHuang Ying 	}
545885b976fSHuang Ying 	entries[i] = id;
546885b976fSHuang Ying 	erst_record_id_cache.len++;
547885b976fSHuang Ying 
548885b976fSHuang Ying 	return 1;
549885b976fSHuang Ying }
550885b976fSHuang Ying 
551a08f82d0SHuang Ying /*
552a08f82d0SHuang Ying  * Get the record ID of an existing error record on the persistent
553a08f82d0SHuang Ying  * storage. If there is no error record on the persistent storage, the
554a08f82d0SHuang Ying  * returned record_id is APEI_ERST_INVALID_RECORD_ID.
555a08f82d0SHuang Ying  */
556885b976fSHuang Ying int erst_get_record_id_next(int *pos, u64 *record_id)
557a08f82d0SHuang Ying {
558885b976fSHuang Ying 	int rc = 0;
559885b976fSHuang Ying 	u64 *entries;
560a08f82d0SHuang Ying 
561a08f82d0SHuang Ying 	if (erst_disable)
562a08f82d0SHuang Ying 		return -ENODEV;
563a08f82d0SHuang Ying 
564885b976fSHuang Ying 	/* must be enclosed by erst_get_record_id_begin/end */
565885b976fSHuang Ying 	BUG_ON(!erst_record_id_cache.refcount);
566885b976fSHuang Ying 	BUG_ON(*pos < 0 || *pos > erst_record_id_cache.len);
567885b976fSHuang Ying 
568885b976fSHuang Ying 	mutex_lock(&erst_record_id_cache.lock);
569885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
570885b976fSHuang Ying 	for (; *pos < erst_record_id_cache.len; (*pos)++)
571885b976fSHuang Ying 		if (entries[*pos] != APEI_ERST_INVALID_RECORD_ID)
572885b976fSHuang Ying 			break;
573885b976fSHuang Ying 	/* found next record id in cache */
574885b976fSHuang Ying 	if (*pos < erst_record_id_cache.len) {
575885b976fSHuang Ying 		*record_id = entries[*pos];
576885b976fSHuang Ying 		(*pos)++;
577885b976fSHuang Ying 		goto out_unlock;
578885b976fSHuang Ying 	}
579885b976fSHuang Ying 
580885b976fSHuang Ying 	/* Try to add one more record ID to cache */
581885b976fSHuang Ying 	rc = __erst_record_id_cache_add_one();
582885b976fSHuang Ying 	if (rc < 0)
583885b976fSHuang Ying 		goto out_unlock;
584885b976fSHuang Ying 	/* successfully add one new ID */
585885b976fSHuang Ying 	if (rc == 1) {
586885b976fSHuang Ying 		*record_id = erst_record_id_cache.entries[*pos];
587885b976fSHuang Ying 		(*pos)++;
588885b976fSHuang Ying 		rc = 0;
589885b976fSHuang Ying 	} else {
590885b976fSHuang Ying 		*pos = -1;
591885b976fSHuang Ying 		*record_id = APEI_ERST_INVALID_RECORD_ID;
592885b976fSHuang Ying 	}
593885b976fSHuang Ying out_unlock:
594885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
595a08f82d0SHuang Ying 
596a08f82d0SHuang Ying 	return rc;
597a08f82d0SHuang Ying }
598885b976fSHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_id_next);
599885b976fSHuang Ying 
600885b976fSHuang Ying /* erst_record_id_cache.lock must be held by caller */
601885b976fSHuang Ying static void __erst_record_id_cache_compact(void)
602885b976fSHuang Ying {
603885b976fSHuang Ying 	int i, wpos = 0;
604885b976fSHuang Ying 	u64 *entries;
605885b976fSHuang Ying 
606885b976fSHuang Ying 	if (erst_record_id_cache.refcount)
607885b976fSHuang Ying 		return;
608885b976fSHuang Ying 
609885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
610885b976fSHuang Ying 	for (i = 0; i < erst_record_id_cache.len; i++) {
611885b976fSHuang Ying 		if (entries[i] == APEI_ERST_INVALID_RECORD_ID)
612885b976fSHuang Ying 			continue;
613885b976fSHuang Ying 		if (wpos != i)
614885b976fSHuang Ying 			memcpy(&entries[wpos], &entries[i], sizeof(entries[i]));
615885b976fSHuang Ying 		wpos++;
616885b976fSHuang Ying 	}
617885b976fSHuang Ying 	erst_record_id_cache.len = wpos;
618885b976fSHuang Ying }
619885b976fSHuang Ying 
620885b976fSHuang Ying void erst_get_record_id_end(void)
621885b976fSHuang Ying {
622885b976fSHuang Ying 	/*
623885b976fSHuang Ying 	 * erst_disable != 0 should be detected by invoker via the
624885b976fSHuang Ying 	 * return value of erst_get_record_id_begin/next, so this
625885b976fSHuang Ying 	 * function should not be called for erst_disable != 0.
626885b976fSHuang Ying 	 */
627885b976fSHuang Ying 	BUG_ON(erst_disable);
628885b976fSHuang Ying 
629885b976fSHuang Ying 	mutex_lock(&erst_record_id_cache.lock);
630885b976fSHuang Ying 	erst_record_id_cache.refcount--;
631885b976fSHuang Ying 	BUG_ON(erst_record_id_cache.refcount < 0);
632885b976fSHuang Ying 	__erst_record_id_cache_compact();
633885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
634885b976fSHuang Ying }
635885b976fSHuang Ying EXPORT_SYMBOL_GPL(erst_get_record_id_end);
636a08f82d0SHuang Ying 
637a08f82d0SHuang Ying static int __erst_write_to_storage(u64 offset)
638a08f82d0SHuang Ying {
639a08f82d0SHuang Ying 	struct apei_exec_context ctx;
640a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
641a08f82d0SHuang Ying 	u64 val;
642a08f82d0SHuang Ying 	int rc;
643a08f82d0SHuang Ying 
644a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
645392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_WRITE);
646a08f82d0SHuang Ying 	if (rc)
647a08f82d0SHuang Ying 		return rc;
648a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, offset);
649a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET);
650a08f82d0SHuang Ying 	if (rc)
651a08f82d0SHuang Ying 		return rc;
652a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
653a08f82d0SHuang Ying 	if (rc)
654a08f82d0SHuang Ying 		return rc;
655a08f82d0SHuang Ying 	for (;;) {
656a08f82d0SHuang Ying 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
657a08f82d0SHuang Ying 		if (rc)
658a08f82d0SHuang Ying 			return rc;
659a08f82d0SHuang Ying 		val = apei_exec_ctx_get_output(&ctx);
660a08f82d0SHuang Ying 		if (!val)
661a08f82d0SHuang Ying 			break;
662a08f82d0SHuang Ying 		if (erst_timedout(&timeout, SPIN_UNIT))
663a08f82d0SHuang Ying 			return -EIO;
664a08f82d0SHuang Ying 	}
665a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
666a08f82d0SHuang Ying 	if (rc)
667a08f82d0SHuang Ying 		return rc;
668a08f82d0SHuang Ying 	val = apei_exec_ctx_get_output(&ctx);
669392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
670a08f82d0SHuang Ying 	if (rc)
671a08f82d0SHuang Ying 		return rc;
672a08f82d0SHuang Ying 
673a08f82d0SHuang Ying 	return erst_errno(val);
674a08f82d0SHuang Ying }
675a08f82d0SHuang Ying 
676a08f82d0SHuang Ying static int __erst_read_from_storage(u64 record_id, u64 offset)
677a08f82d0SHuang Ying {
678a08f82d0SHuang Ying 	struct apei_exec_context ctx;
679a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
680a08f82d0SHuang Ying 	u64 val;
681a08f82d0SHuang Ying 	int rc;
682a08f82d0SHuang Ying 
683a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
684392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_READ);
685a08f82d0SHuang Ying 	if (rc)
686a08f82d0SHuang Ying 		return rc;
687a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, offset);
688a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET);
689a08f82d0SHuang Ying 	if (rc)
690a08f82d0SHuang Ying 		return rc;
691a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, record_id);
692a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID);
693a08f82d0SHuang Ying 	if (rc)
694a08f82d0SHuang Ying 		return rc;
695a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
696a08f82d0SHuang Ying 	if (rc)
697a08f82d0SHuang Ying 		return rc;
698a08f82d0SHuang Ying 	for (;;) {
699a08f82d0SHuang Ying 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
700a08f82d0SHuang Ying 		if (rc)
701a08f82d0SHuang Ying 			return rc;
702a08f82d0SHuang Ying 		val = apei_exec_ctx_get_output(&ctx);
703a08f82d0SHuang Ying 		if (!val)
704a08f82d0SHuang Ying 			break;
705a08f82d0SHuang Ying 		if (erst_timedout(&timeout, SPIN_UNIT))
706a08f82d0SHuang Ying 			return -EIO;
707a08f82d0SHuang Ying 	};
708a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
709a08f82d0SHuang Ying 	if (rc)
710a08f82d0SHuang Ying 		return rc;
711a08f82d0SHuang Ying 	val = apei_exec_ctx_get_output(&ctx);
712392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
713a08f82d0SHuang Ying 	if (rc)
714a08f82d0SHuang Ying 		return rc;
715a08f82d0SHuang Ying 
716a08f82d0SHuang Ying 	return erst_errno(val);
717a08f82d0SHuang Ying }
718a08f82d0SHuang Ying 
719a08f82d0SHuang Ying static int __erst_clear_from_storage(u64 record_id)
720a08f82d0SHuang Ying {
721a08f82d0SHuang Ying 	struct apei_exec_context ctx;
722a08f82d0SHuang Ying 	u64 timeout = FIRMWARE_TIMEOUT;
723a08f82d0SHuang Ying 	u64 val;
724a08f82d0SHuang Ying 	int rc;
725a08f82d0SHuang Ying 
726a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
727392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_CLEAR);
728a08f82d0SHuang Ying 	if (rc)
729a08f82d0SHuang Ying 		return rc;
730a08f82d0SHuang Ying 	apei_exec_ctx_set_input(&ctx, record_id);
731a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID);
732a08f82d0SHuang Ying 	if (rc)
733a08f82d0SHuang Ying 		return rc;
734a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
735a08f82d0SHuang Ying 	if (rc)
736a08f82d0SHuang Ying 		return rc;
737a08f82d0SHuang Ying 	for (;;) {
738a08f82d0SHuang Ying 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
739a08f82d0SHuang Ying 		if (rc)
740a08f82d0SHuang Ying 			return rc;
741a08f82d0SHuang Ying 		val = apei_exec_ctx_get_output(&ctx);
742a08f82d0SHuang Ying 		if (!val)
743a08f82d0SHuang Ying 			break;
744a08f82d0SHuang Ying 		if (erst_timedout(&timeout, SPIN_UNIT))
745a08f82d0SHuang Ying 			return -EIO;
746a08f82d0SHuang Ying 	}
747a08f82d0SHuang Ying 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
748a08f82d0SHuang Ying 	if (rc)
749a08f82d0SHuang Ying 		return rc;
750a08f82d0SHuang Ying 	val = apei_exec_ctx_get_output(&ctx);
751392913deSHuang Ying 	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
752a08f82d0SHuang Ying 	if (rc)
753a08f82d0SHuang Ying 		return rc;
754a08f82d0SHuang Ying 
755a08f82d0SHuang Ying 	return erst_errno(val);
756a08f82d0SHuang Ying }
757a08f82d0SHuang Ying 
758a08f82d0SHuang Ying /* NVRAM ERST Error Log Address Range is not supported yet */
759a08f82d0SHuang Ying static void pr_unimpl_nvram(void)
760a08f82d0SHuang Ying {
761a08f82d0SHuang Ying 	if (printk_ratelimit())
762a08f82d0SHuang Ying 		pr_warning(ERST_PFX
763a08f82d0SHuang Ying 		"NVRAM ERST Log Address Range is not implemented yet\n");
764a08f82d0SHuang Ying }
765a08f82d0SHuang Ying 
766a08f82d0SHuang Ying static int __erst_write_to_nvram(const struct cper_record_header *record)
767a08f82d0SHuang Ying {
768a08f82d0SHuang Ying 	/* do not print message, because printk is not safe for NMI */
769a08f82d0SHuang Ying 	return -ENOSYS;
770a08f82d0SHuang Ying }
771a08f82d0SHuang Ying 
772a08f82d0SHuang Ying static int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset)
773a08f82d0SHuang Ying {
774a08f82d0SHuang Ying 	pr_unimpl_nvram();
775a08f82d0SHuang Ying 	return -ENOSYS;
776a08f82d0SHuang Ying }
777a08f82d0SHuang Ying 
778a08f82d0SHuang Ying static int __erst_clear_from_nvram(u64 record_id)
779a08f82d0SHuang Ying {
780a08f82d0SHuang Ying 	pr_unimpl_nvram();
781a08f82d0SHuang Ying 	return -ENOSYS;
782a08f82d0SHuang Ying }
783a08f82d0SHuang Ying 
784a08f82d0SHuang Ying int erst_write(const struct cper_record_header *record)
785a08f82d0SHuang Ying {
786a08f82d0SHuang Ying 	int rc;
787a08f82d0SHuang Ying 	unsigned long flags;
788a08f82d0SHuang Ying 	struct cper_record_header *rcd_erange;
789a08f82d0SHuang Ying 
790a08f82d0SHuang Ying 	if (erst_disable)
791a08f82d0SHuang Ying 		return -ENODEV;
792a08f82d0SHuang Ying 
793a08f82d0SHuang Ying 	if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE))
794a08f82d0SHuang Ying 		return -EINVAL;
795a08f82d0SHuang Ying 
796a08f82d0SHuang Ying 	if (erst_erange.attr & ERST_RANGE_NVRAM) {
7973b38bb5fSHuang Ying 		if (!raw_spin_trylock_irqsave(&erst_lock, flags))
798a08f82d0SHuang Ying 			return -EBUSY;
799a08f82d0SHuang Ying 		rc = __erst_write_to_nvram(record);
8003b38bb5fSHuang Ying 		raw_spin_unlock_irqrestore(&erst_lock, flags);
801a08f82d0SHuang Ying 		return rc;
802a08f82d0SHuang Ying 	}
803a08f82d0SHuang Ying 
804a08f82d0SHuang Ying 	if (record->record_length > erst_erange.size)
805a08f82d0SHuang Ying 		return -EINVAL;
806a08f82d0SHuang Ying 
8073b38bb5fSHuang Ying 	if (!raw_spin_trylock_irqsave(&erst_lock, flags))
808a08f82d0SHuang Ying 		return -EBUSY;
809a08f82d0SHuang Ying 	memcpy(erst_erange.vaddr, record, record->record_length);
810a08f82d0SHuang Ying 	rcd_erange = erst_erange.vaddr;
811a08f82d0SHuang Ying 	/* signature for serialization system */
812a08f82d0SHuang Ying 	memcpy(&rcd_erange->persistence_information, "ER", 2);
813a08f82d0SHuang Ying 
814a08f82d0SHuang Ying 	rc = __erst_write_to_storage(0);
8153b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
816a08f82d0SHuang Ying 
817a08f82d0SHuang Ying 	return rc;
818a08f82d0SHuang Ying }
819a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_write);
820a08f82d0SHuang Ying 
821a08f82d0SHuang Ying static int __erst_read_to_erange(u64 record_id, u64 *offset)
822a08f82d0SHuang Ying {
823a08f82d0SHuang Ying 	int rc;
824a08f82d0SHuang Ying 
825a08f82d0SHuang Ying 	if (erst_erange.attr & ERST_RANGE_NVRAM)
826a08f82d0SHuang Ying 		return __erst_read_to_erange_from_nvram(
827a08f82d0SHuang Ying 			record_id, offset);
828a08f82d0SHuang Ying 
829a08f82d0SHuang Ying 	rc = __erst_read_from_storage(record_id, 0);
830a08f82d0SHuang Ying 	if (rc)
831a08f82d0SHuang Ying 		return rc;
832a08f82d0SHuang Ying 	*offset = 0;
833a08f82d0SHuang Ying 
834a08f82d0SHuang Ying 	return 0;
835a08f82d0SHuang Ying }
836a08f82d0SHuang Ying 
837a08f82d0SHuang Ying static ssize_t __erst_read(u64 record_id, struct cper_record_header *record,
838a08f82d0SHuang Ying 			   size_t buflen)
839a08f82d0SHuang Ying {
840a08f82d0SHuang Ying 	int rc;
841a08f82d0SHuang Ying 	u64 offset, len = 0;
842a08f82d0SHuang Ying 	struct cper_record_header *rcd_tmp;
843a08f82d0SHuang Ying 
844a08f82d0SHuang Ying 	rc = __erst_read_to_erange(record_id, &offset);
845a08f82d0SHuang Ying 	if (rc)
846a08f82d0SHuang Ying 		return rc;
847a08f82d0SHuang Ying 	rcd_tmp = erst_erange.vaddr + offset;
848a08f82d0SHuang Ying 	len = rcd_tmp->record_length;
849a08f82d0SHuang Ying 	if (len <= buflen)
850a08f82d0SHuang Ying 		memcpy(record, rcd_tmp, len);
851a08f82d0SHuang Ying 
852a08f82d0SHuang Ying 	return len;
853a08f82d0SHuang Ying }
854a08f82d0SHuang Ying 
855a08f82d0SHuang Ying /*
856a08f82d0SHuang Ying  * If return value > buflen, the buffer size is not big enough,
857a08f82d0SHuang Ying  * else if return value < 0, something goes wrong,
858a08f82d0SHuang Ying  * else everything is OK, and return value is record length
859a08f82d0SHuang Ying  */
860a08f82d0SHuang Ying ssize_t erst_read(u64 record_id, struct cper_record_header *record,
861a08f82d0SHuang Ying 		  size_t buflen)
862a08f82d0SHuang Ying {
863a08f82d0SHuang Ying 	ssize_t len;
864a08f82d0SHuang Ying 	unsigned long flags;
865a08f82d0SHuang Ying 
866a08f82d0SHuang Ying 	if (erst_disable)
867a08f82d0SHuang Ying 		return -ENODEV;
868a08f82d0SHuang Ying 
8693b38bb5fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
870a08f82d0SHuang Ying 	len = __erst_read(record_id, record, buflen);
8713b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
872a08f82d0SHuang Ying 	return len;
873a08f82d0SHuang Ying }
874a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_read);
875a08f82d0SHuang Ying 
876a08f82d0SHuang Ying int erst_clear(u64 record_id)
877a08f82d0SHuang Ying {
878885b976fSHuang Ying 	int rc, i;
879a08f82d0SHuang Ying 	unsigned long flags;
880885b976fSHuang Ying 	u64 *entries;
881a08f82d0SHuang Ying 
882a08f82d0SHuang Ying 	if (erst_disable)
883a08f82d0SHuang Ying 		return -ENODEV;
884a08f82d0SHuang Ying 
885885b976fSHuang Ying 	rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
886885b976fSHuang Ying 	if (rc)
887885b976fSHuang Ying 		return rc;
8883b38bb5fSHuang Ying 	raw_spin_lock_irqsave(&erst_lock, flags);
889a08f82d0SHuang Ying 	if (erst_erange.attr & ERST_RANGE_NVRAM)
890a08f82d0SHuang Ying 		rc = __erst_clear_from_nvram(record_id);
891a08f82d0SHuang Ying 	else
892a08f82d0SHuang Ying 		rc = __erst_clear_from_storage(record_id);
8933b38bb5fSHuang Ying 	raw_spin_unlock_irqrestore(&erst_lock, flags);
894885b976fSHuang Ying 	if (rc)
895885b976fSHuang Ying 		goto out;
896885b976fSHuang Ying 	entries = erst_record_id_cache.entries;
897885b976fSHuang Ying 	for (i = 0; i < erst_record_id_cache.len; i++) {
898885b976fSHuang Ying 		if (entries[i] == record_id)
899885b976fSHuang Ying 			entries[i] = APEI_ERST_INVALID_RECORD_ID;
900885b976fSHuang Ying 	}
901885b976fSHuang Ying 	__erst_record_id_cache_compact();
902885b976fSHuang Ying out:
903885b976fSHuang Ying 	mutex_unlock(&erst_record_id_cache.lock);
904a08f82d0SHuang Ying 	return rc;
905a08f82d0SHuang Ying }
906a08f82d0SHuang Ying EXPORT_SYMBOL_GPL(erst_clear);
907a08f82d0SHuang Ying 
908a08f82d0SHuang Ying static int __init setup_erst_disable(char *str)
909a08f82d0SHuang Ying {
910a08f82d0SHuang Ying 	erst_disable = 1;
911a08f82d0SHuang Ying 	return 0;
912a08f82d0SHuang Ying }
913a08f82d0SHuang Ying 
914a08f82d0SHuang Ying __setup("erst_disable", setup_erst_disable);
915a08f82d0SHuang Ying 
916a08f82d0SHuang Ying static int erst_check_table(struct acpi_table_erst *erst_tab)
917a08f82d0SHuang Ying {
9183a78f965SHuang Ying 	if ((erst_tab->header_length !=
9193a78f965SHuang Ying 	     (sizeof(struct acpi_table_erst) - sizeof(erst_tab->header)))
9203a78f965SHuang Ying 	    && (erst_tab->header_length != sizeof(struct acpi_table_einj)))
921a08f82d0SHuang Ying 		return -EINVAL;
922a08f82d0SHuang Ying 	if (erst_tab->header.length < sizeof(struct acpi_table_erst))
923a08f82d0SHuang Ying 		return -EINVAL;
924a08f82d0SHuang Ying 	if (erst_tab->entries !=
925a08f82d0SHuang Ying 	    (erst_tab->header.length - sizeof(struct acpi_table_erst)) /
926a08f82d0SHuang Ying 	    sizeof(struct acpi_erst_entry))
927a08f82d0SHuang Ying 		return -EINVAL;
928a08f82d0SHuang Ying 
929a08f82d0SHuang Ying 	return 0;
930a08f82d0SHuang Ying }
931a08f82d0SHuang Ying 
93206cf91b4SChen Gong static int erst_open_pstore(struct pstore_info *psi);
93306cf91b4SChen Gong static int erst_close_pstore(struct pstore_info *psi);
9348d38d74bSChen Gong static ssize_t erst_reader(u64 *id, enum pstore_type_id *type,
935f6f82851SKees Cook 			   struct timespec *time, char **buf,
936f6f82851SKees Cook 			   struct pstore_info *psi);
937b238b8faSChen Gong static int erst_writer(enum pstore_type_id type, u64 *id, unsigned int part,
938b94fdd07SMatthew Garrett 		       size_t size, struct pstore_info *psi);
93956280682SMatthew Garrett static int erst_clearer(enum pstore_type_id type, u64 id,
94056280682SMatthew Garrett 			struct pstore_info *psi);
9410bb77c46STony Luck 
9420bb77c46STony Luck static struct pstore_info erst_info = {
9430bb77c46STony Luck 	.owner		= THIS_MODULE,
9440bb77c46STony Luck 	.name		= "erst",
94506cf91b4SChen Gong 	.open		= erst_open_pstore,
94606cf91b4SChen Gong 	.close		= erst_close_pstore,
9470bb77c46STony Luck 	.read		= erst_reader,
9480bb77c46STony Luck 	.write		= erst_writer,
949638c1fd3SMatthew Garrett 	.erase		= erst_clearer
9500bb77c46STony Luck };
9510bb77c46STony Luck 
9520bb77c46STony Luck #define CPER_CREATOR_PSTORE						\
9530bb77c46STony Luck 	UUID_LE(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c,	\
9540bb77c46STony Luck 		0x64, 0x90, 0xb8, 0x9d)
9550bb77c46STony Luck #define CPER_SECTION_TYPE_DMESG						\
9560bb77c46STony Luck 	UUID_LE(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54,	\
9570bb77c46STony Luck 		0x94, 0x19, 0xeb, 0x12)
9580bb77c46STony Luck #define CPER_SECTION_TYPE_MCE						\
9590bb77c46STony Luck 	UUID_LE(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96,	\
9600bb77c46STony Luck 		0x04, 0x4a, 0x38, 0xfc)
9610bb77c46STony Luck 
9620bb77c46STony Luck struct cper_pstore_record {
9630bb77c46STony Luck 	struct cper_record_header hdr;
9640bb77c46STony Luck 	struct cper_section_descriptor sec_hdr;
9650bb77c46STony Luck 	char data[];
9660bb77c46STony Luck } __packed;
9670bb77c46STony Luck 
96806cf91b4SChen Gong static int reader_pos;
96906cf91b4SChen Gong 
97006cf91b4SChen Gong static int erst_open_pstore(struct pstore_info *psi)
97106cf91b4SChen Gong {
97206cf91b4SChen Gong 	int rc;
97306cf91b4SChen Gong 
97406cf91b4SChen Gong 	if (erst_disable)
97506cf91b4SChen Gong 		return -ENODEV;
97606cf91b4SChen Gong 
97706cf91b4SChen Gong 	rc = erst_get_record_id_begin(&reader_pos);
97806cf91b4SChen Gong 
97906cf91b4SChen Gong 	return rc;
98006cf91b4SChen Gong }
98106cf91b4SChen Gong 
98206cf91b4SChen Gong static int erst_close_pstore(struct pstore_info *psi)
98306cf91b4SChen Gong {
98406cf91b4SChen Gong 	erst_get_record_id_end();
98506cf91b4SChen Gong 
98606cf91b4SChen Gong 	return 0;
98706cf91b4SChen Gong }
98806cf91b4SChen Gong 
9898d38d74bSChen Gong static ssize_t erst_reader(u64 *id, enum pstore_type_id *type,
990f6f82851SKees Cook 			   struct timespec *time, char **buf,
991f6f82851SKees Cook 			   struct pstore_info *psi)
9920bb77c46STony Luck {
9930bb77c46STony Luck 	int rc;
99406cf91b4SChen Gong 	ssize_t len = 0;
9950bb77c46STony Luck 	u64 record_id;
996f6f82851SKees Cook 	struct cper_pstore_record *rcd;
997f6f82851SKees Cook 	size_t rcd_len = sizeof(*rcd) + erst_info.bufsize;
9980bb77c46STony Luck 
9990bb77c46STony Luck 	if (erst_disable)
10000bb77c46STony Luck 		return -ENODEV;
10010bb77c46STony Luck 
1002f6f82851SKees Cook 	rcd = kmalloc(rcd_len, GFP_KERNEL);
1003f6f82851SKees Cook 	if (!rcd) {
1004f6f82851SKees Cook 		rc = -ENOMEM;
1005f6f82851SKees Cook 		goto out;
1006f6f82851SKees Cook 	}
10070bb77c46STony Luck skip:
100806cf91b4SChen Gong 	rc = erst_get_record_id_next(&reader_pos, &record_id);
100906cf91b4SChen Gong 	if (rc)
101006cf91b4SChen Gong 		goto out;
101106cf91b4SChen Gong 
10120bb77c46STony Luck 	/* no more record */
10130bb77c46STony Luck 	if (record_id == APEI_ERST_INVALID_RECORD_ID) {
1014f6f82851SKees Cook 		rc = -EINVAL;
101506cf91b4SChen Gong 		goto out;
10160bb77c46STony Luck 	}
10170bb77c46STony Luck 
1018f6f82851SKees Cook 	len = erst_read(record_id, &rcd->hdr, rcd_len);
1019f5ec25deSChen Gong 	/* The record may be cleared by others, try read next record */
1020f5ec25deSChen Gong 	if (len == -ENOENT)
1021f5ec25deSChen Gong 		goto skip;
1022f6f82851SKees Cook 	else if (len < sizeof(*rcd)) {
1023f6f82851SKees Cook 		rc = -EIO;
1024f5ec25deSChen Gong 		goto out;
1025f5ec25deSChen Gong 	}
10260bb77c46STony Luck 	if (uuid_le_cmp(rcd->hdr.creator_id, CPER_CREATOR_PSTORE) != 0)
10270bb77c46STony Luck 		goto skip;
10280bb77c46STony Luck 
1029f6f82851SKees Cook 	*buf = kmalloc(len, GFP_KERNEL);
1030f6f82851SKees Cook 	if (*buf == NULL) {
1031f6f82851SKees Cook 		rc = -ENOMEM;
1032f6f82851SKees Cook 		goto out;
1033f6f82851SKees Cook 	}
1034f6f82851SKees Cook 	memcpy(*buf, rcd->data, len - sizeof(*rcd));
10350bb77c46STony Luck 	*id = record_id;
10360bb77c46STony Luck 	if (uuid_le_cmp(rcd->sec_hdr.section_type,
10370bb77c46STony Luck 			CPER_SECTION_TYPE_DMESG) == 0)
10380bb77c46STony Luck 		*type = PSTORE_TYPE_DMESG;
10390bb77c46STony Luck 	else if (uuid_le_cmp(rcd->sec_hdr.section_type,
10400bb77c46STony Luck 			     CPER_SECTION_TYPE_MCE) == 0)
10410bb77c46STony Luck 		*type = PSTORE_TYPE_MCE;
10420bb77c46STony Luck 	else
10430bb77c46STony Luck 		*type = PSTORE_TYPE_UNKNOWN;
10440bb77c46STony Luck 
10450bb77c46STony Luck 	if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP)
10460bb77c46STony Luck 		time->tv_sec = rcd->hdr.timestamp;
10470bb77c46STony Luck 	else
10480bb77c46STony Luck 		time->tv_sec = 0;
10490bb77c46STony Luck 	time->tv_nsec = 0;
10500bb77c46STony Luck 
105106cf91b4SChen Gong out:
1052f6f82851SKees Cook 	kfree(rcd);
105306cf91b4SChen Gong 	return (rc < 0) ? rc : (len - sizeof(*rcd));
10540bb77c46STony Luck }
10550bb77c46STony Luck 
1056b238b8faSChen Gong static int erst_writer(enum pstore_type_id type, u64 *id, unsigned int part,
1057b94fdd07SMatthew Garrett 		       size_t size, struct pstore_info *psi)
10580bb77c46STony Luck {
10590bb77c46STony Luck 	struct cper_pstore_record *rcd = (struct cper_pstore_record *)
10600bb77c46STony Luck 					(erst_info.buf - sizeof(*rcd));
1061b238b8faSChen Gong 	int ret;
10620bb77c46STony Luck 
10630bb77c46STony Luck 	memset(rcd, 0, sizeof(*rcd));
10640bb77c46STony Luck 	memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE);
10650bb77c46STony Luck 	rcd->hdr.revision = CPER_RECORD_REV;
10660bb77c46STony Luck 	rcd->hdr.signature_end = CPER_SIG_END;
10670bb77c46STony Luck 	rcd->hdr.section_count = 1;
10680bb77c46STony Luck 	rcd->hdr.error_severity = CPER_SEV_FATAL;
10690bb77c46STony Luck 	/* timestamp valid. platform_id, partition_id are invalid */
10700bb77c46STony Luck 	rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP;
10710bb77c46STony Luck 	rcd->hdr.timestamp = get_seconds();
10720bb77c46STony Luck 	rcd->hdr.record_length = sizeof(*rcd) + size;
10730bb77c46STony Luck 	rcd->hdr.creator_id = CPER_CREATOR_PSTORE;
10740bb77c46STony Luck 	rcd->hdr.notification_type = CPER_NOTIFY_MCE;
10750bb77c46STony Luck 	rcd->hdr.record_id = cper_next_record_id();
10760bb77c46STony Luck 	rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR;
10770bb77c46STony Luck 
10780bb77c46STony Luck 	rcd->sec_hdr.section_offset = sizeof(*rcd);
10790bb77c46STony Luck 	rcd->sec_hdr.section_length = size;
10800bb77c46STony Luck 	rcd->sec_hdr.revision = CPER_SEC_REV;
10810bb77c46STony Luck 	/* fru_id and fru_text is invalid */
10820bb77c46STony Luck 	rcd->sec_hdr.validation_bits = 0;
10830bb77c46STony Luck 	rcd->sec_hdr.flags = CPER_SEC_PRIMARY;
10840bb77c46STony Luck 	switch (type) {
10850bb77c46STony Luck 	case PSTORE_TYPE_DMESG:
10860bb77c46STony Luck 		rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG;
10870bb77c46STony Luck 		break;
10880bb77c46STony Luck 	case PSTORE_TYPE_MCE:
10890bb77c46STony Luck 		rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE;
10900bb77c46STony Luck 		break;
10910bb77c46STony Luck 	default:
10920bb77c46STony Luck 		return -EINVAL;
10930bb77c46STony Luck 	}
10940bb77c46STony Luck 	rcd->sec_hdr.section_severity = CPER_SEV_FATAL;
10950bb77c46STony Luck 
1096b238b8faSChen Gong 	ret = erst_write(&rcd->hdr);
1097b238b8faSChen Gong 	*id = rcd->hdr.record_id;
10980bb77c46STony Luck 
1099b238b8faSChen Gong 	return ret;
11000bb77c46STony Luck }
11010bb77c46STony Luck 
110256280682SMatthew Garrett static int erst_clearer(enum pstore_type_id type, u64 id,
110356280682SMatthew Garrett 			struct pstore_info *psi)
1104638c1fd3SMatthew Garrett {
1105638c1fd3SMatthew Garrett 	return erst_clear(id);
1106638c1fd3SMatthew Garrett }
1107638c1fd3SMatthew Garrett 
1108a08f82d0SHuang Ying static int __init erst_init(void)
1109a08f82d0SHuang Ying {
1110a08f82d0SHuang Ying 	int rc = 0;
1111a08f82d0SHuang Ying 	acpi_status status;
1112a08f82d0SHuang Ying 	struct apei_exec_context ctx;
1113a08f82d0SHuang Ying 	struct apei_resources erst_resources;
1114a08f82d0SHuang Ying 	struct resource *r;
11150bb77c46STony Luck 	char *buf;
1116a08f82d0SHuang Ying 
1117a08f82d0SHuang Ying 	if (acpi_disabled)
1118a08f82d0SHuang Ying 		goto err;
1119a08f82d0SHuang Ying 
1120a08f82d0SHuang Ying 	if (erst_disable) {
1121a08f82d0SHuang Ying 		pr_info(ERST_PFX
1122a08f82d0SHuang Ying 	"Error Record Serialization Table (ERST) support is disabled.\n");
1123a08f82d0SHuang Ying 		goto err;
1124a08f82d0SHuang Ying 	}
1125a08f82d0SHuang Ying 
1126a08f82d0SHuang Ying 	status = acpi_get_table(ACPI_SIG_ERST, 0,
1127a08f82d0SHuang Ying 				(struct acpi_table_header **)&erst_tab);
1128ad686154SHuang Ying 	if (status == AE_NOT_FOUND)
1129a08f82d0SHuang Ying 		goto err;
1130ad686154SHuang Ying 	else if (ACPI_FAILURE(status)) {
1131a08f82d0SHuang Ying 		const char *msg = acpi_format_exception(status);
1132a08f82d0SHuang Ying 		pr_err(ERST_PFX "Failed to get table, %s\n", msg);
1133a08f82d0SHuang Ying 		rc = -EINVAL;
1134a08f82d0SHuang Ying 		goto err;
1135a08f82d0SHuang Ying 	}
1136a08f82d0SHuang Ying 
1137a08f82d0SHuang Ying 	rc = erst_check_table(erst_tab);
1138a08f82d0SHuang Ying 	if (rc) {
1139a08f82d0SHuang Ying 		pr_err(FW_BUG ERST_PFX "ERST table is invalid\n");
1140a08f82d0SHuang Ying 		goto err;
1141a08f82d0SHuang Ying 	}
1142a08f82d0SHuang Ying 
1143a08f82d0SHuang Ying 	apei_resources_init(&erst_resources);
1144a08f82d0SHuang Ying 	erst_exec_ctx_init(&ctx);
1145a08f82d0SHuang Ying 	rc = apei_exec_collect_resources(&ctx, &erst_resources);
1146a08f82d0SHuang Ying 	if (rc)
1147a08f82d0SHuang Ying 		goto err_fini;
1148a08f82d0SHuang Ying 	rc = apei_resources_request(&erst_resources, "APEI ERST");
1149a08f82d0SHuang Ying 	if (rc)
1150a08f82d0SHuang Ying 		goto err_fini;
1151a08f82d0SHuang Ying 	rc = apei_exec_pre_map_gars(&ctx);
1152a08f82d0SHuang Ying 	if (rc)
1153a08f82d0SHuang Ying 		goto err_release;
1154a08f82d0SHuang Ying 	rc = erst_get_erange(&erst_erange);
1155a08f82d0SHuang Ying 	if (rc) {
1156a08f82d0SHuang Ying 		if (rc == -ENODEV)
1157a08f82d0SHuang Ying 			pr_info(ERST_PFX
1158a08f82d0SHuang Ying 	"The corresponding hardware device or firmware implementation "
1159a08f82d0SHuang Ying 	"is not available.\n");
1160a08f82d0SHuang Ying 		else
1161a08f82d0SHuang Ying 			pr_err(ERST_PFX
1162a08f82d0SHuang Ying 			       "Failed to get Error Log Address Range.\n");
1163a08f82d0SHuang Ying 		goto err_unmap_reg;
1164a08f82d0SHuang Ying 	}
1165a08f82d0SHuang Ying 
1166a08f82d0SHuang Ying 	r = request_mem_region(erst_erange.base, erst_erange.size, "APEI ERST");
1167a08f82d0SHuang Ying 	if (!r) {
1168a08f82d0SHuang Ying 		pr_err(ERST_PFX
1169a08f82d0SHuang Ying 		"Can not request iomem region <0x%16llx-0x%16llx> for ERST.\n",
1170a08f82d0SHuang Ying 		(unsigned long long)erst_erange.base,
1171a08f82d0SHuang Ying 		(unsigned long long)erst_erange.base + erst_erange.size);
1172a08f82d0SHuang Ying 		rc = -EIO;
1173a08f82d0SHuang Ying 		goto err_unmap_reg;
1174a08f82d0SHuang Ying 	}
1175a08f82d0SHuang Ying 	rc = -ENOMEM;
1176a08f82d0SHuang Ying 	erst_erange.vaddr = ioremap_cache(erst_erange.base,
1177a08f82d0SHuang Ying 					  erst_erange.size);
1178a08f82d0SHuang Ying 	if (!erst_erange.vaddr)
1179a08f82d0SHuang Ying 		goto err_release_erange;
1180a08f82d0SHuang Ying 
11810bb77c46STony Luck 	buf = kmalloc(erst_erange.size, GFP_KERNEL);
1182abd4d558SDon Zickus 	spin_lock_init(&erst_info.buf_lock);
11830bb77c46STony Luck 	if (buf) {
11840bb77c46STony Luck 		erst_info.buf = buf + sizeof(struct cper_pstore_record);
11850bb77c46STony Luck 		erst_info.bufsize = erst_erange.size -
11860bb77c46STony Luck 				    sizeof(struct cper_pstore_record);
11870bb77c46STony Luck 		if (pstore_register(&erst_info)) {
11880bb77c46STony Luck 			pr_info(ERST_PFX "Could not register with persistent store\n");
11890bb77c46STony Luck 			kfree(buf);
11900bb77c46STony Luck 		}
11910bb77c46STony Luck 	}
11920bb77c46STony Luck 
1193a08f82d0SHuang Ying 	pr_info(ERST_PFX
1194a08f82d0SHuang Ying 	"Error Record Serialization Table (ERST) support is initialized.\n");
1195a08f82d0SHuang Ying 
1196a08f82d0SHuang Ying 	return 0;
1197a08f82d0SHuang Ying 
1198a08f82d0SHuang Ying err_release_erange:
1199a08f82d0SHuang Ying 	release_mem_region(erst_erange.base, erst_erange.size);
1200a08f82d0SHuang Ying err_unmap_reg:
1201a08f82d0SHuang Ying 	apei_exec_post_unmap_gars(&ctx);
1202a08f82d0SHuang Ying err_release:
1203a08f82d0SHuang Ying 	apei_resources_release(&erst_resources);
1204a08f82d0SHuang Ying err_fini:
1205a08f82d0SHuang Ying 	apei_resources_fini(&erst_resources);
1206a08f82d0SHuang Ying err:
1207a08f82d0SHuang Ying 	erst_disable = 1;
1208a08f82d0SHuang Ying 	return rc;
1209a08f82d0SHuang Ying }
1210a08f82d0SHuang Ying 
1211a08f82d0SHuang Ying device_initcall(erst_init);
1212