162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Provide a pstore intermediate backend, organized into kernel memory 462306a36Sopenharmony_ci * allocated zones that are then mapped and flushed into a single 562306a36Sopenharmony_ci * contiguous region on a storage backend of some kind (block, mtd, etc). 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/mount.h> 1462306a36Sopenharmony_ci#include <linux/printk.h> 1562306a36Sopenharmony_ci#include <linux/fs.h> 1662306a36Sopenharmony_ci#include <linux/pstore_zone.h> 1762306a36Sopenharmony_ci#include <linux/kdev_t.h> 1862306a36Sopenharmony_ci#include <linux/device.h> 1962306a36Sopenharmony_ci#include <linux/namei.h> 2062306a36Sopenharmony_ci#include <linux/fcntl.h> 2162306a36Sopenharmony_ci#include <linux/uio.h> 2262306a36Sopenharmony_ci#include <linux/writeback.h> 2362306a36Sopenharmony_ci#include "internal.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/** 2662306a36Sopenharmony_ci * struct psz_buffer - header of zone to flush to storage 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * @sig: signature to indicate header (PSZ_SIG xor PSZONE-type value) 2962306a36Sopenharmony_ci * @datalen: length of data in @data 3062306a36Sopenharmony_ci * @start: offset into @data where the beginning of the stored bytes begin 3162306a36Sopenharmony_ci * @data: zone data. 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_cistruct psz_buffer { 3462306a36Sopenharmony_ci#define PSZ_SIG (0x43474244) /* DBGC */ 3562306a36Sopenharmony_ci uint32_t sig; 3662306a36Sopenharmony_ci atomic_t datalen; 3762306a36Sopenharmony_ci atomic_t start; 3862306a36Sopenharmony_ci uint8_t data[]; 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci/** 4262306a36Sopenharmony_ci * struct psz_kmsg_header - kmsg dump-specific header to flush to storage 4362306a36Sopenharmony_ci * 4462306a36Sopenharmony_ci * @magic: magic num for kmsg dump header 4562306a36Sopenharmony_ci * @time: kmsg dump trigger time 4662306a36Sopenharmony_ci * @compressed: whether conpressed 4762306a36Sopenharmony_ci * @counter: kmsg dump counter 4862306a36Sopenharmony_ci * @reason: the kmsg dump reason (e.g. oops, panic, etc) 4962306a36Sopenharmony_ci * @data: pointer to log data 5062306a36Sopenharmony_ci * 5162306a36Sopenharmony_ci * This is a sub-header for a kmsg dump, trailing after &psz_buffer. 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_cistruct psz_kmsg_header { 5462306a36Sopenharmony_ci#define PSTORE_KMSG_HEADER_MAGIC 0x4dfc3ae5 /* Just a random number */ 5562306a36Sopenharmony_ci uint32_t magic; 5662306a36Sopenharmony_ci struct timespec64 time; 5762306a36Sopenharmony_ci bool compressed; 5862306a36Sopenharmony_ci uint32_t counter; 5962306a36Sopenharmony_ci enum kmsg_dump_reason reason; 6062306a36Sopenharmony_ci uint8_t data[]; 6162306a36Sopenharmony_ci}; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/** 6462306a36Sopenharmony_ci * struct pstore_zone - single stored buffer 6562306a36Sopenharmony_ci * 6662306a36Sopenharmony_ci * @off: zone offset of storage 6762306a36Sopenharmony_ci * @type: front-end type for this zone 6862306a36Sopenharmony_ci * @name: front-end name for this zone 6962306a36Sopenharmony_ci * @buffer: pointer to data buffer managed by this zone 7062306a36Sopenharmony_ci * @oldbuf: pointer to old data buffer 7162306a36Sopenharmony_ci * @buffer_size: bytes in @buffer->data 7262306a36Sopenharmony_ci * @should_recover: whether this zone should recover from storage 7362306a36Sopenharmony_ci * @dirty: whether the data in @buffer dirty 7462306a36Sopenharmony_ci * 7562306a36Sopenharmony_ci * zone structure in memory. 7662306a36Sopenharmony_ci */ 7762306a36Sopenharmony_cistruct pstore_zone { 7862306a36Sopenharmony_ci loff_t off; 7962306a36Sopenharmony_ci const char *name; 8062306a36Sopenharmony_ci enum pstore_type_id type; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci struct psz_buffer *buffer; 8362306a36Sopenharmony_ci struct psz_buffer *oldbuf; 8462306a36Sopenharmony_ci size_t buffer_size; 8562306a36Sopenharmony_ci bool should_recover; 8662306a36Sopenharmony_ci atomic_t dirty; 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci/** 9062306a36Sopenharmony_ci * struct psz_context - all about running state of pstore/zone 9162306a36Sopenharmony_ci * 9262306a36Sopenharmony_ci * @kpszs: kmsg dump storage zones 9362306a36Sopenharmony_ci * @ppsz: pmsg storage zone 9462306a36Sopenharmony_ci * @cpsz: console storage zone 9562306a36Sopenharmony_ci * @fpszs: ftrace storage zones 9662306a36Sopenharmony_ci * @kmsg_max_cnt: max count of @kpszs 9762306a36Sopenharmony_ci * @kmsg_read_cnt: counter of total read kmsg dumps 9862306a36Sopenharmony_ci * @kmsg_write_cnt: counter of total kmsg dump writes 9962306a36Sopenharmony_ci * @pmsg_read_cnt: counter of total read pmsg zone 10062306a36Sopenharmony_ci * @console_read_cnt: counter of total read console zone 10162306a36Sopenharmony_ci * @ftrace_max_cnt: max count of @fpszs 10262306a36Sopenharmony_ci * @ftrace_read_cnt: counter of max read ftrace zone 10362306a36Sopenharmony_ci * @oops_counter: counter of oops dumps 10462306a36Sopenharmony_ci * @panic_counter: counter of panic dumps 10562306a36Sopenharmony_ci * @recovered: whether finished recovering data from storage 10662306a36Sopenharmony_ci * @on_panic: whether panic is happening 10762306a36Sopenharmony_ci * @pstore_zone_info_lock: lock to @pstore_zone_info 10862306a36Sopenharmony_ci * @pstore_zone_info: information from backend 10962306a36Sopenharmony_ci * @pstore: structure for pstore 11062306a36Sopenharmony_ci */ 11162306a36Sopenharmony_cistruct psz_context { 11262306a36Sopenharmony_ci struct pstore_zone **kpszs; 11362306a36Sopenharmony_ci struct pstore_zone *ppsz; 11462306a36Sopenharmony_ci struct pstore_zone *cpsz; 11562306a36Sopenharmony_ci struct pstore_zone **fpszs; 11662306a36Sopenharmony_ci unsigned int kmsg_max_cnt; 11762306a36Sopenharmony_ci unsigned int kmsg_read_cnt; 11862306a36Sopenharmony_ci unsigned int kmsg_write_cnt; 11962306a36Sopenharmony_ci unsigned int pmsg_read_cnt; 12062306a36Sopenharmony_ci unsigned int console_read_cnt; 12162306a36Sopenharmony_ci unsigned int ftrace_max_cnt; 12262306a36Sopenharmony_ci unsigned int ftrace_read_cnt; 12362306a36Sopenharmony_ci /* 12462306a36Sopenharmony_ci * These counters should be calculated during recovery. 12562306a36Sopenharmony_ci * It records the oops/panic times after crashes rather than boots. 12662306a36Sopenharmony_ci */ 12762306a36Sopenharmony_ci unsigned int oops_counter; 12862306a36Sopenharmony_ci unsigned int panic_counter; 12962306a36Sopenharmony_ci atomic_t recovered; 13062306a36Sopenharmony_ci atomic_t on_panic; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* 13362306a36Sopenharmony_ci * pstore_zone_info_lock protects this entire structure during calls 13462306a36Sopenharmony_ci * to register_pstore_zone()/unregister_pstore_zone(). 13562306a36Sopenharmony_ci */ 13662306a36Sopenharmony_ci struct mutex pstore_zone_info_lock; 13762306a36Sopenharmony_ci struct pstore_zone_info *pstore_zone_info; 13862306a36Sopenharmony_ci struct pstore_info pstore; 13962306a36Sopenharmony_ci}; 14062306a36Sopenharmony_cistatic struct psz_context pstore_zone_cxt; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic void psz_flush_all_dirty_zones(struct work_struct *); 14362306a36Sopenharmony_cistatic DECLARE_DELAYED_WORK(psz_cleaner, psz_flush_all_dirty_zones); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci/** 14662306a36Sopenharmony_ci * enum psz_flush_mode - flush mode for psz_zone_write() 14762306a36Sopenharmony_ci * 14862306a36Sopenharmony_ci * @FLUSH_NONE: do not flush to storage but update data on memory 14962306a36Sopenharmony_ci * @FLUSH_PART: just flush part of data including meta data to storage 15062306a36Sopenharmony_ci * @FLUSH_META: just flush meta data of zone to storage 15162306a36Sopenharmony_ci * @FLUSH_ALL: flush all of zone 15262306a36Sopenharmony_ci */ 15362306a36Sopenharmony_cienum psz_flush_mode { 15462306a36Sopenharmony_ci FLUSH_NONE = 0, 15562306a36Sopenharmony_ci FLUSH_PART, 15662306a36Sopenharmony_ci FLUSH_META, 15762306a36Sopenharmony_ci FLUSH_ALL, 15862306a36Sopenharmony_ci}; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic inline int buffer_datalen(struct pstore_zone *zone) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci return atomic_read(&zone->buffer->datalen); 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic inline int buffer_start(struct pstore_zone *zone) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci return atomic_read(&zone->buffer->start); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic inline bool is_on_panic(void) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci return atomic_read(&pstore_zone_cxt.on_panic); 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic ssize_t psz_zone_read_buffer(struct pstore_zone *zone, char *buf, 17662306a36Sopenharmony_ci size_t len, unsigned long off) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci if (!buf || !zone || !zone->buffer) 17962306a36Sopenharmony_ci return -EINVAL; 18062306a36Sopenharmony_ci if (off > zone->buffer_size) 18162306a36Sopenharmony_ci return -EINVAL; 18262306a36Sopenharmony_ci len = min_t(size_t, len, zone->buffer_size - off); 18362306a36Sopenharmony_ci memcpy(buf, zone->buffer->data + off, len); 18462306a36Sopenharmony_ci return len; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic int psz_zone_read_oldbuf(struct pstore_zone *zone, char *buf, 18862306a36Sopenharmony_ci size_t len, unsigned long off) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci if (!buf || !zone || !zone->oldbuf) 19162306a36Sopenharmony_ci return -EINVAL; 19262306a36Sopenharmony_ci if (off > zone->buffer_size) 19362306a36Sopenharmony_ci return -EINVAL; 19462306a36Sopenharmony_ci len = min_t(size_t, len, zone->buffer_size - off); 19562306a36Sopenharmony_ci memcpy(buf, zone->oldbuf->data + off, len); 19662306a36Sopenharmony_ci return 0; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic int psz_zone_write(struct pstore_zone *zone, 20062306a36Sopenharmony_ci enum psz_flush_mode flush_mode, const char *buf, 20162306a36Sopenharmony_ci size_t len, unsigned long off) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci struct pstore_zone_info *info = pstore_zone_cxt.pstore_zone_info; 20462306a36Sopenharmony_ci ssize_t wcnt = 0; 20562306a36Sopenharmony_ci ssize_t (*writeop)(const char *buf, size_t bytes, loff_t pos); 20662306a36Sopenharmony_ci size_t wlen; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (off > zone->buffer_size) 20962306a36Sopenharmony_ci return -EINVAL; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci wlen = min_t(size_t, len, zone->buffer_size - off); 21262306a36Sopenharmony_ci if (buf && wlen) { 21362306a36Sopenharmony_ci memcpy(zone->buffer->data + off, buf, wlen); 21462306a36Sopenharmony_ci atomic_set(&zone->buffer->datalen, wlen + off); 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* avoid to damage old records */ 21862306a36Sopenharmony_ci if (!is_on_panic() && !atomic_read(&pstore_zone_cxt.recovered)) 21962306a36Sopenharmony_ci goto dirty; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci writeop = is_on_panic() ? info->panic_write : info->write; 22262306a36Sopenharmony_ci if (!writeop) 22362306a36Sopenharmony_ci goto dirty; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci switch (flush_mode) { 22662306a36Sopenharmony_ci case FLUSH_NONE: 22762306a36Sopenharmony_ci if (unlikely(buf && wlen)) 22862306a36Sopenharmony_ci goto dirty; 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci case FLUSH_PART: 23162306a36Sopenharmony_ci wcnt = writeop((const char *)zone->buffer->data + off, wlen, 23262306a36Sopenharmony_ci zone->off + sizeof(*zone->buffer) + off); 23362306a36Sopenharmony_ci if (wcnt != wlen) 23462306a36Sopenharmony_ci goto dirty; 23562306a36Sopenharmony_ci fallthrough; 23662306a36Sopenharmony_ci case FLUSH_META: 23762306a36Sopenharmony_ci wlen = sizeof(struct psz_buffer); 23862306a36Sopenharmony_ci wcnt = writeop((const char *)zone->buffer, wlen, zone->off); 23962306a36Sopenharmony_ci if (wcnt != wlen) 24062306a36Sopenharmony_ci goto dirty; 24162306a36Sopenharmony_ci break; 24262306a36Sopenharmony_ci case FLUSH_ALL: 24362306a36Sopenharmony_ci wlen = zone->buffer_size + sizeof(*zone->buffer); 24462306a36Sopenharmony_ci wcnt = writeop((const char *)zone->buffer, wlen, zone->off); 24562306a36Sopenharmony_ci if (wcnt != wlen) 24662306a36Sopenharmony_ci goto dirty; 24762306a36Sopenharmony_ci break; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci return 0; 25162306a36Sopenharmony_cidirty: 25262306a36Sopenharmony_ci /* no need to mark dirty if going to try next zone */ 25362306a36Sopenharmony_ci if (wcnt == -ENOMSG) 25462306a36Sopenharmony_ci return -ENOMSG; 25562306a36Sopenharmony_ci atomic_set(&zone->dirty, true); 25662306a36Sopenharmony_ci /* flush dirty zones nicely */ 25762306a36Sopenharmony_ci if (wcnt == -EBUSY && !is_on_panic()) 25862306a36Sopenharmony_ci schedule_delayed_work(&psz_cleaner, msecs_to_jiffies(500)); 25962306a36Sopenharmony_ci return -EBUSY; 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic int psz_flush_dirty_zone(struct pstore_zone *zone) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci int ret; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (unlikely(!zone)) 26762306a36Sopenharmony_ci return -EINVAL; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci if (unlikely(!atomic_read(&pstore_zone_cxt.recovered))) 27062306a36Sopenharmony_ci return -EBUSY; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci if (!atomic_xchg(&zone->dirty, false)) 27362306a36Sopenharmony_ci return 0; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci ret = psz_zone_write(zone, FLUSH_ALL, NULL, 0, 0); 27662306a36Sopenharmony_ci if (ret) 27762306a36Sopenharmony_ci atomic_set(&zone->dirty, true); 27862306a36Sopenharmony_ci return ret; 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic int psz_flush_dirty_zones(struct pstore_zone **zones, unsigned int cnt) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci int i, ret; 28462306a36Sopenharmony_ci struct pstore_zone *zone; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (!zones) 28762306a36Sopenharmony_ci return -EINVAL; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci for (i = 0; i < cnt; i++) { 29062306a36Sopenharmony_ci zone = zones[i]; 29162306a36Sopenharmony_ci if (!zone) 29262306a36Sopenharmony_ci return -EINVAL; 29362306a36Sopenharmony_ci ret = psz_flush_dirty_zone(zone); 29462306a36Sopenharmony_ci if (ret) 29562306a36Sopenharmony_ci return ret; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci return 0; 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic int psz_move_zone(struct pstore_zone *old, struct pstore_zone *new) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci const char *data = (const char *)old->buffer->data; 30362306a36Sopenharmony_ci int ret; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci ret = psz_zone_write(new, FLUSH_ALL, data, buffer_datalen(old), 0); 30662306a36Sopenharmony_ci if (ret) { 30762306a36Sopenharmony_ci atomic_set(&new->buffer->datalen, 0); 30862306a36Sopenharmony_ci atomic_set(&new->dirty, false); 30962306a36Sopenharmony_ci return ret; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci atomic_set(&old->buffer->datalen, 0); 31262306a36Sopenharmony_ci return 0; 31362306a36Sopenharmony_ci} 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cistatic void psz_flush_all_dirty_zones(struct work_struct *work) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci struct psz_context *cxt = &pstore_zone_cxt; 31862306a36Sopenharmony_ci int ret = 0; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci if (cxt->ppsz) 32162306a36Sopenharmony_ci ret |= psz_flush_dirty_zone(cxt->ppsz); 32262306a36Sopenharmony_ci if (cxt->cpsz) 32362306a36Sopenharmony_ci ret |= psz_flush_dirty_zone(cxt->cpsz); 32462306a36Sopenharmony_ci if (cxt->kpszs) 32562306a36Sopenharmony_ci ret |= psz_flush_dirty_zones(cxt->kpszs, cxt->kmsg_max_cnt); 32662306a36Sopenharmony_ci if (cxt->fpszs) 32762306a36Sopenharmony_ci ret |= psz_flush_dirty_zones(cxt->fpszs, cxt->ftrace_max_cnt); 32862306a36Sopenharmony_ci if (ret && cxt->pstore_zone_info) 32962306a36Sopenharmony_ci schedule_delayed_work(&psz_cleaner, msecs_to_jiffies(1000)); 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic int psz_kmsg_recover_data(struct psz_context *cxt) 33362306a36Sopenharmony_ci{ 33462306a36Sopenharmony_ci struct pstore_zone_info *info = cxt->pstore_zone_info; 33562306a36Sopenharmony_ci struct pstore_zone *zone = NULL; 33662306a36Sopenharmony_ci struct psz_buffer *buf; 33762306a36Sopenharmony_ci unsigned long i; 33862306a36Sopenharmony_ci ssize_t rcnt; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci if (!info->read) 34162306a36Sopenharmony_ci return -EINVAL; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci for (i = 0; i < cxt->kmsg_max_cnt; i++) { 34462306a36Sopenharmony_ci zone = cxt->kpszs[i]; 34562306a36Sopenharmony_ci if (unlikely(!zone)) 34662306a36Sopenharmony_ci return -EINVAL; 34762306a36Sopenharmony_ci if (atomic_read(&zone->dirty)) { 34862306a36Sopenharmony_ci unsigned int wcnt = cxt->kmsg_write_cnt; 34962306a36Sopenharmony_ci struct pstore_zone *new = cxt->kpszs[wcnt]; 35062306a36Sopenharmony_ci int ret; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci ret = psz_move_zone(zone, new); 35362306a36Sopenharmony_ci if (ret) { 35462306a36Sopenharmony_ci pr_err("move zone from %lu to %d failed\n", 35562306a36Sopenharmony_ci i, wcnt); 35662306a36Sopenharmony_ci return ret; 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci cxt->kmsg_write_cnt = (wcnt + 1) % cxt->kmsg_max_cnt; 35962306a36Sopenharmony_ci } 36062306a36Sopenharmony_ci if (!zone->should_recover) 36162306a36Sopenharmony_ci continue; 36262306a36Sopenharmony_ci buf = zone->buffer; 36362306a36Sopenharmony_ci rcnt = info->read((char *)buf, zone->buffer_size + sizeof(*buf), 36462306a36Sopenharmony_ci zone->off); 36562306a36Sopenharmony_ci if (rcnt != zone->buffer_size + sizeof(*buf)) 36662306a36Sopenharmony_ci return rcnt < 0 ? rcnt : -EIO; 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci return 0; 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_cistatic int psz_kmsg_recover_meta(struct psz_context *cxt) 37262306a36Sopenharmony_ci{ 37362306a36Sopenharmony_ci struct pstore_zone_info *info = cxt->pstore_zone_info; 37462306a36Sopenharmony_ci struct pstore_zone *zone; 37562306a36Sopenharmony_ci ssize_t rcnt, len; 37662306a36Sopenharmony_ci struct psz_buffer *buf; 37762306a36Sopenharmony_ci struct psz_kmsg_header *hdr; 37862306a36Sopenharmony_ci struct timespec64 time = { }; 37962306a36Sopenharmony_ci unsigned long i; 38062306a36Sopenharmony_ci /* 38162306a36Sopenharmony_ci * Recover may on panic, we can't allocate any memory by kmalloc. 38262306a36Sopenharmony_ci * So, we use local array instead. 38362306a36Sopenharmony_ci */ 38462306a36Sopenharmony_ci char buffer_header[sizeof(*buf) + sizeof(*hdr)] = {0}; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci if (!info->read) 38762306a36Sopenharmony_ci return -EINVAL; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci len = sizeof(*buf) + sizeof(*hdr); 39062306a36Sopenharmony_ci buf = (struct psz_buffer *)buffer_header; 39162306a36Sopenharmony_ci for (i = 0; i < cxt->kmsg_max_cnt; i++) { 39262306a36Sopenharmony_ci zone = cxt->kpszs[i]; 39362306a36Sopenharmony_ci if (unlikely(!zone)) 39462306a36Sopenharmony_ci return -EINVAL; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci rcnt = info->read((char *)buf, len, zone->off); 39762306a36Sopenharmony_ci if (rcnt == -ENOMSG) { 39862306a36Sopenharmony_ci pr_debug("%s with id %lu may be broken, skip\n", 39962306a36Sopenharmony_ci zone->name, i); 40062306a36Sopenharmony_ci continue; 40162306a36Sopenharmony_ci } else if (rcnt != len) { 40262306a36Sopenharmony_ci pr_err("read %s with id %lu failed\n", zone->name, i); 40362306a36Sopenharmony_ci return rcnt < 0 ? rcnt : -EIO; 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci if (buf->sig != zone->buffer->sig) { 40762306a36Sopenharmony_ci pr_debug("no valid data in kmsg dump zone %lu\n", i); 40862306a36Sopenharmony_ci continue; 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci if (zone->buffer_size < atomic_read(&buf->datalen)) { 41262306a36Sopenharmony_ci pr_info("found overtop zone: %s: id %lu, off %lld, size %zu\n", 41362306a36Sopenharmony_ci zone->name, i, zone->off, 41462306a36Sopenharmony_ci zone->buffer_size); 41562306a36Sopenharmony_ci continue; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci hdr = (struct psz_kmsg_header *)buf->data; 41962306a36Sopenharmony_ci if (hdr->magic != PSTORE_KMSG_HEADER_MAGIC) { 42062306a36Sopenharmony_ci pr_info("found invalid zone: %s: id %lu, off %lld, size %zu\n", 42162306a36Sopenharmony_ci zone->name, i, zone->off, 42262306a36Sopenharmony_ci zone->buffer_size); 42362306a36Sopenharmony_ci continue; 42462306a36Sopenharmony_ci } 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci /* 42762306a36Sopenharmony_ci * we get the newest zone, and the next one must be the oldest 42862306a36Sopenharmony_ci * or unused zone, because we do write one by one like a circle. 42962306a36Sopenharmony_ci */ 43062306a36Sopenharmony_ci if (hdr->time.tv_sec >= time.tv_sec) { 43162306a36Sopenharmony_ci time.tv_sec = hdr->time.tv_sec; 43262306a36Sopenharmony_ci cxt->kmsg_write_cnt = (i + 1) % cxt->kmsg_max_cnt; 43362306a36Sopenharmony_ci } 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (hdr->reason == KMSG_DUMP_OOPS) 43662306a36Sopenharmony_ci cxt->oops_counter = 43762306a36Sopenharmony_ci max(cxt->oops_counter, hdr->counter); 43862306a36Sopenharmony_ci else if (hdr->reason == KMSG_DUMP_PANIC) 43962306a36Sopenharmony_ci cxt->panic_counter = 44062306a36Sopenharmony_ci max(cxt->panic_counter, hdr->counter); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci if (!atomic_read(&buf->datalen)) { 44362306a36Sopenharmony_ci pr_debug("found erased zone: %s: id %lu, off %lld, size %zu, datalen %d\n", 44462306a36Sopenharmony_ci zone->name, i, zone->off, 44562306a36Sopenharmony_ci zone->buffer_size, 44662306a36Sopenharmony_ci atomic_read(&buf->datalen)); 44762306a36Sopenharmony_ci continue; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci if (!is_on_panic()) 45162306a36Sopenharmony_ci zone->should_recover = true; 45262306a36Sopenharmony_ci pr_debug("found nice zone: %s: id %lu, off %lld, size %zu, datalen %d\n", 45362306a36Sopenharmony_ci zone->name, i, zone->off, 45462306a36Sopenharmony_ci zone->buffer_size, atomic_read(&buf->datalen)); 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci return 0; 45862306a36Sopenharmony_ci} 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_cistatic int psz_kmsg_recover(struct psz_context *cxt) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci int ret; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci if (!cxt->kpszs) 46562306a36Sopenharmony_ci return 0; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci ret = psz_kmsg_recover_meta(cxt); 46862306a36Sopenharmony_ci if (ret) 46962306a36Sopenharmony_ci goto recover_fail; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci ret = psz_kmsg_recover_data(cxt); 47262306a36Sopenharmony_ci if (ret) 47362306a36Sopenharmony_ci goto recover_fail; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci return 0; 47662306a36Sopenharmony_cirecover_fail: 47762306a36Sopenharmony_ci pr_debug("psz_recover_kmsg failed\n"); 47862306a36Sopenharmony_ci return ret; 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_cistatic int psz_recover_zone(struct psz_context *cxt, struct pstore_zone *zone) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci struct pstore_zone_info *info = cxt->pstore_zone_info; 48462306a36Sopenharmony_ci struct psz_buffer *oldbuf, tmpbuf; 48562306a36Sopenharmony_ci int ret = 0; 48662306a36Sopenharmony_ci char *buf; 48762306a36Sopenharmony_ci ssize_t rcnt, len, start, off; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci if (!zone || zone->oldbuf) 49062306a36Sopenharmony_ci return 0; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if (is_on_panic()) { 49362306a36Sopenharmony_ci /* save data as much as possible */ 49462306a36Sopenharmony_ci psz_flush_dirty_zone(zone); 49562306a36Sopenharmony_ci return 0; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci if (unlikely(!info->read)) 49962306a36Sopenharmony_ci return -EINVAL; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci len = sizeof(struct psz_buffer); 50262306a36Sopenharmony_ci rcnt = info->read((char *)&tmpbuf, len, zone->off); 50362306a36Sopenharmony_ci if (rcnt != len) { 50462306a36Sopenharmony_ci pr_debug("read zone %s failed\n", zone->name); 50562306a36Sopenharmony_ci return rcnt < 0 ? rcnt : -EIO; 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci if (tmpbuf.sig != zone->buffer->sig) { 50962306a36Sopenharmony_ci pr_debug("no valid data in zone %s\n", zone->name); 51062306a36Sopenharmony_ci return 0; 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci if (zone->buffer_size < atomic_read(&tmpbuf.datalen) || 51462306a36Sopenharmony_ci zone->buffer_size < atomic_read(&tmpbuf.start)) { 51562306a36Sopenharmony_ci pr_info("found overtop zone: %s: off %lld, size %zu\n", 51662306a36Sopenharmony_ci zone->name, zone->off, zone->buffer_size); 51762306a36Sopenharmony_ci /* just keep going */ 51862306a36Sopenharmony_ci return 0; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci if (!atomic_read(&tmpbuf.datalen)) { 52262306a36Sopenharmony_ci pr_debug("found erased zone: %s: off %lld, size %zu, datalen %d\n", 52362306a36Sopenharmony_ci zone->name, zone->off, zone->buffer_size, 52462306a36Sopenharmony_ci atomic_read(&tmpbuf.datalen)); 52562306a36Sopenharmony_ci return 0; 52662306a36Sopenharmony_ci } 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci pr_debug("found nice zone: %s: off %lld, size %zu, datalen %d\n", 52962306a36Sopenharmony_ci zone->name, zone->off, zone->buffer_size, 53062306a36Sopenharmony_ci atomic_read(&tmpbuf.datalen)); 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci len = atomic_read(&tmpbuf.datalen) + sizeof(*oldbuf); 53362306a36Sopenharmony_ci oldbuf = kzalloc(len, GFP_KERNEL); 53462306a36Sopenharmony_ci if (!oldbuf) 53562306a36Sopenharmony_ci return -ENOMEM; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci memcpy(oldbuf, &tmpbuf, sizeof(*oldbuf)); 53862306a36Sopenharmony_ci buf = (char *)oldbuf + sizeof(*oldbuf); 53962306a36Sopenharmony_ci len = atomic_read(&oldbuf->datalen); 54062306a36Sopenharmony_ci start = atomic_read(&oldbuf->start); 54162306a36Sopenharmony_ci off = zone->off + sizeof(*oldbuf); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci /* get part of data */ 54462306a36Sopenharmony_ci rcnt = info->read(buf, len - start, off + start); 54562306a36Sopenharmony_ci if (rcnt != len - start) { 54662306a36Sopenharmony_ci pr_err("read zone %s failed\n", zone->name); 54762306a36Sopenharmony_ci ret = rcnt < 0 ? rcnt : -EIO; 54862306a36Sopenharmony_ci goto free_oldbuf; 54962306a36Sopenharmony_ci } 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci /* get the rest of data */ 55262306a36Sopenharmony_ci rcnt = info->read(buf + len - start, start, off); 55362306a36Sopenharmony_ci if (rcnt != start) { 55462306a36Sopenharmony_ci pr_err("read zone %s failed\n", zone->name); 55562306a36Sopenharmony_ci ret = rcnt < 0 ? rcnt : -EIO; 55662306a36Sopenharmony_ci goto free_oldbuf; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci zone->oldbuf = oldbuf; 56062306a36Sopenharmony_ci psz_flush_dirty_zone(zone); 56162306a36Sopenharmony_ci return 0; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_cifree_oldbuf: 56462306a36Sopenharmony_ci kfree(oldbuf); 56562306a36Sopenharmony_ci return ret; 56662306a36Sopenharmony_ci} 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_cistatic int psz_recover_zones(struct psz_context *cxt, 56962306a36Sopenharmony_ci struct pstore_zone **zones, unsigned int cnt) 57062306a36Sopenharmony_ci{ 57162306a36Sopenharmony_ci int ret; 57262306a36Sopenharmony_ci unsigned int i; 57362306a36Sopenharmony_ci struct pstore_zone *zone; 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci if (!zones) 57662306a36Sopenharmony_ci return 0; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci for (i = 0; i < cnt; i++) { 57962306a36Sopenharmony_ci zone = zones[i]; 58062306a36Sopenharmony_ci if (unlikely(!zone)) 58162306a36Sopenharmony_ci continue; 58262306a36Sopenharmony_ci ret = psz_recover_zone(cxt, zone); 58362306a36Sopenharmony_ci if (ret) 58462306a36Sopenharmony_ci goto recover_fail; 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci return 0; 58862306a36Sopenharmony_cirecover_fail: 58962306a36Sopenharmony_ci pr_debug("recover %s[%u] failed\n", zone->name, i); 59062306a36Sopenharmony_ci return ret; 59162306a36Sopenharmony_ci} 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci/** 59462306a36Sopenharmony_ci * psz_recovery() - recover data from storage 59562306a36Sopenharmony_ci * @cxt: the context of pstore/zone 59662306a36Sopenharmony_ci * 59762306a36Sopenharmony_ci * recovery means reading data back from storage after rebooting 59862306a36Sopenharmony_ci * 59962306a36Sopenharmony_ci * Return: 0 on success, others on failure. 60062306a36Sopenharmony_ci */ 60162306a36Sopenharmony_cistatic inline int psz_recovery(struct psz_context *cxt) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci int ret; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci if (atomic_read(&cxt->recovered)) 60662306a36Sopenharmony_ci return 0; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci ret = psz_kmsg_recover(cxt); 60962306a36Sopenharmony_ci if (ret) 61062306a36Sopenharmony_ci goto out; 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci ret = psz_recover_zone(cxt, cxt->ppsz); 61362306a36Sopenharmony_ci if (ret) 61462306a36Sopenharmony_ci goto out; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci ret = psz_recover_zone(cxt, cxt->cpsz); 61762306a36Sopenharmony_ci if (ret) 61862306a36Sopenharmony_ci goto out; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci ret = psz_recover_zones(cxt, cxt->fpszs, cxt->ftrace_max_cnt); 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ciout: 62362306a36Sopenharmony_ci if (unlikely(ret)) 62462306a36Sopenharmony_ci pr_err("recover failed\n"); 62562306a36Sopenharmony_ci else { 62662306a36Sopenharmony_ci pr_debug("recover end!\n"); 62762306a36Sopenharmony_ci atomic_set(&cxt->recovered, 1); 62862306a36Sopenharmony_ci } 62962306a36Sopenharmony_ci return ret; 63062306a36Sopenharmony_ci} 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_cistatic int psz_pstore_open(struct pstore_info *psi) 63362306a36Sopenharmony_ci{ 63462306a36Sopenharmony_ci struct psz_context *cxt = psi->data; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci cxt->kmsg_read_cnt = 0; 63762306a36Sopenharmony_ci cxt->pmsg_read_cnt = 0; 63862306a36Sopenharmony_ci cxt->console_read_cnt = 0; 63962306a36Sopenharmony_ci cxt->ftrace_read_cnt = 0; 64062306a36Sopenharmony_ci return 0; 64162306a36Sopenharmony_ci} 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_cistatic inline bool psz_old_ok(struct pstore_zone *zone) 64462306a36Sopenharmony_ci{ 64562306a36Sopenharmony_ci if (zone && zone->oldbuf && atomic_read(&zone->oldbuf->datalen)) 64662306a36Sopenharmony_ci return true; 64762306a36Sopenharmony_ci return false; 64862306a36Sopenharmony_ci} 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_cistatic inline bool psz_ok(struct pstore_zone *zone) 65162306a36Sopenharmony_ci{ 65262306a36Sopenharmony_ci if (zone && zone->buffer && buffer_datalen(zone)) 65362306a36Sopenharmony_ci return true; 65462306a36Sopenharmony_ci return false; 65562306a36Sopenharmony_ci} 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cistatic inline int psz_kmsg_erase(struct psz_context *cxt, 65862306a36Sopenharmony_ci struct pstore_zone *zone, struct pstore_record *record) 65962306a36Sopenharmony_ci{ 66062306a36Sopenharmony_ci struct psz_buffer *buffer = zone->buffer; 66162306a36Sopenharmony_ci struct psz_kmsg_header *hdr = 66262306a36Sopenharmony_ci (struct psz_kmsg_header *)buffer->data; 66362306a36Sopenharmony_ci size_t size; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci if (unlikely(!psz_ok(zone))) 66662306a36Sopenharmony_ci return 0; 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci /* this zone is already updated, no need to erase */ 66962306a36Sopenharmony_ci if (record->count != hdr->counter) 67062306a36Sopenharmony_ci return 0; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci size = buffer_datalen(zone) + sizeof(*zone->buffer); 67362306a36Sopenharmony_ci atomic_set(&zone->buffer->datalen, 0); 67462306a36Sopenharmony_ci if (cxt->pstore_zone_info->erase) 67562306a36Sopenharmony_ci return cxt->pstore_zone_info->erase(size, zone->off); 67662306a36Sopenharmony_ci else 67762306a36Sopenharmony_ci return psz_zone_write(zone, FLUSH_META, NULL, 0, 0); 67862306a36Sopenharmony_ci} 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_cistatic inline int psz_record_erase(struct psz_context *cxt, 68162306a36Sopenharmony_ci struct pstore_zone *zone) 68262306a36Sopenharmony_ci{ 68362306a36Sopenharmony_ci if (unlikely(!psz_old_ok(zone))) 68462306a36Sopenharmony_ci return 0; 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci kfree(zone->oldbuf); 68762306a36Sopenharmony_ci zone->oldbuf = NULL; 68862306a36Sopenharmony_ci /* 68962306a36Sopenharmony_ci * if there are new data in zone buffer, that means the old data 69062306a36Sopenharmony_ci * are already invalid. It is no need to flush 0 (erase) to 69162306a36Sopenharmony_ci * block device. 69262306a36Sopenharmony_ci */ 69362306a36Sopenharmony_ci if (!buffer_datalen(zone)) 69462306a36Sopenharmony_ci return psz_zone_write(zone, FLUSH_META, NULL, 0, 0); 69562306a36Sopenharmony_ci psz_flush_dirty_zone(zone); 69662306a36Sopenharmony_ci return 0; 69762306a36Sopenharmony_ci} 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_cistatic int psz_pstore_erase(struct pstore_record *record) 70062306a36Sopenharmony_ci{ 70162306a36Sopenharmony_ci struct psz_context *cxt = record->psi->data; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci switch (record->type) { 70462306a36Sopenharmony_ci case PSTORE_TYPE_DMESG: 70562306a36Sopenharmony_ci if (record->id >= cxt->kmsg_max_cnt) 70662306a36Sopenharmony_ci return -EINVAL; 70762306a36Sopenharmony_ci return psz_kmsg_erase(cxt, cxt->kpszs[record->id], record); 70862306a36Sopenharmony_ci case PSTORE_TYPE_PMSG: 70962306a36Sopenharmony_ci return psz_record_erase(cxt, cxt->ppsz); 71062306a36Sopenharmony_ci case PSTORE_TYPE_CONSOLE: 71162306a36Sopenharmony_ci return psz_record_erase(cxt, cxt->cpsz); 71262306a36Sopenharmony_ci case PSTORE_TYPE_FTRACE: 71362306a36Sopenharmony_ci if (record->id >= cxt->ftrace_max_cnt) 71462306a36Sopenharmony_ci return -EINVAL; 71562306a36Sopenharmony_ci return psz_record_erase(cxt, cxt->fpszs[record->id]); 71662306a36Sopenharmony_ci default: return -EINVAL; 71762306a36Sopenharmony_ci } 71862306a36Sopenharmony_ci} 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_cistatic void psz_write_kmsg_hdr(struct pstore_zone *zone, 72162306a36Sopenharmony_ci struct pstore_record *record) 72262306a36Sopenharmony_ci{ 72362306a36Sopenharmony_ci struct psz_context *cxt = record->psi->data; 72462306a36Sopenharmony_ci struct psz_buffer *buffer = zone->buffer; 72562306a36Sopenharmony_ci struct psz_kmsg_header *hdr = 72662306a36Sopenharmony_ci (struct psz_kmsg_header *)buffer->data; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci hdr->magic = PSTORE_KMSG_HEADER_MAGIC; 72962306a36Sopenharmony_ci hdr->compressed = record->compressed; 73062306a36Sopenharmony_ci hdr->time.tv_sec = record->time.tv_sec; 73162306a36Sopenharmony_ci hdr->time.tv_nsec = record->time.tv_nsec; 73262306a36Sopenharmony_ci hdr->reason = record->reason; 73362306a36Sopenharmony_ci if (hdr->reason == KMSG_DUMP_OOPS) 73462306a36Sopenharmony_ci hdr->counter = ++cxt->oops_counter; 73562306a36Sopenharmony_ci else if (hdr->reason == KMSG_DUMP_PANIC) 73662306a36Sopenharmony_ci hdr->counter = ++cxt->panic_counter; 73762306a36Sopenharmony_ci else 73862306a36Sopenharmony_ci hdr->counter = 0; 73962306a36Sopenharmony_ci} 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci/* 74262306a36Sopenharmony_ci * In case zone is broken, which may occur to MTD device, we try each zones, 74362306a36Sopenharmony_ci * start at cxt->kmsg_write_cnt. 74462306a36Sopenharmony_ci */ 74562306a36Sopenharmony_cistatic inline int notrace psz_kmsg_write_record(struct psz_context *cxt, 74662306a36Sopenharmony_ci struct pstore_record *record) 74762306a36Sopenharmony_ci{ 74862306a36Sopenharmony_ci size_t size, hlen; 74962306a36Sopenharmony_ci struct pstore_zone *zone; 75062306a36Sopenharmony_ci unsigned int i; 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci for (i = 0; i < cxt->kmsg_max_cnt; i++) { 75362306a36Sopenharmony_ci unsigned int zonenum, len; 75462306a36Sopenharmony_ci int ret; 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ci zonenum = (cxt->kmsg_write_cnt + i) % cxt->kmsg_max_cnt; 75762306a36Sopenharmony_ci zone = cxt->kpszs[zonenum]; 75862306a36Sopenharmony_ci if (unlikely(!zone)) 75962306a36Sopenharmony_ci return -ENOSPC; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci /* avoid destroying old data, allocate a new one */ 76262306a36Sopenharmony_ci len = zone->buffer_size + sizeof(*zone->buffer); 76362306a36Sopenharmony_ci zone->oldbuf = zone->buffer; 76462306a36Sopenharmony_ci zone->buffer = kzalloc(len, GFP_ATOMIC); 76562306a36Sopenharmony_ci if (!zone->buffer) { 76662306a36Sopenharmony_ci zone->buffer = zone->oldbuf; 76762306a36Sopenharmony_ci return -ENOMEM; 76862306a36Sopenharmony_ci } 76962306a36Sopenharmony_ci zone->buffer->sig = zone->oldbuf->sig; 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci pr_debug("write %s to zone id %d\n", zone->name, zonenum); 77262306a36Sopenharmony_ci psz_write_kmsg_hdr(zone, record); 77362306a36Sopenharmony_ci hlen = sizeof(struct psz_kmsg_header); 77462306a36Sopenharmony_ci size = min_t(size_t, record->size, zone->buffer_size - hlen); 77562306a36Sopenharmony_ci ret = psz_zone_write(zone, FLUSH_ALL, record->buf, size, hlen); 77662306a36Sopenharmony_ci if (likely(!ret || ret != -ENOMSG)) { 77762306a36Sopenharmony_ci cxt->kmsg_write_cnt = zonenum + 1; 77862306a36Sopenharmony_ci cxt->kmsg_write_cnt %= cxt->kmsg_max_cnt; 77962306a36Sopenharmony_ci /* no need to try next zone, free last zone buffer */ 78062306a36Sopenharmony_ci kfree(zone->oldbuf); 78162306a36Sopenharmony_ci zone->oldbuf = NULL; 78262306a36Sopenharmony_ci return ret; 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci pr_debug("zone %u may be broken, try next dmesg zone\n", 78662306a36Sopenharmony_ci zonenum); 78762306a36Sopenharmony_ci kfree(zone->buffer); 78862306a36Sopenharmony_ci zone->buffer = zone->oldbuf; 78962306a36Sopenharmony_ci zone->oldbuf = NULL; 79062306a36Sopenharmony_ci } 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci return -EBUSY; 79362306a36Sopenharmony_ci} 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_cistatic int notrace psz_kmsg_write(struct psz_context *cxt, 79662306a36Sopenharmony_ci struct pstore_record *record) 79762306a36Sopenharmony_ci{ 79862306a36Sopenharmony_ci int ret; 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci /* 80162306a36Sopenharmony_ci * Explicitly only take the first part of any new crash. 80262306a36Sopenharmony_ci * If our buffer is larger than kmsg_bytes, this can never happen, 80362306a36Sopenharmony_ci * and if our buffer is smaller than kmsg_bytes, we don't want the 80462306a36Sopenharmony_ci * report split across multiple records. 80562306a36Sopenharmony_ci */ 80662306a36Sopenharmony_ci if (record->part != 1) 80762306a36Sopenharmony_ci return -ENOSPC; 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci if (!cxt->kpszs) 81062306a36Sopenharmony_ci return -ENOSPC; 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci ret = psz_kmsg_write_record(cxt, record); 81362306a36Sopenharmony_ci if (!ret && is_on_panic()) { 81462306a36Sopenharmony_ci /* ensure all data are flushed to storage when panic */ 81562306a36Sopenharmony_ci pr_debug("try to flush other dirty zones\n"); 81662306a36Sopenharmony_ci psz_flush_all_dirty_zones(NULL); 81762306a36Sopenharmony_ci } 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci /* always return 0 as we had handled it on buffer */ 82062306a36Sopenharmony_ci return 0; 82162306a36Sopenharmony_ci} 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_cistatic int notrace psz_record_write(struct pstore_zone *zone, 82462306a36Sopenharmony_ci struct pstore_record *record) 82562306a36Sopenharmony_ci{ 82662306a36Sopenharmony_ci size_t start, rem; 82762306a36Sopenharmony_ci bool is_full_data = false; 82862306a36Sopenharmony_ci char *buf; 82962306a36Sopenharmony_ci int cnt; 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci if (!zone || !record) 83262306a36Sopenharmony_ci return -ENOSPC; 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci if (atomic_read(&zone->buffer->datalen) >= zone->buffer_size) 83562306a36Sopenharmony_ci is_full_data = true; 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci cnt = record->size; 83862306a36Sopenharmony_ci buf = record->buf; 83962306a36Sopenharmony_ci if (unlikely(cnt > zone->buffer_size)) { 84062306a36Sopenharmony_ci buf += cnt - zone->buffer_size; 84162306a36Sopenharmony_ci cnt = zone->buffer_size; 84262306a36Sopenharmony_ci } 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci start = buffer_start(zone); 84562306a36Sopenharmony_ci rem = zone->buffer_size - start; 84662306a36Sopenharmony_ci if (unlikely(rem < cnt)) { 84762306a36Sopenharmony_ci psz_zone_write(zone, FLUSH_PART, buf, rem, start); 84862306a36Sopenharmony_ci buf += rem; 84962306a36Sopenharmony_ci cnt -= rem; 85062306a36Sopenharmony_ci start = 0; 85162306a36Sopenharmony_ci is_full_data = true; 85262306a36Sopenharmony_ci } 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci atomic_set(&zone->buffer->start, cnt + start); 85562306a36Sopenharmony_ci psz_zone_write(zone, FLUSH_PART, buf, cnt, start); 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci /** 85862306a36Sopenharmony_ci * psz_zone_write will set datalen as start + cnt. 85962306a36Sopenharmony_ci * It work if actual data length lesser than buffer size. 86062306a36Sopenharmony_ci * If data length greater than buffer size, pmsg will rewrite to 86162306a36Sopenharmony_ci * beginning of zone, which make buffer->datalen wrongly. 86262306a36Sopenharmony_ci * So we should reset datalen as buffer size once actual data length 86362306a36Sopenharmony_ci * greater than buffer size. 86462306a36Sopenharmony_ci */ 86562306a36Sopenharmony_ci if (is_full_data) { 86662306a36Sopenharmony_ci atomic_set(&zone->buffer->datalen, zone->buffer_size); 86762306a36Sopenharmony_ci psz_zone_write(zone, FLUSH_META, NULL, 0, 0); 86862306a36Sopenharmony_ci } 86962306a36Sopenharmony_ci return 0; 87062306a36Sopenharmony_ci} 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_cistatic int notrace psz_pstore_write(struct pstore_record *record) 87362306a36Sopenharmony_ci{ 87462306a36Sopenharmony_ci struct psz_context *cxt = record->psi->data; 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci if (record->type == PSTORE_TYPE_DMESG && 87762306a36Sopenharmony_ci record->reason == KMSG_DUMP_PANIC) 87862306a36Sopenharmony_ci atomic_set(&cxt->on_panic, 1); 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci /* 88162306a36Sopenharmony_ci * if on panic, do not write except panic records 88262306a36Sopenharmony_ci * Fix case that panic_write prints log which wakes up console backend. 88362306a36Sopenharmony_ci */ 88462306a36Sopenharmony_ci if (is_on_panic() && record->type != PSTORE_TYPE_DMESG) 88562306a36Sopenharmony_ci return -EBUSY; 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci switch (record->type) { 88862306a36Sopenharmony_ci case PSTORE_TYPE_DMESG: 88962306a36Sopenharmony_ci return psz_kmsg_write(cxt, record); 89062306a36Sopenharmony_ci case PSTORE_TYPE_CONSOLE: 89162306a36Sopenharmony_ci return psz_record_write(cxt->cpsz, record); 89262306a36Sopenharmony_ci case PSTORE_TYPE_PMSG: 89362306a36Sopenharmony_ci return psz_record_write(cxt->ppsz, record); 89462306a36Sopenharmony_ci case PSTORE_TYPE_FTRACE: { 89562306a36Sopenharmony_ci int zonenum = smp_processor_id(); 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci if (!cxt->fpszs) 89862306a36Sopenharmony_ci return -ENOSPC; 89962306a36Sopenharmony_ci return psz_record_write(cxt->fpszs[zonenum], record); 90062306a36Sopenharmony_ci } 90162306a36Sopenharmony_ci default: 90262306a36Sopenharmony_ci return -EINVAL; 90362306a36Sopenharmony_ci } 90462306a36Sopenharmony_ci} 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_cistatic struct pstore_zone *psz_read_next_zone(struct psz_context *cxt) 90762306a36Sopenharmony_ci{ 90862306a36Sopenharmony_ci struct pstore_zone *zone = NULL; 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci while (cxt->kmsg_read_cnt < cxt->kmsg_max_cnt) { 91162306a36Sopenharmony_ci zone = cxt->kpszs[cxt->kmsg_read_cnt++]; 91262306a36Sopenharmony_ci if (psz_ok(zone)) 91362306a36Sopenharmony_ci return zone; 91462306a36Sopenharmony_ci } 91562306a36Sopenharmony_ci 91662306a36Sopenharmony_ci if (cxt->ftrace_read_cnt < cxt->ftrace_max_cnt) 91762306a36Sopenharmony_ci /* 91862306a36Sopenharmony_ci * No need psz_old_ok(). Let psz_ftrace_read() do so for 91962306a36Sopenharmony_ci * combination. psz_ftrace_read() should traverse over 92062306a36Sopenharmony_ci * all zones in case of some zone without data. 92162306a36Sopenharmony_ci */ 92262306a36Sopenharmony_ci return cxt->fpszs[cxt->ftrace_read_cnt++]; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci if (cxt->pmsg_read_cnt == 0) { 92562306a36Sopenharmony_ci cxt->pmsg_read_cnt++; 92662306a36Sopenharmony_ci zone = cxt->ppsz; 92762306a36Sopenharmony_ci if (psz_old_ok(zone)) 92862306a36Sopenharmony_ci return zone; 92962306a36Sopenharmony_ci } 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci if (cxt->console_read_cnt == 0) { 93262306a36Sopenharmony_ci cxt->console_read_cnt++; 93362306a36Sopenharmony_ci zone = cxt->cpsz; 93462306a36Sopenharmony_ci if (psz_old_ok(zone)) 93562306a36Sopenharmony_ci return zone; 93662306a36Sopenharmony_ci } 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci return NULL; 93962306a36Sopenharmony_ci} 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_cistatic int psz_kmsg_read_hdr(struct pstore_zone *zone, 94262306a36Sopenharmony_ci struct pstore_record *record) 94362306a36Sopenharmony_ci{ 94462306a36Sopenharmony_ci struct psz_buffer *buffer = zone->buffer; 94562306a36Sopenharmony_ci struct psz_kmsg_header *hdr = 94662306a36Sopenharmony_ci (struct psz_kmsg_header *)buffer->data; 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci if (hdr->magic != PSTORE_KMSG_HEADER_MAGIC) 94962306a36Sopenharmony_ci return -EINVAL; 95062306a36Sopenharmony_ci record->compressed = hdr->compressed; 95162306a36Sopenharmony_ci record->time.tv_sec = hdr->time.tv_sec; 95262306a36Sopenharmony_ci record->time.tv_nsec = hdr->time.tv_nsec; 95362306a36Sopenharmony_ci record->reason = hdr->reason; 95462306a36Sopenharmony_ci record->count = hdr->counter; 95562306a36Sopenharmony_ci return 0; 95662306a36Sopenharmony_ci} 95762306a36Sopenharmony_ci 95862306a36Sopenharmony_cistatic ssize_t psz_kmsg_read(struct pstore_zone *zone, 95962306a36Sopenharmony_ci struct pstore_record *record) 96062306a36Sopenharmony_ci{ 96162306a36Sopenharmony_ci ssize_t size, hlen = 0; 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_ci size = buffer_datalen(zone); 96462306a36Sopenharmony_ci /* Clear and skip this kmsg dump record if it has no valid header */ 96562306a36Sopenharmony_ci if (psz_kmsg_read_hdr(zone, record)) { 96662306a36Sopenharmony_ci atomic_set(&zone->buffer->datalen, 0); 96762306a36Sopenharmony_ci atomic_set(&zone->dirty, 0); 96862306a36Sopenharmony_ci return -ENOMSG; 96962306a36Sopenharmony_ci } 97062306a36Sopenharmony_ci size -= sizeof(struct psz_kmsg_header); 97162306a36Sopenharmony_ci 97262306a36Sopenharmony_ci if (!record->compressed) { 97362306a36Sopenharmony_ci char *buf = kasprintf(GFP_KERNEL, "%s: Total %d times\n", 97462306a36Sopenharmony_ci kmsg_dump_reason_str(record->reason), 97562306a36Sopenharmony_ci record->count); 97662306a36Sopenharmony_ci hlen = strlen(buf); 97762306a36Sopenharmony_ci record->buf = krealloc(buf, hlen + size, GFP_KERNEL); 97862306a36Sopenharmony_ci if (!record->buf) { 97962306a36Sopenharmony_ci kfree(buf); 98062306a36Sopenharmony_ci return -ENOMEM; 98162306a36Sopenharmony_ci } 98262306a36Sopenharmony_ci } else { 98362306a36Sopenharmony_ci record->buf = kmalloc(size, GFP_KERNEL); 98462306a36Sopenharmony_ci if (!record->buf) 98562306a36Sopenharmony_ci return -ENOMEM; 98662306a36Sopenharmony_ci } 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci size = psz_zone_read_buffer(zone, record->buf + hlen, size, 98962306a36Sopenharmony_ci sizeof(struct psz_kmsg_header)); 99062306a36Sopenharmony_ci if (unlikely(size < 0)) { 99162306a36Sopenharmony_ci kfree(record->buf); 99262306a36Sopenharmony_ci return -ENOMSG; 99362306a36Sopenharmony_ci } 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci return size + hlen; 99662306a36Sopenharmony_ci} 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci/* try to combine all ftrace zones */ 99962306a36Sopenharmony_cistatic ssize_t psz_ftrace_read(struct pstore_zone *zone, 100062306a36Sopenharmony_ci struct pstore_record *record) 100162306a36Sopenharmony_ci{ 100262306a36Sopenharmony_ci struct psz_context *cxt; 100362306a36Sopenharmony_ci struct psz_buffer *buf; 100462306a36Sopenharmony_ci int ret; 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci if (!zone || !record) 100762306a36Sopenharmony_ci return -ENOSPC; 100862306a36Sopenharmony_ci 100962306a36Sopenharmony_ci if (!psz_old_ok(zone)) 101062306a36Sopenharmony_ci goto out; 101162306a36Sopenharmony_ci 101262306a36Sopenharmony_ci buf = (struct psz_buffer *)zone->oldbuf; 101362306a36Sopenharmony_ci if (!buf) 101462306a36Sopenharmony_ci return -ENOMSG; 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_ci ret = pstore_ftrace_combine_log(&record->buf, &record->size, 101762306a36Sopenharmony_ci (char *)buf->data, atomic_read(&buf->datalen)); 101862306a36Sopenharmony_ci if (unlikely(ret)) 101962306a36Sopenharmony_ci return ret; 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ciout: 102262306a36Sopenharmony_ci cxt = record->psi->data; 102362306a36Sopenharmony_ci if (cxt->ftrace_read_cnt < cxt->ftrace_max_cnt) 102462306a36Sopenharmony_ci /* then, read next ftrace zone */ 102562306a36Sopenharmony_ci return -ENOMSG; 102662306a36Sopenharmony_ci record->id = 0; 102762306a36Sopenharmony_ci return record->size ? record->size : -ENOMSG; 102862306a36Sopenharmony_ci} 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_cistatic ssize_t psz_record_read(struct pstore_zone *zone, 103162306a36Sopenharmony_ci struct pstore_record *record) 103262306a36Sopenharmony_ci{ 103362306a36Sopenharmony_ci size_t len; 103462306a36Sopenharmony_ci struct psz_buffer *buf; 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_ci if (!zone || !record) 103762306a36Sopenharmony_ci return -ENOSPC; 103862306a36Sopenharmony_ci 103962306a36Sopenharmony_ci buf = (struct psz_buffer *)zone->oldbuf; 104062306a36Sopenharmony_ci if (!buf) 104162306a36Sopenharmony_ci return -ENOMSG; 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_ci len = atomic_read(&buf->datalen); 104462306a36Sopenharmony_ci record->buf = kmalloc(len, GFP_KERNEL); 104562306a36Sopenharmony_ci if (!record->buf) 104662306a36Sopenharmony_ci return -ENOMEM; 104762306a36Sopenharmony_ci 104862306a36Sopenharmony_ci if (unlikely(psz_zone_read_oldbuf(zone, record->buf, len, 0))) { 104962306a36Sopenharmony_ci kfree(record->buf); 105062306a36Sopenharmony_ci return -ENOMSG; 105162306a36Sopenharmony_ci } 105262306a36Sopenharmony_ci 105362306a36Sopenharmony_ci return len; 105462306a36Sopenharmony_ci} 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_cistatic ssize_t psz_pstore_read(struct pstore_record *record) 105762306a36Sopenharmony_ci{ 105862306a36Sopenharmony_ci struct psz_context *cxt = record->psi->data; 105962306a36Sopenharmony_ci ssize_t (*readop)(struct pstore_zone *zone, 106062306a36Sopenharmony_ci struct pstore_record *record); 106162306a36Sopenharmony_ci struct pstore_zone *zone; 106262306a36Sopenharmony_ci ssize_t ret; 106362306a36Sopenharmony_ci 106462306a36Sopenharmony_ci /* before read, we must recover from storage */ 106562306a36Sopenharmony_ci ret = psz_recovery(cxt); 106662306a36Sopenharmony_ci if (ret) 106762306a36Sopenharmony_ci return ret; 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_cinext_zone: 107062306a36Sopenharmony_ci zone = psz_read_next_zone(cxt); 107162306a36Sopenharmony_ci if (!zone) 107262306a36Sopenharmony_ci return 0; 107362306a36Sopenharmony_ci 107462306a36Sopenharmony_ci record->type = zone->type; 107562306a36Sopenharmony_ci switch (record->type) { 107662306a36Sopenharmony_ci case PSTORE_TYPE_DMESG: 107762306a36Sopenharmony_ci readop = psz_kmsg_read; 107862306a36Sopenharmony_ci record->id = cxt->kmsg_read_cnt - 1; 107962306a36Sopenharmony_ci break; 108062306a36Sopenharmony_ci case PSTORE_TYPE_FTRACE: 108162306a36Sopenharmony_ci readop = psz_ftrace_read; 108262306a36Sopenharmony_ci break; 108362306a36Sopenharmony_ci case PSTORE_TYPE_CONSOLE: 108462306a36Sopenharmony_ci case PSTORE_TYPE_PMSG: 108562306a36Sopenharmony_ci readop = psz_record_read; 108662306a36Sopenharmony_ci break; 108762306a36Sopenharmony_ci default: 108862306a36Sopenharmony_ci goto next_zone; 108962306a36Sopenharmony_ci } 109062306a36Sopenharmony_ci 109162306a36Sopenharmony_ci ret = readop(zone, record); 109262306a36Sopenharmony_ci if (ret == -ENOMSG) 109362306a36Sopenharmony_ci goto next_zone; 109462306a36Sopenharmony_ci return ret; 109562306a36Sopenharmony_ci} 109662306a36Sopenharmony_ci 109762306a36Sopenharmony_cistatic struct psz_context pstore_zone_cxt = { 109862306a36Sopenharmony_ci .pstore_zone_info_lock = 109962306a36Sopenharmony_ci __MUTEX_INITIALIZER(pstore_zone_cxt.pstore_zone_info_lock), 110062306a36Sopenharmony_ci .recovered = ATOMIC_INIT(0), 110162306a36Sopenharmony_ci .on_panic = ATOMIC_INIT(0), 110262306a36Sopenharmony_ci .pstore = { 110362306a36Sopenharmony_ci .owner = THIS_MODULE, 110462306a36Sopenharmony_ci .open = psz_pstore_open, 110562306a36Sopenharmony_ci .read = psz_pstore_read, 110662306a36Sopenharmony_ci .write = psz_pstore_write, 110762306a36Sopenharmony_ci .erase = psz_pstore_erase, 110862306a36Sopenharmony_ci }, 110962306a36Sopenharmony_ci}; 111062306a36Sopenharmony_ci 111162306a36Sopenharmony_cistatic void psz_free_zone(struct pstore_zone **pszone) 111262306a36Sopenharmony_ci{ 111362306a36Sopenharmony_ci struct pstore_zone *zone = *pszone; 111462306a36Sopenharmony_ci 111562306a36Sopenharmony_ci if (!zone) 111662306a36Sopenharmony_ci return; 111762306a36Sopenharmony_ci 111862306a36Sopenharmony_ci kfree(zone->buffer); 111962306a36Sopenharmony_ci kfree(zone); 112062306a36Sopenharmony_ci *pszone = NULL; 112162306a36Sopenharmony_ci} 112262306a36Sopenharmony_ci 112362306a36Sopenharmony_cistatic void psz_free_zones(struct pstore_zone ***pszones, unsigned int *cnt) 112462306a36Sopenharmony_ci{ 112562306a36Sopenharmony_ci struct pstore_zone **zones = *pszones; 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci if (!zones) 112862306a36Sopenharmony_ci return; 112962306a36Sopenharmony_ci 113062306a36Sopenharmony_ci while (*cnt > 0) { 113162306a36Sopenharmony_ci (*cnt)--; 113262306a36Sopenharmony_ci psz_free_zone(&(zones[*cnt])); 113362306a36Sopenharmony_ci } 113462306a36Sopenharmony_ci kfree(zones); 113562306a36Sopenharmony_ci *pszones = NULL; 113662306a36Sopenharmony_ci} 113762306a36Sopenharmony_ci 113862306a36Sopenharmony_cistatic void psz_free_all_zones(struct psz_context *cxt) 113962306a36Sopenharmony_ci{ 114062306a36Sopenharmony_ci if (cxt->kpszs) 114162306a36Sopenharmony_ci psz_free_zones(&cxt->kpszs, &cxt->kmsg_max_cnt); 114262306a36Sopenharmony_ci if (cxt->ppsz) 114362306a36Sopenharmony_ci psz_free_zone(&cxt->ppsz); 114462306a36Sopenharmony_ci if (cxt->cpsz) 114562306a36Sopenharmony_ci psz_free_zone(&cxt->cpsz); 114662306a36Sopenharmony_ci if (cxt->fpszs) 114762306a36Sopenharmony_ci psz_free_zones(&cxt->fpszs, &cxt->ftrace_max_cnt); 114862306a36Sopenharmony_ci} 114962306a36Sopenharmony_ci 115062306a36Sopenharmony_cistatic struct pstore_zone *psz_init_zone(enum pstore_type_id type, 115162306a36Sopenharmony_ci loff_t *off, size_t size) 115262306a36Sopenharmony_ci{ 115362306a36Sopenharmony_ci struct pstore_zone_info *info = pstore_zone_cxt.pstore_zone_info; 115462306a36Sopenharmony_ci struct pstore_zone *zone; 115562306a36Sopenharmony_ci const char *name = pstore_type_to_name(type); 115662306a36Sopenharmony_ci 115762306a36Sopenharmony_ci if (!size) 115862306a36Sopenharmony_ci return NULL; 115962306a36Sopenharmony_ci 116062306a36Sopenharmony_ci if (*off + size > info->total_size) { 116162306a36Sopenharmony_ci pr_err("no room for %s (0x%zx@0x%llx over 0x%lx)\n", 116262306a36Sopenharmony_ci name, size, *off, info->total_size); 116362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 116462306a36Sopenharmony_ci } 116562306a36Sopenharmony_ci 116662306a36Sopenharmony_ci zone = kzalloc(sizeof(struct pstore_zone), GFP_KERNEL); 116762306a36Sopenharmony_ci if (!zone) 116862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 116962306a36Sopenharmony_ci 117062306a36Sopenharmony_ci zone->buffer = kmalloc(size, GFP_KERNEL); 117162306a36Sopenharmony_ci if (!zone->buffer) { 117262306a36Sopenharmony_ci kfree(zone); 117362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 117462306a36Sopenharmony_ci } 117562306a36Sopenharmony_ci memset(zone->buffer, 0xFF, size); 117662306a36Sopenharmony_ci zone->off = *off; 117762306a36Sopenharmony_ci zone->name = name; 117862306a36Sopenharmony_ci zone->type = type; 117962306a36Sopenharmony_ci zone->buffer_size = size - sizeof(struct psz_buffer); 118062306a36Sopenharmony_ci zone->buffer->sig = type ^ PSZ_SIG; 118162306a36Sopenharmony_ci zone->oldbuf = NULL; 118262306a36Sopenharmony_ci atomic_set(&zone->dirty, 0); 118362306a36Sopenharmony_ci atomic_set(&zone->buffer->datalen, 0); 118462306a36Sopenharmony_ci atomic_set(&zone->buffer->start, 0); 118562306a36Sopenharmony_ci 118662306a36Sopenharmony_ci *off += size; 118762306a36Sopenharmony_ci 118862306a36Sopenharmony_ci pr_debug("pszone %s: off 0x%llx, %zu header, %zu data\n", zone->name, 118962306a36Sopenharmony_ci zone->off, sizeof(*zone->buffer), zone->buffer_size); 119062306a36Sopenharmony_ci return zone; 119162306a36Sopenharmony_ci} 119262306a36Sopenharmony_ci 119362306a36Sopenharmony_cistatic struct pstore_zone **psz_init_zones(enum pstore_type_id type, 119462306a36Sopenharmony_ci loff_t *off, size_t total_size, ssize_t record_size, 119562306a36Sopenharmony_ci unsigned int *cnt) 119662306a36Sopenharmony_ci{ 119762306a36Sopenharmony_ci struct pstore_zone_info *info = pstore_zone_cxt.pstore_zone_info; 119862306a36Sopenharmony_ci struct pstore_zone **zones, *zone; 119962306a36Sopenharmony_ci const char *name = pstore_type_to_name(type); 120062306a36Sopenharmony_ci int c, i; 120162306a36Sopenharmony_ci 120262306a36Sopenharmony_ci *cnt = 0; 120362306a36Sopenharmony_ci if (!total_size || !record_size) 120462306a36Sopenharmony_ci return NULL; 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ci if (*off + total_size > info->total_size) { 120762306a36Sopenharmony_ci pr_err("no room for zones %s (0x%zx@0x%llx over 0x%lx)\n", 120862306a36Sopenharmony_ci name, total_size, *off, info->total_size); 120962306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 121062306a36Sopenharmony_ci } 121162306a36Sopenharmony_ci 121262306a36Sopenharmony_ci c = total_size / record_size; 121362306a36Sopenharmony_ci zones = kcalloc(c, sizeof(*zones), GFP_KERNEL); 121462306a36Sopenharmony_ci if (!zones) { 121562306a36Sopenharmony_ci pr_err("allocate for zones %s failed\n", name); 121662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 121762306a36Sopenharmony_ci } 121862306a36Sopenharmony_ci memset(zones, 0, c * sizeof(*zones)); 121962306a36Sopenharmony_ci 122062306a36Sopenharmony_ci for (i = 0; i < c; i++) { 122162306a36Sopenharmony_ci zone = psz_init_zone(type, off, record_size); 122262306a36Sopenharmony_ci if (!zone || IS_ERR(zone)) { 122362306a36Sopenharmony_ci pr_err("initialize zones %s failed\n", name); 122462306a36Sopenharmony_ci psz_free_zones(&zones, &i); 122562306a36Sopenharmony_ci return (void *)zone; 122662306a36Sopenharmony_ci } 122762306a36Sopenharmony_ci zones[i] = zone; 122862306a36Sopenharmony_ci } 122962306a36Sopenharmony_ci 123062306a36Sopenharmony_ci *cnt = c; 123162306a36Sopenharmony_ci return zones; 123262306a36Sopenharmony_ci} 123362306a36Sopenharmony_ci 123462306a36Sopenharmony_cistatic int psz_alloc_zones(struct psz_context *cxt) 123562306a36Sopenharmony_ci{ 123662306a36Sopenharmony_ci struct pstore_zone_info *info = cxt->pstore_zone_info; 123762306a36Sopenharmony_ci loff_t off = 0; 123862306a36Sopenharmony_ci int err; 123962306a36Sopenharmony_ci size_t off_size = 0; 124062306a36Sopenharmony_ci 124162306a36Sopenharmony_ci off_size += info->pmsg_size; 124262306a36Sopenharmony_ci cxt->ppsz = psz_init_zone(PSTORE_TYPE_PMSG, &off, info->pmsg_size); 124362306a36Sopenharmony_ci if (IS_ERR(cxt->ppsz)) { 124462306a36Sopenharmony_ci err = PTR_ERR(cxt->ppsz); 124562306a36Sopenharmony_ci cxt->ppsz = NULL; 124662306a36Sopenharmony_ci goto free_out; 124762306a36Sopenharmony_ci } 124862306a36Sopenharmony_ci 124962306a36Sopenharmony_ci off_size += info->console_size; 125062306a36Sopenharmony_ci cxt->cpsz = psz_init_zone(PSTORE_TYPE_CONSOLE, &off, 125162306a36Sopenharmony_ci info->console_size); 125262306a36Sopenharmony_ci if (IS_ERR(cxt->cpsz)) { 125362306a36Sopenharmony_ci err = PTR_ERR(cxt->cpsz); 125462306a36Sopenharmony_ci cxt->cpsz = NULL; 125562306a36Sopenharmony_ci goto free_out; 125662306a36Sopenharmony_ci } 125762306a36Sopenharmony_ci 125862306a36Sopenharmony_ci off_size += info->ftrace_size; 125962306a36Sopenharmony_ci cxt->fpszs = psz_init_zones(PSTORE_TYPE_FTRACE, &off, 126062306a36Sopenharmony_ci info->ftrace_size, 126162306a36Sopenharmony_ci info->ftrace_size / nr_cpu_ids, 126262306a36Sopenharmony_ci &cxt->ftrace_max_cnt); 126362306a36Sopenharmony_ci if (IS_ERR(cxt->fpszs)) { 126462306a36Sopenharmony_ci err = PTR_ERR(cxt->fpszs); 126562306a36Sopenharmony_ci cxt->fpszs = NULL; 126662306a36Sopenharmony_ci goto free_out; 126762306a36Sopenharmony_ci } 126862306a36Sopenharmony_ci 126962306a36Sopenharmony_ci cxt->kpszs = psz_init_zones(PSTORE_TYPE_DMESG, &off, 127062306a36Sopenharmony_ci info->total_size - off_size, 127162306a36Sopenharmony_ci info->kmsg_size, &cxt->kmsg_max_cnt); 127262306a36Sopenharmony_ci if (IS_ERR(cxt->kpszs)) { 127362306a36Sopenharmony_ci err = PTR_ERR(cxt->kpszs); 127462306a36Sopenharmony_ci cxt->kpszs = NULL; 127562306a36Sopenharmony_ci goto free_out; 127662306a36Sopenharmony_ci } 127762306a36Sopenharmony_ci 127862306a36Sopenharmony_ci return 0; 127962306a36Sopenharmony_cifree_out: 128062306a36Sopenharmony_ci psz_free_all_zones(cxt); 128162306a36Sopenharmony_ci return err; 128262306a36Sopenharmony_ci} 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_ci/** 128562306a36Sopenharmony_ci * register_pstore_zone() - register to pstore/zone 128662306a36Sopenharmony_ci * 128762306a36Sopenharmony_ci * @info: back-end driver information. See &struct pstore_zone_info. 128862306a36Sopenharmony_ci * 128962306a36Sopenharmony_ci * Only one back-end at one time. 129062306a36Sopenharmony_ci * 129162306a36Sopenharmony_ci * Return: 0 on success, others on failure. 129262306a36Sopenharmony_ci */ 129362306a36Sopenharmony_ciint register_pstore_zone(struct pstore_zone_info *info) 129462306a36Sopenharmony_ci{ 129562306a36Sopenharmony_ci int err = -EINVAL; 129662306a36Sopenharmony_ci struct psz_context *cxt = &pstore_zone_cxt; 129762306a36Sopenharmony_ci 129862306a36Sopenharmony_ci if (info->total_size < 4096) { 129962306a36Sopenharmony_ci pr_warn("total_size must be >= 4096\n"); 130062306a36Sopenharmony_ci return -EINVAL; 130162306a36Sopenharmony_ci } 130262306a36Sopenharmony_ci if (info->total_size > SZ_128M) { 130362306a36Sopenharmony_ci pr_warn("capping size to 128MiB\n"); 130462306a36Sopenharmony_ci info->total_size = SZ_128M; 130562306a36Sopenharmony_ci } 130662306a36Sopenharmony_ci 130762306a36Sopenharmony_ci if (!info->kmsg_size && !info->pmsg_size && !info->console_size && 130862306a36Sopenharmony_ci !info->ftrace_size) { 130962306a36Sopenharmony_ci pr_warn("at least one record size must be non-zero\n"); 131062306a36Sopenharmony_ci return -EINVAL; 131162306a36Sopenharmony_ci } 131262306a36Sopenharmony_ci 131362306a36Sopenharmony_ci if (!info->name || !info->name[0]) 131462306a36Sopenharmony_ci return -EINVAL; 131562306a36Sopenharmony_ci 131662306a36Sopenharmony_ci#define check_size(name, size) { \ 131762306a36Sopenharmony_ci if (info->name > 0 && info->name < (size)) { \ 131862306a36Sopenharmony_ci pr_err(#name " must be over %d\n", (size)); \ 131962306a36Sopenharmony_ci return -EINVAL; \ 132062306a36Sopenharmony_ci } \ 132162306a36Sopenharmony_ci if (info->name & (size - 1)) { \ 132262306a36Sopenharmony_ci pr_err(#name " must be a multiple of %d\n", \ 132362306a36Sopenharmony_ci (size)); \ 132462306a36Sopenharmony_ci return -EINVAL; \ 132562306a36Sopenharmony_ci } \ 132662306a36Sopenharmony_ci } 132762306a36Sopenharmony_ci 132862306a36Sopenharmony_ci check_size(total_size, 4096); 132962306a36Sopenharmony_ci check_size(kmsg_size, SECTOR_SIZE); 133062306a36Sopenharmony_ci check_size(pmsg_size, SECTOR_SIZE); 133162306a36Sopenharmony_ci check_size(console_size, SECTOR_SIZE); 133262306a36Sopenharmony_ci check_size(ftrace_size, SECTOR_SIZE); 133362306a36Sopenharmony_ci 133462306a36Sopenharmony_ci#undef check_size 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_ci /* 133762306a36Sopenharmony_ci * the @read and @write must be applied. 133862306a36Sopenharmony_ci * if no @read, pstore may mount failed. 133962306a36Sopenharmony_ci * if no @write, pstore do not support to remove record file. 134062306a36Sopenharmony_ci */ 134162306a36Sopenharmony_ci if (!info->read || !info->write) { 134262306a36Sopenharmony_ci pr_err("no valid general read/write interface\n"); 134362306a36Sopenharmony_ci return -EINVAL; 134462306a36Sopenharmony_ci } 134562306a36Sopenharmony_ci 134662306a36Sopenharmony_ci mutex_lock(&cxt->pstore_zone_info_lock); 134762306a36Sopenharmony_ci if (cxt->pstore_zone_info) { 134862306a36Sopenharmony_ci pr_warn("'%s' already loaded: ignoring '%s'\n", 134962306a36Sopenharmony_ci cxt->pstore_zone_info->name, info->name); 135062306a36Sopenharmony_ci mutex_unlock(&cxt->pstore_zone_info_lock); 135162306a36Sopenharmony_ci return -EBUSY; 135262306a36Sopenharmony_ci } 135362306a36Sopenharmony_ci cxt->pstore_zone_info = info; 135462306a36Sopenharmony_ci 135562306a36Sopenharmony_ci pr_debug("register %s with properties:\n", info->name); 135662306a36Sopenharmony_ci pr_debug("\ttotal size : %ld Bytes\n", info->total_size); 135762306a36Sopenharmony_ci pr_debug("\tkmsg size : %ld Bytes\n", info->kmsg_size); 135862306a36Sopenharmony_ci pr_debug("\tpmsg size : %ld Bytes\n", info->pmsg_size); 135962306a36Sopenharmony_ci pr_debug("\tconsole size : %ld Bytes\n", info->console_size); 136062306a36Sopenharmony_ci pr_debug("\tftrace size : %ld Bytes\n", info->ftrace_size); 136162306a36Sopenharmony_ci 136262306a36Sopenharmony_ci err = psz_alloc_zones(cxt); 136362306a36Sopenharmony_ci if (err) { 136462306a36Sopenharmony_ci pr_err("alloc zones failed\n"); 136562306a36Sopenharmony_ci goto fail_out; 136662306a36Sopenharmony_ci } 136762306a36Sopenharmony_ci 136862306a36Sopenharmony_ci if (info->kmsg_size) { 136962306a36Sopenharmony_ci cxt->pstore.bufsize = cxt->kpszs[0]->buffer_size - 137062306a36Sopenharmony_ci sizeof(struct psz_kmsg_header); 137162306a36Sopenharmony_ci cxt->pstore.buf = kzalloc(cxt->pstore.bufsize, GFP_KERNEL); 137262306a36Sopenharmony_ci if (!cxt->pstore.buf) { 137362306a36Sopenharmony_ci err = -ENOMEM; 137462306a36Sopenharmony_ci goto fail_free; 137562306a36Sopenharmony_ci } 137662306a36Sopenharmony_ci } 137762306a36Sopenharmony_ci cxt->pstore.data = cxt; 137862306a36Sopenharmony_ci 137962306a36Sopenharmony_ci pr_info("registered %s as backend for", info->name); 138062306a36Sopenharmony_ci cxt->pstore.max_reason = info->max_reason; 138162306a36Sopenharmony_ci cxt->pstore.name = info->name; 138262306a36Sopenharmony_ci if (info->kmsg_size) { 138362306a36Sopenharmony_ci cxt->pstore.flags |= PSTORE_FLAGS_DMESG; 138462306a36Sopenharmony_ci pr_cont(" kmsg(%s", 138562306a36Sopenharmony_ci kmsg_dump_reason_str(cxt->pstore.max_reason)); 138662306a36Sopenharmony_ci if (cxt->pstore_zone_info->panic_write) 138762306a36Sopenharmony_ci pr_cont(",panic_write"); 138862306a36Sopenharmony_ci pr_cont(")"); 138962306a36Sopenharmony_ci } 139062306a36Sopenharmony_ci if (info->pmsg_size) { 139162306a36Sopenharmony_ci cxt->pstore.flags |= PSTORE_FLAGS_PMSG; 139262306a36Sopenharmony_ci pr_cont(" pmsg"); 139362306a36Sopenharmony_ci } 139462306a36Sopenharmony_ci if (info->console_size) { 139562306a36Sopenharmony_ci cxt->pstore.flags |= PSTORE_FLAGS_CONSOLE; 139662306a36Sopenharmony_ci pr_cont(" console"); 139762306a36Sopenharmony_ci } 139862306a36Sopenharmony_ci if (info->ftrace_size) { 139962306a36Sopenharmony_ci cxt->pstore.flags |= PSTORE_FLAGS_FTRACE; 140062306a36Sopenharmony_ci pr_cont(" ftrace"); 140162306a36Sopenharmony_ci } 140262306a36Sopenharmony_ci pr_cont("\n"); 140362306a36Sopenharmony_ci 140462306a36Sopenharmony_ci err = pstore_register(&cxt->pstore); 140562306a36Sopenharmony_ci if (err) { 140662306a36Sopenharmony_ci pr_err("registering with pstore failed\n"); 140762306a36Sopenharmony_ci goto fail_free; 140862306a36Sopenharmony_ci } 140962306a36Sopenharmony_ci mutex_unlock(&pstore_zone_cxt.pstore_zone_info_lock); 141062306a36Sopenharmony_ci 141162306a36Sopenharmony_ci return 0; 141262306a36Sopenharmony_ci 141362306a36Sopenharmony_cifail_free: 141462306a36Sopenharmony_ci kfree(cxt->pstore.buf); 141562306a36Sopenharmony_ci cxt->pstore.buf = NULL; 141662306a36Sopenharmony_ci cxt->pstore.bufsize = 0; 141762306a36Sopenharmony_ci psz_free_all_zones(cxt); 141862306a36Sopenharmony_cifail_out: 141962306a36Sopenharmony_ci pstore_zone_cxt.pstore_zone_info = NULL; 142062306a36Sopenharmony_ci mutex_unlock(&pstore_zone_cxt.pstore_zone_info_lock); 142162306a36Sopenharmony_ci return err; 142262306a36Sopenharmony_ci} 142362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(register_pstore_zone); 142462306a36Sopenharmony_ci 142562306a36Sopenharmony_ci/** 142662306a36Sopenharmony_ci * unregister_pstore_zone() - unregister to pstore/zone 142762306a36Sopenharmony_ci * 142862306a36Sopenharmony_ci * @info: back-end driver information. See struct pstore_zone_info. 142962306a36Sopenharmony_ci */ 143062306a36Sopenharmony_civoid unregister_pstore_zone(struct pstore_zone_info *info) 143162306a36Sopenharmony_ci{ 143262306a36Sopenharmony_ci struct psz_context *cxt = &pstore_zone_cxt; 143362306a36Sopenharmony_ci 143462306a36Sopenharmony_ci mutex_lock(&cxt->pstore_zone_info_lock); 143562306a36Sopenharmony_ci if (!cxt->pstore_zone_info) { 143662306a36Sopenharmony_ci mutex_unlock(&cxt->pstore_zone_info_lock); 143762306a36Sopenharmony_ci return; 143862306a36Sopenharmony_ci } 143962306a36Sopenharmony_ci 144062306a36Sopenharmony_ci /* Stop incoming writes from pstore. */ 144162306a36Sopenharmony_ci pstore_unregister(&cxt->pstore); 144262306a36Sopenharmony_ci 144362306a36Sopenharmony_ci /* Flush any pending writes. */ 144462306a36Sopenharmony_ci psz_flush_all_dirty_zones(NULL); 144562306a36Sopenharmony_ci flush_delayed_work(&psz_cleaner); 144662306a36Sopenharmony_ci 144762306a36Sopenharmony_ci /* Clean up allocations. */ 144862306a36Sopenharmony_ci kfree(cxt->pstore.buf); 144962306a36Sopenharmony_ci cxt->pstore.buf = NULL; 145062306a36Sopenharmony_ci cxt->pstore.bufsize = 0; 145162306a36Sopenharmony_ci cxt->pstore_zone_info = NULL; 145262306a36Sopenharmony_ci 145362306a36Sopenharmony_ci psz_free_all_zones(cxt); 145462306a36Sopenharmony_ci 145562306a36Sopenharmony_ci /* Clear counters and zone state. */ 145662306a36Sopenharmony_ci cxt->oops_counter = 0; 145762306a36Sopenharmony_ci cxt->panic_counter = 0; 145862306a36Sopenharmony_ci atomic_set(&cxt->recovered, 0); 145962306a36Sopenharmony_ci atomic_set(&cxt->on_panic, 0); 146062306a36Sopenharmony_ci 146162306a36Sopenharmony_ci mutex_unlock(&cxt->pstore_zone_info_lock); 146262306a36Sopenharmony_ci} 146362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(unregister_pstore_zone); 146462306a36Sopenharmony_ci 146562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 146662306a36Sopenharmony_ciMODULE_AUTHOR("WeiXiong Liao <liaoweixiong@allwinnertech.com>"); 146762306a36Sopenharmony_ciMODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); 146862306a36Sopenharmony_ciMODULE_DESCRIPTION("Storage Manager for pstore/blk"); 1469