2008-04-30 13:38:46 +02:00
/*
* IBM System z Huge TLB Page Support for Kernel .
*
2016-07-04 14:47:01 +02:00
* Copyright IBM Corp . 2007 , 2016
2008-04-30 13:38:46 +02:00
* Author ( s ) : Gerald Schaefer < gerald . schaefer @ de . ibm . com >
*/
2016-07-04 14:47:01 +02:00
# define KMSG_COMPONENT "hugetlb"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
2008-04-30 13:38:46 +02:00
# include <linux/mm.h>
# include <linux/hugetlb.h>
2016-07-18 14:35:13 +02:00
/*
* If the bit selected by single - bit bitmask " a " is set within " x " , move
* it to the position indicated by single - bit bitmask " b " .
*/
# define move_set_bit(x, a, b) (((x) & (a)) >> ilog2(a) << ilog2(b))
2016-07-04 14:47:01 +02:00
static inline unsigned long __pte_to_rste ( pte_t pte )
2013-07-23 20:57:57 +02:00
{
2016-07-04 14:47:01 +02:00
unsigned long rste ;
2013-07-23 20:57:57 +02:00
/*
2016-07-04 14:47:01 +02:00
* Convert encoding pte bits pmd / pud bits
2015-04-22 13:55:59 +02:00
* lIR . uswrdy . p dy . . R . . . I . . . wr
* empty 010.000000 .0 - > 00. .0 . . .1 . . .00
* prot - none , clean , old 111.000000 .1 - > 00. .1 . . .1 . . .00
* prot - none , clean , young 111.000001 .1 - > 01. .1 . . .1 . . .00
* prot - none , dirty , old 111.000010 .1 - > 10. .1 . . .1 . . .00
* prot - none , dirty , young 111.000011 .1 - > 11. .1 . . .1 . . .00
* read - only , clean , old 111.000100 .1 - > 00. .1 . . .1 . . .01
* read - only , clean , young 101.000101 .1 - > 01. .1 . . .0 . . .01
* read - only , dirty , old 111.000110 .1 - > 10. .1 . . .1 . . .01
* read - only , dirty , young 101.000111 .1 - > 11. .1 . . .0 . . .01
* read - write , clean , old 111.001100 .1 - > 00. .1 . . .1 . . .11
* read - write , clean , young 101.001101 .1 - > 01. .1 . . .0 . . .11
* read - write , dirty , old 110.001110 .1 - > 10. .0 . . .1 . . .11
* read - write , dirty , young 100.001111 .1 - > 11. .0 . . .0 . . .11
* HW - bits : R read - only , I invalid
* SW - bits : p present , y young , d dirty , r read , w write , s special ,
* u unused , l large
2013-07-23 20:57:57 +02:00
*/
if ( pte_present ( pte ) ) {
2016-07-04 14:47:01 +02:00
rste = pte_val ( pte ) & PAGE_MASK ;
2016-07-18 14:35:13 +02:00
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_READ ,
_SEGMENT_ENTRY_READ ) ;
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_WRITE ,
_SEGMENT_ENTRY_WRITE ) ;
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_INVALID ,
_SEGMENT_ENTRY_INVALID ) ;
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_PROTECT ,
_SEGMENT_ENTRY_PROTECT ) ;
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_DIRTY ,
_SEGMENT_ENTRY_DIRTY ) ;
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_YOUNG ,
_SEGMENT_ENTRY_YOUNG ) ;
# ifdef CONFIG_MEM_SOFT_DIRTY
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_SOFT_DIRTY ,
_SEGMENT_ENTRY_SOFT_DIRTY ) ;
# endif
2016-03-22 10:54:24 +01:00
rste | = move_set_bit ( pte_val ( pte ) , _PAGE_NOEXEC ,
_SEGMENT_ENTRY_NOEXEC ) ;
2013-07-23 20:57:57 +02:00
} else
2016-04-27 11:43:07 +02:00
rste = _SEGMENT_ENTRY_EMPTY ;
2016-07-04 14:47:01 +02:00
return rste ;
2013-07-23 20:57:57 +02:00
}
2016-07-04 14:47:01 +02:00
static inline pte_t __rste_to_pte ( unsigned long rste )
2013-07-23 20:57:57 +02:00
{
2016-07-04 14:47:01 +02:00
int present ;
2013-07-23 20:57:57 +02:00
pte_t pte ;
2016-07-04 14:47:01 +02:00
if ( ( rste & _REGION_ENTRY_TYPE_MASK ) = = _REGION_ENTRY_TYPE_R3 )
present = pud_present ( __pud ( rste ) ) ;
else
present = pmd_present ( __pmd ( rste ) ) ;
2013-07-23 20:57:57 +02:00
/*
2016-07-04 14:47:01 +02:00
* Convert encoding pmd / pud bits pte bits
2015-04-22 13:55:59 +02:00
* dy . . R . . . I . . . wr lIR . uswrdy . p
* empty 00. .0 . . .1 . . .00 - > 010.000000 .0
* prot - none , clean , old 00. .1 . . .1 . . .00 - > 111.000000 .1
* prot - none , clean , young 01. .1 . . .1 . . .00 - > 111.000001 .1
* prot - none , dirty , old 10. .1 . . .1 . . .00 - > 111.000010 .1
* prot - none , dirty , young 11. .1 . . .1 . . .00 - > 111.000011 .1
* read - only , clean , old 00. .1 . . .1 . . .01 - > 111.000100 .1
* read - only , clean , young 01. .1 . . .0 . . .01 - > 101.000101 .1
* read - only , dirty , old 10. .1 . . .1 . . .01 - > 111.000110 .1
* read - only , dirty , young 11. .1 . . .0 . . .01 - > 101.000111 .1
* read - write , clean , old 00. .1 . . .1 . . .11 - > 111.001100 .1
* read - write , clean , young 01. .1 . . .0 . . .11 - > 101.001101 .1
* read - write , dirty , old 10. .0 . . .1 . . .11 - > 110.001110 .1
* read - write , dirty , young 11. .0 . . .0 . . .11 - > 100.001111 .1
* HW - bits : R read - only , I invalid
* SW - bits : p present , y young , d dirty , r read , w write , s special ,
* u unused , l large
2013-07-23 20:57:57 +02:00
*/
2016-07-04 14:47:01 +02:00
if ( present ) {
pte_val ( pte ) = rste & _SEGMENT_ENTRY_ORIGIN_LARGE ;
2014-07-24 11:03:41 +02:00
pte_val ( pte ) | = _PAGE_LARGE | _PAGE_PRESENT ;
2016-07-18 14:35:13 +02:00
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_READ ,
_PAGE_READ ) ;
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_WRITE ,
_PAGE_WRITE ) ;
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_INVALID ,
_PAGE_INVALID ) ;
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_PROTECT ,
_PAGE_PROTECT ) ;
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_DIRTY ,
_PAGE_DIRTY ) ;
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_YOUNG ,
_PAGE_YOUNG ) ;
# ifdef CONFIG_MEM_SOFT_DIRTY
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_SOFT_DIRTY ,
_PAGE_DIRTY ) ;
# endif
2016-03-22 10:54:24 +01:00
pte_val ( pte ) | = move_set_bit ( rste , _SEGMENT_ENTRY_NOEXEC ,
_PAGE_NOEXEC ) ;
2013-07-23 20:57:57 +02:00
} else
pte_val ( pte ) = _PAGE_INVALID ;
return pte ;
}
2008-04-30 13:38:46 +02:00
void set_huge_pte_at ( struct mm_struct * mm , unsigned long addr ,
2013-07-23 20:57:57 +02:00
pte_t * ptep , pte_t pte )
2008-04-30 13:38:46 +02:00
{
2016-03-22 10:54:24 +01:00
unsigned long rste ;
rste = __pte_to_rste ( pte ) ;
if ( ! MACHINE_HAS_NX )
rste & = ~ _SEGMENT_ENTRY_NOEXEC ;
2016-07-04 14:47:01 +02:00
/* Set correct table type for 2G hugepages */
if ( ( pte_val ( * ptep ) & _REGION_ENTRY_TYPE_MASK ) = = _REGION_ENTRY_TYPE_R3 )
rste | = _REGION_ENTRY_TYPE_R3 | _REGION3_ENTRY_LARGE ;
else
rste | = _SEGMENT_ENTRY_LARGE ;
pte_val ( * ptep ) = rste ;
2013-07-23 20:57:57 +02:00
}
pte_t huge_ptep_get ( pte_t * ptep )
{
2016-07-04 14:47:01 +02:00
return __rste_to_pte ( pte_val ( * ptep ) ) ;
2013-07-23 20:57:57 +02:00
}
pte_t huge_ptep_get_and_clear ( struct mm_struct * mm ,
unsigned long addr , pte_t * ptep )
{
2016-07-04 14:47:01 +02:00
pte_t pte = huge_ptep_get ( ptep ) ;
2013-07-23 20:57:57 +02:00
pmd_t * pmdp = ( pmd_t * ) ptep ;
2016-07-04 14:47:01 +02:00
pud_t * pudp = ( pud_t * ) ptep ;
2008-04-30 13:38:46 +02:00
2016-07-04 14:47:01 +02:00
if ( ( pte_val ( * ptep ) & _REGION_ENTRY_TYPE_MASK ) = = _REGION_ENTRY_TYPE_R3 )
pudp_xchg_direct ( mm , addr , pudp , __pud ( _REGION3_ENTRY_EMPTY ) ) ;
else
pmdp_xchg_direct ( mm , addr , pmdp , __pmd ( _SEGMENT_ENTRY_EMPTY ) ) ;
return pte ;
2008-04-30 13:38:46 +02:00
}
2008-07-23 21:27:41 -07:00
pte_t * huge_pte_alloc ( struct mm_struct * mm ,
unsigned long addr , unsigned long sz )
2008-04-30 13:38:46 +02:00
{
pgd_t * pgdp ;
pud_t * pudp ;
pmd_t * pmdp = NULL ;
pgdp = pgd_offset ( mm , addr ) ;
pudp = pud_alloc ( mm , pgdp , addr ) ;
2016-07-04 14:47:01 +02:00
if ( pudp ) {
if ( sz = = PUD_SIZE )
return ( pte_t * ) pudp ;
else if ( sz = = PMD_SIZE )
pmdp = pmd_alloc ( mm , pudp , addr ) ;
}
2008-04-30 13:38:46 +02:00
return ( pte_t * ) pmdp ;
}
pte_t * huge_pte_offset ( struct mm_struct * mm , unsigned long addr )
{
pgd_t * pgdp ;
pud_t * pudp ;
pmd_t * pmdp = NULL ;
pgdp = pgd_offset ( mm , addr ) ;
if ( pgd_present ( * pgdp ) ) {
pudp = pud_offset ( pgdp , addr ) ;
2016-07-04 14:47:01 +02:00
if ( pud_present ( * pudp ) ) {
if ( pud_large ( * pudp ) )
return ( pte_t * ) pudp ;
2008-04-30 13:38:46 +02:00
pmdp = pmd_offset ( pudp , addr ) ;
2016-07-04 14:47:01 +02:00
}
2008-04-30 13:38:46 +02:00
}
return ( pte_t * ) pmdp ;
}
int pmd_huge ( pmd_t pmd )
{
2015-06-25 14:59:49 -07:00
return pmd_large ( pmd ) ;
2008-04-30 13:38:46 +02:00
}
2008-07-23 21:27:50 -07:00
int pud_huge ( pud_t pud )
{
2016-07-04 14:47:01 +02:00
return pud_large ( pud ) ;
}
struct page *
follow_huge_pud ( struct mm_struct * mm , unsigned long address ,
pud_t * pud , int flags )
{
if ( flags & FOLL_GET )
return NULL ;
return pud_page ( * pud ) + ( ( address & ~ PUD_MASK ) > > PAGE_SHIFT ) ;
}
static __init int setup_hugepagesz ( char * opt )
{
unsigned long size ;
char * string = opt ;
size = memparse ( opt , & opt ) ;
if ( MACHINE_HAS_EDAT1 & & size = = PMD_SIZE ) {
hugetlb_add_hstate ( PMD_SHIFT - PAGE_SHIFT ) ;
} else if ( MACHINE_HAS_EDAT2 & & size = = PUD_SIZE ) {
hugetlb_add_hstate ( PUD_SHIFT - PAGE_SHIFT ) ;
} else {
2016-10-13 21:50:07 +05:30
hugetlb_bad_size ( ) ;
2016-07-04 14:47:01 +02:00
pr_err ( " hugepagesz= specifies an unsupported page size %s \n " ,
string ) ;
return 0 ;
}
return 1 ;
2008-07-23 21:27:50 -07:00
}
2016-07-04 14:47:01 +02:00
__setup ( " hugepagesz= " , setup_hugepagesz ) ;