xref: /OK3568_Linux_fs/kernel/arch/powerpc/mm/book3s64/hash_4k.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun /*
2*4882a593Smuzhiyun  * Copyright IBM Corporation, 2015
3*4882a593Smuzhiyun  * Author Aneesh Kumar K.V <aneesh.kumar@linux.ibm.com>
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * This program is free software; you can redistribute it and/or modify it
6*4882a593Smuzhiyun  * under the terms of version 2 of the GNU Lesser General Public License
7*4882a593Smuzhiyun  * as published by the Free Software Foundation.
8*4882a593Smuzhiyun  *
9*4882a593Smuzhiyun  * This program is distributed in the hope that it would be useful, but
10*4882a593Smuzhiyun  * WITHOUT ANY WARRANTY; without even the implied warranty of
11*4882a593Smuzhiyun  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12*4882a593Smuzhiyun  *
13*4882a593Smuzhiyun  */
14*4882a593Smuzhiyun 
15*4882a593Smuzhiyun #include <linux/mm.h>
16*4882a593Smuzhiyun #include <asm/machdep.h>
17*4882a593Smuzhiyun #include <asm/mmu.h>
18*4882a593Smuzhiyun 
__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)19*4882a593Smuzhiyun int __hash_page_4K(unsigned long ea, unsigned long access, unsigned long vsid,
20*4882a593Smuzhiyun 		   pte_t *ptep, unsigned long trap, unsigned long flags,
21*4882a593Smuzhiyun 		   int ssize, int subpg_prot)
22*4882a593Smuzhiyun {
23*4882a593Smuzhiyun 	real_pte_t rpte;
24*4882a593Smuzhiyun 	unsigned long hpte_group;
25*4882a593Smuzhiyun 	unsigned long rflags, pa;
26*4882a593Smuzhiyun 	unsigned long old_pte, new_pte;
27*4882a593Smuzhiyun 	unsigned long vpn, hash, slot;
28*4882a593Smuzhiyun 	unsigned long shift = mmu_psize_defs[MMU_PAGE_4K].shift;
29*4882a593Smuzhiyun 
30*4882a593Smuzhiyun 	/*
31*4882a593Smuzhiyun 	 * atomically mark the linux large page PTE busy and dirty
32*4882a593Smuzhiyun 	 */
33*4882a593Smuzhiyun 	do {
34*4882a593Smuzhiyun 		pte_t pte = READ_ONCE(*ptep);
35*4882a593Smuzhiyun 
36*4882a593Smuzhiyun 		old_pte = pte_val(pte);
37*4882a593Smuzhiyun 		/* If PTE busy, retry the access */
38*4882a593Smuzhiyun 		if (unlikely(old_pte & H_PAGE_BUSY))
39*4882a593Smuzhiyun 			return 0;
40*4882a593Smuzhiyun 		/* If PTE permissions don't match, take page fault */
41*4882a593Smuzhiyun 		if (unlikely(!check_pte_access(access, old_pte)))
42*4882a593Smuzhiyun 			return 1;
43*4882a593Smuzhiyun 		/*
44*4882a593Smuzhiyun 		 * Try to lock the PTE, add ACCESSED and DIRTY if it was
45*4882a593Smuzhiyun 		 * a write access. Since this is 4K insert of 64K page size
46*4882a593Smuzhiyun 		 * also add H_PAGE_COMBO
47*4882a593Smuzhiyun 		 */
48*4882a593Smuzhiyun 		new_pte = old_pte | H_PAGE_BUSY | _PAGE_ACCESSED;
49*4882a593Smuzhiyun 		if (access & _PAGE_WRITE)
50*4882a593Smuzhiyun 			new_pte |= _PAGE_DIRTY;
51*4882a593Smuzhiyun 	} while (!pte_xchg(ptep, __pte(old_pte), __pte(new_pte)));
52*4882a593Smuzhiyun 
53*4882a593Smuzhiyun 	/*
54*4882a593Smuzhiyun 	 * PP bits. _PAGE_USER is already PP bit 0x2, so we only
55*4882a593Smuzhiyun 	 * need to add in 0x1 if it's a read-only user page
56*4882a593Smuzhiyun 	 */
57*4882a593Smuzhiyun 	rflags = htab_convert_pte_flags(new_pte);
58*4882a593Smuzhiyun 	rpte = __real_pte(__pte(old_pte), ptep, PTRS_PER_PTE);
59*4882a593Smuzhiyun 
60*4882a593Smuzhiyun 	if (cpu_has_feature(CPU_FTR_NOEXECUTE) &&
61*4882a593Smuzhiyun 	    !cpu_has_feature(CPU_FTR_COHERENT_ICACHE))
62*4882a593Smuzhiyun 		rflags = hash_page_do_lazy_icache(rflags, __pte(old_pte), trap);
63*4882a593Smuzhiyun 
64*4882a593Smuzhiyun 	vpn  = hpt_vpn(ea, vsid, ssize);
65*4882a593Smuzhiyun 	if (unlikely(old_pte & H_PAGE_HASHPTE)) {
66*4882a593Smuzhiyun 		/*
67*4882a593Smuzhiyun 		 * There MIGHT be an HPTE for this pte
68*4882a593Smuzhiyun 		 */
69*4882a593Smuzhiyun 		unsigned long gslot = pte_get_hash_gslot(vpn, shift, ssize,
70*4882a593Smuzhiyun 							 rpte, 0);
71*4882a593Smuzhiyun 
72*4882a593Smuzhiyun 		if (mmu_hash_ops.hpte_updatepp(gslot, rflags, vpn, MMU_PAGE_4K,
73*4882a593Smuzhiyun 					       MMU_PAGE_4K, ssize, flags) == -1)
74*4882a593Smuzhiyun 			old_pte &= ~_PAGE_HPTEFLAGS;
75*4882a593Smuzhiyun 	}
76*4882a593Smuzhiyun 
77*4882a593Smuzhiyun 	if (likely(!(old_pte & H_PAGE_HASHPTE))) {
78*4882a593Smuzhiyun 
79*4882a593Smuzhiyun 		pa = pte_pfn(__pte(old_pte)) << PAGE_SHIFT;
80*4882a593Smuzhiyun 		hash = hpt_hash(vpn, shift, ssize);
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun repeat:
83*4882a593Smuzhiyun 		hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP;
84*4882a593Smuzhiyun 
85*4882a593Smuzhiyun 		/* Insert into the hash table, primary slot */
86*4882a593Smuzhiyun 		slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa, rflags, 0,
87*4882a593Smuzhiyun 						MMU_PAGE_4K, MMU_PAGE_4K, ssize);
88*4882a593Smuzhiyun 		/*
89*4882a593Smuzhiyun 		 * Primary is full, try the secondary
90*4882a593Smuzhiyun 		 */
91*4882a593Smuzhiyun 		if (unlikely(slot == -1)) {
92*4882a593Smuzhiyun 			hpte_group = (~hash & htab_hash_mask) * HPTES_PER_GROUP;
93*4882a593Smuzhiyun 			slot = mmu_hash_ops.hpte_insert(hpte_group, vpn, pa,
94*4882a593Smuzhiyun 							rflags,
95*4882a593Smuzhiyun 							HPTE_V_SECONDARY,
96*4882a593Smuzhiyun 							MMU_PAGE_4K,
97*4882a593Smuzhiyun 							MMU_PAGE_4K, ssize);
98*4882a593Smuzhiyun 			if (slot == -1) {
99*4882a593Smuzhiyun 				if (mftb() & 0x1)
100*4882a593Smuzhiyun 					hpte_group = (hash & htab_hash_mask) *
101*4882a593Smuzhiyun 							HPTES_PER_GROUP;
102*4882a593Smuzhiyun 				mmu_hash_ops.hpte_remove(hpte_group);
103*4882a593Smuzhiyun 				/*
104*4882a593Smuzhiyun 				 * FIXME!! Should be try the group from which we removed ?
105*4882a593Smuzhiyun 				 */
106*4882a593Smuzhiyun 				goto repeat;
107*4882a593Smuzhiyun 			}
108*4882a593Smuzhiyun 		}
109*4882a593Smuzhiyun 		/*
110*4882a593Smuzhiyun 		 * Hypervisor failure. Restore old pte and return -1
111*4882a593Smuzhiyun 		 * similar to __hash_page_*
112*4882a593Smuzhiyun 		 */
113*4882a593Smuzhiyun 		if (unlikely(slot == -2)) {
114*4882a593Smuzhiyun 			*ptep = __pte(old_pte);
115*4882a593Smuzhiyun 			hash_failure_debug(ea, access, vsid, trap, ssize,
116*4882a593Smuzhiyun 					   MMU_PAGE_4K, MMU_PAGE_4K, old_pte);
117*4882a593Smuzhiyun 			return -1;
118*4882a593Smuzhiyun 		}
119*4882a593Smuzhiyun 		new_pte = (new_pte & ~_PAGE_HPTEFLAGS) | H_PAGE_HASHPTE;
120*4882a593Smuzhiyun 		new_pte |= pte_set_hidx(ptep, rpte, 0, slot, PTRS_PER_PTE);
121*4882a593Smuzhiyun 	}
122*4882a593Smuzhiyun 	*ptep = __pte(new_pte & ~H_PAGE_BUSY);
123*4882a593Smuzhiyun 	return 0;
124*4882a593Smuzhiyun }
125