2004-04-05 21:34:35

by Pavel Machek

[permalink] [raw]
Subject: swsusp update: supports discontingmem/highmem

Hi!

wli 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. Please apply,

Pavel

[okay, this probably should receive some -mm testing before going
mainline.]

Index: linux/include/linux/suspend.h
===================================================================
--- linux.orig/include/linux/suspend.h 2004-04-05 22:47:34.000000000 +0200
+++ linux/include/linux/suspend.h 2004-03-11 18:18:31.000000000 +0100
@@ -24,7 +24,7 @@
#define SWAP_FILENAME_MAXLENGTH 32

struct suspend_header {
- __u32 version_code;
+ u32 version_code;
unsigned long num_physpages;
char machine[8];
char version[20];
Index: linux/kernel/power/swsusp.c
===================================================================
--- linux.orig/kernel/power/swsusp.c 2004-04-05 22:47:34.000000000 +0200
+++ linux/kernel/power/swsusp.c 2004-04-05 22:31:10.000000000 +0200
@@ -1,11 +1,11 @@
/*
- * linux/kernel/suspend.c
+ * linux/kernel/power/swsusp.c
*
* This file is to realize architecture-independent
* machine suspend feature using pretty near only high-level routines
*
* Copyright (C) 1998-2001 Gabor Kuti <[email protected]>
- * Copyright (C) 1998,2001-2003 Pavel Machek <[email protected]>
+ * Copyright (C) 1998,2001-2004 Pavel Machek <[email protected]>
*
* This file is released under the GPLv2.
*
@@ -61,6 +61,7 @@
#include <linux/bootmem.h>
#include <linux/syscalls.h>
#include <linux/console.h>
+#include <linux/highmem.h>

#include <asm/uaccess.h>
#include <asm/mmu_context.h>
@@ -74,11 +75,6 @@
#define NORESUME 1
#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 */
extern char __nosave_begin, __nosave_end;

@@ -105,6 +101,10 @@
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;
@@ -139,15 +139,15 @@
#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)
+# define PRINTK(f, a...) printk(f, ## a)
#else
-# define PRINTK(f, a...)
+# define PRINTK(f, a...) do { } while(0)
#endif

#ifdef DEBUG_SLOW
#define MDELAY(a) mdelay(a)
#else
-#define MDELAY(a)
+#define MDELAY(a) do { } while(0)
#endif

/*
@@ -225,6 +225,7 @@
static void read_swapfiles(void) /* This is called before saving image */
{
int i, len;
+ char buff[sizeof(resume_file)], *sname;

len=strlen(resume_file);
root_swap = 0xFFFF;
@@ -243,8 +244,11 @@
swapfile_used[i] = SWAPFILE_IGNORED;
} else {
/* we ignore all swap devices that are not the resume_file */
- if (1) {
-// FIXME if(resume_device == swap_info[i].swap_device) {
+ 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 {
@@ -346,7 +350,7 @@

cur = (void *) buffer;
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;

@@ -362,73 +366,165 @@
return 0;
}

-/* if pagedir_p != NULL it also copies the counted pages */
-static int count_and_copy_data_pages(struct pbe *pagedir_p)
-{
- int chunk_size;
- int nr_copy_pages = 0;
- int pfn;
+struct highmem_page {
+ char *data;
struct page *page;
-
-#ifdef CONFIG_DISCONTIGMEM
- panic("Discontingmem not supported");
-#else
- BUG_ON (max_pfn != num_physpages);
-#endif
- for (pfn = 0; pfn < max_pfn; pfn++) {
+ struct highmem_page *next;
+};
+
+struct highmem_page *highmem_copy = NULL;
+
+static void 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%200))
+ printk(".");
+ if (!pfn_valid(pfn))
+ continue;
page = pfn_to_page(pfn);
- if (PageHighMem(page))
- panic("Swsusp not supported on highmem boxes. Send 1GB of RAM to <[email protected]> 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)) {
- if (PageNosave(page))
- continue;
-
- if ((chunk_size=is_head_of_free_region(page))!=0) {
- pfn += chunk_size - 1;
- continue;
- }
- } else if (PageReserved(page)) {
- BUG_ON (PageNosave(page));
+static void save_highmem(void)
+{
+ struct zone *zone;
+ for_each_zone(zone) {
+ if (is_highmem(zone))
+ save_highmem_zone(zone);
+ }
+}

- /*
- * Just copy whole code segment. Hopefully it is not that big.
- */
- if ((ADDRESS(pfn) >= (unsigned long) ADDRESS2(&__nosave_begin)) &&
- (ADDRESS(pfn) < (unsigned long) ADDRESS2(&__nosave_end))) {
- 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();
+static int restore_highmem(void)
+{
+ 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);
+ }
+ return 0;
+}

- nr_copy_pages++;
- if (pagedir_p) {
- pagedir_p->orig_address = ADDRESS(pfn);
- copy_page((void *) pagedir_p->address, (void *) pagedir_p->orig_address);
- pagedir_p++;
+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);
+}
+
+/* if *pagedir_p != NULL it also copies the counted pages */
+static int count_and_copy_zone(struct zone *zone, struct pbe **pagedir_p)
+{
+ 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%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;
}

-static void free_suspend_pagedir(unsigned long this_pagedir)
+static int count_and_copy_data_pages(struct pbe *pagedir_p)
{
- struct page *page;
- int pfn;
- unsigned long this_pagedir_end = this_pagedir +
- (PAGE_SIZE << pagedir_order);
+ 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);
+ }
+ 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);
if (!TestClearPageNosave(page))
continue;
+ else if (pfn >= pagedir_pfn && pfn < pagedir_end_pfn)
+ continue;
+ __free_page(page);
+ }
+}

- if (ADDRESS(pfn) >= this_pagedir && ADDRESS(pfn) < this_pagedir_end)
- continue; /* old pagedir gets freed in one */
-
- free_page(ADDRESS(pfn));
+static void free_suspend_pagedir(unsigned long this_pagedir)
+{
+ struct zone *zone;
+ for_each_zone(zone) {
+ if (!is_highmem(zone))
+ free_suspend_pagedir_zone(zone, this_pagedir);
}
free_pages(this_pagedir, pagedir_order);
}
@@ -443,7 +539,7 @@
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);
- if(!pagedir)
+ if (!pagedir)
return NULL;

page = virt_to_page(pagedir);
@@ -492,10 +588,12 @@
struct sysinfo i;
unsigned int nr_needed_pages = 0;

- drain_local_pages();
-
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_needed_pages = nr_copy_pages + PAGES_FOR_IO;

@@ -603,21 +701,23 @@

PRINTK( "Freeing prev allocated pagedir\n" );
free_suspend_pagedir((unsigned long) pagedir_save);
+
+ printk( "Restoring highmem\n" );
+ restore_highmem();
+ printk("done, devices\n");
+
device_power_up();
spin_unlock_irq(&suspend_pagedir_lock);
device_resume();

- acquire_console_sem();
- update_screen(fg_console); /* Hmm, is this the problem? */
- release_console_sem();
-
+ /* 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); /* Hmm, is this the problem? */
+ update_screen(fg_console);
release_console_sem();
#endif
}

--
When do you have a heart between your knees?
[Johanka's followup: and *two* hearts?]


2004-04-05 22:38:22

by Andrew Morton

[permalink] [raw]
Subject: Re: swsusp update: supports discontingmem/highmem

Pavel Machek <[email protected]> wrote:
>
> It makes swsusp behave correctly
> w.r.t. discontingmem, and adds highmem handling

Some of those ENOMEM panics in save_highmem_zone() look like they might
need proper handling instead?

The 256 byte automatic array in read_swapfiles() may bring you a visit from
the stack space police, although I don't think it's really a problem. 256
bytes for a pathname may be a bit excessive though.

Please send me an update patch sometime which makes all the new code go
away again if !CONFIG_HIGHMEM.

Thanks.

2004-04-05 22:43:03

by Nigel Cunningham

[permalink] [raw]
Subject: Re: swsusp update: supports discontingmem/highmem

Hi.

On Tue, 2004-04-06 at 07:23, Pavel Machek wrote:
> + if (PageReserved(page)) {
> + printk("highmem reserved page?!\n");
> + BUG();
> + }

We dealt with this recently in suspend2. It's perfectly valid to have a
Reserved Highmem page. They need to be completely ignored, and whatever
driver is responsible for the memory should handle any required state
persistance. We're setting NoSave for such pages in the parsing of the
e820 table at boot time.

> + if (!save)
> + panic("Not enough memory");

Can't you back out nicely if you don't have enough memory for the image?

> + if (!save->data)
> + panic("Not enough memory");

(Ditto)

> + /* Fixme: this is too late; we should do this ASAP to avoid "infinite reboots" problem */

Fully agree. I've been meaning to do this too.

Regards,

Nigel
--
Nigel Cunningham
C/- Westminster Presbyterian Church Belconnen
61 Templeton Street, Cook, ACT 2614.
+61 (2) 6251 7727(wk); +61 (2) 6253 0250 (home)

Evolution (n): A hypothetical process whereby infinitely improbable events occur
with alarming frequency, order arises from chaos, and no one is given credit.

2004-04-06 11:09:17

by Pavel Machek

[permalink] [raw]
Subject: Re: swsusp update: supports discontingmem/highmem

Hi!

> On Tue, 2004-04-06 at 07:23, Pavel Machek wrote:
> > + if (PageReserved(page)) {
> > + printk("highmem reserved page?!\n");
> > + BUG();
> > + }
>
> We dealt with this recently in suspend2. It's perfectly valid to have a
> Reserved Highmem page. They need to be completely ignored, and whatever
> driver is responsible for the memory should handle any required state
> persistance. We're setting NoSave for such pages in the parsing of the
> e820 table at boot time.

Okay, I changed BUG() into continue. I still left printk, so that when
something goes wrong, we still know.

> > + if (!save)
> > + panic("Not enough memory");
>
> Can't you back out nicely if you don't have enough memory for the image?
>
> > + if (!save->data)
> > + panic("Not enough memory");
>
> (Ditto)

Ugh. I had this in my tree, unfortunately in my other tree. Here's the
diff. (Do not apply it just yet, I'll add #ifdef CONFIG_HIGHMEMs).

Pavel

--- clean/kernel/power/swsusp.c 2004-03-26 12:10:06.000000000 +0100
+++ linux/kernel/power/swsusp.c 2004-03-26 17:06:31.000000000 +0100
@@ -374,7 +374,7 @@

struct highmem_page *highmem_copy = NULL;

-static void save_highmem_zone(struct zone *zone)
+static int save_highmem_zone(struct zone *zone)
{
unsigned long zone_pfn;
for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
@@ -384,7 +384,7 @@
unsigned long pfn = zone_pfn + zone->zone_start_pfn;
int chunk_size;

- if (!(pfn%200))
+ if (!(pfn%1000))
printk(".");
if (!pfn_valid(pfn))
continue;
@@ -406,26 +406,33 @@
}
save = kmalloc(sizeof(struct highmem_page), GFP_ATOMIC);
if (!save)
- panic("Not enough memory");
+ return -ENOMEM;
save->next = highmem_copy;
save->page = page;
save->data = (void *) get_zeroed_page(GFP_ATOMIC);
- if (!save->data)
- panic("Not enough memory");
+ if (!save->data) {
+ kfree(save);
+ return -ENOMEM;
+ }
kaddr = kmap_atomic(page, KM_USER0);
memcpy(save->data, kaddr, PAGE_SIZE);
kunmap_atomic(kaddr, KM_USER0);
highmem_copy = save;
}
+ return 0;
}

-static void save_highmem(void)
+static int save_highmem(void)
{
struct zone *zone;
+ int res = 0;
for_each_zone(zone) {
if (is_highmem(zone))
- save_highmem_zone(zone);
+ res = save_highmem_zone(zone);
+ if (res)
+ return res;
}
+ return 0;
}

static int restore_highmem(void)
@@ -460,7 +467,7 @@
struct page *page;
unsigned long pfn = zone_pfn + zone->zone_start_pfn;

- if (!(pfn%200))
+ if (!(pfn%1000))
printk(".");
if (!pfn_valid(pfn))
continue;
@@ -539,7 +546,7 @@
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);
- if(!pagedir)
+ if (!pagedir)
return NULL;

page = virt_to_page(pagedir);
@@ -548,7 +555,7 @@

while(nr_copy_pages--) {
p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
- if(!p->address) {
+ if (!p->address) {
free_suspend_pagedir((unsigned long) pagedir);
return NULL;
}
@@ -590,7 +597,10 @@

pagedir_nosave = NULL;
printk( "/critical section: Handling highmem" );
- save_highmem();
+ if (save_highmem()) {
+ printk(KERN_CRIT "%sNot enough free pages for highmem\n", name_suspend);
+ return -ENOMEM;
+ }

printk(", counting pages to copy" );
drain_local_pages();
@@ -602,23 +612,22 @@
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 1;
+ 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 1;
+ return -ENOSPC;
}

PRINTK( "Alloc pagedir\n" );
pagedir_save = pagedir_nosave = create_suspend_pagedir(nr_copy_pages);
- if(!pagedir_nosave) {
- /* Shouldn't happen */
- printk(KERN_CRIT "%sCouldn't allocate enough pages\n",name_suspend);
- panic("Really should not happen");
- return 1;
+ 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);
+ return -ENOMEM;
}
nr_copy_pages_check = nr_copy_pages;
pagedir_order_check = pagedir_order;

--
When do you have a heart between your knees?
[Johanka's followup: and *two* hearts?]

2004-04-06 12:16:04

by Pavel Machek

[permalink] [raw]
Subject: Re: swsusp update: supports discontingmem/highmem

Hi!

> > It makes swsusp behave correctly
> > w.r.t. discontingmem, and adds highmem handling
>
> Some of those ENOMEM panics in save_highmem_zone() look like they might
> need proper handling instead?

Done.

> The 256 byte automatic array in read_swapfiles() may bring you a visit from
> the stack space police, although I don't think it's really a problem. 256
> bytes for a pathname may be a bit excessive though.

Marked static, its only called once anyway.

> Please send me an update patch sometime which makes all the new code go
> away again if !CONFIG_HIGHMEM.

Done; plus I added some warnings to swsusp.S (based on discussion with
wli). Relative to previous diff, please apply.
Pavel

--- tmp/linux/arch/i386/power/swsusp.S 2003-09-28 22:05:30.000000000 +0200
+++ linux/arch/i386/power/swsusp.S 2004-04-06 13:25:42.000000000 +0200
@@ -1,6 +1,13 @@
.text

-/* Originally gcc generated, modified by hand */
+/* Originally gcc generated, modified by hand
+ *
+ * This may not use any stack, nor any variable that is not "NoSave":
+ *
+ * Its rewriting one kernel image with another. What is stack in "old"
+ * image could very well be data page in "new" image, and overwriting
+ * your own stack under you is bad idea.
+ */

#include <linux/linkage.h>
#include <asm/segment.h>
--- tmp/linux/kernel/power/swsusp.c 2004-04-06 13:26:39.000000000 +0200
+++ linux/kernel/power/swsusp.c 2004-04-06 13:07:46.000000000 +0200
@@ -225,7 +225,7 @@
static void read_swapfiles(void) /* This is called before saving image */
{
int i, len;
- char buff[sizeof(resume_file)], *sname;
+ static char buff[sizeof(resume_file)], *sname;

len=strlen(resume_file);
root_swap = 0xFFFF;
@@ -366,6 +366,7 @@
return 0;
}

+#ifdef CONFIG_HIGHMEM
struct highmem_page {
char *data;
struct page *page;
@@ -374,7 +375,7 @@

struct highmem_page *highmem_copy = NULL;

-static void save_highmem_zone(struct zone *zone)
+static int save_highmem_zone(struct zone *zone)
{
unsigned long zone_pfn;
for (zone_pfn = 0; zone_pfn < zone->spanned_pages; ++zone_pfn) {
@@ -384,7 +385,7 @@
unsigned long pfn = zone_pfn + zone->zone_start_pfn;
int chunk_size;

- if (!(pfn%200))
+ if (!(pfn%1000))
printk(".");
if (!pfn_valid(pfn))
continue;
@@ -397,7 +398,7 @@
*/
if (PageReserved(page)) {
printk("highmem reserved page?!\n");
- BUG();
+ continue;
}
if ((chunk_size = is_head_of_free_region(page))) {
pfn += chunk_size - 1;
@@ -406,26 +407,33 @@
}
save = kmalloc(sizeof(struct highmem_page), GFP_ATOMIC);
if (!save)
- panic("Not enough memory");
+ return -ENOMEM;
save->next = highmem_copy;
save->page = page;
save->data = (void *) get_zeroed_page(GFP_ATOMIC);
- if (!save->data)
- panic("Not enough memory");
+ if (!save->data) {
+ kfree(save);
+ return -ENOMEM;
+ }
kaddr = kmap_atomic(page, KM_USER0);
memcpy(save->data, kaddr, PAGE_SIZE);
kunmap_atomic(kaddr, KM_USER0);
highmem_copy = save;
}
+ return 0;
}

-static void save_highmem(void)
+static int save_highmem(void)
{
struct zone *zone;
+ int res = 0;
for_each_zone(zone) {
if (is_highmem(zone))
- save_highmem_zone(zone);
+ res = save_highmem_zone(zone);
+ if (res)
+ return res;
}
+ return 0;
}

static int restore_highmem(void)
@@ -443,6 +451,7 @@
}
return 0;
}
+#endif

static int pfn_is_nosave(unsigned long pfn)
{
@@ -460,7 +469,7 @@
struct page *page;
unsigned long pfn = zone_pfn + zone->zone_start_pfn;

- if (!(pfn%200))
+ if (!(pfn%1000))
printk(".");
if (!pfn_valid(pfn))
continue;
@@ -548,7 +557,7 @@

while(nr_copy_pages--) {
p->address = get_zeroed_page(GFP_ATOMIC | __GFP_COLD);
- if(!p->address) {
+ if (!p->address) {
free_suspend_pagedir((unsigned long) pagedir);
return NULL;
}
@@ -589,10 +598,17 @@
unsigned int nr_needed_pages = 0;

pagedir_nosave = NULL;
- printk( "/critical section: Handling highmem" );
- save_highmem();
+ 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;
+ }
+ printk(", ");
+#endif

- printk(", counting pages to copy" );
+ 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;
@@ -602,23 +618,22 @@
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 1;
+ 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 1;
+ return -ENOSPC;
}

PRINTK( "Alloc pagedir\n" );
pagedir_save = pagedir_nosave = create_suspend_pagedir(nr_copy_pages);
- if(!pagedir_nosave) {
- /* Shouldn't happen */
- printk(KERN_CRIT "%sCouldn't allocate enough pages\n",name_suspend);
- panic("Really should not happen");
- return 1;
+ 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);
+ return -ENOMEM;
}
nr_copy_pages_check = nr_copy_pages;
pagedir_order_check = pagedir_order;
@@ -702,8 +717,10 @@
PRINTK( "Freeing prev allocated pagedir\n" );
free_suspend_pagedir((unsigned long) pagedir_save);

+#ifdef CONFIG_HIGHMEM
printk( "Restoring highmem\n" );
restore_highmem();
+#endif
printk("done, devices\n");

device_power_up();

--
When do you have a heart between your knees?
[Johanka's followup: and *two* hearts?]