aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2008-02-04 10:48:07 -0500
committerIngo Molnar <mingo@elte.hu>2008-02-04 10:48:07 -0500
commit65e074dffa198978ab0c9976a19b954fbe1183e2 (patch)
treea50807444598c6d7a64ed75af23f9a246ee40d86
parentf4ae5da0e8e92caa168e7c2a7c4a6c4064b082c2 (diff)
x86: cpa, preserve large pages if possible
When CPA is called on a range which fits into a large page mapping, avoid to split the page when: 1) There is no change of attributes 2) The range to change is a complete large mapping Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Ingo Molnar <mingo@elte.hu>
-rw-r--r--arch/x86/mm/pageattr.c142
1 files changed, 130 insertions, 12 deletions
diff --git a/arch/x86/mm/pageattr.c b/arch/x86/mm/pageattr.c
index 79a9f1b42ddd..40b7ac58e671 100644
--- a/arch/x86/mm/pageattr.c
+++ b/arch/x86/mm/pageattr.c
@@ -18,12 +18,17 @@
18 18
19struct cpa_data { 19struct cpa_data {
20 unsigned long vaddr; 20 unsigned long vaddr;
21 int numpages;
22 pgprot_t mask_set; 21 pgprot_t mask_set;
23 pgprot_t mask_clr; 22 pgprot_t mask_clr;
23 int numpages;
24 int flushtlb; 24 int flushtlb;
25}; 25};
26 26
27enum {
28 CPA_NO_SPLIT = 0,
29 CPA_SPLIT,
30};
31
27static inline int 32static inline int
28within(unsigned long addr, unsigned long start, unsigned long end) 33within(unsigned long addr, unsigned long start, unsigned long end)
29{ 34{
@@ -230,6 +235,86 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte)
230#endif 235#endif
231} 236}
232 237
238static int try_preserve_large_page(pte_t *kpte, unsigned long address,
239 struct cpa_data *cpa)
240{
241 unsigned long nextpage_addr, numpages, pmask, psize, flags;
242 pte_t new_pte, old_pte, *tmp;
243 pgprot_t old_prot, new_prot;
244 int level, res = CPA_SPLIT;
245
246 spin_lock_irqsave(&pgd_lock, flags);
247 /*
248 * Check for races, another CPU might have split this page
249 * up already:
250 */
251 tmp = lookup_address(address, &level);
252 if (tmp != kpte)
253 goto out_unlock;
254
255 switch (level) {
256 case PG_LEVEL_2M:
257 psize = LARGE_PAGE_SIZE;
258 pmask = LARGE_PAGE_MASK;
259 break;
260 case PG_LEVEL_1G:
261 default:
262 res = -EINVAL;
263 goto out_unlock;
264 }
265
266 /*
267 * Calculate the number of pages, which fit into this large
268 * page starting at address:
269 */
270 nextpage_addr = (address + psize) & pmask;
271 numpages = (nextpage_addr - address) >> PAGE_SHIFT;
272 if (numpages < cpa->numpages)
273 cpa->numpages = numpages;
274
275 /*
276 * We are safe now. Check whether the new pgprot is the same:
277 */
278 old_pte = *kpte;
279 old_prot = new_prot = pte_pgprot(old_pte);
280
281 pgprot_val(new_prot) &= ~pgprot_val(cpa->mask_clr);
282 pgprot_val(new_prot) |= pgprot_val(cpa->mask_set);
283 new_prot = static_protections(new_prot, address);
284
285 /*
286 * If there are no changes, return. maxpages has been updated
287 * above:
288 */
289 if (pgprot_val(new_prot) == pgprot_val(old_prot)) {
290 res = CPA_NO_SPLIT;
291 goto out_unlock;
292 }
293
294 /*
295 * We need to change the attributes. Check, whether we can
296 * change the large page in one go. We request a split, when
297 * the address is not aligned and the number of pages is
298 * smaller than the number of pages in the large page. Note
299 * that we limited the number of possible pages already to
300 * the number of pages in the large page.
301 */
302 if (address == (nextpage_addr - psize) && cpa->numpages == numpages) {
303 /*
304 * The address is aligned and the number of pages
305 * covers the full page.
306 */
307 new_pte = pfn_pte(pte_pfn(old_pte), canon_pgprot(new_prot));
308 __set_pmd_pte(kpte, address, new_pte);
309 cpa->flushtlb = 1;
310 res = CPA_NO_SPLIT;
311 }
312
313out_unlock:
314 spin_unlock_irqrestore(&pgd_lock, flags);
315 return res;
316}
317
233static int split_large_page(pte_t *kpte, unsigned long address) 318static int split_large_page(pte_t *kpte, unsigned long address)
234{ 319{
235 pgprot_t ref_prot = pte_pgprot(pte_clrhuge(*kpte)); 320 pgprot_t ref_prot = pte_pgprot(pte_clrhuge(*kpte));
@@ -295,7 +380,7 @@ out_unlock:
295static int __change_page_attr(unsigned long address, struct cpa_data *cpa) 380static int __change_page_attr(unsigned long address, struct cpa_data *cpa)
296{ 381{
297 struct page *kpte_page; 382 struct page *kpte_page;
298 int level, err = 0; 383 int level, res;
299 pte_t *kpte; 384 pte_t *kpte;
300 385
301repeat: 386repeat:
@@ -338,13 +423,34 @@ repeat:
338 set_pte_atomic(kpte, new_pte); 423 set_pte_atomic(kpte, new_pte);
339 cpa->flushtlb = 1; 424 cpa->flushtlb = 1;
340 } 425 }
341 } else { 426 cpa->numpages = 1;
342 err = split_large_page(kpte, address); 427 return 0;
343 if (!err)
344 goto repeat;
345 cpa->flushtlb = 1;
346 } 428 }
347 return err; 429
430 /*
431 * Check, whether we can keep the large page intact
432 * and just change the pte:
433 */
434 res = try_preserve_large_page(kpte, address, cpa);
435 if (res < 0)
436 return res;
437
438 /*
439 * When the range fits into the existing large page,
440 * return. cp->numpages and cpa->tlbflush have been updated in
441 * try_large_page:
442 */
443 if (res == CPA_NO_SPLIT)
444 return 0;
445
446 /*
447 * We have to split the large page:
448 */
449 res = split_large_page(kpte, address);
450 if (res)
451 return res;
452 cpa->flushtlb = 1;
453 goto repeat;
348} 454}
349 455
350/** 456/**
@@ -410,15 +516,27 @@ static int change_page_attr_addr(struct cpa_data *cpa)
410 516
411static int __change_page_attr_set_clr(struct cpa_data *cpa) 517static int __change_page_attr_set_clr(struct cpa_data *cpa)
412{ 518{
413 unsigned int i; 519 int ret, numpages = cpa->numpages;
414 int ret;
415 520
416 for (i = 0; i < cpa->numpages ; i++, cpa->vaddr += PAGE_SIZE) { 521 while (numpages) {
522 /*
523 * Store the remaining nr of pages for the large page
524 * preservation check.
525 */
526 cpa->numpages = numpages;
417 ret = change_page_attr_addr(cpa); 527 ret = change_page_attr_addr(cpa);
418 if (ret) 528 if (ret)
419 return ret; 529 return ret;
420 }
421 530
531 /*
532 * Adjust the number of pages with the result of the
533 * CPA operation. Either a large page has been
534 * preserved or a single page update happened.
535 */
536 BUG_ON(cpa->numpages > numpages);
537 numpages -= cpa->numpages;
538 cpa->vaddr += cpa->numpages * PAGE_SIZE;
539 }
422 return 0; 540 return 0;
423} 541}
424 542