@@ -5,15 +5,18 @@
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
+#include <linux/kobject.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
+#include <linux/pfn.h>
#include <linux/pkram.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/string.h>
+#include <linux/sysfs.h>
#include <linux/types.h>
#include "internal.h"
@@ -84,12 +87,38 @@ struct pkram_node {
#define PKRAM_ACCMODE_MASK 3
/*
+ * The PKRAM super block contains data needed to restore the preserved memory
+ * structure on boot. The pointer to it (pfn) should be passed via the 'pkram'
+ * boot param if one wants to restore preserved data saved by the previously
+ * executing kernel. For that purpose the kernel exports the pfn via
+ * /sys/kernel/pkram. If none is passed, preserved memory if any will not be
+ * preserved and a new clean page will be allocated for the super block.
+ *
+ * The structure occupies a memory page.
+ */
+struct pkram_super_block {
+ __u64 node_pfn; /* first element of the node list */
+};
+
+static unsigned long pkram_sb_pfn __initdata;
+static struct pkram_super_block *pkram_sb;
+
+/*
* For convenience sake PKRAM nodes are kept in an auxiliary doubly-linked list
* connected through the lru field of the page struct.
*/
static LIST_HEAD(pkram_nodes); /* linked through page::lru */
static DEFINE_MUTEX(pkram_mutex); /* serializes open/close */
+/*
+ * The PKRAM super block pfn, see above.
+ */
+static int __init parse_pkram_sb_pfn(char *arg)
+{
+ return kstrtoul(arg, 16, &pkram_sb_pfn);
+}
+early_param("pkram", parse_pkram_sb_pfn);
+
static inline struct page *pkram_alloc_page(gfp_t gfp_mask)
{
return alloc_page(gfp_mask);
@@ -275,6 +304,7 @@ static void pkram_stream_init(struct pkram_stream *ps,
* @gfp_mask specifies the memory allocation mask to be used when saving data.
*
* Error values:
+ * %ENODEV: PKRAM not available
* %ENAMETOOLONG: name len >= PKRAM_NAME_MAX
* %ENOMEM: insufficient memory available
* %EEXIST: node with specified name already exists
@@ -290,6 +320,9 @@ int pkram_prepare_save(struct pkram_stream *ps, const char *name, gfp_t gfp_mask
struct pkram_node *node;
int err = 0;
+ if (!pkram_sb)
+ return -ENODEV;
+
if (strlen(name) >= PKRAM_NAME_MAX)
return -ENAMETOOLONG;
@@ -410,6 +443,7 @@ void pkram_discard_save(struct pkram_stream *ps)
* Returns 0 on success, -errno on failure.
*
* Error values:
+ * %ENODEV: PKRAM not available
* %ENOENT: node with specified name does not exist
* %EBUSY: save to required node has not finished yet
*
@@ -420,6 +454,9 @@ int pkram_prepare_load(struct pkram_stream *ps, const char *name)
struct pkram_node *node;
int err = 0;
+ if (!pkram_sb)
+ return -ENODEV;
+
mutex_lock(&pkram_mutex);
node = pkram_find_node(name);
if (!node) {
@@ -809,6 +846,13 @@ static void __pkram_reboot(void)
node->node_pfn = node_pfn;
node_pfn = page_to_pfn(page);
}
+
+ /*
+ * Zero out pkram_sb completely since it may have been passed from
+ * the previous boot.
+ */
+ memset(pkram_sb, 0, PAGE_SIZE);
+ pkram_sb->node_pfn = node_pfn;
}
static int pkram_reboot(struct notifier_block *notifier,
@@ -816,7 +860,8 @@ static int pkram_reboot(struct notifier_block *notifier,
{
if (val != SYS_RESTART)
return NOTIFY_DONE;
- __pkram_reboot();
+ if (pkram_sb)
+ __pkram_reboot();
return NOTIFY_OK;
}
@@ -824,9 +869,62 @@ static int pkram_reboot(struct notifier_block *notifier,
.notifier_call = pkram_reboot,
};
+static ssize_t show_pkram_sb_pfn(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ unsigned long pfn = pkram_sb ? PFN_DOWN(__pa(pkram_sb)) : 0;
+
+ return sprintf(buf, "%lx\n", pfn);
+}
+
+static struct kobj_attribute pkram_sb_pfn_attr =
+ __ATTR(pkram, 0444, show_pkram_sb_pfn, NULL);
+
+static struct attribute *pkram_attrs[] = {
+ &pkram_sb_pfn_attr.attr,
+ NULL,
+};
+
+static struct attribute_group pkram_attr_group = {
+ .attrs = pkram_attrs,
+};
+
+/* returns non-zero on success */
+static int __init pkram_init_sb(void)
+{
+ unsigned long pfn;
+ struct pkram_node *node;
+
+ if (!pkram_sb) {
+ struct page *page;
+
+ page = pkram_alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!page) {
+ pr_err("PKRAM: Failed to allocate super block\n");
+ return 0;
+ }
+ pkram_sb = page_address(page);
+ }
+
+ /*
+ * Build auxiliary doubly-linked list of nodes connected through
+ * page::lru for convenience sake.
+ */
+ pfn = pkram_sb->node_pfn;
+ while (pfn) {
+ node = pfn_to_kaddr(pfn);
+ pkram_insert_node(node);
+ pfn = node->node_pfn;
+ }
+ return 1;
+}
+
static int __init pkram_init(void)
{
- register_reboot_notifier(&pkram_reboot_notifier);
+ if (pkram_init_sb()) {
+ register_reboot_notifier(&pkram_reboot_notifier);
+ sysfs_update_group(kernel_kobj, &pkram_attr_group);
+ }
return 0;
}
module_init(pkram_init);
The PKRAM super block is the starting point for restoring preserved memory. By providing the super block to the new kernel at boot time, preserved memory can be reserved and made available to be restored. To point the kernel to the location of the super block, one passes its pfn via the 'pkram' boot param. For that purpose, the pkram super block pfn is exported via /sys/kernel/pkram. If none is passed, any preserved memory will not be kept, and a new super block will be allocated. Originally-by: Vladimir Davydov <vdavydov.dev@gmail.com> Signed-off-by: Anthony Yznaga <anthony.yznaga@oracle.com> --- mm/pkram.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 2 deletions(-)