diff options
| -rw-r--r-- | arch/s390/Kconfig | 1 | ||||
| -rw-r--r-- | arch/s390/kernel/suspend.c | 118 | ||||
| -rw-r--r-- | arch/s390/kernel/swsusp_asm64.S | 3 | ||||
| -rw-r--r-- | include/linux/suspend.h | 34 | ||||
| -rw-r--r-- | kernel/power/Kconfig | 3 | ||||
| -rw-r--r-- | kernel/power/snapshot.c | 18 |
6 files changed, 177 insertions, 0 deletions
diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index ed5cb5af5281..6b99fc3f9b63 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig | |||
| @@ -91,6 +91,7 @@ config S390 | |||
| 91 | select HAVE_ARCH_MUTEX_CPU_RELAX | 91 | select HAVE_ARCH_MUTEX_CPU_RELAX |
| 92 | select HAVE_ARCH_JUMP_LABEL if !MARCH_G5 | 92 | select HAVE_ARCH_JUMP_LABEL if !MARCH_G5 |
| 93 | select HAVE_RCU_TABLE_FREE if SMP | 93 | select HAVE_RCU_TABLE_FREE if SMP |
| 94 | select ARCH_SAVE_PAGE_KEYS if HIBERNATION | ||
| 94 | select ARCH_INLINE_SPIN_TRYLOCK | 95 | select ARCH_INLINE_SPIN_TRYLOCK |
| 95 | select ARCH_INLINE_SPIN_TRYLOCK_BH | 96 | select ARCH_INLINE_SPIN_TRYLOCK_BH |
| 96 | select ARCH_INLINE_SPIN_LOCK | 97 | select ARCH_INLINE_SPIN_LOCK |
diff --git a/arch/s390/kernel/suspend.c b/arch/s390/kernel/suspend.c index cf9e5c6d5527..b6f9afed74ec 100644 --- a/arch/s390/kernel/suspend.c +++ b/arch/s390/kernel/suspend.c | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | */ | 7 | */ |
| 8 | 8 | ||
| 9 | #include <linux/pfn.h> | 9 | #include <linux/pfn.h> |
| 10 | #include <linux/mm.h> | ||
| 10 | #include <asm/system.h> | 11 | #include <asm/system.h> |
| 11 | 12 | ||
| 12 | /* | 13 | /* |
| @@ -14,6 +15,123 @@ | |||
| 14 | */ | 15 | */ |
| 15 | extern const void __nosave_begin, __nosave_end; | 16 | extern const void __nosave_begin, __nosave_end; |
| 16 | 17 | ||
| 18 | /* | ||
| 19 | * The restore of the saved pages in an hibernation image will set | ||
| 20 | * the change and referenced bits in the storage key for each page. | ||
| 21 | * Overindication of the referenced bits after an hibernation cycle | ||
| 22 | * does not cause any harm but the overindication of the change bits | ||
| 23 | * would cause trouble. | ||
| 24 | * Use the ARCH_SAVE_PAGE_KEYS hooks to save the storage key of each | ||
| 25 | * page to the most significant byte of the associated page frame | ||
| 26 | * number in the hibernation image. | ||
| 27 | */ | ||
| 28 | |||
| 29 | /* | ||
| 30 | * Key storage is allocated as a linked list of pages. | ||
| 31 | * The size of the keys array is (PAGE_SIZE - sizeof(long)) | ||
| 32 | */ | ||
| 33 | struct page_key_data { | ||
| 34 | struct page_key_data *next; | ||
| 35 | unsigned char data[]; | ||
| 36 | }; | ||
| 37 | |||
| 38 | #define PAGE_KEY_DATA_SIZE (PAGE_SIZE - sizeof(struct page_key_data *)) | ||
| 39 | |||
| 40 | static struct page_key_data *page_key_data; | ||
| 41 | static struct page_key_data *page_key_rp, *page_key_wp; | ||
| 42 | static unsigned long page_key_rx, page_key_wx; | ||
| 43 | |||
| 44 | /* | ||
| 45 | * For each page in the hibernation image one additional byte is | ||
| 46 | * stored in the most significant byte of the page frame number. | ||
| 47 | * On suspend no additional memory is required but on resume the | ||
| 48 | * keys need to be memorized until the page data has been restored. | ||
| 49 | * Only then can the storage keys be set to their old state. | ||
| 50 | */ | ||
| 51 | unsigned long page_key_additional_pages(unsigned long pages) | ||
| 52 | { | ||
| 53 | return DIV_ROUND_UP(pages, PAGE_KEY_DATA_SIZE); | ||
| 54 | } | ||
| 55 | |||
| 56 | /* | ||
| 57 | * Free page_key_data list of arrays. | ||
| 58 | */ | ||
| 59 | void page_key_free(void) | ||
| 60 | { | ||
| 61 | struct page_key_data *pkd; | ||
| 62 | |||
| 63 | while (page_key_data) { | ||
| 64 | pkd = page_key_data; | ||
| 65 | page_key_data = pkd->next; | ||
| 66 | free_page((unsigned long) pkd); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | /* | ||
| 71 | * Allocate page_key_data list of arrays with enough room to store | ||
| 72 | * one byte for each page in the hibernation image. | ||
| 73 | */ | ||
| 74 | int page_key_alloc(unsigned long pages) | ||
| 75 | { | ||
| 76 | struct page_key_data *pk; | ||
| 77 | unsigned long size; | ||
| 78 | |||
| 79 | size = DIV_ROUND_UP(pages, PAGE_KEY_DATA_SIZE); | ||
| 80 | while (size--) { | ||
| 81 | pk = (struct page_key_data *) get_zeroed_page(GFP_KERNEL); | ||
| 82 | if (!pk) { | ||
| 83 | page_key_free(); | ||
| 84 | return -ENOMEM; | ||
| 85 | } | ||
| 86 | pk->next = page_key_data; | ||
| 87 | page_key_data = pk; | ||
| 88 | } | ||
| 89 | page_key_rp = page_key_wp = page_key_data; | ||
| 90 | page_key_rx = page_key_wx = 0; | ||
| 91 | return 0; | ||
| 92 | } | ||
| 93 | |||
| 94 | /* | ||
| 95 | * Save the storage key into the upper 8 bits of the page frame number. | ||
| 96 | */ | ||
| 97 | void page_key_read(unsigned long *pfn) | ||
| 98 | { | ||
| 99 | unsigned long addr; | ||
| 100 | |||
| 101 | addr = (unsigned long) page_address(pfn_to_page(*pfn)); | ||
| 102 | *(unsigned char *) pfn = (unsigned char) page_get_storage_key(addr); | ||
| 103 | } | ||
| 104 | |||
| 105 | /* | ||
| 106 | * Extract the storage key from the upper 8 bits of the page frame number | ||
| 107 | * and store it in the page_key_data list of arrays. | ||
| 108 | */ | ||
| 109 | void page_key_memorize(unsigned long *pfn) | ||
| 110 | { | ||
| 111 | page_key_wp->data[page_key_wx] = *(unsigned char *) pfn; | ||
| 112 | *(unsigned char *) pfn = 0; | ||
| 113 | if (++page_key_wx < PAGE_KEY_DATA_SIZE) | ||
| 114 | return; | ||
| 115 | page_key_wp = page_key_wp->next; | ||
| 116 | page_key_wx = 0; | ||
| 117 | } | ||
| 118 | |||
| 119 | /* | ||
| 120 | * Get the next key from the page_key_data list of arrays and set the | ||
| 121 | * storage key of the page referred by @address. If @address refers to | ||
| 122 | * a "safe" page the swsusp_arch_resume code will transfer the storage | ||
| 123 | * key from the buffer page to the original page. | ||
| 124 | */ | ||
| 125 | void page_key_write(void *address) | ||
| 126 | { | ||
| 127 | page_set_storage_key((unsigned long) address, | ||
| 128 | page_key_rp->data[page_key_rx], 0); | ||
| 129 | if (++page_key_rx >= PAGE_KEY_DATA_SIZE) | ||
| 130 | return; | ||
| 131 | page_key_rp = page_key_rp->next; | ||
| 132 | page_key_rx = 0; | ||
| 133 | } | ||
| 134 | |||
| 17 | int pfn_is_nosave(unsigned long pfn) | 135 | int pfn_is_nosave(unsigned long pfn) |
| 18 | { | 136 | { |
| 19 | unsigned long nosave_begin_pfn = PFN_DOWN(__pa(&__nosave_begin)); | 137 | unsigned long nosave_begin_pfn = PFN_DOWN(__pa(&__nosave_begin)); |
diff --git a/arch/s390/kernel/swsusp_asm64.S b/arch/s390/kernel/swsusp_asm64.S index 51bcdb50a230..acb78cdee896 100644 --- a/arch/s390/kernel/swsusp_asm64.S +++ b/arch/s390/kernel/swsusp_asm64.S | |||
| @@ -136,11 +136,14 @@ ENTRY(swsusp_arch_resume) | |||
| 136 | 0: | 136 | 0: |
| 137 | lg %r2,8(%r1) | 137 | lg %r2,8(%r1) |
| 138 | lg %r4,0(%r1) | 138 | lg %r4,0(%r1) |
| 139 | iske %r0,%r4 | ||
| 139 | lghi %r3,PAGE_SIZE | 140 | lghi %r3,PAGE_SIZE |
| 140 | lghi %r5,PAGE_SIZE | 141 | lghi %r5,PAGE_SIZE |
| 141 | 1: | 142 | 1: |
| 142 | mvcle %r2,%r4,0 | 143 | mvcle %r2,%r4,0 |
| 143 | jo 1b | 144 | jo 1b |
| 145 | lg %r2,8(%r1) | ||
| 146 | sske %r0,%r2 | ||
| 144 | lg %r1,16(%r1) | 147 | lg %r1,16(%r1) |
| 145 | ltgr %r1,%r1 | 148 | ltgr %r1,%r1 |
| 146 | jnz 0b | 149 | jnz 0b |
diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 76f42e49b72d..46f3548aef2d 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h | |||
| @@ -386,4 +386,38 @@ static inline void unlock_system_sleep(void) | |||
| 386 | } | 386 | } |
| 387 | #endif | 387 | #endif |
| 388 | 388 | ||
| 389 | #ifdef CONFIG_ARCH_SAVE_PAGE_KEYS | ||
| 390 | /* | ||
| 391 | * The ARCH_SAVE_PAGE_KEYS functions can be used by an architecture | ||
| 392 | * to save/restore additional information to/from the array of page | ||
| 393 | * frame numbers in the hibernation image. For s390 this is used to | ||
| 394 | * save and restore the storage key for each page that is included | ||
| 395 | * in the hibernation image. | ||
| 396 | */ | ||
| 397 | unsigned long page_key_additional_pages(unsigned long pages); | ||
| 398 | int page_key_alloc(unsigned long pages); | ||
| 399 | void page_key_free(void); | ||
| 400 | void page_key_read(unsigned long *pfn); | ||
| 401 | void page_key_memorize(unsigned long *pfn); | ||
| 402 | void page_key_write(void *address); | ||
| 403 | |||
| 404 | #else /* !CONFIG_ARCH_SAVE_PAGE_KEYS */ | ||
| 405 | |||
| 406 | static inline unsigned long page_key_additional_pages(unsigned long pages) | ||
| 407 | { | ||
| 408 | return 0; | ||
| 409 | } | ||
| 410 | |||
| 411 | static inline int page_key_alloc(unsigned long pages) | ||
| 412 | { | ||
| 413 | return 0; | ||
| 414 | } | ||
| 415 | |||
| 416 | static inline void page_key_free(void) {} | ||
| 417 | static inline void page_key_read(unsigned long *pfn) {} | ||
| 418 | static inline void page_key_memorize(unsigned long *pfn) {} | ||
| 419 | static inline void page_key_write(void *address) {} | ||
| 420 | |||
| 421 | #endif /* !CONFIG_ARCH_SAVE_PAGE_KEYS */ | ||
| 422 | |||
| 389 | #endif /* _LINUX_SUSPEND_H */ | 423 | #endif /* _LINUX_SUSPEND_H */ |
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 3744c594b19b..e01e6899592c 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig | |||
| @@ -65,6 +65,9 @@ config HIBERNATION | |||
| 65 | 65 | ||
| 66 | For more information take a look at <file:Documentation/power/swsusp.txt>. | 66 | For more information take a look at <file:Documentation/power/swsusp.txt>. |
| 67 | 67 | ||
| 68 | config ARCH_SAVE_PAGE_KEYS | ||
| 69 | bool | ||
| 70 | |||
| 68 | config PM_STD_PARTITION | 71 | config PM_STD_PARTITION |
| 69 | string "Default resume partition" | 72 | string "Default resume partition" |
| 70 | depends on HIBERNATION | 73 | depends on HIBERNATION |
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 06efa54f93d6..cbe2c1441392 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c | |||
| @@ -1339,6 +1339,9 @@ int hibernate_preallocate_memory(void) | |||
| 1339 | count += highmem; | 1339 | count += highmem; |
| 1340 | count -= totalreserve_pages; | 1340 | count -= totalreserve_pages; |
| 1341 | 1341 | ||
| 1342 | /* Add number of pages required for page keys (s390 only). */ | ||
| 1343 | size += page_key_additional_pages(saveable); | ||
| 1344 | |||
| 1342 | /* Compute the maximum number of saveable pages to leave in memory. */ | 1345 | /* Compute the maximum number of saveable pages to leave in memory. */ |
| 1343 | max_size = (count - (size + PAGES_FOR_IO)) / 2 | 1346 | max_size = (count - (size + PAGES_FOR_IO)) / 2 |
| 1344 | - 2 * DIV_ROUND_UP(reserved_size, PAGE_SIZE); | 1347 | - 2 * DIV_ROUND_UP(reserved_size, PAGE_SIZE); |
| @@ -1662,6 +1665,8 @@ pack_pfns(unsigned long *buf, struct memory_bitmap *bm) | |||
| 1662 | buf[j] = memory_bm_next_pfn(bm); | 1665 | buf[j] = memory_bm_next_pfn(bm); |
| 1663 | if (unlikely(buf[j] == BM_END_OF_MAP)) | 1666 | if (unlikely(buf[j] == BM_END_OF_MAP)) |
| 1664 | break; | 1667 | break; |
| 1668 | /* Save page key for data page (s390 only). */ | ||
| 1669 | page_key_read(buf + j); | ||
| 1665 | } | 1670 | } |
| 1666 | } | 1671 | } |
| 1667 | 1672 | ||
| @@ -1821,6 +1826,9 @@ static int unpack_orig_pfns(unsigned long *buf, struct memory_bitmap *bm) | |||
| 1821 | if (unlikely(buf[j] == BM_END_OF_MAP)) | 1826 | if (unlikely(buf[j] == BM_END_OF_MAP)) |
| 1822 | break; | 1827 | break; |
| 1823 | 1828 | ||
| 1829 | /* Extract and buffer page key for data page (s390 only). */ | ||
| 1830 | page_key_memorize(buf + j); | ||
| 1831 | |||
| 1824 | if (memory_bm_pfn_present(bm, buf[j])) | 1832 | if (memory_bm_pfn_present(bm, buf[j])) |
| 1825 | memory_bm_set_bit(bm, buf[j]); | 1833 | memory_bm_set_bit(bm, buf[j]); |
| 1826 | else | 1834 | else |
| @@ -2223,6 +2231,11 @@ int snapshot_write_next(struct snapshot_handle *handle) | |||
| 2223 | if (error) | 2231 | if (error) |
| 2224 | return error; | 2232 | return error; |
| 2225 | 2233 | ||
| 2234 | /* Allocate buffer for page keys. */ | ||
| 2235 | error = page_key_alloc(nr_copy_pages); | ||
| 2236 | if (error) | ||
| 2237 | return error; | ||
| 2238 | |||
| 2226 | } else if (handle->cur <= nr_meta_pages + 1) { | 2239 | } else if (handle->cur <= nr_meta_pages + 1) { |
| 2227 | error = unpack_orig_pfns(buffer, ©_bm); | 2240 | error = unpack_orig_pfns(buffer, ©_bm); |
| 2228 | if (error) | 2241 | if (error) |
| @@ -2243,6 +2256,8 @@ int snapshot_write_next(struct snapshot_handle *handle) | |||
| 2243 | } | 2256 | } |
| 2244 | } else { | 2257 | } else { |
| 2245 | copy_last_highmem_page(); | 2258 | copy_last_highmem_page(); |
| 2259 | /* Restore page key for data page (s390 only). */ | ||
| 2260 | page_key_write(handle->buffer); | ||
| 2246 | handle->buffer = get_buffer(&orig_bm, &ca); | 2261 | handle->buffer = get_buffer(&orig_bm, &ca); |
| 2247 | if (IS_ERR(handle->buffer)) | 2262 | if (IS_ERR(handle->buffer)) |
| 2248 | return PTR_ERR(handle->buffer); | 2263 | return PTR_ERR(handle->buffer); |
| @@ -2264,6 +2279,9 @@ int snapshot_write_next(struct snapshot_handle *handle) | |||
| 2264 | void snapshot_write_finalize(struct snapshot_handle *handle) | 2279 | void snapshot_write_finalize(struct snapshot_handle *handle) |
| 2265 | { | 2280 | { |
| 2266 | copy_last_highmem_page(); | 2281 | copy_last_highmem_page(); |
| 2282 | /* Restore page key for data page (s390 only). */ | ||
| 2283 | page_key_write(handle->buffer); | ||
| 2284 | page_key_free(); | ||
| 2267 | /* Free only if we have loaded the image entirely */ | 2285 | /* Free only if we have loaded the image entirely */ |
| 2268 | if (handle->cur > 1 && handle->cur > nr_meta_pages + nr_copy_pages) { | 2286 | if (handle->cur > 1 && handle->cur > nr_meta_pages + nr_copy_pages) { |
| 2269 | memory_bm_free(&orig_bm, PG_UNSAFE_CLEAR); | 2287 | memory_bm_free(&orig_bm, PG_UNSAFE_CLEAR); |
