/*
* linux/kernel/power/swsusp.c
*
- * This file is to realize architecture-independent
- * machine suspend feature using pretty near only high-level routines
+ * This file provides code to write suspend image to swap and read it back.
*
* Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu>
- * Copyright (C) 1998,2001-2004 Pavel Machek <pavel@suse.cz>
+ * Copyright (C) 1998,2001-2005 Pavel Machek <pavel@suse.cz>
*
* This file is released under the GPLv2.
*
* I'd like to thank the following people for their work:
- *
+ *
* Pavel Machek <pavel@ucw.cz>:
* Modifications, defectiveness pointing, being with me at the very beginning,
* suspend to swap space, stop all tasks. Port to 2.4.18-ac and 2.5.17.
*
- * Steve Doddi <dirk@loth.demon.co.uk>:
+ * Steve Doddi <dirk@loth.demon.co.uk>:
* Support the possibility of hardware state restoring.
*
* Raph <grey.havens@earthling.net>:
* Alex Badea <vampire@go.ro>:
* Fixed runaway init
*
+ * Rafael J. Wysocki <rjw@sisk.pl>
+ * Added the swap map data structure and reworked the handling of swap
+ *
* More state savers are welcome. Especially for the scsi layer...
*
* For TODOs,FIXMEs also look in Documentation/power/swsusp.txt
#include <linux/utsname.h>
#include <linux/version.h>
#include <linux/delay.h>
-#include <linux/reboot.h>
#include <linux/bitops.h>
-#include <linux/vt_kern.h>
-#include <linux/kbd_kern.h>
-#include <linux/keyboard.h>
#include <linux/spinlock.h>
#include <linux/genhd.h>
#include <linux/kernel.h>
#include <linux/swapops.h>
#include <linux/bootmem.h>
#include <linux/syscalls.h>
-#include <linux/console.h>
#include <linux/highmem.h>
+#include <linux/bio.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/pgtable.h>
+#include <asm/tlbflush.h>
#include <asm/io.h>
#include "power.h"
-unsigned char software_suspend_enabled = 0;
-
-#define NORESUME 1
-#define RESUME_SPECIFIED 2
-
-/* References to section boundaries */
-extern char __nosave_begin, __nosave_end;
-
-extern int is_head_of_free_region(struct page *);
-
-/* Locks */
-spinlock_t suspend_pagedir_lock __nosavedata = SPIN_LOCK_UNLOCKED;
-
-/* Variables to be preserved over suspend */
-static int pagedir_order_check;
-static int nr_copy_pages_check;
-
-static int resume_status;
-static char resume_file[256] = ""; /* For resume= kernel option */
-static dev_t resume_device;
-/* Local variables that should not be affected by save */
-unsigned int nr_copy_pages __nosavedata = 0;
-
-/* Suspend pagedir is allocated before final copy, therefore it
- must be freed after resume
-
- Warning: this is evil. There are actually two pagedirs at time of
- resume. One is "pagedir_save", which is empty frame allocated at
- time of suspend, that must be freed. Second is "pagedir_nosave",
- allocated at time of resume, that travels through memory not to
- collide with anything.
-
- Warning: this is even more evil than it seems. Pagedirs this file
- talks about are completely different from page directories used by
- MMU hardware.
- */
-suspend_pagedir_t *pagedir_nosave __nosavedata = NULL;
-static suspend_pagedir_t *pagedir_save;
-static int pagedir_order __nosavedata = 0;
-
-struct link {
- char dummy[PAGE_SIZE - sizeof(swp_entry_t)];
- swp_entry_t next;
-};
-
-union diskpage {
- union swap_header swh;
- struct link link;
- struct suspend_header sh;
-};
-
/*
- * XXX: We try to keep some more pages free so that I/O operations succeed
- * without paging. Might this be more?
+ * Preferred image size in bytes (tunable via /sys/power/image_size).
+ * When it is set to N, swsusp will do its best to ensure the image
+ * size will not exceed N bytes, but if that is impossible, it will
+ * try to create the smallest image possible.
*/
-#define PAGES_FOR_IO 512
-
-static const char name_suspend[] = "Suspend Machine: ";
-static const char name_resume[] = "Resume Machine: ";
+unsigned long image_size = 500 * 1024 * 1024;
-/*
- * Debug
- */
-#define DEBUG_DEFAULT
-#undef DEBUG_PROCESS
-#undef DEBUG_SLOW
-#define TEST_SWSUSP 0 /* Set to 1 to reboot instead of halt machine after suspension */
-
-#ifdef DEBUG_DEFAULT
-# define PRINTK(f, a...) printk(f, ## a)
+#ifdef CONFIG_HIGHMEM
+unsigned int count_highmem_pages(void);
+int save_highmem(void);
+int restore_highmem(void);
#else
-# define PRINTK(f, a...) do { } while(0)
+static int save_highmem(void) { return 0; }
+static int restore_highmem(void) { return 0; }
+static unsigned int count_highmem_pages(void) { return 0; }
#endif
-#ifdef DEBUG_SLOW
-#define MDELAY(a) mdelay(a)
-#else
-#define MDELAY(a) do { } while(0)
-#endif
+extern char resume_file[];
+
+#define SWSUSP_SIG "S1SUSPEND"
+
+static struct swsusp_header {
+ char reserved[PAGE_SIZE - 20 - sizeof(swp_entry_t)];
+ swp_entry_t image;
+ char orig_sig[10];
+ char sig[10];
+} __attribute__((packed, aligned(PAGE_SIZE))) swsusp_header;
+
+static struct swsusp_info swsusp_info;
/*
* Saving part...
*/
-static __inline__ int fill_suspend_header(struct suspend_header *sh)
-{
- memset((char *)sh, 0, sizeof(*sh));
-
- sh->version_code = LINUX_VERSION_CODE;
- sh->num_physpages = num_physpages;
- strncpy(sh->machine, system_utsname.machine, 8);
- strncpy(sh->version, system_utsname.version, 20);
- /* FIXME: Is this bogus? --RR */
- sh->num_cpus = num_online_cpus();
- sh->page_size = PAGE_SIZE;
- sh->suspend_pagedir = pagedir_nosave;
- BUG_ON (pagedir_save != pagedir_nosave);
- sh->num_pbes = nr_copy_pages;
- /* TODO: needed? mounted fs' last mounted date comparison
- * [so they haven't been mounted since last suspend.
- * Maybe it isn't.] [we'd need to do this for _all_ fs-es]
- */
- return 0;
-}
-
-/* We memorize in swapfile_used what swap devices are used for suspension */
-#define SWAPFILE_UNUSED 0
-#define SWAPFILE_SUSPEND 1 /* This is the suspending device */
-#define SWAPFILE_IGNORED 2 /* Those are other swap devices ignored for suspension */
+static unsigned short root_swap = 0xffff;
-static unsigned short swapfile_used[MAX_SWAPFILES];
-static unsigned short root_swap;
-#define MARK_SWAP_SUSPEND 0
-#define MARK_SWAP_RESUME 2
-
-static void mark_swapfiles(swp_entry_t prev, int mode)
+static int mark_swapfiles(swp_entry_t start)
{
- swp_entry_t entry;
- union diskpage *cur;
- struct page *page;
-
- if (root_swap == 0xFFFF) /* ignored */
- return;
+ int error;
- page = alloc_page(GFP_ATOMIC);
- if (!page)
- panic("Out of memory in mark_swapfiles");
- cur = page_address(page);
- /* XXX: this is dirty hack to get first page of swap file */
- entry = swp_entry(root_swap, 0);
- rw_swap_page_sync(READ, entry, page);
-
- if (mode == MARK_SWAP_RESUME) {
- if (!memcmp("S1",cur->swh.magic.magic,2))
- memcpy(cur->swh.magic.magic,"SWAP-SPACE",10);
- else if (!memcmp("S2",cur->swh.magic.magic,2))
- memcpy(cur->swh.magic.magic,"SWAPSPACE2",10);
- else printk("%sUnable to find suspended-data signature (%.10s - misspelled?\n",
- name_resume, cur->swh.magic.magic);
+ rw_swap_page_sync(READ,
+ swp_entry(root_swap, 0),
+ virt_to_page((unsigned long)&swsusp_header));
+ if (!memcmp("SWAP-SPACE",swsusp_header.sig, 10) ||
+ !memcmp("SWAPSPACE2",swsusp_header.sig, 10)) {
+ memcpy(swsusp_header.orig_sig,swsusp_header.sig, 10);
+ memcpy(swsusp_header.sig,SWSUSP_SIG, 10);
+ swsusp_header.image = start;
+ error = rw_swap_page_sync(WRITE,
+ swp_entry(root_swap, 0),
+ virt_to_page((unsigned long)
+ &swsusp_header));
} else {
- if ((!memcmp("SWAP-SPACE",cur->swh.magic.magic,10)))
- memcpy(cur->swh.magic.magic,"S1SUSP....",10);
- else if ((!memcmp("SWAPSPACE2",cur->swh.magic.magic,10)))
- memcpy(cur->swh.magic.magic,"S2SUSP....",10);
- else panic("\nSwapspace is not swapspace (%.10s)\n", cur->swh.magic.magic);
- cur->link.next = prev; /* prev is the first/last swap page of the resume area */
- /* link.next lies *no more* in last 4/8 bytes of magic */
+ pr_debug("swsusp: Partition is not swap space.\n");
+ error = -ENODEV;
}
- rw_swap_page_sync(WRITE, entry, page);
- __free_page(page);
+ return error;
}
-static void read_swapfiles(void) /* This is called before saving image */
+/*
+ * Check whether the swap device is the specified resume
+ * device, irrespective of whether they are specified by
+ * identical names.
+ *
+ * (Thus, device inode aliasing is allowed. You can say /dev/hda4
+ * instead of /dev/ide/host0/bus0/target0/lun0/part4 [if using devfs]
+ * and they'll be considered the same device. This is *necessary* for
+ * devfs, since the resume code can only recognize the form /dev/hda4,
+ * but the suspend code would see the long name.)
+ */
+static inline int is_resume_device(const struct swap_info_struct *swap_info)
{
- int i, len;
- static char buff[sizeof(resume_file)], *sname;
-
- len=strlen(resume_file);
- root_swap = 0xFFFF;
-
- swap_list_lock();
- for(i=0; i<MAX_SWAPFILES; i++) {
- if (swap_info[i].flags == 0) {
- swapfile_used[i]=SWAPFILE_UNUSED;
- } else {
- if(!len) {
- printk(KERN_WARNING "resume= option should be used to set suspend device" );
- if(root_swap == 0xFFFF) {
- swapfile_used[i] = SWAPFILE_SUSPEND;
- root_swap = i;
- } else
- swapfile_used[i] = SWAPFILE_IGNORED;
- } else {
- /* we ignore all swap devices that are not the resume_file */
- sname = d_path(swap_info[i].swap_file->f_dentry,
- swap_info[i].swap_file->f_vfsmnt,
- buff,
- sizeof(buff));
- if (!strcmp(sname, resume_file)) {
- swapfile_used[i] = SWAPFILE_SUSPEND;
- root_swap = i;
- } else {
-#if 0
- printk( "Resume: device %s (%x != %x) ignored\n", swap_info[i].swap_file->d_name.name, swap_info[i].swap_device, resume_device );
-#endif
- swapfile_used[i] = SWAPFILE_IGNORED;
- }
- }
- }
- }
- swap_list_unlock();
+ struct file *file = swap_info->swap_file;
+ struct inode *inode = file->f_dentry->d_inode;
+
+ return S_ISBLK(inode->i_mode) &&
+ swsusp_resume_device == MKDEV(imajor(inode), iminor(inode));
}
-static void lock_swapdevices(void) /* This is called after saving image so modification
- will be lost after resume... and that's what we want. */
+static int swsusp_swap_check(void) /* This is called before saving image */
{
int i;
- swap_list_lock();
- for(i = 0; i< MAX_SWAPFILES; i++)
- if(swapfile_used[i] == SWAPFILE_IGNORED) {
- swap_info[i].flags ^= 0xFF; /* we make the device unusable. A new call to
- lock_swapdevices can unlock the devices. */
+ spin_lock(&swap_lock);
+ for (i = 0; i < MAX_SWAPFILES; i++) {
+ if (!(swap_info[i].flags & SWP_WRITEOK))
+ continue;
+ if (!swsusp_resume_device || is_resume_device(swap_info + i)) {
+ spin_unlock(&swap_lock);
+ root_swap = i;
+ return 0;
}
- swap_list_unlock();
+ }
+ spin_unlock(&swap_lock);
+ return -ENODEV;
}
/**
- * write_suspend_image - Write entire image to disk.
+ * write_page - Write one page to a fresh swap location.
+ * @addr: Address we're writing.
+ * @loc: Place to store the entry we used.
*
- * After writing suspend signature to the disk, suspend may no
- * longer fail: we have ready-to-run image in swap, and rollback
- * would happen on next reboot -- corrupting data.
- *
- * Note: The buffer we allocate to use to write the suspend header is
- * not freed; its not needed since the system is going down anyway
- * (plus it causes an oops and I'm lazy^H^H^H^Htoo busy).
+ * Allocate a new swap entry and 'sync' it. Note we discard -EIO
+ * errors. That is an artifact left over from swsusp. It did not
+ * check the return of rw_swap_page_sync() at all, since most pages
+ * written back to swap would return -EIO.
+ * This is a partial improvement, since we will at least return other
+ * errors, though we need to eventually fix the damn code.
*/
-static int write_suspend_image(void)
+static int write_page(unsigned long addr, swp_entry_t *loc)
{
- int i;
- swp_entry_t entry, prev = { 0 };
- int nr_pgdir_pages = SUSPEND_PD_PAGES(nr_copy_pages);
- union diskpage *cur, *buffer = (union diskpage *)get_zeroed_page(GFP_ATOMIC);
- unsigned long address;
- struct page *page;
-
- if (!buffer)
- return -ENOMEM;
+ swp_entry_t entry;
+ int error = -ENOSPC;
- printk( "Writing data to swap (%d pages): ", nr_copy_pages );
- for (i=0; i<nr_copy_pages; i++) {
- if (!(i%100))
- printk( "." );
- if (!(entry = get_swap_page()).val)
- panic("\nNot enough swapspace when writing data" );
-
- if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND)
- panic("\nPage %d: not enough swapspace on suspend device", i );
-
- address = (pagedir_nosave+i)->address;
- page = virt_to_page(address);
- rw_swap_page_sync(WRITE, entry, page);
- (pagedir_nosave+i)->swap_address = entry;
+ entry = get_swap_page_of_type(root_swap);
+ if (swp_offset(entry)) {
+ error = rw_swap_page_sync(WRITE, entry, virt_to_page(addr));
+ if (!error || error == -EIO)
+ *loc = entry;
}
- printk( "|\n" );
- printk( "Writing pagedir (%d pages): ", nr_pgdir_pages);
- for (i=0; i<nr_pgdir_pages; i++) {
- cur = (union diskpage *)((char *) pagedir_nosave)+i;
- BUG_ON ((char *) cur != (((char *) pagedir_nosave) + i*PAGE_SIZE));
- printk( "." );
- if (!(entry = get_swap_page()).val) {
- printk(KERN_CRIT "Not enough swapspace when writing pgdir\n" );
- panic("Don't know how to recover");
- free_page((unsigned long) buffer);
- return -ENOSPC;
- }
+ return error;
+}
- if(swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND)
- panic("\nNot enough swapspace for pagedir on suspend device" );
+/**
+ * Swap map-handling functions
+ *
+ * The swap map is a data structure used for keeping track of each page
+ * written to the swap. It consists of many swap_map_page structures
+ * that contain each an array of MAP_PAGE_SIZE swap entries.
+ * These structures are linked together with the help of either the
+ * .next (in memory) or the .next_swap (in swap) member.
+ *
+ * The swap map is created during suspend. At that time we need to keep
+ * it in memory, because we have to free all of the allocated swap
+ * entries if an error occurs. The memory needed is preallocated
+ * so that we know in advance if there's enough of it.
+ *
+ * The first swap_map_page structure is filled with the swap entries that
+ * correspond to the first MAP_PAGE_SIZE data pages written to swap and
+ * so on. After the all of the data pages have been written, the order
+ * of the swap_map_page structures in the map is reversed so that they
+ * can be read from swap in the original order. This causes the data
+ * pages to be loaded in exactly the same order in which they have been
+ * saved.
+ *
+ * During resume we only need to use one swap_map_page structure
+ * at a time, which means that we only need to use two memory pages for
+ * reading the image - one for reading the swap_map_page structures
+ * and the second for reading the data pages from swap.
+ */
- BUG_ON (sizeof(swp_entry_t) != sizeof(long));
- BUG_ON (PAGE_SIZE % sizeof(struct pbe));
+#define MAP_PAGE_SIZE ((PAGE_SIZE - sizeof(swp_entry_t) - sizeof(void *)) \
+ / sizeof(swp_entry_t))
- cur->link.next = prev;
- page = virt_to_page((unsigned long)cur);
- rw_swap_page_sync(WRITE, entry, page);
- prev = entry;
- }
- printk("H");
- BUG_ON (sizeof(struct suspend_header) > PAGE_SIZE-sizeof(swp_entry_t));
- BUG_ON (sizeof(union diskpage) != PAGE_SIZE);
- BUG_ON (sizeof(struct link) != PAGE_SIZE);
- if (!(entry = get_swap_page()).val)
- panic( "\nNot enough swapspace when writing header" );
- if (swapfile_used[swp_type(entry)] != SWAPFILE_SUSPEND)
- panic("\nNot enough swapspace for header on suspend device" );
-
- cur = (void *) buffer;
- if (fill_suspend_header(&cur->sh))
- BUG(); /* Not a BUG_ON(): we want fill_suspend_header to be called, always */
-
- cur->link.next = prev;
-
- page = virt_to_page((unsigned long)cur);
- rw_swap_page_sync(WRITE, entry, page);
- prev = entry;
+struct swap_map_page {
+ swp_entry_t entries[MAP_PAGE_SIZE];
+ swp_entry_t next_swap;
+ struct swap_map_page *next;
+};
- printk( "S" );
- mark_swapfiles(prev, MARK_SWAP_SUSPEND);
- printk( "|\n" );
+static inline void free_swap_map(struct swap_map_page *swap_map)
+{
+ struct swap_map_page *swp;
- MDELAY(1000);
- return 0;
+ while (swap_map) {
+ swp = swap_map->next;
+ free_page((unsigned long)swap_map);
+ swap_map = swp;
+ }
}
-#ifdef CONFIG_HIGHMEM
-struct highmem_page {
- char *data;
- struct page *page;
- struct highmem_page *next;
-};
+static struct swap_map_page *alloc_swap_map(unsigned int nr_pages)
+{
+ struct swap_map_page *swap_map, *swp;
+ unsigned n = 0;
-struct highmem_page *highmem_copy = NULL;
+ if (!nr_pages)
+ return NULL;
-static int save_highmem_zone(struct zone *zone)
-{
- unsigned long zone_pfn;
- for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
- struct page *page;
- struct highmem_page *save;
- void *kaddr;
- unsigned long pfn = zone_pfn + zone->zone_start_pfn;
- int chunk_size;
-
- if (!(pfn%1000))
- printk(".");
- if (!pfn_valid(pfn))
- continue;
- page = pfn_to_page(pfn);
- /*
- * This condition results from rvmalloc() sans vmalloc_32()
- * and architectural memory reservations. This should be
- * corrected eventually when the cases giving rise to this
- * are better understood.
- */
- if (PageReserved(page)) {
- printk("highmem reserved page?!\n");
- continue;
- }
- if ((chunk_size = is_head_of_free_region(page))) {
- pfn += chunk_size - 1;
- zone_pfn += chunk_size - 1;
- continue;
- }
- save = kmalloc(sizeof(struct highmem_page), GFP_ATOMIC);
- if (!save)
- return -ENOMEM;
- save->next = highmem_copy;
- save->page = page;
- save->data = (void *) get_zeroed_page(GFP_ATOMIC);
- if (!save->data) {
- kfree(save);
- return -ENOMEM;
+ pr_debug("alloc_swap_map(): nr_pages = %d\n", nr_pages);
+ swap_map = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC);
+ swp = swap_map;
+ for (n = MAP_PAGE_SIZE; n < nr_pages; n += MAP_PAGE_SIZE) {
+ swp->next = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC);
+ swp = swp->next;
+ if (!swp) {
+ free_swap_map(swap_map);
+ return NULL;
}
- kaddr = kmap_atomic(page, KM_USER0);
- memcpy(save->data, kaddr, PAGE_SIZE);
- kunmap_atomic(kaddr, KM_USER0);
- highmem_copy = save;
}
- return 0;
+ return swap_map;
}
-static int save_highmem(void)
+/**
+ * reverse_swap_map - reverse the order of pages in the swap map
+ * @swap_map
+ */
+
+static inline struct swap_map_page *reverse_swap_map(struct swap_map_page *swap_map)
{
- struct zone *zone;
- int res = 0;
- for_each_zone(zone) {
- if (is_highmem(zone))
- res = save_highmem_zone(zone);
- if (res)
- return res;
+ struct swap_map_page *prev, *next;
+
+ prev = NULL;
+ while (swap_map) {
+ next = swap_map->next;
+ swap_map->next = prev;
+ prev = swap_map;
+ swap_map = next;
}
- return 0;
+ return prev;
}
-static int restore_highmem(void)
+/**
+ * free_swap_map_entries - free the swap entries allocated to store
+ * the swap map @swap_map (this is only called in case of an error)
+ */
+static inline void free_swap_map_entries(struct swap_map_page *swap_map)
{
- while (highmem_copy) {
- struct highmem_page *save = highmem_copy;
- void *kaddr;
- highmem_copy = save->next;
-
- kaddr = kmap_atomic(save->page, KM_USER0);
- memcpy(kaddr, save->data, PAGE_SIZE);
- kunmap_atomic(kaddr, KM_USER0);
- free_page((long) save->data);
- kfree(save);
+ while (swap_map) {
+ if (swap_map->next_swap.val)
+ swap_free(swap_map->next_swap);
+ swap_map = swap_map->next;
}
- return 0;
}
-#endif
-static int pfn_is_nosave(unsigned long pfn)
-{
- unsigned long nosave_begin_pfn = __pa(&__nosave_begin) >> PAGE_SHIFT;
- unsigned long nosave_end_pfn = PAGE_ALIGN(__pa(&__nosave_end)) >> PAGE_SHIFT;
- return (pfn >= nosave_begin_pfn) && (pfn < nosave_end_pfn);
-}
+/**
+ * save_swap_map - save the swap map used for tracing the data pages
+ * stored in the swap
+ */
-/* if *pagedir_p != NULL it also copies the counted pages */
-static int count_and_copy_zone(struct zone *zone, struct pbe **pagedir_p)
+static int save_swap_map(struct swap_map_page *swap_map, swp_entry_t *start)
{
- unsigned long zone_pfn, chunk_size, nr_copy_pages = 0;
- struct pbe *pbe = *pagedir_p;
- for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
- struct page *page;
- unsigned long pfn = zone_pfn + zone->zone_start_pfn;
-
- if (!(pfn%1000))
- printk(".");
- if (!pfn_valid(pfn))
- continue;
- page = pfn_to_page(pfn);
- BUG_ON(PageReserved(page) && PageNosave(page));
- if (PageNosave(page))
- continue;
- if (PageReserved(page) && pfn_is_nosave(pfn)) {
- PRINTK("[nosave pfn 0x%lx]", pfn);
- continue;
- }
- if ((chunk_size = is_head_of_free_region(page))) {
- pfn += chunk_size - 1;
- zone_pfn += chunk_size - 1;
- continue;
- }
- nr_copy_pages++;
- if (!pbe)
- continue;
- pbe->orig_address = (long) page_address(page);
- copy_page((void *)pbe->address, (void *)pbe->orig_address);
- pbe++;
+ swp_entry_t entry = (swp_entry_t){0};
+ int error;
+
+ while (swap_map) {
+ swap_map->next_swap = entry;
+ if ((error = write_page((unsigned long)swap_map, &entry)))
+ return error;
+ swap_map = swap_map->next;
}
- *pagedir_p = pbe;
- return nr_copy_pages;
+ *start = entry;
+ return 0;
}
-static int count_and_copy_data_pages(struct pbe *pagedir_p)
+/**
+ * free_image_entries - free the swap entries allocated to store
+ * the image data pages (this is only called in case of an error)
+ */
+
+static inline void free_image_entries(struct swap_map_page *swp)
{
- int nr_copy_pages = 0;
- struct zone *zone;
- for_each_zone(zone) {
- if (!is_highmem(zone))
- nr_copy_pages += count_and_copy_zone(zone, &pagedir_p);
+ unsigned k;
+
+ while (swp) {
+ for (k = 0; k < MAP_PAGE_SIZE; k++)
+ if (swp->entries[k].val)
+ swap_free(swp->entries[k]);
+ swp = swp->next;
}
- return nr_copy_pages;
}
-static void free_suspend_pagedir_zone(struct zone *zone, unsigned long pagedir)
+/**
+ * The swap_map_handle structure is used for handling the swap map in
+ * a file-alike way
+ */
+
+struct swap_map_handle {
+ struct swap_map_page *cur;
+ unsigned int k;
+};
+
+static inline void init_swap_map_handle(struct swap_map_handle *handle,
+ struct swap_map_page *map)
{
- unsigned long zone_pfn, pagedir_end, pagedir_pfn, pagedir_end_pfn;
- pagedir_end = pagedir + (PAGE_SIZE << pagedir_order);
- pagedir_pfn = __pa(pagedir) >> PAGE_SHIFT;
- pagedir_end_pfn = __pa(pagedir_end) >> PAGE_SHIFT;
- for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
- struct page *page;
- unsigned long pfn = zone_pfn + zone->zone_start_pfn;
- if (!pfn_valid(pfn))
- continue;
- page = pfn_to_page(pfn);
- if (!TestClearPageNosave(page))
- continue;
- else if (pfn >= pagedir_pfn && pfn < pagedir_end_pfn)
- continue;
- __free_page(page);
- }
+ handle->cur = map;
+ handle->k = 0;
}
-static void free_suspend_pagedir(unsigned long this_pagedir)
+static inline int swap_map_write_page(struct swap_map_handle *handle,
+ unsigned long addr)
{
- struct zone *zone;
- for_each_zone(zone) {
- if (!is_highmem(zone))
- free_suspend_pagedir_zone(zone, this_pagedir);
+ int error;
+
+ error = write_page(addr, handle->cur->entries + handle->k);
+ if (error)
+ return error;
+ if (++handle->k >= MAP_PAGE_SIZE) {
+ handle->cur = handle->cur->next;
+ handle->k = 0;
}
- free_pages(this_pagedir, pagedir_order);
+ return 0;
}
-static suspend_pagedir_t *create_suspend_pagedir(int nr_copy_pages)
+/**
+ * save_image_data - save the data pages pointed to by the PBEs
+ * from the list @pblist using the swap map handle @handle
+ * (assume there are @nr_pages data pages to save)
+ */
+
+static int save_image_data(struct pbe *pblist,
+ struct swap_map_handle *handle,
+ unsigned int nr_pages)
{
- int i;
- suspend_pagedir_t *pagedir;
+ unsigned int m;
struct pbe *p;
- struct page *page;
-
- pagedir_order = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages));
+ int error = 0;
- p = pagedir = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC | __GFP_COLD, pagedir_order);
- if (!pagedir)
- return NULL;
-
- page = virt_to_page(pagedir);
- for(i=0; i < 1<<pagedir_order; i++)
- SetPageNosave(page++);
-
- while(nr_copy_pages--) {
- p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
- if (!p->address) {
- free_suspend_pagedir((unsigned long) pagedir);
- return NULL;
- }
- SetPageNosave(virt_to_page(p->address));
- p->orig_address = 0;
- p++;
+ printk("Saving image data pages (%u pages) ... ", nr_pages);
+ m = nr_pages / 100;
+ if (!m)
+ m = 1;
+ nr_pages = 0;
+ for_each_pbe (p, pblist) {
+ error = swap_map_write_page(handle, p->address);
+ if (error)
+ break;
+ if (!(nr_pages % m))
+ printk("\b\b\b\b%3d%%", nr_pages / m);
+ nr_pages++;
}
- return pagedir;
+ if (!error)
+ printk("\b\b\b\bdone\n");
+ return error;
}
-static int prepare_suspend_processes(void)
+static void dump_info(void)
{
- sys_sync(); /* Syncing needs pdflushd, so do it before stopping processes */
- if (freeze_processes()) {
- printk( KERN_ERR "Suspend failed: Not all processes stopped!\n" );
- thaw_processes();
- return 1;
- }
- return 0;
+ pr_debug(" swsusp: Version: %u\n",swsusp_info.version_code);
+ pr_debug(" swsusp: Num Pages: %ld\n",swsusp_info.num_physpages);
+ pr_debug(" swsusp: UTS Sys: %s\n",swsusp_info.uts.sysname);
+ pr_debug(" swsusp: UTS Node: %s\n",swsusp_info.uts.nodename);
+ pr_debug(" swsusp: UTS Release: %s\n",swsusp_info.uts.release);
+ pr_debug(" swsusp: UTS Version: %s\n",swsusp_info.uts.version);
+ pr_debug(" swsusp: UTS Machine: %s\n",swsusp_info.uts.machine);
+ pr_debug(" swsusp: UTS Domain: %s\n",swsusp_info.uts.domainname);
+ pr_debug(" swsusp: CPUs: %d\n",swsusp_info.cpus);
+ pr_debug(" swsusp: Image: %ld Pages\n",swsusp_info.image_pages);
+ pr_debug(" swsusp: Total: %ld Pages\n", swsusp_info.pages);
}
-/*
- * Try to free as much memory as possible, but do not OOM-kill anyone
- *
- * Notice: all userland should be stopped at this point, or livelock is possible.
- */
-static void free_some_memory(void)
+static void init_header(unsigned int nr_pages)
{
- printk("Freeing memory: ");
- while (shrink_all_memory(10000))
- printk(".");
- printk("|\n");
+ memset(&swsusp_info, 0, sizeof(swsusp_info));
+ swsusp_info.version_code = LINUX_VERSION_CODE;
+ swsusp_info.num_physpages = num_physpages;
+ memcpy(&swsusp_info.uts, &system_utsname, sizeof(system_utsname));
+
+ swsusp_info.cpus = num_online_cpus();
+ swsusp_info.image_pages = nr_pages;
+ swsusp_info.pages = nr_pages +
+ ((nr_pages * sizeof(long) + PAGE_SIZE - 1) >> PAGE_SHIFT) + 1;
}
-static int suspend_prepare_image(void)
+/**
+ * pack_orig_addresses - the .orig_address fields of the PBEs from the
+ * list starting at @pbe are stored in the array @buf[] (1 page)
+ */
+
+static inline struct pbe *pack_orig_addresses(unsigned long *buf,
+ struct pbe *pbe)
{
- struct sysinfo i;
- unsigned int nr_needed_pages = 0;
+ int j;
- pagedir_nosave = NULL;
- printk( "/critical section: ");
-#ifdef CONFIG_HIGHMEM
- printk( "handling highmem" );
- if (save_highmem()) {
- printk(KERN_CRIT "%sNot enough free pages for highmem\n", name_suspend);
- return -ENOMEM;
+ for (j = 0; j < PAGE_SIZE / sizeof(long) && pbe; j++) {
+ buf[j] = pbe->orig_address;
+ pbe = pbe->next;
}
- printk(", ");
-#endif
+ if (!pbe)
+ for (; j < PAGE_SIZE / sizeof(long); j++)
+ buf[j] = 0;
+ return pbe;
+}
- printk("counting pages to copy" );
- drain_local_pages();
- nr_copy_pages = count_and_copy_data_pages(NULL);
- nr_needed_pages = nr_copy_pages + PAGES_FOR_IO;
-
- printk(" (pages needed: %d+%d=%d free: %d)\n",nr_copy_pages,PAGES_FOR_IO,nr_needed_pages,nr_free_pages());
- if(nr_free_pages() < nr_needed_pages) {
- printk(KERN_CRIT "%sCouldn't get enough free pages, on %d pages short\n",
- name_suspend, nr_needed_pages-nr_free_pages());
- root_swap = 0xFFFF;
- return -ENOMEM;
- }
- si_swapinfo(&i); /* FIXME: si_swapinfo(&i) returns all swap devices information.
- We should only consider resume_device. */
- if (i.freeswap < nr_needed_pages) {
- printk(KERN_CRIT "%sThere's not enough swap space available, on %ld pages short\n",
- name_suspend, nr_needed_pages-i.freeswap);
- return -ENOSPC;
- }
+/**
+ * save_image_metadata - save the .orig_address fields of the PBEs
+ * from the list @pblist using the swap map handle @handle
+ */
+
+static int save_image_metadata(struct pbe *pblist,
+ struct swap_map_handle *handle)
+{
+ unsigned long *buf;
+ unsigned int n = 0;
+ struct pbe *p;
+ int error = 0;
- PRINTK( "Alloc pagedir\n" );
- pagedir_save = pagedir_nosave = create_suspend_pagedir(nr_copy_pages);
- if (!pagedir_nosave) {
- /* Pagedir is big, one-chunk allocation. It is easily possible for this allocation to fail */
- printk(KERN_CRIT "%sCouldn't allocate continuous pagedir\n", name_suspend);
+ printk("Saving image metadata ... ");
+ buf = (unsigned long *)get_zeroed_page(GFP_ATOMIC);
+ if (!buf)
return -ENOMEM;
+ p = pblist;
+ while (p) {
+ p = pack_orig_addresses(buf, p);
+ error = swap_map_write_page(handle, (unsigned long)buf);
+ if (error)
+ break;
+ n++;
}
- nr_copy_pages_check = nr_copy_pages;
- pagedir_order_check = pagedir_order;
+ free_page((unsigned long)buf);
+ if (!error)
+ printk("done (%u pages saved)\n", n);
+ return error;
+}
- drain_local_pages(); /* During allocating of suspend pagedir, new cold pages may appear. Kill them */
- if (nr_copy_pages != count_and_copy_data_pages(pagedir_nosave)) /* copy */
- BUG();
+/**
+ * enough_swap - Make sure we have enough swap to save the image.
+ *
+ * Returns TRUE or FALSE after checking the total amount of swap
+ * space avaiable from the resume partition.
+ */
- /*
- * End of critical section. From now on, we can write to memory,
- * but we should not touch disk. This specially means we must _not_
- * touch swap space! Except we must write out our image of course.
- */
+static int enough_swap(unsigned int nr_pages)
+{
+ unsigned int free_swap = swap_info[root_swap].pages -
+ swap_info[root_swap].inuse_pages;
- printk( "critical section/: done (%d pages copied)\n", nr_copy_pages );
- return 0;
+ pr_debug("swsusp: free swap pages: %u\n", free_swap);
+ return free_swap > (nr_pages + PAGES_FOR_IO +
+ (nr_pages + PBES_PER_PAGE - 1) / PBES_PER_PAGE);
}
-static void suspend_save_image(void)
+/**
+ * swsusp_write - Write entire image and metadata.
+ *
+ * It is important _NOT_ to umount filesystems at this point. We want
+ * them synced (in case something goes wrong) but we DO not want to mark
+ * filesystem clean: it is not. (And it does not matter, if we resume
+ * correctly, we'll mark system clean, anyway.)
+ */
+
+int swsusp_write(struct pbe *pblist, unsigned int nr_pages)
{
- device_resume();
+ struct swap_map_page *swap_map;
+ struct swap_map_handle handle;
+ swp_entry_t start;
+ int error;
- lock_swapdevices();
- write_suspend_image();
- lock_swapdevices(); /* This will unlock ignored swap devices since writing is finished */
+ if ((error = swsusp_swap_check())) {
+ printk(KERN_ERR "swsusp: Cannot find swap device, try swapon -a.\n");
+ return error;
+ }
+ if (!enough_swap(nr_pages)) {
+ printk(KERN_ERR "swsusp: Not enough free swap\n");
+ return -ENOSPC;
+ }
- /* It is important _NOT_ to umount filesystems at this point. We want
- * them synced (in case something goes wrong) but we DO not want to mark
- * filesystem clean: it is not. (And it does not matter, if we resume
- * correctly, we'll mark system clean, anyway.)
- */
-}
+ init_header(nr_pages);
+ swap_map = alloc_swap_map(swsusp_info.pages);
+ if (!swap_map)
+ return -ENOMEM;
+ init_swap_map_handle(&handle, swap_map);
+
+ error = swap_map_write_page(&handle, (unsigned long)&swsusp_info);
+ if (!error)
+ error = save_image_metadata(pblist, &handle);
+ if (!error)
+ error = save_image_data(pblist, &handle, nr_pages);
+ if (error)
+ goto Free_image_entries;
+
+ swap_map = reverse_swap_map(swap_map);
+ error = save_swap_map(swap_map, &start);
+ if (error)
+ goto Free_map_entries;
+
+ dump_info();
+ printk( "S" );
+ error = mark_swapfiles(start);
+ printk( "|\n" );
+ if (error)
+ goto Free_map_entries;
-static void suspend_power_down(void)
-{
- extern int C_A_D;
- C_A_D = 0;
- printk(KERN_EMERG "%s%s Trying to power down.\n", name_suspend, TEST_SWSUSP ? "Disable TEST_SWSUSP. NOT ": "");
-#ifdef CONFIG_VT
- PRINTK(KERN_EMERG "shift_state: %04x\n", shift_state);
- mdelay(1000);
- if (TEST_SWSUSP ^ (!!(shift_state & (1 << KG_CTRL))))
- machine_restart(NULL);
- else
-#endif
- {
- device_shutdown();
- machine_power_off();
- }
+Free_swap_map:
+ free_swap_map(swap_map);
+ return error;
- printk(KERN_EMERG "%sProbably not capable for powerdown. System halted.\n", name_suspend);
- machine_halt();
- while (1);
- /* NOTREACHED */
+Free_map_entries:
+ free_swap_map_entries(swap_map);
+Free_image_entries:
+ free_image_entries(swap_map);
+ goto Free_swap_map;
}
-/*
- * Magic happens here
+/**
+ * swsusp_shrink_memory - Try to free as much memory as needed
+ *
+ * ... but do not OOM-kill anyone
+ *
+ * Notice: all userland should be stopped before it is called, or
+ * livelock is possible.
*/
-asmlinkage void do_magic_resume_1(void)
+#define SHRINK_BITE 10000
+
+int swsusp_shrink_memory(void)
{
- barrier();
- mb();
- spin_lock_irq(&suspend_pagedir_lock); /* Done to disable interrupts */
-
- device_power_down(4);
- PRINTK( "Waiting for DMAs to settle down...\n");
- mdelay(1000); /* We do not want some readahead with DMA to corrupt our memory, right?
- Do it with disabled interrupts for best effect. That way, if some
- driver scheduled DMA, we have good chance for DMA to finish ;-). */
+ long size, tmp;
+ struct zone *zone;
+ unsigned long pages = 0;
+ unsigned int i = 0;
+ char *p = "-\\|/";
+
+ printk("Shrinking memory... ");
+ do {
+ size = 2 * count_highmem_pages();
+ size += size / 50 + count_data_pages();
+ size += (size + PBES_PER_PAGE - 1) / PBES_PER_PAGE +
+ PAGES_FOR_IO;
+ tmp = size;
+ for_each_zone (zone)
+ if (!is_highmem(zone))
+ tmp -= zone->free_pages;
+ if (tmp > 0) {
+ tmp = shrink_all_memory(SHRINK_BITE);
+ if (!tmp)
+ return -ENOMEM;
+ pages += tmp;
+ } else if (size > image_size / PAGE_SIZE) {
+ tmp = shrink_all_memory(SHRINK_BITE);
+ pages += tmp;
+ }
+ printk("\b%c", p[i++%4]);
+ } while (tmp > 0);
+ printk("\bdone (%lu pages freed)\n", pages);
+
+ return 0;
}
-asmlinkage void do_magic_resume_2(void)
+int swsusp_suspend(void)
{
- BUG_ON (nr_copy_pages_check != nr_copy_pages);
- BUG_ON (pagedir_order_check != pagedir_order);
+ int error;
- __flush_tlb_global(); /* Even mappings of "global" things (vmalloc) need to be fixed */
+ if ((error = arch_prepare_suspend()))
+ return error;
+ local_irq_disable();
+ /* At this point, device_suspend() has been called, but *not*
+ * device_power_down(). We *must* device_power_down() now.
+ * Otherwise, drivers for some devices (e.g. interrupt controllers)
+ * become desynchronized with the actual state of the hardware
+ * at resume time, and evil weirdness ensues.
+ */
+ if ((error = device_power_down(PMSG_FREEZE))) {
+ printk(KERN_ERR "Some devices failed to power down, aborting suspend\n");
+ goto Enable_irqs;
+ }
- PRINTK( "Freeing prev allocated pagedir\n" );
- free_suspend_pagedir((unsigned long) pagedir_save);
+ if ((error = save_highmem())) {
+ printk(KERN_ERR "swsusp: Not enough free pages for highmem\n");
+ goto Restore_highmem;
+ }
-#ifdef CONFIG_HIGHMEM
- printk( "Restoring highmem\n" );
+ save_processor_state();
+ if ((error = swsusp_arch_suspend()))
+ printk(KERN_ERR "Error %d suspending\n", error);
+ /* Restore control flow magically appears here */
+ restore_processor_state();
+Restore_highmem:
restore_highmem();
-#endif
- printk("done, devices\n");
-
device_power_up();
- spin_unlock_irq(&suspend_pagedir_lock);
- device_resume();
-
- /* Fixme: this is too late; we should do this ASAP to avoid "infinite reboots" problem */
- PRINTK( "Fixing swap signatures... " );
- mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
- PRINTK( "ok\n" );
-
-#ifdef SUSPEND_CONSOLE
- acquire_console_sem();
- update_screen(fg_console);
- release_console_sem();
-#endif
+Enable_irqs:
+ local_irq_enable();
+ return error;
}
-/* do_magic() is implemented in arch/?/kernel/suspend_asm.S, and basically does:
-
- if (!resume) {
- do_magic_suspend_1();
- save_processor_state();
- SAVE_REGISTERS
- do_magic_suspend_2();
- return;
- }
- GO_TO_SWAPPER_PAGE_TABLES
- do_magic_resume_1();
- COPY_PAGES_BACK
- RESTORE_REGISTERS
+int swsusp_resume(void)
+{
+ int error;
+ local_irq_disable();
+ if (device_power_down(PMSG_FREEZE))
+ printk(KERN_ERR "Some devices failed to power down, very bad\n");
+ /* We'll ignore saved state, but this gets preempt count (etc) right */
+ save_processor_state();
+ error = swsusp_arch_resume();
+ /* Code below is only ever reached in case of failure. Otherwise
+ * execution continues at place where swsusp_arch_suspend was called
+ */
+ BUG_ON(!error);
+ /* The only reason why swsusp_arch_resume() can fail is memory being
+ * very tight, so we have to free it as soon as we can to avoid
+ * subsequent failures
+ */
+ swsusp_free();
restore_processor_state();
- do_magic_resume_2();
+ restore_highmem();
+ touch_softlockup_watchdog();
+ device_power_up();
+ local_irq_enable();
+ return error;
+}
+/**
+ * mark_unsafe_pages - mark the pages that cannot be used for storing
+ * the image during resume, because they conflict with the pages that
+ * had been used before suspend
*/
-asmlinkage void do_magic_suspend_1(void)
-{
- mb();
- barrier();
- BUG_ON(in_atomic());
- spin_lock_irq(&suspend_pagedir_lock);
-}
-
-asmlinkage void do_magic_suspend_2(void)
+static void mark_unsafe_pages(struct pbe *pblist)
{
- int is_problem;
- read_swapfiles();
- device_power_down(4);
- is_problem = suspend_prepare_image();
- device_power_up();
- spin_unlock_irq(&suspend_pagedir_lock);
- if (!is_problem) {
- kernel_fpu_end(); /* save_processor_state() does kernel_fpu_begin, and we need to revert it in order to pass in_atomic() checks */
- BUG_ON(in_atomic());
- suspend_save_image();
- suspend_power_down(); /* FIXME: if suspend_power_down is commented out, console is lost after few suspends ?! */
- }
-
- printk(KERN_EMERG "%sSuspend failed, trying to recover...\n", name_suspend);
- MDELAY(1000); /* So user can wait and report us messages if armageddon comes :-) */
-
- barrier();
- mb();
- spin_lock_irq(&suspend_pagedir_lock); /* Done to disable interrupts */
- mdelay(1000);
-
- free_pages((unsigned long) pagedir_nosave, pagedir_order);
- spin_unlock_irq(&suspend_pagedir_lock);
+ struct zone *zone;
+ unsigned long zone_pfn;
+ struct pbe *p;
- device_resume();
- PRINTK( "Fixing swap signatures... " );
- mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
- PRINTK( "ok\n" );
-}
+ if (!pblist) /* a sanity check */
+ return;
-/*
- * This is main interface to the outside world. It needs to be
- * called from process context.
- */
-int software_suspend(void)
-{
- int res;
- if (!software_suspend_enabled)
- return -EAGAIN;
+ /* Clear page flags */
+ for_each_zone (zone) {
+ for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn)
+ if (pfn_valid(zone_pfn + zone->zone_start_pfn))
+ ClearPageNosaveFree(pfn_to_page(zone_pfn +
+ zone->zone_start_pfn));
+ }
- software_suspend_enabled = 0;
- might_sleep();
+ /* Mark orig addresses */
+ for_each_pbe (p, pblist)
+ SetPageNosaveFree(virt_to_page(p->orig_address));
- if (arch_prepare_suspend()) {
- printk("%sArchitecture failed to prepare\n", name_suspend);
- return -EPERM;
- }
- if (pm_prepare_console())
- printk( "%sCan't allocate a console... proceeding\n", name_suspend);
- if (!prepare_suspend_processes()) {
-
- /* At this point, all user processes and "dangerous"
- kernel threads are stopped. Free some memory, as we
- need half of memory free. */
-
- free_some_memory();
-
- /* Save state of all device drivers, and stop them. */
- if ((res = device_suspend(4))==0)
- /* If stopping device drivers worked, we proceed basically into
- * suspend_save_image.
- *
- * do_magic(0) returns after system is resumed.
- *
- * do_magic() copies all "used" memory to "free" memory, then
- * unsuspends all device drivers, and writes memory to disk
- * using normal kernel mechanism.
- */
- do_magic(0);
- thaw_processes();
- } else
- res = -EBUSY;
- software_suspend_enabled = 1;
- MDELAY(1000);
- pm_restore_console();
- return res;
}
-/* More restore stuff */
-
-/* FIXME: Why not memcpy(to, from, 1<<pagedir_order*PAGE_SIZE)? */
-static void copy_pagedir(suspend_pagedir_t *to, suspend_pagedir_t *from)
+static void copy_page_backup_list(struct pbe *dst, struct pbe *src)
{
- int i;
- char *topointer=(char *)to, *frompointer=(char *)from;
-
- for(i=0; i < 1 << pagedir_order; i++) {
- copy_page(topointer, frompointer);
- topointer += PAGE_SIZE;
- frompointer += PAGE_SIZE;
+ /* We assume both lists contain the same number of elements */
+ while (src) {
+ dst->orig_address = src->orig_address;
+ dst = dst->next;
+ src = src->next;
}
}
-#define does_collide(addr) does_collide_order(pagedir_nosave, addr, 0)
-
/*
- * Returns true if given address/order collides with any orig_address
+ * Using bio to read from swap.
+ * This code requires a bit more work than just using buffer heads
+ * but, it is the recommended way for 2.5/2.6.
+ * The following are to signal the beginning and end of I/O. Bios
+ * finish asynchronously, while we want them to happen synchronously.
+ * A simple atomic_t, and a wait loop take care of this problem.
*/
-static int does_collide_order(suspend_pagedir_t *pagedir, unsigned long addr,
- int order)
-{
- int i;
- unsigned long addre = addr + (PAGE_SIZE<<order);
-
- for(i=0; i < nr_copy_pages; i++)
- if((pagedir+i)->orig_address >= addr &&
- (pagedir+i)->orig_address < addre)
- return 1;
- return 0;
-}
+static atomic_t io_done = ATOMIC_INIT(0);
-/*
- * We check here that pagedir & pages it points to won't collide with pages
- * where we're going to restore from the loaded pages later
- */
-static int check_pagedir(void)
+static int end_io(struct bio *bio, unsigned int num, int err)
{
- int i;
-
- for(i=0; i < nr_copy_pages; i++) {
- unsigned long addr;
-
- do {
- addr = get_zeroed_page(GFP_ATOMIC);
- if(!addr)
- return -ENOMEM;
- } while (does_collide(addr));
-
- (pagedir_nosave+i)->address = addr;
- }
+ if (!test_bit(BIO_UPTODATE, &bio->bi_flags))
+ panic("I/O error reading memory image");
+ atomic_set(&io_done, 0);
return 0;
}
-static int relocate_pagedir(void)
-{
- /*
- * We have to avoid recursion (not to overflow kernel stack),
- * and that's why code looks pretty cryptic
- */
- suspend_pagedir_t *new_pagedir, *old_pagedir = pagedir_nosave;
- void **eaten_memory = NULL;
- void **c = eaten_memory, *m, *f;
+static struct block_device *resume_bdev;
- printk("Relocating pagedir");
+/**
+ * submit - submit BIO request.
+ * @rw: READ or WRITE.
+ * @off physical offset of page.
+ * @page: page we're reading or writing.
+ *
+ * Straight from the textbook - allocate and initialize the bio.
+ * If we're writing, make sure the page is marked as dirty.
+ * Then submit it and wait.
+ */
- if(!does_collide_order(old_pagedir, (unsigned long)old_pagedir, pagedir_order)) {
- printk("not necessary\n");
- return 0;
- }
+static int submit(int rw, pgoff_t page_off, void *page)
+{
+ int error = 0;
+ struct bio *bio;
- while ((m = (void *) __get_free_pages(GFP_ATOMIC, pagedir_order))) {
- memset(m, 0, PAGE_SIZE);
- if (!does_collide_order(old_pagedir, (unsigned long)m, pagedir_order))
- break;
- eaten_memory = m;
- printk( "." );
- *eaten_memory = c;
- c = eaten_memory;
+ bio = bio_alloc(GFP_ATOMIC, 1);
+ if (!bio)
+ return -ENOMEM;
+ bio->bi_sector = page_off * (PAGE_SIZE >> 9);
+ bio->bi_bdev = resume_bdev;
+ bio->bi_end_io = end_io;
+
+ if (bio_add_page(bio, virt_to_page(page), PAGE_SIZE, 0) < PAGE_SIZE) {
+ printk("swsusp: ERROR: adding page to bio at %ld\n",page_off);
+ error = -EFAULT;
+ goto Done;
}
- if (!m)
- return -ENOMEM;
- pagedir_nosave = new_pagedir = m;
- copy_pagedir(new_pagedir, old_pagedir);
+ atomic_set(&io_done, 1);
+ submit_bio(rw | (1 << BIO_RW_SYNC), bio);
+ while (atomic_read(&io_done))
+ yield();
+ if (rw == READ)
+ bio_set_pages_dirty(bio);
+ Done:
+ bio_put(bio);
+ return error;
+}
- c = eaten_memory;
- while(c) {
- printk(":");
- f = *c;
- c = *c;
- if (f)
- free_pages((unsigned long)f, pagedir_order);
- }
- printk("|\n");
- return 0;
+static int bio_read_page(pgoff_t page_off, void *page)
+{
+ return submit(READ, page_off, page);
}
-/*
- * Sanity check if this image makes sense with this kernel/swap context
- * I really don't think that it's foolproof but more than nothing..
+static int bio_write_page(pgoff_t page_off, void *page)
+{
+ return submit(WRITE, page_off, page);
+}
+
+/**
+ * The following functions allow us to read data using a swap map
+ * in a file-alike way
*/
-static int sanity_check_failed(char *reason)
+static inline void release_swap_map_reader(struct swap_map_handle *handle)
{
- printk(KERN_ERR "%s%s\n", name_resume, reason);
- return -EPERM;
+ if (handle->cur)
+ free_page((unsigned long)handle->cur);
+ handle->cur = NULL;
}
-static int sanity_check(struct suspend_header *sh)
+static inline int get_swap_map_reader(struct swap_map_handle *handle,
+ swp_entry_t start)
{
- if (sh->version_code != LINUX_VERSION_CODE)
- return sanity_check_failed("Incorrect kernel version");
- if (sh->num_physpages != num_physpages)
- return sanity_check_failed("Incorrect memory size");
- if (strncmp(sh->machine, system_utsname.machine, 8))
- return sanity_check_failed("Incorrect machine type");
- if (strncmp(sh->version, system_utsname.version, 20))
- return sanity_check_failed("Incorrect version");
- if (sh->num_cpus != num_online_cpus())
- return sanity_check_failed("Incorrect number of cpus");
- if (sh->page_size != PAGE_SIZE)
- return sanity_check_failed("Incorrect PAGE_SIZE");
+ int error;
+
+ if (!swp_offset(start))
+ return -EINVAL;
+ handle->cur = (struct swap_map_page *)get_zeroed_page(GFP_ATOMIC);
+ if (!handle->cur)
+ return -ENOMEM;
+ error = bio_read_page(swp_offset(start), handle->cur);
+ if (error) {
+ release_swap_map_reader(handle);
+ return error;
+ }
+ handle->k = 0;
return 0;
}
-static int bdev_read_page(struct block_device *bdev, long pos, void *buf)
+static inline int swap_map_read_page(struct swap_map_handle *handle, void *buf)
{
- struct buffer_head *bh;
- BUG_ON (pos%PAGE_SIZE);
- bh = __bread(bdev, pos/PAGE_SIZE, PAGE_SIZE);
- if (!bh || (!bh->b_data)) {
- return -1;
+ unsigned long offset;
+ int error;
+
+ if (!handle->cur)
+ return -EINVAL;
+ offset = swp_offset(handle->cur->entries[handle->k]);
+ if (!offset)
+ return -EINVAL;
+ error = bio_read_page(offset, buf);
+ if (error)
+ return error;
+ if (++handle->k >= MAP_PAGE_SIZE) {
+ handle->k = 0;
+ offset = swp_offset(handle->cur->next_swap);
+ if (!offset)
+ release_swap_map_reader(handle);
+ else
+ error = bio_read_page(offset, handle->cur);
}
- memcpy(buf, bh->b_data, PAGE_SIZE); /* FIXME: may need kmap() */
- BUG_ON(!buffer_uptodate(bh));
- brelse(bh);
- return 0;
-}
+ return error;
+}
-static int bdev_write_page(struct block_device *bdev, long pos, void *buf)
+static int check_header(void)
{
-#if 0
- struct buffer_head *bh;
- BUG_ON (pos%PAGE_SIZE);
- bh = __bread(bdev, pos/PAGE_SIZE, PAGE_SIZE);
- if (!bh || (!bh->b_data)) {
- return -1;
+ char *reason = NULL;
+
+ dump_info();
+ if (swsusp_info.version_code != LINUX_VERSION_CODE)
+ reason = "kernel version";
+ if (swsusp_info.num_physpages != num_physpages)
+ reason = "memory size";
+ if (strcmp(swsusp_info.uts.sysname,system_utsname.sysname))
+ reason = "system type";
+ if (strcmp(swsusp_info.uts.release,system_utsname.release))
+ reason = "kernel release";
+ if (strcmp(swsusp_info.uts.version,system_utsname.version))
+ reason = "version";
+ if (strcmp(swsusp_info.uts.machine,system_utsname.machine))
+ reason = "machine";
+ if (reason) {
+ printk(KERN_ERR "swsusp: Resume mismatch: %s\n", reason);
+ return -EPERM;
}
- memcpy(bh->b_data, buf, PAGE_SIZE); /* FIXME: may need kmap() */
- BUG_ON(!buffer_uptodate(bh));
- generic_make_request(WRITE, bh);
- if (!buffer_uptodate(bh))
- printk(KERN_CRIT "%sWarning %s: Fixing swap signatures unsuccessful...\n", name_resume, resume_file);
- wait_on_buffer(bh);
- brelse(bh);
- return 0;
-#endif
- printk(KERN_CRIT "%sWarning %s: Fixing swap signatures unimplemented...\n", name_resume, resume_file);
return 0;
}
-extern dev_t __init name_to_dev_t(const char *line);
+/**
+ * load_image_data - load the image data using the swap map handle
+ * @handle and store them using the page backup list @pblist
+ * (assume there are @nr_pages pages to load)
+ */
-static int __init __read_suspend_image(struct block_device *bdev, union diskpage *cur, int noresume)
+static int load_image_data(struct pbe *pblist,
+ struct swap_map_handle *handle,
+ unsigned int nr_pages)
{
- swp_entry_t next;
- int i, nr_pgdir_pages;
-
-#define PREPARENEXT \
- { next = cur->link.next; \
- next.val = swp_offset(next) * PAGE_SIZE; \
- }
-
- if (bdev_read_page(bdev, 0, cur)) return -EIO;
+ int error;
+ unsigned int m;
+ struct pbe *p;
- if ((!memcmp("SWAP-SPACE",cur->swh.magic.magic,10)) ||
- (!memcmp("SWAPSPACE2",cur->swh.magic.magic,10))) {
- printk(KERN_ERR "%sThis is normal swap space\n", name_resume );
+ if (!pblist)
return -EINVAL;
+ printk("Loading image data pages (%u pages) ... ", nr_pages);
+ m = nr_pages / 100;
+ if (!m)
+ m = 1;
+ nr_pages = 0;
+ p = pblist;
+ while (p) {
+ error = swap_map_read_page(handle, (void *)p->address);
+ if (error)
+ break;
+ p = p->next;
+ if (!(nr_pages % m))
+ printk("\b\b\b\b%3d%%", nr_pages / m);
+ nr_pages++;
}
+ if (!error)
+ printk("\b\b\b\bdone\n");
+ return error;
+}
- PREPARENEXT; /* We have to read next position before we overwrite it */
+/**
+ * unpack_orig_addresses - copy the elements of @buf[] (1 page) to
+ * the PBEs in the list starting at @pbe
+ */
- if (!memcmp("S1",cur->swh.magic.magic,2))
- memcpy(cur->swh.magic.magic,"SWAP-SPACE",10);
- else if (!memcmp("S2",cur->swh.magic.magic,2))
- memcpy(cur->swh.magic.magic,"SWAPSPACE2",10);
- else {
- if (noresume)
- return -EINVAL;
- panic("%sUnable to find suspended-data signature (%.10s - misspelled?\n",
- name_resume, cur->swh.magic.magic);
- }
- if (noresume) {
- /* We don't do a sanity check here: we want to restore the swap
- whatever version of kernel made the suspend image;
- We need to write swap, but swap is *not* enabled so
- we must write the device directly */
- printk("%s: Fixing swap signatures %s...\n", name_resume, resume_file);
- bdev_write_page(bdev, 0, cur);
- }
+static inline struct pbe *unpack_orig_addresses(unsigned long *buf,
+ struct pbe *pbe)
+{
+ int j;
- printk( "%sSignature found, resuming\n", name_resume );
- MDELAY(1000);
+ for (j = 0; j < PAGE_SIZE / sizeof(long) && pbe; j++) {
+ pbe->orig_address = buf[j];
+ pbe = pbe->next;
+ }
+ return pbe;
+}
- if (bdev_read_page(bdev, next.val, cur)) return -EIO;
- if (sanity_check(&cur->sh)) /* Is this same machine? */
- return -EPERM;
- PREPARENEXT;
+/**
+ * load_image_metadata - load the image metadata using the swap map
+ * handle @handle and put them into the PBEs in the list @pblist
+ */
- pagedir_save = cur->sh.suspend_pagedir;
- nr_copy_pages = cur->sh.num_pbes;
- nr_pgdir_pages = SUSPEND_PD_PAGES(nr_copy_pages);
- pagedir_order = get_bitmask_order(nr_pgdir_pages);
+static int load_image_metadata(struct pbe *pblist, struct swap_map_handle *handle)
+{
+ struct pbe *p;
+ unsigned long *buf;
+ unsigned int n = 0;
+ int error = 0;
- pagedir_nosave = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC, pagedir_order);
- if (!pagedir_nosave)
+ printk("Loading image metadata ... ");
+ buf = (unsigned long *)get_zeroed_page(GFP_ATOMIC);
+ if (!buf)
return -ENOMEM;
+ p = pblist;
+ while (p) {
+ error = swap_map_read_page(handle, buf);
+ if (error)
+ break;
+ p = unpack_orig_addresses(buf, p);
+ n++;
+ }
+ free_page((unsigned long)buf);
+ if (!error)
+ printk("done (%u pages loaded)\n", n);
+ return error;
+}
- PRINTK( "%sReading pagedir, ", name_resume );
+int swsusp_read(struct pbe **pblist_ptr)
+{
+ int error;
+ struct pbe *p, *pblist;
+ struct swap_map_handle handle;
+ unsigned int nr_pages;
- /* We get pages in reverse order of saving! */
- for (i=nr_pgdir_pages-1; i>=0; i--) {
- BUG_ON (!next.val);
- cur = (union diskpage *)((char *) pagedir_nosave)+i;
- if (bdev_read_page(bdev, next.val, cur)) return -EIO;
- PREPARENEXT;
+ if (IS_ERR(resume_bdev)) {
+ pr_debug("swsusp: block device not initialised\n");
+ return PTR_ERR(resume_bdev);
}
- BUG_ON (next.val);
- if (relocate_pagedir())
- return -ENOMEM;
- if (check_pagedir())
+ error = get_swap_map_reader(&handle, swsusp_header.image);
+ if (!error)
+ error = swap_map_read_page(&handle, &swsusp_info);
+ if (!error)
+ error = check_header();
+ if (error)
+ return error;
+ nr_pages = swsusp_info.image_pages;
+ p = alloc_pagedir(nr_pages, GFP_ATOMIC, 0);
+ if (!p)
return -ENOMEM;
-
- printk( "Reading image data (%d pages): ", nr_copy_pages );
- for(i=0; i < nr_copy_pages; i++) {
- swp_entry_t swap_address = (pagedir_nosave+i)->swap_address;
- if (!(i%100))
- printk( "." );
- /* You do not need to check for overlaps...
- ... check_pagedir already did this work */
- if (bdev_read_page(bdev, swp_offset(swap_address) * PAGE_SIZE, (char *)((pagedir_nosave+i)->address)))
- return -EIO;
+ error = load_image_metadata(p, &handle);
+ if (!error) {
+ mark_unsafe_pages(p);
+ pblist = alloc_pagedir(nr_pages, GFP_ATOMIC, 1);
+ if (pblist)
+ copy_page_backup_list(pblist, p);
+ free_pagedir(p);
+ if (!pblist)
+ error = -ENOMEM;
+
+ /* Allocate memory for the image and read the data from swap */
+ if (!error)
+ error = alloc_data_pages(pblist, GFP_ATOMIC, 1);
+ if (!error) {
+ release_eaten_pages();
+ error = load_image_data(pblist, &handle, nr_pages);
+ }
+ if (!error)
+ *pblist_ptr = pblist;
}
- printk( "|\n" );
- return 0;
-}
+ release_swap_map_reader(&handle);
-static int read_suspend_image(const char * specialfile, int noresume)
-{
- union diskpage *cur;
- unsigned long scratch_page = 0;
- int error;
- char b[BDEVNAME_SIZE];
-
- resume_device = name_to_dev_t(specialfile);
- scratch_page = get_zeroed_page(GFP_ATOMIC);
- cur = (void *) scratch_page;
- if (cur) {
- struct block_device *bdev;
- printk("Resuming from device %s\n",
- __bdevname(resume_device, b));
- bdev = open_by_devnum(resume_device, FMODE_READ);
- if (IS_ERR(bdev)) {
- error = PTR_ERR(bdev);
- } else {
- set_blocksize(bdev, PAGE_SIZE);
- error = __read_suspend_image(bdev, cur, noresume);
- blkdev_put(bdev);
- }
- } else error = -ENOMEM;
+ blkdev_put(resume_bdev);
- if (scratch_page)
- free_page(scratch_page);
- switch (error) {
- case 0:
- PRINTK("Reading resume file was successful\n");
- break;
- case -EINVAL:
- break;
- case -EIO:
- printk( "%sI/O error\n", name_resume);
- break;
- case -ENOENT:
- printk( "%s%s: No such file or directory\n", name_resume, specialfile);
- break;
- case -ENOMEM:
- printk( "%sNot enough memory\n", name_resume);
- break;
- default:
- printk( "%sError %d resuming\n", name_resume, error );
- }
- MDELAY(1000);
+ if (!error)
+ pr_debug("swsusp: Reading resume file was successful\n");
+ else
+ pr_debug("swsusp: Error %d resuming\n", error);
return error;
}
/**
- * software_resume - Resume from a saved image.
- *
- * Called as a late_initcall (so all devices are discovered and
- * initialized), we call swsusp to see if we have a saved image or not.
- * If so, we quiesce devices, then restore the saved image. We will
- * return above (in pm_suspend_disk() ) if everything goes well.
- * Otherwise, we fail gracefully and return to the normally
- * scheduled program.
- *
+ * swsusp_check - Check for swsusp signature in the resume device
*/
-static int __init software_resume(void)
-{
- if (num_online_cpus() > 1) {
- printk(KERN_WARNING "Software Suspend has malfunctioning SMP support. Disabled :(\n");
- return -EINVAL;
- }
- /* We enable the possibility of machine suspend */
- software_suspend_enabled = 1;
- if (!resume_status)
- return 0;
-
- printk( "%s", name_resume );
- if (resume_status == NORESUME) {
- if(resume_file[0])
- read_suspend_image(resume_file, 1);
- printk( "disabled\n" );
- return 0;
- }
- MDELAY(1000);
- if (pm_prepare_console())
- printk("swsusp: Can't allocate a console... proceeding\n");
+int swsusp_check(void)
+{
+ int error;
- if (!resume_file[0] && resume_status == RESUME_SPECIFIED) {
- printk( "suspension device unspecified\n" );
- return -EINVAL;
+ resume_bdev = open_by_devnum(swsusp_resume_device, FMODE_READ);
+ if (!IS_ERR(resume_bdev)) {
+ set_blocksize(resume_bdev, PAGE_SIZE);
+ memset(&swsusp_header, 0, sizeof(swsusp_header));
+ if ((error = bio_read_page(0, &swsusp_header)))
+ return error;
+ if (!memcmp(SWSUSP_SIG, swsusp_header.sig, 10)) {
+ memcpy(swsusp_header.sig, swsusp_header.orig_sig, 10);
+ /* Reset swap signature now */
+ error = bio_write_page(0, &swsusp_header);
+ } else {
+ return -EINVAL;
+ }
+ if (error)
+ blkdev_put(resume_bdev);
+ else
+ pr_debug("swsusp: Signature found, resuming\n");
+ } else {
+ error = PTR_ERR(resume_bdev);
}
- printk( "resuming from %s\n", resume_file);
- if (read_suspend_image(resume_file, 0))
- goto read_failure;
- device_suspend(4);
- do_magic(1);
- panic("This never returns");
+ if (error)
+ pr_debug("swsusp: Error %d check for resume file\n", error);
-read_failure:
- pm_restore_console();
- return 0;
+ return error;
}
-late_initcall(software_resume);
+/**
+ * swsusp_close - close swap device.
+ */
-static int __init resume_setup(char *str)
+void swsusp_close(void)
{
- if (resume_status == NORESUME)
- return 1;
-
- strncpy( resume_file, str, 255 );
- resume_status = RESUME_SPECIFIED;
-
- return 1;
-}
+ if (IS_ERR(resume_bdev)) {
+ pr_debug("swsusp: block device not initialised\n");
+ return;
+ }
-static int __init noresume_setup(char *str)
-{
- resume_status = NORESUME;
- return 1;
+ blkdev_put(resume_bdev);
}
-
-__setup("noresume", noresume_setup);
-__setup("resume=", resume_setup);
-
-EXPORT_SYMBOL(software_suspend);
-EXPORT_SYMBOL(software_suspend_enabled);