1 /*
2  * iSeries hashtable management.
3  *	Derived from pSeries_htab.c
4  *
5  * SMP scalability work:
6  *    Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version
11  * 2 of the License, or (at your option) any later version.
12  */
13 #include <asm/machdep.h>
14 #include <asm/pgtable.h>
15 #include <asm/mmu.h>
16 #include <asm/mmu_context.h>
17 #include <asm/abs_addr.h>
18 #include <linux/spinlock.h>
19 
20 #include "call_hpt.h"
21 
22 static spinlock_t iSeries_hlocks[64] __cacheline_aligned_in_smp;
23 
24 /*
25  * Very primitive algorithm for picking up a lock
26  */
iSeries_hlock(unsigned long slot)27 static inline void iSeries_hlock(unsigned long slot)
28 {
29 	if (slot & 0x8)
30 		slot = ~slot;
31 	spin_lock(&iSeries_hlocks[(slot >> 4) & 0x3f]);
32 }
33 
iSeries_hunlock(unsigned long slot)34 static inline void iSeries_hunlock(unsigned long slot)
35 {
36 	if (slot & 0x8)
37 		slot = ~slot;
38 	spin_unlock(&iSeries_hlocks[(slot >> 4) & 0x3f]);
39 }
40 
iSeries_hpte_insert(unsigned long hpte_group,unsigned long va,unsigned long pa,unsigned long rflags,unsigned long vflags,int psize,int ssize)41 static long iSeries_hpte_insert(unsigned long hpte_group, unsigned long va,
42 			 unsigned long pa, unsigned long rflags,
43 			 unsigned long vflags, int psize, int ssize)
44 {
45 	long slot;
46 	struct hash_pte lhpte;
47 	int secondary = 0;
48 
49 	BUG_ON(psize != MMU_PAGE_4K);
50 
51 	/*
52 	 * The hypervisor tries both primary and secondary.
53 	 * If we are being called to insert in the secondary,
54 	 * it means we have already tried both primary and secondary,
55 	 * so we return failure immediately.
56 	 */
57 	if (vflags & HPTE_V_SECONDARY)
58 		return -1;
59 
60 	iSeries_hlock(hpte_group);
61 
62 	slot = HvCallHpt_findValid(&lhpte, va >> HW_PAGE_SHIFT);
63 	if (unlikely(lhpte.v & HPTE_V_VALID)) {
64 		if (vflags & HPTE_V_BOLTED) {
65 			HvCallHpt_setSwBits(slot, 0x10, 0);
66 			HvCallHpt_setPp(slot, PP_RWXX);
67 			iSeries_hunlock(hpte_group);
68 			if (slot < 0)
69 				return 0x8 | (slot & 7);
70 			else
71 				return slot & 7;
72 		}
73 		BUG();
74 	}
75 
76 	if (slot == -1)	{ /* No available entry found in either group */
77 		iSeries_hunlock(hpte_group);
78 		return -1;
79 	}
80 
81 	if (slot < 0) {		/* MSB set means secondary group */
82 		vflags |= HPTE_V_SECONDARY;
83 		secondary = 1;
84 		slot &= 0x7fffffffffffffff;
85 	}
86 
87 
88 	lhpte.v = hpte_encode_v(va, MMU_PAGE_4K, MMU_SEGSIZE_256M) |
89 		vflags | HPTE_V_VALID;
90 	lhpte.r = hpte_encode_r(phys_to_abs(pa), MMU_PAGE_4K) | rflags;
91 
92 	/* Now fill in the actual HPTE */
93 	HvCallHpt_addValidate(slot, secondary, &lhpte);
94 
95 	iSeries_hunlock(hpte_group);
96 
97 	return (secondary << 3) | (slot & 7);
98 }
99 
iSeries_hpte_getword0(unsigned long slot)100 static unsigned long iSeries_hpte_getword0(unsigned long slot)
101 {
102 	struct hash_pte hpte;
103 
104 	HvCallHpt_get(&hpte, slot);
105 	return hpte.v;
106 }
107 
iSeries_hpte_remove(unsigned long hpte_group)108 static long iSeries_hpte_remove(unsigned long hpte_group)
109 {
110 	unsigned long slot_offset;
111 	int i;
112 	unsigned long hpte_v;
113 
114 	/* Pick a random slot to start at */
115 	slot_offset = mftb() & 0x7;
116 
117 	iSeries_hlock(hpte_group);
118 
119 	for (i = 0; i < HPTES_PER_GROUP; i++) {
120 		hpte_v = iSeries_hpte_getword0(hpte_group + slot_offset);
121 
122 		if (! (hpte_v & HPTE_V_BOLTED)) {
123 			HvCallHpt_invalidateSetSwBitsGet(hpte_group +
124 							 slot_offset, 0, 0);
125 			iSeries_hunlock(hpte_group);
126 			return i;
127 		}
128 
129 		slot_offset++;
130 		slot_offset &= 0x7;
131 	}
132 
133 	iSeries_hunlock(hpte_group);
134 
135 	return -1;
136 }
137 
138 /*
139  * The HyperVisor expects the "flags" argument in this form:
140  *	bits  0..59 : reserved
141  *	bit      60 : N
142  *	bits 61..63 : PP2,PP1,PP0
143  */
iSeries_hpte_updatepp(unsigned long slot,unsigned long newpp,unsigned long va,int psize,int ssize,int local)144 static long iSeries_hpte_updatepp(unsigned long slot, unsigned long newpp,
145 			unsigned long va, int psize, int ssize, int local)
146 {
147 	struct hash_pte hpte;
148 	unsigned long want_v;
149 
150 	iSeries_hlock(slot);
151 
152 	HvCallHpt_get(&hpte, slot);
153 	want_v = hpte_encode_v(va, MMU_PAGE_4K, MMU_SEGSIZE_256M);
154 
155 	if (HPTE_V_COMPARE(hpte.v, want_v) && (hpte.v & HPTE_V_VALID)) {
156 		/*
157 		 * Hypervisor expects bits as NPPP, which is
158 		 * different from how they are mapped in our PP.
159 		 */
160 		HvCallHpt_setPp(slot, (newpp & 0x3) | ((newpp & 0x4) << 1));
161 		iSeries_hunlock(slot);
162 		return 0;
163 	}
164 	iSeries_hunlock(slot);
165 
166 	return -1;
167 }
168 
169 /*
170  * Functions used to find the PTE for a particular virtual address.
171  * Only used during boot when bolting pages.
172  *
173  * Input : vpn      : virtual page number
174  * Output: PTE index within the page table of the entry
175  *         -1 on failure
176  */
iSeries_hpte_find(unsigned long vpn)177 static long iSeries_hpte_find(unsigned long vpn)
178 {
179 	struct hash_pte hpte;
180 	long slot;
181 
182 	/*
183 	 * The HvCallHpt_findValid interface is as follows:
184 	 * 0xffffffffffffffff : No entry found.
185 	 * 0x00000000xxxxxxxx : Entry found in primary group, slot x
186 	 * 0x80000000xxxxxxxx : Entry found in secondary group, slot x
187 	 */
188 	slot = HvCallHpt_findValid(&hpte, vpn);
189 	if (hpte.v & HPTE_V_VALID) {
190 		if (slot < 0) {
191 			slot &= 0x7fffffffffffffff;
192 			slot = -slot;
193 		}
194 	} else
195 		slot = -1;
196 	return slot;
197 }
198 
199 /*
200  * Update the page protection bits. Intended to be used to create
201  * guard pages for kernel data structures on pages which are bolted
202  * in the HPT. Assumes pages being operated on will not be stolen.
203  * Does not work on large pages.
204  *
205  * No need to lock here because we should be the only user.
206  */
iSeries_hpte_updateboltedpp(unsigned long newpp,unsigned long ea,int psize,int ssize)207 static void iSeries_hpte_updateboltedpp(unsigned long newpp, unsigned long ea,
208 					int psize, int ssize)
209 {
210 	unsigned long vsid,va,vpn;
211 	long slot;
212 
213 	BUG_ON(psize != MMU_PAGE_4K);
214 
215 	vsid = get_kernel_vsid(ea, MMU_SEGSIZE_256M);
216 	va = (vsid << 28) | (ea & 0x0fffffff);
217 	vpn = va >> HW_PAGE_SHIFT;
218 	slot = iSeries_hpte_find(vpn);
219 	if (slot == -1)
220 		panic("updateboltedpp: Could not find page to bolt\n");
221 	HvCallHpt_setPp(slot, newpp);
222 }
223 
iSeries_hpte_invalidate(unsigned long slot,unsigned long va,int psize,int ssize,int local)224 static void iSeries_hpte_invalidate(unsigned long slot, unsigned long va,
225 				    int psize, int ssize, int local)
226 {
227 	unsigned long hpte_v;
228 	unsigned long avpn = va >> 23;
229 	unsigned long flags;
230 
231 	local_irq_save(flags);
232 
233 	iSeries_hlock(slot);
234 
235 	hpte_v = iSeries_hpte_getword0(slot);
236 
237 	if ((HPTE_V_AVPN_VAL(hpte_v) == avpn) && (hpte_v & HPTE_V_VALID))
238 		HvCallHpt_invalidateSetSwBitsGet(slot, 0, 0);
239 
240 	iSeries_hunlock(slot);
241 
242 	local_irq_restore(flags);
243 }
244 
hpte_init_iSeries(void)245 void __init hpte_init_iSeries(void)
246 {
247 	int i;
248 
249 	for (i = 0; i < ARRAY_SIZE(iSeries_hlocks); i++)
250 		spin_lock_init(&iSeries_hlocks[i]);
251 
252 	ppc_md.hpte_invalidate	= iSeries_hpte_invalidate;
253 	ppc_md.hpte_updatepp	= iSeries_hpte_updatepp;
254 	ppc_md.hpte_updateboltedpp = iSeries_hpte_updateboltedpp;
255 	ppc_md.hpte_insert	= iSeries_hpte_insert;
256 	ppc_md.hpte_remove	= iSeries_hpte_remove;
257 }
258