@@ -23,10 +23,11 @@
/* This doesn't need to be atomic: speed is chosen over correctness here. */
static u64 pstore_ftrace_stamp;
-static void notrace pstore_ftrace_call(unsigned long ip,
+static void notrace pstore_do_ftrace(unsigned long ip,
unsigned long parent_ip,
struct ftrace_ops *op,
- struct ftrace_regs *fregs)
+ struct ftrace_regs *fregs,
+ struct pstore_info *psinfo)
{
int bit;
unsigned long flags;
@@ -57,6 +58,20 @@ static void notrace pstore_ftrace_call(unsigned long ip,
ftrace_test_recursion_unlock(bit);
}
+static void notrace pstore_ftrace_call(unsigned long ip,
+ unsigned long parent_ip,
+ struct ftrace_ops *op,
+ struct ftrace_regs *fregs)
+{
+ struct pstore_info_list *entry;
+
+ mutex_lock(&psback_lock);
+ list_for_each_entry(entry, &psback->list_entry, list)
+ if (entry->psi->flags & PSTORE_FLAGS_FTRACE)
+ pstore_do_ftrace(ip, parent_ip, op, fregs, entry->psi);
+ mutex_unlock(&psback_lock);
+}
+
static struct ftrace_ops pstore_ftrace_ops __read_mostly = {
.func = pstore_ftrace_call,
};
@@ -131,8 +146,12 @@ MODULE_PARM_DESC(record_ftrace,
void pstore_register_ftrace(void)
{
- if (!psinfo->write)
- return;
+ struct pstore_info_list *entry;
+
+ list_for_each_entry(entry, &psback->list_entry, list)
+ if (entry->psi->flags & PSTORE_FLAGS_FTRACE)
+ if (!entry->psi->write)
+ return;
pstore_ftrace_dir = debugfs_create_dir("pstore", NULL);
@@ -285,7 +285,7 @@ static const struct super_operations pstore_ops = {
.show_options = pstore_show_options,
};
-static struct dentry *psinfo_lock_root(void)
+static struct dentry *psinfo_lock_root(struct pstore_info *psinfo)
{
struct dentry *root;
@@ -309,7 +309,7 @@ int pstore_put_backend_records(struct pstore_info *psi)
struct dentry *root;
int rc = 0;
- root = psinfo_lock_root();
+ root = psinfo_lock_root(psi);
if (!root)
return 0;
@@ -398,21 +398,22 @@ int pstore_mkfile(struct dentry *root, struct pstore_record *record)
* when we are re-scanning the backing store looking to add new
* error records.
*/
-void pstore_get_records(int quiet)
+void pstore_get_records(struct pstore_info *psi, int quiet)
{
struct dentry *root;
- root = psinfo_lock_root();
+ root = psinfo_lock_root(psi);
if (!root)
return;
- pstore_get_backend_records(psinfo, root, quiet);
+ pstore_get_backend_records(psi, root, quiet);
inode_unlock(d_inode(root));
}
static int pstore_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *inode;
+ struct pstore_info_list *entry;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE;
@@ -437,7 +438,13 @@ static int pstore_fill_super(struct super_block *sb, void *data, int silent)
scoped_guard(mutex, &pstore_sb_lock)
pstore_sb = sb;
- pstore_get_records(0);
+ if (!psback)
+ return 0;
+
+ mutex_lock(&psback_lock);
+ list_for_each_entry(entry, &psback->list_entry, list)
+ pstore_get_records(entry->psi, 0);
+ mutex_unlock(&psback_lock);
return 0;
}
@@ -33,10 +33,10 @@ static inline void pstore_register_pmsg(void) {}
static inline void pstore_unregister_pmsg(void) {}
#endif
-extern struct pstore_info *psinfo;
+extern struct pstore_backends *psback;
extern void pstore_set_kmsg_bytes(int);
-extern void pstore_get_records(int);
+extern void pstore_get_records(struct pstore_info *psi, int quiet);
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);
@@ -62,12 +62,12 @@ static void pstore_dowork(struct work_struct *);
static DECLARE_WORK(pstore_work, pstore_dowork);
/*
- * psinfo_lock protects "psinfo" during calls to
+ * psback_lock protects "psback" during calls to
* pstore_register(), pstore_unregister(), and
* the filesystem mount/unmount routines.
*/
-static DEFINE_MUTEX(psinfo_lock);
-struct pstore_info *psinfo;
+DEFINE_MUTEX(psback_lock);
+struct pstore_backends *psback;
static char *backend;
module_param(backend, charp, 0444);
@@ -104,9 +104,6 @@ static void *compress_workspace;
*/
#define DMESG_COMP_PERCENT 60
-static char *big_oops_buf;
-static size_t max_compressed_size;
-
void pstore_set_kmsg_bytes(int bytes)
{
kmsg_bytes = bytes;
@@ -201,7 +198,7 @@ static int pstore_compress(const void *in, void *out,
return zstream.total_out;
}
-static void allocate_buf_for_compression(void)
+static void allocate_buf_for_compression(struct pstore_info_list *entry)
{
size_t compressed_size;
char *buf;
@@ -224,11 +221,11 @@ static void allocate_buf_for_compression(void)
* uncompressed record size, since any record that would be expanded by
* compression is just stored uncompressed.
*/
- compressed_size = (psinfo->bufsize * 100) / DMESG_COMP_PERCENT;
+ compressed_size = (entry->psi->bufsize * 100) / DMESG_COMP_PERCENT;
buf = kvzalloc(compressed_size, GFP_KERNEL);
if (!buf) {
pr_err("Failed %zu byte compression buffer allocation for: %s\n",
- psinfo->bufsize, compress);
+ entry->psi->bufsize, compress);
return;
}
@@ -241,22 +238,22 @@ static void allocate_buf_for_compression(void)
}
/* A non-NULL big_oops_buf indicates compression is available. */
- big_oops_buf = buf;
- max_compressed_size = compressed_size;
+ entry->big_oops_buf = buf;
+ entry->max_compressed_size = compressed_size;
pr_info("Using crash dump compression: %s\n", compress);
}
-static void free_buf_for_compression(void)
+static void free_buf_for_compression(struct pstore_info_list *entry)
{
if (IS_ENABLED(CONFIG_PSTORE_COMPRESS) && compress_workspace) {
vfree(compress_workspace);
compress_workspace = NULL;
}
- kvfree(big_oops_buf);
- big_oops_buf = NULL;
- max_compressed_size = 0;
+ kvfree(entry->big_oops_buf);
+ entry->big_oops_buf = NULL;
+ entry->max_compressed_size = 0;
}
void pstore_record_init(struct pstore_record *record,
@@ -278,6 +275,7 @@ static void pstore_dump(struct kmsg_dumper *dumper,
enum kmsg_dump_reason reason)
{
struct kmsg_dump_iter iter;
+ struct pstore_info_list *entry;
unsigned long total = 0;
const char *why;
unsigned int part = 1;
@@ -285,16 +283,18 @@ static void pstore_dump(struct kmsg_dumper *dumper,
int saved_ret = 0;
int ret;
+ entry = container_of(dumper, struct pstore_info_list, pstore_dumper);
+
why = kmsg_dump_reason_str(reason);
if (pstore_cannot_block_path(reason)) {
- if (!spin_trylock_irqsave(&psinfo->buf_lock, flags)) {
+ if (!spin_trylock_irqsave(&entry->psi->buf_lock, flags)) {
pr_err("dump skipped in %s path because of concurrent dump\n",
in_nmi() ? "NMI" : why);
return;
}
} else {
- spin_lock_irqsave(&psinfo->buf_lock, flags);
+ spin_lock_irqsave(&entry->psi->buf_lock, flags);
}
kmsg_dump_rewind(&iter);
@@ -308,15 +308,15 @@ static void pstore_dump(struct kmsg_dumper *dumper,
size_t dump_size;
struct pstore_record record;
- pstore_record_init(&record, psinfo);
+ pstore_record_init(&record, entry->psi);
record.type = PSTORE_TYPE_DMESG;
record.count = oopscount;
record.reason = reason;
record.part = part;
- record.buf = psinfo->buf;
+ record.buf = entry->psi->buf;
- dst = big_oops_buf ?: psinfo->buf;
- dst_size = max_compressed_size ?: psinfo->bufsize;
+ dst = entry->big_oops_buf ?: entry->psi->buf;
+ dst_size = entry->max_compressed_size ?: entry->psi->bufsize;
/* Write dump header. */
header_size = snprintf(dst, dst_size, "%s#%d Part%u\n", why,
@@ -328,10 +328,10 @@ static void pstore_dump(struct kmsg_dumper *dumper,
dst_size, &dump_size))
break;
- if (big_oops_buf) {
- zipped_len = pstore_compress(dst, psinfo->buf,
+ if (entry->big_oops_buf) {
+ zipped_len = pstore_compress(dst, entry->psi->buf,
header_size + dump_size,
- psinfo->bufsize);
+ entry->psi->bufsize);
if (zipped_len > 0) {
record.compressed = true;
@@ -344,14 +344,14 @@ static void pstore_dump(struct kmsg_dumper *dumper,
* of the uncompressed data as possible into
* the pstore record, and discard the rest.
*/
- record.size = psinfo->bufsize;
- memcpy(psinfo->buf, dst, psinfo->bufsize);
+ record.size = entry->psi->bufsize;
+ memcpy(entry->psi->buf, dst, entry->psi->bufsize);
}
} else {
record.size = header_size + dump_size;
}
- ret = psinfo->write(&record);
+ ret = entry->psi->write(&record);
if (ret == 0 && reason == KMSG_DUMP_OOPS) {
pstore_new_entry = 1;
pstore_timer_kick();
@@ -364,39 +364,33 @@ static void pstore_dump(struct kmsg_dumper *dumper,
total += record.size;
part++;
}
- spin_unlock_irqrestore(&psinfo->buf_lock, flags);
+ spin_unlock_irqrestore(&entry->psi->buf_lock, flags);
if (saved_ret) {
- pr_err_once("backend (%s) writing error (%d)\n", psinfo->name,
+ pr_err_once("backend (%s) writing error (%d)\n", entry->psi->name,
saved_ret);
}
}
-static struct kmsg_dumper pstore_dumper = {
- .dump = pstore_dump,
-};
-
/*
* Register with kmsg_dump to save last part of console log on panic.
*/
-static void pstore_register_kmsg(void)
+static void pstore_register_kmsg(struct kmsg_dumper *pstore_dumper)
{
- kmsg_dump_register(&pstore_dumper);
+ kmsg_dump_register(pstore_dumper);
}
-static void pstore_unregister_kmsg(void)
+static void pstore_unregister_kmsg(struct kmsg_dumper *pstore_dumper)
{
- kmsg_dump_unregister(&pstore_dumper);
+ kmsg_dump_unregister(pstore_dumper);
}
#ifdef CONFIG_PSTORE_CONSOLE
-static void pstore_console_write(struct console *con, const char *s, unsigned c)
+static void pstore_console_do_write(struct console *con, const char *s,
+ unsigned c, struct pstore_info *psinfo)
{
struct pstore_record record;
- if (!c)
- return;
-
pstore_record_init(&record, psinfo);
record.type = PSTORE_TYPE_CONSOLE;
@@ -405,6 +399,21 @@ static void pstore_console_write(struct console *con, const char *s, unsigned c)
psinfo->write(&record);
}
+static void pstore_console_write(struct console *con, const char *s,
+ unsigned int c)
+{
+ struct pstore_info_list *entry;
+
+ if (!c)
+ return;
+
+ mutex_lock(&psback_lock);
+ list_for_each_entry(entry, &psback->list_entry, list)
+ if (entry->psi->flags & PSTORE_FLAGS_CONSOLE)
+ pstore_console_do_write(con, s, c, entry->psi);
+ mutex_unlock(&psback_lock);
+}
+
static struct console pstore_console = {
.write = pstore_console_write,
.index = -1,
@@ -413,7 +422,7 @@ static struct console pstore_console = {
static void pstore_register_console(void)
{
/* Show which backend is going to get console writes. */
- strscpy(pstore_console.name, psinfo->name,
+ strscpy(pstore_console.name, "pstore console",
sizeof(pstore_console.name));
/*
* Always initialize flags here since prior unregister_console()
@@ -455,6 +464,33 @@ static int pstore_write_user_compat(struct pstore_record *record,
return unlikely(ret < 0) ? ret : record->size;
}
+static bool is_backend_enabled(char *backend, const char *name)
+{
+ char *sep, *backend_name;
+
+ if (!backend || strcmp(backend, "all") == 0)
+ return true;
+ else {
+ sep = kstrdup(backend, GFP_KERNEL);
+ while ((backend_name = strsep(&sep, ",")) != NULL) {
+ if (strcmp(name, backend_name) == 0)
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool is_backend_loaded(const char *name)
+{
+ struct pstore_info_list *entry;
+
+ list_for_each_entry(entry, &psback->list_entry, list)
+ if (strcmp(entry->psi->name, name) == 0)
+ return true;
+
+ return false;
+}
+
/*
* platform specific persistent storage driver registers with
* us here. If pstore is already mounted, call the platform
@@ -464,12 +500,14 @@ static int pstore_write_user_compat(struct pstore_record *record,
*/
int pstore_register(struct pstore_info *psi)
{
+ struct pstore_info_list *newpsi;
char *new_backend;
- if (backend && strcmp(backend, psi->name)) {
- pr_warn("backend '%s' already in use: ignoring '%s'\n",
- backend, psi->name);
- return -EBUSY;
+ /* backend has to be in pstore.backend for going on registering */
+ if (!is_backend_enabled(backend, psi->name)) {
+ pr_warn("backend '%s' ignored: not present in "
+ "pstore.backend=...\n", psi->name);
+ return -EINVAL;
}
/* Sanity check flags. */
@@ -486,80 +524,125 @@ int pstore_register(struct pstore_info *psi)
return -EINVAL;
}
- new_backend = kstrdup(psi->name, GFP_KERNEL);
- if (!new_backend)
- return -ENOMEM;
-
- mutex_lock(&psinfo_lock);
- if (psinfo) {
- pr_warn("backend '%s' already loaded: ignoring '%s'\n",
- psinfo->name, psi->name);
- mutex_unlock(&psinfo_lock);
- kfree(new_backend);
- return -EBUSY;
+ mutex_lock(&psback_lock);
+
+ /*
+ * If no backend specified, first come first served to
+ * maintain backward compatibility
+ */
+ if (!backend) {
+ new_backend = kstrdup(psi->name, GFP_KERNEL);
+ if (!new_backend) {
+ mutex_unlock(&psback_lock);
+ return -ENOMEM;
+ }
+ pr_warn("pstore.backend=... not specified, "
+ "registering first available: '%s'\n",
+ psi->name);
+ }
+
+ if (psback) {
+ if (is_backend_loaded(psi->name)) {
+ pr_warn("backend '%s' already loaded; "
+ "not loading it again\n", psi->name);
+ mutex_unlock(&psback_lock);
+ return -EPERM;
+ }
+ } else {
+ psback = kzalloc(sizeof(*psback), GFP_KERNEL);
+ if (!psback) {
+ mutex_unlock(&psback_lock);
+ return -ENOMEM;
+ }
+ INIT_LIST_HEAD(&psback->list_entry);
}
if (!psi->write_user)
psi->write_user = pstore_write_user_compat;
- psinfo = psi;
- mutex_init(&psinfo->read_mutex);
- spin_lock_init(&psinfo->buf_lock);
+
+ newpsi = kzalloc(sizeof(*newpsi), GFP_KERNEL);
+ if (!newpsi) {
+ mutex_unlock(&psback_lock);
+ return -EPERM;
+ }
+ newpsi->psi = psi;
+
+ mutex_init(&psi->read_mutex);
+ spin_lock_init(&psi->buf_lock);
if (psi->flags & PSTORE_FLAGS_DMESG)
- allocate_buf_for_compression();
+ allocate_buf_for_compression(newpsi);
- pstore_get_records(0);
+ pstore_get_records(psi, 0);
+
+ list_add(&newpsi->list, &psback->list_entry);
if (psi->flags & PSTORE_FLAGS_DMESG) {
- pstore_dumper.max_reason = psinfo->max_reason;
- pstore_register_kmsg();
+ newpsi->pstore_dumper.dump = pstore_dump;
+ newpsi->pstore_dumper.max_reason = psi->max_reason;
+ pstore_register_kmsg(&newpsi->pstore_dumper);
}
- if (psi->flags & PSTORE_FLAGS_CONSOLE)
+ if (psi->flags & PSTORE_FLAGS_CONSOLE
+ && !psback->front_cnt[PSTORE_TYPE_CONSOLE]++)
pstore_register_console();
- if (psi->flags & PSTORE_FLAGS_FTRACE)
+ if (psi->flags & PSTORE_FLAGS_FTRACE &&
+ !psback->front_cnt[PSTORE_TYPE_FTRACE]++)
pstore_register_ftrace();
- if (psi->flags & PSTORE_FLAGS_PMSG)
+ if (psi->flags & PSTORE_FLAGS_PMSG &&
+ !psback->front_cnt[PSTORE_TYPE_PMSG]++)
pstore_register_pmsg();
/* Start watching for new records, if desired. */
pstore_timer_kick();
/*
- * Update the module parameter backend, so it is visible
+ * When module parameter backend is not specified,
+ * update the module parameter backend, so it is visible
* through /sys/module/pstore/parameters/backend
*/
- backend = new_backend;
+ if (!backend)
+ backend = new_backend;
pr_info("Registered %s as persistent store backend\n", psi->name);
- mutex_unlock(&psinfo_lock);
+ mutex_unlock(&psback_lock);
return 0;
}
EXPORT_SYMBOL_GPL(pstore_register);
void pstore_unregister(struct pstore_info *psi)
{
+ struct pstore_info_list *entry, *tmp;
+
+ pr_info("Unregistering %s as persistent store backend\n", psi->name);
+
/* It's okay to unregister nothing. */
if (!psi)
return;
- mutex_lock(&psinfo_lock);
+ mutex_lock(&psback_lock);
- /* Only one backend can be registered at a time. */
- if (WARN_ON(psi != psinfo)) {
- mutex_unlock(&psinfo_lock);
+ /* Can not unregister an unloaded backend*/
+ if (WARN_ON(!is_backend_loaded(psi->name))) {
+ mutex_unlock(&psback_lock);
return;
}
/* Unregister all callbacks. */
- if (psi->flags & PSTORE_FLAGS_PMSG)
+ if (psi->flags & PSTORE_FLAGS_PMSG &&
+ !--psback->front_cnt[PSTORE_TYPE_PMSG])
pstore_unregister_pmsg();
- if (psi->flags & PSTORE_FLAGS_FTRACE)
+ if (psi->flags & PSTORE_FLAGS_FTRACE &&
+ !--psback->front_cnt[PSTORE_TYPE_FTRACE])
pstore_unregister_ftrace();
- if (psi->flags & PSTORE_FLAGS_CONSOLE)
+ if (psi->flags & PSTORE_FLAGS_CONSOLE &&
+ !--psback->front_cnt[PSTORE_TYPE_CONSOLE])
pstore_unregister_console();
- if (psi->flags & PSTORE_FLAGS_DMESG)
- pstore_unregister_kmsg();
+ list_for_each_entry(entry, &psback->list_entry, list) {
+ if (entry->psi == psi)
+ if (psi->flags & PSTORE_FLAGS_DMESG)
+ pstore_unregister_kmsg(&entry->pstore_dumper);
+ }
/* Stop timer and make sure all work has finished. */
del_timer_sync(&pstore_timer);
@@ -568,19 +651,28 @@ void pstore_unregister(struct pstore_info *psi)
/* Remove all backend records from filesystem tree. */
pstore_put_backend_records(psi);
- free_buf_for_compression();
+ list_for_each_entry_safe(entry, tmp, &psback->list_entry, list) {
+ if (entry->psi == psi) {
+ list_del(&entry->list);
+ free_buf_for_compression(entry);
+ kfree(entry);
+ break;
+ }
+ }
- psinfo = NULL;
- kfree(backend);
- backend = NULL;
+ if (list_empty(&psback->list_entry)) {
+ kfree(psback);
+ psback = NULL;
+ }
pr_info("Unregistered %s as persistent store backend\n", psi->name);
- mutex_unlock(&psinfo_lock);
+ mutex_unlock(&psback_lock);
}
EXPORT_SYMBOL_GPL(pstore_unregister);
static void decompress_record(struct pstore_record *record,
- struct z_stream_s *zstream)
+ struct z_stream_s *zstream,
+ struct pstore_info *psinfo)
{
int ret;
int unzipped_len;
@@ -697,7 +789,7 @@ void pstore_get_backend_records(struct pstore_info *psi,
break;
}
- decompress_record(record, &zstream);
+ decompress_record(record, &zstream, psi);
rc = pstore_mkfile(root, record);
if (rc) {
/* pstore_mkfile() did not take record, so free it. */
@@ -729,7 +821,12 @@ void pstore_get_backend_records(struct pstore_info *psi,
static void pstore_dowork(struct work_struct *work)
{
- pstore_get_records(1);
+ struct pstore_info_list *entry;
+
+ mutex_lock(&psback_lock);
+ list_for_each_entry(entry, &psback->list_entry, list)
+ pstore_get_records(entry->psi, 1);
+ mutex_unlock(&psback_lock);
}
static void pstore_timefunc(struct timer_list *unused)
@@ -744,13 +841,7 @@ static void pstore_timefunc(struct timer_list *unused)
static int __init pstore_init(void)
{
- int ret;
-
- ret = pstore_init_fs();
- if (ret)
- free_buf_for_compression();
-
- return ret;
+ return pstore_init_fs();
}
late_initcall(pstore_init);
@@ -11,8 +11,9 @@
static DEFINE_MUTEX(pmsg_lock);
-static ssize_t write_pmsg(struct file *file, const char __user *buf,
- size_t count, loff_t *ppos)
+static ssize_t do_write_pmsg(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos,
+ struct pstore_info *psinfo)
{
struct pstore_record record;
int ret;
@@ -34,6 +35,28 @@ static ssize_t write_pmsg(struct file *file, const char __user *buf,
return ret ? ret : count;
}
+static ssize_t write_pmsg(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ret, written = 0, err = 0;
+ struct pstore_info_list *entry;
+
+ mutex_lock(&psback_lock);
+ list_for_each_entry(entry, &psback->list_entry, list) {
+ if (entry->psi->flags & PSTORE_FLAGS_PMSG) {
+ ret = do_write_pmsg(file, buf, count,
+ ppos, entry->psi);
+ /* Return the first negative value or max_seen ret */
+ if (!err && ret < 0)
+ err = ret;
+ written = ret > written ? ret : written;
+ }
+ }
+ mutex_unlock(&psback_lock);
+
+ return err ? err : written;
+}
+
static const struct file_operations pmsg_fops = {
.owner = THIS_MODULE,
.llseek = noop_llseek,
@@ -201,6 +201,29 @@ struct pstore_info {
int (*erase)(struct pstore_record *record);
};
+/* Supported multibackends */
+extern struct mutex psback_lock;
+
+struct pstore_info_list {
+ struct pstore_info *psi;
+ char *big_oops_buf;
+ size_t max_compressed_size;
+ struct kmsg_dumper pstore_dumper;
+ struct list_head list;
+};
+
+/**
+ * struct pstore_backends - management of pstore backends
+ * @front_cnt: count of each enabled frontend
+ * @list_entry: entry of pstore backend driver information list
+ *
+ */
+
+struct pstore_backends {
+ int front_cnt[PSTORE_TYPE_MAX];
+ struct list_head list_entry;
+};
+
/* Supported frontends */
#define PSTORE_FLAGS_DMESG BIT(0)
#define PSTORE_FLAGS_CONSOLE BIT(1)