18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Copyright IBM Corporation, 2015
38c2ecf20Sopenharmony_ci * Author Aneesh Kumar K.V <aneesh.kumar@linux.ibm.com>
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify it
68c2ecf20Sopenharmony_ci * under the terms of version 2 of the GNU Lesser General Public License
78c2ecf20Sopenharmony_ci * as published by the Free Software Foundation.
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * This program is distributed in the hope that it would be useful, but
108c2ecf20Sopenharmony_ci * WITHOUT ANY WARRANTY; without even the implied warranty of
118c2ecf20Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/mm.h>
168c2ecf20Sopenharmony_ci#include <asm/machdep.h>
178c2ecf20Sopenharmony_ci#include <asm/mmu.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ciint __hash_page_4K(unsigned long ea, unsigned long access, unsigned long vsid,
208c2ecf20Sopenharmony_ci		   pte_t *ptep, unsigned long trap, unsigned long flags,
218c2ecf20Sopenharmony_ci		   int ssize, int subpg_prot)
228c2ecf20Sopenharmony_ci{
238c2ecf20Sopenharmony_ci	real_pte_t rpte;
248c2ecf20Sopenharmony_ci	unsigned long hpte_group;
258c2ecf20Sopenharmony_ci	unsigned long rflags, pa;
268c2ecf20Sopenharmony_ci	unsigned long old_pte, new_pte;
278c2ecf20Sopenharmony_ci	unsigned long vpn, hash, slot;
288c2ecf20Sopenharmony_ci	unsigned long shift = mmu_psize_defs[MMU_PAGE_4K].shift;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	/*
318c2ecf20Sopenharmony_ci	 * atomically mark the linux large page PTE busy and dirty
328c2ecf20Sopenharmony_ci	 */
338c2ecf20Sopenharmony_ci	do {
348c2ecf20Sopenharmony_ci		pte_t pte = READ_ONCE(*ptep);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci		old_pte = pte_val(pte);
378c2ecf20Sopenharmony_ci		/* If PTE busy, retry the access */
388c2ecf20Sopenharmony_ci		if (unlikely(old_pte & H_PAGE_BUSY))
398c2ecf20Sopenharmony_ci			return 0;
408c2ecf20Sopenharmony_ci		/* If PTE permissions don't match, take page fault */
418c2ecf20Sopenharmony_ci		if (unlikely(!check_pte_access(access, old_pte)))
428c2ecf20Sopenharmony_ci			return 1;
438c2ecf20Sopenharmony_ci		/*
448c2ecf20Sopenharmony_ci		 * Try to lock the PTE, add ACCESSED and DIRTY if it was
458c2ecf20Sopenharmony_ci		 * a write access. Since this is 4K insert of 64K page size
468c2ecf20Sopenharmony_ci		 * also add H_PAGE_COMBO
478c2ecf20Sopenharmony_ci		 */
488c2ecf20Sopenharmony_ci		new_pte = old_pte | H_PAGE_BUSY | _PAGE_ACCESSED;
498c2ecf20Sopenharmony_ci		if (access & _PAGE_WRITE)
508c2ecf20Sopenharmony_ci			new_pte |= _PAGE_DIRTY;
518c2ecf20Sopenharmony_ci	} while (!pte_xchg(ptep, __pte(old_pte), __pte(new_pte)));
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	/*
548c2ecf20Sopenharmony_ci	 * PP bits. _PAGE_USER is already PP bit 0x2, so we only
558c2ecf20Sopenharmony_ci	 * need to add in 0x1 if it's a read-only user page
568c2ecf20Sopenharmony_ci	 */
578c2ecf20Sopenharmony_ci	rflags = htab_convert_pte_flags(new_pte);
588c2ecf20Sopenharmony_ci	rpte = __real_pte(__pte(old_pte), ptep, PTRS_PER_PTE);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	if (cpu_has_feature(CPU_FTR_NOEXECUTE) &&
618c2ecf20Sopenharmony_ci	    !cpu_has_feature(CPU_FTR_COHERENT_ICACHE))
628c2ecf20Sopenharmony_ci		rflags = hash_page_do_lazy_icache(rflags, __pte(old_pte), trap);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	vpn  = hpt_vpn(ea, vsid, ssize);
658c2ecf20Sopenharmony_ci	if (unlikely(old_pte & H_PAGE_HASHPTE)) {
668c2ecf20Sopenharmony_ci		/*
678c2ecf20Sopenharmony_ci		 * There MIGHT be an HPTE for this pte
688c2ecf20Sopenharmony_ci		 */
698c2ecf20Sopenharmony_ci		unsigned long gslot = pte_get_hash_gslot(vpn, shift, ssize,
708c2ecf20Sopenharmony_ci							 rpte, 0);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci		if (mmu_hash_ops.hpte_updatepp(gslot, rflags, vpn, MMU_PAGE_4K,
738c2ecf20Sopenharmony_ci					       MMU_PAGE_4K, ssize, flags) == -1)
748c2ecf20Sopenharmony_ci			old_pte &= ~_PAGE_HPTEFLAGS;
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	if (likely(!(old_pte & H_PAGE_HASHPTE))) {
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci		pa = pte_pfn(__pte(old_pte)) << PAGE_SHIFT;
808c2ecf20Sopenharmony_ci		hash = hpt_hash(vpn, shift, ssize);
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cirepeat:
838c2ecf20Sopenharmony_ci		hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci		/* Insert into the hash table, primary slot */
868c2ecf20Sopenharmony_ci		slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa, rflags, 0,
878c2ecf20Sopenharmony_ci						MMU_PAGE_4K, MMU_PAGE_4K, ssize);
888c2ecf20Sopenharmony_ci		/*
898c2ecf20Sopenharmony_ci		 * Primary is full, try the secondary
908c2ecf20Sopenharmony_ci		 */
918c2ecf20Sopenharmony_ci		if (unlikely(slot == -1)) {
928c2ecf20Sopenharmony_ci			hpte_group = (~hash & htab_hash_mask) * HPTES_PER_GROUP;
938c2ecf20Sopenharmony_ci			slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa,
948c2ecf20Sopenharmony_ci							rflags,
958c2ecf20Sopenharmony_ci							HPTE_V_SECONDARY,
968c2ecf20Sopenharmony_ci							MMU_PAGE_4K,
978c2ecf20Sopenharmony_ci							MMU_PAGE_4K, ssize);
988c2ecf20Sopenharmony_ci			if (slot == -1) {
998c2ecf20Sopenharmony_ci				if (mftb() & 0x1)
1008c2ecf20Sopenharmony_ci					hpte_group = (hash & htab_hash_mask) *
1018c2ecf20Sopenharmony_ci							HPTES_PER_GROUP;
1028c2ecf20Sopenharmony_ci				mmu_hash_ops.hpte_remove(hpte_group);
1038c2ecf20Sopenharmony_ci				/*
1048c2ecf20Sopenharmony_ci				 * FIXME!! Should be try the group from which we removed ?
1058c2ecf20Sopenharmony_ci				 */
1068c2ecf20Sopenharmony_ci				goto repeat;
1078c2ecf20Sopenharmony_ci			}
1088c2ecf20Sopenharmony_ci		}
1098c2ecf20Sopenharmony_ci		/*
1108c2ecf20Sopenharmony_ci		 * Hypervisor failure. Restore old pte and return -1
1118c2ecf20Sopenharmony_ci		 * similar to __hash_page_*
1128c2ecf20Sopenharmony_ci		 */
1138c2ecf20Sopenharmony_ci		if (unlikely(slot == -2)) {
1148c2ecf20Sopenharmony_ci			*ptep = __pte(old_pte);
1158c2ecf20Sopenharmony_ci			hash_failure_debug(ea, access, vsid, trap, ssize,
1168c2ecf20Sopenharmony_ci					   MMU_PAGE_4K, MMU_PAGE_4K, old_pte);
1178c2ecf20Sopenharmony_ci			return -1;
1188c2ecf20Sopenharmony_ci		}
1198c2ecf20Sopenharmony_ci		new_pte = (new_pte & ~_PAGE_HPTEFLAGS) | H_PAGE_HASHPTE;
1208c2ecf20Sopenharmony_ci		new_pte |= pte_set_hidx(ptep, rpte, 0, slot, PTRS_PER_PTE);
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci	*ptep = __pte(new_pte & ~H_PAGE_BUSY);
1238c2ecf20Sopenharmony_ci	return 0;
1248c2ecf20Sopenharmony_ci}
125