[Mod] 同步v5.10版本Pstore
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config PSTORE
|
||||
tristate "Persistent store support"
|
||||
select CRYPTO if PSTORE_COMPRESS
|
||||
@ -114,18 +115,6 @@ config PSTORE_CONSOLE
|
||||
When the option is enabled, pstore will log all kernel
|
||||
messages, even if no oops or panic happened.
|
||||
|
||||
config PSTORE_CONSOLE_FORCE
|
||||
bool "Log kernel console messages ignore loglevel setting"
|
||||
depends on PSTORE_CONSOLE
|
||||
help
|
||||
When the option is enabled, allow all kernel messages
|
||||
log to the pstore console. Enable this with a kernel bool
|
||||
parameter like "pstore_con_force=1".
|
||||
|
||||
config PSTORE_CONSOLE_FORCE_ON
|
||||
bool "Log kernel console messages ignore loglevel setting on by default"
|
||||
depends on PSTORE_CONSOLE_FORCE
|
||||
|
||||
config PSTORE_PMSG
|
||||
bool "Log user space messages"
|
||||
depends on PSTORE
|
||||
@ -153,7 +142,6 @@ config PSTORE_RAM
|
||||
tristate "Log panic/oops to a RAM buffer"
|
||||
depends on PSTORE
|
||||
depends on HAS_IOMEM
|
||||
depends on HAVE_MEMBLOCK
|
||||
select REED_SOLOMON
|
||||
select REED_SOLOMON_ENC8
|
||||
select REED_SOLOMON_DEC8
|
||||
@ -166,6 +154,116 @@ config PSTORE_RAM
|
||||
|
||||
For more information, see Documentation/admin-guide/ramoops.rst.
|
||||
|
||||
config PSTORE_ZONE
|
||||
tristate
|
||||
depends on PSTORE
|
||||
help
|
||||
The common layer for pstore/blk (and pstore/ram in the future)
|
||||
to manage storage in zones.
|
||||
|
||||
config PSTORE_BLK
|
||||
tristate "Log panic/oops to a block device"
|
||||
depends on PSTORE
|
||||
depends on BLOCK
|
||||
depends on BROKEN
|
||||
select PSTORE_ZONE
|
||||
default n
|
||||
help
|
||||
This enables panic and oops message to be logged to a block dev
|
||||
where it can be read back at some later point.
|
||||
|
||||
For more information, see Documentation/admin-guide/pstore-blk.rst
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config PSTORE_BLK_BLKDEV
|
||||
string "block device identifier"
|
||||
depends on PSTORE_BLK
|
||||
default ""
|
||||
help
|
||||
Which block device should be used for pstore/blk.
|
||||
|
||||
It accepts the following variants:
|
||||
1) <hex_major><hex_minor> device number in hexadecimal representation,
|
||||
with no leading 0x, for example b302.
|
||||
2) /dev/<disk_name> represents the device name of disk
|
||||
3) /dev/<disk_name><decimal> represents the device name and number
|
||||
of partition - device number of disk plus the partition number
|
||||
4) /dev/<disk_name>p<decimal> - same as the above, this form is
|
||||
used when disk name of partitioned disk ends with a digit.
|
||||
5) PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF representing the
|
||||
unique id of a partition if the partition table provides it.
|
||||
The UUID may be either an EFI/GPT UUID, or refer to an MSDOS
|
||||
partition using the format SSSSSSSS-PP, where SSSSSSSS is a zero-
|
||||
filled hex representation of the 32-bit "NT disk signature", and PP
|
||||
is a zero-filled hex representation of the 1-based partition number.
|
||||
6) PARTUUID=<UUID>/PARTNROFF=<int> to select a partition in relation
|
||||
to a partition with a known unique id.
|
||||
7) <major>:<minor> major and minor number of the device separated by
|
||||
a colon.
|
||||
|
||||
NOTE that, both Kconfig and module parameters can configure
|
||||
pstore/blk, but module parameters have priority over Kconfig.
|
||||
|
||||
config PSTORE_BLK_KMSG_SIZE
|
||||
int "Size in Kbytes of kmsg dump log to store"
|
||||
depends on PSTORE_BLK
|
||||
default 64
|
||||
help
|
||||
This just sets size of kmsg dump (oops, panic, etc) log for
|
||||
pstore/blk. The size is in KB and must be a multiple of 4.
|
||||
|
||||
NOTE that, both Kconfig and module parameters can configure
|
||||
pstore/blk, but module parameters have priority over Kconfig.
|
||||
|
||||
config PSTORE_BLK_MAX_REASON
|
||||
int "Maximum kmsg dump reason to store"
|
||||
depends on PSTORE_BLK
|
||||
default 2
|
||||
help
|
||||
The maximum reason for kmsg dumps to store. The default is
|
||||
2 (KMSG_DUMP_OOPS), see include/linux/kmsg_dump.h's
|
||||
enum kmsg_dump_reason for more details.
|
||||
|
||||
NOTE that, both Kconfig and module parameters can configure
|
||||
pstore/blk, but module parameters have priority over Kconfig.
|
||||
|
||||
config PSTORE_BLK_PMSG_SIZE
|
||||
int "Size in Kbytes of pmsg to store"
|
||||
depends on PSTORE_BLK
|
||||
depends on PSTORE_PMSG
|
||||
default 64
|
||||
help
|
||||
This just sets size of pmsg (pmsg_size) for pstore/blk. The size is
|
||||
in KB and must be a multiple of 4.
|
||||
|
||||
NOTE that, both Kconfig and module parameters can configure
|
||||
pstore/blk, but module parameters have priority over Kconfig.
|
||||
|
||||
config PSTORE_BLK_CONSOLE_SIZE
|
||||
int "Size in Kbytes of console log to store"
|
||||
depends on PSTORE_BLK
|
||||
depends on PSTORE_CONSOLE
|
||||
default 64
|
||||
help
|
||||
This just sets size of console log (console_size) to store via
|
||||
pstore/blk. The size is in KB and must be a multiple of 4.
|
||||
|
||||
NOTE that, both Kconfig and module parameters can configure
|
||||
pstore/blk, but module parameters have priority over Kconfig.
|
||||
|
||||
config PSTORE_BLK_FTRACE_SIZE
|
||||
int "Size in Kbytes of ftrace log to store"
|
||||
depends on PSTORE_BLK
|
||||
depends on PSTORE_FTRACE
|
||||
default 64
|
||||
help
|
||||
This just sets size of ftrace log (ftrace_size) for pstore/blk. The
|
||||
size is in KB and must be a multiple of 4.
|
||||
|
||||
NOTE that, both Kconfig and module parameters can configure
|
||||
pstore/blk, but module parameters have priority over Kconfig.
|
||||
|
||||
config PSTORE_BOOT_LOG
|
||||
bool "Print boot log by linux"
|
||||
depends on PSTORE
|
||||
|
||||
@ -12,3 +12,9 @@ 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
|
||||
|
||||
517
fs/pstore/blk.c
Normal file
517
fs/pstore/blk.c
Normal file
@ -0,0 +1,517 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Implements pstore backend driver that write to block (or non-block) storage
|
||||
* devices, using the pstore/zone API.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include "../../block/blk.h"
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pstore_blk.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/uio.h>
|
||||
|
||||
static long kmsg_size = CONFIG_PSTORE_BLK_KMSG_SIZE;
|
||||
module_param(kmsg_size, long, 0400);
|
||||
MODULE_PARM_DESC(kmsg_size, "kmsg dump record size in kbytes");
|
||||
|
||||
static int max_reason = CONFIG_PSTORE_BLK_MAX_REASON;
|
||||
module_param(max_reason, int, 0400);
|
||||
MODULE_PARM_DESC(max_reason,
|
||||
"maximum reason for kmsg dump (default 2: Oops and Panic)");
|
||||
|
||||
#if IS_ENABLED(CONFIG_PSTORE_PMSG)
|
||||
static long pmsg_size = CONFIG_PSTORE_BLK_PMSG_SIZE;
|
||||
#else
|
||||
static long pmsg_size = -1;
|
||||
#endif
|
||||
module_param(pmsg_size, long, 0400);
|
||||
MODULE_PARM_DESC(pmsg_size, "pmsg size in kbytes");
|
||||
|
||||
#if IS_ENABLED(CONFIG_PSTORE_CONSOLE)
|
||||
static long console_size = CONFIG_PSTORE_BLK_CONSOLE_SIZE;
|
||||
#else
|
||||
static long console_size = -1;
|
||||
#endif
|
||||
module_param(console_size, long, 0400);
|
||||
MODULE_PARM_DESC(console_size, "console size in kbytes");
|
||||
|
||||
#if IS_ENABLED(CONFIG_PSTORE_FTRACE)
|
||||
static long ftrace_size = CONFIG_PSTORE_BLK_FTRACE_SIZE;
|
||||
#else
|
||||
static long ftrace_size = -1;
|
||||
#endif
|
||||
module_param(ftrace_size, long, 0400);
|
||||
MODULE_PARM_DESC(ftrace_size, "ftrace size in kbytes");
|
||||
|
||||
static bool best_effort;
|
||||
module_param(best_effort, bool, 0400);
|
||||
MODULE_PARM_DESC(best_effort, "use best effort to write (i.e. do not require storage driver pstore support, default: off)");
|
||||
|
||||
/*
|
||||
* blkdev - the block device to use for pstore storage
|
||||
*
|
||||
* Usually, this will be a partition of a block device.
|
||||
*
|
||||
* blkdev accepts the following variants:
|
||||
* 1) <hex_major><hex_minor> device number in hexadecimal representation,
|
||||
* with no leading 0x, for example b302.
|
||||
* 2) /dev/<disk_name> represents the device number of disk
|
||||
* 3) /dev/<disk_name><decimal> represents the device number
|
||||
* of partition - device number of disk plus the partition number
|
||||
* 4) /dev/<disk_name>p<decimal> - same as the above, that form is
|
||||
* used when disk name of partitioned disk ends on a digit.
|
||||
* 5) PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF representing the
|
||||
* unique id of a partition if the partition table provides it.
|
||||
* The UUID may be either an EFI/GPT UUID, or refer to an MSDOS
|
||||
* partition using the format SSSSSSSS-PP, where SSSSSSSS is a zero-
|
||||
* filled hex representation of the 32-bit "NT disk signature", and PP
|
||||
* is a zero-filled hex representation of the 1-based partition number.
|
||||
* 6) PARTUUID=<UUID>/PARTNROFF=<int> to select a partition in relation to
|
||||
* a partition with a known unique id.
|
||||
* 7) <major>:<minor> major and minor number of the device separated by
|
||||
* a colon.
|
||||
*/
|
||||
static char blkdev[80] = CONFIG_PSTORE_BLK_BLKDEV;
|
||||
module_param_string(blkdev, blkdev, 80, 0400);
|
||||
MODULE_PARM_DESC(blkdev, "block device for pstore storage");
|
||||
|
||||
/*
|
||||
* All globals must only be accessed under the pstore_blk_lock
|
||||
* during the register/unregister functions.
|
||||
*/
|
||||
static DEFINE_MUTEX(pstore_blk_lock);
|
||||
static struct block_device *psblk_bdev;
|
||||
static struct pstore_zone_info *pstore_zone_info;
|
||||
static pstore_blk_panic_write_op blkdev_panic_write;
|
||||
|
||||
struct bdev_info {
|
||||
dev_t devt;
|
||||
sector_t nr_sects;
|
||||
sector_t start_sect;
|
||||
};
|
||||
|
||||
#define check_size(name, alignsize) ({ \
|
||||
long _##name_ = (name); \
|
||||
_##name_ = _##name_ <= 0 ? 0 : (_##name_ * 1024); \
|
||||
if (_##name_ & ((alignsize) - 1)) { \
|
||||
pr_info(#name " must align to %d\n", \
|
||||
(alignsize)); \
|
||||
_##name_ = ALIGN(name, (alignsize)); \
|
||||
} \
|
||||
_##name_; \
|
||||
})
|
||||
|
||||
static int __register_pstore_device(struct pstore_device_info *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&pstore_blk_lock);
|
||||
|
||||
if (!dev || !dev->total_size || !dev->read || !dev->write)
|
||||
return -EINVAL;
|
||||
|
||||
/* someone already registered before */
|
||||
if (pstore_zone_info)
|
||||
return -EBUSY;
|
||||
|
||||
pstore_zone_info = kzalloc(sizeof(struct pstore_zone_info), GFP_KERNEL);
|
||||
if (!pstore_zone_info)
|
||||
return -ENOMEM;
|
||||
|
||||
/* zero means not limit on which backends to attempt to store. */
|
||||
if (!dev->flags)
|
||||
dev->flags = UINT_MAX;
|
||||
|
||||
#define verify_size(name, alignsize, enabled) { \
|
||||
long _##name_; \
|
||||
if (enabled) \
|
||||
_##name_ = check_size(name, alignsize); \
|
||||
else \
|
||||
_##name_ = 0; \
|
||||
name = _##name_ / 1024; \
|
||||
pstore_zone_info->name = _##name_; \
|
||||
}
|
||||
|
||||
verify_size(kmsg_size, 4096, dev->flags & PSTORE_FLAGS_DMESG);
|
||||
verify_size(pmsg_size, 4096, dev->flags & PSTORE_FLAGS_PMSG);
|
||||
verify_size(console_size, 4096, dev->flags & PSTORE_FLAGS_CONSOLE);
|
||||
verify_size(ftrace_size, 4096, dev->flags & PSTORE_FLAGS_FTRACE);
|
||||
#undef verify_size
|
||||
|
||||
pstore_zone_info->total_size = dev->total_size;
|
||||
pstore_zone_info->max_reason = max_reason;
|
||||
pstore_zone_info->read = dev->read;
|
||||
pstore_zone_info->write = dev->write;
|
||||
pstore_zone_info->erase = dev->erase;
|
||||
pstore_zone_info->panic_write = dev->panic_write;
|
||||
pstore_zone_info->name = KBUILD_MODNAME;
|
||||
pstore_zone_info->owner = THIS_MODULE;
|
||||
|
||||
ret = register_pstore_zone(pstore_zone_info);
|
||||
if (ret) {
|
||||
kfree(pstore_zone_info);
|
||||
pstore_zone_info = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* register_pstore_device() - register non-block device to pstore/blk
|
||||
*
|
||||
* @dev: non-block device information
|
||||
*
|
||||
* Return:
|
||||
* * 0 - OK
|
||||
* * Others - something error.
|
||||
*/
|
||||
int register_pstore_device(struct pstore_device_info *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&pstore_blk_lock);
|
||||
ret = __register_pstore_device(dev);
|
||||
mutex_unlock(&pstore_blk_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_pstore_device);
|
||||
|
||||
static void __unregister_pstore_device(struct pstore_device_info *dev)
|
||||
{
|
||||
lockdep_assert_held(&pstore_blk_lock);
|
||||
if (pstore_zone_info && pstore_zone_info->read == dev->read) {
|
||||
unregister_pstore_zone(pstore_zone_info);
|
||||
kfree(pstore_zone_info);
|
||||
pstore_zone_info = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister_pstore_device() - unregister non-block device from pstore/blk
|
||||
*
|
||||
* @dev: non-block device information
|
||||
*/
|
||||
void unregister_pstore_device(struct pstore_device_info *dev)
|
||||
{
|
||||
mutex_lock(&pstore_blk_lock);
|
||||
__unregister_pstore_device(dev);
|
||||
mutex_unlock(&pstore_blk_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unregister_pstore_device);
|
||||
|
||||
/**
|
||||
* psblk_get_bdev() - open block device
|
||||
*
|
||||
* @holder: Exclusive holder identifier
|
||||
* @info: Information about bdev to fill in
|
||||
*
|
||||
* Return: pointer to block device on success and others on error.
|
||||
*
|
||||
* On success, the returned block_device has reference count of one.
|
||||
*/
|
||||
static struct block_device *psblk_get_bdev(void *holder,
|
||||
struct bdev_info *info)
|
||||
{
|
||||
struct block_device *bdev = ERR_PTR(-ENODEV);
|
||||
fmode_t mode = FMODE_READ | FMODE_WRITE;
|
||||
sector_t nr_sects;
|
||||
|
||||
lockdep_assert_held(&pstore_blk_lock);
|
||||
|
||||
if (pstore_zone_info)
|
||||
return ERR_PTR(-EBUSY);
|
||||
|
||||
if (!blkdev[0])
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
if (holder)
|
||||
mode |= FMODE_EXCL;
|
||||
bdev = blkdev_get_by_path(blkdev, mode, holder);
|
||||
if (IS_ERR(bdev)) {
|
||||
dev_t devt;
|
||||
|
||||
devt = name_to_dev_t(blkdev);
|
||||
if (devt == 0)
|
||||
return ERR_PTR(-ENODEV);
|
||||
bdev = blkdev_get_by_dev(devt, mode, holder);
|
||||
if (IS_ERR(bdev))
|
||||
return bdev;
|
||||
}
|
||||
|
||||
nr_sects = part_nr_sects_read(bdev->bd_part);
|
||||
if (!nr_sects) {
|
||||
pr_err("not enough space for '%s'\n", blkdev);
|
||||
blkdev_put(bdev, mode);
|
||||
return ERR_PTR(-ENOSPC);
|
||||
}
|
||||
|
||||
if (info) {
|
||||
info->devt = bdev->bd_dev;
|
||||
info->nr_sects = nr_sects;
|
||||
info->start_sect = get_start_sect(bdev);
|
||||
}
|
||||
|
||||
return bdev;
|
||||
}
|
||||
|
||||
static void psblk_put_bdev(struct block_device *bdev, void *holder)
|
||||
{
|
||||
fmode_t mode = FMODE_READ | FMODE_WRITE;
|
||||
|
||||
lockdep_assert_held(&pstore_blk_lock);
|
||||
|
||||
if (!bdev)
|
||||
return;
|
||||
|
||||
if (holder)
|
||||
mode |= FMODE_EXCL;
|
||||
blkdev_put(bdev, mode);
|
||||
}
|
||||
|
||||
static ssize_t psblk_generic_blk_read(char *buf, size_t bytes, loff_t pos)
|
||||
{
|
||||
struct block_device *bdev = psblk_bdev;
|
||||
struct file file;
|
||||
struct kiocb kiocb;
|
||||
struct iov_iter iter;
|
||||
struct kvec iov = {.iov_base = buf, .iov_len = bytes};
|
||||
|
||||
if (!bdev)
|
||||
return -ENODEV;
|
||||
|
||||
memset(&file, 0, sizeof(struct file));
|
||||
file.f_mapping = bdev->bd_inode->i_mapping;
|
||||
file.f_flags = O_DSYNC | __O_SYNC | O_NOATIME;
|
||||
file.f_inode = bdev->bd_inode;
|
||||
file_ra_state_init(&file.f_ra, file.f_mapping);
|
||||
|
||||
init_sync_kiocb(&kiocb, &file);
|
||||
kiocb.ki_pos = pos;
|
||||
iov_iter_kvec(&iter, READ, &iov, 1, bytes);
|
||||
|
||||
return generic_file_read_iter(&kiocb, &iter);
|
||||
}
|
||||
|
||||
static ssize_t psblk_generic_blk_write(const char *buf, size_t bytes,
|
||||
loff_t pos)
|
||||
{
|
||||
struct block_device *bdev = psblk_bdev;
|
||||
struct iov_iter iter;
|
||||
struct kiocb kiocb;
|
||||
struct file file;
|
||||
ssize_t ret;
|
||||
struct kvec iov = {.iov_base = (void *)buf, .iov_len = bytes};
|
||||
|
||||
if (!bdev)
|
||||
return -ENODEV;
|
||||
|
||||
/* Console/Ftrace backend may handle buffer until flush dirty zones */
|
||||
if (in_interrupt() || irqs_disabled())
|
||||
return -EBUSY;
|
||||
|
||||
memset(&file, 0, sizeof(struct file));
|
||||
file.f_mapping = bdev->bd_inode->i_mapping;
|
||||
file.f_flags = O_DSYNC | __O_SYNC | O_NOATIME;
|
||||
file.f_inode = bdev->bd_inode;
|
||||
|
||||
init_sync_kiocb(&kiocb, &file);
|
||||
kiocb.ki_pos = pos;
|
||||
iov_iter_kvec(&iter, WRITE, &iov, 1, bytes);
|
||||
|
||||
inode_lock(bdev->bd_inode);
|
||||
ret = generic_write_checks(&kiocb, &iter);
|
||||
if (ret > 0)
|
||||
ret = generic_perform_write(&file, &iter, pos);
|
||||
inode_unlock(bdev->bd_inode);
|
||||
|
||||
if (likely(ret > 0)) {
|
||||
const struct file_operations f_op = {.fsync = blkdev_fsync};
|
||||
|
||||
file.f_op = &f_op;
|
||||
kiocb.ki_pos += ret;
|
||||
ret = generic_write_sync(&kiocb, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t psblk_blk_panic_write(const char *buf, size_t size,
|
||||
loff_t off)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!blkdev_panic_write)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* size and off must align to SECTOR_SIZE for block device */
|
||||
ret = blkdev_panic_write(buf, off >> SECTOR_SHIFT,
|
||||
size >> SECTOR_SHIFT);
|
||||
/* try next zone */
|
||||
if (ret == -ENOMSG)
|
||||
return ret;
|
||||
return ret ? -EIO : size;
|
||||
}
|
||||
|
||||
static int __register_pstore_blk(struct pstore_blk_info *info)
|
||||
{
|
||||
char bdev_name[BDEVNAME_SIZE];
|
||||
struct block_device *bdev;
|
||||
struct pstore_device_info dev;
|
||||
struct bdev_info binfo;
|
||||
void *holder = blkdev;
|
||||
int ret = -ENODEV;
|
||||
|
||||
lockdep_assert_held(&pstore_blk_lock);
|
||||
|
||||
/* hold bdev exclusively */
|
||||
memset(&binfo, 0, sizeof(binfo));
|
||||
bdev = psblk_get_bdev(holder, &binfo);
|
||||
if (IS_ERR(bdev)) {
|
||||
pr_err("failed to open '%s'!\n", blkdev);
|
||||
return PTR_ERR(bdev);
|
||||
}
|
||||
|
||||
/* only allow driver matching the @blkdev */
|
||||
if (!binfo.devt || (!best_effort &&
|
||||
MAJOR(binfo.devt) != info->major)) {
|
||||
pr_debug("invalid major %u (expect %u)\n",
|
||||
info->major, MAJOR(binfo.devt));
|
||||
ret = -ENODEV;
|
||||
goto err_put_bdev;
|
||||
}
|
||||
|
||||
/* psblk_bdev must be assigned before register to pstore/blk */
|
||||
psblk_bdev = bdev;
|
||||
blkdev_panic_write = info->panic_write;
|
||||
|
||||
/* Copy back block device details. */
|
||||
info->devt = binfo.devt;
|
||||
info->nr_sects = binfo.nr_sects;
|
||||
info->start_sect = binfo.start_sect;
|
||||
|
||||
memset(&dev, 0, sizeof(dev));
|
||||
dev.total_size = info->nr_sects << SECTOR_SHIFT;
|
||||
dev.flags = info->flags;
|
||||
dev.read = psblk_generic_blk_read;
|
||||
dev.write = psblk_generic_blk_write;
|
||||
dev.erase = NULL;
|
||||
dev.panic_write = info->panic_write ? psblk_blk_panic_write : NULL;
|
||||
|
||||
ret = __register_pstore_device(&dev);
|
||||
if (ret)
|
||||
goto err_put_bdev;
|
||||
|
||||
bdevname(bdev, bdev_name);
|
||||
pr_info("attached %s%s\n", bdev_name,
|
||||
info->panic_write ? "" : " (no dedicated panic_write!)");
|
||||
return 0;
|
||||
|
||||
err_put_bdev:
|
||||
psblk_bdev = NULL;
|
||||
blkdev_panic_write = NULL;
|
||||
psblk_put_bdev(bdev, holder);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* register_pstore_blk() - register block device to pstore/blk
|
||||
*
|
||||
* @info: details on the desired block device interface
|
||||
*
|
||||
* Return:
|
||||
* * 0 - OK
|
||||
* * Others - something error.
|
||||
*/
|
||||
int register_pstore_blk(struct pstore_blk_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&pstore_blk_lock);
|
||||
ret = __register_pstore_blk(info);
|
||||
mutex_unlock(&pstore_blk_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_pstore_blk);
|
||||
|
||||
static void __unregister_pstore_blk(unsigned int major)
|
||||
{
|
||||
struct pstore_device_info dev = { .read = psblk_generic_blk_read };
|
||||
void *holder = blkdev;
|
||||
|
||||
lockdep_assert_held(&pstore_blk_lock);
|
||||
if (psblk_bdev && MAJOR(psblk_bdev->bd_dev) == major) {
|
||||
__unregister_pstore_device(&dev);
|
||||
psblk_put_bdev(psblk_bdev, holder);
|
||||
blkdev_panic_write = NULL;
|
||||
psblk_bdev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister_pstore_blk() - unregister block device from pstore/blk
|
||||
*
|
||||
* @major: the major device number of device
|
||||
*/
|
||||
void unregister_pstore_blk(unsigned int major)
|
||||
{
|
||||
mutex_lock(&pstore_blk_lock);
|
||||
__unregister_pstore_blk(major);
|
||||
mutex_unlock(&pstore_blk_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unregister_pstore_blk);
|
||||
|
||||
/* get information of pstore/blk */
|
||||
int pstore_blk_get_config(struct pstore_blk_config *info)
|
||||
{
|
||||
strncpy(info->device, blkdev, 80);
|
||||
info->max_reason = max_reason;
|
||||
info->kmsg_size = check_size(kmsg_size, 4096);
|
||||
info->pmsg_size = check_size(pmsg_size, 4096);
|
||||
info->ftrace_size = check_size(ftrace_size, 4096);
|
||||
info->console_size = check_size(console_size, 4096);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pstore_blk_get_config);
|
||||
|
||||
static int __init pstore_blk_init(void)
|
||||
{
|
||||
struct pstore_blk_info info = { };
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&pstore_blk_lock);
|
||||
if (!pstore_zone_info && best_effort && blkdev[0])
|
||||
ret = __register_pstore_blk(&info);
|
||||
mutex_unlock(&pstore_blk_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
late_initcall(pstore_blk_init);
|
||||
|
||||
static void __exit pstore_blk_exit(void)
|
||||
{
|
||||
mutex_lock(&pstore_blk_lock);
|
||||
if (psblk_bdev)
|
||||
__unregister_pstore_blk(MAJOR(psblk_bdev->bd_dev));
|
||||
else {
|
||||
struct pstore_device_info dev = { };
|
||||
|
||||
if (pstore_zone_info)
|
||||
dev.read = pstore_zone_info->read;
|
||||
__unregister_pstore_device(&dev);
|
||||
}
|
||||
mutex_unlock(&pstore_blk_lock);
|
||||
}
|
||||
module_exit(pstore_blk_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("WeiXiong Liao <liaoweixiong@allwinnertech.com>");
|
||||
MODULE_AUTHOR("Kees Cook <keescook@chromium.org>");
|
||||
MODULE_DESCRIPTION("pstore backend for block devices");
|
||||
@ -1,14 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2012 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
@ -24,6 +16,7 @@
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/cache.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/barrier.h>
|
||||
#include "internal.h"
|
||||
|
||||
@ -120,27 +113,13 @@ static struct dentry *pstore_ftrace_dir;
|
||||
|
||||
void pstore_register_ftrace(void)
|
||||
{
|
||||
struct dentry *file;
|
||||
|
||||
if (!psinfo->write)
|
||||
return;
|
||||
|
||||
pstore_ftrace_dir = debugfs_create_dir("pstore", NULL);
|
||||
if (!pstore_ftrace_dir) {
|
||||
pr_err("%s: unable to create pstore directory\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("record_ftrace", 0600, pstore_ftrace_dir,
|
||||
NULL, &pstore_knob_fops);
|
||||
if (!file) {
|
||||
pr_err("%s: unable to create record_ftrace file\n", __func__);
|
||||
goto err_file;
|
||||
}
|
||||
|
||||
return;
|
||||
err_file:
|
||||
debugfs_remove(pstore_ftrace_dir);
|
||||
debugfs_create_file("record_ftrace", 0600, pstore_ftrace_dir, NULL,
|
||||
&pstore_knob_fops);
|
||||
}
|
||||
|
||||
void pstore_unregister_ftrace(void)
|
||||
@ -148,9 +127,62 @@ void pstore_unregister_ftrace(void)
|
||||
mutex_lock(&pstore_ftrace_lock);
|
||||
if (pstore_ftrace_enabled) {
|
||||
unregister_ftrace_function(&pstore_ftrace_ops);
|
||||
pstore_ftrace_enabled = 0;
|
||||
pstore_ftrace_enabled = 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);
|
||||
|
||||
@ -1,20 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Persistent Storage - ramfs parts.
|
||||
*
|
||||
* Copyright (C) 2010 Intel Corporation <tony.luck@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
@ -34,7 +22,6 @@
|
||||
#include <linux/magic.h>
|
||||
#include <linux/pstore.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
@ -46,11 +33,15 @@
|
||||
|
||||
#define PSTORE_NAMELEN 64
|
||||
|
||||
static DEFINE_SPINLOCK(allpstore_lock);
|
||||
static LIST_HEAD(allpstore);
|
||||
static DEFINE_MUTEX(records_list_lock);
|
||||
static LIST_HEAD(records_list);
|
||||
|
||||
static DEFINE_MUTEX(pstore_sb_lock);
|
||||
static struct super_block *pstore_sb;
|
||||
|
||||
struct pstore_private {
|
||||
struct list_head list;
|
||||
struct dentry *dentry;
|
||||
struct pstore_record *record;
|
||||
size_t total_size;
|
||||
};
|
||||
@ -123,7 +114,7 @@ static int pstore_ftrace_seq_show(struct seq_file *s, void *v)
|
||||
|
||||
rec = (struct pstore_ftrace_record *)(ps->record->buf + data->off);
|
||||
|
||||
seq_printf(s, "CPU:%d ts:%llu %08lx %08lx %pf <- %pF\n",
|
||||
seq_printf(s, "CPU:%d ts:%llu %08lx %08lx %ps <- %pS\n",
|
||||
pstore_ftrace_decode_cpu(rec),
|
||||
pstore_ftrace_read_timestamp(rec),
|
||||
rec->ip, rec->parent_ip, (void *)rec->ip,
|
||||
@ -145,39 +136,13 @@ static ssize_t pstore_file_read(struct file *file, char __user *userbuf,
|
||||
struct seq_file *sf = file->private_data;
|
||||
struct pstore_private *ps = sf->private;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
size_t size = 0;
|
||||
struct pstore_record *record = ps->record;
|
||||
struct ramoops_context *cxt = record->psi->data;
|
||||
struct persistent_ram_zone *prz;
|
||||
struct persistent_ram_buffer *buffer;
|
||||
char *log_tmp;
|
||||
size_t size, start, n;
|
||||
|
||||
if (ps->record->type == PSTORE_TYPE_BOOT_LOG) {
|
||||
|
||||
if (!cxt)
|
||||
return count;
|
||||
|
||||
prz = cxt->boot_przs[record->id];
|
||||
|
||||
if (!prz)
|
||||
return count;
|
||||
|
||||
buffer = prz->buffer;
|
||||
if (!buffer)
|
||||
return count;
|
||||
|
||||
size = atomic_read(&buffer->size);
|
||||
start = atomic_read(&buffer->start);
|
||||
|
||||
log_tmp = kmalloc(size, GFP_KERNEL);
|
||||
if (!log_tmp)
|
||||
return count;
|
||||
memcpy_fromio(log_tmp, &buffer->data[start], size - start);
|
||||
memcpy_fromio(log_tmp + size - start, &buffer->data[0], start);
|
||||
|
||||
n = simple_read_from_buffer(userbuf, count, ppos, log_tmp, size);
|
||||
kfree(log_tmp);
|
||||
return n;
|
||||
if (record->type == PSTORE_TYPE_BOOT_LOG) {
|
||||
size = ramoops_pstore_read_for_boot_log(ps->record);
|
||||
size = simple_read_from_buffer(userbuf, count, ppos, record->buf, size);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
if (ps->record->type == PSTORE_TYPE_FTRACE)
|
||||
@ -230,10 +195,22 @@ static int pstore_unlink(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
struct pstore_private *p = d_inode(dentry)->i_private;
|
||||
struct pstore_record *record = p->record;
|
||||
int rc = 0;
|
||||
|
||||
if (!record->psi->erase)
|
||||
return -EPERM;
|
||||
|
||||
/* Make sure we can't race while removing this file. */
|
||||
mutex_lock(&records_list_lock);
|
||||
if (!list_empty(&p->list))
|
||||
list_del_init(&p->list);
|
||||
else
|
||||
rc = -ENOENT;
|
||||
p->dentry = NULL;
|
||||
mutex_unlock(&records_list_lock);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
mutex_lock(&record->psi->read_mutex);
|
||||
record->psi->erase(record);
|
||||
mutex_unlock(&record->psi->read_mutex);
|
||||
@ -244,16 +221,10 @@ static int pstore_unlink(struct inode *dir, struct dentry *dentry)
|
||||
static void pstore_evict_inode(struct inode *inode)
|
||||
{
|
||||
struct pstore_private *p = inode->i_private;
|
||||
unsigned long flags;
|
||||
|
||||
clear_inode(inode);
|
||||
if (p) {
|
||||
spin_lock_irqsave(&allpstore_lock, flags);
|
||||
list_del(&p->list);
|
||||
spin_unlock_irqrestore(&allpstore_lock, flags);
|
||||
free_pstore_private(p);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct inode_operations pstore_dir_inode_operations = {
|
||||
.lookup = simple_lookup,
|
||||
@ -330,11 +301,54 @@ static const struct super_operations pstore_ops = {
|
||||
.show_options = pstore_show_options,
|
||||
};
|
||||
|
||||
static struct super_block *pstore_sb;
|
||||
|
||||
bool pstore_is_mounted(void)
|
||||
static struct dentry *psinfo_lock_root(void)
|
||||
{
|
||||
return pstore_sb != NULL;
|
||||
struct dentry *root;
|
||||
|
||||
mutex_lock(&pstore_sb_lock);
|
||||
/*
|
||||
* Having no backend is fine -- no records appear.
|
||||
* Not being mounted is fine -- nothing to do.
|
||||
*/
|
||||
if (!psinfo || !pstore_sb) {
|
||||
mutex_unlock(&pstore_sb_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
root = pstore_sb->s_root;
|
||||
inode_lock(d_inode(root));
|
||||
mutex_unlock(&pstore_sb_lock);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
int pstore_put_backend_records(struct pstore_info *psi)
|
||||
{
|
||||
struct pstore_private *pos, *tmp;
|
||||
struct dentry *root;
|
||||
int rc = 0;
|
||||
|
||||
root = psinfo_lock_root();
|
||||
if (!root)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&records_list_lock);
|
||||
list_for_each_entry_safe(pos, tmp, &records_list, list) {
|
||||
if (pos->record->psi == psi) {
|
||||
list_del_init(&pos->list);
|
||||
rc = simple_unlink(d_inode(root), pos->dentry);
|
||||
if (WARN_ON(rc))
|
||||
break;
|
||||
d_drop(pos->dentry);
|
||||
dput(pos->dentry);
|
||||
pos->dentry = NULL;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&records_list_lock);
|
||||
|
||||
inode_unlock(d_inode(root));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -349,23 +363,20 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
|
||||
int rc = 0;
|
||||
char name[PSTORE_NAMELEN];
|
||||
struct pstore_private *private, *pos;
|
||||
unsigned long flags;
|
||||
size_t size = record->size + record->ecc_notice_size;
|
||||
|
||||
WARN_ON(!inode_is_locked(d_inode(root)));
|
||||
if (WARN_ON(!inode_is_locked(d_inode(root))))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&allpstore_lock, flags);
|
||||
list_for_each_entry(pos, &allpstore, list) {
|
||||
rc = -EEXIST;
|
||||
/* Skip records that are already present in the filesystem. */
|
||||
mutex_lock(&records_list_lock);
|
||||
list_for_each_entry(pos, &records_list, list) {
|
||||
if (pos->record->type == record->type &&
|
||||
pos->record->id == record->id &&
|
||||
pos->record->psi == record->psi) {
|
||||
rc = -EEXIST;
|
||||
break;
|
||||
pos->record->psi == record->psi)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&allpstore_lock, flags);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = -ENOMEM;
|
||||
inode = pstore_get_inode(root->d_sb);
|
||||
@ -373,59 +384,10 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
|
||||
goto fail;
|
||||
inode->i_mode = S_IFREG | 0444;
|
||||
inode->i_fop = &pstore_file_operations;
|
||||
|
||||
switch (record->type) {
|
||||
case PSTORE_TYPE_DMESG:
|
||||
scnprintf(name, sizeof(name), "dmesg-%s-%llu%s",
|
||||
scnprintf(name, sizeof(name), "%s-%s-%llu%s",
|
||||
pstore_type_to_name(record->type),
|
||||
record->psi->name, record->id,
|
||||
record->compressed ? ".enc.z" : "");
|
||||
break;
|
||||
case PSTORE_TYPE_CONSOLE:
|
||||
scnprintf(name, sizeof(name), "console-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
case PSTORE_TYPE_FTRACE:
|
||||
scnprintf(name, sizeof(name), "ftrace-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
case PSTORE_TYPE_MCE:
|
||||
scnprintf(name, sizeof(name), "mce-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
case PSTORE_TYPE_PPC_RTAS:
|
||||
scnprintf(name, sizeof(name), "rtas-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
case PSTORE_TYPE_PPC_OF:
|
||||
scnprintf(name, sizeof(name), "powerpc-ofw-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
case PSTORE_TYPE_PPC_COMMON:
|
||||
scnprintf(name, sizeof(name), "powerpc-common-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
case PSTORE_TYPE_PMSG:
|
||||
scnprintf(name, sizeof(name), "pmsg-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
case PSTORE_TYPE_PPC_OPAL:
|
||||
scnprintf(name, sizeof(name), "powerpc-opal-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
case PSTORE_TYPE_BOOT_LOG:
|
||||
scnprintf(name, sizeof(name), "boot-log-%llu", record->id);
|
||||
break;
|
||||
#endif
|
||||
case PSTORE_TYPE_UNKNOWN:
|
||||
scnprintf(name, sizeof(name), "unknown-%s-%llu",
|
||||
record->psi->name, record->id);
|
||||
break;
|
||||
default:
|
||||
scnprintf(name, sizeof(name), "type%d-%s-%llu",
|
||||
record->type, record->psi->name, record->id);
|
||||
break;
|
||||
}
|
||||
|
||||
private = kzalloc(sizeof(*private), GFP_KERNEL);
|
||||
if (!private)
|
||||
@ -435,6 +397,7 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
|
||||
if (!dentry)
|
||||
goto fail_private;
|
||||
|
||||
private->dentry = dentry;
|
||||
private->record = record;
|
||||
inode->i_size = private->total_size = size;
|
||||
inode->i_private = private;
|
||||
@ -444,9 +407,8 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
|
||||
|
||||
d_add(dentry, inode);
|
||||
|
||||
spin_lock_irqsave(&allpstore_lock, flags);
|
||||
list_add(&private->list, &allpstore);
|
||||
spin_unlock_irqrestore(&allpstore_lock, flags);
|
||||
list_add(&private->list, &records_list);
|
||||
mutex_unlock(&records_list_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
@ -454,8 +416,8 @@ fail_private:
|
||||
free_pstore_private(private);
|
||||
fail_inode:
|
||||
iput(inode);
|
||||
|
||||
fail:
|
||||
mutex_unlock(&records_list_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -467,16 +429,13 @@ fail:
|
||||
*/
|
||||
void pstore_get_records(int quiet)
|
||||
{
|
||||
struct pstore_info *psi = psinfo;
|
||||
struct dentry *root;
|
||||
|
||||
if (!psi || !pstore_sb)
|
||||
root = psinfo_lock_root();
|
||||
if (!root)
|
||||
return;
|
||||
|
||||
root = pstore_sb->s_root;
|
||||
|
||||
inode_lock(d_inode(root));
|
||||
pstore_get_backend_records(psi, root, quiet);
|
||||
pstore_get_backend_records(psinfo, root, quiet);
|
||||
inode_unlock(d_inode(root));
|
||||
}
|
||||
|
||||
@ -484,8 +443,6 @@ static int pstore_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct inode *inode;
|
||||
|
||||
pstore_sb = sb;
|
||||
|
||||
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
||||
sb->s_blocksize = PAGE_SIZE;
|
||||
sb->s_blocksize_bits = PAGE_SHIFT;
|
||||
@ -506,6 +463,10 @@ static int pstore_fill_super(struct super_block *sb, void *data, int silent)
|
||||
if (!sb->s_root)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&pstore_sb_lock);
|
||||
pstore_sb = sb;
|
||||
mutex_unlock(&pstore_sb_lock);
|
||||
|
||||
pstore_get_records(0);
|
||||
|
||||
return 0;
|
||||
@ -519,8 +480,17 @@ static struct dentry *pstore_mount(struct file_system_type *fs_type,
|
||||
|
||||
static void pstore_kill_sb(struct super_block *sb)
|
||||
{
|
||||
mutex_lock(&pstore_sb_lock);
|
||||
WARN_ON(pstore_sb && pstore_sb != sb);
|
||||
|
||||
kill_litter_super(sb);
|
||||
pstore_sb = NULL;
|
||||
|
||||
mutex_lock(&records_list_lock);
|
||||
INIT_LIST_HEAD(&records_list);
|
||||
mutex_unlock(&records_list_lock);
|
||||
|
||||
mutex_unlock(&pstore_sb_lock);
|
||||
}
|
||||
|
||||
static struct file_system_type pstore_fs_type = {
|
||||
|
||||
@ -12,9 +12,18 @@ extern unsigned long kmsg_bytes;
|
||||
#ifdef CONFIG_PSTORE_FTRACE
|
||||
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 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
|
||||
@ -31,9 +40,9 @@ extern void pstore_set_kmsg_bytes(int);
|
||||
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 bool pstore_is_mounted(void);
|
||||
extern void pstore_record_init(struct pstore_record *record,
|
||||
struct pstore_info *psi);
|
||||
|
||||
|
||||
@ -1,21 +1,9 @@
|
||||
// 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>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "pstore: " fmt
|
||||
@ -56,9 +44,25 @@ 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 is not safe, it may lead to further "
|
||||
"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",
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
"boot-log",
|
||||
#endif
|
||||
};
|
||||
|
||||
static int pstore_new_entry;
|
||||
|
||||
static void pstore_timefunc(struct timer_list *);
|
||||
@ -68,19 +72,25 @@ static void pstore_dowork(struct work_struct *);
|
||||
static DECLARE_WORK(pstore_work, pstore_dowork);
|
||||
|
||||
/*
|
||||
* pstore_lock just protects "psinfo" during
|
||||
* calls to pstore_register()
|
||||
* psinfo_lock protects "psinfo" during calls to
|
||||
* pstore_register(), pstore_unregister(), and
|
||||
* the filesystem mount/unmount routines.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(pstore_lock);
|
||||
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");
|
||||
|
||||
static char *compress =
|
||||
#ifdef CONFIG_PSTORE_COMPRESS_DEFAULT
|
||||
CONFIG_PSTORE_COMPRESS_DEFAULT;
|
||||
#else
|
||||
NULL;
|
||||
#endif
|
||||
module_param(compress, charp, 0444);
|
||||
MODULE_PARM_DESC(compress, "compression to use");
|
||||
|
||||
/* Compression parameters */
|
||||
static struct crypto_comp *tfm;
|
||||
@ -104,24 +114,36 @@ void pstore_set_kmsg_bytes(int bytes)
|
||||
/* Tag each group of saved records with a sequence number */
|
||||
static int oopscount;
|
||||
|
||||
static const char *get_reason_str(enum kmsg_dump_reason reason)
|
||||
const char *pstore_type_to_name(enum pstore_type_id type)
|
||||
{
|
||||
switch (reason) {
|
||||
case KMSG_DUMP_PANIC:
|
||||
return "Panic";
|
||||
case KMSG_DUMP_OOPS:
|
||||
return "Oops";
|
||||
case KMSG_DUMP_EMERG:
|
||||
return "Emergency";
|
||||
case KMSG_DUMP_RESTART:
|
||||
return "Restart";
|
||||
case KMSG_DUMP_HALT:
|
||||
return "Halt";
|
||||
case KMSG_DUMP_POWEROFF:
|
||||
return "Poweroff";
|
||||
default:
|
||||
return "Unknown";
|
||||
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));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -262,20 +284,6 @@ static int pstore_compress(const void *in, void *out,
|
||||
return outlen;
|
||||
}
|
||||
|
||||
static int pstore_decompress(void *in, void *out,
|
||||
unsigned int inlen, unsigned int outlen)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = crypto_comp_decompress(tfm, in, inlen, out, &outlen);
|
||||
if (ret) {
|
||||
pr_err("crypto_comp_decompress failed, ret = %d!\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
static void allocate_buf_for_compression(void)
|
||||
{
|
||||
struct crypto_comp *ctx;
|
||||
@ -322,7 +330,7 @@ static void allocate_buf_for_compression(void)
|
||||
big_oops_buf_sz = size;
|
||||
big_oops_buf = buf;
|
||||
|
||||
pr_info("Using compression: %s\n", zbackend->name);
|
||||
pr_info("Using crash dump compression: %s\n", zbackend->name);
|
||||
}
|
||||
|
||||
static void free_buf_for_compression(void)
|
||||
@ -374,9 +382,8 @@ void pstore_record_init(struct pstore_record *record,
|
||||
}
|
||||
|
||||
/*
|
||||
* callback from kmsg_dump. (s2,l2) has the most recently
|
||||
* written bytes, older bytes are in (s1,l1). Save as much
|
||||
* as we can from the end of the buffer.
|
||||
* 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,
|
||||
enum kmsg_dump_reason reason)
|
||||
@ -386,7 +393,7 @@ static void pstore_dump(struct kmsg_dumper *dumper,
|
||||
unsigned int part = 1;
|
||||
int ret;
|
||||
|
||||
why = get_reason_str(reason);
|
||||
why = kmsg_dump_reason_str(reason);
|
||||
|
||||
if (down_trylock(&psinfo->buf_lock)) {
|
||||
/* Failed to acquire lock: give up if we cannot wait. */
|
||||
@ -452,8 +459,10 @@ static void pstore_dump(struct kmsg_dumper *dumper,
|
||||
}
|
||||
|
||||
ret = psinfo->write(&record);
|
||||
if (ret == 0 && reason == KMSG_DUMP_OOPS && pstore_is_mounted())
|
||||
if (ret == 0 && reason == KMSG_DUMP_OOPS) {
|
||||
pstore_new_entry = 1;
|
||||
pstore_timer_kick();
|
||||
}
|
||||
|
||||
total += record.size;
|
||||
part++;
|
||||
@ -484,6 +493,9 @@ 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;
|
||||
|
||||
@ -493,18 +505,20 @@ static void pstore_console_write(struct console *con, const char *s, unsigned c)
|
||||
}
|
||||
|
||||
static struct console pstore_console = {
|
||||
.name = "pstore",
|
||||
.write = pstore_console_write,
|
||||
#ifdef CON_PSTORE
|
||||
.flags = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME | CON_PSTORE,
|
||||
#else
|
||||
.flags = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME,
|
||||
#endif
|
||||
.index = -1,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -549,8 +563,6 @@ out:
|
||||
*/
|
||||
int pstore_register(struct pstore_info *psi)
|
||||
{
|
||||
struct module *owner = psi->owner;
|
||||
|
||||
if (backend && strcmp(backend, psi->name)) {
|
||||
pr_warn("ignoring unexpected backend '%s'\n", psi->name);
|
||||
return -EPERM;
|
||||
@ -570,11 +582,11 @@ int pstore_register(struct pstore_info *psi)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spin_lock(&pstore_lock);
|
||||
mutex_lock(&psinfo_lock);
|
||||
if (psinfo) {
|
||||
pr_warn("backend '%s' already loaded: ignoring '%s'\n",
|
||||
psinfo->name, psi->name);
|
||||
spin_unlock(&pstore_lock);
|
||||
mutex_unlock(&psinfo_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
@ -583,21 +595,16 @@ int pstore_register(struct pstore_info *psi)
|
||||
psinfo = psi;
|
||||
mutex_init(&psinfo->read_mutex);
|
||||
sema_init(&psinfo->buf_lock, 1);
|
||||
spin_unlock(&pstore_lock);
|
||||
|
||||
if (owner && !try_module_get(owner)) {
|
||||
psinfo = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (psi->flags & PSTORE_FLAGS_DMESG)
|
||||
allocate_buf_for_compression();
|
||||
|
||||
if (pstore_is_mounted())
|
||||
pstore_get_records(0);
|
||||
|
||||
if (psi->flags & PSTORE_FLAGS_DMESG)
|
||||
if (psi->flags & PSTORE_FLAGS_DMESG) {
|
||||
pstore_dumper.max_reason = psinfo->max_reason;
|
||||
pstore_register_kmsg();
|
||||
}
|
||||
if (psi->flags & PSTORE_FLAGS_CONSOLE)
|
||||
pstore_register_console();
|
||||
if (psi->flags & PSTORE_FLAGS_FTRACE)
|
||||
@ -606,33 +613,36 @@ int pstore_register(struct pstore_info *psi)
|
||||
pstore_register_pmsg();
|
||||
|
||||
/* Start watching for new records, if desired. */
|
||||
if (pstore_update_ms >= 0) {
|
||||
pstore_timer.expires = jiffies +
|
||||
msecs_to_jiffies(pstore_update_ms);
|
||||
add_timer(&pstore_timer);
|
||||
}
|
||||
pstore_timer_kick();
|
||||
|
||||
/*
|
||||
* Update the module parameter backend, so it is visible
|
||||
* through /sys/module/pstore/parameters/backend
|
||||
*/
|
||||
backend = psi->name;
|
||||
backend = kstrdup(psi->name, GFP_KERNEL);
|
||||
|
||||
pr_info("Registered %s as persistent store backend\n", psi->name);
|
||||
|
||||
module_put(owner);
|
||||
|
||||
mutex_unlock(&psinfo_lock);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pstore_register);
|
||||
|
||||
void pstore_unregister(struct pstore_info *psi)
|
||||
{
|
||||
/* Stop timer and make sure all work has finished. */
|
||||
pstore_update_ms = -1;
|
||||
del_timer_sync(&pstore_timer);
|
||||
flush_work(&pstore_work);
|
||||
/* 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)
|
||||
@ -642,17 +652,27 @@ void pstore_unregister(struct pstore_info *psi)
|
||||
if (psi->flags & PSTORE_FLAGS_DMESG)
|
||||
pstore_unregister_kmsg();
|
||||
|
||||
/* Stop timer and make sure all work has finished. */
|
||||
del_timer_sync(&pstore_timer);
|
||||
flush_work(&pstore_work);
|
||||
|
||||
/* Remove all backend records from filesystem tree. */
|
||||
pstore_put_backend_records(psi);
|
||||
|
||||
free_buf_for_compression();
|
||||
|
||||
psinfo = NULL;
|
||||
kfree(backend);
|
||||
backend = NULL;
|
||||
mutex_unlock(&psinfo_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pstore_unregister);
|
||||
|
||||
static void decompress_record(struct pstore_record *record)
|
||||
{
|
||||
int ret;
|
||||
int unzipped_len;
|
||||
char *decompressed;
|
||||
char *unzipped, *workspace;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_PSTORE_COMPRESS) || !record->compressed)
|
||||
return;
|
||||
@ -663,35 +683,42 @@ static void decompress_record(struct pstore_record *record)
|
||||
return;
|
||||
}
|
||||
|
||||
/* No compression method has created the common buffer. */
|
||||
/* Missing compression buffer means compression was not initialized. */
|
||||
if (!big_oops_buf) {
|
||||
pr_warn("no decompression buffer allocated\n");
|
||||
pr_warn("no decompression method initialized!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
unzipped_len = pstore_decompress(record->buf, big_oops_buf,
|
||||
record->size, big_oops_buf_sz);
|
||||
if (unzipped_len <= 0) {
|
||||
pr_err("decompression failed: %d\n", unzipped_len);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build new buffer for decompressed contents. */
|
||||
decompressed = kmalloc(unzipped_len + record->ecc_notice_size,
|
||||
/* Allocate enough space to hold max decompression and ECC. */
|
||||
unzipped_len = big_oops_buf_sz;
|
||||
workspace = kmalloc(unzipped_len + record->ecc_notice_size,
|
||||
GFP_KERNEL);
|
||||
if (!decompressed) {
|
||||
pr_err("decompression ran out of memory\n");
|
||||
if (!workspace)
|
||||
return;
|
||||
|
||||
/* After decompression "unzipped_len" is almost certainly smaller. */
|
||||
ret = crypto_comp_decompress(tfm, record->buf, record->size,
|
||||
workspace, &unzipped_len);
|
||||
if (ret) {
|
||||
pr_err("crypto_comp_decompress failed, ret = %d!\n", ret);
|
||||
kfree(workspace);
|
||||
return;
|
||||
}
|
||||
memcpy(decompressed, big_oops_buf, unzipped_len);
|
||||
|
||||
/* Append ECC notice to decompressed buffer. */
|
||||
memcpy(decompressed + unzipped_len, record->buf + record->size,
|
||||
memcpy(workspace + unzipped_len, record->buf + record->size,
|
||||
record->ecc_notice_size);
|
||||
|
||||
/* Swap out compresed contents with decompressed contents. */
|
||||
/* Copy decompressed contents into an minimum-sized allocation. */
|
||||
unzipped = kmemdup(workspace, unzipped_len + record->ecc_notice_size,
|
||||
GFP_KERNEL);
|
||||
kfree(workspace);
|
||||
if (!unzipped)
|
||||
return;
|
||||
|
||||
/* Swap out compressed contents with decompressed contents. */
|
||||
kfree(record->buf);
|
||||
record->buf = decompressed;
|
||||
record->buf = unzipped;
|
||||
record->size = unzipped_len;
|
||||
record->compressed = false;
|
||||
}
|
||||
@ -774,12 +801,10 @@ static void pstore_timefunc(struct timer_list *unused)
|
||||
schedule_work(&pstore_work);
|
||||
}
|
||||
|
||||
if (pstore_update_ms >= 0)
|
||||
mod_timer(&pstore_timer,
|
||||
jiffies + msecs_to_jiffies(pstore_update_ms));
|
||||
pstore_timer_kick();
|
||||
}
|
||||
|
||||
void __init pstore_choose_compression(void)
|
||||
static void __init pstore_choose_compression(void)
|
||||
{
|
||||
const struct pstore_zbackend *step;
|
||||
|
||||
@ -821,11 +846,5 @@ static void __exit pstore_exit(void)
|
||||
}
|
||||
module_exit(pstore_exit)
|
||||
|
||||
module_param(compress, charp, 0444);
|
||||
MODULE_PARM_DESC(compress, "Pstore compression to use");
|
||||
|
||||
module_param(backend, charp, 0444);
|
||||
MODULE_PARM_DESC(backend, "Pstore backend to use");
|
||||
|
||||
MODULE_AUTHOR("Tony Luck <tony.luck@intel.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@ -1,14 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright 2014 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/cdev.h>
|
||||
@ -33,7 +25,7 @@ static ssize_t write_pmsg(struct file *file, const char __user *buf,
|
||||
record.size = count;
|
||||
|
||||
/* check outside lock, page in any data. write_user also checks */
|
||||
if (!access_ok(VERIFY_READ, buf, count))
|
||||
if (!access_ok(buf, count))
|
||||
return -EFAULT;
|
||||
|
||||
mutex_lock(&pmsg_lock);
|
||||
|
||||
469
fs/pstore/ram.c
469
fs/pstore/ram.c
@ -1,23 +1,9 @@
|
||||
// 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>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
@ -35,6 +21,12 @@
|
||||
#include <linux/pstore_ram.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_reserved_mem.h>
|
||||
#include "internal.h"
|
||||
|
||||
#if IS_REACHABLE(CONFIG_ROCKCHIP_MINIDUMP)
|
||||
#include <soc/rockchip/rk_minidump.h>
|
||||
#endif
|
||||
|
||||
#define RAMOOPS_KERNMSG_HDR "===="
|
||||
#define MIN_MEM_SIZE 4096UL
|
||||
@ -67,24 +59,63 @@ 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, 0600);
|
||||
module_param(mem_type, uint, 0400);
|
||||
MODULE_PARM_DESC(mem_type,
|
||||
"set to 1 to try to use unbuffered memory (default 0)");
|
||||
"memory type: 0=write-combined (default), 1=unbuffered, 2=cached");
|
||||
|
||||
static int dump_oops = 1;
|
||||
module_param(dump_oops, int, 0600);
|
||||
MODULE_PARM_DESC(dump_oops,
|
||||
"set to 1 to dump oopses, 0 to only dump panics (default 1)");
|
||||
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, 0600);
|
||||
module_param_named(ecc, ramoops_ecc, int, 0400);
|
||||
MODULE_PARM_DESC(ramoops_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 */
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
struct persistent_ram_zone **boot_przs; /* BOOT log zones */
|
||||
#endif
|
||||
phys_addr_t phys_addr;
|
||||
unsigned long size;
|
||||
unsigned int memtype;
|
||||
size_t record_size;
|
||||
size_t console_size;
|
||||
size_t ftrace_size;
|
||||
size_t pmsg_size;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
size_t boot_log_size;
|
||||
#endif
|
||||
u32 flags;
|
||||
struct persistent_ram_ecc_info ecc_info;
|
||||
unsigned int max_dump_cnt;
|
||||
unsigned int dump_write_cnt;
|
||||
/* _read_cnt need clear on ramoops_pstore_open */
|
||||
unsigned int dump_read_cnt;
|
||||
unsigned int console_read_cnt;
|
||||
unsigned int max_ftrace_cnt;
|
||||
unsigned int ftrace_read_cnt;
|
||||
unsigned int pmsg_read_cnt;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
unsigned int boot_log_read_cnt;
|
||||
unsigned int max_boot_log_cnt;
|
||||
#endif
|
||||
struct pstore_info pstore;
|
||||
};
|
||||
|
||||
static struct platform_device *dummy;
|
||||
static struct ramoops_platform_data *dummy_data;
|
||||
|
||||
static int ramoops_pstore_open(struct pstore_info *psi)
|
||||
{
|
||||
@ -98,31 +129,28 @@ static int ramoops_pstore_open(struct pstore_info *psi)
|
||||
}
|
||||
|
||||
static struct persistent_ram_zone *
|
||||
ramoops_get_next_prz(struct persistent_ram_zone *przs[], uint *c, uint max,
|
||||
u64 *id,
|
||||
enum pstore_type_id *typep, enum pstore_type_id type,
|
||||
bool update)
|
||||
ramoops_get_next_prz(struct persistent_ram_zone *przs[], int id,
|
||||
struct pstore_record *record)
|
||||
{
|
||||
struct persistent_ram_zone *prz;
|
||||
int i = (*c)++;
|
||||
|
||||
/* Give up if we never existed or have hit the end. */
|
||||
if (!przs || i >= max)
|
||||
if (!przs)
|
||||
return NULL;
|
||||
|
||||
prz = przs[i];
|
||||
prz = przs[id];
|
||||
if (!prz)
|
||||
return NULL;
|
||||
|
||||
/* Update old/shadowed buffer. */
|
||||
if (update)
|
||||
if (prz->type == PSTORE_TYPE_DMESG)
|
||||
persistent_ram_save_old(prz);
|
||||
|
||||
if (!persistent_ram_old_size(prz))
|
||||
return NULL;
|
||||
|
||||
*typep = type;
|
||||
*id = i;
|
||||
record->type = prz->type;
|
||||
record->id = id;
|
||||
|
||||
return prz;
|
||||
}
|
||||
@ -160,57 +188,27 @@ static bool prz_ok(struct persistent_ram_zone *prz)
|
||||
persistent_ram_ecc_string(prz, NULL, 0));
|
||||
}
|
||||
|
||||
static ssize_t ftrace_log_combine(struct persistent_ram_zone *dest,
|
||||
struct persistent_ram_zone *src)
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
ssize_t ramoops_pstore_read_for_boot_log(struct pstore_record *record)
|
||||
{
|
||||
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->old_log_size % record_size;
|
||||
dest_size = dest->old_log_size - dest_off;
|
||||
|
||||
src_off = src->old_log_size % record_size;
|
||||
src_size = src->old_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->old_log + dest_off);
|
||||
srec = (struct pstore_ftrace_record *)(src->old_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->old_log);
|
||||
dest->old_log = merged_buf;
|
||||
dest->old_log_size = total;
|
||||
struct ramoops_context *cxt = record->psi->data;
|
||||
struct persistent_ram_zone *prz;
|
||||
|
||||
if (!cxt)
|
||||
return 0;
|
||||
|
||||
prz = cxt->boot_przs[record->id];
|
||||
|
||||
if (!prz)
|
||||
return 0;
|
||||
|
||||
persistent_ram_free_old(prz);
|
||||
persistent_ram_save_old(prz);
|
||||
record->buf = prz->old_log;
|
||||
record->size = prz->old_log_size;
|
||||
return record->size;
|
||||
}
|
||||
#endif
|
||||
|
||||
static ssize_t ramoops_pstore_read(struct pstore_record *record)
|
||||
{
|
||||
@ -231,10 +229,8 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
|
||||
|
||||
/* Find the next valid persistent_ram_zone for DMESG */
|
||||
while (cxt->dump_read_cnt < cxt->max_dump_cnt && !prz) {
|
||||
prz = ramoops_get_next_prz(cxt->dprzs, &cxt->dump_read_cnt,
|
||||
cxt->max_dump_cnt, &record->id,
|
||||
&record->type,
|
||||
PSTORE_TYPE_DMESG, 1);
|
||||
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),
|
||||
@ -248,22 +244,18 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
|
||||
}
|
||||
}
|
||||
|
||||
if (!prz_ok(prz))
|
||||
prz = ramoops_get_next_prz(&cxt->cprz, &cxt->console_read_cnt,
|
||||
1, &record->id, &record->type,
|
||||
PSTORE_TYPE_CONSOLE, 0);
|
||||
if (!prz_ok(prz) && !cxt->console_read_cnt++)
|
||||
prz = ramoops_get_next_prz(&cxt->cprz, 0 /* single */, record);
|
||||
|
||||
if (!prz_ok(prz))
|
||||
prz = ramoops_get_next_prz(&cxt->mprz, &cxt->pmsg_read_cnt,
|
||||
1, &record->id, &record->type,
|
||||
PSTORE_TYPE_PMSG, 0);
|
||||
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)) {
|
||||
prz = ramoops_get_next_prz(cxt->fprzs,
|
||||
&cxt->ftrace_read_cnt, 1, &record->id,
|
||||
&record->type, PSTORE_TYPE_FTRACE, 0);
|
||||
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
|
||||
@ -280,11 +272,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
|
||||
|
||||
while (cxt->ftrace_read_cnt < cxt->max_ftrace_cnt) {
|
||||
prz_next = ramoops_get_next_prz(cxt->fprzs,
|
||||
&cxt->ftrace_read_cnt,
|
||||
cxt->max_ftrace_cnt,
|
||||
&record->id,
|
||||
&record->type,
|
||||
PSTORE_TYPE_FTRACE, 0);
|
||||
cxt->ftrace_read_cnt++, record);
|
||||
|
||||
if (!prz_ok(prz_next))
|
||||
continue;
|
||||
@ -293,7 +281,12 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
|
||||
tmp_prz->corrected_bytes +=
|
||||
prz_next->corrected_bytes;
|
||||
tmp_prz->bad_blocks += prz_next->bad_blocks;
|
||||
size = ftrace_log_combine(tmp_prz, prz_next);
|
||||
|
||||
size = pstore_ftrace_combine_log(
|
||||
&tmp_prz->old_log,
|
||||
&tmp_prz->old_log_size,
|
||||
prz_next->old_log,
|
||||
prz_next->old_log_size);
|
||||
if (size)
|
||||
goto out;
|
||||
}
|
||||
@ -304,10 +297,7 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
if (!prz_ok(prz)) {
|
||||
while (cxt->boot_log_read_cnt < cxt->max_boot_log_cnt && !prz) {
|
||||
prz = ramoops_get_next_prz(cxt->boot_przs, &cxt->boot_log_read_cnt,
|
||||
cxt->max_boot_log_cnt, &record->id,
|
||||
&record->type,
|
||||
PSTORE_TYPE_BOOT_LOG, 0);
|
||||
prz = ramoops_get_next_prz(cxt->boot_przs, cxt->boot_log_read_cnt++, record);
|
||||
if (!prz_ok(prz))
|
||||
continue;
|
||||
}
|
||||
@ -319,12 +309,15 @@ static ssize_t ramoops_pstore_read(struct pstore_record *record)
|
||||
goto out;
|
||||
}
|
||||
|
||||
size = persistent_ram_old_size(prz) - header_length;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
/* don't copy boot log */
|
||||
if (record->type == PSTORE_TYPE_BOOT_LOG)
|
||||
goto out;
|
||||
if (record->type == PSTORE_TYPE_BOOT_LOG) {
|
||||
persistent_ram_free_old(prz);
|
||||
persistent_ram_save_old(prz);
|
||||
}
|
||||
#endif
|
||||
|
||||
size = persistent_ram_old_size(prz) - header_length;
|
||||
|
||||
/* ECC correction notice */
|
||||
record->ecc_notice_size = persistent_ram_ecc_string(prz, NULL, 0);
|
||||
|
||||
@ -352,17 +345,15 @@ out:
|
||||
static size_t ramoops_write_kmsg_hdr(struct persistent_ram_zone *prz,
|
||||
struct pstore_record *record)
|
||||
{
|
||||
char *hdr;
|
||||
char hdr[36]; /* "===="(4), %lld(20), "."(1), %06lu(6), "-%c\n"(3) */
|
||||
size_t len;
|
||||
|
||||
hdr = kasprintf(GFP_ATOMIC, RAMOOPS_KERNMSG_HDR "%lld.%06lu-%c\n",
|
||||
len = scnprintf(hdr, sizeof(hdr),
|
||||
RAMOOPS_KERNMSG_HDR "%lld.%06lu-%c\n",
|
||||
(time64_t)record->time.tv_sec,
|
||||
record->time.tv_nsec / 1000,
|
||||
record->compressed ? 'C' : 'D');
|
||||
WARN_ON_ONCE(!hdr);
|
||||
len = hdr ? strlen(hdr) : 0;
|
||||
persistent_ram_write(prz, hdr, len);
|
||||
kfree(hdr);
|
||||
|
||||
return len;
|
||||
}
|
||||
@ -403,16 +394,14 @@ static int notrace ramoops_pstore_write(struct pstore_record *record)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Out of the various dmesg dump types, ramoops is currently designed
|
||||
* to only store crash logs, rather than storing general kernel logs.
|
||||
* 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".
|
||||
*/
|
||||
if (record->reason != KMSG_DUMP_OOPS &&
|
||||
record->reason != KMSG_DUMP_PANIC)
|
||||
return -EINVAL;
|
||||
|
||||
/* Skip Oopes when configured to do so. */
|
||||
if (record->reason == KMSG_DUMP_OOPS && !cxt->dump_oops)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Explicitly only take the first part of any new crash.
|
||||
@ -441,6 +430,9 @@ static int notrace ramoops_pstore_write(struct pstore_record *record)
|
||||
|
||||
/* 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;
|
||||
@ -600,9 +592,17 @@ static int ramoops_init_przs(const char *name,
|
||||
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);
|
||||
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",
|
||||
@ -617,6 +617,7 @@ static int ramoops_init_przs(const char *name,
|
||||
goto fail;
|
||||
}
|
||||
*paddr += zone_sz;
|
||||
prz_ar[i]->type = pstore_name_to_type(name);
|
||||
}
|
||||
|
||||
*przs = prz_ar;
|
||||
@ -632,6 +633,8 @@ static int ramoops_init_prz(const char *name,
|
||||
struct persistent_ram_zone **prz,
|
||||
phys_addr_t *paddr, size_t sz, u32 sig)
|
||||
{
|
||||
char *label;
|
||||
|
||||
if (!sz)
|
||||
return 0;
|
||||
|
||||
@ -642,8 +645,10 @@ static int ramoops_init_prz(const char *name,
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
label = kasprintf(GFP_KERNEL, "ramoops:%s", name);
|
||||
*prz = persistent_ram_new(*paddr, sz, sig, &cxt->ecc_info,
|
||||
cxt->memtype, 0);
|
||||
cxt->memtype, PRZ_FLAG_ZAP_OLD, label);
|
||||
kfree(label);
|
||||
if (IS_ERR(*prz)) {
|
||||
int err = PTR_ERR(*prz);
|
||||
|
||||
@ -652,26 +657,31 @@ static int ramoops_init_prz(const char *name,
|
||||
return err;
|
||||
}
|
||||
|
||||
persistent_ram_zap(*prz);
|
||||
|
||||
*paddr += sz;
|
||||
(*prz)->type = pstore_name_to_type(name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ramoops_parse_dt_size(struct platform_device *pdev,
|
||||
const char *propname, u32 *value)
|
||||
/* 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 < 0 && ret != -EINVAL) {
|
||||
if (ret == -EINVAL) {
|
||||
/* field is missing, use default value. */
|
||||
val32 = default_value;
|
||||
} else if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to parse property %s: %d\n",
|
||||
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;
|
||||
@ -685,6 +695,8 @@ 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 reserved_mem *rmem;
|
||||
struct resource *res;
|
||||
u32 value;
|
||||
int ret;
|
||||
@ -693,38 +705,123 @@ static int ramoops_parse_dt(struct platform_device *pdev,
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
rmem = of_reserved_mem_lookup(of_node);
|
||||
if (rmem) {
|
||||
pdata->mem_size = rmem->size;
|
||||
pdata->mem_address = rmem->base;
|
||||
} else {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to locate DT /reserved-memory resource\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
} else {
|
||||
pdata->mem_size = resource_size(res);
|
||||
pdata->mem_address = res->start;
|
||||
pdata->mem_type = of_property_read_bool(of_node, "unbuffered");
|
||||
pdata->dump_oops = !of_property_read_bool(of_node, "no-dump-oops");
|
||||
}
|
||||
|
||||
#define parse_size(name, field) { \
|
||||
ret = ramoops_parse_dt_size(pdev, name, &value); \
|
||||
/*
|
||||
* 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_size("record-size", pdata->record_size);
|
||||
parse_size("console-size", pdata->console_size);
|
||||
parse_size("ftrace-size", pdata->ftrace_size);
|
||||
parse_size("pmsg-size", pdata->pmsg_size);
|
||||
parse_size("ecc-size", pdata->ecc_info.ecc_size);
|
||||
parse_size("flags", pdata->flags);
|
||||
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);
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
parse_size("boot-log-size", pdata->boot_log_size);
|
||||
parse_size("boot-log-count", pdata->max_boot_log_cnt);
|
||||
parse_u32("boot-log-size", pdata->boot_log_size, 0);
|
||||
parse_u32("boot-log-count", pdata->max_boot_log_cnt, 0);
|
||||
#endif
|
||||
#undef parse_size
|
||||
|
||||
#undef parse_u32
|
||||
|
||||
/*
|
||||
* Some old Chromebooks relied on the kernel setting the
|
||||
* console_size and pmsg_size to the record size since that's
|
||||
* what the downstream kernel did. These same Chromebooks had
|
||||
* "ramoops" straight under the root node which isn't
|
||||
* according to the current upstream bindings (though it was
|
||||
* arguably acceptable under a prior version of the bindings).
|
||||
* Let's make those old Chromebooks work by detecting that
|
||||
* we're not a child of "reserved-memory" and mimicking the
|
||||
* expected behavior.
|
||||
*/
|
||||
parent_node = of_get_parent(of_node);
|
||||
if (!of_node_name_eq(parent_node, "reserved-memory") &&
|
||||
!pdata->console_size && !pdata->ftrace_size &&
|
||||
!pdata->pmsg_size && !pdata->ecc_info.ecc_size) {
|
||||
pdata->console_size = pdata->record_size;
|
||||
pdata->pmsg_size = pdata->record_size;
|
||||
}
|
||||
of_node_put(parent_node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if IS_REACHABLE(CONFIG_ROCKCHIP_MINIDUMP)
|
||||
static void _ramoops_register_ram_zone_info_to_minidump(struct persistent_ram_zone *prz)
|
||||
{
|
||||
struct md_region md_entry = {};
|
||||
|
||||
strscpy(md_entry.name, prz->label, sizeof(md_entry.name));
|
||||
|
||||
md_entry.virt_addr = (u64)prz->vaddr;
|
||||
md_entry.phys_addr = prz->paddr;
|
||||
md_entry.size = prz->size;
|
||||
|
||||
if (rk_minidump_add_region(&md_entry) < 0)
|
||||
pr_err("Failed to add %s in Minidump\n", prz->label);
|
||||
}
|
||||
|
||||
static void ramoops_register_ram_zone_info_to_minidump(struct ramoops_context *cxt)
|
||||
{
|
||||
int i = 0;
|
||||
struct persistent_ram_zone *prz = NULL;
|
||||
|
||||
for (i = 0; i < cxt->max_boot_log_cnt; i++) {
|
||||
prz = cxt->boot_przs[i];
|
||||
_ramoops_register_ram_zone_info_to_minidump(prz);
|
||||
}
|
||||
|
||||
for (i = 0; i < cxt->max_dump_cnt; i++) {
|
||||
prz = cxt->dprzs[i];
|
||||
_ramoops_register_ram_zone_info_to_minidump(prz);
|
||||
}
|
||||
|
||||
for (i = 0; i < cxt->max_ftrace_cnt; i++) {
|
||||
prz = cxt->fprzs[i];
|
||||
_ramoops_register_ram_zone_info_to_minidump(prz);
|
||||
}
|
||||
|
||||
prz = cxt->cprz;
|
||||
_ramoops_register_ram_zone_info_to_minidump(prz);
|
||||
|
||||
prz = cxt->mprz;
|
||||
_ramoops_register_ram_zone_info_to_minidump(prz);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ramoops_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@ -736,15 +833,6 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
int err = -EINVAL;
|
||||
int i = 0;
|
||||
|
||||
if (dev_of_node(dev) && !pdata) {
|
||||
pdata = &pdata_local;
|
||||
memset(pdata, 0, sizeof(*pdata));
|
||||
|
||||
err = ramoops_parse_dt(pdev, pdata);
|
||||
if (err < 0)
|
||||
goto fail_out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only a single ramoops area allowed at a time, so fail extra
|
||||
* probes.
|
||||
@ -754,6 +842,15 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
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");
|
||||
@ -794,7 +891,6 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
cxt->console_size = pdata->console_size;
|
||||
cxt->ftrace_size = pdata->ftrace_size;
|
||||
cxt->pmsg_size = pdata->pmsg_size;
|
||||
cxt->dump_oops = pdata->dump_oops;
|
||||
cxt->flags = pdata->flags;
|
||||
cxt->ecc_info = pdata->ecc_info;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
@ -811,7 +907,7 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
err = ramoops_init_przs("boot_log", dev, cxt, &cxt->boot_przs, &paddr,
|
||||
err = ramoops_init_przs("boot-log", dev, cxt, &cxt->boot_przs, &paddr,
|
||||
cxt->boot_log_size, -1,
|
||||
&cxt->max_boot_log_cnt, 0, 0);
|
||||
if (err)
|
||||
@ -821,7 +917,7 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
pr_info("boot-log-%d\t0x%zx@%pa\n", i, cxt->boot_przs[i]->size, &cxt->boot_przs[i]->paddr);
|
||||
#endif
|
||||
|
||||
err = ramoops_init_przs("dump", dev, cxt, &cxt->dprzs, &paddr,
|
||||
err = ramoops_init_przs("dmesg", dev, cxt, &cxt->dprzs, &paddr,
|
||||
dump_mem_sz, cxt->record_size,
|
||||
&cxt->max_dump_cnt, 0, 0);
|
||||
if (err)
|
||||
@ -855,7 +951,6 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
cxt->pmsg_size, 0);
|
||||
if (err)
|
||||
goto fail_init_mprz;
|
||||
|
||||
if (cxt->pmsg_size > 0)
|
||||
pr_info("pmsg\t0x%zx@%pa\n", cxt->mprz->size, &cxt->mprz->paddr);
|
||||
|
||||
@ -867,8 +962,10 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
* the single region size is how to check.
|
||||
*/
|
||||
cxt->pstore.flags = 0;
|
||||
if (cxt->max_dump_cnt)
|
||||
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)
|
||||
@ -908,14 +1005,16 @@ static int ramoops_probe(struct platform_device *pdev)
|
||||
mem_size = pdata->mem_size;
|
||||
mem_address = pdata->mem_address;
|
||||
record_size = pdata->record_size;
|
||||
dump_oops = pdata->dump_oops;
|
||||
ramoops_max_reason = pdata->max_reason;
|
||||
ramoops_console_size = pdata->console_size;
|
||||
ramoops_pmsg_size = pdata->pmsg_size;
|
||||
ramoops_ftrace_size = pdata->ftrace_size;
|
||||
|
||||
pr_info("attached 0x%lx@0x%llx, ecc: %d/%d\n",
|
||||
#if IS_REACHABLE(CONFIG_ROCKCHIP_MINIDUMP)
|
||||
ramoops_register_ram_zone_info_to_minidump(cxt);
|
||||
#endif
|
||||
pr_info("using 0x%lx@0x%llx, ecc: %d\n",
|
||||
cxt->size, (unsigned long long)cxt->phys_addr,
|
||||
cxt->ecc_info.ecc_size, cxt->ecc_info.block_size);
|
||||
cxt->ecc_info.ecc_size);
|
||||
|
||||
return 0;
|
||||
|
||||
@ -967,13 +1066,12 @@ static inline void ramoops_unregister_dummy(void)
|
||||
{
|
||||
platform_device_unregister(dummy);
|
||||
dummy = NULL;
|
||||
|
||||
kfree(dummy_data);
|
||||
dummy_data = NULL;
|
||||
}
|
||||
|
||||
static void __init ramoops_register_dummy(void)
|
||||
{
|
||||
struct ramoops_platform_data pdata;
|
||||
|
||||
/*
|
||||
* Prepare a dummy platform data structure to carry the module
|
||||
* parameters. If mem_size isn't set, then there are no module
|
||||
@ -984,35 +1082,38 @@ static void __init ramoops_register_dummy(void)
|
||||
|
||||
pr_info("using module parameters\n");
|
||||
|
||||
dummy_data = kzalloc(sizeof(*dummy_data), GFP_KERNEL);
|
||||
if (!dummy_data) {
|
||||
pr_info("could not allocate pdata\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dummy_data->mem_size = mem_size;
|
||||
dummy_data->mem_address = mem_address;
|
||||
dummy_data->mem_type = mem_type;
|
||||
dummy_data->record_size = record_size;
|
||||
dummy_data->console_size = ramoops_console_size;
|
||||
dummy_data->ftrace_size = ramoops_ftrace_size;
|
||||
dummy_data->pmsg_size = ramoops_pmsg_size;
|
||||
dummy_data->dump_oops = dump_oops;
|
||||
dummy_data->flags = RAMOOPS_FLAG_FTRACE_PER_CPU;
|
||||
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).
|
||||
*/
|
||||
dummy_data->ecc_info.ecc_size = ramoops_ecc == 1 ? 16 : ramoops_ecc;
|
||||
pdata.ecc_info.ecc_size = ramoops_ecc == 1 ? 16 : ramoops_ecc;
|
||||
|
||||
dummy = platform_device_register_data(NULL, "ramoops", -1,
|
||||
dummy_data, sizeof(struct ramoops_platform_data));
|
||||
&pdata, sizeof(pdata));
|
||||
if (IS_ERR(dummy)) {
|
||||
pr_info("could not create platform device: %ld\n",
|
||||
PTR_ERR(dummy));
|
||||
dummy = NULL;
|
||||
ramoops_unregister_dummy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2012 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "persistent_ram: " fmt
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
@ -29,6 +20,23 @@
|
||||
#include <linux/vmalloc.h>
|
||||
#include <asm/page.h>
|
||||
|
||||
/**
|
||||
* struct persistent_ram_buffer - persistent circular RAM buffer
|
||||
*
|
||||
* @sig:
|
||||
* signature to indicate header (PERSISTENT_RAM_SIG xor PRZ-type value)
|
||||
* @start:
|
||||
* offset into @data where the beginning of the stored bytes begin
|
||||
* @size:
|
||||
* number of valid bytes stored in @data
|
||||
*/
|
||||
struct persistent_ram_buffer {
|
||||
uint32_t sig;
|
||||
atomic_t start;
|
||||
atomic_t size;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
#define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */
|
||||
|
||||
static inline size_t buffer_size(struct persistent_ram_zone *prz)
|
||||
@ -275,7 +283,7 @@ static int notrace persistent_ram_update_user(struct persistent_ram_zone *prz,
|
||||
const void __user *s, unsigned int start, unsigned int count)
|
||||
{
|
||||
struct persistent_ram_buffer *buffer = prz->buffer;
|
||||
int ret = unlikely(__copy_from_user(buffer->data + start, s, count)) ?
|
||||
int ret = unlikely(copy_from_user(buffer->data + start, s, count)) ?
|
||||
-EFAULT : 0;
|
||||
persistent_ram_update_ecc(prz, start, count);
|
||||
return ret;
|
||||
@ -340,8 +348,6 @@ int notrace persistent_ram_write_user(struct persistent_ram_zone *prz,
|
||||
int rem, ret = 0, c = count;
|
||||
size_t start;
|
||||
|
||||
if (unlikely(!access_ok(VERIFY_READ, s, count)))
|
||||
return -EFAULT;
|
||||
if (unlikely(c > prz->buffer_size)) {
|
||||
s += c - prz->buffer_size;
|
||||
c = prz->buffer_size;
|
||||
@ -390,6 +396,10 @@ void persistent_ram_zap(struct persistent_ram_zone *prz)
|
||||
persistent_ram_update_header_ecc(prz);
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
@ -403,10 +413,20 @@ static void *persistent_ram_vmap(phys_addr_t start, size_t size,
|
||||
page_start = start - offset_in_page(start);
|
||||
page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE);
|
||||
|
||||
if (memtype)
|
||||
switch (memtype) {
|
||||
case MEM_TYPE_NORMAL:
|
||||
prot = PAGE_KERNEL;
|
||||
break;
|
||||
case MEM_TYPE_NONCACHED:
|
||||
prot = pgprot_noncached(PAGE_KERNEL);
|
||||
else
|
||||
break;
|
||||
case MEM_TYPE_WCOMBINE:
|
||||
prot = pgprot_writecombine(PAGE_KERNEL);
|
||||
break;
|
||||
default:
|
||||
pr_err("invalid mem_type=%d\n", memtype);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL);
|
||||
if (!pages) {
|
||||
@ -431,12 +451,13 @@ static void *persistent_ram_vmap(phys_addr_t start, size_t size,
|
||||
}
|
||||
|
||||
static void *persistent_ram_iomap(phys_addr_t start, size_t size,
|
||||
unsigned int memtype)
|
||||
unsigned int memtype, char *label)
|
||||
{
|
||||
void *va;
|
||||
|
||||
if (!request_mem_region(start, size, "persistent_ram")) {
|
||||
pr_err("request mem region (0x%llx@0x%llx) failed\n",
|
||||
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;
|
||||
}
|
||||
@ -463,7 +484,8 @@ static int persistent_ram_buffer_map(phys_addr_t start, phys_addr_t 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->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__,
|
||||
@ -481,10 +503,13 @@ 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)
|
||||
if (ret) {
|
||||
pr_warn("ECC failed %s\n", prz->label);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sig ^= PERSISTENT_RAM_SIG;
|
||||
|
||||
@ -495,22 +520,24 @@ static int persistent_ram_post_init(struct persistent_ram_zone *prz, u32 sig,
|
||||
}
|
||||
|
||||
if (buffer_size(prz) > prz->buffer_size ||
|
||||
buffer_start(prz) > buffer_size(prz))
|
||||
buffer_start(prz) > buffer_size(prz)) {
|
||||
pr_info("found existing invalid buffer, size %zu, start %zu\n",
|
||||
buffer_size(prz), buffer_start(prz));
|
||||
else {
|
||||
zap = true;
|
||||
} else {
|
||||
pr_debug("found existing buffer, size %zu, start %zu\n",
|
||||
buffer_size(prz), buffer_start(prz));
|
||||
persistent_ram_save_old(prz);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
pr_debug("no valid data in buffer (sig = 0x%08x)\n",
|
||||
prz->buffer->sig);
|
||||
prz->buffer->sig = sig;
|
||||
zap = true;
|
||||
}
|
||||
|
||||
/* Rewind missing or invalid memory area. */
|
||||
prz->buffer->sig = sig;
|
||||
/* Reset missing, invalid, or single-use memory area. */
|
||||
if (zap)
|
||||
persistent_ram_zap(prz);
|
||||
|
||||
return 0;
|
||||
@ -539,12 +566,13 @@ void persistent_ram_free(struct persistent_ram_zone *prz)
|
||||
prz->ecc_info.par = NULL;
|
||||
|
||||
persistent_ram_free_old(prz);
|
||||
kfree(prz->label);
|
||||
kfree(prz);
|
||||
}
|
||||
|
||||
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)
|
||||
unsigned int memtype, u32 flags, char *label)
|
||||
{
|
||||
struct persistent_ram_zone *prz;
|
||||
int ret = -ENOMEM;
|
||||
@ -558,6 +586,7 @@ struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
|
||||
/* Initialize general buffer state. */
|
||||
raw_spin_lock_init(&prz->buffer_lock);
|
||||
prz->flags = flags;
|
||||
prz->label = kstrdup(label, GFP_KERNEL);
|
||||
|
||||
ret = persistent_ram_buffer_map(start, size, prz, memtype);
|
||||
if (ret)
|
||||
@ -567,6 +596,12 @@ struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
|
||||
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);
|
||||
|
||||
1464
fs/pstore/zone.c
Normal file
1464
fs/pstore/zone.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@ enum kmsg_dump_reason {
|
||||
KMSG_DUMP_RESTART,
|
||||
KMSG_DUMP_HALT,
|
||||
KMSG_DUMP_POWEROFF,
|
||||
KMSG_DUMP_SHUTDOWN = KMSG_DUMP_POWEROFF,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -54,7 +55,7 @@ struct kmsg_dumper {
|
||||
|
||||
#ifdef CONFIG_PRINTK
|
||||
void kmsg_dump(enum kmsg_dump_reason reason);
|
||||
|
||||
const char *kmsg_dump_reason_str(enum kmsg_dump_reason reason);
|
||||
bool kmsg_dump_get_line_nolock(struct kmsg_dumper *dumper, bool syslog,
|
||||
char *line, size_t size, size_t *len);
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Persistent Storage - pstore.h
|
||||
*
|
||||
@ -5,19 +6,6 @@
|
||||
*
|
||||
* This code is the generic layer to export data records from platform
|
||||
* level persistent storage via a file system.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef _LINUX_PSTORE_H
|
||||
#define _LINUX_PSTORE_H
|
||||
@ -32,13 +20,19 @@
|
||||
|
||||
struct module;
|
||||
|
||||
/* pstore record types (see fs/pstore/inode.c for filename templates) */
|
||||
/*
|
||||
* pstore record types (see fs/pstore/platform.c for pstore_type_names[])
|
||||
* These values may be written to storage (see EFI vars backend), so
|
||||
* they are kind of an ABI. Be careful changing the mappings.
|
||||
*/
|
||||
enum pstore_type_id {
|
||||
/* Frontend storage types */
|
||||
PSTORE_TYPE_DMESG = 0,
|
||||
PSTORE_TYPE_MCE = 1,
|
||||
PSTORE_TYPE_CONSOLE = 2,
|
||||
PSTORE_TYPE_FTRACE = 3,
|
||||
/* PPC64 partition types */
|
||||
|
||||
/* PPC64-specific partition types */
|
||||
PSTORE_TYPE_PPC_RTAS = 4,
|
||||
PSTORE_TYPE_PPC_OF = 5,
|
||||
PSTORE_TYPE_PPC_COMMON = 6,
|
||||
@ -47,9 +41,14 @@ enum pstore_type_id {
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
PSTORE_TYPE_BOOT_LOG = 9,
|
||||
#endif
|
||||
PSTORE_TYPE_UNKNOWN = 255
|
||||
|
||||
/* End of the list */
|
||||
PSTORE_TYPE_MAX
|
||||
};
|
||||
|
||||
const char *pstore_type_to_name(enum pstore_type_id type);
|
||||
enum pstore_type_id pstore_name_to_type(const char *name);
|
||||
|
||||
struct pstore_info;
|
||||
/**
|
||||
* struct pstore_record - details of a pstore record entry
|
||||
@ -88,7 +87,7 @@ struct pstore_record {
|
||||
/**
|
||||
* struct pstore_info - backend pstore driver structure
|
||||
*
|
||||
* @owner: module which is repsonsible for this backend driver
|
||||
* @owner: module which is responsible for this backend driver
|
||||
* @name: name of the backend driver
|
||||
*
|
||||
* @buf_lock: semaphore to serialize access to @buf
|
||||
@ -100,6 +99,12 @@ struct pstore_record {
|
||||
*
|
||||
* @read_mutex: serializes @open, @read, @close, and @erase callbacks
|
||||
* @flags: bitfield of frontends the backend can accept writes for
|
||||
* @max_reason: Used when PSTORE_FLAGS_DMESG is set. Contains the
|
||||
* kmsg_dump_reason enum value. KMSG_DUMP_UNDEF means
|
||||
* "use existing kmsg_dump() filtering, based on the
|
||||
* printk.always_kmsg_dump boot param" (which is either
|
||||
* KMSG_DUMP_OOPS when false, or KMSG_DUMP_MAX when
|
||||
* true); see printk.always_kmsg_dump for more details.
|
||||
* @data: backend-private pointer passed back during callbacks
|
||||
*
|
||||
* Callbacks:
|
||||
@ -174,7 +179,7 @@ struct pstore_record {
|
||||
*/
|
||||
struct pstore_info {
|
||||
struct module *owner;
|
||||
char *name;
|
||||
const char *name;
|
||||
|
||||
struct semaphore buf_lock;
|
||||
char *buf;
|
||||
@ -183,6 +188,7 @@ struct pstore_info {
|
||||
struct mutex read_mutex;
|
||||
|
||||
int flags;
|
||||
int max_reason;
|
||||
void *data;
|
||||
|
||||
int (*open)(struct pstore_info *psi);
|
||||
@ -195,13 +201,14 @@ struct pstore_info {
|
||||
};
|
||||
|
||||
/* Supported frontends */
|
||||
#define PSTORE_FLAGS_DMESG (1 << 0)
|
||||
#define PSTORE_FLAGS_CONSOLE (1 << 1)
|
||||
#define PSTORE_FLAGS_FTRACE (1 << 2)
|
||||
#define PSTORE_FLAGS_PMSG (1 << 3)
|
||||
#define PSTORE_FLAGS_DMESG BIT(0)
|
||||
#define PSTORE_FLAGS_CONSOLE BIT(1)
|
||||
#define PSTORE_FLAGS_FTRACE BIT(2)
|
||||
#define PSTORE_FLAGS_PMSG BIT(3)
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
#define PSTORE_FLAGS_BOOT_LOG (1 << 4)
|
||||
#define PSTORE_FLAGS_BOOT_LOG BIT(4)
|
||||
#endif
|
||||
|
||||
extern int pstore_register(struct pstore_info *);
|
||||
extern void pstore_unregister(struct pstore_info *);
|
||||
|
||||
|
||||
@ -1,17 +1,8 @@
|
||||
/* 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.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_PSTORE_RAM_H__
|
||||
@ -22,8 +13,8 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/pstore.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/*
|
||||
* Choose whether access to the RAM zone requires locking or not. If a zone
|
||||
@ -31,17 +22,15 @@
|
||||
* PRZ_FLAG_NO_LOCK is used. For all other cases, locking is required.
|
||||
*/
|
||||
#define PRZ_FLAG_NO_LOCK BIT(0)
|
||||
/*
|
||||
* If a PRZ should only have a single-boot lifetime, this marks it as
|
||||
* getting wiped after its contents get copied out after boot.
|
||||
*/
|
||||
#define PRZ_FLAG_ZAP_OLD BIT(1)
|
||||
|
||||
struct persistent_ram_buffer;
|
||||
struct rs_control;
|
||||
|
||||
struct persistent_ram_buffer {
|
||||
uint32_t sig;
|
||||
atomic_t start;
|
||||
atomic_t size;
|
||||
uint8_t data[0];
|
||||
};
|
||||
|
||||
struct persistent_ram_ecc_info {
|
||||
int block_size;
|
||||
int ecc_size;
|
||||
@ -50,16 +39,55 @@ struct persistent_ram_ecc_info {
|
||||
uint16_t *par;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct persistent_ram_zone - Details of a persistent RAM zone (PRZ)
|
||||
* used as a pstore backend
|
||||
*
|
||||
* @paddr: physical address of the mapped RAM area
|
||||
* @size: size of mapping
|
||||
* @label: unique name of this PRZ
|
||||
* @type: frontend type for this PRZ
|
||||
* @flags: holds PRZ_FLAGS_* bits
|
||||
*
|
||||
* @buffer_lock:
|
||||
* locks access to @buffer "size" bytes and "start" offset
|
||||
* @buffer:
|
||||
* pointer to actual RAM area managed by this PRZ
|
||||
* @buffer_size:
|
||||
* bytes in @buffer->data (not including any trailing ECC bytes)
|
||||
*
|
||||
* @par_buffer:
|
||||
* pointer into @buffer->data containing ECC bytes for @buffer->data
|
||||
* @par_header:
|
||||
* pointer into @buffer->data containing ECC bytes for @buffer header
|
||||
* (i.e. all fields up to @data)
|
||||
* @rs_decoder:
|
||||
* RSLIB instance for doing ECC calculations
|
||||
* @corrected_bytes:
|
||||
* ECC corrected bytes accounting since boot
|
||||
* @bad_blocks:
|
||||
* ECC uncorrectable bytes accounting since boot
|
||||
* @ecc_info:
|
||||
* ECC configuration details
|
||||
*
|
||||
* @old_log:
|
||||
* saved copy of @buffer->data prior to most recent wipe
|
||||
* @old_log_size:
|
||||
* bytes contained in @old_log
|
||||
*
|
||||
*/
|
||||
struct persistent_ram_zone {
|
||||
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;
|
||||
u32 flags;
|
||||
raw_spinlock_t buffer_lock;
|
||||
|
||||
/* ECC correction */
|
||||
char *par_buffer;
|
||||
char *par_header;
|
||||
struct rs_control *rs_decoder;
|
||||
@ -71,45 +99,9 @@ struct persistent_ram_zone {
|
||||
size_t old_log_size;
|
||||
};
|
||||
|
||||
struct ramoops_context {
|
||||
struct persistent_ram_zone **dprzs; /* Oops dump zones */
|
||||
struct persistent_ram_zone *cprz; /* Console zone */
|
||||
struct persistent_ram_zone **fprzs; /* Ftrace zones */
|
||||
struct persistent_ram_zone *mprz; /* PMSG zone */
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
struct persistent_ram_zone **boot_przs; /* BOOT log zones */
|
||||
#endif
|
||||
phys_addr_t phys_addr;
|
||||
unsigned long size;
|
||||
unsigned int memtype;
|
||||
size_t record_size;
|
||||
size_t console_size;
|
||||
size_t ftrace_size;
|
||||
size_t pmsg_size;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
size_t boot_log_size;
|
||||
#endif
|
||||
int dump_oops;
|
||||
u32 flags;
|
||||
struct persistent_ram_ecc_info ecc_info;
|
||||
unsigned int max_dump_cnt;
|
||||
unsigned int dump_write_cnt;
|
||||
/* _read_cnt need clear on ramoops_pstore_open */
|
||||
unsigned int dump_read_cnt;
|
||||
unsigned int console_read_cnt;
|
||||
unsigned int max_ftrace_cnt;
|
||||
unsigned int ftrace_read_cnt;
|
||||
unsigned int pmsg_read_cnt;
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
unsigned int boot_log_read_cnt;
|
||||
unsigned int max_boot_log_cnt;
|
||||
#endif
|
||||
struct pstore_info pstore;
|
||||
};
|
||||
|
||||
struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size,
|
||||
u32 sig, struct persistent_ram_ecc_info *ecc_info,
|
||||
unsigned int memtype, u32 flags);
|
||||
unsigned int memtype, u32 flags, char *label);
|
||||
void persistent_ram_free(struct persistent_ram_zone *prz);
|
||||
void persistent_ram_zap(struct persistent_ram_zone *prz);
|
||||
|
||||
@ -124,6 +116,9 @@ void *persistent_ram_old(struct persistent_ram_zone *prz);
|
||||
void persistent_ram_free_old(struct persistent_ram_zone *prz);
|
||||
ssize_t persistent_ram_ecc_string(struct persistent_ram_zone *prz,
|
||||
char *str, size_t len);
|
||||
#ifdef CONFIG_PSTORE_BOOT_LOG
|
||||
ssize_t ramoops_pstore_read_for_boot_log(struct pstore_record *record);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Ramoops platform data
|
||||
@ -145,7 +140,7 @@ struct ramoops_platform_data {
|
||||
unsigned long boot_log_size;
|
||||
unsigned long max_boot_log_cnt;
|
||||
#endif
|
||||
int dump_oops;
|
||||
int max_reason;
|
||||
u32 flags;
|
||||
struct persistent_ram_ecc_info ecc_info;
|
||||
};
|
||||
|
||||
@ -3260,6 +3260,23 @@ void kmsg_dump(enum kmsg_dump_reason reason)
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
const char *kmsg_dump_reason_str(enum kmsg_dump_reason reason)
|
||||
{
|
||||
switch (reason) {
|
||||
case KMSG_DUMP_PANIC:
|
||||
return "Panic";
|
||||
case KMSG_DUMP_OOPS:
|
||||
return "Oops";
|
||||
case KMSG_DUMP_EMERG:
|
||||
return "Emergency";
|
||||
case KMSG_DUMP_SHUTDOWN:
|
||||
return "Shutdown";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kmsg_dump_reason_str);
|
||||
|
||||
/**
|
||||
* kmsg_dump_get_line_nolock - retrieve one kmsg log line (unlocked version)
|
||||
* @dumper: registered kmsg dumper
|
||||
|
||||
Reference in New Issue
Block a user