[Mod] 同步v5.10版本Pstore

This commit is contained in:
gaoyang3513
2024-04-23 00:57:34 +08:00
parent ba555b1993
commit f84c84aa2b
15 changed files with 2856 additions and 593 deletions

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
config PSTORE config PSTORE
tristate "Persistent store support" tristate "Persistent store support"
select CRYPTO if PSTORE_COMPRESS select CRYPTO if PSTORE_COMPRESS
@ -114,18 +115,6 @@ config PSTORE_CONSOLE
When the option is enabled, pstore will log all kernel When the option is enabled, pstore will log all kernel
messages, even if no oops or panic happened. messages, even if no oops or panic happened.
config PSTORE_CONSOLE_FORCE
bool "Log kernel console messages ignore loglevel setting"
depends on PSTORE_CONSOLE
help
When the option is enabled, allow all kernel messages
log to the pstore console. Enable this with a kernel bool
parameter like "pstore_con_force=1".
config PSTORE_CONSOLE_FORCE_ON
bool "Log kernel console messages ignore loglevel setting on by default"
depends on PSTORE_CONSOLE_FORCE
config PSTORE_PMSG config PSTORE_PMSG
bool "Log user space messages" bool "Log user space messages"
depends on PSTORE depends on PSTORE
@ -153,7 +142,6 @@ config PSTORE_RAM
tristate "Log panic/oops to a RAM buffer" tristate "Log panic/oops to a RAM buffer"
depends on PSTORE depends on PSTORE
depends on HAS_IOMEM depends on HAS_IOMEM
depends on HAVE_MEMBLOCK
select REED_SOLOMON select REED_SOLOMON
select REED_SOLOMON_ENC8 select REED_SOLOMON_ENC8
select REED_SOLOMON_DEC8 select REED_SOLOMON_DEC8
@ -166,6 +154,116 @@ config PSTORE_RAM
For more information, see Documentation/admin-guide/ramoops.rst. For more information, see Documentation/admin-guide/ramoops.rst.
config PSTORE_ZONE
tristate
depends on PSTORE
help
The common layer for pstore/blk (and pstore/ram in the future)
to manage storage in zones.
config PSTORE_BLK
tristate "Log panic/oops to a block device"
depends on PSTORE
depends on BLOCK
depends on BROKEN
select PSTORE_ZONE
default n
help
This enables panic and oops message to be logged to a block dev
where it can be read back at some later point.
For more information, see Documentation/admin-guide/pstore-blk.rst
If unsure, say N.
config PSTORE_BLK_BLKDEV
string "block device identifier"
depends on PSTORE_BLK
default ""
help
Which block device should be used for pstore/blk.
It accepts the following variants:
1) <hex_major><hex_minor> device number in hexadecimal representation,
with no leading 0x, for example b302.
2) /dev/<disk_name> represents the device name of disk
3) /dev/<disk_name><decimal> represents the device name and number
of partition - device number of disk plus the partition number
4) /dev/<disk_name>p<decimal> - same as the above, this form is
used when disk name of partitioned disk ends with a digit.
5) PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF representing the
unique id of a partition if the partition table provides it.
The UUID may be either an EFI/GPT UUID, or refer to an MSDOS
partition using the format SSSSSSSS-PP, where SSSSSSSS is a zero-
filled hex representation of the 32-bit "NT disk signature", and PP
is a zero-filled hex representation of the 1-based partition number.
6) PARTUUID=<UUID>/PARTNROFF=<int> to select a partition in relation
to a partition with a known unique id.
7) <major>:<minor> major and minor number of the device separated by
a colon.
NOTE that, both Kconfig and module parameters can configure
pstore/blk, but module parameters have priority over Kconfig.
config PSTORE_BLK_KMSG_SIZE
int "Size in Kbytes of kmsg dump log to store"
depends on PSTORE_BLK
default 64
help
This just sets size of kmsg dump (oops, panic, etc) log for
pstore/blk. The size is in KB and must be a multiple of 4.
NOTE that, both Kconfig and module parameters can configure
pstore/blk, but module parameters have priority over Kconfig.
config PSTORE_BLK_MAX_REASON
int "Maximum kmsg dump reason to store"
depends on PSTORE_BLK
default 2
help
The maximum reason for kmsg dumps to store. The default is
2 (KMSG_DUMP_OOPS), see include/linux/kmsg_dump.h's
enum kmsg_dump_reason for more details.
NOTE that, both Kconfig and module parameters can configure
pstore/blk, but module parameters have priority over Kconfig.
config PSTORE_BLK_PMSG_SIZE
int "Size in Kbytes of pmsg to store"
depends on PSTORE_BLK
depends on PSTORE_PMSG
default 64
help
This just sets size of pmsg (pmsg_size) for pstore/blk. The size is
in KB and must be a multiple of 4.
NOTE that, both Kconfig and module parameters can configure
pstore/blk, but module parameters have priority over Kconfig.
config PSTORE_BLK_CONSOLE_SIZE
int "Size in Kbytes of console log to store"
depends on PSTORE_BLK
depends on PSTORE_CONSOLE
default 64
help
This just sets size of console log (console_size) to store via
pstore/blk. The size is in KB and must be a multiple of 4.
NOTE that, both Kconfig and module parameters can configure
pstore/blk, but module parameters have priority over Kconfig.
config PSTORE_BLK_FTRACE_SIZE
int "Size in Kbytes of ftrace log to store"
depends on PSTORE_BLK
depends on PSTORE_FTRACE
default 64
help
This just sets size of ftrace log (ftrace_size) for pstore/blk. The
size is in KB and must be a multiple of 4.
NOTE that, both Kconfig and module parameters can configure
pstore/blk, but module parameters have priority over Kconfig.
config PSTORE_BOOT_LOG config PSTORE_BOOT_LOG
bool "Print boot log by linux" bool "Print boot log by linux"
depends on PSTORE depends on PSTORE

View File

@ -12,3 +12,9 @@ pstore-$(CONFIG_PSTORE_PMSG) += pmsg.o
ramoops-objs += ram.o ram_core.o ramoops-objs += ram.o ram_core.o
obj-$(CONFIG_PSTORE_RAM) += ramoops.o obj-$(CONFIG_PSTORE_RAM) += ramoops.o
pstore_zone-objs += zone.o
obj-$(CONFIG_PSTORE_ZONE) += pstore_zone.o
pstore_blk-objs += blk.o
obj-$(CONFIG_PSTORE_BLK) += pstore_blk.o

517
fs/pstore/blk.c Normal file
View File

@ -0,0 +1,517 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Implements pstore backend driver that write to block (or non-block) storage
* devices, using the pstore/zone API.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include "../../block/blk.h"
#include <linux/blkdev.h>
#include <linux/string.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pstore_blk.h>
#include <linux/mount.h>
#include <linux/uio.h>
static long kmsg_size = CONFIG_PSTORE_BLK_KMSG_SIZE;
module_param(kmsg_size, long, 0400);
MODULE_PARM_DESC(kmsg_size, "kmsg dump record size in kbytes");
static int max_reason = CONFIG_PSTORE_BLK_MAX_REASON;
module_param(max_reason, int, 0400);
MODULE_PARM_DESC(max_reason,
"maximum reason for kmsg dump (default 2: Oops and Panic)");
#if IS_ENABLED(CONFIG_PSTORE_PMSG)
static long pmsg_size = CONFIG_PSTORE_BLK_PMSG_SIZE;
#else
static long pmsg_size = -1;
#endif
module_param(pmsg_size, long, 0400);
MODULE_PARM_DESC(pmsg_size, "pmsg size in kbytes");
#if IS_ENABLED(CONFIG_PSTORE_CONSOLE)
static long console_size = CONFIG_PSTORE_BLK_CONSOLE_SIZE;
#else
static long console_size = -1;
#endif
module_param(console_size, long, 0400);
MODULE_PARM_DESC(console_size, "console size in kbytes");
#if IS_ENABLED(CONFIG_PSTORE_FTRACE)
static long ftrace_size = CONFIG_PSTORE_BLK_FTRACE_SIZE;
#else
static long ftrace_size = -1;
#endif
module_param(ftrace_size, long, 0400);
MODULE_PARM_DESC(ftrace_size, "ftrace size in kbytes");
static bool best_effort;
module_param(best_effort, bool, 0400);
MODULE_PARM_DESC(best_effort, "use best effort to write (i.e. do not require storage driver pstore support, default: off)");
/*
* blkdev - the block device to use for pstore storage
*
* Usually, this will be a partition of a block device.
*
* blkdev accepts the following variants:
* 1) <hex_major><hex_minor> device number in hexadecimal representation,
* with no leading 0x, for example b302.
* 2) /dev/<disk_name> represents the device number of disk
* 3) /dev/<disk_name><decimal> represents the device number
* of partition - device number of disk plus the partition number
* 4) /dev/<disk_name>p<decimal> - same as the above, that form is
* used when disk name of partitioned disk ends on a digit.
* 5) PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF representing the
* unique id of a partition if the partition table provides it.
* The UUID may be either an EFI/GPT UUID, or refer to an MSDOS
* partition using the format SSSSSSSS-PP, where SSSSSSSS is a zero-
* filled hex representation of the 32-bit "NT disk signature", and PP
* is a zero-filled hex representation of the 1-based partition number.
* 6) PARTUUID=<UUID>/PARTNROFF=<int> to select a partition in relation to
* a partition with a known unique id.
* 7) <major>:<minor> major and minor number of the device separated by
* a colon.
*/
static char blkdev[80] = CONFIG_PSTORE_BLK_BLKDEV;
module_param_string(blkdev, blkdev, 80, 0400);
MODULE_PARM_DESC(blkdev, "block device for pstore storage");
/*
* All globals must only be accessed under the pstore_blk_lock
* during the register/unregister functions.
*/
static DEFINE_MUTEX(pstore_blk_lock);
static struct block_device *psblk_bdev;
static struct pstore_zone_info *pstore_zone_info;
static pstore_blk_panic_write_op blkdev_panic_write;
struct bdev_info {
dev_t devt;
sector_t nr_sects;
sector_t start_sect;
};
#define check_size(name, alignsize) ({ \
long _##name_ = (name); \
_##name_ = _##name_ <= 0 ? 0 : (_##name_ * 1024); \
if (_##name_ & ((alignsize) - 1)) { \
pr_info(#name " must align to %d\n", \
(alignsize)); \
_##name_ = ALIGN(name, (alignsize)); \
} \
_##name_; \
})
static int __register_pstore_device(struct pstore_device_info *dev)
{
int ret;
lockdep_assert_held(&pstore_blk_lock);
if (!dev || !dev->total_size || !dev->read || !dev->write)
return -EINVAL;
/* someone already registered before */
if (pstore_zone_info)
return -EBUSY;
pstore_zone_info = kzalloc(sizeof(struct pstore_zone_info), GFP_KERNEL);
if (!pstore_zone_info)
return -ENOMEM;
/* zero means not limit on which backends to attempt to store. */
if (!dev->flags)
dev->flags = UINT_MAX;
#define verify_size(name, alignsize, enabled) { \
long _##name_; \
if (enabled) \
_##name_ = check_size(name, alignsize); \
else \
_##name_ = 0; \
name = _##name_ / 1024; \
pstore_zone_info->name = _##name_; \
}
verify_size(kmsg_size, 4096, dev->flags & PSTORE_FLAGS_DMESG);
verify_size(pmsg_size, 4096, dev->flags & PSTORE_FLAGS_PMSG);
verify_size(console_size, 4096, dev->flags & PSTORE_FLAGS_CONSOLE);
verify_size(ftrace_size, 4096, dev->flags & PSTORE_FLAGS_FTRACE);
#undef verify_size
pstore_zone_info->total_size = dev->total_size;
pstore_zone_info->max_reason = max_reason;
pstore_zone_info->read = dev->read;
pstore_zone_info->write = dev->write;
pstore_zone_info->erase = dev->erase;
pstore_zone_info->panic_write = dev->panic_write;
pstore_zone_info->name = KBUILD_MODNAME;
pstore_zone_info->owner = THIS_MODULE;
ret = register_pstore_zone(pstore_zone_info);
if (ret) {
kfree(pstore_zone_info);
pstore_zone_info = NULL;
}
return ret;
}
/**
* register_pstore_device() - register non-block device to pstore/blk
*
* @dev: non-block device information
*
* Return:
* * 0 - OK
* * Others - something error.
*/
int register_pstore_device(struct pstore_device_info *dev)
{
int ret;
mutex_lock(&pstore_blk_lock);
ret = __register_pstore_device(dev);
mutex_unlock(&pstore_blk_lock);
return ret;
}
EXPORT_SYMBOL_GPL(register_pstore_device);
static void __unregister_pstore_device(struct pstore_device_info *dev)
{
lockdep_assert_held(&pstore_blk_lock);
if (pstore_zone_info && pstore_zone_info->read == dev->read) {
unregister_pstore_zone(pstore_zone_info);
kfree(pstore_zone_info);
pstore_zone_info = NULL;
}
}
/**
* unregister_pstore_device() - unregister non-block device from pstore/blk
*
* @dev: non-block device information
*/
void unregister_pstore_device(struct pstore_device_info *dev)
{
mutex_lock(&pstore_blk_lock);
__unregister_pstore_device(dev);
mutex_unlock(&pstore_blk_lock);
}
EXPORT_SYMBOL_GPL(unregister_pstore_device);
/**
* psblk_get_bdev() - open block device
*
* @holder: Exclusive holder identifier
* @info: Information about bdev to fill in
*
* Return: pointer to block device on success and others on error.
*
* On success, the returned block_device has reference count of one.
*/
static struct block_device *psblk_get_bdev(void *holder,
struct bdev_info *info)
{
struct block_device *bdev = ERR_PTR(-ENODEV);
fmode_t mode = FMODE_READ | FMODE_WRITE;
sector_t nr_sects;
lockdep_assert_held(&pstore_blk_lock);
if (pstore_zone_info)
return ERR_PTR(-EBUSY);
if (!blkdev[0])
return ERR_PTR(-ENODEV);
if (holder)
mode |= FMODE_EXCL;
bdev = blkdev_get_by_path(blkdev, mode, holder);
if (IS_ERR(bdev)) {
dev_t devt;
devt = name_to_dev_t(blkdev);
if (devt == 0)
return ERR_PTR(-ENODEV);
bdev = blkdev_get_by_dev(devt, mode, holder);
if (IS_ERR(bdev))
return bdev;
}
nr_sects = part_nr_sects_read(bdev->bd_part);
if (!nr_sects) {
pr_err("not enough space for '%s'\n", blkdev);
blkdev_put(bdev, mode);
return ERR_PTR(-ENOSPC);
}
if (info) {
info->devt = bdev->bd_dev;
info->nr_sects = nr_sects;
info->start_sect = get_start_sect(bdev);
}
return bdev;
}
static void psblk_put_bdev(struct block_device *bdev, void *holder)
{
fmode_t mode = FMODE_READ | FMODE_WRITE;
lockdep_assert_held(&pstore_blk_lock);
if (!bdev)
return;
if (holder)
mode |= FMODE_EXCL;
blkdev_put(bdev, mode);
}
static ssize_t psblk_generic_blk_read(char *buf, size_t bytes, loff_t pos)
{
struct block_device *bdev = psblk_bdev;
struct file file;
struct kiocb kiocb;
struct iov_iter iter;
struct kvec iov = {.iov_base = buf, .iov_len = bytes};
if (!bdev)
return -ENODEV;
memset(&file, 0, sizeof(struct file));
file.f_mapping = bdev->bd_inode->i_mapping;
file.f_flags = O_DSYNC | __O_SYNC | O_NOATIME;
file.f_inode = bdev->bd_inode;
file_ra_state_init(&file.f_ra, file.f_mapping);
init_sync_kiocb(&kiocb, &file);
kiocb.ki_pos = pos;
iov_iter_kvec(&iter, READ, &iov, 1, bytes);
return generic_file_read_iter(&kiocb, &iter);
}
static ssize_t psblk_generic_blk_write(const char *buf, size_t bytes,
loff_t pos)
{
struct block_device *bdev = psblk_bdev;
struct iov_iter iter;
struct kiocb kiocb;
struct file file;
ssize_t ret;
struct kvec iov = {.iov_base = (void *)buf, .iov_len = bytes};
if (!bdev)
return -ENODEV;
/* Console/Ftrace backend may handle buffer until flush dirty zones */
if (in_interrupt() || irqs_disabled())
return -EBUSY;
memset(&file, 0, sizeof(struct file));
file.f_mapping = bdev->bd_inode->i_mapping;
file.f_flags = O_DSYNC | __O_SYNC | O_NOATIME;
file.f_inode = bdev->bd_inode;
init_sync_kiocb(&kiocb, &file);
kiocb.ki_pos = pos;
iov_iter_kvec(&iter, WRITE, &iov, 1, bytes);
inode_lock(bdev->bd_inode);
ret = generic_write_checks(&kiocb, &iter);
if (ret > 0)
ret = generic_perform_write(&file, &iter, pos);
inode_unlock(bdev->bd_inode);
if (likely(ret > 0)) {
const struct file_operations f_op = {.fsync = blkdev_fsync};
file.f_op = &f_op;
kiocb.ki_pos += ret;
ret = generic_write_sync(&kiocb, ret);
}
return ret;
}
static ssize_t psblk_blk_panic_write(const char *buf, size_t size,
loff_t off)
{
int ret;
if (!blkdev_panic_write)
return -EOPNOTSUPP;
/* size and off must align to SECTOR_SIZE for block device */
ret = blkdev_panic_write(buf, off >> SECTOR_SHIFT,
size >> SECTOR_SHIFT);
/* try next zone */
if (ret == -ENOMSG)
return ret;
return ret ? -EIO : size;
}
static int __register_pstore_blk(struct pstore_blk_info *info)
{
char bdev_name[BDEVNAME_SIZE];
struct block_device *bdev;
struct pstore_device_info dev;
struct bdev_info binfo;
void *holder = blkdev;
int ret = -ENODEV;
lockdep_assert_held(&pstore_blk_lock);
/* hold bdev exclusively */
memset(&binfo, 0, sizeof(binfo));
bdev = psblk_get_bdev(holder, &binfo);
if (IS_ERR(bdev)) {
pr_err("failed to open '%s'!\n", blkdev);
return PTR_ERR(bdev);
}
/* only allow driver matching the @blkdev */
if (!binfo.devt || (!best_effort &&
MAJOR(binfo.devt) != info->major)) {
pr_debug("invalid major %u (expect %u)\n",
info->major, MAJOR(binfo.devt));
ret = -ENODEV;
goto err_put_bdev;
}
/* psblk_bdev must be assigned before register to pstore/blk */
psblk_bdev = bdev;
blkdev_panic_write = info->panic_write;
/* Copy back block device details. */
info->devt = binfo.devt;
info->nr_sects = binfo.nr_sects;
info->start_sect = binfo.start_sect;
memset(&dev, 0, sizeof(dev));
dev.total_size = info->nr_sects << SECTOR_SHIFT;
dev.flags = info->flags;
dev.read = psblk_generic_blk_read;
dev.write = psblk_generic_blk_write;
dev.erase = NULL;
dev.panic_write = info->panic_write ? psblk_blk_panic_write : NULL;
ret = __register_pstore_device(&dev);
if (ret)
goto err_put_bdev;
bdevname(bdev, bdev_name);
pr_info("attached %s%s\n", bdev_name,
info->panic_write ? "" : " (no dedicated panic_write!)");
return 0;
err_put_bdev:
psblk_bdev = NULL;
blkdev_panic_write = NULL;
psblk_put_bdev(bdev, holder);
return ret;
}
/**
* register_pstore_blk() - register block device to pstore/blk
*
* @info: details on the desired block device interface
*
* Return:
* * 0 - OK
* * Others - something error.
*/
int register_pstore_blk(struct pstore_blk_info *info)
{
int ret;
mutex_lock(&pstore_blk_lock);
ret = __register_pstore_blk(info);
mutex_unlock(&pstore_blk_lock);
return ret;
}
EXPORT_SYMBOL_GPL(register_pstore_blk);
static void __unregister_pstore_blk(unsigned int major)
{
struct pstore_device_info dev = { .read = psblk_generic_blk_read };
void *holder = blkdev;
lockdep_assert_held(&pstore_blk_lock);
if (psblk_bdev && MAJOR(psblk_bdev->bd_dev) == major) {
__unregister_pstore_device(&dev);
psblk_put_bdev(psblk_bdev, holder);
blkdev_panic_write = NULL;
psblk_bdev = NULL;
}
}
/**
* unregister_pstore_blk() - unregister block device from pstore/blk
*
* @major: the major device number of device
*/
void unregister_pstore_blk(unsigned int major)
{
mutex_lock(&pstore_blk_lock);
__unregister_pstore_blk(major);
mutex_unlock(&pstore_blk_lock);
}
EXPORT_SYMBOL_GPL(unregister_pstore_blk);
/* get information of pstore/blk */
int pstore_blk_get_config(struct pstore_blk_config *info)
{
strncpy(info->device, blkdev, 80);
info->max_reason = max_reason;
info->kmsg_size = check_size(kmsg_size, 4096);
info->pmsg_size = check_size(pmsg_size, 4096);
info->ftrace_size = check_size(ftrace_size, 4096);
info->console_size = check_size(console_size, 4096);
return 0;
}
EXPORT_SYMBOL_GPL(pstore_blk_get_config);
static int __init pstore_blk_init(void)
{
struct pstore_blk_info info = { };
int ret = 0;
mutex_lock(&pstore_blk_lock);
if (!pstore_zone_info && best_effort && blkdev[0])
ret = __register_pstore_blk(&info);
mutex_unlock(&pstore_blk_lock);
return ret;
}
late_initcall(pstore_blk_init);
static void __exit pstore_blk_exit(void)
{
mutex_lock(&pstore_blk_lock);
if (psblk_bdev)
__unregister_pstore_blk(MAJOR(psblk_bdev->bd_dev));
else {
struct pstore_device_info dev = { };
if (pstore_zone_info)
dev.read = pstore_zone_info->read;
__unregister_pstore_device(&dev);
}
mutex_unlock(&pstore_blk_lock);
}
module_exit(pstore_blk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("WeiXiong Liao <liaoweixiong@allwinnertech.com>");
MODULE_AUTHOR("Kees Cook <keescook@chromium.org>");
MODULE_DESCRIPTION("pstore backend for block devices");

View File

@ -1,14 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/* /*
* Copyright 2012 Google, Inc. * Copyright 2012 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
@ -24,6 +16,7 @@
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/cache.h> #include <linux/cache.h>
#include <linux/slab.h>
#include <asm/barrier.h> #include <asm/barrier.h>
#include "internal.h" #include "internal.h"
@ -120,27 +113,13 @@ static struct dentry *pstore_ftrace_dir;
void pstore_register_ftrace(void) void pstore_register_ftrace(void)
{ {
struct dentry *file;
if (!psinfo->write) if (!psinfo->write)
return; return;
pstore_ftrace_dir = debugfs_create_dir("pstore", NULL); pstore_ftrace_dir = debugfs_create_dir("pstore", NULL);
if (!pstore_ftrace_dir) {
pr_err("%s: unable to create pstore directory\n", __func__);
return;
}
file = debugfs_create_file("record_ftrace", 0600, pstore_ftrace_dir, debugfs_create_file("record_ftrace", 0600, pstore_ftrace_dir, NULL,
NULL, &pstore_knob_fops); &pstore_knob_fops);
if (!file) {
pr_err("%s: unable to create record_ftrace file\n", __func__);
goto err_file;
}
return;
err_file:
debugfs_remove(pstore_ftrace_dir);
} }
void pstore_unregister_ftrace(void) void pstore_unregister_ftrace(void)
@ -148,9 +127,62 @@ void pstore_unregister_ftrace(void)
mutex_lock(&pstore_ftrace_lock); mutex_lock(&pstore_ftrace_lock);
if (pstore_ftrace_enabled) { if (pstore_ftrace_enabled) {
unregister_ftrace_function(&pstore_ftrace_ops); unregister_ftrace_function(&pstore_ftrace_ops);
pstore_ftrace_enabled = 0; pstore_ftrace_enabled = false;
} }
mutex_unlock(&pstore_ftrace_lock); mutex_unlock(&pstore_ftrace_lock);
debugfs_remove_recursive(pstore_ftrace_dir); debugfs_remove_recursive(pstore_ftrace_dir);
} }
ssize_t pstore_ftrace_combine_log(char **dest_log, size_t *dest_log_size,
const char *src_log, size_t src_log_size)
{
size_t dest_size, src_size, total, dest_off, src_off;
size_t dest_idx = 0, src_idx = 0, merged_idx = 0;
void *merged_buf;
struct pstore_ftrace_record *drec, *srec, *mrec;
size_t record_size = sizeof(struct pstore_ftrace_record);
dest_off = *dest_log_size % record_size;
dest_size = *dest_log_size - dest_off;
src_off = src_log_size % record_size;
src_size = src_log_size - src_off;
total = dest_size + src_size;
merged_buf = kmalloc(total, GFP_KERNEL);
if (!merged_buf)
return -ENOMEM;
drec = (struct pstore_ftrace_record *)(*dest_log + dest_off);
srec = (struct pstore_ftrace_record *)(src_log + src_off);
mrec = (struct pstore_ftrace_record *)(merged_buf);
while (dest_size > 0 && src_size > 0) {
if (pstore_ftrace_read_timestamp(&drec[dest_idx]) <
pstore_ftrace_read_timestamp(&srec[src_idx])) {
mrec[merged_idx++] = drec[dest_idx++];
dest_size -= record_size;
} else {
mrec[merged_idx++] = srec[src_idx++];
src_size -= record_size;
}
}
while (dest_size > 0) {
mrec[merged_idx++] = drec[dest_idx++];
dest_size -= record_size;
}
while (src_size > 0) {
mrec[merged_idx++] = srec[src_idx++];
src_size -= record_size;
}
kfree(*dest_log);
*dest_log = merged_buf;
*dest_log_size = total;
return 0;
}
EXPORT_SYMBOL_GPL(pstore_ftrace_combine_log);

View File

@ -1,20 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
/* /*
* Persistent Storage - ramfs parts. * Persistent Storage - ramfs parts.
* *
* Copyright (C) 2010 Intel Corporation <tony.luck@intel.com> * Copyright (C) 2010 Intel Corporation <tony.luck@intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include <linux/module.h> #include <linux/module.h>
@ -34,7 +22,6 @@
#include <linux/magic.h> #include <linux/magic.h>
#include <linux/pstore.h> #include <linux/pstore.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
@ -46,11 +33,15 @@
#define PSTORE_NAMELEN 64 #define PSTORE_NAMELEN 64
static DEFINE_SPINLOCK(allpstore_lock); static DEFINE_MUTEX(records_list_lock);
static LIST_HEAD(allpstore); static LIST_HEAD(records_list);
static DEFINE_MUTEX(pstore_sb_lock);
static struct super_block *pstore_sb;
struct pstore_private { struct pstore_private {
struct list_head list; struct list_head list;
struct dentry *dentry;
struct pstore_record *record; struct pstore_record *record;
size_t total_size; size_t total_size;
}; };
@ -123,7 +114,7 @@ static int pstore_ftrace_seq_show(struct seq_file *s, void *v)
rec = (struct pstore_ftrace_record *)(ps->record->buf + data->off); rec = (struct pstore_ftrace_record *)(ps->record->buf + data->off);
seq_printf(s, "CPU:%d ts:%llu %08lx %08lx %pf <- %pF\n", seq_printf(s, "CPU:%d ts:%llu %08lx %08lx %ps <- %pS\n",
pstore_ftrace_decode_cpu(rec), pstore_ftrace_decode_cpu(rec),
pstore_ftrace_read_timestamp(rec), pstore_ftrace_read_timestamp(rec),
rec->ip, rec->parent_ip, (void *)rec->ip, rec->ip, rec->parent_ip, (void *)rec->ip,
@ -145,39 +136,13 @@ static ssize_t pstore_file_read(struct file *file, char __user *userbuf,
struct seq_file *sf = file->private_data; struct seq_file *sf = file->private_data;
struct pstore_private *ps = sf->private; struct pstore_private *ps = sf->private;
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
size_t size = 0;
struct pstore_record *record = ps->record; struct pstore_record *record = ps->record;
struct ramoops_context *cxt = record->psi->data;
struct persistent_ram_zone *prz;
struct persistent_ram_buffer *buffer;
char *log_tmp;
size_t size, start, n;
if (ps->record->type == PSTORE_TYPE_BOOT_LOG) { if (record->type == PSTORE_TYPE_BOOT_LOG) {
size = ramoops_pstore_read_for_boot_log(ps->record);
if (!cxt) size = simple_read_from_buffer(userbuf, count, ppos, record->buf, size);
return count; return size;
prz = cxt->boot_przs[record->id];
if (!prz)
return count;
buffer = prz->buffer;
if (!buffer)
return count;
size = atomic_read(&buffer->size);
start = atomic_read(&buffer->start);
log_tmp = kmalloc(size, GFP_KERNEL);
if (!log_tmp)
return count;
memcpy_fromio(log_tmp, &buffer->data[start], size - start);
memcpy_fromio(log_tmp + size - start, &buffer->data[0], start);
n = simple_read_from_buffer(userbuf, count, ppos, log_tmp, size);
kfree(log_tmp);
return n;
} }
#endif #endif
if (ps->record->type == PSTORE_TYPE_FTRACE) if (ps->record->type == PSTORE_TYPE_FTRACE)
@ -230,10 +195,22 @@ static int pstore_unlink(struct inode *dir, struct dentry *dentry)
{ {
struct pstore_private *p = d_inode(dentry)->i_private; struct pstore_private *p = d_inode(dentry)->i_private;
struct pstore_record *record = p->record; struct pstore_record *record = p->record;
int rc = 0;
if (!record->psi->erase) if (!record->psi->erase)
return -EPERM; return -EPERM;
/* Make sure we can't race while removing this file. */
mutex_lock(&records_list_lock);
if (!list_empty(&p->list))
list_del_init(&p->list);
else
rc = -ENOENT;
p->dentry = NULL;
mutex_unlock(&records_list_lock);
if (rc)
return rc;
mutex_lock(&record->psi->read_mutex); mutex_lock(&record->psi->read_mutex);
record->psi->erase(record); record->psi->erase(record);
mutex_unlock(&record->psi->read_mutex); mutex_unlock(&record->psi->read_mutex);
@ -244,15 +221,9 @@ static int pstore_unlink(struct inode *dir, struct dentry *dentry)
static void pstore_evict_inode(struct inode *inode) static void pstore_evict_inode(struct inode *inode)
{ {
struct pstore_private *p = inode->i_private; struct pstore_private *p = inode->i_private;
unsigned long flags;
clear_inode(inode); clear_inode(inode);
if (p) { free_pstore_private(p);
spin_lock_irqsave(&allpstore_lock, flags);
list_del(&p->list);
spin_unlock_irqrestore(&allpstore_lock, flags);
free_pstore_private(p);
}
} }
static const struct inode_operations pstore_dir_inode_operations = { static const struct inode_operations pstore_dir_inode_operations = {
@ -330,11 +301,54 @@ static const struct super_operations pstore_ops = {
.show_options = pstore_show_options, .show_options = pstore_show_options,
}; };
static struct super_block *pstore_sb; static struct dentry *psinfo_lock_root(void)
bool pstore_is_mounted(void)
{ {
return pstore_sb != NULL; struct dentry *root;
mutex_lock(&pstore_sb_lock);
/*
* Having no backend is fine -- no records appear.
* Not being mounted is fine -- nothing to do.
*/
if (!psinfo || !pstore_sb) {
mutex_unlock(&pstore_sb_lock);
return NULL;
}
root = pstore_sb->s_root;
inode_lock(d_inode(root));
mutex_unlock(&pstore_sb_lock);
return root;
}
int pstore_put_backend_records(struct pstore_info *psi)
{
struct pstore_private *pos, *tmp;
struct dentry *root;
int rc = 0;
root = psinfo_lock_root();
if (!root)
return 0;
mutex_lock(&records_list_lock);
list_for_each_entry_safe(pos, tmp, &records_list, list) {
if (pos->record->psi == psi) {
list_del_init(&pos->list);
rc = simple_unlink(d_inode(root), pos->dentry);
if (WARN_ON(rc))
break;
d_drop(pos->dentry);
dput(pos->dentry);
pos->dentry = NULL;
}
}
mutex_unlock(&records_list_lock);
inode_unlock(d_inode(root));
return rc;
} }
/* /*
@ -349,23 +363,20 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
int rc = 0; int rc = 0;
char name[PSTORE_NAMELEN]; char name[PSTORE_NAMELEN];
struct pstore_private *private, *pos; struct pstore_private *private, *pos;
unsigned long flags;
size_t size = record->size + record->ecc_notice_size; size_t size = record->size + record->ecc_notice_size;
WARN_ON(!inode_is_locked(d_inode(root))); if (WARN_ON(!inode_is_locked(d_inode(root))))
return -EINVAL;
spin_lock_irqsave(&allpstore_lock, flags); rc = -EEXIST;
list_for_each_entry(pos, &allpstore, list) { /* Skip records that are already present in the filesystem. */
mutex_lock(&records_list_lock);
list_for_each_entry(pos, &records_list, list) {
if (pos->record->type == record->type && if (pos->record->type == record->type &&
pos->record->id == record->id && pos->record->id == record->id &&
pos->record->psi == record->psi) { pos->record->psi == record->psi)
rc = -EEXIST; goto fail;
break;
}
} }
spin_unlock_irqrestore(&allpstore_lock, flags);
if (rc)
return rc;
rc = -ENOMEM; rc = -ENOMEM;
inode = pstore_get_inode(root->d_sb); inode = pstore_get_inode(root->d_sb);
@ -373,59 +384,10 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
goto fail; goto fail;
inode->i_mode = S_IFREG | 0444; inode->i_mode = S_IFREG | 0444;
inode->i_fop = &pstore_file_operations; inode->i_fop = &pstore_file_operations;
scnprintf(name, sizeof(name), "%s-%s-%llu%s",
switch (record->type) { pstore_type_to_name(record->type),
case PSTORE_TYPE_DMESG: record->psi->name, record->id,
scnprintf(name, sizeof(name), "dmesg-%s-%llu%s", record->compressed ? ".enc.z" : "");
record->psi->name, record->id,
record->compressed ? ".enc.z" : "");
break;
case PSTORE_TYPE_CONSOLE:
scnprintf(name, sizeof(name), "console-%s-%llu",
record->psi->name, record->id);
break;
case PSTORE_TYPE_FTRACE:
scnprintf(name, sizeof(name), "ftrace-%s-%llu",
record->psi->name, record->id);
break;
case PSTORE_TYPE_MCE:
scnprintf(name, sizeof(name), "mce-%s-%llu",
record->psi->name, record->id);
break;
case PSTORE_TYPE_PPC_RTAS:
scnprintf(name, sizeof(name), "rtas-%s-%llu",
record->psi->name, record->id);
break;
case PSTORE_TYPE_PPC_OF:
scnprintf(name, sizeof(name), "powerpc-ofw-%s-%llu",
record->psi->name, record->id);
break;
case PSTORE_TYPE_PPC_COMMON:
scnprintf(name, sizeof(name), "powerpc-common-%s-%llu",
record->psi->name, record->id);
break;
case PSTORE_TYPE_PMSG:
scnprintf(name, sizeof(name), "pmsg-%s-%llu",
record->psi->name, record->id);
break;
case PSTORE_TYPE_PPC_OPAL:
scnprintf(name, sizeof(name), "powerpc-opal-%s-%llu",
record->psi->name, record->id);
break;
#ifdef CONFIG_PSTORE_BOOT_LOG
case PSTORE_TYPE_BOOT_LOG:
scnprintf(name, sizeof(name), "boot-log-%llu", record->id);
break;
#endif
case PSTORE_TYPE_UNKNOWN:
scnprintf(name, sizeof(name), "unknown-%s-%llu",
record->psi->name, record->id);
break;
default:
scnprintf(name, sizeof(name), "type%d-%s-%llu",
record->type, record->psi->name, record->id);
break;
}
private = kzalloc(sizeof(*private), GFP_KERNEL); private = kzalloc(sizeof(*private), GFP_KERNEL);
if (!private) if (!private)
@ -435,6 +397,7 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
if (!dentry) if (!dentry)
goto fail_private; goto fail_private;
private->dentry = dentry;
private->record = record; private->record = record;
inode->i_size = private->total_size = size; inode->i_size = private->total_size = size;
inode->i_private = private; inode->i_private = private;
@ -444,9 +407,8 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
d_add(dentry, inode); d_add(dentry, inode);
spin_lock_irqsave(&allpstore_lock, flags); list_add(&private->list, &records_list);
list_add(&private->list, &allpstore); mutex_unlock(&records_list_lock);
spin_unlock_irqrestore(&allpstore_lock, flags);
return 0; return 0;
@ -454,8 +416,8 @@ fail_private:
free_pstore_private(private); free_pstore_private(private);
fail_inode: fail_inode:
iput(inode); iput(inode);
fail: fail:
mutex_unlock(&records_list_lock);
return rc; return rc;
} }
@ -467,16 +429,13 @@ fail:
*/ */
void pstore_get_records(int quiet) void pstore_get_records(int quiet)
{ {
struct pstore_info *psi = psinfo;
struct dentry *root; struct dentry *root;
if (!psi || !pstore_sb) root = psinfo_lock_root();
if (!root)
return; return;
root = pstore_sb->s_root; pstore_get_backend_records(psinfo, root, quiet);
inode_lock(d_inode(root));
pstore_get_backend_records(psi, root, quiet);
inode_unlock(d_inode(root)); inode_unlock(d_inode(root));
} }
@ -484,8 +443,6 @@ static int pstore_fill_super(struct super_block *sb, void *data, int silent)
{ {
struct inode *inode; struct inode *inode;
pstore_sb = sb;
sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE; sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT; sb->s_blocksize_bits = PAGE_SHIFT;
@ -506,6 +463,10 @@ static int pstore_fill_super(struct super_block *sb, void *data, int silent)
if (!sb->s_root) if (!sb->s_root)
return -ENOMEM; return -ENOMEM;
mutex_lock(&pstore_sb_lock);
pstore_sb = sb;
mutex_unlock(&pstore_sb_lock);
pstore_get_records(0); pstore_get_records(0);
return 0; return 0;
@ -519,8 +480,17 @@ static struct dentry *pstore_mount(struct file_system_type *fs_type,
static void pstore_kill_sb(struct super_block *sb) static void pstore_kill_sb(struct super_block *sb)
{ {
mutex_lock(&pstore_sb_lock);
WARN_ON(pstore_sb && pstore_sb != sb);
kill_litter_super(sb); kill_litter_super(sb);
pstore_sb = NULL; pstore_sb = NULL;
mutex_lock(&records_list_lock);
INIT_LIST_HEAD(&records_list);
mutex_unlock(&records_list_lock);
mutex_unlock(&pstore_sb_lock);
} }
static struct file_system_type pstore_fs_type = { static struct file_system_type pstore_fs_type = {

View File

@ -12,9 +12,18 @@ extern unsigned long kmsg_bytes;
#ifdef CONFIG_PSTORE_FTRACE #ifdef CONFIG_PSTORE_FTRACE
extern void pstore_register_ftrace(void); extern void pstore_register_ftrace(void);
extern void pstore_unregister_ftrace(void); extern void pstore_unregister_ftrace(void);
ssize_t pstore_ftrace_combine_log(char **dest_log, size_t *dest_log_size,
const char *src_log, size_t src_log_size);
#else #else
static inline void pstore_register_ftrace(void) {} static inline void pstore_register_ftrace(void) {}
static inline void pstore_unregister_ftrace(void) {} static inline void pstore_unregister_ftrace(void) {}
static inline ssize_t
pstore_ftrace_combine_log(char **dest_log, size_t *dest_log_size,
const char *src_log, size_t src_log_size)
{
*dest_log_size = 0;
return 0;
}
#endif #endif
#ifdef CONFIG_PSTORE_PMSG #ifdef CONFIG_PSTORE_PMSG
@ -31,9 +40,9 @@ extern void pstore_set_kmsg_bytes(int);
extern void pstore_get_records(int); extern void pstore_get_records(int);
extern void pstore_get_backend_records(struct pstore_info *psi, extern void pstore_get_backend_records(struct pstore_info *psi,
struct dentry *root, int quiet); struct dentry *root, int quiet);
extern int pstore_put_backend_records(struct pstore_info *psi);
extern int pstore_mkfile(struct dentry *root, extern int pstore_mkfile(struct dentry *root,
struct pstore_record *record); struct pstore_record *record);
extern bool pstore_is_mounted(void);
extern void pstore_record_init(struct pstore_record *record, extern void pstore_record_init(struct pstore_record *record,
struct pstore_info *psi); struct pstore_info *psi);

View File

@ -1,21 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
/* /*
* Persistent Storage - platform driver interface parts. * Persistent Storage - platform driver interface parts.
* *
* Copyright (C) 2007-2008 Google, Inc. * Copyright (C) 2007-2008 Google, Inc.
* Copyright (C) 2010 Intel Corporation <tony.luck@intel.com> * Copyright (C) 2010 Intel Corporation <tony.luck@intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#define pr_fmt(fmt) "pstore: " fmt #define pr_fmt(fmt) "pstore: " fmt
@ -56,9 +44,25 @@ static int pstore_update_ms = -1;
module_param_named(update_ms, pstore_update_ms, int, 0600); module_param_named(update_ms, pstore_update_ms, int, 0600);
MODULE_PARM_DESC(update_ms, "milliseconds before pstore updates its content " MODULE_PARM_DESC(update_ms, "milliseconds before pstore updates its content "
"(default is -1, which means runtime updates are disabled; " "(default is -1, which means runtime updates are disabled; "
"enabling this option is not safe, it may lead to further " "enabling this option may not be safe; it may lead to further "
"corruption on Oopses)"); "corruption on Oopses)");
/* Names should be in the same order as the enum pstore_type_id */
static const char * const pstore_type_names[] = {
"dmesg",
"mce",
"console",
"ftrace",
"rtas",
"powerpc-ofw",
"powerpc-common",
"pmsg",
"powerpc-opal",
#ifdef CONFIG_PSTORE_BOOT_LOG
"boot-log",
#endif
};
static int pstore_new_entry; static int pstore_new_entry;
static void pstore_timefunc(struct timer_list *); static void pstore_timefunc(struct timer_list *);
@ -68,19 +72,25 @@ static void pstore_dowork(struct work_struct *);
static DECLARE_WORK(pstore_work, pstore_dowork); static DECLARE_WORK(pstore_work, pstore_dowork);
/* /*
* pstore_lock just protects "psinfo" during * psinfo_lock protects "psinfo" during calls to
* calls to pstore_register() * pstore_register(), pstore_unregister(), and
* the filesystem mount/unmount routines.
*/ */
static DEFINE_SPINLOCK(pstore_lock); static DEFINE_MUTEX(psinfo_lock);
struct pstore_info *psinfo; struct pstore_info *psinfo;
static char *backend; static char *backend;
module_param(backend, charp, 0444);
MODULE_PARM_DESC(backend, "specific backend to use");
static char *compress = static char *compress =
#ifdef CONFIG_PSTORE_COMPRESS_DEFAULT #ifdef CONFIG_PSTORE_COMPRESS_DEFAULT
CONFIG_PSTORE_COMPRESS_DEFAULT; CONFIG_PSTORE_COMPRESS_DEFAULT;
#else #else
NULL; NULL;
#endif #endif
module_param(compress, charp, 0444);
MODULE_PARM_DESC(compress, "compression to use");
/* Compression parameters */ /* Compression parameters */
static struct crypto_comp *tfm; static struct crypto_comp *tfm;
@ -104,24 +114,36 @@ void pstore_set_kmsg_bytes(int bytes)
/* Tag each group of saved records with a sequence number */ /* Tag each group of saved records with a sequence number */
static int oopscount; static int oopscount;
static const char *get_reason_str(enum kmsg_dump_reason reason) const char *pstore_type_to_name(enum pstore_type_id type)
{ {
switch (reason) { BUILD_BUG_ON(ARRAY_SIZE(pstore_type_names) != PSTORE_TYPE_MAX);
case KMSG_DUMP_PANIC:
return "Panic"; if (WARN_ON_ONCE(type >= PSTORE_TYPE_MAX))
case KMSG_DUMP_OOPS: return "unknown";
return "Oops";
case KMSG_DUMP_EMERG: return pstore_type_names[type];
return "Emergency"; }
case KMSG_DUMP_RESTART: EXPORT_SYMBOL_GPL(pstore_type_to_name);
return "Restart";
case KMSG_DUMP_HALT: enum pstore_type_id pstore_name_to_type(const char *name)
return "Halt"; {
case KMSG_DUMP_POWEROFF: int i;
return "Poweroff";
default: for (i = 0; i < PSTORE_TYPE_MAX; i++) {
return "Unknown"; if (!strcmp(pstore_type_names[i], name))
return i;
} }
return PSTORE_TYPE_MAX;
}
EXPORT_SYMBOL_GPL(pstore_name_to_type);
static void pstore_timer_kick(void)
{
if (pstore_update_ms < 0)
return;
mod_timer(&pstore_timer, jiffies + msecs_to_jiffies(pstore_update_ms));
} }
/* /*
@ -262,20 +284,6 @@ static int pstore_compress(const void *in, void *out,
return outlen; return outlen;
} }
static int pstore_decompress(void *in, void *out,
unsigned int inlen, unsigned int outlen)
{
int ret;
ret = crypto_comp_decompress(tfm, in, inlen, out, &outlen);
if (ret) {
pr_err("crypto_comp_decompress failed, ret = %d!\n", ret);
return ret;
}
return outlen;
}
static void allocate_buf_for_compression(void) static void allocate_buf_for_compression(void)
{ {
struct crypto_comp *ctx; struct crypto_comp *ctx;
@ -322,7 +330,7 @@ static void allocate_buf_for_compression(void)
big_oops_buf_sz = size; big_oops_buf_sz = size;
big_oops_buf = buf; big_oops_buf = buf;
pr_info("Using compression: %s\n", zbackend->name); pr_info("Using crash dump compression: %s\n", zbackend->name);
} }
static void free_buf_for_compression(void) static void free_buf_for_compression(void)
@ -374,9 +382,8 @@ void pstore_record_init(struct pstore_record *record,
} }
/* /*
* callback from kmsg_dump. (s2,l2) has the most recently * callback from kmsg_dump. Save as much as we can (up to kmsg_bytes) from the
* written bytes, older bytes are in (s1,l1). Save as much * end of the buffer.
* as we can from the end of the buffer.
*/ */
static void pstore_dump(struct kmsg_dumper *dumper, static void pstore_dump(struct kmsg_dumper *dumper,
enum kmsg_dump_reason reason) enum kmsg_dump_reason reason)
@ -386,7 +393,7 @@ static void pstore_dump(struct kmsg_dumper *dumper,
unsigned int part = 1; unsigned int part = 1;
int ret; int ret;
why = get_reason_str(reason); why = kmsg_dump_reason_str(reason);
if (down_trylock(&psinfo->buf_lock)) { if (down_trylock(&psinfo->buf_lock)) {
/* Failed to acquire lock: give up if we cannot wait. */ /* Failed to acquire lock: give up if we cannot wait. */
@ -452,8 +459,10 @@ static void pstore_dump(struct kmsg_dumper *dumper,
} }
ret = psinfo->write(&record); ret = psinfo->write(&record);
if (ret == 0 && reason == KMSG_DUMP_OOPS && pstore_is_mounted()) if (ret == 0 && reason == KMSG_DUMP_OOPS) {
pstore_new_entry = 1; pstore_new_entry = 1;
pstore_timer_kick();
}
total += record.size; total += record.size;
part++; part++;
@ -484,6 +493,9 @@ static void pstore_console_write(struct console *con, const char *s, unsigned c)
{ {
struct pstore_record record; struct pstore_record record;
if (!c)
return;
pstore_record_init(&record, psinfo); pstore_record_init(&record, psinfo);
record.type = PSTORE_TYPE_CONSOLE; record.type = PSTORE_TYPE_CONSOLE;
@ -493,18 +505,20 @@ static void pstore_console_write(struct console *con, const char *s, unsigned c)
} }
static struct console pstore_console = { static struct console pstore_console = {
.name = "pstore",
.write = pstore_console_write, .write = pstore_console_write,
#ifdef CON_PSTORE
.flags = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME | CON_PSTORE,
#else
.flags = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME,
#endif
.index = -1, .index = -1,
}; };
static void pstore_register_console(void) static void pstore_register_console(void)
{ {
/* Show which backend is going to get console writes. */
strscpy(pstore_console.name, psinfo->name,
sizeof(pstore_console.name));
/*
* Always initialize flags here since prior unregister_console()
* calls may have changed settings (specifically CON_ENABLED).
*/
pstore_console.flags = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME;
register_console(&pstore_console); register_console(&pstore_console);
} }
@ -549,8 +563,6 @@ out:
*/ */
int pstore_register(struct pstore_info *psi) int pstore_register(struct pstore_info *psi)
{ {
struct module *owner = psi->owner;
if (backend && strcmp(backend, psi->name)) { if (backend && strcmp(backend, psi->name)) {
pr_warn("ignoring unexpected backend '%s'\n", psi->name); pr_warn("ignoring unexpected backend '%s'\n", psi->name);
return -EPERM; return -EPERM;
@ -570,11 +582,11 @@ int pstore_register(struct pstore_info *psi)
return -EINVAL; return -EINVAL;
} }
spin_lock(&pstore_lock); mutex_lock(&psinfo_lock);
if (psinfo) { if (psinfo) {
pr_warn("backend '%s' already loaded: ignoring '%s'\n", pr_warn("backend '%s' already loaded: ignoring '%s'\n",
psinfo->name, psi->name); psinfo->name, psi->name);
spin_unlock(&pstore_lock); mutex_unlock(&psinfo_lock);
return -EBUSY; return -EBUSY;
} }
@ -583,21 +595,16 @@ int pstore_register(struct pstore_info *psi)
psinfo = psi; psinfo = psi;
mutex_init(&psinfo->read_mutex); mutex_init(&psinfo->read_mutex);
sema_init(&psinfo->buf_lock, 1); sema_init(&psinfo->buf_lock, 1);
spin_unlock(&pstore_lock);
if (owner && !try_module_get(owner)) {
psinfo = NULL;
return -EINVAL;
}
if (psi->flags & PSTORE_FLAGS_DMESG) if (psi->flags & PSTORE_FLAGS_DMESG)
allocate_buf_for_compression(); allocate_buf_for_compression();
if (pstore_is_mounted()) pstore_get_records(0);
pstore_get_records(0);
if (psi->flags & PSTORE_FLAGS_DMESG) if (psi->flags & PSTORE_FLAGS_DMESG) {
pstore_dumper.max_reason = psinfo->max_reason;
pstore_register_kmsg(); pstore_register_kmsg();
}
if (psi->flags & PSTORE_FLAGS_CONSOLE) if (psi->flags & PSTORE_FLAGS_CONSOLE)
pstore_register_console(); pstore_register_console();
if (psi->flags & PSTORE_FLAGS_FTRACE) if (psi->flags & PSTORE_FLAGS_FTRACE)
@ -606,33 +613,36 @@ int pstore_register(struct pstore_info *psi)
pstore_register_pmsg(); pstore_register_pmsg();
/* Start watching for new records, if desired. */ /* Start watching for new records, if desired. */
if (pstore_update_ms >= 0) { pstore_timer_kick();
pstore_timer.expires = jiffies +
msecs_to_jiffies(pstore_update_ms);
add_timer(&pstore_timer);
}
/* /*
* Update the module parameter backend, so it is visible * Update the module parameter backend, so it is visible
* through /sys/module/pstore/parameters/backend * through /sys/module/pstore/parameters/backend
*/ */
backend = psi->name; backend = kstrdup(psi->name, GFP_KERNEL);
pr_info("Registered %s as persistent store backend\n", psi->name); pr_info("Registered %s as persistent store backend\n", psi->name);
module_put(owner); mutex_unlock(&psinfo_lock);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(pstore_register); EXPORT_SYMBOL_GPL(pstore_register);
void pstore_unregister(struct pstore_info *psi) void pstore_unregister(struct pstore_info *psi)
{ {
/* Stop timer and make sure all work has finished. */ /* It's okay to unregister nothing. */
pstore_update_ms = -1; if (!psi)
del_timer_sync(&pstore_timer); return;
flush_work(&pstore_work);
mutex_lock(&psinfo_lock);
/* Only one backend can be registered at a time. */
if (WARN_ON(psi != psinfo)) {
mutex_unlock(&psinfo_lock);
return;
}
/* Unregister all callbacks. */
if (psi->flags & PSTORE_FLAGS_PMSG) if (psi->flags & PSTORE_FLAGS_PMSG)
pstore_unregister_pmsg(); pstore_unregister_pmsg();
if (psi->flags & PSTORE_FLAGS_FTRACE) if (psi->flags & PSTORE_FLAGS_FTRACE)
@ -642,17 +652,27 @@ void pstore_unregister(struct pstore_info *psi)
if (psi->flags & PSTORE_FLAGS_DMESG) if (psi->flags & PSTORE_FLAGS_DMESG)
pstore_unregister_kmsg(); pstore_unregister_kmsg();
/* Stop timer and make sure all work has finished. */
del_timer_sync(&pstore_timer);
flush_work(&pstore_work);
/* Remove all backend records from filesystem tree. */
pstore_put_backend_records(psi);
free_buf_for_compression(); free_buf_for_compression();
psinfo = NULL; psinfo = NULL;
kfree(backend);
backend = NULL; backend = NULL;
mutex_unlock(&psinfo_lock);
} }
EXPORT_SYMBOL_GPL(pstore_unregister); EXPORT_SYMBOL_GPL(pstore_unregister);
static void decompress_record(struct pstore_record *record) static void decompress_record(struct pstore_record *record)
{ {
int ret;
int unzipped_len; int unzipped_len;
char *decompressed; char *unzipped, *workspace;
if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS) || !record->compressed) if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS) || !record->compressed)
return; return;
@ -663,35 +683,42 @@ static void decompress_record(struct pstore_record *record)
return; return;
} }
/* No compression method has created the common buffer. */ /* Missing compression buffer means compression was not initialized. */
if (!big_oops_buf) { if (!big_oops_buf) {
pr_warn("no decompression buffer allocated\n"); pr_warn("no decompression method initialized!\n");
return; return;
} }
unzipped_len = pstore_decompress(record->buf, big_oops_buf, /* Allocate enough space to hold max decompression and ECC. */
record->size, big_oops_buf_sz); unzipped_len = big_oops_buf_sz;
if (unzipped_len <= 0) { workspace = kmalloc(unzipped_len + record->ecc_notice_size,
pr_err("decompression failed: %d\n", unzipped_len); GFP_KERNEL);
if (!workspace)
return; return;
}
/* Build new buffer for decompressed contents. */ /* After decompression "unzipped_len" is almost certainly smaller. */
decompressed = kmalloc(unzipped_len + record->ecc_notice_size, ret = crypto_comp_decompress(tfm, record->buf, record->size,
GFP_KERNEL); workspace, &unzipped_len);
if (!decompressed) { if (ret) {
pr_err("decompression ran out of memory\n"); pr_err("crypto_comp_decompress failed, ret = %d!\n", ret);
kfree(workspace);
return; return;
} }
memcpy(decompressed, big_oops_buf, unzipped_len);
/* Append ECC notice to decompressed buffer. */ /* Append ECC notice to decompressed buffer. */
memcpy(decompressed + unzipped_len, record->buf + record->size, memcpy(workspace + unzipped_len, record->buf + record->size,
record->ecc_notice_size); record->ecc_notice_size);
/* Swap out compresed contents with decompressed contents. */ /* Copy decompressed contents into an minimum-sized allocation. */
unzipped = kmemdup(workspace, unzipped_len + record->ecc_notice_size,
GFP_KERNEL);
kfree(workspace);
if (!unzipped)
return;
/* Swap out compressed contents with decompressed contents. */
kfree(record->buf); kfree(record->buf);
record->buf = decompressed; record->buf = unzipped;
record->size = unzipped_len; record->size = unzipped_len;
record->compressed = false; record->compressed = false;
} }
@ -774,12 +801,10 @@ static void pstore_timefunc(struct timer_list *unused)
schedule_work(&pstore_work); schedule_work(&pstore_work);
} }
if (pstore_update_ms >= 0) pstore_timer_kick();
mod_timer(&pstore_timer,
jiffies + msecs_to_jiffies(pstore_update_ms));
} }
void __init pstore_choose_compression(void) static void __init pstore_choose_compression(void)
{ {
const struct pstore_zbackend *step; const struct pstore_zbackend *step;
@ -821,11 +846,5 @@ static void __exit pstore_exit(void)
} }
module_exit(pstore_exit) module_exit(pstore_exit)
module_param(compress, charp, 0444);
MODULE_PARM_DESC(compress, "Pstore compression to use");
module_param(backend, charp, 0444);
MODULE_PARM_DESC(backend, "Pstore backend to use");
MODULE_AUTHOR("Tony Luck <tony.luck@intel.com>"); MODULE_AUTHOR("Tony Luck <tony.luck@intel.com>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");

View File

@ -1,14 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/* /*
* Copyright 2014 Google, Inc. * Copyright 2014 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/ */
#include <linux/cdev.h> #include <linux/cdev.h>
@ -33,7 +25,7 @@ static ssize_t write_pmsg(struct file *file, const char __user *buf,
record.size = count; record.size = count;
/* check outside lock, page in any data. write_user also checks */ /* check outside lock, page in any data. write_user also checks */
if (!access_ok(VERIFY_READ, buf, count)) if (!access_ok(buf, count))
return -EFAULT; return -EFAULT;
mutex_lock(&pmsg_lock); mutex_lock(&pmsg_lock);

View File

@ -1,23 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
/* /*
* RAM Oops/Panic logger * RAM Oops/Panic logger
* *
* Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com> * Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com>
* Copyright (C) 2011 Kees Cook <keescook@chromium.org> * Copyright (C) 2011 Kees Cook <keescook@chromium.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@ -35,6 +21,12 @@
#include <linux/pstore_ram.h> #include <linux/pstore_ram.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_address.h> #include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include "internal.h"
#if IS_REACHABLE(CONFIG_ROCKCHIP_MINIDUMP)
#include <soc/rockchip/rk_minidump.h>
#endif
#define RAMOOPS_KERNMSG_HDR "====" #define RAMOOPS_KERNMSG_HDR "===="
#define MIN_MEM_SIZE 4096UL #define MIN_MEM_SIZE 4096UL
@ -67,24 +59,63 @@ MODULE_PARM_DESC(mem_size,
"size of reserved RAM used to store oops/panic logs"); "size of reserved RAM used to store oops/panic logs");
static unsigned int mem_type; static unsigned int mem_type;
module_param(mem_type, uint, 0600); module_param(mem_type, uint, 0400);
MODULE_PARM_DESC(mem_type, MODULE_PARM_DESC(mem_type,
"set to 1 to try to use unbuffered memory (default 0)"); "memory type: 0=write-combined (default), 1=unbuffered, 2=cached");
static int dump_oops = 1; static int ramoops_max_reason = -1;
module_param(dump_oops, int, 0600); module_param_named(max_reason, ramoops_max_reason, int, 0400);
MODULE_PARM_DESC(dump_oops, MODULE_PARM_DESC(max_reason,
"set to 1 to dump oopses, 0 to only dump panics (default 1)"); "maximum reason for kmsg dump (default 2: Oops and Panic) ");
static int ramoops_ecc; static int ramoops_ecc;
module_param_named(ecc, ramoops_ecc, int, 0600); module_param_named(ecc, ramoops_ecc, int, 0400);
MODULE_PARM_DESC(ramoops_ecc, MODULE_PARM_DESC(ramoops_ecc,
"if non-zero, the option enables ECC support and specifies " "if non-zero, the option enables ECC support and specifies "
"ECC buffer size in bytes (1 is a special value, means 16 " "ECC buffer size in bytes (1 is a special value, means 16 "
"bytes ECC)"); "bytes ECC)");
static int ramoops_dump_oops = -1;
module_param_named(dump_oops, ramoops_dump_oops, int, 0400);
MODULE_PARM_DESC(dump_oops,
"(deprecated: use max_reason instead) set to 1 to dump oopses & panics, 0 to only dump panics");
struct ramoops_context {
struct persistent_ram_zone **dprzs; /* Oops dump zones */
struct persistent_ram_zone *cprz; /* Console zone */
struct persistent_ram_zone **fprzs; /* Ftrace zones */
struct persistent_ram_zone *mprz; /* PMSG zone */
#ifdef CONFIG_PSTORE_BOOT_LOG
struct persistent_ram_zone **boot_przs; /* BOOT log zones */
#endif
phys_addr_t phys_addr;
unsigned long size;
unsigned int memtype;
size_t record_size;
size_t console_size;
size_t ftrace_size;
size_t pmsg_size;
#ifdef CONFIG_PSTORE_BOOT_LOG
size_t boot_log_size;
#endif
u32 flags;
struct persistent_ram_ecc_info ecc_info;
unsigned int max_dump_cnt;
unsigned int dump_write_cnt;
/* _read_cnt need clear on ramoops_pstore_open */
unsigned int dump_read_cnt;
unsigned int console_read_cnt;
unsigned int max_ftrace_cnt;
unsigned int ftrace_read_cnt;
unsigned int pmsg_read_cnt;
#ifdef CONFIG_PSTORE_BOOT_LOG
unsigned int boot_log_read_cnt;
unsigned int max_boot_log_cnt;
#endif
struct pstore_info pstore;
};
static struct platform_device *dummy; static struct platform_device *dummy;
static struct ramoops_platform_data *dummy_data;
static int ramoops_pstore_open(struct pstore_info *psi) static int ramoops_pstore_open(struct pstore_info *psi)
{ {
@ -98,31 +129,28 @@ static int ramoops_pstore_open(struct pstore_info *psi)
} }
static struct persistent_ram_zone * static struct persistent_ram_zone *
ramoops_get_next_prz(struct persistent_ram_zone *przs[], uint *c, uint max, ramoops_get_next_prz(struct persistent_ram_zone *przs[], int id,
u64 *id, struct pstore_record *record)
enum pstore_type_id *typep, enum pstore_type_id type,
bool update)
{ {
struct persistent_ram_zone *prz; struct persistent_ram_zone *prz;
int i = (*c)++;
/* Give up if we never existed or have hit the end. */ /* Give up if we never existed or have hit the end. */
if (!przs || i >= max) if (!przs)
return NULL; return NULL;
prz = przs[i]; prz = przs[id];
if (!prz) if (!prz)
return NULL; return NULL;
/* Update old/shadowed buffer. */ /* Update old/shadowed buffer. */
if (update) if (prz->type == PSTORE_TYPE_DMESG)
persistent_ram_save_old(prz); persistent_ram_save_old(prz);
if (!persistent_ram_old_size(prz)) if (!persistent_ram_old_size(prz))
return NULL; return NULL;
*typep = type; record->type = prz->type;
*id = i; record->id = id;
return prz; return prz;
} }
@ -160,57 +188,27 @@ static bool prz_ok(struct persistent_ram_zone *prz)
persistent_ram_ecc_string(prz, NULL, 0)); persistent_ram_ecc_string(prz, NULL, 0));
} }
static ssize_t ftrace_log_combine(struct persistent_ram_zone *dest, #ifdef CONFIG_PSTORE_BOOT_LOG
struct persistent_ram_zone *src) ssize_t ramoops_pstore_read_for_boot_log(struct pstore_record *record)
{ {
size_t dest_size, src_size, total, dest_off, src_off; struct ramoops_context *cxt = record->psi->data;
size_t dest_idx = 0, src_idx = 0, merged_idx = 0; struct persistent_ram_zone *prz;
void *merged_buf;
struct pstore_ftrace_record *drec, *srec, *mrec;
size_t record_size = sizeof(struct pstore_ftrace_record);
dest_off = dest->old_log_size % record_size; if (!cxt)
dest_size = dest->old_log_size - dest_off; return 0;
src_off = src->old_log_size % record_size; prz = cxt->boot_przs[record->id];
src_size = src->old_log_size - src_off;
total = dest_size + src_size; if (!prz)
merged_buf = kmalloc(total, GFP_KERNEL); return 0;
if (!merged_buf)
return -ENOMEM;
drec = (struct pstore_ftrace_record *)(dest->old_log + dest_off); persistent_ram_free_old(prz);
srec = (struct pstore_ftrace_record *)(src->old_log + src_off); persistent_ram_save_old(prz);
mrec = (struct pstore_ftrace_record *)(merged_buf); record->buf = prz->old_log;
record->size = prz->old_log_size;
while (dest_size > 0 && src_size > 0) { return record->size;
if (pstore_ftrace_read_timestamp(&drec[dest_idx]) <
pstore_ftrace_read_timestamp(&srec[src_idx])) {
mrec[merged_idx++] = drec[dest_idx++];
dest_size -= record_size;
} else {
mrec[merged_idx++] = srec[src_idx++];
src_size -= record_size;
}
}
while (dest_size > 0) {
mrec[merged_idx++] = drec[dest_idx++];
dest_size -= record_size;
}
while (src_size > 0) {
mrec[merged_idx++] = srec[src_idx++];
src_size -= record_size;
}
kfree(dest->old_log);
dest->old_log = merged_buf;
dest->old_log_size = total;
return 0;
} }
#endif
static ssize_t ramoops_pstore_read(struct pstore_record *record) static ssize_t ramoops_pstore_read(struct pstore_record *record)
{ {
@ -231,10 +229,8 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
/* Find the next valid persistent_ram_zone for DMESG */ /* Find the next valid persistent_ram_zone for DMESG */
while (cxt->dump_read_cnt < cxt->max_dump_cnt && !prz) { while (cxt->dump_read_cnt < cxt->max_dump_cnt && !prz) {
prz = ramoops_get_next_prz(cxt->dprzs, &cxt->dump_read_cnt, prz = ramoops_get_next_prz(cxt->dprzs, cxt->dump_read_cnt++,
cxt->max_dump_cnt, &record->id, record);
&record->type,
PSTORE_TYPE_DMESG, 1);
if (!prz_ok(prz)) if (!prz_ok(prz))
continue; continue;
header_length = ramoops_read_kmsg_hdr(persistent_ram_old(prz), header_length = ramoops_read_kmsg_hdr(persistent_ram_old(prz),
@ -248,22 +244,18 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
} }
} }
if (!prz_ok(prz)) if (!prz_ok(prz) && !cxt->console_read_cnt++)
prz = ramoops_get_next_prz(&cxt->cprz, &cxt->console_read_cnt, prz = ramoops_get_next_prz(&cxt->cprz, 0 /* single */, record);
1, &record->id, &record->type,
PSTORE_TYPE_CONSOLE, 0);
if (!prz_ok(prz)) if (!prz_ok(prz) && !cxt->pmsg_read_cnt++)
prz = ramoops_get_next_prz(&cxt->mprz, &cxt->pmsg_read_cnt, prz = ramoops_get_next_prz(&cxt->mprz, 0 /* single */, record);
1, &record->id, &record->type,
PSTORE_TYPE_PMSG, 0);
/* ftrace is last since it may want to dynamically allocate memory. */ /* ftrace is last since it may want to dynamically allocate memory. */
if (!prz_ok(prz)) { if (!prz_ok(prz)) {
if (!(cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU)) { if (!(cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU) &&
prz = ramoops_get_next_prz(cxt->fprzs, !cxt->ftrace_read_cnt++) {
&cxt->ftrace_read_cnt, 1, &record->id, prz = ramoops_get_next_prz(cxt->fprzs, 0 /* single */,
&record->type, PSTORE_TYPE_FTRACE, 0); record);
} else { } else {
/* /*
* Build a new dummy record which combines all the * Build a new dummy record which combines all the
@ -280,11 +272,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
while (cxt->ftrace_read_cnt < cxt->max_ftrace_cnt) { while (cxt->ftrace_read_cnt < cxt->max_ftrace_cnt) {
prz_next = ramoops_get_next_prz(cxt->fprzs, prz_next = ramoops_get_next_prz(cxt->fprzs,
&cxt->ftrace_read_cnt, cxt->ftrace_read_cnt++, record);
cxt->max_ftrace_cnt,
&record->id,
&record->type,
PSTORE_TYPE_FTRACE, 0);
if (!prz_ok(prz_next)) if (!prz_ok(prz_next))
continue; continue;
@ -293,7 +281,12 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
tmp_prz->corrected_bytes += tmp_prz->corrected_bytes +=
prz_next->corrected_bytes; prz_next->corrected_bytes;
tmp_prz->bad_blocks += prz_next->bad_blocks; tmp_prz->bad_blocks += prz_next->bad_blocks;
size = ftrace_log_combine(tmp_prz, prz_next);
size = pstore_ftrace_combine_log(
&tmp_prz->old_log,
&tmp_prz->old_log_size,
prz_next->old_log,
prz_next->old_log_size);
if (size) if (size)
goto out; goto out;
} }
@ -304,10 +297,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
if (!prz_ok(prz)) { if (!prz_ok(prz)) {
while (cxt->boot_log_read_cnt < cxt->max_boot_log_cnt && !prz) { while (cxt->boot_log_read_cnt < cxt->max_boot_log_cnt && !prz) {
prz = ramoops_get_next_prz(cxt->boot_przs, &cxt->boot_log_read_cnt, prz = ramoops_get_next_prz(cxt->boot_przs, cxt->boot_log_read_cnt++, record);
cxt->max_boot_log_cnt, &record->id,
&record->type,
PSTORE_TYPE_BOOT_LOG, 0);
if (!prz_ok(prz)) if (!prz_ok(prz))
continue; continue;
} }
@ -319,12 +309,15 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
goto out; goto out;
} }
size = persistent_ram_old_size(prz) - header_length;
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
/* don't copy boot log */ if (record->type == PSTORE_TYPE_BOOT_LOG) {
if (record->type == PSTORE_TYPE_BOOT_LOG) persistent_ram_free_old(prz);
goto out; persistent_ram_save_old(prz);
}
#endif #endif
size = persistent_ram_old_size(prz) - header_length;
/* ECC correction notice */ /* ECC correction notice */
record->ecc_notice_size = persistent_ram_ecc_string(prz, NULL, 0); record->ecc_notice_size = persistent_ram_ecc_string(prz, NULL, 0);
@ -352,17 +345,15 @@ out:
static size_t ramoops_write_kmsg_hdr(struct persistent_ram_zone *prz, static size_t ramoops_write_kmsg_hdr(struct persistent_ram_zone *prz,
struct pstore_record *record) struct pstore_record *record)
{ {
char *hdr; char hdr[36]; /* "===="(4), %lld(20), "."(1), %06lu(6), "-%c\n"(3) */
size_t len; size_t len;
hdr = kasprintf(GFP_ATOMIC, RAMOOPS_KERNMSG_HDR "%lld.%06lu-%c\n", len = scnprintf(hdr, sizeof(hdr),
RAMOOPS_KERNMSG_HDR "%lld.%06lu-%c\n",
(time64_t)record->time.tv_sec, (time64_t)record->time.tv_sec,
record->time.tv_nsec / 1000, record->time.tv_nsec / 1000,
record->compressed ? 'C' : 'D'); record->compressed ? 'C' : 'D');
WARN_ON_ONCE(!hdr);
len = hdr ? strlen(hdr) : 0;
persistent_ram_write(prz, hdr, len); persistent_ram_write(prz, hdr, len);
kfree(hdr);
return len; return len;
} }
@ -403,16 +394,14 @@ static int notrace ramoops_pstore_write(struct pstore_record *record)
return -EINVAL; return -EINVAL;
/* /*
* Out of the various dmesg dump types, ramoops is currently designed * We could filter on record->reason here if we wanted to (which
* to only store crash logs, rather than storing general kernel logs. * would duplicate what happened before the "max_reason" setting
* was added), but that would defeat the purpose of a system
* changing printk.always_kmsg_dump, so instead log everything that
* the kmsg dumper sends us, since it should be doing the filtering
* based on the combination of printk.always_kmsg_dump and our
* requested "max_reason".
*/ */
if (record->reason != KMSG_DUMP_OOPS &&
record->reason != KMSG_DUMP_PANIC)
return -EINVAL;
/* Skip Oopes when configured to do so. */
if (record->reason == KMSG_DUMP_OOPS && !cxt->dump_oops)
return -EINVAL;
/* /*
* Explicitly only take the first part of any new crash. * Explicitly only take the first part of any new crash.
@ -441,6 +430,9 @@ static int notrace ramoops_pstore_write(struct pstore_record *record)
/* Build header and append record contents. */ /* Build header and append record contents. */
hlen = ramoops_write_kmsg_hdr(prz, record); hlen = ramoops_write_kmsg_hdr(prz, record);
if (!hlen)
return -ENOMEM;
size = record->size; size = record->size;
if (size + hlen > prz->buffer_size) if (size + hlen > prz->buffer_size)
size = prz->buffer_size - hlen; size = prz->buffer_size - hlen;
@ -600,9 +592,17 @@ static int ramoops_init_przs(const char *name,
goto fail; goto fail;
for (i = 0; i < *cnt; i++) { for (i = 0; i < *cnt; i++) {
char *label;
if (*cnt == 1)
label = kasprintf(GFP_KERNEL, "ramoops:%s", name);
else
label = kasprintf(GFP_KERNEL, "ramoops:%s(%d/%d)",
name, i, *cnt - 1);
prz_ar[i] = persistent_ram_new(*paddr, zone_sz, sig, prz_ar[i] = persistent_ram_new(*paddr, zone_sz, sig,
&cxt->ecc_info, &cxt->ecc_info,
cxt->memtype, flags); cxt->memtype, flags, label);
kfree(label);
if (IS_ERR(prz_ar[i])) { if (IS_ERR(prz_ar[i])) {
err = PTR_ERR(prz_ar[i]); err = PTR_ERR(prz_ar[i]);
dev_err(dev, "failed to request %s mem region (0x%zx@0x%llx): %d\n", dev_err(dev, "failed to request %s mem region (0x%zx@0x%llx): %d\n",
@ -617,6 +617,7 @@ static int ramoops_init_przs(const char *name,
goto fail; goto fail;
} }
*paddr += zone_sz; *paddr += zone_sz;
prz_ar[i]->type = pstore_name_to_type(name);
} }
*przs = prz_ar; *przs = prz_ar;
@ -632,6 +633,8 @@ static int ramoops_init_prz(const char *name,
struct persistent_ram_zone **prz, struct persistent_ram_zone **prz,
phys_addr_t *paddr, size_t sz, u32 sig) phys_addr_t *paddr, size_t sz, u32 sig)
{ {
char *label;
if (!sz) if (!sz)
return 0; return 0;
@ -642,8 +645,10 @@ static int ramoops_init_prz(const char *name,
return -ENOMEM; return -ENOMEM;
} }
label = kasprintf(GFP_KERNEL, "ramoops:%s", name);
*prz = persistent_ram_new(*paddr, sz, sig, &cxt->ecc_info, *prz = persistent_ram_new(*paddr, sz, sig, &cxt->ecc_info,
cxt->memtype, 0); cxt->memtype, PRZ_FLAG_ZAP_OLD, label);
kfree(label);
if (IS_ERR(*prz)) { if (IS_ERR(*prz)) {
int err = PTR_ERR(*prz); int err = PTR_ERR(*prz);
@ -652,26 +657,31 @@ static int ramoops_init_prz(const char *name,
return err; return err;
} }
persistent_ram_zap(*prz);
*paddr += sz; *paddr += sz;
(*prz)->type = pstore_name_to_type(name);
return 0; return 0;
} }
static int ramoops_parse_dt_size(struct platform_device *pdev, /* Read a u32 from a dt property and make sure it's safe for an int. */
const char *propname, u32 *value) static int ramoops_parse_dt_u32(struct platform_device *pdev,
const char *propname,
u32 default_value, u32 *value)
{ {
u32 val32 = 0; u32 val32 = 0;
int ret; int ret;
ret = of_property_read_u32(pdev->dev.of_node, propname, &val32); ret = of_property_read_u32(pdev->dev.of_node, propname, &val32);
if (ret < 0 && ret != -EINVAL) { if (ret == -EINVAL) {
/* field is missing, use default value. */
val32 = default_value;
} else if (ret < 0) {
dev_err(&pdev->dev, "failed to parse property %s: %d\n", dev_err(&pdev->dev, "failed to parse property %s: %d\n",
propname, ret); propname, ret);
return ret; return ret;
} }
/* Sanity check our results. */
if (val32 > INT_MAX) { if (val32 > INT_MAX) {
dev_err(&pdev->dev, "%s %u > INT_MAX\n", propname, val32); dev_err(&pdev->dev, "%s %u > INT_MAX\n", propname, val32);
return -EOVERFLOW; return -EOVERFLOW;
@ -685,6 +695,8 @@ static int ramoops_parse_dt(struct platform_device *pdev,
struct ramoops_platform_data *pdata) struct ramoops_platform_data *pdata)
{ {
struct device_node *of_node = pdev->dev.of_node; struct device_node *of_node = pdev->dev.of_node;
struct device_node *parent_node;
struct reserved_mem *rmem;
struct resource *res; struct resource *res;
u32 value; u32 value;
int ret; int ret;
@ -693,38 +705,123 @@ static int ramoops_parse_dt(struct platform_device *pdev,
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) { if (!res) {
dev_err(&pdev->dev, rmem = of_reserved_mem_lookup(of_node);
"failed to locate DT /reserved-memory resource\n"); if (rmem) {
return -EINVAL; pdata->mem_size = rmem->size;
pdata->mem_address = rmem->base;
} else {
dev_err(&pdev->dev,
"failed to locate DT /reserved-memory resource\n");
return -EINVAL;
}
} else {
pdata->mem_size = resource_size(res);
pdata->mem_address = res->start;
} }
pdata->mem_size = resource_size(res); /*
pdata->mem_address = res->start; * Setting "unbuffered" is deprecated and will be ignored if
* "mem_type" is also specified.
*/
pdata->mem_type = of_property_read_bool(of_node, "unbuffered"); pdata->mem_type = of_property_read_bool(of_node, "unbuffered");
pdata->dump_oops = !of_property_read_bool(of_node, "no-dump-oops"); /*
* Setting "no-dump-oops" is deprecated and will be ignored if
* "max_reason" is also specified.
*/
if (of_property_read_bool(of_node, "no-dump-oops"))
pdata->max_reason = KMSG_DUMP_PANIC;
else
pdata->max_reason = KMSG_DUMP_OOPS;
#define parse_size(name, field) { \ #define parse_u32(name, field, default_value) { \
ret = ramoops_parse_dt_size(pdev, name, &value); \ ret = ramoops_parse_dt_u32(pdev, name, default_value, \
&value); \
if (ret < 0) \ if (ret < 0) \
return ret; \ return ret; \
field = value; \ field = value; \
} }
parse_size("record-size", pdata->record_size); parse_u32("mem-type", pdata->mem_type, pdata->mem_type);
parse_size("console-size", pdata->console_size); parse_u32("record-size", pdata->record_size, 0);
parse_size("ftrace-size", pdata->ftrace_size); parse_u32("console-size", pdata->console_size, 0);
parse_size("pmsg-size", pdata->pmsg_size); parse_u32("ftrace-size", pdata->ftrace_size, 0);
parse_size("ecc-size", pdata->ecc_info.ecc_size); parse_u32("pmsg-size", pdata->pmsg_size, 0);
parse_size("flags", pdata->flags); parse_u32("ecc-size", pdata->ecc_info.ecc_size, 0);
parse_u32("flags", pdata->flags, 0);
parse_u32("max-reason", pdata->max_reason, pdata->max_reason);
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
parse_size("boot-log-size", pdata->boot_log_size); parse_u32("boot-log-size", pdata->boot_log_size, 0);
parse_size("boot-log-count", pdata->max_boot_log_cnt); parse_u32("boot-log-count", pdata->max_boot_log_cnt, 0);
#endif #endif
#undef parse_size
#undef parse_u32
/*
* Some old Chromebooks relied on the kernel setting the
* console_size and pmsg_size to the record size since that's
* what the downstream kernel did. These same Chromebooks had
* "ramoops" straight under the root node which isn't
* according to the current upstream bindings (though it was
* arguably acceptable under a prior version of the bindings).
* Let's make those old Chromebooks work by detecting that
* we're not a child of "reserved-memory" and mimicking the
* expected behavior.
*/
parent_node = of_get_parent(of_node);
if (!of_node_name_eq(parent_node, "reserved-memory") &&
!pdata->console_size && !pdata->ftrace_size &&
!pdata->pmsg_size && !pdata->ecc_info.ecc_size) {
pdata->console_size = pdata->record_size;
pdata->pmsg_size = pdata->record_size;
}
of_node_put(parent_node);
return 0; return 0;
} }
#if IS_REACHABLE(CONFIG_ROCKCHIP_MINIDUMP)
static void _ramoops_register_ram_zone_info_to_minidump(struct persistent_ram_zone *prz)
{
struct md_region md_entry = {};
strscpy(md_entry.name, prz->label, sizeof(md_entry.name));
md_entry.virt_addr = (u64)prz->vaddr;
md_entry.phys_addr = prz->paddr;
md_entry.size = prz->size;
if (rk_minidump_add_region(&md_entry) < 0)
pr_err("Failed to add %s in Minidump\n", prz->label);
}
static void ramoops_register_ram_zone_info_to_minidump(struct ramoops_context *cxt)
{
int i = 0;
struct persistent_ram_zone *prz = NULL;
for (i = 0; i < cxt->max_boot_log_cnt; i++) {
prz = cxt->boot_przs[i];
_ramoops_register_ram_zone_info_to_minidump(prz);
}
for (i = 0; i < cxt->max_dump_cnt; i++) {
prz = cxt->dprzs[i];
_ramoops_register_ram_zone_info_to_minidump(prz);
}
for (i = 0; i < cxt->max_ftrace_cnt; i++) {
prz = cxt->fprzs[i];
_ramoops_register_ram_zone_info_to_minidump(prz);
}
prz = cxt->cprz;
_ramoops_register_ram_zone_info_to_minidump(prz);
prz = cxt->mprz;
_ramoops_register_ram_zone_info_to_minidump(prz);
}
#endif
static int ramoops_probe(struct platform_device *pdev) static int ramoops_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
@ -736,15 +833,6 @@ static int ramoops_probe(struct platform_device *pdev)
int err = -EINVAL; int err = -EINVAL;
int i = 0; int i = 0;
if (dev_of_node(dev) && !pdata) {
pdata = &pdata_local;
memset(pdata, 0, sizeof(*pdata));
err = ramoops_parse_dt(pdev, pdata);
if (err < 0)
goto fail_out;
}
/* /*
* Only a single ramoops area allowed at a time, so fail extra * Only a single ramoops area allowed at a time, so fail extra
* probes. * probes.
@ -754,6 +842,15 @@ static int ramoops_probe(struct platform_device *pdev)
goto fail_out; goto fail_out;
} }
if (dev_of_node(dev) && !pdata) {
pdata = &pdata_local;
memset(pdata, 0, sizeof(*pdata));
err = ramoops_parse_dt(pdev, pdata);
if (err < 0)
goto fail_out;
}
/* Make sure we didn't get bogus platform data pointer. */ /* Make sure we didn't get bogus platform data pointer. */
if (!pdata) { if (!pdata) {
pr_err("NULL platform data\n"); pr_err("NULL platform data\n");
@ -794,7 +891,6 @@ static int ramoops_probe(struct platform_device *pdev)
cxt->console_size = pdata->console_size; cxt->console_size = pdata->console_size;
cxt->ftrace_size = pdata->ftrace_size; cxt->ftrace_size = pdata->ftrace_size;
cxt->pmsg_size = pdata->pmsg_size; cxt->pmsg_size = pdata->pmsg_size;
cxt->dump_oops = pdata->dump_oops;
cxt->flags = pdata->flags; cxt->flags = pdata->flags;
cxt->ecc_info = pdata->ecc_info; cxt->ecc_info = pdata->ecc_info;
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
@ -811,7 +907,7 @@ static int ramoops_probe(struct platform_device *pdev)
#endif #endif
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
err = ramoops_init_przs("boot_log", dev, cxt, &cxt->boot_przs, &paddr, err = ramoops_init_przs("boot-log", dev, cxt, &cxt->boot_przs, &paddr,
cxt->boot_log_size, -1, cxt->boot_log_size, -1,
&cxt->max_boot_log_cnt, 0, 0); &cxt->max_boot_log_cnt, 0, 0);
if (err) if (err)
@ -821,7 +917,7 @@ static int ramoops_probe(struct platform_device *pdev)
pr_info("boot-log-%d\t0x%zx@%pa\n", i, cxt->boot_przs[i]->size, &cxt->boot_przs[i]->paddr); pr_info("boot-log-%d\t0x%zx@%pa\n", i, cxt->boot_przs[i]->size, &cxt->boot_przs[i]->paddr);
#endif #endif
err = ramoops_init_przs("dump", dev, cxt, &cxt->dprzs, &paddr, err = ramoops_init_przs("dmesg", dev, cxt, &cxt->dprzs, &paddr,
dump_mem_sz, cxt->record_size, dump_mem_sz, cxt->record_size,
&cxt->max_dump_cnt, 0, 0); &cxt->max_dump_cnt, 0, 0);
if (err) if (err)
@ -855,7 +951,6 @@ static int ramoops_probe(struct platform_device *pdev)
cxt->pmsg_size, 0); cxt->pmsg_size, 0);
if (err) if (err)
goto fail_init_mprz; goto fail_init_mprz;
if (cxt->pmsg_size > 0) if (cxt->pmsg_size > 0)
pr_info("pmsg\t0x%zx@%pa\n", cxt->mprz->size, &cxt->mprz->paddr); pr_info("pmsg\t0x%zx@%pa\n", cxt->mprz->size, &cxt->mprz->paddr);
@ -867,8 +962,10 @@ static int ramoops_probe(struct platform_device *pdev)
* the single region size is how to check. * the single region size is how to check.
*/ */
cxt->pstore.flags = 0; cxt->pstore.flags = 0;
if (cxt->max_dump_cnt) if (cxt->max_dump_cnt) {
cxt->pstore.flags |= PSTORE_FLAGS_DMESG; cxt->pstore.flags |= PSTORE_FLAGS_DMESG;
cxt->pstore.max_reason = pdata->max_reason;
}
if (cxt->console_size) if (cxt->console_size)
cxt->pstore.flags |= PSTORE_FLAGS_CONSOLE; cxt->pstore.flags |= PSTORE_FLAGS_CONSOLE;
if (cxt->max_ftrace_cnt) if (cxt->max_ftrace_cnt)
@ -908,14 +1005,16 @@ static int ramoops_probe(struct platform_device *pdev)
mem_size = pdata->mem_size; mem_size = pdata->mem_size;
mem_address = pdata->mem_address; mem_address = pdata->mem_address;
record_size = pdata->record_size; record_size = pdata->record_size;
dump_oops = pdata->dump_oops; ramoops_max_reason = pdata->max_reason;
ramoops_console_size = pdata->console_size; ramoops_console_size = pdata->console_size;
ramoops_pmsg_size = pdata->pmsg_size; ramoops_pmsg_size = pdata->pmsg_size;
ramoops_ftrace_size = pdata->ftrace_size; ramoops_ftrace_size = pdata->ftrace_size;
#if IS_REACHABLE(CONFIG_ROCKCHIP_MINIDUMP)
pr_info("attached 0x%lx@0x%llx, ecc: %d/%d\n", ramoops_register_ram_zone_info_to_minidump(cxt);
#endif
pr_info("using 0x%lx@0x%llx, ecc: %d\n",
cxt->size, (unsigned long long)cxt->phys_addr, cxt->size, (unsigned long long)cxt->phys_addr,
cxt->ecc_info.ecc_size, cxt->ecc_info.block_size); cxt->ecc_info.ecc_size);
return 0; return 0;
@ -967,13 +1066,12 @@ static inline void ramoops_unregister_dummy(void)
{ {
platform_device_unregister(dummy); platform_device_unregister(dummy);
dummy = NULL; dummy = NULL;
kfree(dummy_data);
dummy_data = NULL;
} }
static void __init ramoops_register_dummy(void) static void __init ramoops_register_dummy(void)
{ {
struct ramoops_platform_data pdata;
/* /*
* Prepare a dummy platform data structure to carry the module * Prepare a dummy platform data structure to carry the module
* parameters. If mem_size isn't set, then there are no module * parameters. If mem_size isn't set, then there are no module
@ -984,35 +1082,38 @@ static void __init ramoops_register_dummy(void)
pr_info("using module parameters\n"); pr_info("using module parameters\n");
dummy_data = kzalloc(sizeof(*dummy_data), GFP_KERNEL); memset(&pdata, 0, sizeof(pdata));
if (!dummy_data) { pdata.mem_size = mem_size;
pr_info("could not allocate pdata\n"); pdata.mem_address = mem_address;
return; pdata.mem_type = mem_type;
} pdata.record_size = record_size;
pdata.console_size = ramoops_console_size;
dummy_data->mem_size = mem_size; pdata.ftrace_size = ramoops_ftrace_size;
dummy_data->mem_address = mem_address; pdata.pmsg_size = ramoops_pmsg_size;
dummy_data->mem_type = mem_type; /* If "max_reason" is set, its value has priority over "dump_oops". */
dummy_data->record_size = record_size; if (ramoops_max_reason >= 0)
dummy_data->console_size = ramoops_console_size; pdata.max_reason = ramoops_max_reason;
dummy_data->ftrace_size = ramoops_ftrace_size; /* Otherwise, if "dump_oops" is set, parse it into "max_reason". */
dummy_data->pmsg_size = ramoops_pmsg_size; else if (ramoops_dump_oops != -1)
dummy_data->dump_oops = dump_oops; pdata.max_reason = ramoops_dump_oops ? KMSG_DUMP_OOPS
dummy_data->flags = RAMOOPS_FLAG_FTRACE_PER_CPU; : KMSG_DUMP_PANIC;
/* And if neither are explicitly set, use the default. */
else
pdata.max_reason = KMSG_DUMP_OOPS;
pdata.flags = RAMOOPS_FLAG_FTRACE_PER_CPU;
/* /*
* For backwards compatibility ramoops.ecc=1 means 16 bytes ECC * For backwards compatibility ramoops.ecc=1 means 16 bytes ECC
* (using 1 byte for ECC isn't much of use anyway). * (using 1 byte for ECC isn't much of use anyway).
*/ */
dummy_data->ecc_info.ecc_size = ramoops_ecc == 1 ? 16 : ramoops_ecc; pdata.ecc_info.ecc_size = ramoops_ecc == 1 ? 16 : ramoops_ecc;
dummy = platform_device_register_data(NULL, "ramoops", -1, dummy = platform_device_register_data(NULL, "ramoops", -1,
dummy_data, sizeof(struct ramoops_platform_data)); &pdata, sizeof(pdata));
if (IS_ERR(dummy)) { if (IS_ERR(dummy)) {
pr_info("could not create platform device: %ld\n", pr_info("could not create platform device: %ld\n",
PTR_ERR(dummy)); PTR_ERR(dummy));
dummy = NULL; dummy = NULL;
ramoops_unregister_dummy();
} }
} }

View File

@ -1,18 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
/* /*
* Copyright (C) 2012 Google, Inc. * Copyright (C) 2012 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/ */
#define pr_fmt(fmt) "persistent_ram: " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h> #include <linux/device.h>
#include <linux/err.h> #include <linux/err.h>
@ -29,6 +20,23 @@
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <asm/page.h> #include <asm/page.h>
/**
* struct persistent_ram_buffer - persistent circular RAM buffer
*
* @sig:
* signature to indicate header (PERSISTENT_RAM_SIG xor PRZ-type value)
* @start:
* offset into @data where the beginning of the stored bytes begin
* @size:
* number of valid bytes stored in @data
*/
struct persistent_ram_buffer {
uint32_t sig;
atomic_t start;
atomic_t size;
uint8_t data[];
};
#define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */ #define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */
static inline size_t buffer_size(struct persistent_ram_zone *prz) static inline size_t buffer_size(struct persistent_ram_zone *prz)
@ -275,7 +283,7 @@ static int notrace persistent_ram_update_user(struct persistent_ram_zone *prz,
const void __user *s, unsigned int start, unsigned int count) const void __user *s, unsigned int start, unsigned int count)
{ {
struct persistent_ram_buffer *buffer = prz->buffer; struct persistent_ram_buffer *buffer = prz->buffer;
int ret = unlikely(__copy_from_user(buffer->data + start, s, count)) ? int ret = unlikely(copy_from_user(buffer->data + start, s, count)) ?
-EFAULT : 0; -EFAULT : 0;
persistent_ram_update_ecc(prz, start, count); persistent_ram_update_ecc(prz, start, count);
return ret; return ret;
@ -340,8 +348,6 @@ int notrace persistent_ram_write_user(struct persistent_ram_zone *prz,
int rem, ret = 0, c = count; int rem, ret = 0, c = count;
size_t start; size_t start;
if (unlikely(!access_ok(VERIFY_READ, s, count)))
return -EFAULT;
if (unlikely(c > prz->buffer_size)) { if (unlikely(c > prz->buffer_size)) {
s += c - prz->buffer_size; s += c - prz->buffer_size;
c = prz->buffer_size; c = prz->buffer_size;
@ -390,6 +396,10 @@ void persistent_ram_zap(struct persistent_ram_zone *prz)
persistent_ram_update_header_ecc(prz); persistent_ram_update_header_ecc(prz);
} }
#define MEM_TYPE_WCOMBINE 0
#define MEM_TYPE_NONCACHED 1
#define MEM_TYPE_NORMAL 2
static void *persistent_ram_vmap(phys_addr_t start, size_t size, static void *persistent_ram_vmap(phys_addr_t start, size_t size,
unsigned int memtype) unsigned int memtype)
{ {
@ -403,10 +413,20 @@ static void *persistent_ram_vmap(phys_addr_t start, size_t size,
page_start = start - offset_in_page(start); page_start = start - offset_in_page(start);
page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE); page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE);
if (memtype) switch (memtype) {
case MEM_TYPE_NORMAL:
prot = PAGE_KERNEL;
break;
case MEM_TYPE_NONCACHED:
prot = pgprot_noncached(PAGE_KERNEL); prot = pgprot_noncached(PAGE_KERNEL);
else break;
case MEM_TYPE_WCOMBINE:
prot = pgprot_writecombine(PAGE_KERNEL); prot = pgprot_writecombine(PAGE_KERNEL);
break;
default:
pr_err("invalid mem_type=%d\n", memtype);
return NULL;
}
pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL); pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL);
if (!pages) { if (!pages) {
@ -431,12 +451,13 @@ static void *persistent_ram_vmap(phys_addr_t start, size_t size,
} }
static void *persistent_ram_iomap(phys_addr_t start, size_t size, static void *persistent_ram_iomap(phys_addr_t start, size_t size,
unsigned int memtype) unsigned int memtype, char *label)
{ {
void *va; void *va;
if (!request_mem_region(start, size, "persistent_ram")) { if (!request_mem_region(start, size, label ?: "ramoops")) {
pr_err("request mem region (0x%llx@0x%llx) failed\n", pr_err("request mem region (%s 0x%llx@0x%llx) failed\n",
label ?: "ramoops",
(unsigned long long)size, (unsigned long long)start); (unsigned long long)size, (unsigned long long)start);
return NULL; return NULL;
} }
@ -463,7 +484,8 @@ static int persistent_ram_buffer_map(phys_addr_t start, phys_addr_t size,
if (pfn_valid(start >> PAGE_SHIFT)) if (pfn_valid(start >> PAGE_SHIFT))
prz->vaddr = persistent_ram_vmap(start, size, memtype); prz->vaddr = persistent_ram_vmap(start, size, memtype);
else else
prz->vaddr = persistent_ram_iomap(start, size, memtype); prz->vaddr = persistent_ram_iomap(start, size, memtype,
prz->label);
if (!prz->vaddr) { if (!prz->vaddr) {
pr_err("%s: Failed to map 0x%llx pages at 0x%llx\n", __func__, pr_err("%s: Failed to map 0x%llx pages at 0x%llx\n", __func__,
@ -481,10 +503,13 @@ static int persistent_ram_post_init(struct persistent_ram_zone *prz, u32 sig,
struct persistent_ram_ecc_info *ecc_info) struct persistent_ram_ecc_info *ecc_info)
{ {
int ret; int ret;
bool zap = !!(prz->flags & PRZ_FLAG_ZAP_OLD);
ret = persistent_ram_init_ecc(prz, ecc_info); ret = persistent_ram_init_ecc(prz, ecc_info);
if (ret) if (ret) {
pr_warn("ECC failed %s\n", prz->label);
return ret; return ret;
}
sig ^= PERSISTENT_RAM_SIG; sig ^= PERSISTENT_RAM_SIG;
@ -495,23 +520,25 @@ static int persistent_ram_post_init(struct persistent_ram_zone *prz, u32 sig,
} }
if (buffer_size(prz) > prz->buffer_size || if (buffer_size(prz) > prz->buffer_size ||
buffer_start(prz) > buffer_size(prz)) buffer_start(prz) > buffer_size(prz)) {
pr_info("found existing invalid buffer, size %zu, start %zu\n", pr_info("found existing invalid buffer, size %zu, start %zu\n",
buffer_size(prz), buffer_start(prz)); buffer_size(prz), buffer_start(prz));
else { zap = true;
} else {
pr_debug("found existing buffer, size %zu, start %zu\n", pr_debug("found existing buffer, size %zu, start %zu\n",
buffer_size(prz), buffer_start(prz)); buffer_size(prz), buffer_start(prz));
persistent_ram_save_old(prz); persistent_ram_save_old(prz);
return 0;
} }
} else { } else {
pr_debug("no valid data in buffer (sig = 0x%08x)\n", pr_debug("no valid data in buffer (sig = 0x%08x)\n",
prz->buffer->sig); prz->buffer->sig);
prz->buffer->sig = sig;
zap = true;
} }
/* Rewind missing or invalid memory area. */ /* Reset missing, invalid, or single-use memory area. */
prz->buffer->sig = sig; if (zap)
persistent_ram_zap(prz); persistent_ram_zap(prz);
return 0; return 0;
} }
@ -539,12 +566,13 @@ void persistent_ram_free(struct persistent_ram_zone *prz)
prz->ecc_info.par = NULL; prz->ecc_info.par = NULL;
persistent_ram_free_old(prz); persistent_ram_free_old(prz);
kfree(prz->label);
kfree(prz); kfree(prz);
} }
struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size, struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
u32 sig, struct persistent_ram_ecc_info *ecc_info, u32 sig, struct persistent_ram_ecc_info *ecc_info,
unsigned int memtype, u32 flags) unsigned int memtype, u32 flags, char *label)
{ {
struct persistent_ram_zone *prz; struct persistent_ram_zone *prz;
int ret = -ENOMEM; int ret = -ENOMEM;
@ -558,6 +586,7 @@ struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
/* Initialize general buffer state. */ /* Initialize general buffer state. */
raw_spin_lock_init(&prz->buffer_lock); raw_spin_lock_init(&prz->buffer_lock);
prz->flags = flags; prz->flags = flags;
prz->label = kstrdup(label, GFP_KERNEL);
ret = persistent_ram_buffer_map(start, size, prz, memtype); ret = persistent_ram_buffer_map(start, size, prz, memtype);
if (ret) if (ret)
@ -567,6 +596,12 @@ struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
if (ret) if (ret)
goto err; goto err;
pr_debug("attached %s 0x%zx@0x%llx: %zu header, %zu data, %zu ecc (%d/%d)\n",
prz->label, prz->size, (unsigned long long)prz->paddr,
sizeof(*prz->buffer), prz->buffer_size,
prz->size - sizeof(*prz->buffer) - prz->buffer_size,
prz->ecc_info.ecc_size, prz->ecc_info.block_size);
return prz; return prz;
err: err:
persistent_ram_free(prz); persistent_ram_free(prz);

1464
fs/pstore/zone.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@ enum kmsg_dump_reason {
KMSG_DUMP_RESTART, KMSG_DUMP_RESTART,
KMSG_DUMP_HALT, KMSG_DUMP_HALT,
KMSG_DUMP_POWEROFF, KMSG_DUMP_POWEROFF,
KMSG_DUMP_SHUTDOWN = KMSG_DUMP_POWEROFF,
}; };
/** /**
@ -54,7 +55,7 @@ struct kmsg_dumper {
#ifdef CONFIG_PRINTK #ifdef CONFIG_PRINTK
void kmsg_dump(enum kmsg_dump_reason reason); void kmsg_dump(enum kmsg_dump_reason reason);
const char *kmsg_dump_reason_str(enum kmsg_dump_reason reason);
bool kmsg_dump_get_line_nolock(struct kmsg_dumper *dumper, bool syslog, bool kmsg_dump_get_line_nolock(struct kmsg_dumper *dumper, bool syslog,
char *line, size_t size, size_t *len); char *line, size_t size, size_t *len);

View File

@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* /*
* Persistent Storage - pstore.h * Persistent Storage - pstore.h
* *
@ -5,19 +6,6 @@
* *
* This code is the generic layer to export data records from platform * This code is the generic layer to export data records from platform
* level persistent storage via a file system. * level persistent storage via a file system.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#ifndef _LINUX_PSTORE_H #ifndef _LINUX_PSTORE_H
#define _LINUX_PSTORE_H #define _LINUX_PSTORE_H
@ -32,13 +20,19 @@
struct module; struct module;
/* pstore record types (see fs/pstore/inode.c for filename templates) */ /*
* pstore record types (see fs/pstore/platform.c for pstore_type_names[])
* These values may be written to storage (see EFI vars backend), so
* they are kind of an ABI. Be careful changing the mappings.
*/
enum pstore_type_id { enum pstore_type_id {
/* Frontend storage types */
PSTORE_TYPE_DMESG = 0, PSTORE_TYPE_DMESG = 0,
PSTORE_TYPE_MCE = 1, PSTORE_TYPE_MCE = 1,
PSTORE_TYPE_CONSOLE = 2, PSTORE_TYPE_CONSOLE = 2,
PSTORE_TYPE_FTRACE = 3, PSTORE_TYPE_FTRACE = 3,
/* PPC64 partition types */
/* PPC64-specific partition types */
PSTORE_TYPE_PPC_RTAS = 4, PSTORE_TYPE_PPC_RTAS = 4,
PSTORE_TYPE_PPC_OF = 5, PSTORE_TYPE_PPC_OF = 5,
PSTORE_TYPE_PPC_COMMON = 6, PSTORE_TYPE_PPC_COMMON = 6,
@ -47,9 +41,14 @@ enum pstore_type_id {
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
PSTORE_TYPE_BOOT_LOG = 9, PSTORE_TYPE_BOOT_LOG = 9,
#endif #endif
PSTORE_TYPE_UNKNOWN = 255
/* End of the list */
PSTORE_TYPE_MAX
}; };
const char *pstore_type_to_name(enum pstore_type_id type);
enum pstore_type_id pstore_name_to_type(const char *name);
struct pstore_info; struct pstore_info;
/** /**
* struct pstore_record - details of a pstore record entry * struct pstore_record - details of a pstore record entry
@ -88,7 +87,7 @@ struct pstore_record {
/** /**
* struct pstore_info - backend pstore driver structure * struct pstore_info - backend pstore driver structure
* *
* @owner: module which is repsonsible for this backend driver * @owner: module which is responsible for this backend driver
* @name: name of the backend driver * @name: name of the backend driver
* *
* @buf_lock: semaphore to serialize access to @buf * @buf_lock: semaphore to serialize access to @buf
@ -100,6 +99,12 @@ struct pstore_record {
* *
* @read_mutex: serializes @open, @read, @close, and @erase callbacks * @read_mutex: serializes @open, @read, @close, and @erase callbacks
* @flags: bitfield of frontends the backend can accept writes for * @flags: bitfield of frontends the backend can accept writes for
* @max_reason: Used when PSTORE_FLAGS_DMESG is set. Contains the
* kmsg_dump_reason enum value. KMSG_DUMP_UNDEF means
* "use existing kmsg_dump() filtering, based on the
* printk.always_kmsg_dump boot param" (which is either
* KMSG_DUMP_OOPS when false, or KMSG_DUMP_MAX when
* true); see printk.always_kmsg_dump for more details.
* @data: backend-private pointer passed back during callbacks * @data: backend-private pointer passed back during callbacks
* *
* Callbacks: * Callbacks:
@ -174,7 +179,7 @@ struct pstore_record {
*/ */
struct pstore_info { struct pstore_info {
struct module *owner; struct module *owner;
char *name; const char *name;
struct semaphore buf_lock; struct semaphore buf_lock;
char *buf; char *buf;
@ -183,6 +188,7 @@ struct pstore_info {
struct mutex read_mutex; struct mutex read_mutex;
int flags; int flags;
int max_reason;
void *data; void *data;
int (*open)(struct pstore_info *psi); int (*open)(struct pstore_info *psi);
@ -195,13 +201,14 @@ struct pstore_info {
}; };
/* Supported frontends */ /* Supported frontends */
#define PSTORE_FLAGS_DMESG (1 << 0) #define PSTORE_FLAGS_DMESG BIT(0)
#define PSTORE_FLAGS_CONSOLE (1 << 1) #define PSTORE_FLAGS_CONSOLE BIT(1)
#define PSTORE_FLAGS_FTRACE (1 << 2) #define PSTORE_FLAGS_FTRACE BIT(2)
#define PSTORE_FLAGS_PMSG (1 << 3) #define PSTORE_FLAGS_PMSG BIT(3)
#ifdef CONFIG_PSTORE_BOOT_LOG #ifdef CONFIG_PSTORE_BOOT_LOG
#define PSTORE_FLAGS_BOOT_LOG (1 << 4) #define PSTORE_FLAGS_BOOT_LOG BIT(4)
#endif #endif
extern int pstore_register(struct pstore_info *); extern int pstore_register(struct pstore_info *);
extern void pstore_unregister(struct pstore_info *); extern void pstore_unregister(struct pstore_info *);

View File

@ -1,17 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* /*
* Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com> * Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com>
* Copyright (C) 2011 Kees Cook <keescook@chromium.org> * Copyright (C) 2011 Kees Cook <keescook@chromium.org>
* Copyright (C) 2011 Google, Inc. * Copyright (C) 2011 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/ */
#ifndef __LINUX_PSTORE_RAM_H__ #ifndef __LINUX_PSTORE_RAM_H__
@ -22,8 +13,8 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/types.h>
#include <linux/pstore.h> #include <linux/pstore.h>
#include <linux/types.h>
/* /*
* Choose whether access to the RAM zone requires locking or not. If a zone * Choose whether access to the RAM zone requires locking or not. If a zone
@ -31,17 +22,15 @@
* PRZ_FLAG_NO_LOCK is used. For all other cases, locking is required. * PRZ_FLAG_NO_LOCK is used. For all other cases, locking is required.
*/ */
#define PRZ_FLAG_NO_LOCK BIT(0) #define PRZ_FLAG_NO_LOCK BIT(0)
/*
* If a PRZ should only have a single-boot lifetime, this marks it as
* getting wiped after its contents get copied out after boot.
*/
#define PRZ_FLAG_ZAP_OLD BIT(1)
struct persistent_ram_buffer; struct persistent_ram_buffer;
struct rs_control; struct rs_control;
struct persistent_ram_buffer {
uint32_t sig;
atomic_t start;
atomic_t size;
uint8_t data[0];
};
struct persistent_ram_ecc_info { struct persistent_ram_ecc_info {
int block_size; int block_size;
int ecc_size; int ecc_size;
@ -50,16 +39,55 @@ struct persistent_ram_ecc_info {
uint16_t *par; uint16_t *par;
}; };
/**
* struct persistent_ram_zone - Details of a persistent RAM zone (PRZ)
* used as a pstore backend
*
* @paddr: physical address of the mapped RAM area
* @size: size of mapping
* @label: unique name of this PRZ
* @type: frontend type for this PRZ
* @flags: holds PRZ_FLAGS_* bits
*
* @buffer_lock:
* locks access to @buffer "size" bytes and "start" offset
* @buffer:
* pointer to actual RAM area managed by this PRZ
* @buffer_size:
* bytes in @buffer->data (not including any trailing ECC bytes)
*
* @par_buffer:
* pointer into @buffer->data containing ECC bytes for @buffer->data
* @par_header:
* pointer into @buffer->data containing ECC bytes for @buffer header
* (i.e. all fields up to @data)
* @rs_decoder:
* RSLIB instance for doing ECC calculations
* @corrected_bytes:
* ECC corrected bytes accounting since boot
* @bad_blocks:
* ECC uncorrectable bytes accounting since boot
* @ecc_info:
* ECC configuration details
*
* @old_log:
* saved copy of @buffer->data prior to most recent wipe
* @old_log_size:
* bytes contained in @old_log
*
*/
struct persistent_ram_zone { struct persistent_ram_zone {
phys_addr_t paddr; phys_addr_t paddr;
size_t size; size_t size;
void *vaddr; void *vaddr;
char *label;
enum pstore_type_id type;
u32 flags;
raw_spinlock_t buffer_lock;
struct persistent_ram_buffer *buffer; struct persistent_ram_buffer *buffer;
size_t buffer_size; size_t buffer_size;
u32 flags;
raw_spinlock_t buffer_lock;
/* ECC correction */
char *par_buffer; char *par_buffer;
char *par_header; char *par_header;
struct rs_control *rs_decoder; struct rs_control *rs_decoder;
@ -71,45 +99,9 @@ struct persistent_ram_zone {
size_t old_log_size; size_t old_log_size;
}; };
struct ramoops_context {
struct persistent_ram_zone **dprzs; /* Oops dump zones */
struct persistent_ram_zone *cprz; /* Console zone */
struct persistent_ram_zone **fprzs; /* Ftrace zones */
struct persistent_ram_zone *mprz; /* PMSG zone */
#ifdef CONFIG_PSTORE_BOOT_LOG
struct persistent_ram_zone **boot_przs; /* BOOT log zones */
#endif
phys_addr_t phys_addr;
unsigned long size;
unsigned int memtype;
size_t record_size;
size_t console_size;
size_t ftrace_size;
size_t pmsg_size;
#ifdef CONFIG_PSTORE_BOOT_LOG
size_t boot_log_size;
#endif
int dump_oops;
u32 flags;
struct persistent_ram_ecc_info ecc_info;
unsigned int max_dump_cnt;
unsigned int dump_write_cnt;
/* _read_cnt need clear on ramoops_pstore_open */
unsigned int dump_read_cnt;
unsigned int console_read_cnt;
unsigned int max_ftrace_cnt;
unsigned int ftrace_read_cnt;
unsigned int pmsg_read_cnt;
#ifdef CONFIG_PSTORE_BOOT_LOG
unsigned int boot_log_read_cnt;
unsigned int max_boot_log_cnt;
#endif
struct pstore_info pstore;
};
struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size, struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
u32 sig, struct persistent_ram_ecc_info *ecc_info, u32 sig, struct persistent_ram_ecc_info *ecc_info,
unsigned int memtype, u32 flags); unsigned int memtype, u32 flags, char *label);
void persistent_ram_free(struct persistent_ram_zone *prz); void persistent_ram_free(struct persistent_ram_zone *prz);
void persistent_ram_zap(struct persistent_ram_zone *prz); void persistent_ram_zap(struct persistent_ram_zone *prz);
@ -124,6 +116,9 @@ void *persistent_ram_old(struct persistent_ram_zone *prz);
void persistent_ram_free_old(struct persistent_ram_zone *prz); void persistent_ram_free_old(struct persistent_ram_zone *prz);
ssize_t persistent_ram_ecc_string(struct persistent_ram_zone *prz, ssize_t persistent_ram_ecc_string(struct persistent_ram_zone *prz,
char *str, size_t len); char *str, size_t len);
#ifdef CONFIG_PSTORE_BOOT_LOG
ssize_t ramoops_pstore_read_for_boot_log(struct pstore_record *record);
#endif
/* /*
* Ramoops platform data * Ramoops platform data
@ -145,7 +140,7 @@ struct ramoops_platform_data {
unsigned long boot_log_size; unsigned long boot_log_size;
unsigned long max_boot_log_cnt; unsigned long max_boot_log_cnt;
#endif #endif
int dump_oops; int max_reason;
u32 flags; u32 flags;
struct persistent_ram_ecc_info ecc_info; struct persistent_ram_ecc_info ecc_info;
}; };

View File

@ -3260,6 +3260,23 @@ void kmsg_dump(enum kmsg_dump_reason reason)
rcu_read_unlock(); rcu_read_unlock();
} }
const char *kmsg_dump_reason_str(enum kmsg_dump_reason reason)
{
switch (reason) {
case KMSG_DUMP_PANIC:
return "Panic";
case KMSG_DUMP_OOPS:
return "Oops";
case KMSG_DUMP_EMERG:
return "Emergency";
case KMSG_DUMP_SHUTDOWN:
return "Shutdown";
default:
return "Unknown";
}
}
EXPORT_SYMBOL_GPL(kmsg_dump_reason_str);
/** /**
* kmsg_dump_get_line_nolock - retrieve one kmsg log line (unlocked version) * kmsg_dump_get_line_nolock - retrieve one kmsg log line (unlocked version)
* @dumper: registered kmsg dumper * @dumper: registered kmsg dumper