diff options
| author | Thomas Gleixner <tglx@linutronix.de> | 2008-02-04 10:48:07 -0500 |
|---|---|---|
| committer | Ingo Molnar <mingo@elte.hu> | 2008-02-04 10:48:07 -0500 |
| commit | 65e074dffa198978ab0c9976a19b954fbe1183e2 (patch) | |
| tree | a50807444598c6d7a64ed75af23f9a246ee40d86 | |
| parent | f4ae5da0e8e92caa168e7c2a7c4a6c4064b082c2 (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.c | 142 |
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 | ||
| 19 | struct cpa_data { | 19 | struct 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 | ||
| 27 | enum { | ||
| 28 | CPA_NO_SPLIT = 0, | ||
| 29 | CPA_SPLIT, | ||
| 30 | }; | ||
| 31 | |||
| 27 | static inline int | 32 | static inline int |
| 28 | within(unsigned long addr, unsigned long start, unsigned long end) | 33 | within(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 | ||
| 238 | static 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 | |||
| 313 | out_unlock: | ||
| 314 | spin_unlock_irqrestore(&pgd_lock, flags); | ||
| 315 | return res; | ||
| 316 | } | ||
| 317 | |||
| 233 | static int split_large_page(pte_t *kpte, unsigned long address) | 318 | static 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: | |||
| 295 | static int __change_page_attr(unsigned long address, struct cpa_data *cpa) | 380 | static 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 | ||
| 301 | repeat: | 386 | repeat: |
| @@ -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 | ||
| 411 | static int __change_page_attr_set_clr(struct cpa_data *cpa) | 517 | static 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 | ||
