Rafael J. WysockiMerge branch 'pm-sleep'b38f67c12 years agoHistory
2360 lines · 60.5 KB · C
RawDownload
/* * linux/kernel/power/snapshot.c * * This file provides system snapshot/restore functionality for swsusp. * * Copyright (C) 1998-2005 Pavel Machek <pavel@ucw.cz> * Copyright (C) 2006 Rafael J. Wysocki <rjw@sisk.pl> * * This file is released under the GPLv2. * */ #include <linux/version.h>#include <linux/module.h>#include <linux/mm.h>#include <linux/suspend.h>#include <linux/delay.h>#include <linux/bitops.h>#include <linux/spinlock.h>#include <linux/kernel.h>#include <linux/pm.h>#include <linux/device.h>#include <linux/init.h>#include <linux/bootmem.h>#include <linux/syscalls.h>#include <linux/console.h>#include <linux/highmem.h>#include <linux/list.h>#include <linux/slab.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" static int swsusp_page_is_free(struct page *);static void swsusp_set_page_forbidden(struct page *);static void swsusp_unset_page_forbidden(struct page *); /* * Number of bytes to reserve for memory allocations made by device drivers * from their ->freeze() and ->freeze_noirq() callbacks so that they don't * cause image creation to fail (tunable via /sys/power/reserved_size). */unsigned long reserved_size; void __init hibernate_reserved_size_init(void){	reserved_size = SPARE_PAGES * PAGE_SIZE;} /* * 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. */unsigned long image_size; void __init hibernate_image_size_init(void){	image_size = ((totalram_pages * 2) / 5) * PAGE_SIZE;} /* List of PBEs needed for restoring the pages that were allocated before * the suspend and included in the suspend image, but have also been * allocated by the "resume" kernel, so their contents cannot be written * directly to their "original" page frames. */struct pbe *restore_pblist; /* Pointer to an auxiliary buffer (1 page) */static void *buffer; /** *	@safe_needed - on resume, for storing the PBE list and the image, *	we can only use memory pages that do not conflict with the pages *	used before suspend.  The unsafe pages have PageNosaveFree set *	and we count them using unsafe_pages. * *	Each allocated image page is marked as PageNosave and PageNosaveFree *	so that swsusp_free() can release it. */ #define PG_ANY		0#define PG_SAFE		1#define PG_UNSAFE_CLEAR	1#define PG_UNSAFE_KEEP	0 static unsigned int allocated_unsafe_pages; static void *get_image_page(gfp_t gfp_mask, int safe_needed){	void *res; 	res = (void *)get_zeroed_page(gfp_mask);	if (safe_needed)		while (res && swsusp_page_is_free(virt_to_page(res))) {			/* The page is unsafe, mark it for swsusp_free() */			swsusp_set_page_forbidden(virt_to_page(res));			allocated_unsafe_pages++;			res = (void *)get_zeroed_page(gfp_mask);		}	if (res) {		swsusp_set_page_forbidden(virt_to_page(res));		swsusp_set_page_free(virt_to_page(res));	}	return res;} unsigned long get_safe_page(gfp_t gfp_mask){	return (unsigned long)get_image_page(gfp_mask, PG_SAFE);} static struct page *alloc_image_page(gfp_t gfp_mask){	struct page *page; 	page = alloc_page(gfp_mask);	if (page) {		swsusp_set_page_forbidden(page);		swsusp_set_page_free(page);	}	return page;} /** *	free_image_page - free page represented by @addr, allocated with *	get_image_page (page flags set by it must be cleared) */ static inline void free_image_page(void *addr, int clear_nosave_free){	struct page *page; 	BUG_ON(!virt_addr_valid(addr)); 	page = virt_to_page(addr); 	swsusp_unset_page_forbidden(page);	if (clear_nosave_free)		swsusp_unset_page_free(page); 	__free_page(page);} /* struct linked_page is used to build chains of pages */ #define LINKED_PAGE_DATA_SIZE	(PAGE_SIZE - sizeof(void *)) struct linked_page {	struct linked_page *next;	char data[LINKED_PAGE_DATA_SIZE];} __attribute__((packed)); static inline voidfree_list_of_pages(struct linked_page *list, int clear_page_nosave){	while (list) {		struct linked_page *lp = list->next; 		free_image_page(list, clear_page_nosave);		list = lp;	}} /**  *	struct chain_allocator is used for allocating small objects out of  *	a linked list of pages called 'the chain'.  *  *	The chain grows each time when there is no room for a new object in  *	the current page.  The allocated objects cannot be freed individually.  *	It is only possible to free them all at once, by freeing the entire  *	chain.  *  *	NOTE: The chain allocator may be inefficient if the allocated objects  *	are not much smaller than PAGE_SIZE.  */ struct chain_allocator {	struct linked_page *chain;	/* the chain */	unsigned int used_space;	/* total size of objects allocated out					 * of the current page					 */	gfp_t gfp_mask;		/* mask for allocating pages */	int safe_needed;	/* if set, only "safe" pages are allocated */}; static voidchain_init(struct chain_allocator *ca, gfp_t gfp_mask, int safe_needed){	ca->chain = NULL;	ca->used_space = LINKED_PAGE_DATA_SIZE;	ca->gfp_mask = gfp_mask;	ca->safe_needed = safe_needed;} static void *chain_alloc(struct chain_allocator *ca, unsigned int size){	void *ret; 	if (LINKED_PAGE_DATA_SIZE - ca->used_space < size) {		struct linked_page *lp; 		lp = get_image_page(ca->gfp_mask, ca->safe_needed);		if (!lp)			return NULL; 		lp->next = ca->chain;		ca->chain = lp;		ca->used_space = 0;	}	ret = ca->chain->data + ca->used_space;	ca->used_space += size;	return ret;} /** *	Data types related to memory bitmaps. * *	Memory bitmap is a structure consiting of many linked lists of *	objects.  The main list's elements are of type struct zone_bitmap *	and each of them corresonds to one zone.  For each zone bitmap *	object there is a list of objects of type struct bm_block that *	represent each blocks of bitmap in which information is stored. * *	struct memory_bitmap contains a pointer to the main list of zone *	bitmap objects, a struct bm_position used for browsing the bitmap, *	and a pointer to the list of pages used for allocating all of the *	zone bitmap objects and bitmap block objects. * *	NOTE: It has to be possible to lay out the bitmap in memory *	using only allocations of order 0.  Additionally, the bitmap is *	designed to work with arbitrary number of zones (this is over the *	top for now, but let's avoid making unnecessary assumptions ;-). * *	struct zone_bitmap contains a pointer to a list of bitmap block *	objects and a pointer to the bitmap block object that has been *	most recently used for setting bits.  Additionally, it contains the *	pfns that correspond to the start and end of the represented zone. * *	struct bm_block contains a pointer to the memory page in which *	information is stored (in the form of a block of bitmap) *	It also contains the pfns that correspond to the start and end of *	the represented memory area. */ #define BM_END_OF_MAP	(~0UL) #define BM_BITS_PER_BLOCK	(PAGE_SIZE * BITS_PER_BYTE) struct bm_block {	struct list_head hook;	/* hook into a list of bitmap blocks */	unsigned long start_pfn;	/* pfn represented by the first bit */	unsigned long end_pfn;	/* pfn represented by the last bit plus 1 */	unsigned long *data;	/* bitmap representing pages */}; static inline unsigned long bm_block_bits(struct bm_block *bb){	return bb->end_pfn - bb->start_pfn;} /* strcut bm_position is used for browsing memory bitmaps */ struct bm_position {	struct bm_block *block;	int bit;}; struct memory_bitmap {	struct list_head blocks;	/* list of bitmap blocks */	struct linked_page *p_list;	/* list of pages used to store zone					 * bitmap objects and bitmap block					 * objects					 */	struct bm_position cur;	/* most recently used bit position */}; /* Functions that operate on memory bitmaps */ static void memory_bm_position_reset(struct memory_bitmap *bm){	bm->cur.block = list_entry(bm->blocks.next, struct bm_block, hook);	bm->cur.bit = 0;} static void memory_bm_free(struct memory_bitmap *bm, int clear_nosave_free); /** *	create_bm_block_list - create a list of block bitmap objects *	@pages - number of pages to track *	@list - list to put the allocated blocks into *	@ca - chain allocator to be used for allocating memory */static int create_bm_block_list(unsigned long pages,				struct list_head *list,				struct chain_allocator *ca){	unsigned int nr_blocks = DIV_ROUND_UP(pages, BM_BITS_PER_BLOCK); 	while (nr_blocks-- > 0) {		struct bm_block *bb; 		bb = chain_alloc(ca, sizeof(struct bm_block));		if (!bb)			return -ENOMEM;		list_add(&bb->hook, list);	} 	return 0;} struct mem_extent {	struct list_head hook;	unsigned long start;	unsigned long end;}; /** *	free_mem_extents - free a list of memory extents *	@list - list of extents to empty */static void free_mem_extents(struct list_head *list){	struct mem_extent *ext, *aux; 	list_for_each_entry_safe(ext, aux, list, hook) {		list_del(&ext->hook);		kfree(ext);	}} /** *	create_mem_extents - create a list of memory extents representing *	                     contiguous ranges of PFNs *	@list - list to put the extents into *	@gfp_mask - mask to use for memory allocations */static int create_mem_extents(struct list_head *list, gfp_t gfp_mask){	struct zone *zone; 	INIT_LIST_HEAD(list); 	for_each_populated_zone(zone) {		unsigned long zone_start, zone_end;		struct mem_extent *ext, *cur, *aux; 		zone_start = zone->zone_start_pfn;		zone_end = zone_end_pfn(zone); 		list_for_each_entry(ext, list, hook)			if (zone_start <= ext->end)				break; 		if (&ext->hook == list || zone_end < ext->start) {			/* New extent is necessary */			struct mem_extent *new_ext; 			new_ext = kzalloc(sizeof(struct mem_extent), gfp_mask);			if (!new_ext) {				free_mem_extents(list);				return -ENOMEM;			}			new_ext->start = zone_start;			new_ext->end = zone_end;			list_add_tail(&new_ext->hook, &ext->hook);			continue;		} 		/* Merge this zone's range of PFNs with the existing one */		if (zone_start < ext->start)			ext->start = zone_start;		if (zone_end > ext->end)			ext->end = zone_end; 		/* More merging may be possible */		cur = ext;		list_for_each_entry_safe_continue(cur, aux, list, hook) {			if (zone_end < cur->start)				break;			if (zone_end < cur->end)				ext->end = cur->end;			list_del(&cur->hook);			kfree(cur);		}	} 	return 0;} /**  *	memory_bm_create - allocate memory for a memory bitmap  */static intmemory_bm_create(struct memory_bitmap *bm, gfp_t gfp_mask, int safe_needed){	struct chain_allocator ca;	struct list_head mem_extents;	struct mem_extent *ext;	int error; 	chain_init(&ca, gfp_mask, safe_needed);	INIT_LIST_HEAD(&bm->blocks); 	error = create_mem_extents(&mem_extents, gfp_mask);	if (error)		return error; 	list_for_each_entry(ext, &mem_extents, hook) {		struct bm_block *bb;		unsigned long pfn = ext->start;		unsigned long pages = ext->end - ext->start; 		bb = list_entry(bm->blocks.prev, struct bm_block, hook); 		error = create_bm_block_list(pages, bm->blocks.prev, &ca);		if (error)			goto Error; 		list_for_each_entry_continue(bb, &bm->blocks, hook) {			bb->data = get_image_page(gfp_mask, safe_needed);			if (!bb->data) {				error = -ENOMEM;				goto Error;			} 			bb->start_pfn = pfn;			if (pages >= BM_BITS_PER_BLOCK) {				pfn += BM_BITS_PER_BLOCK;				pages -= BM_BITS_PER_BLOCK;			} else {				/* This is executed only once in the loop */				pfn += pages;			}			bb->end_pfn = pfn;		}	} 	bm->p_list = ca.chain;	memory_bm_position_reset(bm); Exit:	free_mem_extents(&mem_extents);	return error;  Error:	bm->p_list = ca.chain;	memory_bm_free(bm, PG_UNSAFE_CLEAR);	goto Exit;} /**  *	memory_bm_free - free memory occupied by the memory bitmap @bm  */static void memory_bm_free(struct memory_bitmap *bm, int clear_nosave_free){	struct bm_block *bb; 	list_for_each_entry(bb, &bm->blocks, hook)		if (bb->data)			free_image_page(bb->data, clear_nosave_free); 	free_list_of_pages(bm->p_list, clear_nosave_free); 	INIT_LIST_HEAD(&bm->blocks);} /** *	memory_bm_find_bit - find the bit in the bitmap @bm that corresponds *	to given pfn.  The cur_zone_bm member of @bm and the cur_block member *	of @bm->cur_zone_bm are updated. */static int memory_bm_find_bit(struct memory_bitmap *bm, unsigned long pfn,				void **addr, unsigned int *bit_nr){	struct bm_block *bb; 	/*	 * Check if the pfn corresponds to the current bitmap block and find	 * the block where it fits if this is not the case.	 */	bb = bm->cur.block;	if (pfn < bb->start_pfn)		list_for_each_entry_continue_reverse(bb, &bm->blocks, hook)			if (pfn >= bb->start_pfn)				break; 	if (pfn >= bb->end_pfn)		list_for_each_entry_continue(bb, &bm->blocks, hook)			if (pfn >= bb->start_pfn && pfn < bb->end_pfn)				break; 	if (&bb->hook == &bm->blocks)		return -EFAULT; 

Lines 1–500 of 2360.