diff options
Diffstat (limited to 'arch/sparc64/mm/tsb.c')
-rw-r--r-- | arch/sparc64/mm/tsb.c | 151 |
1 files changed, 145 insertions, 6 deletions
diff --git a/arch/sparc64/mm/tsb.c b/arch/sparc64/mm/tsb.c index dfe7144fcdf6..707af4b84a0e 100644 --- a/arch/sparc64/mm/tsb.c +++ b/arch/sparc64/mm/tsb.c | |||
@@ -10,6 +10,7 @@ | |||
10 | #include <asm/tlb.h> | 10 | #include <asm/tlb.h> |
11 | #include <asm/mmu_context.h> | 11 | #include <asm/mmu_context.h> |
12 | #include <asm/pgtable.h> | 12 | #include <asm/pgtable.h> |
13 | #include <asm/tsb.h> | ||
13 | 14 | ||
14 | /* We use an 8K TSB for the whole kernel, this allows to | 15 | /* We use an 8K TSB for the whole kernel, this allows to |
15 | * handle about 4MB of modules and vmalloc mappings without | 16 | * handle about 4MB of modules and vmalloc mappings without |
@@ -146,6 +147,9 @@ static void setup_tsb_params(struct mm_struct *mm, unsigned long tsb_bytes) | |||
146 | tte |= _PAGE_SZ4MB; | 147 | tte |= _PAGE_SZ4MB; |
147 | page_sz = 4 * 1024 * 1024; | 148 | page_sz = 4 * 1024 * 1024; |
148 | break; | 149 | break; |
150 | |||
151 | default: | ||
152 | BUG(); | ||
149 | }; | 153 | }; |
150 | 154 | ||
151 | tsb_reg |= base; | 155 | tsb_reg |= base; |
@@ -157,23 +161,158 @@ static void setup_tsb_params(struct mm_struct *mm, unsigned long tsb_bytes) | |||
157 | mm->context.tsb_map_pte = tte; | 161 | mm->context.tsb_map_pte = tte; |
158 | } | 162 | } |
159 | 163 | ||
164 | /* The page tables are locked against modifications while this | ||
165 | * runs. | ||
166 | * | ||
167 | * XXX do some prefetching... | ||
168 | */ | ||
169 | static void copy_tsb(struct tsb *old_tsb, unsigned long old_size, | ||
170 | struct tsb *new_tsb, unsigned long new_size) | ||
171 | { | ||
172 | unsigned long old_nentries = old_size / sizeof(struct tsb); | ||
173 | unsigned long new_nentries = new_size / sizeof(struct tsb); | ||
174 | unsigned long i; | ||
175 | |||
176 | for (i = 0; i < old_nentries; i++) { | ||
177 | register unsigned long tag asm("o4"); | ||
178 | register unsigned long pte asm("o5"); | ||
179 | unsigned long v; | ||
180 | unsigned int hash; | ||
181 | |||
182 | __asm__ __volatile__( | ||
183 | "ldda [%2] %3, %0" | ||
184 | : "=r" (tag), "=r" (pte) | ||
185 | : "r" (&old_tsb[i]), "i" (ASI_NUCLEUS_QUAD_LDD)); | ||
186 | |||
187 | if (!tag || (tag & TSB_TAG_LOCK)) | ||
188 | continue; | ||
189 | |||
190 | /* We only put base page size PTEs into the TSB, | ||
191 | * but that might change in the future. This code | ||
192 | * would need to be changed if we start putting larger | ||
193 | * page size PTEs into there. | ||
194 | */ | ||
195 | WARN_ON((pte & _PAGE_ALL_SZ_BITS) != _PAGE_SZBITS); | ||
196 | |||
197 | /* The tag holds bits 22 to 63 of the virtual address | ||
198 | * and the context. Clear out the context, and shift | ||
199 | * up to make a virtual address. | ||
200 | */ | ||
201 | v = (tag & ((1UL << 42UL) - 1UL)) << 22UL; | ||
202 | |||
203 | /* The implied bits of the tag (bits 13 to 21) are | ||
204 | * determined by the TSB entry index, so fill that in. | ||
205 | */ | ||
206 | v |= (i & (512UL - 1UL)) << 13UL; | ||
207 | |||
208 | hash = tsb_hash(v, new_nentries); | ||
209 | new_tsb[hash].tag = tag; | ||
210 | new_tsb[hash].pte = pte; | ||
211 | } | ||
212 | } | ||
213 | |||
214 | /* When the RSS of an address space exceeds mm->context.tsb_rss_limit, | ||
215 | * update_mmu_cache() invokes this routine to try and grow the TSB. | ||
216 | * When we reach the maximum TSB size supported, we stick ~0UL into | ||
217 | * mm->context.tsb_rss_limit so the grow checks in update_mmu_cache() | ||
218 | * will not trigger any longer. | ||
219 | * | ||
220 | * The TSB can be anywhere from 8K to 1MB in size, in increasing powers | ||
221 | * of two. The TSB must be aligned to it's size, so f.e. a 512K TSB | ||
222 | * must be 512K aligned. | ||
223 | * | ||
224 | * The idea here is to grow the TSB when the RSS of the process approaches | ||
225 | * the number of entries that the current TSB can hold at once. Currently, | ||
226 | * we trigger when the RSS hits 3/4 of the TSB capacity. | ||
227 | */ | ||
228 | void tsb_grow(struct mm_struct *mm, unsigned long rss, gfp_t gfp_flags) | ||
229 | { | ||
230 | unsigned long max_tsb_size = 1 * 1024 * 1024; | ||
231 | unsigned long size, old_size; | ||
232 | struct page *page; | ||
233 | struct tsb *old_tsb; | ||
234 | |||
235 | if (max_tsb_size > (PAGE_SIZE << MAX_ORDER)) | ||
236 | max_tsb_size = (PAGE_SIZE << MAX_ORDER); | ||
237 | |||
238 | for (size = PAGE_SIZE; size < max_tsb_size; size <<= 1UL) { | ||
239 | unsigned long n_entries = size / sizeof(struct tsb); | ||
240 | |||
241 | n_entries = (n_entries * 3) / 4; | ||
242 | if (n_entries > rss) | ||
243 | break; | ||
244 | } | ||
245 | |||
246 | page = alloc_pages(gfp_flags | __GFP_ZERO, get_order(size)); | ||
247 | if (unlikely(!page)) | ||
248 | return; | ||
249 | |||
250 | if (size == max_tsb_size) | ||
251 | mm->context.tsb_rss_limit = ~0UL; | ||
252 | else | ||
253 | mm->context.tsb_rss_limit = | ||
254 | ((size / sizeof(struct tsb)) * 3) / 4; | ||
255 | |||
256 | old_tsb = mm->context.tsb; | ||
257 | old_size = mm->context.tsb_nentries * sizeof(struct tsb); | ||
258 | |||
259 | if (old_tsb) | ||
260 | copy_tsb(old_tsb, old_size, page_address(page), size); | ||
261 | |||
262 | mm->context.tsb = page_address(page); | ||
263 | setup_tsb_params(mm, size); | ||
264 | |||
265 | /* If old_tsb is NULL, we're being invoked for the first time | ||
266 | * from init_new_context(). | ||
267 | */ | ||
268 | if (old_tsb) { | ||
269 | /* Now force all other processors to reload the new | ||
270 | * TSB state. | ||
271 | */ | ||
272 | smp_tsb_sync(mm); | ||
273 | |||
274 | /* Finally reload it on the local cpu. No further | ||
275 | * references will remain to the old TSB and we can | ||
276 | * thus free it up. | ||
277 | */ | ||
278 | tsb_context_switch(mm); | ||
279 | |||
280 | free_pages((unsigned long) old_tsb, get_order(old_size)); | ||
281 | } | ||
282 | } | ||
283 | |||
160 | int init_new_context(struct task_struct *tsk, struct mm_struct *mm) | 284 | int init_new_context(struct task_struct *tsk, struct mm_struct *mm) |
161 | { | 285 | { |
162 | unsigned long page = get_zeroed_page(GFP_KERNEL); | 286 | unsigned long initial_rss; |
163 | 287 | ||
164 | mm->context.sparc64_ctx_val = 0UL; | 288 | mm->context.sparc64_ctx_val = 0UL; |
165 | if (unlikely(!page)) | ||
166 | return -ENOMEM; | ||
167 | 289 | ||
168 | mm->context.tsb = (struct tsb *) page; | 290 | /* copy_mm() copies over the parent's mm_struct before calling |
169 | setup_tsb_params(mm, PAGE_SIZE); | 291 | * us, so we need to zero out the TSB pointer or else tsb_grow() |
292 | * will be confused and think there is an older TSB to free up. | ||
293 | */ | ||
294 | mm->context.tsb = NULL; | ||
295 | |||
296 | /* If this is fork, inherit the parent's TSB size. We would | ||
297 | * grow it to that size on the first page fault anyways. | ||
298 | */ | ||
299 | initial_rss = mm->context.tsb_nentries; | ||
300 | if (initial_rss) | ||
301 | initial_rss -= 1; | ||
302 | |||
303 | tsb_grow(mm, initial_rss, GFP_KERNEL); | ||
304 | |||
305 | if (unlikely(!mm->context.tsb)) | ||
306 | return -ENOMEM; | ||
170 | 307 | ||
171 | return 0; | 308 | return 0; |
172 | } | 309 | } |
173 | 310 | ||
174 | void destroy_context(struct mm_struct *mm) | 311 | void destroy_context(struct mm_struct *mm) |
175 | { | 312 | { |
176 | free_page((unsigned long) mm->context.tsb); | 313 | unsigned long size = mm->context.tsb_nentries * sizeof(struct tsb); |
314 | |||
315 | free_pages((unsigned long) mm->context.tsb, get_order(size)); | ||
177 | 316 | ||
178 | /* We can remove these later, but for now it's useful | 317 | /* We can remove these later, but for now it's useful |
179 | * to catch any bogus post-destroy_context() references | 318 | * to catch any bogus post-destroy_context() references |