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