PM / Hibernate: Include storage keys in hibernation image on s390
authorMartin Schwidefsky <schwidefsky@de.ibm.com>
Wed, 17 Aug 2011 18:42:24 +0000 (20:42 +0200)
committerRafael J. Wysocki <rjw@sisk.pl>
Sun, 16 Oct 2011 21:27:46 +0000 (23:27 +0200)
For s390 there is one additional byte associated with each page,
the storage key. This byte contains the referenced and changed
bits and needs to be included into the hibernation image.
If the storage keys are not restored to their previous state all
original pages would appear to be dirty. This can cause
inconsistencies e.g. with read-only filesystems.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
arch/s390/Kconfig
arch/s390/kernel/suspend.c
arch/s390/kernel/swsusp_asm64.S
include/linux/suspend.h
kernel/power/Kconfig
kernel/power/snapshot.c

index ed5cb5af52816940d45c6610e3e2e0d8c1d60a82..6b99fc3f9b6380f529ea5e65737194b079d47044 100644 (file)
@@ -91,6 +91,7 @@ config S390
        select HAVE_ARCH_MUTEX_CPU_RELAX
        select HAVE_ARCH_JUMP_LABEL if !MARCH_G5
        select HAVE_RCU_TABLE_FREE if SMP
+       select ARCH_SAVE_PAGE_KEYS if HIBERNATION
        select ARCH_INLINE_SPIN_TRYLOCK
        select ARCH_INLINE_SPIN_TRYLOCK_BH
        select ARCH_INLINE_SPIN_LOCK
index cf9e5c6d55274ce2da221e5f258fab08c58ee21e..b6f9afed74ec8e3c7e1082c82185e491b8b213aa 100644 (file)
@@ -7,6 +7,7 @@
  */
 
 #include <linux/pfn.h>
+#include <linux/mm.h>
 #include <asm/system.h>
 
 /*
  */
 extern const void __nosave_begin, __nosave_end;
 
+/*
+ * The restore of the saved pages in an hibernation image will set
+ * the change and referenced bits in the storage key for each page.
+ * Overindication of the referenced bits after an hibernation cycle
+ * does not cause any harm but the overindication of the change bits
+ * would cause trouble.
+ * Use the ARCH_SAVE_PAGE_KEYS hooks to save the storage key of each
+ * page to the most significant byte of the associated page frame
+ * number in the hibernation image.
+ */
+
+/*
+ * Key storage is allocated as a linked list of pages.
+ * The size of the keys array is (PAGE_SIZE - sizeof(long))
+ */
+struct page_key_data {
+       struct page_key_data *next;
+       unsigned char data[];
+};
+
+#define PAGE_KEY_DATA_SIZE     (PAGE_SIZE - sizeof(struct page_key_data *))
+
+static struct page_key_data *page_key_data;
+static struct page_key_data *page_key_rp, *page_key_wp;
+static unsigned long page_key_rx, page_key_wx;
+
+/*
+ * For each page in the hibernation image one additional byte is
+ * stored in the most significant byte of the page frame number.
+ * On suspend no additional memory is required but on resume the
+ * keys need to be memorized until the page data has been restored.
+ * Only then can the storage keys be set to their old state.
+ */
+unsigned long page_key_additional_pages(unsigned long pages)
+{
+       return DIV_ROUND_UP(pages, PAGE_KEY_DATA_SIZE);
+}
+
+/*
+ * Free page_key_data list of arrays.
+ */
+void page_key_free(void)
+{
+       struct page_key_data *pkd;
+
+       while (page_key_data) {
+               pkd = page_key_data;
+               page_key_data = pkd->next;
+               free_page((unsigned long) pkd);
+       }
+}
+
+/*
+ * Allocate page_key_data list of arrays with enough room to store
+ * one byte for each page in the hibernation image.
+ */
+int page_key_alloc(unsigned long pages)
+{
+       struct page_key_data *pk;
+       unsigned long size;
+
+       size = DIV_ROUND_UP(pages, PAGE_KEY_DATA_SIZE);
+       while (size--) {
+               pk = (struct page_key_data *) get_zeroed_page(GFP_KERNEL);
+               if (!pk) {
+                       page_key_free();
+                       return -ENOMEM;
+               }
+               pk->next = page_key_data;
+               page_key_data = pk;
+       }
+       page_key_rp = page_key_wp = page_key_data;
+       page_key_rx = page_key_wx = 0;
+       return 0;
+}
+
+/*
+ * Save the storage key into the upper 8 bits of the page frame number.
+ */
+void page_key_read(unsigned long *pfn)
+{
+       unsigned long addr;
+
+       addr = (unsigned long) page_address(pfn_to_page(*pfn));
+       *(unsigned char *) pfn = (unsigned char) page_get_storage_key(addr);
+}
+
+/*
+ * Extract the storage key from the upper 8 bits of the page frame number
+ * and store it in the page_key_data list of arrays.
+ */
+void page_key_memorize(unsigned long *pfn)
+{
+       page_key_wp->data[page_key_wx] = *(unsigned char *) pfn;
+       *(unsigned char *) pfn = 0;
+       if (++page_key_wx < PAGE_KEY_DATA_SIZE)
+               return;
+       page_key_wp = page_key_wp->next;
+       page_key_wx = 0;
+}
+
+/*
+ * Get the next key from the page_key_data list of arrays and set the
+ * storage key of the page referred by @address. If @address refers to
+ * a "safe" page the swsusp_arch_resume code will transfer the storage
+ * key from the buffer page to the original page.
+ */
+void page_key_write(void *address)
+{
+       page_set_storage_key((unsigned long) address,
+                            page_key_rp->data[page_key_rx], 0);
+       if (++page_key_rx >= PAGE_KEY_DATA_SIZE)
+               return;
+       page_key_rp = page_key_rp->next;
+       page_key_rx = 0;
+}
+
 int pfn_is_nosave(unsigned long pfn)
 {
        unsigned long nosave_begin_pfn = PFN_DOWN(__pa(&__nosave_begin));
index 51bcdb50a23011a520afb29b092cc8aa871d1c80..acb78cdee896c1b9fe9c2d74ce212b254044a461 100644 (file)
@@ -136,11 +136,14 @@ ENTRY(swsusp_arch_resume)
 0:
        lg      %r2,8(%r1)
        lg      %r4,0(%r1)
+       iske    %r0,%r4
        lghi    %r3,PAGE_SIZE
        lghi    %r5,PAGE_SIZE
 1:
        mvcle   %r2,%r4,0
        jo      1b
+       lg      %r2,8(%r1)
+       sske    %r0,%r2
        lg      %r1,16(%r1)
        ltgr    %r1,%r1
        jnz     0b
index 76f42e49b72deea43a190677822070bea4e53eb1..46f3548aef2d23368c4f655f7e3d9cd82fa79c94 100644 (file)
@@ -386,4 +386,38 @@ static inline void unlock_system_sleep(void)
 }
 #endif
 
+#ifdef CONFIG_ARCH_SAVE_PAGE_KEYS
+/*
+ * The ARCH_SAVE_PAGE_KEYS functions can be used by an architecture
+ * to save/restore additional information to/from the array of page
+ * frame numbers in the hibernation image. For s390 this is used to
+ * save and restore the storage key for each page that is included
+ * in the hibernation image.
+ */
+unsigned long page_key_additional_pages(unsigned long pages);
+int page_key_alloc(unsigned long pages);
+void page_key_free(void);
+void page_key_read(unsigned long *pfn);
+void page_key_memorize(unsigned long *pfn);
+void page_key_write(void *address);
+
+#else /* !CONFIG_ARCH_SAVE_PAGE_KEYS */
+
+static inline unsigned long page_key_additional_pages(unsigned long pages)
+{
+       return 0;
+}
+
+static inline int  page_key_alloc(unsigned long pages)
+{
+       return 0;
+}
+
+static inline void page_key_free(void) {}
+static inline void page_key_read(unsigned long *pfn) {}
+static inline void page_key_memorize(unsigned long *pfn) {}
+static inline void page_key_write(void *address) {}
+
+#endif /* !CONFIG_ARCH_SAVE_PAGE_KEYS */
+
 #endif /* _LINUX_SUSPEND_H */
index 3744c594b19b18b779b8d1f8660788a1e0aeacbf..e01e6899592c6b43b3d983fc300d8f62cfc2cb4f 100644 (file)
@@ -65,6 +65,9 @@ config HIBERNATION
 
          For more information take a look at <file:Documentation/power/swsusp.txt>.
 
+config ARCH_SAVE_PAGE_KEYS
+       bool
+
 config PM_STD_PARTITION
        string "Default resume partition"
        depends on HIBERNATION
index 06efa54f93d6686bfc2f218d92149ee56cf9e511..cbe2c14413927c665f25d778fb03fde84af73421 100644 (file)
@@ -1339,6 +1339,9 @@ int hibernate_preallocate_memory(void)
        count += highmem;
        count -= totalreserve_pages;
 
+       /* Add number of pages required for page keys (s390 only). */
+       size += page_key_additional_pages(saveable);
+
        /* Compute the maximum number of saveable pages to leave in memory. */
        max_size = (count - (size + PAGES_FOR_IO)) / 2
                        - 2 * DIV_ROUND_UP(reserved_size, PAGE_SIZE);
@@ -1662,6 +1665,8 @@ pack_pfns(unsigned long *buf, struct memory_bitmap *bm)
                buf[j] = memory_bm_next_pfn(bm);
                if (unlikely(buf[j] == BM_END_OF_MAP))
                        break;
+               /* Save page key for data page (s390 only). */
+               page_key_read(buf + j);
        }
 }
 
@@ -1821,6 +1826,9 @@ static int unpack_orig_pfns(unsigned long *buf, struct memory_bitmap *bm)
                if (unlikely(buf[j] == BM_END_OF_MAP))
                        break;
 
+               /* Extract and buffer page key for data page (s390 only). */
+               page_key_memorize(buf + j);
+
                if (memory_bm_pfn_present(bm, buf[j]))
                        memory_bm_set_bit(bm, buf[j]);
                else
@@ -2223,6 +2231,11 @@ int snapshot_write_next(struct snapshot_handle *handle)
                if (error)
                        return error;
 
+               /* Allocate buffer for page keys. */
+               error = page_key_alloc(nr_copy_pages);
+               if (error)
+                       return error;
+
        } else if (handle->cur <= nr_meta_pages + 1) {
                error = unpack_orig_pfns(buffer, &copy_bm);
                if (error)
@@ -2243,6 +2256,8 @@ int snapshot_write_next(struct snapshot_handle *handle)
                }
        } else {
                copy_last_highmem_page();
+               /* Restore page key for data page (s390 only). */
+               page_key_write(handle->buffer);
                handle->buffer = get_buffer(&orig_bm, &ca);
                if (IS_ERR(handle->buffer))
                        return PTR_ERR(handle->buffer);
@@ -2264,6 +2279,9 @@ int snapshot_write_next(struct snapshot_handle *handle)
 void snapshot_write_finalize(struct snapshot_handle *handle)
 {
        copy_last_highmem_page();
+       /* Restore page key for data page (s390 only). */
+       page_key_write(handle->buffer);
+       page_key_free();
        /* Free only if we have loaded the image entirely */
        if (handle->cur > 1 && handle->cur > nr_meta_pages + nr_copy_pages) {
                memory_bm_free(&orig_bm, PG_UNSAFE_CLEAR);