162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Copyright IBM Corporation, 2015 362306a36Sopenharmony_ci * Author Aneesh Kumar K.V <aneesh.kumar@linux.ibm.com> 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify it 662306a36Sopenharmony_ci * under the terms of version 2 of the GNU Lesser General Public License 762306a36Sopenharmony_ci * as published by the Free Software Foundation. 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * This program is distributed in the hope that it would be useful, but 1062306a36Sopenharmony_ci * WITHOUT ANY WARRANTY; without even the implied warranty of 1162306a36Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/mm.h> 1662306a36Sopenharmony_ci#include <asm/machdep.h> 1762306a36Sopenharmony_ci#include <asm/mmu.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "internal.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * Return true, if the entry has a slot value which 2362306a36Sopenharmony_ci * the software considers as invalid. 2462306a36Sopenharmony_ci */ 2562306a36Sopenharmony_cistatic inline bool hpte_soft_invalid(unsigned long hidx) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci return ((hidx & 0xfUL) == 0xfUL); 2862306a36Sopenharmony_ci} 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* 3162306a36Sopenharmony_ci * index from 0 - 15 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_cibool __rpte_sub_valid(real_pte_t rpte, unsigned long index) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci return !(hpte_soft_invalid(__rpte_to_hidx(rpte, index))); 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ciint __hash_page_4K(unsigned long ea, unsigned long access, unsigned long vsid, 3962306a36Sopenharmony_ci pte_t *ptep, unsigned long trap, unsigned long flags, 4062306a36Sopenharmony_ci int ssize, int subpg_prot) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci real_pte_t rpte; 4362306a36Sopenharmony_ci unsigned long hpte_group; 4462306a36Sopenharmony_ci unsigned int subpg_index; 4562306a36Sopenharmony_ci unsigned long rflags, pa; 4662306a36Sopenharmony_ci unsigned long old_pte, new_pte, subpg_pte; 4762306a36Sopenharmony_ci unsigned long vpn, hash, slot, gslot; 4862306a36Sopenharmony_ci unsigned long shift = mmu_psize_defs[MMU_PAGE_4K].shift; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci /* 5162306a36Sopenharmony_ci * atomically mark the linux large page PTE busy and dirty 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci do { 5462306a36Sopenharmony_ci pte_t pte = READ_ONCE(*ptep); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci old_pte = pte_val(pte); 5762306a36Sopenharmony_ci /* If PTE busy, retry the access */ 5862306a36Sopenharmony_ci if (unlikely(old_pte & H_PAGE_BUSY)) 5962306a36Sopenharmony_ci return 0; 6062306a36Sopenharmony_ci /* If PTE permissions don't match, take page fault */ 6162306a36Sopenharmony_ci if (unlikely(!check_pte_access(access, old_pte))) 6262306a36Sopenharmony_ci return 1; 6362306a36Sopenharmony_ci /* 6462306a36Sopenharmony_ci * Try to lock the PTE, add ACCESSED and DIRTY if it was 6562306a36Sopenharmony_ci * a write access. Since this is 4K insert of 64K page size 6662306a36Sopenharmony_ci * also add H_PAGE_COMBO 6762306a36Sopenharmony_ci */ 6862306a36Sopenharmony_ci new_pte = old_pte | H_PAGE_BUSY | _PAGE_ACCESSED | H_PAGE_COMBO; 6962306a36Sopenharmony_ci if (access & _PAGE_WRITE) 7062306a36Sopenharmony_ci new_pte |= _PAGE_DIRTY; 7162306a36Sopenharmony_ci } while (!pte_xchg(ptep, __pte(old_pte), __pte(new_pte))); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci /* 7462306a36Sopenharmony_ci * Handle the subpage protection bits 7562306a36Sopenharmony_ci */ 7662306a36Sopenharmony_ci subpg_pte = new_pte & ~subpg_prot; 7762306a36Sopenharmony_ci rflags = htab_convert_pte_flags(subpg_pte, flags); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (cpu_has_feature(CPU_FTR_NOEXECUTE) && 8062306a36Sopenharmony_ci !cpu_has_feature(CPU_FTR_COHERENT_ICACHE)) { 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci /* 8362306a36Sopenharmony_ci * No CPU has hugepages but lacks no execute, so we 8462306a36Sopenharmony_ci * don't need to worry about that case 8562306a36Sopenharmony_ci */ 8662306a36Sopenharmony_ci rflags = hash_page_do_lazy_icache(rflags, __pte(old_pte), trap); 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci subpg_index = (ea & (PAGE_SIZE - 1)) >> shift; 9062306a36Sopenharmony_ci vpn = hpt_vpn(ea, vsid, ssize); 9162306a36Sopenharmony_ci rpte = __real_pte(__pte(old_pte), ptep, PTRS_PER_PTE); 9262306a36Sopenharmony_ci /* 9362306a36Sopenharmony_ci *None of the sub 4k page is hashed 9462306a36Sopenharmony_ci */ 9562306a36Sopenharmony_ci if (!(old_pte & H_PAGE_HASHPTE)) 9662306a36Sopenharmony_ci goto htab_insert_hpte; 9762306a36Sopenharmony_ci /* 9862306a36Sopenharmony_ci * Check if the pte was already inserted into the hash table 9962306a36Sopenharmony_ci * as a 64k HW page, and invalidate the 64k HPTE if so. 10062306a36Sopenharmony_ci */ 10162306a36Sopenharmony_ci if (!(old_pte & H_PAGE_COMBO)) { 10262306a36Sopenharmony_ci flush_hash_page(vpn, rpte, MMU_PAGE_64K, ssize, flags); 10362306a36Sopenharmony_ci /* 10462306a36Sopenharmony_ci * clear the old slot details from the old and new pte. 10562306a36Sopenharmony_ci * On hash insert failure we use old pte value and we don't 10662306a36Sopenharmony_ci * want slot information there if we have a insert failure. 10762306a36Sopenharmony_ci */ 10862306a36Sopenharmony_ci old_pte &= ~H_PAGE_HASHPTE; 10962306a36Sopenharmony_ci new_pte &= ~H_PAGE_HASHPTE; 11062306a36Sopenharmony_ci goto htab_insert_hpte; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * Check for sub page valid and update 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_ci if (__rpte_sub_valid(rpte, subpg_index)) { 11662306a36Sopenharmony_ci int ret; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci gslot = pte_get_hash_gslot(vpn, shift, ssize, rpte, 11962306a36Sopenharmony_ci subpg_index); 12062306a36Sopenharmony_ci ret = mmu_hash_ops.hpte_updatepp(gslot, rflags, vpn, 12162306a36Sopenharmony_ci MMU_PAGE_4K, MMU_PAGE_4K, 12262306a36Sopenharmony_ci ssize, flags); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* 12562306a36Sopenharmony_ci * If we failed because typically the HPTE wasn't really here 12662306a36Sopenharmony_ci * we try an insertion. 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_ci if (ret == -1) 12962306a36Sopenharmony_ci goto htab_insert_hpte; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci *ptep = __pte(new_pte & ~H_PAGE_BUSY); 13262306a36Sopenharmony_ci return 0; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cihtab_insert_hpte: 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* 13862306a36Sopenharmony_ci * Initialize all hidx entries to invalid value, the first time 13962306a36Sopenharmony_ci * the PTE is about to allocate a 4K HPTE. 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_ci if (!(old_pte & H_PAGE_COMBO)) 14262306a36Sopenharmony_ci rpte.hidx = INVALID_RPTE_HIDX; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* 14562306a36Sopenharmony_ci * handle H_PAGE_4K_PFN case 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_ci if (old_pte & H_PAGE_4K_PFN) { 14862306a36Sopenharmony_ci /* 14962306a36Sopenharmony_ci * All the sub 4k page have the same 15062306a36Sopenharmony_ci * physical address. 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_ci pa = pte_pfn(__pte(old_pte)) << HW_PAGE_SHIFT; 15362306a36Sopenharmony_ci } else { 15462306a36Sopenharmony_ci pa = pte_pfn(__pte(old_pte)) << PAGE_SHIFT; 15562306a36Sopenharmony_ci pa += (subpg_index << shift); 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci hash = hpt_hash(vpn, shift, ssize); 15862306a36Sopenharmony_cirepeat: 15962306a36Sopenharmony_ci hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* Insert into the hash table, primary slot */ 16262306a36Sopenharmony_ci slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa, rflags, 0, 16362306a36Sopenharmony_ci MMU_PAGE_4K, MMU_PAGE_4K, ssize); 16462306a36Sopenharmony_ci /* 16562306a36Sopenharmony_ci * Primary is full, try the secondary 16662306a36Sopenharmony_ci */ 16762306a36Sopenharmony_ci if (unlikely(slot == -1)) { 16862306a36Sopenharmony_ci bool soft_invalid; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci hpte_group = (~hash & htab_hash_mask) * HPTES_PER_GROUP; 17162306a36Sopenharmony_ci slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa, 17262306a36Sopenharmony_ci rflags, HPTE_V_SECONDARY, 17362306a36Sopenharmony_ci MMU_PAGE_4K, MMU_PAGE_4K, 17462306a36Sopenharmony_ci ssize); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci soft_invalid = hpte_soft_invalid(slot); 17762306a36Sopenharmony_ci if (unlikely(soft_invalid)) { 17862306a36Sopenharmony_ci /* 17962306a36Sopenharmony_ci * We got a valid slot from a hardware point of view. 18062306a36Sopenharmony_ci * but we cannot use it, because we use this special 18162306a36Sopenharmony_ci * value; as defined by hpte_soft_invalid(), to track 18262306a36Sopenharmony_ci * invalid slots. We cannot use it. So invalidate it. 18362306a36Sopenharmony_ci */ 18462306a36Sopenharmony_ci gslot = slot & _PTEIDX_GROUP_IX; 18562306a36Sopenharmony_ci mmu_hash_ops.hpte_invalidate(hpte_group + gslot, vpn, 18662306a36Sopenharmony_ci MMU_PAGE_4K, MMU_PAGE_4K, 18762306a36Sopenharmony_ci ssize, 0); 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (unlikely(slot == -1 || soft_invalid)) { 19162306a36Sopenharmony_ci /* 19262306a36Sopenharmony_ci * For soft invalid slot, let's ensure that we release a 19362306a36Sopenharmony_ci * slot from the primary, with the hope that we will 19462306a36Sopenharmony_ci * acquire that slot next time we try. This will ensure 19562306a36Sopenharmony_ci * that we do not get the same soft-invalid slot. 19662306a36Sopenharmony_ci */ 19762306a36Sopenharmony_ci if (soft_invalid || (mftb() & 0x1)) 19862306a36Sopenharmony_ci hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci mmu_hash_ops.hpte_remove(hpte_group); 20162306a36Sopenharmony_ci /* 20262306a36Sopenharmony_ci * FIXME!! Should be try the group from which we removed ? 20362306a36Sopenharmony_ci */ 20462306a36Sopenharmony_ci goto repeat; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci /* 20862306a36Sopenharmony_ci * Hypervisor failure. Restore old pte and return -1 20962306a36Sopenharmony_ci * similar to __hash_page_* 21062306a36Sopenharmony_ci */ 21162306a36Sopenharmony_ci if (unlikely(slot == -2)) { 21262306a36Sopenharmony_ci *ptep = __pte(old_pte); 21362306a36Sopenharmony_ci hash_failure_debug(ea, access, vsid, trap, ssize, 21462306a36Sopenharmony_ci MMU_PAGE_4K, MMU_PAGE_4K, old_pte); 21562306a36Sopenharmony_ci return -1; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci new_pte |= pte_set_hidx(ptep, rpte, subpg_index, slot, PTRS_PER_PTE); 21962306a36Sopenharmony_ci new_pte |= H_PAGE_HASHPTE; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (stress_hpt()) 22262306a36Sopenharmony_ci hpt_do_stress(ea, hpte_group); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci *ptep = __pte(new_pte & ~H_PAGE_BUSY); 22562306a36Sopenharmony_ci return 0; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ciint __hash_page_64K(unsigned long ea, unsigned long access, 22962306a36Sopenharmony_ci unsigned long vsid, pte_t *ptep, unsigned long trap, 23062306a36Sopenharmony_ci unsigned long flags, int ssize) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci real_pte_t rpte; 23362306a36Sopenharmony_ci unsigned long hpte_group; 23462306a36Sopenharmony_ci unsigned long rflags, pa; 23562306a36Sopenharmony_ci unsigned long old_pte, new_pte; 23662306a36Sopenharmony_ci unsigned long vpn, hash, slot; 23762306a36Sopenharmony_ci unsigned long shift = mmu_psize_defs[MMU_PAGE_64K].shift; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* 24062306a36Sopenharmony_ci * atomically mark the linux large page PTE busy and dirty 24162306a36Sopenharmony_ci */ 24262306a36Sopenharmony_ci do { 24362306a36Sopenharmony_ci pte_t pte = READ_ONCE(*ptep); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci old_pte = pte_val(pte); 24662306a36Sopenharmony_ci /* If PTE busy, retry the access */ 24762306a36Sopenharmony_ci if (unlikely(old_pte & H_PAGE_BUSY)) 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci /* If PTE permissions don't match, take page fault */ 25062306a36Sopenharmony_ci if (unlikely(!check_pte_access(access, old_pte))) 25162306a36Sopenharmony_ci return 1; 25262306a36Sopenharmony_ci /* 25362306a36Sopenharmony_ci * Check if PTE has the cache-inhibit bit set 25462306a36Sopenharmony_ci * If so, bail out and refault as a 4k page 25562306a36Sopenharmony_ci */ 25662306a36Sopenharmony_ci if (!mmu_has_feature(MMU_FTR_CI_LARGE_PAGE) && 25762306a36Sopenharmony_ci unlikely(pte_ci(pte))) 25862306a36Sopenharmony_ci return 0; 25962306a36Sopenharmony_ci /* 26062306a36Sopenharmony_ci * Try to lock the PTE, add ACCESSED and DIRTY if it was 26162306a36Sopenharmony_ci * a write access. 26262306a36Sopenharmony_ci */ 26362306a36Sopenharmony_ci new_pte = old_pte | H_PAGE_BUSY | _PAGE_ACCESSED; 26462306a36Sopenharmony_ci if (access & _PAGE_WRITE) 26562306a36Sopenharmony_ci new_pte |= _PAGE_DIRTY; 26662306a36Sopenharmony_ci } while (!pte_xchg(ptep, __pte(old_pte), __pte(new_pte))); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci rflags = htab_convert_pte_flags(new_pte, flags); 26962306a36Sopenharmony_ci rpte = __real_pte(__pte(old_pte), ptep, PTRS_PER_PTE); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci if (cpu_has_feature(CPU_FTR_NOEXECUTE) && 27262306a36Sopenharmony_ci !cpu_has_feature(CPU_FTR_COHERENT_ICACHE)) 27362306a36Sopenharmony_ci rflags = hash_page_do_lazy_icache(rflags, __pte(old_pte), trap); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci vpn = hpt_vpn(ea, vsid, ssize); 27662306a36Sopenharmony_ci if (unlikely(old_pte & H_PAGE_HASHPTE)) { 27762306a36Sopenharmony_ci unsigned long gslot; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci /* 28062306a36Sopenharmony_ci * There MIGHT be an HPTE for this pte 28162306a36Sopenharmony_ci */ 28262306a36Sopenharmony_ci gslot = pte_get_hash_gslot(vpn, shift, ssize, rpte, 0); 28362306a36Sopenharmony_ci if (mmu_hash_ops.hpte_updatepp(gslot, rflags, vpn, MMU_PAGE_64K, 28462306a36Sopenharmony_ci MMU_PAGE_64K, ssize, 28562306a36Sopenharmony_ci flags) == -1) 28662306a36Sopenharmony_ci old_pte &= ~_PAGE_HPTEFLAGS; 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci if (likely(!(old_pte & H_PAGE_HASHPTE))) { 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci pa = pte_pfn(__pte(old_pte)) << PAGE_SHIFT; 29262306a36Sopenharmony_ci hash = hpt_hash(vpn, shift, ssize); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cirepeat: 29562306a36Sopenharmony_ci hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci /* Insert into the hash table, primary slot */ 29862306a36Sopenharmony_ci slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa, rflags, 0, 29962306a36Sopenharmony_ci MMU_PAGE_64K, MMU_PAGE_64K, 30062306a36Sopenharmony_ci ssize); 30162306a36Sopenharmony_ci /* 30262306a36Sopenharmony_ci * Primary is full, try the secondary 30362306a36Sopenharmony_ci */ 30462306a36Sopenharmony_ci if (unlikely(slot == -1)) { 30562306a36Sopenharmony_ci hpte_group = (~hash & htab_hash_mask) * HPTES_PER_GROUP; 30662306a36Sopenharmony_ci slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa, 30762306a36Sopenharmony_ci rflags, 30862306a36Sopenharmony_ci HPTE_V_SECONDARY, 30962306a36Sopenharmony_ci MMU_PAGE_64K, 31062306a36Sopenharmony_ci MMU_PAGE_64K, ssize); 31162306a36Sopenharmony_ci if (slot == -1) { 31262306a36Sopenharmony_ci if (mftb() & 0x1) 31362306a36Sopenharmony_ci hpte_group = (hash & htab_hash_mask) * 31462306a36Sopenharmony_ci HPTES_PER_GROUP; 31562306a36Sopenharmony_ci mmu_hash_ops.hpte_remove(hpte_group); 31662306a36Sopenharmony_ci /* 31762306a36Sopenharmony_ci * FIXME!! Should be try the group from which we removed ? 31862306a36Sopenharmony_ci */ 31962306a36Sopenharmony_ci goto repeat; 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci } 32262306a36Sopenharmony_ci /* 32362306a36Sopenharmony_ci * Hypervisor failure. Restore old pte and return -1 32462306a36Sopenharmony_ci * similar to __hash_page_* 32562306a36Sopenharmony_ci */ 32662306a36Sopenharmony_ci if (unlikely(slot == -2)) { 32762306a36Sopenharmony_ci *ptep = __pte(old_pte); 32862306a36Sopenharmony_ci hash_failure_debug(ea, access, vsid, trap, ssize, 32962306a36Sopenharmony_ci MMU_PAGE_64K, MMU_PAGE_64K, old_pte); 33062306a36Sopenharmony_ci return -1; 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci new_pte = (new_pte & ~_PAGE_HPTEFLAGS) | H_PAGE_HASHPTE; 33462306a36Sopenharmony_ci new_pte |= pte_set_hidx(ptep, rpte, 0, slot, PTRS_PER_PTE); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci if (stress_hpt()) 33762306a36Sopenharmony_ci hpt_do_stress(ea, hpte_group); 33862306a36Sopenharmony_ci } 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci *ptep = __pte(new_pte & ~H_PAGE_BUSY); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci return 0; 34362306a36Sopenharmony_ci} 344