2008-01-30 15:33:43 +03:00
/*
* self test for change_page_attr .
*
2008-07-02 03:46:37 +04:00
* Clears the a test pte bit on random pages in the direct mapping ,
* then reverts and compares page tables forwards and afterwards .
2008-01-30 15:33:43 +03:00
*/
2008-01-30 15:33:43 +03:00
# include <linux/bootmem.h>
2008-02-07 00:39:45 +03:00
# include <linux/kthread.h>
2008-01-30 15:33:43 +03:00
# include <linux/random.h>
# include <linux/kernel.h>
# include <linux/init.h>
2008-01-30 15:33:43 +03:00
# include <linux/mm.h>
2008-01-30 15:33:43 +03:00
# include <asm/cacheflush.h>
# include <asm/pgtable.h>
# include <asm/kdebug.h>
2008-02-07 00:39:45 +03:00
/*
* Only print the results of the first pass :
*/
static __read_mostly int print = 1 ;
2008-01-30 15:33:43 +03:00
enum {
2008-02-07 00:39:45 +03:00
NTEST = 400 ,
2008-01-30 15:33:43 +03:00
# ifdef CONFIG_X86_64
2008-01-30 15:33:43 +03:00
LPS = ( 1 < < PMD_SHIFT ) ,
2008-01-30 15:33:43 +03:00
# elif defined(CONFIG_X86_PAE)
2008-01-30 15:33:43 +03:00
LPS = ( 1 < < PMD_SHIFT ) ,
2008-01-30 15:33:43 +03:00
# else
2008-01-30 15:33:43 +03:00
LPS = ( 1 < < 22 ) ,
2008-01-30 15:33:43 +03:00
# endif
2008-01-30 15:33:43 +03:00
GPS = ( 1 < < 30 )
2008-01-30 15:33:43 +03:00
} ;
2008-08-29 00:58:39 +04:00
# define PAGE_CPA_TEST __pgprot(_PAGE_CPA_TEST)
2008-07-02 03:46:37 +04:00
static int pte_testbit ( pte_t pte )
{
return pte_flags ( pte ) & _PAGE_UNUSED1 ;
}
2008-01-30 15:33:43 +03:00
struct split_state {
long lpg , gpg , spg , exec ;
long min_exec , max_exec ;
} ;
2008-02-07 00:39:45 +03:00
static int print_split ( struct split_state * s )
2008-01-30 15:33:43 +03:00
{
long i , expected , missed = 0 ;
int err = 0 ;
s - > lpg = s - > gpg = s - > spg = s - > exec = 0 ;
s - > min_exec = ~ 0UL ;
s - > max_exec = 0 ;
2008-01-30 15:34:05 +03:00
for ( i = 0 ; i < max_pfn_mapped ; ) {
2008-01-30 15:33:43 +03:00
unsigned long addr = ( unsigned long ) __va ( i < < PAGE_SHIFT ) ;
2008-02-01 19:49:43 +03:00
unsigned int level ;
2008-01-30 15:33:43 +03:00
pte_t * pte ;
2008-01-30 15:33:43 +03:00
pte = lookup_address ( addr , & level ) ;
2008-01-30 15:33:43 +03:00
if ( ! pte ) {
missed + + ;
i + + ;
continue ;
}
2008-01-30 15:34:09 +03:00
if ( level = = PG_LEVEL_1G & & sizeof ( long ) = = 8 ) {
2008-01-30 15:33:43 +03:00
s - > gpg + + ;
i + = GPS / PAGE_SIZE ;
2008-01-30 15:34:09 +03:00
} else if ( level = = PG_LEVEL_2M ) {
2008-01-30 15:33:43 +03:00
if ( ! ( pte_val ( * pte ) & _PAGE_PSE ) ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR
" %lx level %d but not PSE %Lx \n " ,
addr , level , ( u64 ) pte_val ( * pte ) ) ;
2008-01-30 15:33:43 +03:00
err = 1 ;
}
s - > lpg + + ;
i + = LPS / PAGE_SIZE ;
} else {
s - > spg + + ;
i + + ;
}
if ( ! ( pte_val ( * pte ) & _PAGE_NX ) ) {
s - > exec + + ;
2008-01-30 15:33:43 +03:00
if ( addr < s - > min_exec )
s - > min_exec = addr ;
if ( addr > s - > max_exec )
s - > max_exec = addr ;
2008-01-30 15:33:43 +03:00
}
}
2008-02-07 00:39:45 +03:00
if ( print ) {
printk ( KERN_INFO
" 4k %lu large %lu gb %lu x %lu[%lx-%lx] miss %lu \n " ,
s - > spg , s - > lpg , s - > gpg , s - > exec ,
s - > min_exec ! = ~ 0UL ? s - > min_exec : 0 ,
s - > max_exec , missed ) ;
}
2008-01-30 15:33:43 +03:00
2008-01-30 15:33:43 +03:00
expected = ( s - > gpg * GPS + s - > lpg * LPS ) / PAGE_SIZE + s - > spg + missed ;
if ( expected ! = i ) {
2008-01-30 15:34:05 +03:00
printk ( KERN_ERR " CPA max_pfn_mapped %lu but expected %lu \n " ,
max_pfn_mapped , expected ) ;
2008-01-30 15:33:43 +03:00
return 1 ;
}
return err ;
}
2008-02-07 00:39:45 +03:00
static unsigned long addr [ NTEST ] ;
static unsigned int len [ NTEST ] ;
2008-01-30 15:33:43 +03:00
/* Change the global bit on random pages in the direct mapping */
2008-02-07 00:39:45 +03:00
static int pageattr_test ( void )
2008-01-30 15:33:43 +03:00
{
2008-01-30 15:33:43 +03:00
struct split_state sa , sb , sc ;
unsigned long * bm ;
2008-01-30 15:33:43 +03:00
pte_t * pte , pte0 ;
2008-01-30 15:33:43 +03:00
int failed = 0 ;
2008-02-01 19:49:43 +03:00
unsigned int level ;
2008-01-30 15:33:43 +03:00
int i , k ;
2008-01-30 15:33:43 +03:00
int err ;
2008-08-21 06:46:21 +04:00
unsigned long test_addr ;
2008-01-30 15:33:43 +03:00
2008-02-07 00:39:45 +03:00
if ( print )
printk ( KERN_INFO " CPA self-test: \n " ) ;
2008-01-30 15:33:43 +03:00
2008-01-30 15:34:05 +03:00
bm = vmalloc ( ( max_pfn_mapped + 7 ) / 8 ) ;
2008-01-30 15:33:43 +03:00
if ( ! bm ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR " CPA Cannot vmalloc bitmap \n " ) ;
2008-01-30 15:33:43 +03:00
return - ENOMEM ;
}
2008-01-30 15:34:05 +03:00
memset ( bm , 0 , ( max_pfn_mapped + 7 ) / 8 ) ;
2008-01-30 15:33:43 +03:00
failed + = print_split ( & sa ) ;
srandom32 ( 100 ) ;
2008-01-30 15:33:43 +03:00
2008-01-30 15:33:43 +03:00
for ( i = 0 ; i < NTEST ; i + + ) {
2008-01-30 15:34:05 +03:00
unsigned long pfn = random32 ( ) % max_pfn_mapped ;
2008-01-30 15:33:43 +03:00
2008-01-30 15:33:43 +03:00
addr [ i ] = ( unsigned long ) __va ( pfn < < PAGE_SHIFT ) ;
len [ i ] = random32 ( ) % 100 ;
2008-01-30 15:34:05 +03:00
len [ i ] = min_t ( unsigned long , len [ i ] , max_pfn_mapped - pfn - 1 ) ;
2008-01-30 15:33:43 +03:00
2008-01-30 15:33:43 +03:00
if ( len [ i ] = = 0 )
len [ i ] = 1 ;
pte = NULL ;
pte0 = pfn_pte ( 0 , __pgprot ( 0 ) ) ; /* shut gcc up */
2008-01-30 15:33:43 +03:00
2008-01-30 15:33:43 +03:00
for ( k = 0 ; k < len [ i ] ; k + + ) {
pte = lookup_address ( addr [ i ] + k * PAGE_SIZE , & level ) ;
2008-02-04 18:48:08 +03:00
if ( ! pte | | pgprot_val ( pte_pgprot ( * pte ) ) = = 0 | |
! ( pte_val ( * pte ) & _PAGE_PRESENT ) ) {
2008-01-30 15:33:43 +03:00
addr [ i ] = 0 ;
break ;
}
2008-01-30 15:33:43 +03:00
if ( k = = 0 ) {
2008-01-30 15:33:43 +03:00
pte0 = * pte ;
2008-01-30 15:33:43 +03:00
} else {
if ( pgprot_val ( pte_pgprot ( * pte ) ) ! =
2008-01-30 15:33:43 +03:00
pgprot_val ( pte_pgprot ( pte0 ) ) ) {
2008-01-30 15:33:43 +03:00
len [ i ] = k ;
break ;
}
2008-01-30 15:33:43 +03:00
}
if ( test_bit ( pfn + k , bm ) ) {
len [ i ] = k ;
break ;
}
__set_bit ( pfn + k , bm ) ;
}
if ( ! addr [ i ] | | ! pte | | ! k ) {
addr [ i ] = 0 ;
continue ;
}
2008-08-21 06:46:21 +04:00
test_addr = addr [ i ] ;
2008-08-29 00:58:39 +04:00
err = change_page_attr_set ( & test_addr , len [ i ] , PAGE_CPA_TEST , 0 ) ;
2008-01-30 15:33:43 +03:00
if ( err < 0 ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR " CPA %d failed %d \n " , i , err ) ;
2008-01-30 15:33:43 +03:00
failed + + ;
}
pte = lookup_address ( addr [ i ] , & level ) ;
2008-07-02 03:46:37 +04:00
if ( ! pte | | ! pte_testbit ( * pte ) | | pte_huge ( * pte ) ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR " CPA %lx: bad pte %Lx \n " , addr [ i ] ,
2008-01-30 15:33:43 +03:00
pte ? ( u64 ) pte_val ( * pte ) : 0ULL ) ;
failed + + ;
}
2008-01-30 15:34:09 +03:00
if ( level ! = PG_LEVEL_4K ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR " CPA %lx: unexpected level %d \n " ,
addr [ i ] , level ) ;
2008-01-30 15:33:43 +03:00
failed + + ;
}
}
vfree ( bm ) ;
failed + = print_split ( & sb ) ;
for ( i = 0 ; i < NTEST ; i + + ) {
if ( ! addr [ i ] )
continue ;
pte = lookup_address ( addr [ i ] , & level ) ;
if ( ! pte ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR " CPA lookup of %lx failed \n " , addr [ i ] ) ;
2008-01-30 15:33:43 +03:00
failed + + ;
continue ;
}
2008-08-21 06:46:21 +04:00
test_addr = addr [ i ] ;
2008-08-29 00:58:39 +04:00
err = change_page_attr_clear ( & test_addr , len [ i ] , PAGE_CPA_TEST , 0 ) ;
2008-01-30 15:33:43 +03:00
if ( err < 0 ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR " CPA reverting failed: %d \n " , err ) ;
2008-01-30 15:33:43 +03:00
failed + + ;
}
pte = lookup_address ( addr [ i ] , & level ) ;
2008-07-02 03:46:37 +04:00
if ( ! pte | | pte_testbit ( * pte ) ) {
2008-01-30 15:33:43 +03:00
printk ( KERN_ERR " CPA %lx: bad pte after revert %Lx \n " ,
addr [ i ] , pte ? ( u64 ) pte_val ( * pte ) : 0ULL ) ;
2008-01-30 15:33:43 +03:00
failed + + ;
}
}
failed + = print_split ( & sc ) ;
2008-01-30 15:33:58 +03:00
if ( failed ) {
2008-07-30 23:26:26 +04:00
WARN ( 1 , KERN_ERR " NOT PASSED. Please report. \n " ) ;
2008-02-07 00:39:45 +03:00
return - EINVAL ;
2008-01-30 15:33:58 +03:00
} else {
2008-02-07 00:39:45 +03:00
if ( print )
printk ( KERN_INFO " ok. \n " ) ;
2008-01-30 15:33:58 +03:00
}
2008-01-30 15:33:43 +03:00
return 0 ;
}
2008-02-07 00:39:45 +03:00
static int do_pageattr_test ( void * __unused )
{
while ( ! kthread_should_stop ( ) ) {
schedule_timeout_interruptible ( HZ * 30 ) ;
if ( pageattr_test ( ) < 0 )
break ;
if ( print )
print - - ;
}
return 0 ;
}
static int start_pageattr_test ( void )
{
struct task_struct * p ;
p = kthread_create ( do_pageattr_test , NULL , " pageattr-test " ) ;
if ( ! IS_ERR ( p ) )
wake_up_process ( p ) ;
else
WARN_ON ( 1 ) ;
return 0 ;
}
module_init ( start_pageattr_test ) ;