blob: 22dee9a7d7648c97f4919013055164a03b3c3c43 [file] [log] [blame]
/*
* kernel/power/tuxonice_pagedir.c
*
* Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu>
* Copyright (C) 1998,2001,2002 Pavel Machek <pavel@suse.cz>
* Copyright (C) 2002-2003 Florent Chabaud <fchabaud@free.fr>
* Copyright (C) 2006-2010 Nigel Cunningham (nigel at tuxonice net)
*
* This file is released under the GPLv2.
*
* Routines for handling pagesets.
* Note that pbes aren't actually stored as such. They're stored as
* bitmaps and extents.
*/
#include <linux/suspend.h>
#include <linux/highmem.h>
#include <linux/bootmem.h>
#include <linux/hardirq.h>
#include <linux/sched.h>
#include <linux/cpu.h>
#include <asm/tlbflush.h>
#include "tuxonice_pageflags.h"
#include "tuxonice_ui.h"
#include "tuxonice_pagedir.h"
#include "tuxonice_prepare_image.h"
#include "tuxonice.h"
#include "tuxonice_builtin.h"
#include "tuxonice_alloc.h"
static int ptoi_pfn;
static struct pbe *this_low_pbe;
static struct pbe **last_low_pbe_ptr;
void toi_reset_alt_image_pageset2_pfn(void)
{
memory_bm_position_reset(pageset2_map);
}
static struct page *first_conflicting_page;
/*
* free_conflicting_pages
*/
static void free_conflicting_pages(void)
{
while (first_conflicting_page) {
struct page *next = *((struct page **)kmap(first_conflicting_page));
kunmap(first_conflicting_page);
toi__free_page(29, first_conflicting_page);
first_conflicting_page = next;
}
}
/* __toi_get_nonconflicting_page
*
* Description: Gets order zero pages that won't be overwritten
* while copying the original pages.
*/
struct page *___toi_get_nonconflicting_page(int can_be_highmem)
{
struct page *page;
gfp_t flags = TOI_ATOMIC_GFP;
if (can_be_highmem)
flags |= __GFP_HIGHMEM;
if (test_toi_state(TOI_LOADING_ALT_IMAGE) && pageset2_map && (ptoi_pfn != BM_END_OF_MAP)) {
do {
ptoi_pfn = memory_bm_next_pfn(pageset2_map);
if (ptoi_pfn != BM_END_OF_MAP) {
page = pfn_to_page(ptoi_pfn);
if (!PagePageset1(page) && (can_be_highmem || !PageHighMem(page)))
return page;
}
} while (ptoi_pfn != BM_END_OF_MAP);
}
do {
page = toi_alloc_page(29, flags);
if (!page) {
printk(KERN_INFO "Failed to get nonconflicting " "page.\n");
return NULL;
}
if (PagePageset1(page)) {
struct page **next = (struct page **)kmap(page);
*next = first_conflicting_page;
first_conflicting_page = page;
kunmap(page);
}
} while (PagePageset1(page));
return page;
}
unsigned long __toi_get_nonconflicting_page(void)
{
struct page *page = ___toi_get_nonconflicting_page(0);
return page ? (unsigned long)page_address(page) : 0;
}
static struct pbe *get_next_pbe(struct page **page_ptr, struct pbe *this_pbe, int highmem)
{
if (((((unsigned long)this_pbe) & (PAGE_SIZE - 1))
+ 2 * sizeof(struct pbe)) > PAGE_SIZE) {
struct page *new_page = ___toi_get_nonconflicting_page(highmem);
if (!new_page)
return ERR_PTR(-ENOMEM);
this_pbe = (struct pbe *)kmap(new_page);
memset(this_pbe, 0, PAGE_SIZE);
*page_ptr = new_page;
} else
this_pbe++;
return this_pbe;
}
/**
* get_pageset1_load_addresses - generate pbes for conflicting pages
*
* 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.
*
* Returns:
* Zero on success, one if couldn't find enough pages (shouldn't
* happen).
**/
int toi_get_pageset1_load_addresses(void)
{
int pfn, highallocd = 0, lowallocd = 0;
int low_needed = pagedir1.size - get_highmem_size(pagedir1);
int high_needed = get_highmem_size(pagedir1);
int low_pages_for_highmem = 0;
gfp_t flags = GFP_ATOMIC | __GFP_NOWARN | __GFP_HIGHMEM;
struct page *page, *high_pbe_page = NULL, *last_high_pbe_page = NULL,
*low_pbe_page, *last_low_pbe_page = NULL;
struct pbe **last_high_pbe_ptr = &restore_highmem_pblist, *this_high_pbe = NULL;
unsigned long orig_low_pfn, orig_high_pfn;
int high_pbes_done = 0, low_pbes_done = 0;
int low_direct = 0, high_direct = 0, result = 0, i;
int high_page = 1, high_offset = 0, low_page = 1, low_offset = 0;
memory_bm_set_iterators(pageset1_map, 3);
memory_bm_position_reset(pageset1_map);
memory_bm_set_iterators(pageset1_copy_map, 2);
memory_bm_position_reset(pageset1_copy_map);
last_low_pbe_ptr = &restore_pblist;
/* First, allocate pages for the start of our pbe lists. */
if (high_needed) {
high_pbe_page = ___toi_get_nonconflicting_page(1);
if (!high_pbe_page) {
result = -ENOMEM;
goto out;
}
this_high_pbe = (struct pbe *)kmap(high_pbe_page);
memset(this_high_pbe, 0, PAGE_SIZE);
}
low_pbe_page = ___toi_get_nonconflicting_page(0);
if (!low_pbe_page) {
result = -ENOMEM;
goto out;
}
this_low_pbe = (struct pbe *)page_address(low_pbe_page);
/*
* Next, allocate the number of pages we need.
*/
i = low_needed + high_needed;
do {
int is_high;
if (i == low_needed)
flags &= ~__GFP_HIGHMEM;
page = toi_alloc_page(30, flags);
BUG_ON(!page);
SetPagePageset1Copy(page);
is_high = PageHighMem(page);
if (PagePageset1(page)) {
if (is_high)
high_direct++;
else
low_direct++;
} else {
if (is_high)
highallocd++;
else
lowallocd++;
}
} while (--i);
high_needed -= high_direct;
low_needed -= low_direct;
/*
* Do we need to use some lowmem pages for the copies of highmem
* pages?
*/
if (high_needed > highallocd) {
low_pages_for_highmem = high_needed - highallocd;
high_needed -= low_pages_for_highmem;
low_needed += low_pages_for_highmem;
}
/*
* Now generate our pbes (which will be used for the atomic restore),
* and free unneeded pages.
*/
memory_bm_position_reset(pageset1_copy_map);
for (pfn = memory_bm_next_pfn_index(pageset1_copy_map, 1); pfn != BM_END_OF_MAP;
pfn = memory_bm_next_pfn_index(pageset1_copy_map, 1)) {
int is_high;
page = pfn_to_page(pfn);
is_high = PageHighMem(page);
if (PagePageset1(page))
continue;
/* Nope. We're going to use this page. Add a pbe. */
if (is_high || low_pages_for_highmem) {
struct page *orig_page;
high_pbes_done++;
if (!is_high)
low_pages_for_highmem--;
do {
orig_high_pfn = memory_bm_next_pfn_index(pageset1_map, 1);
BUG_ON(orig_high_pfn == BM_END_OF_MAP);
orig_page = pfn_to_page(orig_high_pfn);
} while (!PageHighMem(orig_page) || PagePageset1Copy(orig_page));
this_high_pbe->orig_address = (void *)orig_high_pfn;
this_high_pbe->address = page;
this_high_pbe->next = NULL;
toi_message(TOI_PAGEDIR, TOI_VERBOSE, 0, "High pbe %d/%d: %p(%d)=>%p",
high_page, high_offset, page, orig_high_pfn, orig_page);
if (last_high_pbe_page != high_pbe_page) {
*last_high_pbe_ptr = (struct pbe *)high_pbe_page;
if (last_high_pbe_page) {
kunmap(last_high_pbe_page);
high_page++;
high_offset = 0;
} else
high_offset++;
last_high_pbe_page = high_pbe_page;
} else {
*last_high_pbe_ptr = this_high_pbe;
high_offset++;
}
last_high_pbe_ptr = &this_high_pbe->next;
this_high_pbe = get_next_pbe(&high_pbe_page, this_high_pbe, 1);
if (IS_ERR(this_high_pbe)) {
printk(KERN_INFO "This high pbe is an error.\n");
return -ENOMEM;
}
} else {
struct page *orig_page;
low_pbes_done++;
do {
orig_low_pfn = memory_bm_next_pfn_index(pageset1_map, 2);
BUG_ON(orig_low_pfn == BM_END_OF_MAP);
orig_page = pfn_to_page(orig_low_pfn);
} while (PageHighMem(orig_page) || PagePageset1Copy(orig_page));
this_low_pbe->orig_address = page_address(orig_page);
this_low_pbe->address = page_address(page);
this_low_pbe->next = NULL;
toi_message(TOI_PAGEDIR, TOI_VERBOSE, 0, "Low pbe %d/%d: %p(%d)=>%p",
low_page, low_offset, this_low_pbe->orig_address,
orig_low_pfn, this_low_pbe->address);
*last_low_pbe_ptr = this_low_pbe;
last_low_pbe_ptr = &this_low_pbe->next;
this_low_pbe = get_next_pbe(&low_pbe_page, this_low_pbe, 0);
if (low_pbe_page != last_low_pbe_page) {
if (last_low_pbe_page) {
low_page++;
low_offset = 0;
}
last_low_pbe_page = low_pbe_page;
} else
low_offset++;
if (IS_ERR(this_low_pbe)) {
printk(KERN_INFO "this_low_pbe is an error.\n");
return -ENOMEM;
}
}
}
if (high_pbe_page)
kunmap(high_pbe_page);
if (last_high_pbe_page != high_pbe_page) {
if (last_high_pbe_page)
kunmap(last_high_pbe_page);
toi__free_page(29, high_pbe_page);
}
free_conflicting_pages();
out:
memory_bm_set_iterators(pageset1_map, 1);
memory_bm_set_iterators(pageset1_copy_map, 1);
return result;
}
int add_boot_kernel_data_pbe(void)
{
this_low_pbe->address = (char *)__toi_get_nonconflicting_page();
if (!this_low_pbe->address) {
printk(KERN_INFO "Failed to get bkd atomic restore buffer.");
return -ENOMEM;
}
toi_bkd.size = sizeof(toi_bkd);
memcpy(this_low_pbe->address, &toi_bkd, sizeof(toi_bkd));
*last_low_pbe_ptr = this_low_pbe;
this_low_pbe->orig_address = (char *)boot_kernel_data_buffer;
this_low_pbe->next = NULL;
return 0;
}