Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+191
View File
@@ -0,0 +1,191 @@
# SPDX-License-Identifier: GPL-2.0-only
config PSTORE
tristate "Persistent store support"
default n
help
This option enables generic access to platform level
persistent storage via "pstore" filesystem that can
be mounted as /dev/pstore. Only useful if you have
a platform level driver that registers with pstore to
provide the data, so you probably should just go say "Y"
(or "M") to a platform specific persistent store driver
(e.g. ACPI_APEI on X86) which will select this for you.
If you don't have a platform persistent store driver,
say N.
config PSTORE_DEFAULT_KMSG_BYTES
int "Default kernel log storage space" if EXPERT
depends on PSTORE
default "10240"
help
Defines default size of pstore kernel log storage.
Can be enlarged if needed, not recommended to shrink it.
config PSTORE_COMPRESS
bool "Pstore compression (deflate)"
depends on PSTORE
select ZLIB_INFLATE
select ZLIB_DEFLATE
default y
help
Whether pstore records should be compressed before being written to
the backing store. This is implemented using the zlib 'deflate'
algorithm, using the library implementation instead of using the full
blown crypto API. This reduces the risk of secondary oopses or other
problems while pstore is recording panic metadata.
config PSTORE_CONSOLE
bool "Log kernel console messages"
depends on PSTORE
help
When the option is enabled, pstore will log all kernel
messages, even if no oops or panic happened.
config PSTORE_PMSG
bool "Log user space messages"
depends on PSTORE
select RT_MUTEXES
help
When the option is enabled, pstore will export a character
interface /dev/pmsg0 to log user space messages. On reboot
data can be retrieved from /sys/fs/pstore/pmsg-ramoops-[ID].
If unsure, say N.
config PSTORE_FTRACE
bool "Persistent function tracer"
depends on PSTORE
depends on FUNCTION_TRACER
depends on DEBUG_FS
help
With this option kernel traces function calls into a persistent
ram buffer that can be decoded and dumped after reboot through
pstore filesystem. It can be used to determine what function
was last called before a reset or panic.
If unsure, say N.
config PSTORE_RAM
tristate "Log panic/oops to a RAM buffer"
depends on PSTORE
depends on HAS_IOMEM
select REED_SOLOMON
select REED_SOLOMON_ENC8
select REED_SOLOMON_DEC8
help
This enables panic and oops messages to be logged to a circular
buffer in RAM where it can be read back at some later point.
Note that for historical reasons, the module will be named
"ramoops.ko".
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
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.
+20
View File
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the linux pstorefs routines.
#
obj-$(CONFIG_PSTORE) += pstore.o
pstore-objs += inode.o platform.o
pstore-$(CONFIG_PSTORE_FTRACE) += ftrace.o
pstore-$(CONFIG_PSTORE_PMSG) += pmsg.o
ramoops-objs += ram.o ram_core.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
+361
View File
@@ -0,0 +1,361 @@
// 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 <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/fs.h>
#include <linux/file.h>
#include <linux/init_syscalls.h>
#include <linux/mount.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
* See Documentation/admin-guide/pstore-blk.rst for details.
*/
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 file *psblk_file;
static struct pstore_device_info *pstore_device_info;
#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_; \
})
#define verify_size(name, alignsize, enabled) { \
long _##name_; \
if (enabled) \
_##name_ = check_size(name, alignsize); \
else \
_##name_ = 0; \
/* Synchronize module parameters with results. */ \
name = _##name_ / 1024; \
dev->zone.name = _##name_; \
}
static int __register_pstore_device(struct pstore_device_info *dev)
{
int ret;
lockdep_assert_held(&pstore_blk_lock);
if (!dev) {
pr_err("NULL device info\n");
return -EINVAL;
}
if (!dev->zone.total_size) {
pr_err("zero sized device\n");
return -EINVAL;
}
if (!dev->zone.read) {
pr_err("no read handler for device\n");
return -EINVAL;
}
if (!dev->zone.write) {
pr_err("no write handler for device\n");
return -EINVAL;
}
/* someone already registered before */
if (pstore_device_info)
return -EBUSY;
/* zero means no limit on which backends attempt to store. */
if (!dev->flags)
dev->flags = UINT_MAX;
/* Copy in module parameters. */
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);
dev->zone.max_reason = max_reason;
/* Initialize required zone ownership details. */
dev->zone.name = KBUILD_MODNAME;
dev->zone.owner = THIS_MODULE;
ret = register_pstore_zone(&dev->zone);
if (ret == 0)
pstore_device_info = dev;
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_device_info && pstore_device_info == dev) {
unregister_pstore_zone(&dev->zone);
pstore_device_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);
static ssize_t psblk_generic_blk_read(char *buf, size_t bytes, loff_t pos)
{
return kernel_read(psblk_file, buf, bytes, &pos);
}
static ssize_t psblk_generic_blk_write(const char *buf, size_t bytes,
loff_t pos)
{
/* Console/Ftrace backend may handle buffer until flush dirty zones */
if (in_interrupt() || irqs_disabled())
return -EBUSY;
return kernel_write(psblk_file, buf, bytes, &pos);
}
/*
* This takes its configuration only from the module parameters now.
*/
static int __register_pstore_blk(struct pstore_device_info *dev,
const char *devpath)
{
int ret = -ENODEV;
lockdep_assert_held(&pstore_blk_lock);
psblk_file = filp_open(devpath, O_RDWR | O_DSYNC | O_NOATIME | O_EXCL, 0);
if (IS_ERR(psblk_file)) {
ret = PTR_ERR(psblk_file);
pr_err("failed to open '%s': %d!\n", devpath, ret);
goto err;
}
if (!S_ISBLK(file_inode(psblk_file)->i_mode)) {
pr_err("'%s' is not block device!\n", devpath);
goto err_fput;
}
dev->zone.total_size =
bdev_nr_bytes(I_BDEV(psblk_file->f_mapping->host));
ret = __register_pstore_device(dev);
if (ret)
goto err_fput;
return 0;
err_fput:
fput(psblk_file);
err:
psblk_file = NULL;
return ret;
}
/* get information of pstore/blk */
int pstore_blk_get_config(struct pstore_blk_config *info)
{
strscpy(info->device, blkdev);
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);
#ifndef MODULE
static const char devname[] = "/dev/pstore-blk";
static __init const char *early_boot_devpath(const char *initial_devname)
{
/*
* During early boot the real root file system hasn't been
* mounted yet, and no device nodes are present yet. Use the
* same scheme to find the device that we use for mounting
* the root file system.
*/
dev_t dev;
if (early_lookup_bdev(initial_devname, &dev)) {
pr_err("failed to resolve '%s'!\n", initial_devname);
return initial_devname;
}
init_unlink(devname);
init_mknod(devname, S_IFBLK | 0600, new_encode_dev(dev));
return devname;
}
#else
static inline const char *early_boot_devpath(const char *initial_devname)
{
return initial_devname;
}
#endif
static int __init __best_effort_init(void)
{
struct pstore_device_info *best_effort_dev;
int ret;
/* No best-effort mode requested. */
if (!best_effort)
return 0;
/* Reject an empty blkdev. */
if (!blkdev[0]) {
pr_err("blkdev empty with best_effort=Y\n");
return -EINVAL;
}
best_effort_dev = kzalloc_obj(*best_effort_dev);
if (!best_effort_dev)
return -ENOMEM;
best_effort_dev->zone.read = psblk_generic_blk_read;
best_effort_dev->zone.write = psblk_generic_blk_write;
ret = __register_pstore_blk(best_effort_dev,
early_boot_devpath(blkdev));
if (ret)
kfree(best_effort_dev);
else
pr_info("attached %s (%lu) (no dedicated panic_write!)\n",
blkdev, best_effort_dev->zone.total_size);
return ret;
}
static void __exit __best_effort_exit(void)
{
/*
* Currently, the only user of psblk_file is best_effort, so
* we can assume that pstore_device_info is associated with it.
* Once there are "real" blk devices, there will need to be a
* dedicated pstore_blk_info, etc.
*/
if (psblk_file) {
struct pstore_device_info *dev = pstore_device_info;
__unregister_pstore_device(dev);
kfree(dev);
fput(psblk_file);
psblk_file = NULL;
}
}
static int __init pstore_blk_init(void)
{
int ret;
mutex_lock(&pstore_blk_lock);
ret = __best_effort_init();
mutex_unlock(&pstore_blk_lock);
return ret;
}
late_initcall(pstore_blk_init);
static void __exit pstore_blk_exit(void)
{
mutex_lock(&pstore_blk_lock);
__best_effort_exit();
/* If we've been asked to unload, unregister any remaining device. */
__unregister_pstore_device(pstore_device_info);
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");
+230
View File
@@ -0,0 +1,230 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2012 Google, Inc.
*/
#include <linux/kernel.h>
#include <linux/compiler.h>
#include <linux/irqflags.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/ftrace.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/cache.h>
#include <linux/slab.h>
#include <asm/barrier.h>
#include <asm/setup.h>
#include "internal.h"
/* This doesn't need to be atomic: speed is chosen over correctness here. */
static u64 pstore_ftrace_stamp;
static inline unsigned long adjust_ip(unsigned long ip)
{
#if defined(CONFIG_RANDOMIZE_BASE) && !defined(PSTORE_CPU_IN_IP) && IS_BUILTIN(CONFIG_PSTORE)
if (core_kernel_text(ip))
return ip - kaslr_offset();
__clear_bit(BITS_PER_LONG - 1, &ip);
#endif
return ip;
}
inline unsigned long decode_ip(unsigned long ip)
{
#if defined(CONFIG_RANDOMIZE_BASE) && !defined(PSTORE_CPU_IN_IP) && IS_BUILTIN(CONFIG_PSTORE)
if (test_bit(BITS_PER_LONG - 1, &ip))
return ip + kaslr_offset();
__set_bit(BITS_PER_LONG - 1, &ip);
#endif
return ip;
}
static void notrace pstore_ftrace_call(unsigned long ip,
unsigned long parent_ip,
struct ftrace_ops *op,
struct ftrace_regs *fregs)
{
int bit;
unsigned long flags;
struct pstore_ftrace_record rec = {};
struct pstore_record record = {
.type = PSTORE_TYPE_FTRACE,
.buf = (char *)&rec,
.size = sizeof(rec),
.psi = psinfo,
};
if (unlikely(oops_in_progress))
return;
bit = ftrace_test_recursion_trylock(ip, parent_ip);
if (bit < 0)
return;
local_irq_save(flags);
rec.ip = adjust_ip(ip);
rec.parent_ip = adjust_ip(parent_ip);
pstore_ftrace_write_timestamp(&rec, pstore_ftrace_stamp++);
pstore_ftrace_encode_cpu(&rec, raw_smp_processor_id());
psinfo->write(&record);
local_irq_restore(flags);
ftrace_test_recursion_unlock(bit);
}
static struct ftrace_ops pstore_ftrace_ops __read_mostly = {
.func = pstore_ftrace_call,
};
static DEFINE_MUTEX(pstore_ftrace_lock);
static bool record_ftrace;
module_param(record_ftrace, bool, 0400);
MODULE_PARM_DESC(record_ftrace,
"enable ftrace recording immediately (default: off)");
static int pstore_set_ftrace_enabled(bool on)
{
ssize_t ret;
if (on == record_ftrace)
return 0;
if (on) {
ftrace_ops_set_global_filter(&pstore_ftrace_ops);
ret = register_ftrace_function(&pstore_ftrace_ops);
} else {
ret = unregister_ftrace_function(&pstore_ftrace_ops);
}
if (ret) {
pr_err("%s: unable to %sregister ftrace ops: %zd\n",
__func__, on ? "" : "un", ret);
} else {
record_ftrace = on;
}
return ret;
}
static ssize_t pstore_ftrace_knob_write(struct file *f, const char __user *buf,
size_t count, loff_t *ppos)
{
u8 on;
ssize_t ret;
ret = kstrtou8_from_user(buf, count, 2, &on);
if (ret)
return ret;
mutex_lock(&pstore_ftrace_lock);
ret = pstore_set_ftrace_enabled(on);
mutex_unlock(&pstore_ftrace_lock);
if (ret == 0)
ret = count;
return ret;
}
static ssize_t pstore_ftrace_knob_read(struct file *f, char __user *buf,
size_t count, loff_t *ppos)
{
char val[] = { '0' + record_ftrace, '\n' };
return simple_read_from_buffer(buf, count, ppos, val, sizeof(val));
}
static const struct file_operations pstore_knob_fops = {
.open = simple_open,
.read = pstore_ftrace_knob_read,
.write = pstore_ftrace_knob_write,
};
static struct dentry *pstore_ftrace_dir;
void pstore_register_ftrace(void)
{
if (!psinfo->write)
return;
pstore_ftrace_dir = debugfs_create_dir("pstore", NULL);
pstore_set_ftrace_enabled(record_ftrace);
debugfs_create_file("record_ftrace", 0600, pstore_ftrace_dir, NULL,
&pstore_knob_fops);
}
void pstore_unregister_ftrace(void)
{
mutex_lock(&pstore_ftrace_lock);
if (record_ftrace) {
unregister_ftrace_function(&pstore_ftrace_ops);
record_ftrace = false;
}
mutex_unlock(&pstore_ftrace_lock);
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);
+528
View File
@@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Persistent Storage - ramfs parts.
*
* Copyright (C) 2010 Intel Corporation <tony.luck@intel.com>
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/pagemap.h>
#include <linux/highmem.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/seq_file.h>
#include <linux/ramfs.h>
#include <linux/fs_parser.h>
#include <linux/fs_context.h>
#include <linux/sched.h>
#include <linux/magic.h>
#include <linux/pstore.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/cleanup.h>
#include "internal.h"
#define PSTORE_NAMELEN 64
static DEFINE_MUTEX(records_list_lock);
static LIST_HEAD(records_list);
static DEFINE_MUTEX(pstore_sb_lock);
static struct super_block *pstore_sb;
DEFINE_FREE(pstore_iput, struct inode *, if (_T) iput(_T))
struct pstore_private {
struct list_head list;
struct dentry *dentry;
struct pstore_record *record;
size_t total_size;
};
struct pstore_ftrace_seq_data {
const void *ptr;
size_t off;
size_t size;
};
#define REC_SIZE sizeof(struct pstore_ftrace_record)
static void free_pstore_private(struct pstore_private *private)
{
if (!private)
return;
if (private->record) {
kvfree(private->record->buf);
kfree(private->record->priv);
kfree(private->record);
}
kfree(private);
}
DEFINE_FREE(pstore_private, struct pstore_private *, free_pstore_private(_T));
static void *pstore_ftrace_seq_start(struct seq_file *s, loff_t *pos)
{
struct pstore_private *ps = s->private;
struct pstore_ftrace_seq_data *data __free(kfree) = NULL;
data = kzalloc_obj(*data);
if (!data)
return NULL;
data->off = ps->record->size % REC_SIZE;
data->off += *pos * REC_SIZE;
if (data->off + REC_SIZE > ps->record->size)
return NULL;
return_ptr(data);
}
static void pstore_ftrace_seq_stop(struct seq_file *s, void *v)
{
kfree(v);
}
static void *pstore_ftrace_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
struct pstore_private *ps = s->private;
struct pstore_ftrace_seq_data *data = v;
(*pos)++;
data->off += REC_SIZE;
if (data->off + REC_SIZE > ps->record->size)
return NULL;
return data;
}
static int pstore_ftrace_seq_show(struct seq_file *s, void *v)
{
struct pstore_private *ps = s->private;
struct pstore_ftrace_seq_data *data = v;
struct pstore_ftrace_record *rec;
unsigned long ip, parent_ip;
if (!data)
return 0;
rec = (struct pstore_ftrace_record *)(ps->record->buf + data->off);
ip = decode_ip(rec->ip);
parent_ip = decode_ip(rec->parent_ip);
seq_printf(s, "CPU:%d ts:%llu %08lx %08lx %ps <- %pS\n",
pstore_ftrace_decode_cpu(rec),
pstore_ftrace_read_timestamp(rec),
ip, parent_ip, (void *)ip, (void *)parent_ip);
return 0;
}
static const struct seq_operations pstore_ftrace_seq_ops = {
.start = pstore_ftrace_seq_start,
.next = pstore_ftrace_seq_next,
.stop = pstore_ftrace_seq_stop,
.show = pstore_ftrace_seq_show,
};
static ssize_t pstore_file_read(struct file *file, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct seq_file *sf = file->private_data;
struct pstore_private *ps = sf->private;
if (ps->record->type == PSTORE_TYPE_FTRACE)
return seq_read(file, userbuf, count, ppos);
return simple_read_from_buffer(userbuf, count, ppos,
ps->record->buf, ps->total_size);
}
static int pstore_file_open(struct inode *inode, struct file *file)
{
struct pstore_private *ps = inode->i_private;
struct seq_file *sf;
int err;
const struct seq_operations *sops = NULL;
if (ps->record->type == PSTORE_TYPE_FTRACE)
sops = &pstore_ftrace_seq_ops;
err = seq_open(file, sops);
if (err < 0)
return err;
sf = file->private_data;
sf->private = ps;
return 0;
}
static loff_t pstore_file_llseek(struct file *file, loff_t off, int whence)
{
struct seq_file *sf = file->private_data;
if (sf->op)
return seq_lseek(file, off, whence);
return default_llseek(file, off, whence);
}
static const struct file_operations pstore_file_operations = {
.open = pstore_file_open,
.read = pstore_file_read,
.llseek = pstore_file_llseek,
.release = seq_release,
};
/*
* When a file is unlinked from our file system we call the
* platform driver to erase the record from persistent store.
*/
static int pstore_unlink(struct inode *dir, struct dentry *dentry)
{
struct pstore_private *p = d_inode(dentry)->i_private;
struct pstore_record *record = p->record;
if (!record->psi->erase)
return -EPERM;
/* Make sure we can't race while removing this file. */
scoped_guard(mutex, &records_list_lock) {
if (!list_empty(&p->list))
list_del_init(&p->list);
else
return -ENOENT;
p->dentry = NULL;
}
scoped_guard(mutex, &record->psi->read_mutex)
record->psi->erase(record);
return simple_unlink(dir, dentry);
}
static void pstore_evict_inode(struct inode *inode)
{
struct pstore_private *p = inode->i_private;
clear_inode(inode);
free_pstore_private(p);
}
static const struct inode_operations pstore_dir_inode_operations = {
.lookup = simple_lookup,
.unlink = pstore_unlink,
};
static struct inode *pstore_get_inode(struct super_block *sb)
{
struct inode *inode = new_inode(sb);
if (inode) {
inode->i_ino = get_next_ino();
simple_inode_init_ts(inode);
}
return inode;
}
enum {
Opt_kmsg_bytes
};
static const struct fs_parameter_spec pstore_param_spec[] = {
fsparam_u32 ("kmsg_bytes", Opt_kmsg_bytes),
{}
};
struct pstore_context {
unsigned int kmsg_bytes;
};
static int pstore_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct pstore_context *ctx = fc->fs_private;
struct fs_parse_result result;
int opt;
opt = fs_parse(fc, pstore_param_spec, param, &result);
/* pstore has historically ignored invalid kmsg_bytes param */
if (opt < 0)
return 0;
switch (opt) {
case Opt_kmsg_bytes:
ctx->kmsg_bytes = result.uint_32;
break;
default:
return -EINVAL;
}
return 0;
}
/*
* Display the mount options in /proc/mounts.
*/
static int pstore_show_options(struct seq_file *m, struct dentry *root)
{
if (kmsg_bytes != CONFIG_PSTORE_DEFAULT_KMSG_BYTES)
seq_printf(m, ",kmsg_bytes=%u", kmsg_bytes);
return 0;
}
static int pstore_reconfigure(struct fs_context *fc)
{
struct pstore_context *ctx = fc->fs_private;
sync_filesystem(fc->root->d_sb);
pstore_set_kmsg_bytes(ctx->kmsg_bytes);
return 0;
}
static const struct super_operations pstore_ops = {
.statfs = simple_statfs,
.drop_inode = inode_just_drop,
.evict_inode = pstore_evict_inode,
.show_options = pstore_show_options,
};
static struct dentry *psinfo_lock_root(void)
{
struct dentry *root;
guard(mutex)(&pstore_sb_lock);
/*
* Having no backend is fine -- no records appear.
* Not being mounted is fine -- nothing to do.
*/
if (!psinfo || !pstore_sb)
return NULL;
root = pstore_sb->s_root;
inode_lock_nested(d_inode(root), I_MUTEX_PARENT);
return root;
}
int pstore_put_backend_records(struct pstore_info *psi)
{
struct pstore_private *pos, *tmp;
struct dentry *root;
root = psinfo_lock_root();
if (!root)
return 0;
scoped_guard(mutex, &records_list_lock) {
list_for_each_entry_safe(pos, tmp, &records_list, list) {
if (pos->record->psi == psi) {
list_del_init(&pos->list);
locked_recursive_removal(pos->dentry, NULL);
pos->dentry = NULL;
}
}
}
inode_unlock(d_inode(root));
return 0;
}
/*
* Make a regular file in the root directory of our file system.
* Load it up with "size" bytes of data from "buf".
* Set the mtime & ctime to the date that this record was originally stored.
*/
int pstore_mkfile(struct dentry *root, struct pstore_record *record)
{
struct dentry *dentry;
struct inode *inode __free(pstore_iput) = NULL;
char name[PSTORE_NAMELEN];
struct pstore_private *private __free(pstore_private) = NULL, *pos;
size_t size = record->size + record->ecc_notice_size;
if (WARN_ON(!inode_is_locked(d_inode(root))))
return -EINVAL;
guard(mutex)(&records_list_lock);
/* Skip records that are already present in the filesystem. */
list_for_each_entry(pos, &records_list, list) {
if (pos->record->type == record->type &&
pos->record->id == record->id &&
pos->record->psi == record->psi)
return -EEXIST;
}
inode = pstore_get_inode(root->d_sb);
if (!inode)
return -ENOMEM;
inode->i_mode = S_IFREG | 0444;
inode->i_fop = &pstore_file_operations;
scnprintf(name, sizeof(name), "%s-%s-%llu%s",
pstore_type_to_name(record->type),
record->psi->name, record->id,
record->compressed ? ".enc.z" : "");
private = kzalloc_obj(*private);
if (!private)
return -ENOMEM;
dentry = d_alloc_name(root, name);
if (!dentry)
return -ENOMEM;
private->dentry = dentry; // borrowed
private->record = record;
inode->i_size = private->total_size = size;
inode->i_private = private;
if (record->time.tv_sec)
inode_set_mtime_to_ts(inode,
inode_set_ctime_to_ts(inode, record->time));
d_make_persistent(dentry, no_free_ptr(inode));
dput(dentry);
list_add(&(no_free_ptr(private))->list, &records_list);
return 0;
}
/*
* Read all the records from the persistent store. Create
* files in our filesystem. Don't warn about -EEXIST errors
* when we are re-scanning the backing store looking to add new
* error records.
*/
void pstore_get_records(int quiet)
{
struct dentry *root;
root = psinfo_lock_root();
if (!root)
return;
pstore_get_backend_records(psinfo, root, quiet);
inode_unlock(d_inode(root));
}
static int pstore_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct pstore_context *ctx = fc->fs_private;
struct inode *inode;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = PSTOREFS_MAGIC;
sb->s_op = &pstore_ops;
sb->s_time_gran = 1;
pstore_set_kmsg_bytes(ctx->kmsg_bytes);
inode = pstore_get_inode(sb);
if (inode) {
inode->i_mode = S_IFDIR | 0750;
inode->i_op = &pstore_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inc_nlink(inode);
}
sb->s_root = d_make_root(inode);
if (!sb->s_root)
return -ENOMEM;
scoped_guard(mutex, &pstore_sb_lock)
pstore_sb = sb;
pstore_get_records(0);
return 0;
}
static int pstore_get_tree(struct fs_context *fc)
{
if (fc->root)
return pstore_reconfigure(fc);
return get_tree_single(fc, pstore_fill_super);
}
static void pstore_free_fc(struct fs_context *fc)
{
kfree(fc->fs_private);
}
static const struct fs_context_operations pstore_context_ops = {
.parse_param = pstore_parse_param,
.get_tree = pstore_get_tree,
.reconfigure = pstore_reconfigure,
.free = pstore_free_fc,
};
static void pstore_kill_sb(struct super_block *sb)
{
guard(mutex)(&pstore_sb_lock);
WARN_ON(pstore_sb && pstore_sb != sb);
kill_anon_super(sb);
pstore_sb = NULL;
guard(mutex)(&records_list_lock);
INIT_LIST_HEAD(&records_list);
}
static int pstore_init_fs_context(struct fs_context *fc)
{
struct pstore_context *ctx;
ctx = kzalloc_obj(struct pstore_context);
if (!ctx)
return -ENOMEM;
/*
* Global kmsg_bytes is initialized to default, and updated
* every time we (re)mount the single-sb filesystem with the
* option specified.
*/
ctx->kmsg_bytes = kmsg_bytes;
fc->fs_private = ctx;
fc->ops = &pstore_context_ops;
return 0;
}
static struct file_system_type pstore_fs_type = {
.owner = THIS_MODULE,
.name = "pstore",
.kill_sb = pstore_kill_sb,
.init_fs_context = pstore_init_fs_context,
.parameters = pstore_param_spec,
};
int __init pstore_init_fs(void)
{
int err;
/* Create a convenient mount point for people to access pstore */
err = sysfs_create_mount_point(fs_kobj, "pstore");
if (err)
goto out;
err = register_filesystem(&pstore_fs_type);
if (err < 0)
sysfs_remove_mount_point(fs_kobj, "pstore");
out:
return err;
}
void __exit pstore_exit_fs(void)
{
unregister_filesystem(&pstore_fs_type);
sysfs_remove_mount_point(fs_kobj, "pstore");
}
+54
View File
@@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __PSTORE_INTERNAL_H__
#define __PSTORE_INTERNAL_H__
#include <linux/types.h>
#include <linux/time.h>
#include <linux/pstore.h>
extern unsigned int kmsg_bytes;
#ifdef CONFIG_PSTORE_FTRACE
extern unsigned long decode_ip(unsigned long ip);
extern void pstore_register_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
static inline void pstore_register_ftrace(void) {}
static inline void pstore_unregister_ftrace(void) {}
static inline unsigned long decode_ip(unsigned long ip) { return ip; }
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
#ifdef CONFIG_PSTORE_PMSG
extern void pstore_register_pmsg(void);
extern void pstore_unregister_pmsg(void);
#else
static inline void pstore_register_pmsg(void) {}
static inline void pstore_unregister_pmsg(void) {}
#endif
extern struct pstore_info *psinfo;
extern void pstore_set_kmsg_bytes(unsigned int bytes);
extern void pstore_get_records(int);
extern void pstore_get_backend_records(struct pstore_info *psi,
struct dentry *root, int quiet);
extern int pstore_put_backend_records(struct pstore_info *psi);
extern int pstore_mkfile(struct dentry *root,
struct pstore_record *record);
extern void pstore_record_init(struct pstore_record *record,
struct pstore_info *psi);
/* Called during pstore init/exit. */
int __init pstore_init_fs(void);
void __exit pstore_exit_fs(void);
#endif
+766
View File
@@ -0,0 +1,766 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Persistent Storage - platform driver interface parts.
*
* Copyright (C) 2007-2008 Google, Inc.
* Copyright (C) 2010 Intel Corporation <tony.luck@intel.com>
*/
#define pr_fmt(fmt) "pstore: " fmt
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kmsg_dump.h>
#include <linux/console.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/pstore.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/jiffies.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <linux/zlib.h>
#include "internal.h"
/*
* We defer making "oops" entries appear in pstore - see
* whether the system is actually still running well enough
* to let someone see the entry
*/
static int pstore_update_ms = -1;
module_param_named(update_ms, pstore_update_ms, int, 0600);
MODULE_PARM_DESC(update_ms, "milliseconds before pstore updates its content "
"(default is -1, which means runtime updates are disabled; "
"enabling this option may not be safe; it may lead to further "
"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",
};
static int pstore_new_entry;
static void pstore_timefunc(struct timer_list *);
static DEFINE_TIMER(pstore_timer, pstore_timefunc);
static void pstore_dowork(struct work_struct *);
static DECLARE_WORK(pstore_work, pstore_dowork);
/*
* psinfo_lock protects "psinfo" during calls to
* pstore_register(), pstore_unregister(), and
* the filesystem mount/unmount routines.
*/
static DEFINE_MUTEX(psinfo_lock);
struct pstore_info *psinfo;
static char *backend;
module_param(backend, charp, 0444);
MODULE_PARM_DESC(backend, "specific backend to use");
/*
* pstore no longer implements compression via the crypto API, and only
* supports zlib deflate compression implemented using the zlib library
* interface. This removes additional complexity which is hard to justify for a
* diagnostic facility that has to operate in conditions where the system may
* have become unstable. Zlib deflate is comparatively small in terms of code
* size, and compresses ASCII text comparatively well. In terms of compression
* speed, deflate is not the best performer but for recording the log output on
* a kernel panic, this is not considered critical.
*
* The only remaining arguments supported by the compress= module parameter are
* 'deflate' and 'none'. To retain compatibility with existing installations,
* all other values are logged and replaced with 'deflate'.
*/
static char *compress = "deflate";
module_param(compress, charp, 0444);
MODULE_PARM_DESC(compress, "compression to use");
/* How much of the kernel log to snapshot */
unsigned int kmsg_bytes = CONFIG_PSTORE_DEFAULT_KMSG_BYTES;
module_param(kmsg_bytes, uint, 0444);
MODULE_PARM_DESC(kmsg_bytes, "amount of kernel log to snapshot (in bytes)");
static void *compress_workspace;
/*
* Compression is only used for dmesg output, which consists of low-entropy
* ASCII text, and so we can assume worst-case 60%.
*/
#define DMESG_COMP_PERCENT 60
static char *big_oops_buf;
static size_t max_compressed_size;
void pstore_set_kmsg_bytes(unsigned int bytes)
{
WRITE_ONCE(kmsg_bytes, bytes);
}
/* Tag each group of saved records with a sequence number */
static int oopscount;
const char *pstore_type_to_name(enum pstore_type_id type)
{
BUILD_BUG_ON(ARRAY_SIZE(pstore_type_names) != PSTORE_TYPE_MAX);
if (WARN_ON_ONCE(type >= PSTORE_TYPE_MAX))
return "unknown";
return pstore_type_names[type];
}
EXPORT_SYMBOL_GPL(pstore_type_to_name);
enum pstore_type_id pstore_name_to_type(const char *name)
{
int i;
for (i = 0; i < PSTORE_TYPE_MAX; i++) {
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));
}
static bool pstore_cannot_block_path(enum kmsg_dump_reason reason)
{
/*
* In case of NMI path, pstore shouldn't be blocked
* regardless of reason.
*/
if (in_nmi())
return true;
switch (reason) {
/* In panic case, other cpus are stopped by smp_send_stop(). */
case KMSG_DUMP_PANIC:
/*
* Emergency restart shouldn't be blocked by spinning on
* pstore_info::buf_lock.
*/
case KMSG_DUMP_EMERG:
return true;
default:
return false;
}
}
static int pstore_compress(const void *in, void *out,
unsigned int inlen, unsigned int outlen)
{
struct z_stream_s zstream = {
.next_in = in,
.avail_in = inlen,
.next_out = out,
.avail_out = outlen,
.workspace = compress_workspace,
};
int ret;
if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS))
return -EINVAL;
ret = zlib_deflateInit2(&zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (ret != Z_OK)
return -EINVAL;
ret = zlib_deflate(&zstream, Z_FINISH);
if (ret != Z_STREAM_END)
return -EINVAL;
ret = zlib_deflateEnd(&zstream);
if (ret != Z_OK)
pr_warn_once("zlib_deflateEnd() failed: %d\n", ret);
return zstream.total_out;
}
static void allocate_buf_for_compression(void)
{
size_t compressed_size;
char *buf;
/* Skip if not built-in or compression disabled. */
if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS) || !compress ||
!strcmp(compress, "none")) {
compress = NULL;
return;
}
if (strcmp(compress, "deflate")) {
pr_err("Unsupported compression '%s', falling back to deflate\n",
compress);
compress = "deflate";
}
/*
* The compression buffer only needs to be as large as the maximum
* uncompressed record size, since any record that would be expanded by
* compression is just stored uncompressed.
*/
compressed_size = (psinfo->bufsize * 100) / DMESG_COMP_PERCENT;
buf = kvzalloc(compressed_size, GFP_KERNEL);
if (!buf) {
pr_err("Failed %zu byte compression buffer allocation for: %s\n",
psinfo->bufsize, compress);
return;
}
compress_workspace =
vmalloc(zlib_deflate_workspacesize(MAX_WBITS, DEF_MEM_LEVEL));
if (!compress_workspace) {
pr_err("Failed to allocate zlib deflate workspace\n");
kvfree(buf);
return;
}
/* A non-NULL big_oops_buf indicates compression is available. */
big_oops_buf = buf;
max_compressed_size = compressed_size;
pr_info("Using crash dump compression: %s\n", compress);
}
static void free_buf_for_compression(void)
{
if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && compress_workspace) {
vfree(compress_workspace);
compress_workspace = NULL;
}
kvfree(big_oops_buf);
big_oops_buf = NULL;
max_compressed_size = 0;
}
void pstore_record_init(struct pstore_record *record,
struct pstore_info *psinfo)
{
memset(record, 0, sizeof(*record));
record->psi = psinfo;
/* Report zeroed timestamp if called before timekeeping has resumed. */
record->time = ns_to_timespec64(ktime_get_real_fast_ns());
}
/*
* callback from kmsg_dump. Save as much as we can (up to kmsg_bytes) from the
* end of the buffer.
*/
static void pstore_dump(struct kmsg_dumper *dumper,
struct kmsg_dump_detail *detail)
{
struct kmsg_dump_iter iter;
unsigned int remaining = READ_ONCE(kmsg_bytes);
unsigned long total = 0;
const char *why;
unsigned int part = 1;
unsigned long flags = 0;
int saved_ret = 0;
int ret;
why = kmsg_dump_reason_str(detail->reason);
if (pstore_cannot_block_path(detail->reason)) {
if (!raw_spin_trylock_irqsave(&psinfo->buf_lock, flags)) {
pr_err("dump skipped in %s path because of concurrent dump\n",
in_nmi() ? "NMI" : why);
return;
}
} else {
raw_spin_lock_irqsave(&psinfo->buf_lock, flags);
}
kmsg_dump_rewind(&iter);
oopscount++;
while (total < remaining) {
char *dst;
size_t dst_size;
int header_size;
int zipped_len = -1;
size_t dump_size;
struct pstore_record record;
pstore_record_init(&record, psinfo);
record.type = PSTORE_TYPE_DMESG;
record.count = oopscount;
record.reason = detail->reason;
record.part = part;
record.buf = psinfo->buf;
dst = big_oops_buf ?: psinfo->buf;
dst_size = max_compressed_size ?: psinfo->bufsize;
/* Write dump header. */
header_size = snprintf(dst, dst_size, "%s#%d Part%u\n", why,
oopscount, part);
dst_size -= header_size;
/* Write dump contents. */
if (!kmsg_dump_get_buffer(&iter, true, dst + header_size,
dst_size, &dump_size))
break;
if (big_oops_buf) {
zipped_len = pstore_compress(dst, psinfo->buf,
header_size + dump_size,
psinfo->bufsize);
if (zipped_len > 0) {
record.compressed = true;
record.size = zipped_len;
} else {
/*
* Compression failed, so the buffer is most
* likely filled with binary data that does not
* compress as well as ASCII text. Copy as much
* of the uncompressed data as possible into
* the pstore record, and discard the rest.
*/
record.size = psinfo->bufsize;
memcpy(psinfo->buf, dst, psinfo->bufsize);
}
} else {
record.size = header_size + dump_size;
}
ret = psinfo->write(&record);
if (ret == 0 && detail->reason == KMSG_DUMP_OOPS) {
pstore_new_entry = 1;
pstore_timer_kick();
} else {
/* Preserve only the first non-zero returned value. */
if (!saved_ret)
saved_ret = ret;
}
total += record.size;
part++;
}
raw_spin_unlock_irqrestore(&psinfo->buf_lock, flags);
if (saved_ret) {
pr_err_once("backend (%s) writing error (%d)\n", psinfo->name,
saved_ret);
}
}
static struct kmsg_dumper pstore_dumper = {
.dump = pstore_dump,
};
/*
* Register with kmsg_dump to save last part of console log on panic.
*/
static void pstore_register_kmsg(void)
{
kmsg_dump_register(&pstore_dumper);
}
static void pstore_unregister_kmsg(void)
{
kmsg_dump_unregister(&pstore_dumper);
}
#ifdef CONFIG_PSTORE_CONSOLE
static void pstore_console_write(struct console *con, const char *s, unsigned c)
{
struct pstore_record record;
if (!c)
return;
pstore_record_init(&record, psinfo);
record.type = PSTORE_TYPE_CONSOLE;
record.buf = (char *)s;
record.size = c;
psinfo->write(&record);
}
static struct console pstore_console = {
.write = pstore_console_write,
.index = -1,
};
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);
}
static void pstore_unregister_console(void)
{
unregister_console(&pstore_console);
}
#else
static void pstore_register_console(void) {}
static void pstore_unregister_console(void) {}
#endif
static int pstore_write_user_compat(struct pstore_record *record,
const char __user *buf)
{
int ret = 0;
if (record->buf)
return -EINVAL;
record->buf = vmemdup_user(buf, record->size);
if (IS_ERR(record->buf)) {
ret = PTR_ERR(record->buf);
goto out;
}
ret = record->psi->write(record);
kvfree(record->buf);
out:
record->buf = NULL;
return unlikely(ret < 0) ? ret : record->size;
}
/*
* platform specific persistent storage driver registers with
* us here. If pstore is already mounted, call the platform
* read function right away to populate the file system. If not
* then the pstore mount code will call us later to fill out
* the file system.
*/
int pstore_register(struct pstore_info *psi)
{
char *new_backend;
if (backend && strcmp(backend, psi->name)) {
pr_warn("backend '%s' already in use: ignoring '%s'\n",
backend, psi->name);
return -EBUSY;
}
/* Sanity check flags. */
if (!psi->flags) {
pr_warn("backend '%s' must support at least one frontend\n",
psi->name);
return -EINVAL;
}
/* Check for required functions. */
if (!psi->read || !psi->write) {
pr_warn("backend '%s' must implement read() and write()\n",
psi->name);
return -EINVAL;
}
new_backend = kstrdup(psi->name, GFP_KERNEL);
if (!new_backend)
return -ENOMEM;
mutex_lock(&psinfo_lock);
if (psinfo) {
pr_warn("backend '%s' already loaded: ignoring '%s'\n",
psinfo->name, psi->name);
mutex_unlock(&psinfo_lock);
kfree(new_backend);
return -EBUSY;
}
if (!psi->write_user)
psi->write_user = pstore_write_user_compat;
psinfo = psi;
mutex_init(&psinfo->read_mutex);
raw_spin_lock_init(&psinfo->buf_lock);
if (psi->flags & PSTORE_FLAGS_DMESG)
allocate_buf_for_compression();
pstore_get_records(0);
if (psi->flags & PSTORE_FLAGS_DMESG) {
pstore_dumper.max_reason = psinfo->max_reason;
pstore_register_kmsg();
}
if (psi->flags & PSTORE_FLAGS_CONSOLE)
pstore_register_console();
if (psi->flags & PSTORE_FLAGS_FTRACE)
pstore_register_ftrace();
if (psi->flags & PSTORE_FLAGS_PMSG)
pstore_register_pmsg();
/* Start watching for new records, if desired. */
pstore_timer_kick();
/*
* Update the module parameter backend, so it is visible
* through /sys/module/pstore/parameters/backend
*/
backend = new_backend;
pr_info("Registered %s as persistent store backend\n", psi->name);
mutex_unlock(&psinfo_lock);
return 0;
}
EXPORT_SYMBOL_GPL(pstore_register);
void pstore_unregister(struct pstore_info *psi)
{
/* It's okay to unregister nothing. */
if (!psi)
return;
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)
pstore_unregister_pmsg();
if (psi->flags & PSTORE_FLAGS_FTRACE)
pstore_unregister_ftrace();
if (psi->flags & PSTORE_FLAGS_CONSOLE)
pstore_unregister_console();
if (psi->flags & PSTORE_FLAGS_DMESG)
pstore_unregister_kmsg();
/* Stop timer and make sure all work has finished. */
timer_delete_sync(&pstore_timer);
flush_work(&pstore_work);
/* Remove all backend records from filesystem tree. */
pstore_put_backend_records(psi);
free_buf_for_compression();
psinfo = NULL;
kfree(backend);
backend = NULL;
pr_info("Unregistered %s as persistent store backend\n", psi->name);
mutex_unlock(&psinfo_lock);
}
EXPORT_SYMBOL_GPL(pstore_unregister);
static void decompress_record(struct pstore_record *record,
struct z_stream_s *zstream)
{
int ret;
int unzipped_len;
char *unzipped, *workspace;
size_t max_uncompressed_size;
if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS) || !record->compressed)
return;
/* Only PSTORE_TYPE_DMESG support compression. */
if (record->type != PSTORE_TYPE_DMESG) {
pr_warn("ignored compressed record type %d\n", record->type);
return;
}
/* Missing compression buffer means compression was not initialized. */
if (!zstream->workspace) {
pr_warn("no decompression method initialized!\n");
return;
}
ret = zlib_inflateReset(zstream);
if (ret != Z_OK) {
pr_err("zlib_inflateReset() failed, ret = %d!\n", ret);
return;
}
/* Allocate enough space to hold max decompression and ECC. */
max_uncompressed_size = 3 * psinfo->bufsize;
workspace = kvzalloc(max_uncompressed_size + record->ecc_notice_size,
GFP_KERNEL);
if (!workspace)
return;
zstream->next_in = record->buf;
zstream->avail_in = record->size;
zstream->next_out = workspace;
zstream->avail_out = max_uncompressed_size;
ret = zlib_inflate(zstream, Z_FINISH);
if (ret != Z_STREAM_END) {
pr_err_ratelimited("zlib_inflate() failed, ret = %d!\n", ret);
kvfree(workspace);
return;
}
unzipped_len = zstream->total_out;
/* Append ECC notice to decompressed buffer. */
memcpy(workspace + unzipped_len, record->buf + record->size,
record->ecc_notice_size);
/* Copy decompressed contents into an minimum-sized allocation. */
unzipped = kvmemdup(workspace, unzipped_len + record->ecc_notice_size,
GFP_KERNEL);
kvfree(workspace);
if (!unzipped)
return;
/* Swap out compressed contents with decompressed contents. */
kvfree(record->buf);
record->buf = unzipped;
record->size = unzipped_len;
record->compressed = false;
}
/*
* Read all the records from one persistent store backend. Create
* files in our filesystem. Don't warn about -EEXIST errors
* when we are re-scanning the backing store looking to add new
* error records.
*/
void pstore_get_backend_records(struct pstore_info *psi,
struct dentry *root, int quiet)
{
int failed = 0;
unsigned int stop_loop = 65536;
struct z_stream_s zstream = {};
if (!psi || !root)
return;
if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && compress) {
zstream.workspace = kvmalloc(zlib_inflate_workspacesize(),
GFP_KERNEL);
zlib_inflateInit2(&zstream, -DEF_WBITS);
}
mutex_lock(&psi->read_mutex);
if (psi->open && psi->open(psi))
goto out;
/*
* Backend callback read() allocates record.buf. decompress_record()
* may reallocate record.buf. On success, pstore_mkfile() will keep
* the record.buf, so free it only on failure.
*/
for (; stop_loop; stop_loop--) {
struct pstore_record *record;
int rc;
record = kzalloc_obj(*record);
if (!record) {
pr_err("out of memory creating record\n");
break;
}
pstore_record_init(record, psi);
record->size = psi->read(record);
/* No more records left in backend? */
if (record->size <= 0) {
kfree(record);
break;
}
decompress_record(record, &zstream);
rc = pstore_mkfile(root, record);
if (rc) {
/* pstore_mkfile() did not take record, so free it. */
kvfree(record->buf);
kfree(record->priv);
kfree(record);
if (rc != -EEXIST || !quiet)
failed++;
}
}
if (psi->close)
psi->close(psi);
out:
mutex_unlock(&psi->read_mutex);
if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && compress) {
if (zlib_inflateEnd(&zstream) != Z_OK)
pr_warn("zlib_inflateEnd() failed\n");
kvfree(zstream.workspace);
}
if (failed)
pr_warn("failed to create %d record(s) from '%s'\n",
failed, psi->name);
if (!stop_loop)
pr_err("looping? Too many records seen from '%s'\n",
psi->name);
}
static void pstore_dowork(struct work_struct *work)
{
pstore_get_records(1);
}
static void pstore_timefunc(struct timer_list *unused)
{
if (pstore_new_entry) {
pstore_new_entry = 0;
schedule_work(&pstore_work);
}
pstore_timer_kick();
}
static int __init pstore_init(void)
{
int ret;
ret = pstore_init_fs();
if (ret)
free_buf_for_compression();
return ret;
}
late_initcall(pstore_init);
static void __exit pstore_exit(void)
{
pstore_exit_fs();
}
module_exit(pstore_exit)
MODULE_AUTHOR("Tony Luck <tony.luck@intel.com>");
MODULE_DESCRIPTION("Persistent Storage - platform driver interface");
MODULE_LICENSE("GPL");
+94
View File
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2014 Google, Inc.
*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include "internal.h"
static DEFINE_MUTEX(pmsg_lock);
static ssize_t write_pmsg(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct pstore_record record;
int ret;
if (!count)
return 0;
pstore_record_init(&record, psinfo);
record.type = PSTORE_TYPE_PMSG;
record.size = count;
/* check outside lock, page in any data. write_user also checks */
if (!access_ok(buf, count))
return -EFAULT;
mutex_lock(&pmsg_lock);
ret = psinfo->write_user(&record, buf);
mutex_unlock(&pmsg_lock);
return ret ? ret : count;
}
static const struct file_operations pmsg_fops = {
.owner = THIS_MODULE,
.llseek = noop_llseek,
.write = write_pmsg,
};
static struct class *pmsg_class;
static int pmsg_major;
#define PMSG_NAME "pmsg"
#undef pr_fmt
#define pr_fmt(fmt) PMSG_NAME ": " fmt
static char *pmsg_devnode(const struct device *dev, umode_t *mode)
{
if (mode)
*mode = 0220;
return NULL;
}
void pstore_register_pmsg(void)
{
struct device *pmsg_device;
pmsg_major = register_chrdev(0, PMSG_NAME, &pmsg_fops);
if (pmsg_major < 0) {
pr_err("register_chrdev failed\n");
goto err;
}
pmsg_class = class_create(PMSG_NAME);
if (IS_ERR(pmsg_class)) {
pr_err("device class file already in use\n");
goto err_class;
}
pmsg_class->devnode = pmsg_devnode;
pmsg_device = device_create(pmsg_class, NULL, MKDEV(pmsg_major, 0),
NULL, "%s%d", PMSG_NAME, 0);
if (IS_ERR(pmsg_device)) {
pr_err("failed to create device\n");
goto err_device;
}
return;
err_device:
class_destroy(pmsg_class);
err_class:
unregister_chrdev(pmsg_major, PMSG_NAME);
err:
return;
}
void pstore_unregister_pmsg(void)
{
device_destroy(pmsg_class, MKDEV(pmsg_major, 0));
class_destroy(pmsg_class);
unregister_chrdev(pmsg_major, PMSG_NAME);
}
+999
View File
@@ -0,0 +1,999 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* RAM Oops/Panic logger
*
* Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com>
* Copyright (C) 2011 Kees Cook <keescook@chromium.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/pstore.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/compiler.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/mm.h>
#include "internal.h"
#include "ram_internal.h"
#define RAMOOPS_KERNMSG_HDR "===="
#define MIN_MEM_SIZE 4096UL
static ulong record_size = MIN_MEM_SIZE;
module_param(record_size, ulong, 0400);
MODULE_PARM_DESC(record_size,
"size of each dump done on oops/panic");
static ulong ramoops_console_size = MIN_MEM_SIZE;
module_param_named(console_size, ramoops_console_size, ulong, 0400);
MODULE_PARM_DESC(console_size, "size of kernel console log");
static ulong ramoops_ftrace_size = MIN_MEM_SIZE;
module_param_named(ftrace_size, ramoops_ftrace_size, ulong, 0400);
MODULE_PARM_DESC(ftrace_size, "size of ftrace log");
static ulong ramoops_pmsg_size = MIN_MEM_SIZE;
module_param_named(pmsg_size, ramoops_pmsg_size, ulong, 0400);
MODULE_PARM_DESC(pmsg_size, "size of user space message log");
static unsigned long long mem_address;
module_param_hw(mem_address, ullong, other, 0400);
MODULE_PARM_DESC(mem_address,
"start of reserved RAM used to store oops/panic logs");
static char *mem_name;
module_param_named(mem_name, mem_name, charp, 0400);
MODULE_PARM_DESC(mem_name, "name of kernel param that holds addr");
static ulong mem_size;
module_param(mem_size, ulong, 0400);
MODULE_PARM_DESC(mem_size,
"size of reserved RAM used to store oops/panic logs");
static unsigned int mem_type;
module_param(mem_type, uint, 0400);
MODULE_PARM_DESC(mem_type,
"memory type: 0=write-combined (default), 1=unbuffered, 2=cached");
static int ramoops_max_reason = -1;
module_param_named(max_reason, ramoops_max_reason, int, 0400);
MODULE_PARM_DESC(max_reason,
"maximum reason for kmsg dump (default 2: Oops and Panic) ");
static int ramoops_ecc;
module_param_named(ecc, ramoops_ecc, int, 0400);
MODULE_PARM_DESC(ecc,
"if non-zero, the option enables ECC support and specifies "
"ECC buffer size in bytes (1 is a special value, means 16 "
"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 */
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;
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;
struct pstore_info pstore;
};
static struct platform_device *dummy;
static int ramoops_pstore_open(struct pstore_info *psi)
{
struct ramoops_context *cxt = psi->data;
cxt->dump_read_cnt = 0;
cxt->console_read_cnt = 0;
cxt->ftrace_read_cnt = 0;
cxt->pmsg_read_cnt = 0;
return 0;
}
static struct persistent_ram_zone *
ramoops_get_next_prz(struct persistent_ram_zone *przs[], int id,
struct pstore_record *record)
{
struct persistent_ram_zone *prz;
/* Give up if we never existed or have hit the end. */
if (!przs)
return NULL;
prz = przs[id];
if (!prz)
return NULL;
/* Update old/shadowed buffer. */
if (prz->type == PSTORE_TYPE_DMESG)
persistent_ram_save_old(prz);
if (!persistent_ram_old_size(prz))
return NULL;
record->type = prz->type;
record->id = id;
return prz;
}
static int ramoops_read_kmsg_hdr(char *buffer, struct timespec64 *time,
bool *compressed)
{
char data_type;
int header_length = 0;
if (sscanf(buffer, RAMOOPS_KERNMSG_HDR "%lld.%lu-%c\n%n",
(time64_t *)&time->tv_sec, &time->tv_nsec, &data_type,
&header_length) == 3) {
time->tv_nsec *= 1000;
if (data_type == 'C')
*compressed = true;
else
*compressed = false;
} else if (sscanf(buffer, RAMOOPS_KERNMSG_HDR "%lld.%lu\n%n",
(time64_t *)&time->tv_sec, &time->tv_nsec,
&header_length) == 2) {
time->tv_nsec *= 1000;
*compressed = false;
} else {
time->tv_sec = 0;
time->tv_nsec = 0;
*compressed = false;
}
return header_length;
}
static bool prz_ok(struct persistent_ram_zone *prz)
{
return !!prz && !!(persistent_ram_old_size(prz) +
persistent_ram_ecc_string(prz, NULL, 0));
}
static ssize_t ramoops_pstore_read(struct pstore_record *record)
{
ssize_t size = 0;
struct ramoops_context *cxt = record->psi->data;
struct persistent_ram_zone *prz = NULL;
int header_length = 0;
bool free_prz = false;
/*
* Ramoops headers provide time stamps for PSTORE_TYPE_DMESG, but
* PSTORE_TYPE_CONSOLE and PSTORE_TYPE_FTRACE don't currently have
* valid time stamps, so it is initialized to zero.
*/
record->time.tv_sec = 0;
record->time.tv_nsec = 0;
record->compressed = false;
/* Find the next valid persistent_ram_zone for DMESG */
while (cxt->dump_read_cnt < cxt->max_dump_cnt && !prz) {
prz = ramoops_get_next_prz(cxt->dprzs, cxt->dump_read_cnt++,
record);
if (!prz_ok(prz))
continue;
header_length = ramoops_read_kmsg_hdr(persistent_ram_old(prz),
&record->time,
&record->compressed);
/* Clear and skip this DMESG record if it has no valid header */
if (!header_length) {
persistent_ram_free_old(prz);
persistent_ram_zap(prz);
prz = NULL;
}
}
if (!prz_ok(prz) && !cxt->console_read_cnt++)
prz = ramoops_get_next_prz(&cxt->cprz, 0 /* single */, record);
if (!prz_ok(prz) && !cxt->pmsg_read_cnt++)
prz = ramoops_get_next_prz(&cxt->mprz, 0 /* single */, record);
/* ftrace is last since it may want to dynamically allocate memory. */
if (!prz_ok(prz)) {
if (!(cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU) &&
!cxt->ftrace_read_cnt++) {
prz = ramoops_get_next_prz(cxt->fprzs, 0 /* single */,
record);
} else {
/*
* Build a new dummy record which combines all the
* per-cpu records including metadata and ecc info.
*/
struct persistent_ram_zone *tmp_prz, *prz_next;
tmp_prz = kzalloc_obj(struct persistent_ram_zone);
if (!tmp_prz)
return -ENOMEM;
prz = tmp_prz;
free_prz = true;
while (cxt->ftrace_read_cnt < cxt->max_ftrace_cnt) {
prz_next = ramoops_get_next_prz(cxt->fprzs,
cxt->ftrace_read_cnt++, record);
if (!prz_ok(prz_next))
continue;
tmp_prz->ecc_info = prz_next->ecc_info;
tmp_prz->corrected_bytes +=
prz_next->corrected_bytes;
tmp_prz->bad_blocks += prz_next->bad_blocks;
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)
goto out;
}
record->id = 0;
}
}
if (!prz_ok(prz)) {
size = 0;
goto out;
}
size = persistent_ram_old_size(prz) - header_length;
/* ECC correction notice */
record->ecc_notice_size = persistent_ram_ecc_string(prz, NULL, 0);
record->buf = kvzalloc(size + record->ecc_notice_size + 1, GFP_KERNEL);
if (record->buf == NULL) {
size = -ENOMEM;
goto out;
}
memcpy(record->buf, (char *)persistent_ram_old(prz) + header_length,
size);
persistent_ram_ecc_string(prz, record->buf + size,
record->ecc_notice_size + 1);
out:
if (free_prz) {
kvfree(prz->old_log);
kfree(prz);
}
return size;
}
static size_t ramoops_write_kmsg_hdr(struct persistent_ram_zone *prz,
struct pstore_record *record)
{
char hdr[36]; /* "===="(4), %lld(20), "."(1), %06lu(6), "-%c\n"(3) */
size_t len;
len = scnprintf(hdr, sizeof(hdr),
RAMOOPS_KERNMSG_HDR "%lld.%06lu-%c\n",
(time64_t)record->time.tv_sec,
record->time.tv_nsec / 1000,
record->compressed ? 'C' : 'D');
persistent_ram_write(prz, hdr, len);
return len;
}
static int notrace ramoops_pstore_write(struct pstore_record *record)
{
struct ramoops_context *cxt = record->psi->data;
struct persistent_ram_zone *prz;
size_t size, hlen;
if (record->type == PSTORE_TYPE_CONSOLE) {
if (!cxt->cprz)
return -ENOMEM;
persistent_ram_write(cxt->cprz, record->buf, record->size);
return 0;
} else if (record->type == PSTORE_TYPE_FTRACE) {
int zonenum;
if (!cxt->fprzs)
return -ENOMEM;
/*
* Choose zone by if we're using per-cpu buffers.
*/
if (cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU)
zonenum = smp_processor_id();
else
zonenum = 0;
persistent_ram_write(cxt->fprzs[zonenum], record->buf,
record->size);
return 0;
} else if (record->type == PSTORE_TYPE_PMSG) {
pr_warn_ratelimited("PMSG shouldn't call %s\n", __func__);
return -EINVAL;
}
if (record->type != PSTORE_TYPE_DMESG)
return -EINVAL;
/*
* We could filter on record->reason here if we wanted to (which
* 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".
*/
/*
* Explicitly only take the first part of any new crash.
* If our buffer is larger than kmsg_bytes, this can never happen,
* and if our buffer is smaller than kmsg_bytes, we don't want the
* report split across multiple records.
*/
if (record->part != 1)
return -ENOSPC;
if (!cxt->dprzs)
return -ENOSPC;
prz = cxt->dprzs[cxt->dump_write_cnt];
/*
* Since this is a new crash dump, we need to reset the buffer in
* case it still has an old dump present. Without this, the new dump
* will get appended, which would seriously confuse anything trying
* to check dump file contents. Specifically, ramoops_read_kmsg_hdr()
* expects to find a dump header in the beginning of buffer data, so
* we must to reset the buffer values, in order to ensure that the
* header will be written to the beginning of the buffer.
*/
persistent_ram_zap(prz);
/* Build header and append record contents. */
hlen = ramoops_write_kmsg_hdr(prz, record);
if (!hlen)
return -ENOMEM;
size = record->size;
if (size + hlen > prz->buffer_size)
size = prz->buffer_size - hlen;
persistent_ram_write(prz, record->buf, size);
cxt->dump_write_cnt = (cxt->dump_write_cnt + 1) % cxt->max_dump_cnt;
return 0;
}
static int notrace ramoops_pstore_write_user(struct pstore_record *record,
const char __user *buf)
{
if (record->type == PSTORE_TYPE_PMSG) {
struct ramoops_context *cxt = record->psi->data;
if (!cxt->mprz)
return -ENOMEM;
return persistent_ram_write_user(cxt->mprz, buf, record->size);
}
return -EINVAL;
}
static int ramoops_pstore_erase(struct pstore_record *record)
{
struct ramoops_context *cxt = record->psi->data;
struct persistent_ram_zone *prz;
switch (record->type) {
case PSTORE_TYPE_DMESG:
if (record->id >= cxt->max_dump_cnt)
return -EINVAL;
prz = cxt->dprzs[record->id];
break;
case PSTORE_TYPE_CONSOLE:
prz = cxt->cprz;
break;
case PSTORE_TYPE_FTRACE:
if (record->id >= cxt->max_ftrace_cnt)
return -EINVAL;
prz = cxt->fprzs[record->id];
break;
case PSTORE_TYPE_PMSG:
prz = cxt->mprz;
break;
default:
return -EINVAL;
}
persistent_ram_free_old(prz);
persistent_ram_zap(prz);
return 0;
}
static struct ramoops_context oops_cxt = {
.pstore = {
.owner = THIS_MODULE,
.name = "ramoops",
.open = ramoops_pstore_open,
.read = ramoops_pstore_read,
.write = ramoops_pstore_write,
.write_user = ramoops_pstore_write_user,
.erase = ramoops_pstore_erase,
},
};
static void ramoops_free_przs(struct ramoops_context *cxt)
{
int i;
/* Free pmsg PRZ */
persistent_ram_free(&cxt->mprz);
/* Free console PRZ */
persistent_ram_free(&cxt->cprz);
/* Free dump PRZs */
if (cxt->dprzs) {
for (i = 0; i < cxt->max_dump_cnt; i++)
persistent_ram_free(&cxt->dprzs[i]);
kfree(cxt->dprzs);
cxt->dprzs = NULL;
cxt->max_dump_cnt = 0;
}
/* Free ftrace PRZs */
if (cxt->fprzs) {
for (i = 0; i < cxt->max_ftrace_cnt; i++)
persistent_ram_free(&cxt->fprzs[i]);
kfree(cxt->fprzs);
cxt->fprzs = NULL;
cxt->max_ftrace_cnt = 0;
}
}
static int ramoops_init_przs(const char *name,
struct device *dev, struct ramoops_context *cxt,
struct persistent_ram_zone ***przs,
phys_addr_t *paddr, size_t mem_sz,
ssize_t record_size,
unsigned int *cnt, u32 sig, u32 flags)
{
int err = -ENOMEM;
int i;
size_t zone_sz;
struct persistent_ram_zone **prz_ar;
/* Allocate nothing for 0 mem_sz or 0 record_size. */
if (mem_sz == 0 || record_size == 0) {
*cnt = 0;
return 0;
}
/*
* If we have a negative record size, calculate it based on
* mem_sz / *cnt. If we have a positive record size, calculate
* cnt from mem_sz / record_size.
*/
if (record_size < 0) {
if (*cnt == 0)
return 0;
record_size = mem_sz / *cnt;
if (record_size == 0) {
dev_err(dev, "%s record size == 0 (%zu / %u)\n",
name, mem_sz, *cnt);
goto fail;
}
} else {
*cnt = mem_sz / record_size;
if (*cnt == 0) {
dev_err(dev, "%s record count == 0 (%zu / %zu)\n",
name, mem_sz, record_size);
goto fail;
}
}
if (*paddr + mem_sz - cxt->phys_addr > cxt->size) {
dev_err(dev, "no room for %s mem region (0x%zx@0x%llx) in (0x%lx@0x%llx)\n",
name,
mem_sz, (unsigned long long)*paddr,
cxt->size, (unsigned long long)cxt->phys_addr);
goto fail;
}
zone_sz = mem_sz / *cnt;
zone_sz = ALIGN_DOWN(zone_sz, 2);
if (!zone_sz) {
dev_err(dev, "%s zone size == 0\n", name);
goto fail;
}
prz_ar = kzalloc_objs(**przs, *cnt);
if (!prz_ar)
goto fail;
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,
&cxt->ecc_info,
cxt->memtype, flags, label);
kfree(label);
if (IS_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",
name, record_size,
(unsigned long long)*paddr, err);
while (i > 0) {
i--;
persistent_ram_free(&prz_ar[i]);
}
kfree(prz_ar);
prz_ar = NULL;
goto fail;
}
*paddr += zone_sz;
prz_ar[i]->type = pstore_name_to_type(name);
}
*przs = prz_ar;
return 0;
fail:
*cnt = 0;
return err;
}
static int ramoops_init_prz(const char *name,
struct device *dev, struct ramoops_context *cxt,
struct persistent_ram_zone **prz,
phys_addr_t *paddr, size_t sz, u32 sig)
{
char *label;
if (!sz)
return 0;
if (*paddr + sz - cxt->phys_addr > cxt->size) {
dev_err(dev, "no room for %s mem region (0x%zx@0x%llx) in (0x%lx@0x%llx)\n",
name, sz, (unsigned long long)*paddr,
cxt->size, (unsigned long long)cxt->phys_addr);
return -ENOMEM;
}
label = kasprintf(GFP_KERNEL, "ramoops:%s", name);
*prz = persistent_ram_new(*paddr, sz, sig, &cxt->ecc_info,
cxt->memtype, PRZ_FLAG_ZAP_OLD, label);
kfree(label);
if (IS_ERR(*prz)) {
int err = PTR_ERR(*prz);
dev_err(dev, "failed to request %s mem region (0x%zx@0x%llx): %d\n",
name, sz, (unsigned long long)*paddr, err);
return err;
}
*paddr += sz;
(*prz)->type = pstore_name_to_type(name);
return 0;
}
/* Read a u32 from a dt property and make sure it's safe for an int. */
static int ramoops_parse_dt_u32(struct platform_device *pdev,
const char *propname,
u32 default_value, u32 *value)
{
u32 val32 = 0;
int ret;
ret = of_property_read_u32(pdev->dev.of_node, propname, &val32);
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",
propname, ret);
return ret;
}
/* Sanity check our results. */
if (val32 > INT_MAX) {
dev_err(&pdev->dev, "%s %u > INT_MAX\n", propname, val32);
return -EOVERFLOW;
}
*value = val32;
return 0;
}
static int ramoops_parse_dt(struct platform_device *pdev,
struct ramoops_platform_data *pdata)
{
struct device_node *of_node = pdev->dev.of_node;
struct device_node *parent_node;
struct resource *res;
u32 value;
int ret;
dev_dbg(&pdev->dev, "using Device Tree\n");
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev,
"failed to locate DT /reserved-memory resource\n");
return -EINVAL;
}
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");
/*
* 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_u32(name, field, default_value) { \
ret = ramoops_parse_dt_u32(pdev, name, default_value, \
&value); \
if (ret < 0) \
return ret; \
field = value; \
}
parse_u32("mem-type", pdata->mem_type, pdata->mem_type);
parse_u32("record-size", pdata->record_size, 0);
parse_u32("console-size", pdata->console_size, 0);
parse_u32("ftrace-size", pdata->ftrace_size, 0);
parse_u32("pmsg-size", pdata->pmsg_size, 0);
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);
#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;
}
static int ramoops_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ramoops_platform_data *pdata = dev->platform_data;
struct ramoops_platform_data pdata_local;
struct ramoops_context *cxt = &oops_cxt;
size_t dump_mem_sz;
phys_addr_t paddr;
int err = -EINVAL;
/*
* Only a single ramoops area allowed at a time, so fail extra
* probes.
*/
if (cxt->max_dump_cnt) {
pr_err("already initialized\n");
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. */
if (!pdata) {
pr_err("NULL platform data\n");
err = -EINVAL;
goto fail_out;
}
if (!pdata->mem_size || (!pdata->record_size && !pdata->console_size &&
!pdata->ftrace_size && !pdata->pmsg_size)) {
pr_err("The memory size and the record/console size must be "
"non-zero\n");
err = -EINVAL;
goto fail_out;
}
if (pdata->record_size && !is_power_of_2(pdata->record_size))
pdata->record_size = rounddown_pow_of_two(pdata->record_size);
if (pdata->console_size && !is_power_of_2(pdata->console_size))
pdata->console_size = rounddown_pow_of_two(pdata->console_size);
if (pdata->ftrace_size && !is_power_of_2(pdata->ftrace_size))
pdata->ftrace_size = rounddown_pow_of_two(pdata->ftrace_size);
if (pdata->pmsg_size && !is_power_of_2(pdata->pmsg_size))
pdata->pmsg_size = rounddown_pow_of_two(pdata->pmsg_size);
cxt->size = pdata->mem_size;
cxt->phys_addr = pdata->mem_address;
cxt->memtype = pdata->mem_type;
cxt->record_size = pdata->record_size;
cxt->console_size = pdata->console_size;
cxt->ftrace_size = pdata->ftrace_size;
cxt->pmsg_size = pdata->pmsg_size;
cxt->flags = pdata->flags;
cxt->ecc_info = pdata->ecc_info;
paddr = cxt->phys_addr;
dump_mem_sz = cxt->size - cxt->console_size - cxt->ftrace_size
- cxt->pmsg_size;
err = ramoops_init_przs("dmesg", dev, cxt, &cxt->dprzs, &paddr,
dump_mem_sz, cxt->record_size,
&cxt->max_dump_cnt, 0, 0);
if (err)
goto fail_init;
err = ramoops_init_prz("console", dev, cxt, &cxt->cprz, &paddr,
cxt->console_size, 0);
if (err)
goto fail_init;
err = ramoops_init_prz("pmsg", dev, cxt, &cxt->mprz, &paddr,
cxt->pmsg_size, 0);
if (err)
goto fail_init;
cxt->max_ftrace_cnt = (cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU)
? nr_cpu_ids
: 1;
err = ramoops_init_przs("ftrace", dev, cxt, &cxt->fprzs, &paddr,
cxt->ftrace_size, -1,
&cxt->max_ftrace_cnt, LINUX_VERSION_CODE,
(cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU)
? PRZ_FLAG_NO_LOCK : 0);
if (err)
goto fail_init;
cxt->pstore.data = cxt;
/*
* Prepare frontend flags based on which areas are initialized.
* For ramoops_init_przs() cases, the "max count" variable tells
* if there are regions present. For ramoops_init_prz() cases,
* the single region size is how to check.
*/
cxt->pstore.flags = 0;
if (cxt->max_dump_cnt) {
cxt->pstore.flags |= PSTORE_FLAGS_DMESG;
cxt->pstore.max_reason = pdata->max_reason;
}
if (cxt->console_size)
cxt->pstore.flags |= PSTORE_FLAGS_CONSOLE;
if (cxt->max_ftrace_cnt)
cxt->pstore.flags |= PSTORE_FLAGS_FTRACE;
if (cxt->pmsg_size)
cxt->pstore.flags |= PSTORE_FLAGS_PMSG;
/*
* Since bufsize is only used for dmesg crash dumps, it
* must match the size of the dprz record (after PRZ header
* and ECC bytes have been accounted for).
*/
if (cxt->pstore.flags & PSTORE_FLAGS_DMESG) {
cxt->pstore.bufsize = cxt->dprzs[0]->buffer_size;
cxt->pstore.buf = kvzalloc(cxt->pstore.bufsize, GFP_KERNEL);
if (!cxt->pstore.buf) {
pr_err("cannot allocate pstore crash dump buffer\n");
err = -ENOMEM;
goto fail_clear;
}
}
err = pstore_register(&cxt->pstore);
if (err) {
pr_err("registering with pstore failed\n");
goto fail_buf;
}
/*
* Update the module parameter variables as well so they are visible
* through /sys/module/ramoops/parameters/
*/
mem_size = pdata->mem_size;
mem_address = pdata->mem_address;
record_size = pdata->record_size;
ramoops_max_reason = pdata->max_reason;
ramoops_console_size = pdata->console_size;
ramoops_pmsg_size = pdata->pmsg_size;
ramoops_ftrace_size = pdata->ftrace_size;
mem_type = pdata->mem_type;
ramoops_ecc = pdata->ecc_info.ecc_size;
pr_info("using 0x%lx@0x%llx, ecc: %d\n",
cxt->size, (unsigned long long)cxt->phys_addr,
cxt->ecc_info.ecc_size);
return 0;
fail_buf:
kvfree(cxt->pstore.buf);
fail_clear:
cxt->pstore.bufsize = 0;
fail_init:
ramoops_free_przs(cxt);
fail_out:
return err;
}
static void ramoops_remove(struct platform_device *pdev)
{
struct ramoops_context *cxt = &oops_cxt;
pstore_unregister(&cxt->pstore);
kvfree(cxt->pstore.buf);
cxt->pstore.bufsize = 0;
ramoops_free_przs(cxt);
}
static const struct of_device_id dt_match[] = {
{ .compatible = "ramoops" },
{}
};
MODULE_DEVICE_TABLE(of, dt_match);
static struct platform_driver ramoops_driver = {
.probe = ramoops_probe,
.remove = ramoops_remove,
.driver = {
.name = "ramoops",
.of_match_table = dt_match,
},
};
static inline void ramoops_unregister_dummy(void)
{
platform_device_unregister(dummy);
dummy = NULL;
}
static void __init ramoops_register_dummy(void)
{
struct ramoops_platform_data pdata;
if (mem_name) {
phys_addr_t start;
phys_addr_t size;
if (reserve_mem_find_by_name(mem_name, &start, &size)) {
mem_address = start;
mem_size = size;
}
}
/*
* Prepare a dummy platform data structure to carry the module
* parameters. If mem_size isn't set, then there are no module
* parameters, and we can skip this.
*/
if (!mem_size)
return;
pr_info("using module parameters\n");
memset(&pdata, 0, sizeof(pdata));
pdata.mem_size = mem_size;
pdata.mem_address = mem_address;
pdata.mem_type = mem_type;
pdata.record_size = record_size;
pdata.console_size = ramoops_console_size;
pdata.ftrace_size = ramoops_ftrace_size;
pdata.pmsg_size = ramoops_pmsg_size;
/* If "max_reason" is set, its value has priority over "dump_oops". */
if (ramoops_max_reason >= 0)
pdata.max_reason = ramoops_max_reason;
/* Otherwise, if "dump_oops" is set, parse it into "max_reason". */
else if (ramoops_dump_oops != -1)
pdata.max_reason = ramoops_dump_oops ? KMSG_DUMP_OOPS
: 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
* (using 1 byte for ECC isn't much of use anyway).
*/
pdata.ecc_info.ecc_size = ramoops_ecc == 1 ? 16 : ramoops_ecc;
dummy = platform_device_register_data(NULL, "ramoops", -1,
&pdata, sizeof(pdata));
if (IS_ERR(dummy)) {
pr_info("could not create platform device: %ld\n",
PTR_ERR(dummy));
dummy = NULL;
}
}
static int __init ramoops_init(void)
{
int ret;
ramoops_register_dummy();
ret = platform_driver_register(&ramoops_driver);
if (ret != 0)
ramoops_unregister_dummy();
return ret;
}
postcore_initcall(ramoops_init);
static void __exit ramoops_exit(void)
{
platform_driver_unregister(&ramoops_driver);
ramoops_unregister_dummy();
}
module_exit(ramoops_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marco Stornelli <marco.stornelli@gmail.com>");
MODULE_DESCRIPTION("RAM Oops/Panic logger/driver");
+642
View File
@@ -0,0 +1,642 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 Google, Inc.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/rslib.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/page.h>
#include "ram_internal.h"
/**
* struct persistent_ram_buffer - persistent circular RAM buffer
*
* @sig: Signature to indicate header (PERSISTENT_RAM_SIG xor PRZ-type value)
* @start: First valid byte in the buffer.
* @size: Number of valid bytes in the buffer.
* @data: The contents of the buffer.
*/
struct persistent_ram_buffer {
uint32_t sig;
atomic_t start;
atomic_t size;
uint8_t data[];
};
#define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */
static inline size_t buffer_size(struct persistent_ram_zone *prz)
{
return atomic_read(&prz->buffer->size);
}
static inline size_t buffer_start(struct persistent_ram_zone *prz)
{
return atomic_read(&prz->buffer->start);
}
/* increase and wrap the start pointer, returning the old value */
static size_t buffer_start_add(struct persistent_ram_zone *prz, size_t a)
{
int old;
int new;
unsigned long flags = 0;
if (!(prz->flags & PRZ_FLAG_NO_LOCK))
raw_spin_lock_irqsave(&prz->buffer_lock, flags);
old = atomic_read(&prz->buffer->start);
new = old + a;
while (unlikely(new >= prz->buffer_size))
new -= prz->buffer_size;
atomic_set(&prz->buffer->start, new);
if (!(prz->flags & PRZ_FLAG_NO_LOCK))
raw_spin_unlock_irqrestore(&prz->buffer_lock, flags);
return old;
}
/* increase the size counter until it hits the max size */
static void buffer_size_add(struct persistent_ram_zone *prz, size_t a)
{
size_t old;
size_t new;
unsigned long flags = 0;
if (!(prz->flags & PRZ_FLAG_NO_LOCK))
raw_spin_lock_irqsave(&prz->buffer_lock, flags);
old = atomic_read(&prz->buffer->size);
if (old == prz->buffer_size)
goto exit;
new = old + a;
if (new > prz->buffer_size)
new = prz->buffer_size;
atomic_set(&prz->buffer->size, new);
exit:
if (!(prz->flags & PRZ_FLAG_NO_LOCK))
raw_spin_unlock_irqrestore(&prz->buffer_lock, flags);
}
static void notrace persistent_ram_encode_rs8(struct persistent_ram_zone *prz,
uint8_t *data, size_t len, uint8_t *ecc)
{
int i;
/* Initialize the parity buffer */
memset(prz->ecc_info.par, 0,
prz->ecc_info.ecc_size * sizeof(prz->ecc_info.par[0]));
encode_rs8(prz->rs_decoder, data, len, prz->ecc_info.par, 0);
for (i = 0; i < prz->ecc_info.ecc_size; i++)
ecc[i] = prz->ecc_info.par[i];
}
static int persistent_ram_decode_rs8(struct persistent_ram_zone *prz,
void *data, size_t len, uint8_t *ecc)
{
int i;
for (i = 0; i < prz->ecc_info.ecc_size; i++)
prz->ecc_info.par[i] = ecc[i];
return decode_rs8(prz->rs_decoder, data, prz->ecc_info.par, len,
NULL, 0, NULL, 0, NULL);
}
static void notrace persistent_ram_update_ecc(struct persistent_ram_zone *prz,
unsigned int start, unsigned int count)
{
struct persistent_ram_buffer *buffer = prz->buffer;
uint8_t *buffer_end = buffer->data + prz->buffer_size;
uint8_t *block;
uint8_t *par;
int ecc_block_size = prz->ecc_info.block_size;
int ecc_size = prz->ecc_info.ecc_size;
int size = ecc_block_size;
if (!ecc_size)
return;
block = buffer->data + (start & ~(ecc_block_size - 1));
par = prz->par_buffer + (start / ecc_block_size) * ecc_size;
do {
if (block + ecc_block_size > buffer_end)
size = buffer_end - block;
persistent_ram_encode_rs8(prz, block, size, par);
block += ecc_block_size;
par += ecc_size;
} while (block < buffer->data + start + count);
}
static void persistent_ram_update_header_ecc(struct persistent_ram_zone *prz)
{
struct persistent_ram_buffer *buffer = prz->buffer;
if (!prz->ecc_info.ecc_size)
return;
persistent_ram_encode_rs8(prz, (uint8_t *)buffer, sizeof(*buffer),
prz->par_header);
}
static void persistent_ram_ecc_old(struct persistent_ram_zone *prz)
{
struct persistent_ram_buffer *buffer = prz->buffer;
uint8_t *block;
uint8_t *par;
if (!prz->ecc_info.ecc_size)
return;
block = buffer->data;
par = prz->par_buffer;
while (block < buffer->data + buffer_size(prz)) {
int numerr;
int size = prz->ecc_info.block_size;
if (block + size > buffer->data + prz->buffer_size)
size = buffer->data + prz->buffer_size - block;
numerr = persistent_ram_decode_rs8(prz, block, size, par);
if (numerr > 0) {
pr_devel("error in block %p, %d\n", block, numerr);
prz->corrected_bytes += numerr;
} else if (numerr < 0) {
pr_devel("uncorrectable error in block %p\n", block);
prz->bad_blocks++;
}
block += prz->ecc_info.block_size;
par += prz->ecc_info.ecc_size;
}
}
static int persistent_ram_init_ecc(struct persistent_ram_zone *prz,
struct persistent_ram_ecc_info *ecc_info)
{
int numerr;
struct persistent_ram_buffer *buffer = prz->buffer;
size_t ecc_blocks;
size_t ecc_total;
if (!ecc_info || !ecc_info->ecc_size)
return 0;
prz->ecc_info.block_size = ecc_info->block_size ?: 128;
prz->ecc_info.ecc_size = ecc_info->ecc_size ?: 16;
prz->ecc_info.symsize = ecc_info->symsize ?: 8;
prz->ecc_info.poly = ecc_info->poly ?: 0x11d;
ecc_blocks = DIV_ROUND_UP(prz->buffer_size - prz->ecc_info.ecc_size,
prz->ecc_info.block_size +
prz->ecc_info.ecc_size);
ecc_total = (ecc_blocks + 1) * prz->ecc_info.ecc_size;
if (ecc_total >= prz->buffer_size) {
pr_err("%s: invalid ecc_size %u (total %zu, buffer size %zu)\n",
__func__, prz->ecc_info.ecc_size,
ecc_total, prz->buffer_size);
return -EINVAL;
}
prz->buffer_size -= ecc_total;
prz->par_buffer = buffer->data + prz->buffer_size;
prz->par_header = prz->par_buffer +
ecc_blocks * prz->ecc_info.ecc_size;
/*
* first consecutive root is 0
* primitive element to generate roots = 1
*/
prz->rs_decoder = init_rs(prz->ecc_info.symsize, prz->ecc_info.poly,
0, 1, prz->ecc_info.ecc_size);
if (prz->rs_decoder == NULL) {
pr_info("init_rs failed\n");
return -EINVAL;
}
/* allocate workspace instead of using stack VLA */
prz->ecc_info.par = kmalloc_objs(*prz->ecc_info.par,
prz->ecc_info.ecc_size);
if (!prz->ecc_info.par) {
pr_err("cannot allocate ECC parity workspace\n");
return -ENOMEM;
}
prz->corrected_bytes = 0;
prz->bad_blocks = 0;
numerr = persistent_ram_decode_rs8(prz, buffer, sizeof(*buffer),
prz->par_header);
if (numerr > 0) {
pr_info("error in header, %d\n", numerr);
prz->corrected_bytes += numerr;
} else if (numerr < 0) {
pr_info_ratelimited("uncorrectable error in header\n");
prz->bad_blocks++;
}
return 0;
}
ssize_t persistent_ram_ecc_string(struct persistent_ram_zone *prz,
char *str, size_t len)
{
ssize_t ret;
if (!prz->ecc_info.ecc_size)
return 0;
if (prz->corrected_bytes || prz->bad_blocks)
ret = snprintf(str, len, ""
"\nECC: %d Corrected bytes, %d unrecoverable blocks\n",
prz->corrected_bytes, prz->bad_blocks);
else
ret = snprintf(str, len, "\nECC: No errors detected\n");
return ret;
}
static void notrace persistent_ram_update(struct persistent_ram_zone *prz,
const void *s, unsigned int start, unsigned int count)
{
struct persistent_ram_buffer *buffer = prz->buffer;
memcpy_toio(buffer->data + start, s, count);
persistent_ram_update_ecc(prz, start, count);
}
static int notrace persistent_ram_update_user(struct persistent_ram_zone *prz,
const void __user *s, unsigned int start, unsigned int count)
{
struct persistent_ram_buffer *buffer = prz->buffer;
int ret = unlikely(copy_from_user(buffer->data + start, s, count)) ?
-EFAULT : 0;
persistent_ram_update_ecc(prz, start, count);
return ret;
}
void persistent_ram_save_old(struct persistent_ram_zone *prz)
{
struct persistent_ram_buffer *buffer = prz->buffer;
size_t size = buffer_size(prz);
size_t start = buffer_start(prz);
if (!size)
return;
/*
* If the existing buffer is differently sized, free it so a new
* one is allocated. This can happen when persistent_ram_save_old()
* is called early in boot and later for a timer-triggered
* survivable crash when the crash dumps don't match in size
* (which would be extremely unlikely given kmsg buffers usually
* exceed prz buffer sizes).
*/
if (prz->old_log && prz->old_log_size != size)
persistent_ram_free_old(prz);
if (!prz->old_log) {
persistent_ram_ecc_old(prz);
prz->old_log = kvzalloc(size, GFP_KERNEL);
}
if (!prz->old_log) {
pr_err("failed to allocate buffer\n");
return;
}
prz->old_log_size = size;
memcpy_fromio(prz->old_log, &buffer->data[start], size - start);
memcpy_fromio(prz->old_log + size - start, &buffer->data[0], start);
}
int notrace persistent_ram_write(struct persistent_ram_zone *prz,
const void *s, unsigned int count)
{
int rem;
int c = count;
size_t start;
if (unlikely(c > prz->buffer_size)) {
s += c - prz->buffer_size;
c = prz->buffer_size;
}
buffer_size_add(prz, c);
start = buffer_start_add(prz, c);
rem = prz->buffer_size - start;
if (unlikely(rem < c)) {
persistent_ram_update(prz, s, start, rem);
s += rem;
c -= rem;
start = 0;
}
persistent_ram_update(prz, s, start, c);
persistent_ram_update_header_ecc(prz);
return count;
}
int notrace persistent_ram_write_user(struct persistent_ram_zone *prz,
const void __user *s, unsigned int count)
{
int rem, ret = 0, c = count;
size_t start;
if (unlikely(c > prz->buffer_size)) {
s += c - prz->buffer_size;
c = prz->buffer_size;
}
buffer_size_add(prz, c);
start = buffer_start_add(prz, c);
rem = prz->buffer_size - start;
if (unlikely(rem < c)) {
ret = persistent_ram_update_user(prz, s, start, rem);
s += rem;
c -= rem;
start = 0;
}
if (likely(!ret))
ret = persistent_ram_update_user(prz, s, start, c);
persistent_ram_update_header_ecc(prz);
return unlikely(ret) ? ret : count;
}
size_t persistent_ram_old_size(struct persistent_ram_zone *prz)
{
return prz->old_log_size;
}
void *persistent_ram_old(struct persistent_ram_zone *prz)
{
return prz->old_log;
}
void persistent_ram_free_old(struct persistent_ram_zone *prz)
{
kvfree(prz->old_log);
prz->old_log = NULL;
prz->old_log_size = 0;
}
void persistent_ram_zap(struct persistent_ram_zone *prz)
{
atomic_set(&prz->buffer->start, 0);
atomic_set(&prz->buffer->size, 0);
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,
unsigned int memtype)
{
struct page **pages;
phys_addr_t page_start;
unsigned int page_count;
pgprot_t prot;
unsigned int i;
void *vaddr;
page_start = start - offset_in_page(start);
page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE);
switch (memtype) {
case MEM_TYPE_NORMAL:
prot = PAGE_KERNEL;
break;
case MEM_TYPE_NONCACHED:
prot = pgprot_noncached(PAGE_KERNEL);
break;
case MEM_TYPE_WCOMBINE:
prot = pgprot_writecombine(PAGE_KERNEL);
break;
default:
pr_err("invalid mem_type=%d\n", memtype);
return NULL;
}
pages = kmalloc_objs(struct page *, page_count);
if (!pages) {
pr_err("%s: Failed to allocate array for %u pages\n",
__func__, page_count);
return NULL;
}
for (i = 0; i < page_count; i++) {
phys_addr_t addr = page_start + i * PAGE_SIZE;
pages[i] = pfn_to_page(addr >> PAGE_SHIFT);
}
/*
* VM_IOREMAP used here to bypass this region during vread_iter()
* and kmap_atomic() (i.e. kcore) to avoid __va() failures.
*/
vaddr = vmap(pages, page_count, VM_MAP | VM_IOREMAP, prot);
kfree(pages);
/*
* vmap() may fail and return NULL. Do not add the offset in this
* case, otherwise a NULL mapping would appear successful.
*/
if (!vaddr)
return NULL;
/*
* Since vmap() uses page granularity, we must add the offset
* into the page here, to get the byte granularity address
* into the mapping to represent the actual "start" location.
*/
return vaddr + offset_in_page(start);
}
static void *persistent_ram_iomap(phys_addr_t start, size_t size,
unsigned int memtype, char *label)
{
void *va;
if (!request_mem_region(start, size, label ?: "ramoops")) {
pr_err("request mem region (%s 0x%llx@0x%llx) failed\n",
label ?: "ramoops",
(unsigned long long)size, (unsigned long long)start);
return NULL;
}
if (memtype)
va = ioremap(start, size);
else
va = ioremap_wc(start, size);
/* We must release the mem region if ioremap fails. */
if (!va)
release_mem_region(start, size);
/*
* Since request_mem_region() and ioremap() are byte-granularity
* there is no need handle anything special like we do when the
* vmap() case in persistent_ram_vmap() above.
*/
return va;
}
static int persistent_ram_buffer_map(phys_addr_t start, phys_addr_t size,
struct persistent_ram_zone *prz, int memtype)
{
prz->paddr = start;
prz->size = size;
if (pfn_valid(start >> PAGE_SHIFT))
prz->vaddr = persistent_ram_vmap(start, size, memtype);
else
prz->vaddr = persistent_ram_iomap(start, size, memtype,
prz->label);
if (!prz->vaddr) {
pr_err("%s: Failed to map 0x%llx pages at 0x%llx\n", __func__,
(unsigned long long)size, (unsigned long long)start);
return -ENOMEM;
}
prz->buffer = prz->vaddr;
prz->buffer_size = size - sizeof(struct persistent_ram_buffer);
return 0;
}
static int persistent_ram_post_init(struct persistent_ram_zone *prz, u32 sig,
struct persistent_ram_ecc_info *ecc_info)
{
int ret;
bool zap = !!(prz->flags & PRZ_FLAG_ZAP_OLD);
ret = persistent_ram_init_ecc(prz, ecc_info);
if (ret) {
pr_warn("ECC failed %s\n", prz->label);
return ret;
}
sig ^= PERSISTENT_RAM_SIG;
if (prz->buffer->sig == sig) {
if (buffer_size(prz) == 0 && buffer_start(prz) == 0) {
pr_debug("found existing empty buffer\n");
return 0;
}
if (buffer_size(prz) > prz->buffer_size ||
buffer_start(prz) > buffer_size(prz)) {
pr_info("found existing invalid buffer, size %zu, start %zu\n",
buffer_size(prz), buffer_start(prz));
zap = true;
} else {
pr_debug("found existing buffer, size %zu, start %zu\n",
buffer_size(prz), buffer_start(prz));
persistent_ram_save_old(prz);
}
} else {
pr_debug("no valid data in buffer (sig = 0x%08x)\n",
prz->buffer->sig);
prz->buffer->sig = sig;
zap = true;
}
/* Reset missing, invalid, or single-use memory area. */
if (zap)
persistent_ram_zap(prz);
return 0;
}
void persistent_ram_free(struct persistent_ram_zone **_prz)
{
struct persistent_ram_zone *prz;
if (!_prz)
return;
prz = *_prz;
if (!prz)
return;
if (prz->vaddr) {
if (pfn_valid(prz->paddr >> PAGE_SHIFT)) {
/* We must vunmap() at page-granularity. */
vunmap(prz->vaddr - offset_in_page(prz->paddr));
} else {
iounmap(prz->vaddr);
release_mem_region(prz->paddr, prz->size);
}
prz->vaddr = NULL;
}
if (prz->rs_decoder) {
free_rs(prz->rs_decoder);
prz->rs_decoder = NULL;
}
kfree(prz->ecc_info.par);
prz->ecc_info.par = NULL;
persistent_ram_free_old(prz);
kfree(prz->label);
kfree(prz);
*_prz = NULL;
}
struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
u32 sig, struct persistent_ram_ecc_info *ecc_info,
unsigned int memtype, u32 flags, char *label)
{
struct persistent_ram_zone *prz;
int ret = -ENOMEM;
prz = kzalloc_obj(struct persistent_ram_zone);
if (!prz) {
pr_err("failed to allocate persistent ram zone\n");
goto err;
}
/* Initialize general buffer state. */
raw_spin_lock_init(&prz->buffer_lock);
prz->flags = flags;
prz->label = kstrdup(label, GFP_KERNEL);
if (!prz->label)
goto err;
ret = persistent_ram_buffer_map(start, size, prz, memtype);
if (ret)
goto err;
ret = persistent_ram_post_init(prz, sig, ecc_info);
if (ret)
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;
err:
persistent_ram_free(&prz);
return ERR_PTR(ret);
}
+98
View File
@@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2010 Marco Stornelli <marco.stornelli@gmail.com>
* Copyright (C) 2011 Kees Cook <keescook@chromium.org>
* Copyright (C) 2011 Google, Inc.
*/
#include <linux/pstore_ram.h>
/*
* Choose whether access to the RAM zone requires locking or not. If a zone
* can be written to from different CPUs like with ftrace for example, then
* PRZ_FLAG_NO_LOCK is used. For all other cases, locking is required.
*/
#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_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 {
phys_addr_t paddr;
size_t size;
void *vaddr;
char *label;
enum pstore_type_id type;
u32 flags;
raw_spinlock_t buffer_lock;
struct persistent_ram_buffer *buffer;
size_t buffer_size;
char *par_buffer;
char *par_header;
struct rs_control *rs_decoder;
int corrected_bytes;
int bad_blocks;
struct persistent_ram_ecc_info ecc_info;
char *old_log;
size_t old_log_size;
};
struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
u32 sig, struct persistent_ram_ecc_info *ecc_info,
unsigned int memtype, u32 flags, char *label);
void persistent_ram_free(struct persistent_ram_zone **_prz);
void persistent_ram_zap(struct persistent_ram_zone *prz);
int persistent_ram_write(struct persistent_ram_zone *prz, const void *s,
unsigned int count);
int persistent_ram_write_user(struct persistent_ram_zone *prz,
const void __user *s, unsigned int count);
void persistent_ram_save_old(struct persistent_ram_zone *prz);
size_t persistent_ram_old_size(struct persistent_ram_zone *prz);
void *persistent_ram_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,
char *str, size_t len);
File diff suppressed because it is too large Load Diff