162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * RAM Oops/Panic logger
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com>
662306a36Sopenharmony_ci * Copyright (C) 2011 Kees Cook <keescook@chromium.org>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/err.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/version.h>
1562306a36Sopenharmony_ci#include <linux/pstore.h>
1662306a36Sopenharmony_ci#include <linux/io.h>
1762306a36Sopenharmony_ci#include <linux/ioport.h>
1862306a36Sopenharmony_ci#include <linux/platform_device.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci#include <linux/compiler.h>
2162306a36Sopenharmony_ci#include <linux/of.h>
2262306a36Sopenharmony_ci#include <linux/of_address.h>
2362306a36Sopenharmony_ci#include <linux/mm.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include "internal.h"
2662306a36Sopenharmony_ci#include "ram_internal.h"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define RAMOOPS_KERNMSG_HDR "===="
2962306a36Sopenharmony_ci#define MIN_MEM_SIZE 4096UL
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic ulong record_size = MIN_MEM_SIZE;
3262306a36Sopenharmony_cimodule_param(record_size, ulong, 0400);
3362306a36Sopenharmony_ciMODULE_PARM_DESC(record_size,
3462306a36Sopenharmony_ci		"size of each dump done on oops/panic");
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic ulong ramoops_console_size = MIN_MEM_SIZE;
3762306a36Sopenharmony_cimodule_param_named(console_size, ramoops_console_size, ulong, 0400);
3862306a36Sopenharmony_ciMODULE_PARM_DESC(console_size, "size of kernel console log");
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic ulong ramoops_ftrace_size = MIN_MEM_SIZE;
4162306a36Sopenharmony_cimodule_param_named(ftrace_size, ramoops_ftrace_size, ulong, 0400);
4262306a36Sopenharmony_ciMODULE_PARM_DESC(ftrace_size, "size of ftrace log");
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic ulong ramoops_pmsg_size = MIN_MEM_SIZE;
4562306a36Sopenharmony_cimodule_param_named(pmsg_size, ramoops_pmsg_size, ulong, 0400);
4662306a36Sopenharmony_ciMODULE_PARM_DESC(pmsg_size, "size of user space message log");
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic unsigned long long mem_address;
4962306a36Sopenharmony_cimodule_param_hw(mem_address, ullong, other, 0400);
5062306a36Sopenharmony_ciMODULE_PARM_DESC(mem_address,
5162306a36Sopenharmony_ci		"start of reserved RAM used to store oops/panic logs");
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic ulong mem_size;
5462306a36Sopenharmony_cimodule_param(mem_size, ulong, 0400);
5562306a36Sopenharmony_ciMODULE_PARM_DESC(mem_size,
5662306a36Sopenharmony_ci		"size of reserved RAM used to store oops/panic logs");
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic unsigned int mem_type;
5962306a36Sopenharmony_cimodule_param(mem_type, uint, 0400);
6062306a36Sopenharmony_ciMODULE_PARM_DESC(mem_type,
6162306a36Sopenharmony_ci		"memory type: 0=write-combined (default), 1=unbuffered, 2=cached");
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic int ramoops_max_reason = -1;
6462306a36Sopenharmony_cimodule_param_named(max_reason, ramoops_max_reason, int, 0400);
6562306a36Sopenharmony_ciMODULE_PARM_DESC(max_reason,
6662306a36Sopenharmony_ci		 "maximum reason for kmsg dump (default 2: Oops and Panic) ");
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic int ramoops_ecc;
6962306a36Sopenharmony_cimodule_param_named(ecc, ramoops_ecc, int, 0400);
7062306a36Sopenharmony_ciMODULE_PARM_DESC(ramoops_ecc,
7162306a36Sopenharmony_ci		"if non-zero, the option enables ECC support and specifies "
7262306a36Sopenharmony_ci		"ECC buffer size in bytes (1 is a special value, means 16 "
7362306a36Sopenharmony_ci		"bytes ECC)");
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic int ramoops_dump_oops = -1;
7662306a36Sopenharmony_cimodule_param_named(dump_oops, ramoops_dump_oops, int, 0400);
7762306a36Sopenharmony_ciMODULE_PARM_DESC(dump_oops,
7862306a36Sopenharmony_ci		 "(deprecated: use max_reason instead) set to 1 to dump oopses & panics, 0 to only dump panics");
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistruct ramoops_context {
8162306a36Sopenharmony_ci	struct persistent_ram_zone **dprzs;	/* Oops dump zones */
8262306a36Sopenharmony_ci	struct persistent_ram_zone *cprz;	/* Console zone */
8362306a36Sopenharmony_ci	struct persistent_ram_zone **fprzs;	/* Ftrace zones */
8462306a36Sopenharmony_ci	struct persistent_ram_zone *mprz;	/* PMSG zone */
8562306a36Sopenharmony_ci	phys_addr_t phys_addr;
8662306a36Sopenharmony_ci	unsigned long size;
8762306a36Sopenharmony_ci	unsigned int memtype;
8862306a36Sopenharmony_ci	size_t record_size;
8962306a36Sopenharmony_ci	size_t console_size;
9062306a36Sopenharmony_ci	size_t ftrace_size;
9162306a36Sopenharmony_ci	size_t pmsg_size;
9262306a36Sopenharmony_ci	u32 flags;
9362306a36Sopenharmony_ci	struct persistent_ram_ecc_info ecc_info;
9462306a36Sopenharmony_ci	unsigned int max_dump_cnt;
9562306a36Sopenharmony_ci	unsigned int dump_write_cnt;
9662306a36Sopenharmony_ci	/* _read_cnt need clear on ramoops_pstore_open */
9762306a36Sopenharmony_ci	unsigned int dump_read_cnt;
9862306a36Sopenharmony_ci	unsigned int console_read_cnt;
9962306a36Sopenharmony_ci	unsigned int max_ftrace_cnt;
10062306a36Sopenharmony_ci	unsigned int ftrace_read_cnt;
10162306a36Sopenharmony_ci	unsigned int pmsg_read_cnt;
10262306a36Sopenharmony_ci	struct pstore_info pstore;
10362306a36Sopenharmony_ci};
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic struct platform_device *dummy;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int ramoops_pstore_open(struct pstore_info *psi)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	struct ramoops_context *cxt = psi->data;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	cxt->dump_read_cnt = 0;
11262306a36Sopenharmony_ci	cxt->console_read_cnt = 0;
11362306a36Sopenharmony_ci	cxt->ftrace_read_cnt = 0;
11462306a36Sopenharmony_ci	cxt->pmsg_read_cnt = 0;
11562306a36Sopenharmony_ci	return 0;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic struct persistent_ram_zone *
11962306a36Sopenharmony_ciramoops_get_next_prz(struct persistent_ram_zone *przs[], int id,
12062306a36Sopenharmony_ci		     struct pstore_record *record)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct persistent_ram_zone *prz;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Give up if we never existed or have hit the end. */
12562306a36Sopenharmony_ci	if (!przs)
12662306a36Sopenharmony_ci		return NULL;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	prz = przs[id];
12962306a36Sopenharmony_ci	if (!prz)
13062306a36Sopenharmony_ci		return NULL;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* Update old/shadowed buffer. */
13362306a36Sopenharmony_ci	if (prz->type == PSTORE_TYPE_DMESG)
13462306a36Sopenharmony_ci		persistent_ram_save_old(prz);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (!persistent_ram_old_size(prz))
13762306a36Sopenharmony_ci		return NULL;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	record->type = prz->type;
14062306a36Sopenharmony_ci	record->id = id;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return prz;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic int ramoops_read_kmsg_hdr(char *buffer, struct timespec64 *time,
14662306a36Sopenharmony_ci				  bool *compressed)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	char data_type;
14962306a36Sopenharmony_ci	int header_length = 0;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (sscanf(buffer, RAMOOPS_KERNMSG_HDR "%lld.%lu-%c\n%n",
15262306a36Sopenharmony_ci		   (time64_t *)&time->tv_sec, &time->tv_nsec, &data_type,
15362306a36Sopenharmony_ci		   &header_length) == 3) {
15462306a36Sopenharmony_ci		time->tv_nsec *= 1000;
15562306a36Sopenharmony_ci		if (data_type == 'C')
15662306a36Sopenharmony_ci			*compressed = true;
15762306a36Sopenharmony_ci		else
15862306a36Sopenharmony_ci			*compressed = false;
15962306a36Sopenharmony_ci	} else if (sscanf(buffer, RAMOOPS_KERNMSG_HDR "%lld.%lu\n%n",
16062306a36Sopenharmony_ci			  (time64_t *)&time->tv_sec, &time->tv_nsec,
16162306a36Sopenharmony_ci			  &header_length) == 2) {
16262306a36Sopenharmony_ci		time->tv_nsec *= 1000;
16362306a36Sopenharmony_ci		*compressed = false;
16462306a36Sopenharmony_ci	} else {
16562306a36Sopenharmony_ci		time->tv_sec = 0;
16662306a36Sopenharmony_ci		time->tv_nsec = 0;
16762306a36Sopenharmony_ci		*compressed = false;
16862306a36Sopenharmony_ci	}
16962306a36Sopenharmony_ci	return header_length;
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic bool prz_ok(struct persistent_ram_zone *prz)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	return !!prz && !!(persistent_ram_old_size(prz) +
17562306a36Sopenharmony_ci			   persistent_ram_ecc_string(prz, NULL, 0));
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic ssize_t ramoops_pstore_read(struct pstore_record *record)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	ssize_t size = 0;
18162306a36Sopenharmony_ci	struct ramoops_context *cxt = record->psi->data;
18262306a36Sopenharmony_ci	struct persistent_ram_zone *prz = NULL;
18362306a36Sopenharmony_ci	int header_length = 0;
18462306a36Sopenharmony_ci	bool free_prz = false;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	/*
18762306a36Sopenharmony_ci	 * Ramoops headers provide time stamps for PSTORE_TYPE_DMESG, but
18862306a36Sopenharmony_ci	 * PSTORE_TYPE_CONSOLE and PSTORE_TYPE_FTRACE don't currently have
18962306a36Sopenharmony_ci	 * valid time stamps, so it is initialized to zero.
19062306a36Sopenharmony_ci	 */
19162306a36Sopenharmony_ci	record->time.tv_sec = 0;
19262306a36Sopenharmony_ci	record->time.tv_nsec = 0;
19362306a36Sopenharmony_ci	record->compressed = false;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	/* Find the next valid persistent_ram_zone for DMESG */
19662306a36Sopenharmony_ci	while (cxt->dump_read_cnt < cxt->max_dump_cnt && !prz) {
19762306a36Sopenharmony_ci		prz = ramoops_get_next_prz(cxt->dprzs, cxt->dump_read_cnt++,
19862306a36Sopenharmony_ci					   record);
19962306a36Sopenharmony_ci		if (!prz_ok(prz))
20062306a36Sopenharmony_ci			continue;
20162306a36Sopenharmony_ci		header_length = ramoops_read_kmsg_hdr(persistent_ram_old(prz),
20262306a36Sopenharmony_ci						      &record->time,
20362306a36Sopenharmony_ci						      &record->compressed);
20462306a36Sopenharmony_ci		/* Clear and skip this DMESG record if it has no valid header */
20562306a36Sopenharmony_ci		if (!header_length) {
20662306a36Sopenharmony_ci			persistent_ram_free_old(prz);
20762306a36Sopenharmony_ci			persistent_ram_zap(prz);
20862306a36Sopenharmony_ci			prz = NULL;
20962306a36Sopenharmony_ci		}
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	if (!prz_ok(prz) && !cxt->console_read_cnt++)
21362306a36Sopenharmony_ci		prz = ramoops_get_next_prz(&cxt->cprz, 0 /* single */, record);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	if (!prz_ok(prz) && !cxt->pmsg_read_cnt++)
21662306a36Sopenharmony_ci		prz = ramoops_get_next_prz(&cxt->mprz, 0 /* single */, record);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* ftrace is last since it may want to dynamically allocate memory. */
21962306a36Sopenharmony_ci	if (!prz_ok(prz)) {
22062306a36Sopenharmony_ci		if (!(cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU) &&
22162306a36Sopenharmony_ci		    !cxt->ftrace_read_cnt++) {
22262306a36Sopenharmony_ci			prz = ramoops_get_next_prz(cxt->fprzs, 0 /* single */,
22362306a36Sopenharmony_ci						   record);
22462306a36Sopenharmony_ci		} else {
22562306a36Sopenharmony_ci			/*
22662306a36Sopenharmony_ci			 * Build a new dummy record which combines all the
22762306a36Sopenharmony_ci			 * per-cpu records including metadata and ecc info.
22862306a36Sopenharmony_ci			 */
22962306a36Sopenharmony_ci			struct persistent_ram_zone *tmp_prz, *prz_next;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci			tmp_prz = kzalloc(sizeof(struct persistent_ram_zone),
23262306a36Sopenharmony_ci					  GFP_KERNEL);
23362306a36Sopenharmony_ci			if (!tmp_prz)
23462306a36Sopenharmony_ci				return -ENOMEM;
23562306a36Sopenharmony_ci			prz = tmp_prz;
23662306a36Sopenharmony_ci			free_prz = true;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci			while (cxt->ftrace_read_cnt < cxt->max_ftrace_cnt) {
23962306a36Sopenharmony_ci				prz_next = ramoops_get_next_prz(cxt->fprzs,
24062306a36Sopenharmony_ci						cxt->ftrace_read_cnt++, record);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci				if (!prz_ok(prz_next))
24362306a36Sopenharmony_ci					continue;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci				tmp_prz->ecc_info = prz_next->ecc_info;
24662306a36Sopenharmony_ci				tmp_prz->corrected_bytes +=
24762306a36Sopenharmony_ci						prz_next->corrected_bytes;
24862306a36Sopenharmony_ci				tmp_prz->bad_blocks += prz_next->bad_blocks;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci				size = pstore_ftrace_combine_log(
25162306a36Sopenharmony_ci						&tmp_prz->old_log,
25262306a36Sopenharmony_ci						&tmp_prz->old_log_size,
25362306a36Sopenharmony_ci						prz_next->old_log,
25462306a36Sopenharmony_ci						prz_next->old_log_size);
25562306a36Sopenharmony_ci				if (size)
25662306a36Sopenharmony_ci					goto out;
25762306a36Sopenharmony_ci			}
25862306a36Sopenharmony_ci			record->id = 0;
25962306a36Sopenharmony_ci		}
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	if (!prz_ok(prz)) {
26362306a36Sopenharmony_ci		size = 0;
26462306a36Sopenharmony_ci		goto out;
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	size = persistent_ram_old_size(prz) - header_length;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/* ECC correction notice */
27062306a36Sopenharmony_ci	record->ecc_notice_size = persistent_ram_ecc_string(prz, NULL, 0);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	record->buf = kvzalloc(size + record->ecc_notice_size + 1, GFP_KERNEL);
27362306a36Sopenharmony_ci	if (record->buf == NULL) {
27462306a36Sopenharmony_ci		size = -ENOMEM;
27562306a36Sopenharmony_ci		goto out;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	memcpy(record->buf, (char *)persistent_ram_old(prz) + header_length,
27962306a36Sopenharmony_ci	       size);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	persistent_ram_ecc_string(prz, record->buf + size,
28262306a36Sopenharmony_ci				  record->ecc_notice_size + 1);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ciout:
28562306a36Sopenharmony_ci	if (free_prz) {
28662306a36Sopenharmony_ci		kvfree(prz->old_log);
28762306a36Sopenharmony_ci		kfree(prz);
28862306a36Sopenharmony_ci	}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	return size;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic size_t ramoops_write_kmsg_hdr(struct persistent_ram_zone *prz,
29462306a36Sopenharmony_ci				     struct pstore_record *record)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	char hdr[36]; /* "===="(4), %lld(20), "."(1), %06lu(6), "-%c\n"(3) */
29762306a36Sopenharmony_ci	size_t len;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	len = scnprintf(hdr, sizeof(hdr),
30062306a36Sopenharmony_ci		RAMOOPS_KERNMSG_HDR "%lld.%06lu-%c\n",
30162306a36Sopenharmony_ci		(time64_t)record->time.tv_sec,
30262306a36Sopenharmony_ci		record->time.tv_nsec / 1000,
30362306a36Sopenharmony_ci		record->compressed ? 'C' : 'D');
30462306a36Sopenharmony_ci	persistent_ram_write(prz, hdr, len);
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	return len;
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cistatic int notrace ramoops_pstore_write(struct pstore_record *record)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	struct ramoops_context *cxt = record->psi->data;
31262306a36Sopenharmony_ci	struct persistent_ram_zone *prz;
31362306a36Sopenharmony_ci	size_t size, hlen;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	if (record->type == PSTORE_TYPE_CONSOLE) {
31662306a36Sopenharmony_ci		if (!cxt->cprz)
31762306a36Sopenharmony_ci			return -ENOMEM;
31862306a36Sopenharmony_ci		persistent_ram_write(cxt->cprz, record->buf, record->size);
31962306a36Sopenharmony_ci		return 0;
32062306a36Sopenharmony_ci	} else if (record->type == PSTORE_TYPE_FTRACE) {
32162306a36Sopenharmony_ci		int zonenum;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci		if (!cxt->fprzs)
32462306a36Sopenharmony_ci			return -ENOMEM;
32562306a36Sopenharmony_ci		/*
32662306a36Sopenharmony_ci		 * Choose zone by if we're using per-cpu buffers.
32762306a36Sopenharmony_ci		 */
32862306a36Sopenharmony_ci		if (cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU)
32962306a36Sopenharmony_ci			zonenum = smp_processor_id();
33062306a36Sopenharmony_ci		else
33162306a36Sopenharmony_ci			zonenum = 0;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci		persistent_ram_write(cxt->fprzs[zonenum], record->buf,
33462306a36Sopenharmony_ci				     record->size);
33562306a36Sopenharmony_ci		return 0;
33662306a36Sopenharmony_ci	} else if (record->type == PSTORE_TYPE_PMSG) {
33762306a36Sopenharmony_ci		pr_warn_ratelimited("PMSG shouldn't call %s\n", __func__);
33862306a36Sopenharmony_ci		return -EINVAL;
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	if (record->type != PSTORE_TYPE_DMESG)
34262306a36Sopenharmony_ci		return -EINVAL;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	/*
34562306a36Sopenharmony_ci	 * We could filter on record->reason here if we wanted to (which
34662306a36Sopenharmony_ci	 * would duplicate what happened before the "max_reason" setting
34762306a36Sopenharmony_ci	 * was added), but that would defeat the purpose of a system
34862306a36Sopenharmony_ci	 * changing printk.always_kmsg_dump, so instead log everything that
34962306a36Sopenharmony_ci	 * the kmsg dumper sends us, since it should be doing the filtering
35062306a36Sopenharmony_ci	 * based on the combination of printk.always_kmsg_dump and our
35162306a36Sopenharmony_ci	 * requested "max_reason".
35262306a36Sopenharmony_ci	 */
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	/*
35562306a36Sopenharmony_ci	 * Explicitly only take the first part of any new crash.
35662306a36Sopenharmony_ci	 * If our buffer is larger than kmsg_bytes, this can never happen,
35762306a36Sopenharmony_ci	 * and if our buffer is smaller than kmsg_bytes, we don't want the
35862306a36Sopenharmony_ci	 * report split across multiple records.
35962306a36Sopenharmony_ci	 */
36062306a36Sopenharmony_ci	if (record->part != 1)
36162306a36Sopenharmony_ci		return -ENOSPC;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	if (!cxt->dprzs)
36462306a36Sopenharmony_ci		return -ENOSPC;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	prz = cxt->dprzs[cxt->dump_write_cnt];
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	/*
36962306a36Sopenharmony_ci	 * Since this is a new crash dump, we need to reset the buffer in
37062306a36Sopenharmony_ci	 * case it still has an old dump present. Without this, the new dump
37162306a36Sopenharmony_ci	 * will get appended, which would seriously confuse anything trying
37262306a36Sopenharmony_ci	 * to check dump file contents. Specifically, ramoops_read_kmsg_hdr()
37362306a36Sopenharmony_ci	 * expects to find a dump header in the beginning of buffer data, so
37462306a36Sopenharmony_ci	 * we must to reset the buffer values, in order to ensure that the
37562306a36Sopenharmony_ci	 * header will be written to the beginning of the buffer.
37662306a36Sopenharmony_ci	 */
37762306a36Sopenharmony_ci	persistent_ram_zap(prz);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	/* Build header and append record contents. */
38062306a36Sopenharmony_ci	hlen = ramoops_write_kmsg_hdr(prz, record);
38162306a36Sopenharmony_ci	if (!hlen)
38262306a36Sopenharmony_ci		return -ENOMEM;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	size = record->size;
38562306a36Sopenharmony_ci	if (size + hlen > prz->buffer_size)
38662306a36Sopenharmony_ci		size = prz->buffer_size - hlen;
38762306a36Sopenharmony_ci	persistent_ram_write(prz, record->buf, size);
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	cxt->dump_write_cnt = (cxt->dump_write_cnt + 1) % cxt->max_dump_cnt;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	return 0;
39262306a36Sopenharmony_ci}
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_cistatic int notrace ramoops_pstore_write_user(struct pstore_record *record,
39562306a36Sopenharmony_ci					     const char __user *buf)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	if (record->type == PSTORE_TYPE_PMSG) {
39862306a36Sopenharmony_ci		struct ramoops_context *cxt = record->psi->data;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		if (!cxt->mprz)
40162306a36Sopenharmony_ci			return -ENOMEM;
40262306a36Sopenharmony_ci		return persistent_ram_write_user(cxt->mprz, buf, record->size);
40362306a36Sopenharmony_ci	}
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	return -EINVAL;
40662306a36Sopenharmony_ci}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_cistatic int ramoops_pstore_erase(struct pstore_record *record)
40962306a36Sopenharmony_ci{
41062306a36Sopenharmony_ci	struct ramoops_context *cxt = record->psi->data;
41162306a36Sopenharmony_ci	struct persistent_ram_zone *prz;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	switch (record->type) {
41462306a36Sopenharmony_ci	case PSTORE_TYPE_DMESG:
41562306a36Sopenharmony_ci		if (record->id >= cxt->max_dump_cnt)
41662306a36Sopenharmony_ci			return -EINVAL;
41762306a36Sopenharmony_ci		prz = cxt->dprzs[record->id];
41862306a36Sopenharmony_ci		break;
41962306a36Sopenharmony_ci	case PSTORE_TYPE_CONSOLE:
42062306a36Sopenharmony_ci		prz = cxt->cprz;
42162306a36Sopenharmony_ci		break;
42262306a36Sopenharmony_ci	case PSTORE_TYPE_FTRACE:
42362306a36Sopenharmony_ci		if (record->id >= cxt->max_ftrace_cnt)
42462306a36Sopenharmony_ci			return -EINVAL;
42562306a36Sopenharmony_ci		prz = cxt->fprzs[record->id];
42662306a36Sopenharmony_ci		break;
42762306a36Sopenharmony_ci	case PSTORE_TYPE_PMSG:
42862306a36Sopenharmony_ci		prz = cxt->mprz;
42962306a36Sopenharmony_ci		break;
43062306a36Sopenharmony_ci	default:
43162306a36Sopenharmony_ci		return -EINVAL;
43262306a36Sopenharmony_ci	}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	persistent_ram_free_old(prz);
43562306a36Sopenharmony_ci	persistent_ram_zap(prz);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	return 0;
43862306a36Sopenharmony_ci}
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_cistatic struct ramoops_context oops_cxt = {
44162306a36Sopenharmony_ci	.pstore = {
44262306a36Sopenharmony_ci		.owner	= THIS_MODULE,
44362306a36Sopenharmony_ci		.name	= "ramoops",
44462306a36Sopenharmony_ci		.open	= ramoops_pstore_open,
44562306a36Sopenharmony_ci		.read	= ramoops_pstore_read,
44662306a36Sopenharmony_ci		.write	= ramoops_pstore_write,
44762306a36Sopenharmony_ci		.write_user	= ramoops_pstore_write_user,
44862306a36Sopenharmony_ci		.erase	= ramoops_pstore_erase,
44962306a36Sopenharmony_ci	},
45062306a36Sopenharmony_ci};
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_cistatic void ramoops_free_przs(struct ramoops_context *cxt)
45362306a36Sopenharmony_ci{
45462306a36Sopenharmony_ci	int i;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	/* Free pmsg PRZ */
45762306a36Sopenharmony_ci	persistent_ram_free(&cxt->mprz);
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	/* Free console PRZ */
46062306a36Sopenharmony_ci	persistent_ram_free(&cxt->cprz);
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	/* Free dump PRZs */
46362306a36Sopenharmony_ci	if (cxt->dprzs) {
46462306a36Sopenharmony_ci		for (i = 0; i < cxt->max_dump_cnt; i++)
46562306a36Sopenharmony_ci			persistent_ram_free(&cxt->dprzs[i]);
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci		kfree(cxt->dprzs);
46862306a36Sopenharmony_ci		cxt->dprzs = NULL;
46962306a36Sopenharmony_ci		cxt->max_dump_cnt = 0;
47062306a36Sopenharmony_ci	}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	/* Free ftrace PRZs */
47362306a36Sopenharmony_ci	if (cxt->fprzs) {
47462306a36Sopenharmony_ci		for (i = 0; i < cxt->max_ftrace_cnt; i++)
47562306a36Sopenharmony_ci			persistent_ram_free(&cxt->fprzs[i]);
47662306a36Sopenharmony_ci		kfree(cxt->fprzs);
47762306a36Sopenharmony_ci		cxt->fprzs = NULL;
47862306a36Sopenharmony_ci		cxt->max_ftrace_cnt = 0;
47962306a36Sopenharmony_ci	}
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic int ramoops_init_przs(const char *name,
48362306a36Sopenharmony_ci			     struct device *dev, struct ramoops_context *cxt,
48462306a36Sopenharmony_ci			     struct persistent_ram_zone ***przs,
48562306a36Sopenharmony_ci			     phys_addr_t *paddr, size_t mem_sz,
48662306a36Sopenharmony_ci			     ssize_t record_size,
48762306a36Sopenharmony_ci			     unsigned int *cnt, u32 sig, u32 flags)
48862306a36Sopenharmony_ci{
48962306a36Sopenharmony_ci	int err = -ENOMEM;
49062306a36Sopenharmony_ci	int i;
49162306a36Sopenharmony_ci	size_t zone_sz;
49262306a36Sopenharmony_ci	struct persistent_ram_zone **prz_ar;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	/* Allocate nothing for 0 mem_sz or 0 record_size. */
49562306a36Sopenharmony_ci	if (mem_sz == 0 || record_size == 0) {
49662306a36Sopenharmony_ci		*cnt = 0;
49762306a36Sopenharmony_ci		return 0;
49862306a36Sopenharmony_ci	}
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	/*
50162306a36Sopenharmony_ci	 * If we have a negative record size, calculate it based on
50262306a36Sopenharmony_ci	 * mem_sz / *cnt. If we have a positive record size, calculate
50362306a36Sopenharmony_ci	 * cnt from mem_sz / record_size.
50462306a36Sopenharmony_ci	 */
50562306a36Sopenharmony_ci	if (record_size < 0) {
50662306a36Sopenharmony_ci		if (*cnt == 0)
50762306a36Sopenharmony_ci			return 0;
50862306a36Sopenharmony_ci		record_size = mem_sz / *cnt;
50962306a36Sopenharmony_ci		if (record_size == 0) {
51062306a36Sopenharmony_ci			dev_err(dev, "%s record size == 0 (%zu / %u)\n",
51162306a36Sopenharmony_ci				name, mem_sz, *cnt);
51262306a36Sopenharmony_ci			goto fail;
51362306a36Sopenharmony_ci		}
51462306a36Sopenharmony_ci	} else {
51562306a36Sopenharmony_ci		*cnt = mem_sz / record_size;
51662306a36Sopenharmony_ci		if (*cnt == 0) {
51762306a36Sopenharmony_ci			dev_err(dev, "%s record count == 0 (%zu / %zu)\n",
51862306a36Sopenharmony_ci				name, mem_sz, record_size);
51962306a36Sopenharmony_ci			goto fail;
52062306a36Sopenharmony_ci		}
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	if (*paddr + mem_sz - cxt->phys_addr > cxt->size) {
52462306a36Sopenharmony_ci		dev_err(dev, "no room for %s mem region (0x%zx@0x%llx) in (0x%lx@0x%llx)\n",
52562306a36Sopenharmony_ci			name,
52662306a36Sopenharmony_ci			mem_sz, (unsigned long long)*paddr,
52762306a36Sopenharmony_ci			cxt->size, (unsigned long long)cxt->phys_addr);
52862306a36Sopenharmony_ci		goto fail;
52962306a36Sopenharmony_ci	}
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	zone_sz = mem_sz / *cnt;
53262306a36Sopenharmony_ci	zone_sz = ALIGN_DOWN(zone_sz, 2);
53362306a36Sopenharmony_ci	if (!zone_sz) {
53462306a36Sopenharmony_ci		dev_err(dev, "%s zone size == 0\n", name);
53562306a36Sopenharmony_ci		goto fail;
53662306a36Sopenharmony_ci	}
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	prz_ar = kcalloc(*cnt, sizeof(**przs), GFP_KERNEL);
53962306a36Sopenharmony_ci	if (!prz_ar)
54062306a36Sopenharmony_ci		goto fail;
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	for (i = 0; i < *cnt; i++) {
54362306a36Sopenharmony_ci		char *label;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci		if (*cnt == 1)
54662306a36Sopenharmony_ci			label = kasprintf(GFP_KERNEL, "ramoops:%s", name);
54762306a36Sopenharmony_ci		else
54862306a36Sopenharmony_ci			label = kasprintf(GFP_KERNEL, "ramoops:%s(%d/%d)",
54962306a36Sopenharmony_ci					  name, i, *cnt - 1);
55062306a36Sopenharmony_ci		prz_ar[i] = persistent_ram_new(*paddr, zone_sz, sig,
55162306a36Sopenharmony_ci					       &cxt->ecc_info,
55262306a36Sopenharmony_ci					       cxt->memtype, flags, label);
55362306a36Sopenharmony_ci		kfree(label);
55462306a36Sopenharmony_ci		if (IS_ERR(prz_ar[i])) {
55562306a36Sopenharmony_ci			err = PTR_ERR(prz_ar[i]);
55662306a36Sopenharmony_ci			dev_err(dev, "failed to request %s mem region (0x%zx@0x%llx): %d\n",
55762306a36Sopenharmony_ci				name, record_size,
55862306a36Sopenharmony_ci				(unsigned long long)*paddr, err);
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci			while (i > 0) {
56162306a36Sopenharmony_ci				i--;
56262306a36Sopenharmony_ci				persistent_ram_free(&prz_ar[i]);
56362306a36Sopenharmony_ci			}
56462306a36Sopenharmony_ci			kfree(prz_ar);
56562306a36Sopenharmony_ci			prz_ar = NULL;
56662306a36Sopenharmony_ci			goto fail;
56762306a36Sopenharmony_ci		}
56862306a36Sopenharmony_ci		*paddr += zone_sz;
56962306a36Sopenharmony_ci		prz_ar[i]->type = pstore_name_to_type(name);
57062306a36Sopenharmony_ci	}
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	*przs = prz_ar;
57362306a36Sopenharmony_ci	return 0;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_cifail:
57662306a36Sopenharmony_ci	*cnt = 0;
57762306a36Sopenharmony_ci	return err;
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic int ramoops_init_prz(const char *name,
58162306a36Sopenharmony_ci			    struct device *dev, struct ramoops_context *cxt,
58262306a36Sopenharmony_ci			    struct persistent_ram_zone **prz,
58362306a36Sopenharmony_ci			    phys_addr_t *paddr, size_t sz, u32 sig)
58462306a36Sopenharmony_ci{
58562306a36Sopenharmony_ci	char *label;
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	if (!sz)
58862306a36Sopenharmony_ci		return 0;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	if (*paddr + sz - cxt->phys_addr > cxt->size) {
59162306a36Sopenharmony_ci		dev_err(dev, "no room for %s mem region (0x%zx@0x%llx) in (0x%lx@0x%llx)\n",
59262306a36Sopenharmony_ci			name, sz, (unsigned long long)*paddr,
59362306a36Sopenharmony_ci			cxt->size, (unsigned long long)cxt->phys_addr);
59462306a36Sopenharmony_ci		return -ENOMEM;
59562306a36Sopenharmony_ci	}
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	label = kasprintf(GFP_KERNEL, "ramoops:%s", name);
59862306a36Sopenharmony_ci	*prz = persistent_ram_new(*paddr, sz, sig, &cxt->ecc_info,
59962306a36Sopenharmony_ci				  cxt->memtype, PRZ_FLAG_ZAP_OLD, label);
60062306a36Sopenharmony_ci	kfree(label);
60162306a36Sopenharmony_ci	if (IS_ERR(*prz)) {
60262306a36Sopenharmony_ci		int err = PTR_ERR(*prz);
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci		dev_err(dev, "failed to request %s mem region (0x%zx@0x%llx): %d\n",
60562306a36Sopenharmony_ci			name, sz, (unsigned long long)*paddr, err);
60662306a36Sopenharmony_ci		return err;
60762306a36Sopenharmony_ci	}
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	*paddr += sz;
61062306a36Sopenharmony_ci	(*prz)->type = pstore_name_to_type(name);
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	return 0;
61362306a36Sopenharmony_ci}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci/* Read a u32 from a dt property and make sure it's safe for an int. */
61662306a36Sopenharmony_cistatic int ramoops_parse_dt_u32(struct platform_device *pdev,
61762306a36Sopenharmony_ci				const char *propname,
61862306a36Sopenharmony_ci				u32 default_value, u32 *value)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	u32 val32 = 0;
62162306a36Sopenharmony_ci	int ret;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	ret = of_property_read_u32(pdev->dev.of_node, propname, &val32);
62462306a36Sopenharmony_ci	if (ret == -EINVAL) {
62562306a36Sopenharmony_ci		/* field is missing, use default value. */
62662306a36Sopenharmony_ci		val32 = default_value;
62762306a36Sopenharmony_ci	} else if (ret < 0) {
62862306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to parse property %s: %d\n",
62962306a36Sopenharmony_ci			propname, ret);
63062306a36Sopenharmony_ci		return ret;
63162306a36Sopenharmony_ci	}
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	/* Sanity check our results. */
63462306a36Sopenharmony_ci	if (val32 > INT_MAX) {
63562306a36Sopenharmony_ci		dev_err(&pdev->dev, "%s %u > INT_MAX\n", propname, val32);
63662306a36Sopenharmony_ci		return -EOVERFLOW;
63762306a36Sopenharmony_ci	}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	*value = val32;
64062306a36Sopenharmony_ci	return 0;
64162306a36Sopenharmony_ci}
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_cistatic int ramoops_parse_dt(struct platform_device *pdev,
64462306a36Sopenharmony_ci			    struct ramoops_platform_data *pdata)
64562306a36Sopenharmony_ci{
64662306a36Sopenharmony_ci	struct device_node *of_node = pdev->dev.of_node;
64762306a36Sopenharmony_ci	struct device_node *parent_node;
64862306a36Sopenharmony_ci	struct resource *res;
64962306a36Sopenharmony_ci	u32 value;
65062306a36Sopenharmony_ci	int ret;
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	dev_dbg(&pdev->dev, "using Device Tree\n");
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
65562306a36Sopenharmony_ci	if (!res) {
65662306a36Sopenharmony_ci		dev_err(&pdev->dev,
65762306a36Sopenharmony_ci			"failed to locate DT /reserved-memory resource\n");
65862306a36Sopenharmony_ci		return -EINVAL;
65962306a36Sopenharmony_ci	}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	pdata->mem_size = resource_size(res);
66262306a36Sopenharmony_ci	pdata->mem_address = res->start;
66362306a36Sopenharmony_ci	/*
66462306a36Sopenharmony_ci	 * Setting "unbuffered" is deprecated and will be ignored if
66562306a36Sopenharmony_ci	 * "mem_type" is also specified.
66662306a36Sopenharmony_ci	 */
66762306a36Sopenharmony_ci	pdata->mem_type = of_property_read_bool(of_node, "unbuffered");
66862306a36Sopenharmony_ci	/*
66962306a36Sopenharmony_ci	 * Setting "no-dump-oops" is deprecated and will be ignored if
67062306a36Sopenharmony_ci	 * "max_reason" is also specified.
67162306a36Sopenharmony_ci	 */
67262306a36Sopenharmony_ci	if (of_property_read_bool(of_node, "no-dump-oops"))
67362306a36Sopenharmony_ci		pdata->max_reason = KMSG_DUMP_PANIC;
67462306a36Sopenharmony_ci	else
67562306a36Sopenharmony_ci		pdata->max_reason = KMSG_DUMP_OOPS;
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci#define parse_u32(name, field, default_value) {				\
67862306a36Sopenharmony_ci		ret = ramoops_parse_dt_u32(pdev, name, default_value,	\
67962306a36Sopenharmony_ci					    &value);			\
68062306a36Sopenharmony_ci		if (ret < 0)						\
68162306a36Sopenharmony_ci			return ret;					\
68262306a36Sopenharmony_ci		field = value;						\
68362306a36Sopenharmony_ci	}
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	parse_u32("mem-type", pdata->mem_type, pdata->mem_type);
68662306a36Sopenharmony_ci	parse_u32("record-size", pdata->record_size, 0);
68762306a36Sopenharmony_ci	parse_u32("console-size", pdata->console_size, 0);
68862306a36Sopenharmony_ci	parse_u32("ftrace-size", pdata->ftrace_size, 0);
68962306a36Sopenharmony_ci	parse_u32("pmsg-size", pdata->pmsg_size, 0);
69062306a36Sopenharmony_ci	parse_u32("ecc-size", pdata->ecc_info.ecc_size, 0);
69162306a36Sopenharmony_ci	parse_u32("flags", pdata->flags, 0);
69262306a36Sopenharmony_ci	parse_u32("max-reason", pdata->max_reason, pdata->max_reason);
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci#undef parse_u32
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci	/*
69762306a36Sopenharmony_ci	 * Some old Chromebooks relied on the kernel setting the
69862306a36Sopenharmony_ci	 * console_size and pmsg_size to the record size since that's
69962306a36Sopenharmony_ci	 * what the downstream kernel did.  These same Chromebooks had
70062306a36Sopenharmony_ci	 * "ramoops" straight under the root node which isn't
70162306a36Sopenharmony_ci	 * according to the current upstream bindings (though it was
70262306a36Sopenharmony_ci	 * arguably acceptable under a prior version of the bindings).
70362306a36Sopenharmony_ci	 * Let's make those old Chromebooks work by detecting that
70462306a36Sopenharmony_ci	 * we're not a child of "reserved-memory" and mimicking the
70562306a36Sopenharmony_ci	 * expected behavior.
70662306a36Sopenharmony_ci	 */
70762306a36Sopenharmony_ci	parent_node = of_get_parent(of_node);
70862306a36Sopenharmony_ci	if (!of_node_name_eq(parent_node, "reserved-memory") &&
70962306a36Sopenharmony_ci	    !pdata->console_size && !pdata->ftrace_size &&
71062306a36Sopenharmony_ci	    !pdata->pmsg_size && !pdata->ecc_info.ecc_size) {
71162306a36Sopenharmony_ci		pdata->console_size = pdata->record_size;
71262306a36Sopenharmony_ci		pdata->pmsg_size = pdata->record_size;
71362306a36Sopenharmony_ci	}
71462306a36Sopenharmony_ci	of_node_put(parent_node);
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci	return 0;
71762306a36Sopenharmony_ci}
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_cistatic int ramoops_probe(struct platform_device *pdev)
72062306a36Sopenharmony_ci{
72162306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
72262306a36Sopenharmony_ci	struct ramoops_platform_data *pdata = dev->platform_data;
72362306a36Sopenharmony_ci	struct ramoops_platform_data pdata_local;
72462306a36Sopenharmony_ci	struct ramoops_context *cxt = &oops_cxt;
72562306a36Sopenharmony_ci	size_t dump_mem_sz;
72662306a36Sopenharmony_ci	phys_addr_t paddr;
72762306a36Sopenharmony_ci	int err = -EINVAL;
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	/*
73062306a36Sopenharmony_ci	 * Only a single ramoops area allowed at a time, so fail extra
73162306a36Sopenharmony_ci	 * probes.
73262306a36Sopenharmony_ci	 */
73362306a36Sopenharmony_ci	if (cxt->max_dump_cnt) {
73462306a36Sopenharmony_ci		pr_err("already initialized\n");
73562306a36Sopenharmony_ci		goto fail_out;
73662306a36Sopenharmony_ci	}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	if (dev_of_node(dev) && !pdata) {
73962306a36Sopenharmony_ci		pdata = &pdata_local;
74062306a36Sopenharmony_ci		memset(pdata, 0, sizeof(*pdata));
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci		err = ramoops_parse_dt(pdev, pdata);
74362306a36Sopenharmony_ci		if (err < 0)
74462306a36Sopenharmony_ci			goto fail_out;
74562306a36Sopenharmony_ci	}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	/* Make sure we didn't get bogus platform data pointer. */
74862306a36Sopenharmony_ci	if (!pdata) {
74962306a36Sopenharmony_ci		pr_err("NULL platform data\n");
75062306a36Sopenharmony_ci		err = -EINVAL;
75162306a36Sopenharmony_ci		goto fail_out;
75262306a36Sopenharmony_ci	}
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci	if (!pdata->mem_size || (!pdata->record_size && !pdata->console_size &&
75562306a36Sopenharmony_ci			!pdata->ftrace_size && !pdata->pmsg_size)) {
75662306a36Sopenharmony_ci		pr_err("The memory size and the record/console size must be "
75762306a36Sopenharmony_ci			"non-zero\n");
75862306a36Sopenharmony_ci		err = -EINVAL;
75962306a36Sopenharmony_ci		goto fail_out;
76062306a36Sopenharmony_ci	}
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	if (pdata->record_size && !is_power_of_2(pdata->record_size))
76362306a36Sopenharmony_ci		pdata->record_size = rounddown_pow_of_two(pdata->record_size);
76462306a36Sopenharmony_ci	if (pdata->console_size && !is_power_of_2(pdata->console_size))
76562306a36Sopenharmony_ci		pdata->console_size = rounddown_pow_of_two(pdata->console_size);
76662306a36Sopenharmony_ci	if (pdata->ftrace_size && !is_power_of_2(pdata->ftrace_size))
76762306a36Sopenharmony_ci		pdata->ftrace_size = rounddown_pow_of_two(pdata->ftrace_size);
76862306a36Sopenharmony_ci	if (pdata->pmsg_size && !is_power_of_2(pdata->pmsg_size))
76962306a36Sopenharmony_ci		pdata->pmsg_size = rounddown_pow_of_two(pdata->pmsg_size);
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_ci	cxt->size = pdata->mem_size;
77262306a36Sopenharmony_ci	cxt->phys_addr = pdata->mem_address;
77362306a36Sopenharmony_ci	cxt->memtype = pdata->mem_type;
77462306a36Sopenharmony_ci	cxt->record_size = pdata->record_size;
77562306a36Sopenharmony_ci	cxt->console_size = pdata->console_size;
77662306a36Sopenharmony_ci	cxt->ftrace_size = pdata->ftrace_size;
77762306a36Sopenharmony_ci	cxt->pmsg_size = pdata->pmsg_size;
77862306a36Sopenharmony_ci	cxt->flags = pdata->flags;
77962306a36Sopenharmony_ci	cxt->ecc_info = pdata->ecc_info;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	paddr = cxt->phys_addr;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	dump_mem_sz = cxt->size - cxt->console_size - cxt->ftrace_size
78462306a36Sopenharmony_ci			- cxt->pmsg_size;
78562306a36Sopenharmony_ci	err = ramoops_init_przs("dmesg", dev, cxt, &cxt->dprzs, &paddr,
78662306a36Sopenharmony_ci				dump_mem_sz, cxt->record_size,
78762306a36Sopenharmony_ci				&cxt->max_dump_cnt, 0, 0);
78862306a36Sopenharmony_ci	if (err)
78962306a36Sopenharmony_ci		goto fail_init;
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci	err = ramoops_init_prz("console", dev, cxt, &cxt->cprz, &paddr,
79262306a36Sopenharmony_ci			       cxt->console_size, 0);
79362306a36Sopenharmony_ci	if (err)
79462306a36Sopenharmony_ci		goto fail_init;
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_ci	err = ramoops_init_prz("pmsg", dev, cxt, &cxt->mprz, &paddr,
79762306a36Sopenharmony_ci				cxt->pmsg_size, 0);
79862306a36Sopenharmony_ci	if (err)
79962306a36Sopenharmony_ci		goto fail_init;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	cxt->max_ftrace_cnt = (cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU)
80262306a36Sopenharmony_ci				? nr_cpu_ids
80362306a36Sopenharmony_ci				: 1;
80462306a36Sopenharmony_ci	err = ramoops_init_przs("ftrace", dev, cxt, &cxt->fprzs, &paddr,
80562306a36Sopenharmony_ci				cxt->ftrace_size, -1,
80662306a36Sopenharmony_ci				&cxt->max_ftrace_cnt, LINUX_VERSION_CODE,
80762306a36Sopenharmony_ci				(cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU)
80862306a36Sopenharmony_ci					? PRZ_FLAG_NO_LOCK : 0);
80962306a36Sopenharmony_ci	if (err)
81062306a36Sopenharmony_ci		goto fail_init;
81162306a36Sopenharmony_ci
81262306a36Sopenharmony_ci	cxt->pstore.data = cxt;
81362306a36Sopenharmony_ci	/*
81462306a36Sopenharmony_ci	 * Prepare frontend flags based on which areas are initialized.
81562306a36Sopenharmony_ci	 * For ramoops_init_przs() cases, the "max count" variable tells
81662306a36Sopenharmony_ci	 * if there are regions present. For ramoops_init_prz() cases,
81762306a36Sopenharmony_ci	 * the single region size is how to check.
81862306a36Sopenharmony_ci	 */
81962306a36Sopenharmony_ci	cxt->pstore.flags = 0;
82062306a36Sopenharmony_ci	if (cxt->max_dump_cnt) {
82162306a36Sopenharmony_ci		cxt->pstore.flags |= PSTORE_FLAGS_DMESG;
82262306a36Sopenharmony_ci		cxt->pstore.max_reason = pdata->max_reason;
82362306a36Sopenharmony_ci	}
82462306a36Sopenharmony_ci	if (cxt->console_size)
82562306a36Sopenharmony_ci		cxt->pstore.flags |= PSTORE_FLAGS_CONSOLE;
82662306a36Sopenharmony_ci	if (cxt->max_ftrace_cnt)
82762306a36Sopenharmony_ci		cxt->pstore.flags |= PSTORE_FLAGS_FTRACE;
82862306a36Sopenharmony_ci	if (cxt->pmsg_size)
82962306a36Sopenharmony_ci		cxt->pstore.flags |= PSTORE_FLAGS_PMSG;
83062306a36Sopenharmony_ci
83162306a36Sopenharmony_ci	/*
83262306a36Sopenharmony_ci	 * Since bufsize is only used for dmesg crash dumps, it
83362306a36Sopenharmony_ci	 * must match the size of the dprz record (after PRZ header
83462306a36Sopenharmony_ci	 * and ECC bytes have been accounted for).
83562306a36Sopenharmony_ci	 */
83662306a36Sopenharmony_ci	if (cxt->pstore.flags & PSTORE_FLAGS_DMESG) {
83762306a36Sopenharmony_ci		cxt->pstore.bufsize = cxt->dprzs[0]->buffer_size;
83862306a36Sopenharmony_ci		cxt->pstore.buf = kvzalloc(cxt->pstore.bufsize, GFP_KERNEL);
83962306a36Sopenharmony_ci		if (!cxt->pstore.buf) {
84062306a36Sopenharmony_ci			pr_err("cannot allocate pstore crash dump buffer\n");
84162306a36Sopenharmony_ci			err = -ENOMEM;
84262306a36Sopenharmony_ci			goto fail_clear;
84362306a36Sopenharmony_ci		}
84462306a36Sopenharmony_ci	}
84562306a36Sopenharmony_ci
84662306a36Sopenharmony_ci	err = pstore_register(&cxt->pstore);
84762306a36Sopenharmony_ci	if (err) {
84862306a36Sopenharmony_ci		pr_err("registering with pstore failed\n");
84962306a36Sopenharmony_ci		goto fail_buf;
85062306a36Sopenharmony_ci	}
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_ci	/*
85362306a36Sopenharmony_ci	 * Update the module parameter variables as well so they are visible
85462306a36Sopenharmony_ci	 * through /sys/module/ramoops/parameters/
85562306a36Sopenharmony_ci	 */
85662306a36Sopenharmony_ci	mem_size = pdata->mem_size;
85762306a36Sopenharmony_ci	mem_address = pdata->mem_address;
85862306a36Sopenharmony_ci	record_size = pdata->record_size;
85962306a36Sopenharmony_ci	ramoops_max_reason = pdata->max_reason;
86062306a36Sopenharmony_ci	ramoops_console_size = pdata->console_size;
86162306a36Sopenharmony_ci	ramoops_pmsg_size = pdata->pmsg_size;
86262306a36Sopenharmony_ci	ramoops_ftrace_size = pdata->ftrace_size;
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_ci	pr_info("using 0x%lx@0x%llx, ecc: %d\n",
86562306a36Sopenharmony_ci		cxt->size, (unsigned long long)cxt->phys_addr,
86662306a36Sopenharmony_ci		cxt->ecc_info.ecc_size);
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	return 0;
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_cifail_buf:
87162306a36Sopenharmony_ci	kvfree(cxt->pstore.buf);
87262306a36Sopenharmony_cifail_clear:
87362306a36Sopenharmony_ci	cxt->pstore.bufsize = 0;
87462306a36Sopenharmony_cifail_init:
87562306a36Sopenharmony_ci	ramoops_free_przs(cxt);
87662306a36Sopenharmony_cifail_out:
87762306a36Sopenharmony_ci	return err;
87862306a36Sopenharmony_ci}
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_cistatic void ramoops_remove(struct platform_device *pdev)
88162306a36Sopenharmony_ci{
88262306a36Sopenharmony_ci	struct ramoops_context *cxt = &oops_cxt;
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_ci	pstore_unregister(&cxt->pstore);
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_ci	kvfree(cxt->pstore.buf);
88762306a36Sopenharmony_ci	cxt->pstore.bufsize = 0;
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci	ramoops_free_przs(cxt);
89062306a36Sopenharmony_ci}
89162306a36Sopenharmony_ci
89262306a36Sopenharmony_cistatic const struct of_device_id dt_match[] = {
89362306a36Sopenharmony_ci	{ .compatible = "ramoops" },
89462306a36Sopenharmony_ci	{}
89562306a36Sopenharmony_ci};
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_cistatic struct platform_driver ramoops_driver = {
89862306a36Sopenharmony_ci	.probe		= ramoops_probe,
89962306a36Sopenharmony_ci	.remove_new	= ramoops_remove,
90062306a36Sopenharmony_ci	.driver		= {
90162306a36Sopenharmony_ci		.name		= "ramoops",
90262306a36Sopenharmony_ci		.of_match_table	= dt_match,
90362306a36Sopenharmony_ci	},
90462306a36Sopenharmony_ci};
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_cistatic inline void ramoops_unregister_dummy(void)
90762306a36Sopenharmony_ci{
90862306a36Sopenharmony_ci	platform_device_unregister(dummy);
90962306a36Sopenharmony_ci	dummy = NULL;
91062306a36Sopenharmony_ci}
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_cistatic void __init ramoops_register_dummy(void)
91362306a36Sopenharmony_ci{
91462306a36Sopenharmony_ci	struct ramoops_platform_data pdata;
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	/*
91762306a36Sopenharmony_ci	 * Prepare a dummy platform data structure to carry the module
91862306a36Sopenharmony_ci	 * parameters. If mem_size isn't set, then there are no module
91962306a36Sopenharmony_ci	 * parameters, and we can skip this.
92062306a36Sopenharmony_ci	 */
92162306a36Sopenharmony_ci	if (!mem_size)
92262306a36Sopenharmony_ci		return;
92362306a36Sopenharmony_ci
92462306a36Sopenharmony_ci	pr_info("using module parameters\n");
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	memset(&pdata, 0, sizeof(pdata));
92762306a36Sopenharmony_ci	pdata.mem_size = mem_size;
92862306a36Sopenharmony_ci	pdata.mem_address = mem_address;
92962306a36Sopenharmony_ci	pdata.mem_type = mem_type;
93062306a36Sopenharmony_ci	pdata.record_size = record_size;
93162306a36Sopenharmony_ci	pdata.console_size = ramoops_console_size;
93262306a36Sopenharmony_ci	pdata.ftrace_size = ramoops_ftrace_size;
93362306a36Sopenharmony_ci	pdata.pmsg_size = ramoops_pmsg_size;
93462306a36Sopenharmony_ci	/* If "max_reason" is set, its value has priority over "dump_oops". */
93562306a36Sopenharmony_ci	if (ramoops_max_reason >= 0)
93662306a36Sopenharmony_ci		pdata.max_reason = ramoops_max_reason;
93762306a36Sopenharmony_ci	/* Otherwise, if "dump_oops" is set, parse it into "max_reason". */
93862306a36Sopenharmony_ci	else if (ramoops_dump_oops != -1)
93962306a36Sopenharmony_ci		pdata.max_reason = ramoops_dump_oops ? KMSG_DUMP_OOPS
94062306a36Sopenharmony_ci						     : KMSG_DUMP_PANIC;
94162306a36Sopenharmony_ci	/* And if neither are explicitly set, use the default. */
94262306a36Sopenharmony_ci	else
94362306a36Sopenharmony_ci		pdata.max_reason = KMSG_DUMP_OOPS;
94462306a36Sopenharmony_ci	pdata.flags = RAMOOPS_FLAG_FTRACE_PER_CPU;
94562306a36Sopenharmony_ci
94662306a36Sopenharmony_ci	/*
94762306a36Sopenharmony_ci	 * For backwards compatibility ramoops.ecc=1 means 16 bytes ECC
94862306a36Sopenharmony_ci	 * (using 1 byte for ECC isn't much of use anyway).
94962306a36Sopenharmony_ci	 */
95062306a36Sopenharmony_ci	pdata.ecc_info.ecc_size = ramoops_ecc == 1 ? 16 : ramoops_ecc;
95162306a36Sopenharmony_ci
95262306a36Sopenharmony_ci	dummy = platform_device_register_data(NULL, "ramoops", -1,
95362306a36Sopenharmony_ci			&pdata, sizeof(pdata));
95462306a36Sopenharmony_ci	if (IS_ERR(dummy)) {
95562306a36Sopenharmony_ci		pr_info("could not create platform device: %ld\n",
95662306a36Sopenharmony_ci			PTR_ERR(dummy));
95762306a36Sopenharmony_ci		dummy = NULL;
95862306a36Sopenharmony_ci	}
95962306a36Sopenharmony_ci}
96062306a36Sopenharmony_ci
96162306a36Sopenharmony_cistatic int __init ramoops_init(void)
96262306a36Sopenharmony_ci{
96362306a36Sopenharmony_ci	int ret;
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci	ramoops_register_dummy();
96662306a36Sopenharmony_ci	ret = platform_driver_register(&ramoops_driver);
96762306a36Sopenharmony_ci	if (ret != 0)
96862306a36Sopenharmony_ci		ramoops_unregister_dummy();
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ci	return ret;
97162306a36Sopenharmony_ci}
97262306a36Sopenharmony_cipostcore_initcall(ramoops_init);
97362306a36Sopenharmony_ci
97462306a36Sopenharmony_cistatic void __exit ramoops_exit(void)
97562306a36Sopenharmony_ci{
97662306a36Sopenharmony_ci	platform_driver_unregister(&ramoops_driver);
97762306a36Sopenharmony_ci	ramoops_unregister_dummy();
97862306a36Sopenharmony_ci}
97962306a36Sopenharmony_cimodule_exit(ramoops_exit);
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
98262306a36Sopenharmony_ciMODULE_AUTHOR("Marco Stornelli <marco.stornelli@gmail.com>");
98362306a36Sopenharmony_ciMODULE_DESCRIPTION("RAM Oops/Panic logger/driver");
984