2015-12-01 06:36:49 +03:00
/*
* Copyright IBM Corporation , 2015
* Author Aneesh Kumar K . V < aneesh . kumar @ linux . vnet . ibm . com >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of version 2 of the GNU Lesser General Public License
* as published by the Free Software Foundation .
*
* This program is distributed in the hope that it would be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
*
*/
# include <linux/mm.h>
# include <asm/machdep.h>
# include <asm/mmu.h>
int __hash_page_4K ( unsigned long ea , unsigned long access , unsigned long vsid ,
pte_t * ptep , unsigned long trap , unsigned long flags ,
int ssize , int subpg_prot )
{
2017-11-06 11:50:51 +03:00
real_pte_t rpte ;
2015-12-01 06:36:49 +03:00
unsigned long hpte_group ;
unsigned long rflags , pa ;
unsigned long old_pte , new_pte ;
unsigned long vpn , hash , slot ;
unsigned long shift = mmu_psize_defs [ MMU_PAGE_4K ] . shift ;
/*
* atomically mark the linux large page PTE busy and dirty
*/
do {
pte_t pte = READ_ONCE ( * ptep ) ;
old_pte = pte_val ( pte ) ;
/* If PTE busy, retry the access */
2016-04-29 16:25:45 +03:00
if ( unlikely ( old_pte & H_PAGE_BUSY ) )
2015-12-01 06:36:49 +03:00
return 0 ;
/* If PTE permissions don't match, take page fault */
2016-04-29 16:25:34 +03:00
if ( unlikely ( ! check_pte_access ( access , old_pte ) ) )
2015-12-01 06:36:49 +03:00
return 1 ;
/*
* Try to lock the PTE , add ACCESSED and DIRTY if it was
* a write access . Since this is 4 K insert of 64 K page size
2016-04-29 16:25:45 +03:00
* also add H_PAGE_COMBO
2015-12-01 06:36:49 +03:00
*/
2016-04-29 16:25:45 +03:00
new_pte = old_pte | H_PAGE_BUSY | _PAGE_ACCESSED ;
2016-04-29 16:25:30 +03:00
if ( access & _PAGE_WRITE )
2015-12-01 06:36:49 +03:00
new_pte | = _PAGE_DIRTY ;
2016-04-29 16:25:27 +03:00
} while ( ! pte_xchg ( ptep , __pte ( old_pte ) , __pte ( new_pte ) ) ) ;
2015-12-01 06:36:49 +03:00
/*
* PP bits . _PAGE_USER is already PP bit 0x2 , so we only
* need to add in 0x1 if it ' s a read - only user page
*/
2015-12-01 06:36:50 +03:00
rflags = htab_convert_pte_flags ( new_pte ) ;
2018-02-11 18:00:08 +03:00
rpte = __real_pte ( __pte ( old_pte ) , ptep , PTRS_PER_PTE ) ;
2015-12-01 06:36:49 +03:00
2016-11-29 05:13:46 +03:00
if ( cpu_has_feature ( CPU_FTR_NOEXECUTE ) & &
2015-12-01 06:36:49 +03:00
! cpu_has_feature ( CPU_FTR_COHERENT_ICACHE ) )
rflags = hash_page_do_lazy_icache ( rflags , __pte ( old_pte ) , trap ) ;
vpn = hpt_vpn ( ea , vsid , ssize ) ;
2016-04-29 16:25:45 +03:00
if ( unlikely ( old_pte & H_PAGE_HASHPTE ) ) {
2015-12-01 06:36:49 +03:00
/*
* There MIGHT be an HPTE for this pte
*/
2017-11-06 11:50:51 +03:00
unsigned long gslot = pte_get_hash_gslot ( vpn , shift , ssize ,
rpte , 0 ) ;
2015-12-01 06:36:49 +03:00
2017-11-06 11:50:51 +03:00
if ( mmu_hash_ops . hpte_updatepp ( gslot , rflags , vpn , MMU_PAGE_4K ,
2016-07-05 08:03:58 +03:00
MMU_PAGE_4K , ssize , flags ) = = - 1 )
2015-12-01 06:36:49 +03:00
old_pte & = ~ _PAGE_HPTEFLAGS ;
}
2016-04-29 16:25:45 +03:00
if ( likely ( ! ( old_pte & H_PAGE_HASHPTE ) ) ) {
2015-12-01 06:36:49 +03:00
pa = pte_pfn ( __pte ( old_pte ) ) < < PAGE_SHIFT ;
hash = hpt_hash ( vpn , shift , ssize ) ;
repeat :
hpte_group = ( ( hash & htab_hash_mask ) * HPTES_PER_GROUP ) & ~ 0x7UL ;
/* Insert into the hash table, primary slot */
2016-07-05 08:03:58 +03:00
slot = mmu_hash_ops . hpte_insert ( hpte_group , vpn , pa , rflags , 0 ,
MMU_PAGE_4K , MMU_PAGE_4K , ssize ) ;
2015-12-01 06:36:49 +03:00
/*
* Primary is full , try the secondary
*/
if ( unlikely ( slot = = - 1 ) ) {
hpte_group = ( ( ~ hash & htab_hash_mask ) * HPTES_PER_GROUP ) & ~ 0x7UL ;
2016-07-05 08:03:58 +03:00
slot = mmu_hash_ops . hpte_insert ( hpte_group , vpn , pa ,
rflags ,
HPTE_V_SECONDARY ,
MMU_PAGE_4K ,
MMU_PAGE_4K , ssize ) ;
2015-12-01 06:36:49 +03:00
if ( slot = = - 1 ) {
if ( mftb ( ) & 0x1 )
hpte_group = ( ( hash & htab_hash_mask ) *
HPTES_PER_GROUP ) & ~ 0x7UL ;
2016-07-05 08:03:58 +03:00
mmu_hash_ops . hpte_remove ( hpte_group ) ;
2015-12-01 06:36:49 +03:00
/*
* FIXME ! ! Should be try the group from which we removed ?
*/
goto repeat ;
}
}
/*
2016-03-01 10:29:17 +03:00
* Hypervisor failure . Restore old pte and return - 1
2015-12-01 06:36:49 +03:00
* similar to __hash_page_ *
*/
if ( unlikely ( slot = = - 2 ) ) {
* ptep = __pte ( old_pte ) ;
hash_failure_debug ( ea , access , vsid , trap , ssize ,
MMU_PAGE_4K , MMU_PAGE_4K , old_pte ) ;
return - 1 ;
}
2016-04-29 16:25:45 +03:00
new_pte = ( new_pte & ~ _PAGE_HPTEFLAGS ) | H_PAGE_HASHPTE ;
2018-02-11 18:00:08 +03:00
new_pte | = pte_set_hidx ( ptep , rpte , 0 , slot , PTRS_PER_PTE ) ;
2015-12-01 06:36:49 +03:00
}
2016-04-29 16:25:45 +03:00
* ptep = __pte ( new_pte & ~ H_PAGE_BUSY ) ;
2015-12-01 06:36:49 +03:00
return 0 ;
}