2009-06-16 10:30:52 +02:00
/*
2009-09-11 10:28:37 +02:00
* Suspend support specific for s390 .
2009-06-16 10:30:52 +02:00
*
* Copyright IBM Corp . 2009
*
* Author ( s ) : Hans - Joachim Picht < hans @ linux . vnet . ibm . com >
*/
2009-09-11 10:28:37 +02:00
# include <linux/pfn.h>
2011-10-30 15:17:13 +01:00
# include <linux/suspend.h>
2011-08-17 20:42:24 +02:00
# include <linux/mm.h>
2012-03-28 18:30:02 +01:00
# include <asm/ctl_reg.h>
2009-09-11 10:28:37 +02:00
/*
* References to section boundaries
*/
extern const void __nosave_begin , __nosave_end ;
2011-08-17 20:42:24 +02:00
/*
* The restore of the saved pages in an hibernation image will set
* the change and referenced bits in the storage key for each page .
* Overindication of the referenced bits after an hibernation cycle
* does not cause any harm but the overindication of the change bits
* would cause trouble .
* Use the ARCH_SAVE_PAGE_KEYS hooks to save the storage key of each
* page to the most significant byte of the associated page frame
* number in the hibernation image .
*/
/*
* Key storage is allocated as a linked list of pages .
* The size of the keys array is ( PAGE_SIZE - sizeof ( long ) )
*/
struct page_key_data {
struct page_key_data * next ;
unsigned char data [ ] ;
} ;
# define PAGE_KEY_DATA_SIZE (PAGE_SIZE - sizeof(struct page_key_data *))
static struct page_key_data * page_key_data ;
static struct page_key_data * page_key_rp , * page_key_wp ;
static unsigned long page_key_rx , page_key_wx ;
/*
* For each page in the hibernation image one additional byte is
* stored in the most significant byte of the page frame number .
* On suspend no additional memory is required but on resume the
* keys need to be memorized until the page data has been restored .
* Only then can the storage keys be set to their old state .
*/
unsigned long page_key_additional_pages ( unsigned long pages )
{
return DIV_ROUND_UP ( pages , PAGE_KEY_DATA_SIZE ) ;
}
/*
* Free page_key_data list of arrays .
*/
void page_key_free ( void )
{
struct page_key_data * pkd ;
while ( page_key_data ) {
pkd = page_key_data ;
page_key_data = pkd - > next ;
free_page ( ( unsigned long ) pkd ) ;
}
}
/*
* Allocate page_key_data list of arrays with enough room to store
* one byte for each page in the hibernation image .
*/
int page_key_alloc ( unsigned long pages )
{
struct page_key_data * pk ;
unsigned long size ;
size = DIV_ROUND_UP ( pages , PAGE_KEY_DATA_SIZE ) ;
while ( size - - ) {
pk = ( struct page_key_data * ) get_zeroed_page ( GFP_KERNEL ) ;
if ( ! pk ) {
page_key_free ( ) ;
return - ENOMEM ;
}
pk - > next = page_key_data ;
page_key_data = pk ;
}
page_key_rp = page_key_wp = page_key_data ;
page_key_rx = page_key_wx = 0 ;
return 0 ;
}
/*
* Save the storage key into the upper 8 bits of the page frame number .
*/
void page_key_read ( unsigned long * pfn )
{
unsigned long addr ;
addr = ( unsigned long ) page_address ( pfn_to_page ( * pfn ) ) ;
* ( unsigned char * ) pfn = ( unsigned char ) page_get_storage_key ( addr ) ;
}
/*
* Extract the storage key from the upper 8 bits of the page frame number
* and store it in the page_key_data list of arrays .
*/
void page_key_memorize ( unsigned long * pfn )
{
page_key_wp - > data [ page_key_wx ] = * ( unsigned char * ) pfn ;
* ( unsigned char * ) pfn = 0 ;
if ( + + page_key_wx < PAGE_KEY_DATA_SIZE )
return ;
page_key_wp = page_key_wp - > next ;
page_key_wx = 0 ;
}
/*
* Get the next key from the page_key_data list of arrays and set the
* storage key of the page referred by @ address . If @ address refers to
* a " safe " page the swsusp_arch_resume code will transfer the storage
* key from the buffer page to the original page .
*/
void page_key_write ( void * address )
{
page_set_storage_key ( ( unsigned long ) address ,
page_key_rp - > data [ page_key_rx ] , 0 ) ;
if ( + + page_key_rx > = PAGE_KEY_DATA_SIZE )
return ;
page_key_rp = page_key_rp - > next ;
page_key_rx = 0 ;
}
2009-09-11 10:28:37 +02:00
int pfn_is_nosave ( unsigned long pfn )
{
2009-09-22 22:58:50 +02:00
unsigned long nosave_begin_pfn = PFN_DOWN ( __pa ( & __nosave_begin ) ) ;
unsigned long nosave_end_pfn = PFN_DOWN ( __pa ( & __nosave_end ) ) ;
2009-09-11 10:28:37 +02:00
2009-09-22 22:58:50 +02:00
/* Always save lowcore pages (LC protection might be enabled). */
if ( pfn < = LC_PAGES )
return 0 ;
2009-09-11 10:28:37 +02:00
if ( pfn > = nosave_begin_pfn & & pfn < nosave_end_pfn )
return 1 ;
2009-09-22 22:58:50 +02:00
/* Skip memory holes and read-only pages (NSS, DCSS, ...). */
if ( tprot ( PFN_PHYS ( pfn ) ) )
2009-09-11 10:28:37 +02:00
return 1 ;
return 0 ;
}
2009-06-16 10:30:52 +02:00
void save_processor_state ( void )
{
2009-07-24 12:39:50 +02:00
/* swsusp_arch_suspend() actually saves all cpu register contents.
* Machine checks must be disabled since swsusp_arch_suspend ( ) stores
* register contents to their lowcore save areas . That ' s the same
* place where register contents on machine checks would be saved .
* To avoid register corruption disable machine checks .
* We must also disable machine checks in the new psw mask for
* program checks , since swsusp_arch_suspend ( ) may generate program
* checks . Disabling machine checks for all other new psw masks is
* just paranoia .
2009-06-16 10:30:52 +02:00
*/
2009-07-24 12:39:50 +02:00
local_mcck_disable ( ) ;
/* Disable lowcore protection */
__ctl_clear_bit ( 0 , 28 ) ;
S390_lowcore . external_new_psw . mask & = ~ PSW_MASK_MCHECK ;
S390_lowcore . svc_new_psw . mask & = ~ PSW_MASK_MCHECK ;
S390_lowcore . io_new_psw . mask & = ~ PSW_MASK_MCHECK ;
S390_lowcore . program_new_psw . mask & = ~ PSW_MASK_MCHECK ;
2009-06-16 10:30:52 +02:00
}
void restore_processor_state ( void )
{
2009-07-24 12:39:50 +02:00
S390_lowcore . external_new_psw . mask | = PSW_MASK_MCHECK ;
S390_lowcore . svc_new_psw . mask | = PSW_MASK_MCHECK ;
S390_lowcore . io_new_psw . mask | = PSW_MASK_MCHECK ;
S390_lowcore . program_new_psw . mask | = PSW_MASK_MCHECK ;
/* Enable lowcore protection */
__ctl_set_bit ( 0 , 28 ) ;
local_mcck_enable ( ) ;
2009-06-16 10:30:52 +02:00
}