162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * APEI Error Record Serialization Table support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * ERST is a way provided by APEI to save and retrieve hardware error
662306a36Sopenharmony_ci * information to and from a persistent store.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * For more information about ERST, please refer to ACPI Specification
962306a36Sopenharmony_ci * version 4.0, section 17.4.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Copyright 2010 Intel Corp.
1262306a36Sopenharmony_ci *   Author: Huang Ying <ying.huang@intel.com>
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/kernel.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/init.h>
1862306a36Sopenharmony_ci#include <linux/delay.h>
1962306a36Sopenharmony_ci#include <linux/io.h>
2062306a36Sopenharmony_ci#include <linux/acpi.h>
2162306a36Sopenharmony_ci#include <linux/uaccess.h>
2262306a36Sopenharmony_ci#include <linux/cper.h>
2362306a36Sopenharmony_ci#include <linux/nmi.h>
2462306a36Sopenharmony_ci#include <linux/hardirq.h>
2562306a36Sopenharmony_ci#include <linux/pstore.h>
2662306a36Sopenharmony_ci#include <linux/vmalloc.h>
2762306a36Sopenharmony_ci#include <linux/mm.h> /* kvfree() */
2862306a36Sopenharmony_ci#include <acpi/apei.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#include "apei-internal.h"
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#undef pr_fmt
3362306a36Sopenharmony_ci#define pr_fmt(fmt) "ERST: " fmt
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/* ERST command status */
3662306a36Sopenharmony_ci#define ERST_STATUS_SUCCESS			0x0
3762306a36Sopenharmony_ci#define ERST_STATUS_NOT_ENOUGH_SPACE		0x1
3862306a36Sopenharmony_ci#define ERST_STATUS_HARDWARE_NOT_AVAILABLE	0x2
3962306a36Sopenharmony_ci#define ERST_STATUS_FAILED			0x3
4062306a36Sopenharmony_ci#define ERST_STATUS_RECORD_STORE_EMPTY		0x4
4162306a36Sopenharmony_ci#define ERST_STATUS_RECORD_NOT_FOUND		0x5
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define ERST_TAB_ENTRY(tab)						\
4462306a36Sopenharmony_ci	((struct acpi_whea_header *)((char *)(tab) +			\
4562306a36Sopenharmony_ci				     sizeof(struct acpi_table_erst)))
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define SPIN_UNIT		100			/* 100ns */
4862306a36Sopenharmony_ci/* Firmware should respond within 1 milliseconds */
4962306a36Sopenharmony_ci#define FIRMWARE_TIMEOUT	(1 * NSEC_PER_MSEC)
5062306a36Sopenharmony_ci#define FIRMWARE_MAX_STALL	50			/* 50us */
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ciint erst_disable;
5362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_disable);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic struct acpi_table_erst *erst_tab;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/* ERST Error Log Address Range attributes */
5862306a36Sopenharmony_ci#define ERST_RANGE_RESERVED	0x0001
5962306a36Sopenharmony_ci#define ERST_RANGE_NVRAM	0x0002
6062306a36Sopenharmony_ci#define ERST_RANGE_SLOW		0x0004
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/*
6362306a36Sopenharmony_ci * ERST Error Log Address Range, used as buffer for reading/writing
6462306a36Sopenharmony_ci * error records.
6562306a36Sopenharmony_ci */
6662306a36Sopenharmony_cistatic struct erst_erange {
6762306a36Sopenharmony_ci	u64 base;
6862306a36Sopenharmony_ci	u64 size;
6962306a36Sopenharmony_ci	void __iomem *vaddr;
7062306a36Sopenharmony_ci	u32 attr;
7162306a36Sopenharmony_ci} erst_erange;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/*
7462306a36Sopenharmony_ci * Prevent ERST interpreter to run simultaneously, because the
7562306a36Sopenharmony_ci * corresponding firmware implementation may not work properly when
7662306a36Sopenharmony_ci * invoked simultaneously.
7762306a36Sopenharmony_ci *
7862306a36Sopenharmony_ci * It is used to provide exclusive accessing for ERST Error Log
7962306a36Sopenharmony_ci * Address Range too.
8062306a36Sopenharmony_ci */
8162306a36Sopenharmony_cistatic DEFINE_RAW_SPINLOCK(erst_lock);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic inline int erst_errno(int command_status)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	switch (command_status) {
8662306a36Sopenharmony_ci	case ERST_STATUS_SUCCESS:
8762306a36Sopenharmony_ci		return 0;
8862306a36Sopenharmony_ci	case ERST_STATUS_HARDWARE_NOT_AVAILABLE:
8962306a36Sopenharmony_ci		return -ENODEV;
9062306a36Sopenharmony_ci	case ERST_STATUS_NOT_ENOUGH_SPACE:
9162306a36Sopenharmony_ci		return -ENOSPC;
9262306a36Sopenharmony_ci	case ERST_STATUS_RECORD_STORE_EMPTY:
9362306a36Sopenharmony_ci	case ERST_STATUS_RECORD_NOT_FOUND:
9462306a36Sopenharmony_ci		return -ENOENT;
9562306a36Sopenharmony_ci	default:
9662306a36Sopenharmony_ci		return -EINVAL;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int erst_timedout(u64 *t, u64 spin_unit)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	if ((s64)*t < spin_unit) {
10362306a36Sopenharmony_ci		pr_warn(FW_WARN "Firmware does not respond in time.\n");
10462306a36Sopenharmony_ci		return 1;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci	*t -= spin_unit;
10762306a36Sopenharmony_ci	ndelay(spin_unit);
10862306a36Sopenharmony_ci	touch_nmi_watchdog();
10962306a36Sopenharmony_ci	return 0;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int erst_exec_load_var1(struct apei_exec_context *ctx,
11362306a36Sopenharmony_ci			       struct acpi_whea_header *entry)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	return __apei_exec_read_register(entry, &ctx->var1);
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int erst_exec_load_var2(struct apei_exec_context *ctx,
11962306a36Sopenharmony_ci			       struct acpi_whea_header *entry)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	return __apei_exec_read_register(entry, &ctx->var2);
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic int erst_exec_store_var1(struct apei_exec_context *ctx,
12562306a36Sopenharmony_ci				struct acpi_whea_header *entry)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	return __apei_exec_write_register(entry, ctx->var1);
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic int erst_exec_add(struct apei_exec_context *ctx,
13162306a36Sopenharmony_ci			 struct acpi_whea_header *entry)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	ctx->var1 += ctx->var2;
13462306a36Sopenharmony_ci	return 0;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int erst_exec_subtract(struct apei_exec_context *ctx,
13862306a36Sopenharmony_ci			      struct acpi_whea_header *entry)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	ctx->var1 -= ctx->var2;
14162306a36Sopenharmony_ci	return 0;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int erst_exec_add_value(struct apei_exec_context *ctx,
14562306a36Sopenharmony_ci			       struct acpi_whea_header *entry)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	int rc;
14862306a36Sopenharmony_ci	u64 val;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	rc = __apei_exec_read_register(entry, &val);
15162306a36Sopenharmony_ci	if (rc)
15262306a36Sopenharmony_ci		return rc;
15362306a36Sopenharmony_ci	val += ctx->value;
15462306a36Sopenharmony_ci	rc = __apei_exec_write_register(entry, val);
15562306a36Sopenharmony_ci	return rc;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic int erst_exec_subtract_value(struct apei_exec_context *ctx,
15962306a36Sopenharmony_ci				    struct acpi_whea_header *entry)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	int rc;
16262306a36Sopenharmony_ci	u64 val;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	rc = __apei_exec_read_register(entry, &val);
16562306a36Sopenharmony_ci	if (rc)
16662306a36Sopenharmony_ci		return rc;
16762306a36Sopenharmony_ci	val -= ctx->value;
16862306a36Sopenharmony_ci	rc = __apei_exec_write_register(entry, val);
16962306a36Sopenharmony_ci	return rc;
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic int erst_exec_stall(struct apei_exec_context *ctx,
17362306a36Sopenharmony_ci			   struct acpi_whea_header *entry)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	u64 stall_time;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (ctx->value > FIRMWARE_MAX_STALL) {
17862306a36Sopenharmony_ci		if (!in_nmi())
17962306a36Sopenharmony_ci			pr_warn(FW_WARN
18062306a36Sopenharmony_ci			"Too long stall time for stall instruction: 0x%llx.\n",
18162306a36Sopenharmony_ci				   ctx->value);
18262306a36Sopenharmony_ci		stall_time = FIRMWARE_MAX_STALL;
18362306a36Sopenharmony_ci	} else
18462306a36Sopenharmony_ci		stall_time = ctx->value;
18562306a36Sopenharmony_ci	udelay(stall_time);
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic int erst_exec_stall_while_true(struct apei_exec_context *ctx,
19062306a36Sopenharmony_ci				      struct acpi_whea_header *entry)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	int rc;
19362306a36Sopenharmony_ci	u64 val;
19462306a36Sopenharmony_ci	u64 timeout = FIRMWARE_TIMEOUT;
19562306a36Sopenharmony_ci	u64 stall_time;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	if (ctx->var1 > FIRMWARE_MAX_STALL) {
19862306a36Sopenharmony_ci		if (!in_nmi())
19962306a36Sopenharmony_ci			pr_warn(FW_WARN
20062306a36Sopenharmony_ci		"Too long stall time for stall while true instruction: 0x%llx.\n",
20162306a36Sopenharmony_ci				   ctx->var1);
20262306a36Sopenharmony_ci		stall_time = FIRMWARE_MAX_STALL;
20362306a36Sopenharmony_ci	} else
20462306a36Sopenharmony_ci		stall_time = ctx->var1;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	for (;;) {
20762306a36Sopenharmony_ci		rc = __apei_exec_read_register(entry, &val);
20862306a36Sopenharmony_ci		if (rc)
20962306a36Sopenharmony_ci			return rc;
21062306a36Sopenharmony_ci		if (val != ctx->value)
21162306a36Sopenharmony_ci			break;
21262306a36Sopenharmony_ci		if (erst_timedout(&timeout, stall_time * NSEC_PER_USEC))
21362306a36Sopenharmony_ci			return -EIO;
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci	return 0;
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic int erst_exec_skip_next_instruction_if_true(
21962306a36Sopenharmony_ci	struct apei_exec_context *ctx,
22062306a36Sopenharmony_ci	struct acpi_whea_header *entry)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	int rc;
22362306a36Sopenharmony_ci	u64 val;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	rc = __apei_exec_read_register(entry, &val);
22662306a36Sopenharmony_ci	if (rc)
22762306a36Sopenharmony_ci		return rc;
22862306a36Sopenharmony_ci	if (val == ctx->value) {
22962306a36Sopenharmony_ci		ctx->ip += 2;
23062306a36Sopenharmony_ci		return APEI_EXEC_SET_IP;
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	return 0;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic int erst_exec_goto(struct apei_exec_context *ctx,
23762306a36Sopenharmony_ci			  struct acpi_whea_header *entry)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	ctx->ip = ctx->value;
24062306a36Sopenharmony_ci	return APEI_EXEC_SET_IP;
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic int erst_exec_set_src_address_base(struct apei_exec_context *ctx,
24462306a36Sopenharmony_ci					  struct acpi_whea_header *entry)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	return __apei_exec_read_register(entry, &ctx->src_base);
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic int erst_exec_set_dst_address_base(struct apei_exec_context *ctx,
25062306a36Sopenharmony_ci					  struct acpi_whea_header *entry)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	return __apei_exec_read_register(entry, &ctx->dst_base);
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic int erst_exec_move_data(struct apei_exec_context *ctx,
25662306a36Sopenharmony_ci			       struct acpi_whea_header *entry)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	int rc;
25962306a36Sopenharmony_ci	u64 offset;
26062306a36Sopenharmony_ci	void *src, *dst;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	/* ioremap does not work in interrupt context */
26362306a36Sopenharmony_ci	if (in_interrupt()) {
26462306a36Sopenharmony_ci		pr_warn("MOVE_DATA can not be used in interrupt context.\n");
26562306a36Sopenharmony_ci		return -EBUSY;
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	rc = __apei_exec_read_register(entry, &offset);
26962306a36Sopenharmony_ci	if (rc)
27062306a36Sopenharmony_ci		return rc;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	src = ioremap(ctx->src_base + offset, ctx->var2);
27362306a36Sopenharmony_ci	if (!src)
27462306a36Sopenharmony_ci		return -ENOMEM;
27562306a36Sopenharmony_ci	dst = ioremap(ctx->dst_base + offset, ctx->var2);
27662306a36Sopenharmony_ci	if (!dst) {
27762306a36Sopenharmony_ci		iounmap(src);
27862306a36Sopenharmony_ci		return -ENOMEM;
27962306a36Sopenharmony_ci	}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	memmove(dst, src, ctx->var2);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	iounmap(src);
28462306a36Sopenharmony_ci	iounmap(dst);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	return 0;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic struct apei_exec_ins_type erst_ins_type[] = {
29062306a36Sopenharmony_ci	[ACPI_ERST_READ_REGISTER] = {
29162306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
29262306a36Sopenharmony_ci		.run = apei_exec_read_register,
29362306a36Sopenharmony_ci	},
29462306a36Sopenharmony_ci	[ACPI_ERST_READ_REGISTER_VALUE] = {
29562306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
29662306a36Sopenharmony_ci		.run = apei_exec_read_register_value,
29762306a36Sopenharmony_ci	},
29862306a36Sopenharmony_ci	[ACPI_ERST_WRITE_REGISTER] = {
29962306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
30062306a36Sopenharmony_ci		.run = apei_exec_write_register,
30162306a36Sopenharmony_ci	},
30262306a36Sopenharmony_ci	[ACPI_ERST_WRITE_REGISTER_VALUE] = {
30362306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
30462306a36Sopenharmony_ci		.run = apei_exec_write_register_value,
30562306a36Sopenharmony_ci	},
30662306a36Sopenharmony_ci	[ACPI_ERST_NOOP] = {
30762306a36Sopenharmony_ci		.flags = 0,
30862306a36Sopenharmony_ci		.run = apei_exec_noop,
30962306a36Sopenharmony_ci	},
31062306a36Sopenharmony_ci	[ACPI_ERST_LOAD_VAR1] = {
31162306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
31262306a36Sopenharmony_ci		.run = erst_exec_load_var1,
31362306a36Sopenharmony_ci	},
31462306a36Sopenharmony_ci	[ACPI_ERST_LOAD_VAR2] = {
31562306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
31662306a36Sopenharmony_ci		.run = erst_exec_load_var2,
31762306a36Sopenharmony_ci	},
31862306a36Sopenharmony_ci	[ACPI_ERST_STORE_VAR1] = {
31962306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
32062306a36Sopenharmony_ci		.run = erst_exec_store_var1,
32162306a36Sopenharmony_ci	},
32262306a36Sopenharmony_ci	[ACPI_ERST_ADD] = {
32362306a36Sopenharmony_ci		.flags = 0,
32462306a36Sopenharmony_ci		.run = erst_exec_add,
32562306a36Sopenharmony_ci	},
32662306a36Sopenharmony_ci	[ACPI_ERST_SUBTRACT] = {
32762306a36Sopenharmony_ci		.flags = 0,
32862306a36Sopenharmony_ci		.run = erst_exec_subtract,
32962306a36Sopenharmony_ci	},
33062306a36Sopenharmony_ci	[ACPI_ERST_ADD_VALUE] = {
33162306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
33262306a36Sopenharmony_ci		.run = erst_exec_add_value,
33362306a36Sopenharmony_ci	},
33462306a36Sopenharmony_ci	[ACPI_ERST_SUBTRACT_VALUE] = {
33562306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
33662306a36Sopenharmony_ci		.run = erst_exec_subtract_value,
33762306a36Sopenharmony_ci	},
33862306a36Sopenharmony_ci	[ACPI_ERST_STALL] = {
33962306a36Sopenharmony_ci		.flags = 0,
34062306a36Sopenharmony_ci		.run = erst_exec_stall,
34162306a36Sopenharmony_ci	},
34262306a36Sopenharmony_ci	[ACPI_ERST_STALL_WHILE_TRUE] = {
34362306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
34462306a36Sopenharmony_ci		.run = erst_exec_stall_while_true,
34562306a36Sopenharmony_ci	},
34662306a36Sopenharmony_ci	[ACPI_ERST_SKIP_NEXT_IF_TRUE] = {
34762306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
34862306a36Sopenharmony_ci		.run = erst_exec_skip_next_instruction_if_true,
34962306a36Sopenharmony_ci	},
35062306a36Sopenharmony_ci	[ACPI_ERST_GOTO] = {
35162306a36Sopenharmony_ci		.flags = 0,
35262306a36Sopenharmony_ci		.run = erst_exec_goto,
35362306a36Sopenharmony_ci	},
35462306a36Sopenharmony_ci	[ACPI_ERST_SET_SRC_ADDRESS_BASE] = {
35562306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
35662306a36Sopenharmony_ci		.run = erst_exec_set_src_address_base,
35762306a36Sopenharmony_ci	},
35862306a36Sopenharmony_ci	[ACPI_ERST_SET_DST_ADDRESS_BASE] = {
35962306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
36062306a36Sopenharmony_ci		.run = erst_exec_set_dst_address_base,
36162306a36Sopenharmony_ci	},
36262306a36Sopenharmony_ci	[ACPI_ERST_MOVE_DATA] = {
36362306a36Sopenharmony_ci		.flags = APEI_EXEC_INS_ACCESS_REGISTER,
36462306a36Sopenharmony_ci		.run = erst_exec_move_data,
36562306a36Sopenharmony_ci	},
36662306a36Sopenharmony_ci};
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cistatic inline void erst_exec_ctx_init(struct apei_exec_context *ctx)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	apei_exec_ctx_init(ctx, erst_ins_type, ARRAY_SIZE(erst_ins_type),
37162306a36Sopenharmony_ci			   ERST_TAB_ENTRY(erst_tab), erst_tab->entries);
37262306a36Sopenharmony_ci}
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_cistatic int erst_get_erange(struct erst_erange *range)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct apei_exec_context ctx;
37762306a36Sopenharmony_ci	int rc;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	erst_exec_ctx_init(&ctx);
38062306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_RANGE);
38162306a36Sopenharmony_ci	if (rc)
38262306a36Sopenharmony_ci		return rc;
38362306a36Sopenharmony_ci	range->base = apei_exec_ctx_get_output(&ctx);
38462306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_LENGTH);
38562306a36Sopenharmony_ci	if (rc)
38662306a36Sopenharmony_ci		return rc;
38762306a36Sopenharmony_ci	range->size = apei_exec_ctx_get_output(&ctx);
38862306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_ATTRIBUTES);
38962306a36Sopenharmony_ci	if (rc)
39062306a36Sopenharmony_ci		return rc;
39162306a36Sopenharmony_ci	range->attr = apei_exec_ctx_get_output(&ctx);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	return 0;
39462306a36Sopenharmony_ci}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_cistatic ssize_t __erst_get_record_count(void)
39762306a36Sopenharmony_ci{
39862306a36Sopenharmony_ci	struct apei_exec_context ctx;
39962306a36Sopenharmony_ci	int rc;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	erst_exec_ctx_init(&ctx);
40262306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_COUNT);
40362306a36Sopenharmony_ci	if (rc)
40462306a36Sopenharmony_ci		return rc;
40562306a36Sopenharmony_ci	return apei_exec_ctx_get_output(&ctx);
40662306a36Sopenharmony_ci}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_cissize_t erst_get_record_count(void)
40962306a36Sopenharmony_ci{
41062306a36Sopenharmony_ci	ssize_t count;
41162306a36Sopenharmony_ci	unsigned long flags;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	if (erst_disable)
41462306a36Sopenharmony_ci		return -ENODEV;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	raw_spin_lock_irqsave(&erst_lock, flags);
41762306a36Sopenharmony_ci	count = __erst_get_record_count();
41862306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&erst_lock, flags);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	return count;
42162306a36Sopenharmony_ci}
42262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_get_record_count);
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci#define ERST_RECORD_ID_CACHE_SIZE_MIN	16
42562306a36Sopenharmony_ci#define ERST_RECORD_ID_CACHE_SIZE_MAX	1024
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_cistruct erst_record_id_cache {
42862306a36Sopenharmony_ci	struct mutex lock;
42962306a36Sopenharmony_ci	u64 *entries;
43062306a36Sopenharmony_ci	int len;
43162306a36Sopenharmony_ci	int size;
43262306a36Sopenharmony_ci	int refcount;
43362306a36Sopenharmony_ci};
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_cistatic struct erst_record_id_cache erst_record_id_cache = {
43662306a36Sopenharmony_ci	.lock = __MUTEX_INITIALIZER(erst_record_id_cache.lock),
43762306a36Sopenharmony_ci	.refcount = 0,
43862306a36Sopenharmony_ci};
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_cistatic int __erst_get_next_record_id(u64 *record_id)
44162306a36Sopenharmony_ci{
44262306a36Sopenharmony_ci	struct apei_exec_context ctx;
44362306a36Sopenharmony_ci	int rc;
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	erst_exec_ctx_init(&ctx);
44662306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_ID);
44762306a36Sopenharmony_ci	if (rc)
44862306a36Sopenharmony_ci		return rc;
44962306a36Sopenharmony_ci	*record_id = apei_exec_ctx_get_output(&ctx);
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	return 0;
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ciint erst_get_record_id_begin(int *pos)
45562306a36Sopenharmony_ci{
45662306a36Sopenharmony_ci	int rc;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	if (erst_disable)
45962306a36Sopenharmony_ci		return -ENODEV;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
46262306a36Sopenharmony_ci	if (rc)
46362306a36Sopenharmony_ci		return rc;
46462306a36Sopenharmony_ci	erst_record_id_cache.refcount++;
46562306a36Sopenharmony_ci	mutex_unlock(&erst_record_id_cache.lock);
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	*pos = 0;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	return 0;
47062306a36Sopenharmony_ci}
47162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_get_record_id_begin);
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci/* erst_record_id_cache.lock must be held by caller */
47462306a36Sopenharmony_cistatic int __erst_record_id_cache_add_one(void)
47562306a36Sopenharmony_ci{
47662306a36Sopenharmony_ci	u64 id, prev_id, first_id;
47762306a36Sopenharmony_ci	int i, rc;
47862306a36Sopenharmony_ci	u64 *entries;
47962306a36Sopenharmony_ci	unsigned long flags;
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	id = prev_id = first_id = APEI_ERST_INVALID_RECORD_ID;
48262306a36Sopenharmony_ciretry:
48362306a36Sopenharmony_ci	raw_spin_lock_irqsave(&erst_lock, flags);
48462306a36Sopenharmony_ci	rc = __erst_get_next_record_id(&id);
48562306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&erst_lock, flags);
48662306a36Sopenharmony_ci	if (rc == -ENOENT)
48762306a36Sopenharmony_ci		return 0;
48862306a36Sopenharmony_ci	if (rc)
48962306a36Sopenharmony_ci		return rc;
49062306a36Sopenharmony_ci	if (id == APEI_ERST_INVALID_RECORD_ID)
49162306a36Sopenharmony_ci		return 0;
49262306a36Sopenharmony_ci	/* can not skip current ID, or loop back to first ID */
49362306a36Sopenharmony_ci	if (id == prev_id || id == first_id)
49462306a36Sopenharmony_ci		return 0;
49562306a36Sopenharmony_ci	if (first_id == APEI_ERST_INVALID_RECORD_ID)
49662306a36Sopenharmony_ci		first_id = id;
49762306a36Sopenharmony_ci	prev_id = id;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	entries = erst_record_id_cache.entries;
50062306a36Sopenharmony_ci	for (i = 0; i < erst_record_id_cache.len; i++) {
50162306a36Sopenharmony_ci		if (entries[i] == id)
50262306a36Sopenharmony_ci			break;
50362306a36Sopenharmony_ci	}
50462306a36Sopenharmony_ci	/* record id already in cache, try next */
50562306a36Sopenharmony_ci	if (i < erst_record_id_cache.len)
50662306a36Sopenharmony_ci		goto retry;
50762306a36Sopenharmony_ci	if (erst_record_id_cache.len >= erst_record_id_cache.size) {
50862306a36Sopenharmony_ci		int new_size;
50962306a36Sopenharmony_ci		u64 *new_entries;
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci		new_size = erst_record_id_cache.size * 2;
51262306a36Sopenharmony_ci		new_size = clamp_val(new_size, ERST_RECORD_ID_CACHE_SIZE_MIN,
51362306a36Sopenharmony_ci				     ERST_RECORD_ID_CACHE_SIZE_MAX);
51462306a36Sopenharmony_ci		if (new_size <= erst_record_id_cache.size) {
51562306a36Sopenharmony_ci			if (printk_ratelimit())
51662306a36Sopenharmony_ci				pr_warn(FW_WARN "too many record IDs!\n");
51762306a36Sopenharmony_ci			return 0;
51862306a36Sopenharmony_ci		}
51962306a36Sopenharmony_ci		new_entries = kvmalloc_array(new_size, sizeof(entries[0]),
52062306a36Sopenharmony_ci					     GFP_KERNEL);
52162306a36Sopenharmony_ci		if (!new_entries)
52262306a36Sopenharmony_ci			return -ENOMEM;
52362306a36Sopenharmony_ci		memcpy(new_entries, entries,
52462306a36Sopenharmony_ci		       erst_record_id_cache.len * sizeof(entries[0]));
52562306a36Sopenharmony_ci		kvfree(entries);
52662306a36Sopenharmony_ci		erst_record_id_cache.entries = entries = new_entries;
52762306a36Sopenharmony_ci		erst_record_id_cache.size = new_size;
52862306a36Sopenharmony_ci	}
52962306a36Sopenharmony_ci	entries[i] = id;
53062306a36Sopenharmony_ci	erst_record_id_cache.len++;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	return 1;
53362306a36Sopenharmony_ci}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci/*
53662306a36Sopenharmony_ci * Get the record ID of an existing error record on the persistent
53762306a36Sopenharmony_ci * storage. If there is no error record on the persistent storage, the
53862306a36Sopenharmony_ci * returned record_id is APEI_ERST_INVALID_RECORD_ID.
53962306a36Sopenharmony_ci */
54062306a36Sopenharmony_ciint erst_get_record_id_next(int *pos, u64 *record_id)
54162306a36Sopenharmony_ci{
54262306a36Sopenharmony_ci	int rc = 0;
54362306a36Sopenharmony_ci	u64 *entries;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	if (erst_disable)
54662306a36Sopenharmony_ci		return -ENODEV;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	/* must be enclosed by erst_get_record_id_begin/end */
54962306a36Sopenharmony_ci	BUG_ON(!erst_record_id_cache.refcount);
55062306a36Sopenharmony_ci	BUG_ON(*pos < 0 || *pos > erst_record_id_cache.len);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	mutex_lock(&erst_record_id_cache.lock);
55362306a36Sopenharmony_ci	entries = erst_record_id_cache.entries;
55462306a36Sopenharmony_ci	for (; *pos < erst_record_id_cache.len; (*pos)++)
55562306a36Sopenharmony_ci		if (entries[*pos] != APEI_ERST_INVALID_RECORD_ID)
55662306a36Sopenharmony_ci			break;
55762306a36Sopenharmony_ci	/* found next record id in cache */
55862306a36Sopenharmony_ci	if (*pos < erst_record_id_cache.len) {
55962306a36Sopenharmony_ci		*record_id = entries[*pos];
56062306a36Sopenharmony_ci		(*pos)++;
56162306a36Sopenharmony_ci		goto out_unlock;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	/* Try to add one more record ID to cache */
56562306a36Sopenharmony_ci	rc = __erst_record_id_cache_add_one();
56662306a36Sopenharmony_ci	if (rc < 0)
56762306a36Sopenharmony_ci		goto out_unlock;
56862306a36Sopenharmony_ci	/* successfully add one new ID */
56962306a36Sopenharmony_ci	if (rc == 1) {
57062306a36Sopenharmony_ci		*record_id = erst_record_id_cache.entries[*pos];
57162306a36Sopenharmony_ci		(*pos)++;
57262306a36Sopenharmony_ci		rc = 0;
57362306a36Sopenharmony_ci	} else {
57462306a36Sopenharmony_ci		*pos = -1;
57562306a36Sopenharmony_ci		*record_id = APEI_ERST_INVALID_RECORD_ID;
57662306a36Sopenharmony_ci	}
57762306a36Sopenharmony_ciout_unlock:
57862306a36Sopenharmony_ci	mutex_unlock(&erst_record_id_cache.lock);
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	return rc;
58162306a36Sopenharmony_ci}
58262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_get_record_id_next);
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci/* erst_record_id_cache.lock must be held by caller */
58562306a36Sopenharmony_cistatic void __erst_record_id_cache_compact(void)
58662306a36Sopenharmony_ci{
58762306a36Sopenharmony_ci	int i, wpos = 0;
58862306a36Sopenharmony_ci	u64 *entries;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	if (erst_record_id_cache.refcount)
59162306a36Sopenharmony_ci		return;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	entries = erst_record_id_cache.entries;
59462306a36Sopenharmony_ci	for (i = 0; i < erst_record_id_cache.len; i++) {
59562306a36Sopenharmony_ci		if (entries[i] == APEI_ERST_INVALID_RECORD_ID)
59662306a36Sopenharmony_ci			continue;
59762306a36Sopenharmony_ci		if (wpos != i)
59862306a36Sopenharmony_ci			entries[wpos] = entries[i];
59962306a36Sopenharmony_ci		wpos++;
60062306a36Sopenharmony_ci	}
60162306a36Sopenharmony_ci	erst_record_id_cache.len = wpos;
60262306a36Sopenharmony_ci}
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_civoid erst_get_record_id_end(void)
60562306a36Sopenharmony_ci{
60662306a36Sopenharmony_ci	/*
60762306a36Sopenharmony_ci	 * erst_disable != 0 should be detected by invoker via the
60862306a36Sopenharmony_ci	 * return value of erst_get_record_id_begin/next, so this
60962306a36Sopenharmony_ci	 * function should not be called for erst_disable != 0.
61062306a36Sopenharmony_ci	 */
61162306a36Sopenharmony_ci	BUG_ON(erst_disable);
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	mutex_lock(&erst_record_id_cache.lock);
61462306a36Sopenharmony_ci	erst_record_id_cache.refcount--;
61562306a36Sopenharmony_ci	BUG_ON(erst_record_id_cache.refcount < 0);
61662306a36Sopenharmony_ci	__erst_record_id_cache_compact();
61762306a36Sopenharmony_ci	mutex_unlock(&erst_record_id_cache.lock);
61862306a36Sopenharmony_ci}
61962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_get_record_id_end);
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_cistatic int __erst_write_to_storage(u64 offset)
62262306a36Sopenharmony_ci{
62362306a36Sopenharmony_ci	struct apei_exec_context ctx;
62462306a36Sopenharmony_ci	u64 timeout = FIRMWARE_TIMEOUT;
62562306a36Sopenharmony_ci	u64 val;
62662306a36Sopenharmony_ci	int rc;
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	erst_exec_ctx_init(&ctx);
62962306a36Sopenharmony_ci	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_WRITE);
63062306a36Sopenharmony_ci	if (rc)
63162306a36Sopenharmony_ci		return rc;
63262306a36Sopenharmony_ci	apei_exec_ctx_set_input(&ctx, offset);
63362306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET);
63462306a36Sopenharmony_ci	if (rc)
63562306a36Sopenharmony_ci		return rc;
63662306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
63762306a36Sopenharmony_ci	if (rc)
63862306a36Sopenharmony_ci		return rc;
63962306a36Sopenharmony_ci	for (;;) {
64062306a36Sopenharmony_ci		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
64162306a36Sopenharmony_ci		if (rc)
64262306a36Sopenharmony_ci			return rc;
64362306a36Sopenharmony_ci		val = apei_exec_ctx_get_output(&ctx);
64462306a36Sopenharmony_ci		if (!val)
64562306a36Sopenharmony_ci			break;
64662306a36Sopenharmony_ci		if (erst_timedout(&timeout, SPIN_UNIT))
64762306a36Sopenharmony_ci			return -EIO;
64862306a36Sopenharmony_ci	}
64962306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
65062306a36Sopenharmony_ci	if (rc)
65162306a36Sopenharmony_ci		return rc;
65262306a36Sopenharmony_ci	val = apei_exec_ctx_get_output(&ctx);
65362306a36Sopenharmony_ci	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
65462306a36Sopenharmony_ci	if (rc)
65562306a36Sopenharmony_ci		return rc;
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci	return erst_errno(val);
65862306a36Sopenharmony_ci}
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_cistatic int __erst_read_from_storage(u64 record_id, u64 offset)
66162306a36Sopenharmony_ci{
66262306a36Sopenharmony_ci	struct apei_exec_context ctx;
66362306a36Sopenharmony_ci	u64 timeout = FIRMWARE_TIMEOUT;
66462306a36Sopenharmony_ci	u64 val;
66562306a36Sopenharmony_ci	int rc;
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci	erst_exec_ctx_init(&ctx);
66862306a36Sopenharmony_ci	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_READ);
66962306a36Sopenharmony_ci	if (rc)
67062306a36Sopenharmony_ci		return rc;
67162306a36Sopenharmony_ci	apei_exec_ctx_set_input(&ctx, offset);
67262306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET);
67362306a36Sopenharmony_ci	if (rc)
67462306a36Sopenharmony_ci		return rc;
67562306a36Sopenharmony_ci	apei_exec_ctx_set_input(&ctx, record_id);
67662306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID);
67762306a36Sopenharmony_ci	if (rc)
67862306a36Sopenharmony_ci		return rc;
67962306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
68062306a36Sopenharmony_ci	if (rc)
68162306a36Sopenharmony_ci		return rc;
68262306a36Sopenharmony_ci	for (;;) {
68362306a36Sopenharmony_ci		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
68462306a36Sopenharmony_ci		if (rc)
68562306a36Sopenharmony_ci			return rc;
68662306a36Sopenharmony_ci		val = apei_exec_ctx_get_output(&ctx);
68762306a36Sopenharmony_ci		if (!val)
68862306a36Sopenharmony_ci			break;
68962306a36Sopenharmony_ci		if (erst_timedout(&timeout, SPIN_UNIT))
69062306a36Sopenharmony_ci			return -EIO;
69162306a36Sopenharmony_ci	}
69262306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
69362306a36Sopenharmony_ci	if (rc)
69462306a36Sopenharmony_ci		return rc;
69562306a36Sopenharmony_ci	val = apei_exec_ctx_get_output(&ctx);
69662306a36Sopenharmony_ci	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
69762306a36Sopenharmony_ci	if (rc)
69862306a36Sopenharmony_ci		return rc;
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	return erst_errno(val);
70162306a36Sopenharmony_ci}
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_cistatic int __erst_clear_from_storage(u64 record_id)
70462306a36Sopenharmony_ci{
70562306a36Sopenharmony_ci	struct apei_exec_context ctx;
70662306a36Sopenharmony_ci	u64 timeout = FIRMWARE_TIMEOUT;
70762306a36Sopenharmony_ci	u64 val;
70862306a36Sopenharmony_ci	int rc;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	erst_exec_ctx_init(&ctx);
71162306a36Sopenharmony_ci	rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_CLEAR);
71262306a36Sopenharmony_ci	if (rc)
71362306a36Sopenharmony_ci		return rc;
71462306a36Sopenharmony_ci	apei_exec_ctx_set_input(&ctx, record_id);
71562306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID);
71662306a36Sopenharmony_ci	if (rc)
71762306a36Sopenharmony_ci		return rc;
71862306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION);
71962306a36Sopenharmony_ci	if (rc)
72062306a36Sopenharmony_ci		return rc;
72162306a36Sopenharmony_ci	for (;;) {
72262306a36Sopenharmony_ci		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS);
72362306a36Sopenharmony_ci		if (rc)
72462306a36Sopenharmony_ci			return rc;
72562306a36Sopenharmony_ci		val = apei_exec_ctx_get_output(&ctx);
72662306a36Sopenharmony_ci		if (!val)
72762306a36Sopenharmony_ci			break;
72862306a36Sopenharmony_ci		if (erst_timedout(&timeout, SPIN_UNIT))
72962306a36Sopenharmony_ci			return -EIO;
73062306a36Sopenharmony_ci	}
73162306a36Sopenharmony_ci	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS);
73262306a36Sopenharmony_ci	if (rc)
73362306a36Sopenharmony_ci		return rc;
73462306a36Sopenharmony_ci	val = apei_exec_ctx_get_output(&ctx);
73562306a36Sopenharmony_ci	rc = apei_exec_run_optional(&ctx, ACPI_ERST_END);
73662306a36Sopenharmony_ci	if (rc)
73762306a36Sopenharmony_ci		return rc;
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ci	return erst_errno(val);
74062306a36Sopenharmony_ci}
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci/* NVRAM ERST Error Log Address Range is not supported yet */
74362306a36Sopenharmony_cistatic void pr_unimpl_nvram(void)
74462306a36Sopenharmony_ci{
74562306a36Sopenharmony_ci	if (printk_ratelimit())
74662306a36Sopenharmony_ci		pr_warn("NVRAM ERST Log Address Range not implemented yet.\n");
74762306a36Sopenharmony_ci}
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_cistatic int __erst_write_to_nvram(const struct cper_record_header *record)
75062306a36Sopenharmony_ci{
75162306a36Sopenharmony_ci	/* do not print message, because printk is not safe for NMI */
75262306a36Sopenharmony_ci	return -ENOSYS;
75362306a36Sopenharmony_ci}
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_cistatic int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset)
75662306a36Sopenharmony_ci{
75762306a36Sopenharmony_ci	pr_unimpl_nvram();
75862306a36Sopenharmony_ci	return -ENOSYS;
75962306a36Sopenharmony_ci}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_cistatic int __erst_clear_from_nvram(u64 record_id)
76262306a36Sopenharmony_ci{
76362306a36Sopenharmony_ci	pr_unimpl_nvram();
76462306a36Sopenharmony_ci	return -ENOSYS;
76562306a36Sopenharmony_ci}
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ciint erst_write(const struct cper_record_header *record)
76862306a36Sopenharmony_ci{
76962306a36Sopenharmony_ci	int rc;
77062306a36Sopenharmony_ci	unsigned long flags;
77162306a36Sopenharmony_ci	struct cper_record_header *rcd_erange;
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	if (erst_disable)
77462306a36Sopenharmony_ci		return -ENODEV;
77562306a36Sopenharmony_ci
77662306a36Sopenharmony_ci	if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE))
77762306a36Sopenharmony_ci		return -EINVAL;
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	if (erst_erange.attr & ERST_RANGE_NVRAM) {
78062306a36Sopenharmony_ci		if (!raw_spin_trylock_irqsave(&erst_lock, flags))
78162306a36Sopenharmony_ci			return -EBUSY;
78262306a36Sopenharmony_ci		rc = __erst_write_to_nvram(record);
78362306a36Sopenharmony_ci		raw_spin_unlock_irqrestore(&erst_lock, flags);
78462306a36Sopenharmony_ci		return rc;
78562306a36Sopenharmony_ci	}
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ci	if (record->record_length > erst_erange.size)
78862306a36Sopenharmony_ci		return -EINVAL;
78962306a36Sopenharmony_ci
79062306a36Sopenharmony_ci	if (!raw_spin_trylock_irqsave(&erst_lock, flags))
79162306a36Sopenharmony_ci		return -EBUSY;
79262306a36Sopenharmony_ci	memcpy(erst_erange.vaddr, record, record->record_length);
79362306a36Sopenharmony_ci	rcd_erange = erst_erange.vaddr;
79462306a36Sopenharmony_ci	/* signature for serialization system */
79562306a36Sopenharmony_ci	memcpy(&rcd_erange->persistence_information, "ER", 2);
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	rc = __erst_write_to_storage(0);
79862306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&erst_lock, flags);
79962306a36Sopenharmony_ci
80062306a36Sopenharmony_ci	return rc;
80162306a36Sopenharmony_ci}
80262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_write);
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_cistatic int __erst_read_to_erange(u64 record_id, u64 *offset)
80562306a36Sopenharmony_ci{
80662306a36Sopenharmony_ci	int rc;
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	if (erst_erange.attr & ERST_RANGE_NVRAM)
80962306a36Sopenharmony_ci		return __erst_read_to_erange_from_nvram(
81062306a36Sopenharmony_ci			record_id, offset);
81162306a36Sopenharmony_ci
81262306a36Sopenharmony_ci	rc = __erst_read_from_storage(record_id, 0);
81362306a36Sopenharmony_ci	if (rc)
81462306a36Sopenharmony_ci		return rc;
81562306a36Sopenharmony_ci	*offset = 0;
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci	return 0;
81862306a36Sopenharmony_ci}
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_cistatic ssize_t __erst_read(u64 record_id, struct cper_record_header *record,
82162306a36Sopenharmony_ci			   size_t buflen)
82262306a36Sopenharmony_ci{
82362306a36Sopenharmony_ci	int rc;
82462306a36Sopenharmony_ci	u64 offset, len = 0;
82562306a36Sopenharmony_ci	struct cper_record_header *rcd_tmp;
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	rc = __erst_read_to_erange(record_id, &offset);
82862306a36Sopenharmony_ci	if (rc)
82962306a36Sopenharmony_ci		return rc;
83062306a36Sopenharmony_ci	rcd_tmp = erst_erange.vaddr + offset;
83162306a36Sopenharmony_ci	len = rcd_tmp->record_length;
83262306a36Sopenharmony_ci	if (len <= buflen)
83362306a36Sopenharmony_ci		memcpy(record, rcd_tmp, len);
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	return len;
83662306a36Sopenharmony_ci}
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci/*
83962306a36Sopenharmony_ci * If return value > buflen, the buffer size is not big enough,
84062306a36Sopenharmony_ci * else if return value < 0, something goes wrong,
84162306a36Sopenharmony_ci * else everything is OK, and return value is record length
84262306a36Sopenharmony_ci */
84362306a36Sopenharmony_cissize_t erst_read(u64 record_id, struct cper_record_header *record,
84462306a36Sopenharmony_ci		  size_t buflen)
84562306a36Sopenharmony_ci{
84662306a36Sopenharmony_ci	ssize_t len;
84762306a36Sopenharmony_ci	unsigned long flags;
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_ci	if (erst_disable)
85062306a36Sopenharmony_ci		return -ENODEV;
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_ci	raw_spin_lock_irqsave(&erst_lock, flags);
85362306a36Sopenharmony_ci	len = __erst_read(record_id, record, buflen);
85462306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&erst_lock, flags);
85562306a36Sopenharmony_ci	return len;
85662306a36Sopenharmony_ci}
85762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_read);
85862306a36Sopenharmony_ci
85962306a36Sopenharmony_cistatic void erst_clear_cache(u64 record_id)
86062306a36Sopenharmony_ci{
86162306a36Sopenharmony_ci	int i;
86262306a36Sopenharmony_ci	u64 *entries;
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_ci	mutex_lock(&erst_record_id_cache.lock);
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci	entries = erst_record_id_cache.entries;
86762306a36Sopenharmony_ci	for (i = 0; i < erst_record_id_cache.len; i++) {
86862306a36Sopenharmony_ci		if (entries[i] == record_id)
86962306a36Sopenharmony_ci			entries[i] = APEI_ERST_INVALID_RECORD_ID;
87062306a36Sopenharmony_ci	}
87162306a36Sopenharmony_ci	__erst_record_id_cache_compact();
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	mutex_unlock(&erst_record_id_cache.lock);
87462306a36Sopenharmony_ci}
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_cissize_t erst_read_record(u64 record_id, struct cper_record_header *record,
87762306a36Sopenharmony_ci		size_t buflen, size_t recordlen, const guid_t *creatorid)
87862306a36Sopenharmony_ci{
87962306a36Sopenharmony_ci	ssize_t len;
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_ci	/*
88262306a36Sopenharmony_ci	 * if creatorid is NULL, read any record for erst-dbg module
88362306a36Sopenharmony_ci	 */
88462306a36Sopenharmony_ci	if (creatorid == NULL) {
88562306a36Sopenharmony_ci		len = erst_read(record_id, record, buflen);
88662306a36Sopenharmony_ci		if (len == -ENOENT)
88762306a36Sopenharmony_ci			erst_clear_cache(record_id);
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci		return len;
89062306a36Sopenharmony_ci	}
89162306a36Sopenharmony_ci
89262306a36Sopenharmony_ci	len = erst_read(record_id, record, buflen);
89362306a36Sopenharmony_ci	/*
89462306a36Sopenharmony_ci	 * if erst_read return value is -ENOENT skip to next record_id,
89562306a36Sopenharmony_ci	 * and clear the record_id cache.
89662306a36Sopenharmony_ci	 */
89762306a36Sopenharmony_ci	if (len == -ENOENT) {
89862306a36Sopenharmony_ci		erst_clear_cache(record_id);
89962306a36Sopenharmony_ci		goto out;
90062306a36Sopenharmony_ci	}
90162306a36Sopenharmony_ci
90262306a36Sopenharmony_ci	if (len < 0)
90362306a36Sopenharmony_ci		goto out;
90462306a36Sopenharmony_ci
90562306a36Sopenharmony_ci	/*
90662306a36Sopenharmony_ci	 * if erst_read return value is less than record head length,
90762306a36Sopenharmony_ci	 * consider it as -EIO, and clear the record_id cache.
90862306a36Sopenharmony_ci	 */
90962306a36Sopenharmony_ci	if (len < recordlen) {
91062306a36Sopenharmony_ci		len = -EIO;
91162306a36Sopenharmony_ci		erst_clear_cache(record_id);
91262306a36Sopenharmony_ci		goto out;
91362306a36Sopenharmony_ci	}
91462306a36Sopenharmony_ci
91562306a36Sopenharmony_ci	/*
91662306a36Sopenharmony_ci	 * if creatorid is not wanted, consider it as not found,
91762306a36Sopenharmony_ci	 * for skipping to next record_id.
91862306a36Sopenharmony_ci	 */
91962306a36Sopenharmony_ci	if (!guid_equal(&record->creator_id, creatorid))
92062306a36Sopenharmony_ci		len = -ENOENT;
92162306a36Sopenharmony_ci
92262306a36Sopenharmony_ciout:
92362306a36Sopenharmony_ci	return len;
92462306a36Sopenharmony_ci}
92562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_read_record);
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_ciint erst_clear(u64 record_id)
92862306a36Sopenharmony_ci{
92962306a36Sopenharmony_ci	int rc, i;
93062306a36Sopenharmony_ci	unsigned long flags;
93162306a36Sopenharmony_ci	u64 *entries;
93262306a36Sopenharmony_ci
93362306a36Sopenharmony_ci	if (erst_disable)
93462306a36Sopenharmony_ci		return -ENODEV;
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
93762306a36Sopenharmony_ci	if (rc)
93862306a36Sopenharmony_ci		return rc;
93962306a36Sopenharmony_ci	raw_spin_lock_irqsave(&erst_lock, flags);
94062306a36Sopenharmony_ci	if (erst_erange.attr & ERST_RANGE_NVRAM)
94162306a36Sopenharmony_ci		rc = __erst_clear_from_nvram(record_id);
94262306a36Sopenharmony_ci	else
94362306a36Sopenharmony_ci		rc = __erst_clear_from_storage(record_id);
94462306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&erst_lock, flags);
94562306a36Sopenharmony_ci	if (rc)
94662306a36Sopenharmony_ci		goto out;
94762306a36Sopenharmony_ci	entries = erst_record_id_cache.entries;
94862306a36Sopenharmony_ci	for (i = 0; i < erst_record_id_cache.len; i++) {
94962306a36Sopenharmony_ci		if (entries[i] == record_id)
95062306a36Sopenharmony_ci			entries[i] = APEI_ERST_INVALID_RECORD_ID;
95162306a36Sopenharmony_ci	}
95262306a36Sopenharmony_ci	__erst_record_id_cache_compact();
95362306a36Sopenharmony_ciout:
95462306a36Sopenharmony_ci	mutex_unlock(&erst_record_id_cache.lock);
95562306a36Sopenharmony_ci	return rc;
95662306a36Sopenharmony_ci}
95762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(erst_clear);
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_cistatic int __init setup_erst_disable(char *str)
96062306a36Sopenharmony_ci{
96162306a36Sopenharmony_ci	erst_disable = 1;
96262306a36Sopenharmony_ci	return 1;
96362306a36Sopenharmony_ci}
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci__setup("erst_disable", setup_erst_disable);
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_cistatic int erst_check_table(struct acpi_table_erst *erst_tab)
96862306a36Sopenharmony_ci{
96962306a36Sopenharmony_ci	if ((erst_tab->header_length !=
97062306a36Sopenharmony_ci	     (sizeof(struct acpi_table_erst) - sizeof(erst_tab->header)))
97162306a36Sopenharmony_ci	    && (erst_tab->header_length != sizeof(struct acpi_table_erst)))
97262306a36Sopenharmony_ci		return -EINVAL;
97362306a36Sopenharmony_ci	if (erst_tab->header.length < sizeof(struct acpi_table_erst))
97462306a36Sopenharmony_ci		return -EINVAL;
97562306a36Sopenharmony_ci	if (erst_tab->entries !=
97662306a36Sopenharmony_ci	    (erst_tab->header.length - sizeof(struct acpi_table_erst)) /
97762306a36Sopenharmony_ci	    sizeof(struct acpi_erst_entry))
97862306a36Sopenharmony_ci		return -EINVAL;
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ci	return 0;
98162306a36Sopenharmony_ci}
98262306a36Sopenharmony_ci
98362306a36Sopenharmony_cistatic int erst_open_pstore(struct pstore_info *psi);
98462306a36Sopenharmony_cistatic int erst_close_pstore(struct pstore_info *psi);
98562306a36Sopenharmony_cistatic ssize_t erst_reader(struct pstore_record *record);
98662306a36Sopenharmony_cistatic int erst_writer(struct pstore_record *record);
98762306a36Sopenharmony_cistatic int erst_clearer(struct pstore_record *record);
98862306a36Sopenharmony_ci
98962306a36Sopenharmony_cistatic struct pstore_info erst_info = {
99062306a36Sopenharmony_ci	.owner		= THIS_MODULE,
99162306a36Sopenharmony_ci	.name		= "erst",
99262306a36Sopenharmony_ci	.flags		= PSTORE_FLAGS_DMESG,
99362306a36Sopenharmony_ci	.open		= erst_open_pstore,
99462306a36Sopenharmony_ci	.close		= erst_close_pstore,
99562306a36Sopenharmony_ci	.read		= erst_reader,
99662306a36Sopenharmony_ci	.write		= erst_writer,
99762306a36Sopenharmony_ci	.erase		= erst_clearer
99862306a36Sopenharmony_ci};
99962306a36Sopenharmony_ci
100062306a36Sopenharmony_ci#define CPER_CREATOR_PSTORE						\
100162306a36Sopenharmony_ci	GUID_INIT(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c,	\
100262306a36Sopenharmony_ci		  0x64, 0x90, 0xb8, 0x9d)
100362306a36Sopenharmony_ci#define CPER_SECTION_TYPE_DMESG						\
100462306a36Sopenharmony_ci	GUID_INIT(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54,	\
100562306a36Sopenharmony_ci		  0x94, 0x19, 0xeb, 0x12)
100662306a36Sopenharmony_ci#define CPER_SECTION_TYPE_DMESG_Z					\
100762306a36Sopenharmony_ci	GUID_INIT(0x4f118707, 0x04dd, 0x4055, 0xb5, 0xdd, 0x95, 0x6d,	\
100862306a36Sopenharmony_ci		  0x34, 0xdd, 0xfa, 0xc6)
100962306a36Sopenharmony_ci#define CPER_SECTION_TYPE_MCE						\
101062306a36Sopenharmony_ci	GUID_INIT(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96,	\
101162306a36Sopenharmony_ci		  0x04, 0x4a, 0x38, 0xfc)
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_cistruct cper_pstore_record {
101462306a36Sopenharmony_ci	struct cper_record_header hdr;
101562306a36Sopenharmony_ci	struct cper_section_descriptor sec_hdr;
101662306a36Sopenharmony_ci	char data[];
101762306a36Sopenharmony_ci} __packed;
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_cistatic int reader_pos;
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_cistatic int erst_open_pstore(struct pstore_info *psi)
102262306a36Sopenharmony_ci{
102362306a36Sopenharmony_ci	if (erst_disable)
102462306a36Sopenharmony_ci		return -ENODEV;
102562306a36Sopenharmony_ci
102662306a36Sopenharmony_ci	return erst_get_record_id_begin(&reader_pos);
102762306a36Sopenharmony_ci}
102862306a36Sopenharmony_ci
102962306a36Sopenharmony_cistatic int erst_close_pstore(struct pstore_info *psi)
103062306a36Sopenharmony_ci{
103162306a36Sopenharmony_ci	erst_get_record_id_end();
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_ci	return 0;
103462306a36Sopenharmony_ci}
103562306a36Sopenharmony_ci
103662306a36Sopenharmony_cistatic ssize_t erst_reader(struct pstore_record *record)
103762306a36Sopenharmony_ci{
103862306a36Sopenharmony_ci	int rc;
103962306a36Sopenharmony_ci	ssize_t len = 0;
104062306a36Sopenharmony_ci	u64 record_id;
104162306a36Sopenharmony_ci	struct cper_pstore_record *rcd;
104262306a36Sopenharmony_ci	size_t rcd_len = sizeof(*rcd) + erst_info.bufsize;
104362306a36Sopenharmony_ci
104462306a36Sopenharmony_ci	if (erst_disable)
104562306a36Sopenharmony_ci		return -ENODEV;
104662306a36Sopenharmony_ci
104762306a36Sopenharmony_ci	rcd = kmalloc(rcd_len, GFP_KERNEL);
104862306a36Sopenharmony_ci	if (!rcd) {
104962306a36Sopenharmony_ci		rc = -ENOMEM;
105062306a36Sopenharmony_ci		goto out;
105162306a36Sopenharmony_ci	}
105262306a36Sopenharmony_ciskip:
105362306a36Sopenharmony_ci	rc = erst_get_record_id_next(&reader_pos, &record_id);
105462306a36Sopenharmony_ci	if (rc)
105562306a36Sopenharmony_ci		goto out;
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_ci	/* no more record */
105862306a36Sopenharmony_ci	if (record_id == APEI_ERST_INVALID_RECORD_ID) {
105962306a36Sopenharmony_ci		rc = -EINVAL;
106062306a36Sopenharmony_ci		goto out;
106162306a36Sopenharmony_ci	}
106262306a36Sopenharmony_ci
106362306a36Sopenharmony_ci	len = erst_read_record(record_id, &rcd->hdr, rcd_len, sizeof(*rcd),
106462306a36Sopenharmony_ci			&CPER_CREATOR_PSTORE);
106562306a36Sopenharmony_ci	/* The record may be cleared by others, try read next record */
106662306a36Sopenharmony_ci	if (len == -ENOENT)
106762306a36Sopenharmony_ci		goto skip;
106862306a36Sopenharmony_ci	else if (len < 0)
106962306a36Sopenharmony_ci		goto out;
107062306a36Sopenharmony_ci
107162306a36Sopenharmony_ci	record->buf = kmalloc(len, GFP_KERNEL);
107262306a36Sopenharmony_ci	if (record->buf == NULL) {
107362306a36Sopenharmony_ci		rc = -ENOMEM;
107462306a36Sopenharmony_ci		goto out;
107562306a36Sopenharmony_ci	}
107662306a36Sopenharmony_ci	memcpy(record->buf, rcd->data, len - sizeof(*rcd));
107762306a36Sopenharmony_ci	record->id = record_id;
107862306a36Sopenharmony_ci	record->compressed = false;
107962306a36Sopenharmony_ci	record->ecc_notice_size = 0;
108062306a36Sopenharmony_ci	if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_DMESG_Z)) {
108162306a36Sopenharmony_ci		record->type = PSTORE_TYPE_DMESG;
108262306a36Sopenharmony_ci		record->compressed = true;
108362306a36Sopenharmony_ci	} else if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_DMESG))
108462306a36Sopenharmony_ci		record->type = PSTORE_TYPE_DMESG;
108562306a36Sopenharmony_ci	else if (guid_equal(&rcd->sec_hdr.section_type, &CPER_SECTION_TYPE_MCE))
108662306a36Sopenharmony_ci		record->type = PSTORE_TYPE_MCE;
108762306a36Sopenharmony_ci	else
108862306a36Sopenharmony_ci		record->type = PSTORE_TYPE_MAX;
108962306a36Sopenharmony_ci
109062306a36Sopenharmony_ci	if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP)
109162306a36Sopenharmony_ci		record->time.tv_sec = rcd->hdr.timestamp;
109262306a36Sopenharmony_ci	else
109362306a36Sopenharmony_ci		record->time.tv_sec = 0;
109462306a36Sopenharmony_ci	record->time.tv_nsec = 0;
109562306a36Sopenharmony_ci
109662306a36Sopenharmony_ciout:
109762306a36Sopenharmony_ci	kfree(rcd);
109862306a36Sopenharmony_ci	return (rc < 0) ? rc : (len - sizeof(*rcd));
109962306a36Sopenharmony_ci}
110062306a36Sopenharmony_ci
110162306a36Sopenharmony_cistatic int erst_writer(struct pstore_record *record)
110262306a36Sopenharmony_ci{
110362306a36Sopenharmony_ci	struct cper_pstore_record *rcd = (struct cper_pstore_record *)
110462306a36Sopenharmony_ci					(erst_info.buf - sizeof(*rcd));
110562306a36Sopenharmony_ci	int ret;
110662306a36Sopenharmony_ci
110762306a36Sopenharmony_ci	memset(rcd, 0, sizeof(*rcd));
110862306a36Sopenharmony_ci	memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE);
110962306a36Sopenharmony_ci	rcd->hdr.revision = CPER_RECORD_REV;
111062306a36Sopenharmony_ci	rcd->hdr.signature_end = CPER_SIG_END;
111162306a36Sopenharmony_ci	rcd->hdr.section_count = 1;
111262306a36Sopenharmony_ci	rcd->hdr.error_severity = CPER_SEV_FATAL;
111362306a36Sopenharmony_ci	/* timestamp valid. platform_id, partition_id are invalid */
111462306a36Sopenharmony_ci	rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP;
111562306a36Sopenharmony_ci	rcd->hdr.timestamp = ktime_get_real_seconds();
111662306a36Sopenharmony_ci	rcd->hdr.record_length = sizeof(*rcd) + record->size;
111762306a36Sopenharmony_ci	rcd->hdr.creator_id = CPER_CREATOR_PSTORE;
111862306a36Sopenharmony_ci	rcd->hdr.notification_type = CPER_NOTIFY_MCE;
111962306a36Sopenharmony_ci	rcd->hdr.record_id = cper_next_record_id();
112062306a36Sopenharmony_ci	rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR;
112162306a36Sopenharmony_ci
112262306a36Sopenharmony_ci	rcd->sec_hdr.section_offset = sizeof(*rcd);
112362306a36Sopenharmony_ci	rcd->sec_hdr.section_length = record->size;
112462306a36Sopenharmony_ci	rcd->sec_hdr.revision = CPER_SEC_REV;
112562306a36Sopenharmony_ci	/* fru_id and fru_text is invalid */
112662306a36Sopenharmony_ci	rcd->sec_hdr.validation_bits = 0;
112762306a36Sopenharmony_ci	rcd->sec_hdr.flags = CPER_SEC_PRIMARY;
112862306a36Sopenharmony_ci	switch (record->type) {
112962306a36Sopenharmony_ci	case PSTORE_TYPE_DMESG:
113062306a36Sopenharmony_ci		if (record->compressed)
113162306a36Sopenharmony_ci			rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG_Z;
113262306a36Sopenharmony_ci		else
113362306a36Sopenharmony_ci			rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG;
113462306a36Sopenharmony_ci		break;
113562306a36Sopenharmony_ci	case PSTORE_TYPE_MCE:
113662306a36Sopenharmony_ci		rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE;
113762306a36Sopenharmony_ci		break;
113862306a36Sopenharmony_ci	default:
113962306a36Sopenharmony_ci		return -EINVAL;
114062306a36Sopenharmony_ci	}
114162306a36Sopenharmony_ci	rcd->sec_hdr.section_severity = CPER_SEV_FATAL;
114262306a36Sopenharmony_ci
114362306a36Sopenharmony_ci	ret = erst_write(&rcd->hdr);
114462306a36Sopenharmony_ci	record->id = rcd->hdr.record_id;
114562306a36Sopenharmony_ci
114662306a36Sopenharmony_ci	return ret;
114762306a36Sopenharmony_ci}
114862306a36Sopenharmony_ci
114962306a36Sopenharmony_cistatic int erst_clearer(struct pstore_record *record)
115062306a36Sopenharmony_ci{
115162306a36Sopenharmony_ci	return erst_clear(record->id);
115262306a36Sopenharmony_ci}
115362306a36Sopenharmony_ci
115462306a36Sopenharmony_cistatic int __init erst_init(void)
115562306a36Sopenharmony_ci{
115662306a36Sopenharmony_ci	int rc = 0;
115762306a36Sopenharmony_ci	acpi_status status;
115862306a36Sopenharmony_ci	struct apei_exec_context ctx;
115962306a36Sopenharmony_ci	struct apei_resources erst_resources;
116062306a36Sopenharmony_ci	struct resource *r;
116162306a36Sopenharmony_ci	char *buf;
116262306a36Sopenharmony_ci
116362306a36Sopenharmony_ci	if (acpi_disabled)
116462306a36Sopenharmony_ci		goto err;
116562306a36Sopenharmony_ci
116662306a36Sopenharmony_ci	if (erst_disable) {
116762306a36Sopenharmony_ci		pr_info(
116862306a36Sopenharmony_ci	"Error Record Serialization Table (ERST) support is disabled.\n");
116962306a36Sopenharmony_ci		goto err;
117062306a36Sopenharmony_ci	}
117162306a36Sopenharmony_ci
117262306a36Sopenharmony_ci	status = acpi_get_table(ACPI_SIG_ERST, 0,
117362306a36Sopenharmony_ci				(struct acpi_table_header **)&erst_tab);
117462306a36Sopenharmony_ci	if (status == AE_NOT_FOUND)
117562306a36Sopenharmony_ci		goto err;
117662306a36Sopenharmony_ci	else if (ACPI_FAILURE(status)) {
117762306a36Sopenharmony_ci		const char *msg = acpi_format_exception(status);
117862306a36Sopenharmony_ci		pr_err("Failed to get table, %s\n", msg);
117962306a36Sopenharmony_ci		rc = -EINVAL;
118062306a36Sopenharmony_ci		goto err;
118162306a36Sopenharmony_ci	}
118262306a36Sopenharmony_ci
118362306a36Sopenharmony_ci	rc = erst_check_table(erst_tab);
118462306a36Sopenharmony_ci	if (rc) {
118562306a36Sopenharmony_ci		pr_err(FW_BUG "ERST table is invalid.\n");
118662306a36Sopenharmony_ci		goto err_put_erst_tab;
118762306a36Sopenharmony_ci	}
118862306a36Sopenharmony_ci
118962306a36Sopenharmony_ci	apei_resources_init(&erst_resources);
119062306a36Sopenharmony_ci	erst_exec_ctx_init(&ctx);
119162306a36Sopenharmony_ci	rc = apei_exec_collect_resources(&ctx, &erst_resources);
119262306a36Sopenharmony_ci	if (rc)
119362306a36Sopenharmony_ci		goto err_fini;
119462306a36Sopenharmony_ci	rc = apei_resources_request(&erst_resources, "APEI ERST");
119562306a36Sopenharmony_ci	if (rc)
119662306a36Sopenharmony_ci		goto err_fini;
119762306a36Sopenharmony_ci	rc = apei_exec_pre_map_gars(&ctx);
119862306a36Sopenharmony_ci	if (rc)
119962306a36Sopenharmony_ci		goto err_release;
120062306a36Sopenharmony_ci	rc = erst_get_erange(&erst_erange);
120162306a36Sopenharmony_ci	if (rc) {
120262306a36Sopenharmony_ci		if (rc == -ENODEV)
120362306a36Sopenharmony_ci			pr_info(
120462306a36Sopenharmony_ci	"The corresponding hardware device or firmware implementation "
120562306a36Sopenharmony_ci	"is not available.\n");
120662306a36Sopenharmony_ci		else
120762306a36Sopenharmony_ci			pr_err("Failed to get Error Log Address Range.\n");
120862306a36Sopenharmony_ci		goto err_unmap_reg;
120962306a36Sopenharmony_ci	}
121062306a36Sopenharmony_ci
121162306a36Sopenharmony_ci	r = request_mem_region(erst_erange.base, erst_erange.size, "APEI ERST");
121262306a36Sopenharmony_ci	if (!r) {
121362306a36Sopenharmony_ci		pr_err("Can not request [mem %#010llx-%#010llx] for ERST.\n",
121462306a36Sopenharmony_ci		       (unsigned long long)erst_erange.base,
121562306a36Sopenharmony_ci		       (unsigned long long)erst_erange.base + erst_erange.size - 1);
121662306a36Sopenharmony_ci		rc = -EIO;
121762306a36Sopenharmony_ci		goto err_unmap_reg;
121862306a36Sopenharmony_ci	}
121962306a36Sopenharmony_ci	rc = -ENOMEM;
122062306a36Sopenharmony_ci	erst_erange.vaddr = ioremap_cache(erst_erange.base,
122162306a36Sopenharmony_ci					  erst_erange.size);
122262306a36Sopenharmony_ci	if (!erst_erange.vaddr)
122362306a36Sopenharmony_ci		goto err_release_erange;
122462306a36Sopenharmony_ci
122562306a36Sopenharmony_ci	pr_info(
122662306a36Sopenharmony_ci	"Error Record Serialization Table (ERST) support is initialized.\n");
122762306a36Sopenharmony_ci
122862306a36Sopenharmony_ci	buf = kmalloc(erst_erange.size, GFP_KERNEL);
122962306a36Sopenharmony_ci	if (buf) {
123062306a36Sopenharmony_ci		erst_info.buf = buf + sizeof(struct cper_pstore_record);
123162306a36Sopenharmony_ci		erst_info.bufsize = erst_erange.size -
123262306a36Sopenharmony_ci				    sizeof(struct cper_pstore_record);
123362306a36Sopenharmony_ci		rc = pstore_register(&erst_info);
123462306a36Sopenharmony_ci		if (rc) {
123562306a36Sopenharmony_ci			if (rc != -EPERM)
123662306a36Sopenharmony_ci				pr_info(
123762306a36Sopenharmony_ci				"Could not register with persistent store.\n");
123862306a36Sopenharmony_ci			erst_info.buf = NULL;
123962306a36Sopenharmony_ci			erst_info.bufsize = 0;
124062306a36Sopenharmony_ci			kfree(buf);
124162306a36Sopenharmony_ci		}
124262306a36Sopenharmony_ci	} else
124362306a36Sopenharmony_ci		pr_err(
124462306a36Sopenharmony_ci		"Failed to allocate %lld bytes for persistent store error log.\n",
124562306a36Sopenharmony_ci		erst_erange.size);
124662306a36Sopenharmony_ci
124762306a36Sopenharmony_ci	/* Cleanup ERST Resources */
124862306a36Sopenharmony_ci	apei_resources_fini(&erst_resources);
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci	return 0;
125162306a36Sopenharmony_ci
125262306a36Sopenharmony_cierr_release_erange:
125362306a36Sopenharmony_ci	release_mem_region(erst_erange.base, erst_erange.size);
125462306a36Sopenharmony_cierr_unmap_reg:
125562306a36Sopenharmony_ci	apei_exec_post_unmap_gars(&ctx);
125662306a36Sopenharmony_cierr_release:
125762306a36Sopenharmony_ci	apei_resources_release(&erst_resources);
125862306a36Sopenharmony_cierr_fini:
125962306a36Sopenharmony_ci	apei_resources_fini(&erst_resources);
126062306a36Sopenharmony_cierr_put_erst_tab:
126162306a36Sopenharmony_ci	acpi_put_table((struct acpi_table_header *)erst_tab);
126262306a36Sopenharmony_cierr:
126362306a36Sopenharmony_ci	erst_disable = 1;
126462306a36Sopenharmony_ci	return rc;
126562306a36Sopenharmony_ci}
126662306a36Sopenharmony_ci
126762306a36Sopenharmony_cidevice_initcall(erst_init);
1268