Commit 69a03ded authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] swsusp update: supports discontingmem/highmem

From: Pavel Machek <pavel@ucw.cz>

Bill Irwin did some work on this.  It makes swsusp behave correctly w.r.t. 
discontingmem, and adds highmem handling (very simple-minded, but should work
ok with 1GB).  It now should behave correctly w.r.t.  more than one swap
device, and fixes double restoring of console.
parent 31a359d2
...@@ -24,7 +24,7 @@ typedef struct pbe { ...@@ -24,7 +24,7 @@ typedef struct pbe {
#define SWAP_FILENAME_MAXLENGTH 32 #define SWAP_FILENAME_MAXLENGTH 32
struct suspend_header { struct suspend_header {
__u32 version_code; u32 version_code;
unsigned long num_physpages; unsigned long num_physpages;
char machine[8]; char machine[8];
char version[20]; char version[20];
......
/* /*
* linux/kernel/suspend.c * linux/kernel/power/swsusp.c
* *
* This file is to realize architecture-independent * This file is to realize architecture-independent
* machine suspend feature using pretty near only high-level routines * machine suspend feature using pretty near only high-level routines
* *
* Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu> * Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu>
* Copyright (C) 1998,2001-2003 Pavel Machek <pavel@suse.cz> * Copyright (C) 1998,2001-2004 Pavel Machek <pavel@suse.cz>
* *
* This file is released under the GPLv2. * This file is released under the GPLv2.
* *
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
#include <linux/bootmem.h> #include <linux/bootmem.h>
#include <linux/syscalls.h> #include <linux/syscalls.h>
#include <linux/console.h> #include <linux/console.h>
#include <linux/highmem.h>
#include <asm/uaccess.h> #include <asm/uaccess.h>
#include <asm/mmu_context.h> #include <asm/mmu_context.h>
...@@ -74,11 +75,6 @@ unsigned char software_suspend_enabled = 0; ...@@ -74,11 +75,6 @@ unsigned char software_suspend_enabled = 0;
#define NORESUME 1 #define NORESUME 1
#define RESUME_SPECIFIED 2 #define RESUME_SPECIFIED 2
#define __ADDRESS(x) ((unsigned long) phys_to_virt(x))
#define ADDRESS(x) __ADDRESS((x) << PAGE_SHIFT)
#define ADDRESS2(x) __ADDRESS(__pa(x)) /* Needed for x86-64 where some pages are in memory twice */
/* References to section boundaries */ /* References to section boundaries */
extern char __nosave_begin, __nosave_end; extern char __nosave_begin, __nosave_end;
...@@ -105,6 +101,10 @@ unsigned int nr_copy_pages __nosavedata = 0; ...@@ -105,6 +101,10 @@ unsigned int nr_copy_pages __nosavedata = 0;
time of suspend, that must be freed. Second is "pagedir_nosave", time of suspend, that must be freed. Second is "pagedir_nosave",
allocated at time of resume, that travels through memory not to allocated at time of resume, that travels through memory not to
collide with anything. 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; suspend_pagedir_t *pagedir_nosave __nosavedata = NULL;
static suspend_pagedir_t *pagedir_save; static suspend_pagedir_t *pagedir_save;
...@@ -139,15 +139,15 @@ static const char name_resume[] = "Resume Machine: "; ...@@ -139,15 +139,15 @@ static const char name_resume[] = "Resume Machine: ";
#define TEST_SWSUSP 0 /* Set to 1 to reboot instead of halt machine after suspension */ #define TEST_SWSUSP 0 /* Set to 1 to reboot instead of halt machine after suspension */
#ifdef DEBUG_DEFAULT #ifdef DEBUG_DEFAULT
# define PRINTK(f, a...) printk(f, ## a) # define PRINTK(f, a...) printk(f, ## a)
#else #else
# define PRINTK(f, a...) # define PRINTK(f, a...) do { } while(0)
#endif #endif
#ifdef DEBUG_SLOW #ifdef DEBUG_SLOW
#define MDELAY(a) mdelay(a) #define MDELAY(a) mdelay(a)
#else #else
#define MDELAY(a) #define MDELAY(a) do { } while(0)
#endif #endif
/* /*
...@@ -225,6 +225,7 @@ static void mark_swapfiles(swp_entry_t prev, int mode) ...@@ -225,6 +225,7 @@ static void mark_swapfiles(swp_entry_t prev, int mode)
static void read_swapfiles(void) /* This is called before saving image */ static void read_swapfiles(void) /* This is called before saving image */
{ {
int i, len; int i, len;
char buff[sizeof(resume_file)], *sname;
len=strlen(resume_file); len=strlen(resume_file);
root_swap = 0xFFFF; root_swap = 0xFFFF;
...@@ -243,8 +244,11 @@ static void read_swapfiles(void) /* This is called before saving image */ ...@@ -243,8 +244,11 @@ static void read_swapfiles(void) /* This is called before saving image */
swapfile_used[i] = SWAPFILE_IGNORED; swapfile_used[i] = SWAPFILE_IGNORED;
} else { } else {
/* we ignore all swap devices that are not the resume_file */ /* we ignore all swap devices that are not the resume_file */
if (1) { sname = d_path(swap_info[i].swap_file->f_dentry,
// FIXME if(resume_device == swap_info[i].swap_device) { swap_info[i].swap_file->f_vfsmnt,
buff,
sizeof(buff));
if (!strcmp(sname, resume_file)) {
swapfile_used[i] = SWAPFILE_SUSPEND; swapfile_used[i] = SWAPFILE_SUSPEND;
root_swap = i; root_swap = i;
} else { } else {
...@@ -346,7 +350,7 @@ static int write_suspend_image(void) ...@@ -346,7 +350,7 @@ static int write_suspend_image(void)
cur = (void *) buffer; cur = (void *) buffer;
if (fill_suspend_header(&cur->sh)) if (fill_suspend_header(&cur->sh))
panic("\nOut of memory while writing header"); BUG(); /* Not a BUG_ON(): we want fill_suspend_header to be called, always */
cur->link.next = prev; cur->link.next = prev;
...@@ -362,73 +366,165 @@ static int write_suspend_image(void) ...@@ -362,73 +366,165 @@ static int write_suspend_image(void)
return 0; return 0;
} }
/* if pagedir_p != NULL it also copies the counted pages */ struct highmem_page {
static int count_and_copy_data_pages(struct pbe *pagedir_p) char *data;
{
int chunk_size;
int nr_copy_pages = 0;
int pfn;
struct page *page; struct page *page;
struct highmem_page *next;
#ifdef CONFIG_DISCONTIGMEM };
panic("Discontingmem not supported");
#else struct highmem_page *highmem_copy = NULL;
BUG_ON (max_pfn != num_physpages);
#endif static void save_highmem_zone(struct zone *zone)
for (pfn = 0; pfn < max_pfn; pfn++) { {
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%200))
printk(".");
if (!pfn_valid(pfn))
continue;
page = pfn_to_page(pfn); page = pfn_to_page(pfn);
if (PageHighMem(page)) /*
panic("Swsusp not supported on highmem boxes. Send 1GB of RAM to <pavel@ucw.cz> and try again ;-)."); * 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");
BUG();
}
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)
panic("Not enough memory");
save->next = highmem_copy;
save->page = page;
save->data = (void *) get_zeroed_page(GFP_ATOMIC);
if (!save->data)
panic("Not enough memory");
kaddr = kmap_atomic(page, KM_USER0);
memcpy(save->data, kaddr, PAGE_SIZE);
kunmap_atomic(kaddr, KM_USER0);
highmem_copy = save;
}
}
if (!PageReserved(page)) { static void save_highmem(void)
if (PageNosave(page)) {
continue; struct zone *zone;
for_each_zone(zone) {
if (is_highmem(zone))
save_highmem_zone(zone);
}
}
if ((chunk_size=is_head_of_free_region(page))!=0) { static int restore_highmem(void)
pfn += chunk_size - 1; {
continue; while (highmem_copy) {
} struct highmem_page *save = highmem_copy;
} else if (PageReserved(page)) { void *kaddr;
BUG_ON (PageNosave(page)); 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);
}
return 0;
}
/* static int pfn_is_nosave(unsigned long pfn)
* Just copy whole code segment. Hopefully it is not that big. {
*/ unsigned long nosave_begin_pfn = __pa(&__nosave_begin) >> PAGE_SHIFT;
if ((ADDRESS(pfn) >= (unsigned long) ADDRESS2(&__nosave_begin)) && unsigned long nosave_end_pfn = PAGE_ALIGN(__pa(&__nosave_end)) >> PAGE_SHIFT;
(ADDRESS(pfn) < (unsigned long) ADDRESS2(&__nosave_end))) { return (pfn >= nosave_begin_pfn) && (pfn < nosave_end_pfn);
PRINTK("[nosave %lx]", ADDRESS(pfn)); }
continue;
}
/* Hmm, perhaps copying all reserved pages is not too healthy as they may contain
critical bios data? */
} else BUG();
nr_copy_pages++; /* if *pagedir_p != NULL it also copies the counted pages */
if (pagedir_p) { static int count_and_copy_zone(struct zone *zone, struct pbe **pagedir_p)
pagedir_p->orig_address = ADDRESS(pfn); {
copy_page((void *) pagedir_p->address, (void *) pagedir_p->orig_address); unsigned long zone_pfn, chunk_size, nr_copy_pages = 0;
pagedir_p++; 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%200))
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++;
} }
*pagedir_p = pbe;
return nr_copy_pages; return nr_copy_pages;
} }
static void free_suspend_pagedir(unsigned long this_pagedir) static int count_and_copy_data_pages(struct pbe *pagedir_p)
{ {
struct page *page; int nr_copy_pages = 0;
int pfn; struct zone *zone;
unsigned long this_pagedir_end = this_pagedir + for_each_zone(zone) {
(PAGE_SIZE << pagedir_order); if (!is_highmem(zone))
nr_copy_pages += count_and_copy_zone(zone, &pagedir_p);
}
return nr_copy_pages;
}
for(pfn = 0; pfn < num_physpages; pfn++) { static void free_suspend_pagedir_zone(struct zone *zone, unsigned long pagedir)
{
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); page = pfn_to_page(pfn);
if (!TestClearPageNosave(page)) if (!TestClearPageNosave(page))
continue; continue;
else if (pfn >= pagedir_pfn && pfn < pagedir_end_pfn)
continue;
__free_page(page);
}
}
if (ADDRESS(pfn) >= this_pagedir && ADDRESS(pfn) < this_pagedir_end) static void free_suspend_pagedir(unsigned long this_pagedir)
continue; /* old pagedir gets freed in one */ {
struct zone *zone;
free_page(ADDRESS(pfn)); for_each_zone(zone) {
if (!is_highmem(zone))
free_suspend_pagedir_zone(zone, this_pagedir);
} }
free_pages(this_pagedir, pagedir_order); free_pages(this_pagedir, pagedir_order);
} }
...@@ -443,7 +539,7 @@ static suspend_pagedir_t *create_suspend_pagedir(int nr_copy_pages) ...@@ -443,7 +539,7 @@ static suspend_pagedir_t *create_suspend_pagedir(int nr_copy_pages)
pagedir_order = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages)); pagedir_order = get_bitmask_order(SUSPEND_PD_PAGES(nr_copy_pages));
p = pagedir = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC | __GFP_COLD, pagedir_order); p = pagedir = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC | __GFP_COLD, pagedir_order);
if(!pagedir) if (!pagedir)
return NULL; return NULL;
page = virt_to_page(pagedir); page = virt_to_page(pagedir);
...@@ -492,10 +588,12 @@ static int suspend_prepare_image(void) ...@@ -492,10 +588,12 @@ static int suspend_prepare_image(void)
struct sysinfo i; struct sysinfo i;
unsigned int nr_needed_pages = 0; unsigned int nr_needed_pages = 0;
drain_local_pages();
pagedir_nosave = NULL; pagedir_nosave = NULL;
printk( "/critical section: Counting pages to copy" ); printk( "/critical section: Handling highmem" );
save_highmem();
printk(", counting pages to copy" );
drain_local_pages();
nr_copy_pages = count_and_copy_data_pages(NULL); nr_copy_pages = count_and_copy_data_pages(NULL);
nr_needed_pages = nr_copy_pages + PAGES_FOR_IO; nr_needed_pages = nr_copy_pages + PAGES_FOR_IO;
...@@ -603,21 +701,23 @@ asmlinkage void do_magic_resume_2(void) ...@@ -603,21 +701,23 @@ asmlinkage void do_magic_resume_2(void)
PRINTK( "Freeing prev allocated pagedir\n" ); PRINTK( "Freeing prev allocated pagedir\n" );
free_suspend_pagedir((unsigned long) pagedir_save); free_suspend_pagedir((unsigned long) pagedir_save);
printk( "Restoring highmem\n" );
restore_highmem();
printk("done, devices\n");
device_power_up(); device_power_up();
spin_unlock_irq(&suspend_pagedir_lock); spin_unlock_irq(&suspend_pagedir_lock);
device_resume(); device_resume();
acquire_console_sem(); /* Fixme: this is too late; we should do this ASAP to avoid "infinite reboots" problem */
update_screen(fg_console); /* Hmm, is this the problem? */
release_console_sem();
PRINTK( "Fixing swap signatures... " ); PRINTK( "Fixing swap signatures... " );
mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME); mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
PRINTK( "ok\n" ); PRINTK( "ok\n" );
#ifdef SUSPEND_CONSOLE #ifdef SUSPEND_CONSOLE
acquire_console_sem(); acquire_console_sem();
update_screen(fg_console); /* Hmm, is this the problem? */ update_screen(fg_console);
release_console_sem(); release_console_sem();
#endif #endif
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment