diff options
author | Ingo Molnar <mingo@elte.hu> | 2008-01-30 07:34:03 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-01-30 07:34:03 -0500 |
commit | 6faa4c53b2f06fd271060761ce27f4f53289175c (patch) | |
tree | a10304d5ed232f9ad862dee6350cdfea5f4c9076 /arch | |
parent | 44136717e06b888c2d3129a80abf9ac79233f1eb (diff) |
x86: 64-bit, add the new split_large_page() function
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/x86/mm/pageattr_64.c | 95 |
1 files changed, 71 insertions, 24 deletions
diff --git a/arch/x86/mm/pageattr_64.c b/arch/x86/mm/pageattr_64.c index 190d4d37bc2f..139795029fb6 100644 --- a/arch/x86/mm/pageattr_64.c +++ b/arch/x86/mm/pageattr_64.c | |||
@@ -45,34 +45,91 @@ pte_t *lookup_address(unsigned long address, int *level) | |||
45 | return pte_offset_kernel(pmd, address); | 45 | return pte_offset_kernel(pmd, address); |
46 | } | 46 | } |
47 | 47 | ||
48 | static struct page * | 48 | static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) |
49 | split_large_page(unsigned long address, pgprot_t ref_prot) | ||
50 | { | 49 | { |
50 | /* change init_mm */ | ||
51 | set_pte_atomic(kpte, pte); | ||
52 | #ifdef CONFIG_X86_32 | ||
53 | if (SHARED_KERNEL_PMD) | ||
54 | return; | ||
55 | { | ||
56 | struct page *page; | ||
57 | |||
58 | for (page = pgd_list; page; page = (struct page *)page->index) { | ||
59 | pgd_t *pgd; | ||
60 | pud_t *pud; | ||
61 | pmd_t *pmd; | ||
62 | |||
63 | pgd = (pgd_t *)page_address(page) + pgd_index(address); | ||
64 | pud = pud_offset(pgd, address); | ||
65 | pmd = pmd_offset(pud, address); | ||
66 | set_pte_atomic((pte_t *)pmd, pte); | ||
67 | } | ||
68 | } | ||
69 | #endif | ||
70 | } | ||
71 | |||
72 | static int split_large_page(pte_t *kpte, unsigned long address) | ||
73 | { | ||
74 | pgprot_t ref_prot = pte_pgprot(pte_clrhuge(*kpte)); | ||
75 | gfp_t gfp_flags = GFP_KERNEL; | ||
76 | unsigned long flags; | ||
51 | unsigned long addr; | 77 | unsigned long addr; |
78 | pte_t *pbase, *tmp; | ||
52 | struct page *base; | 79 | struct page *base; |
53 | pte_t *pbase; | 80 | int i, level; |
54 | int i; | 81 | |
55 | 82 | ||
56 | base = alloc_pages(GFP_KERNEL, 0); | 83 | #ifdef CONFIG_DEBUG_PAGEALLOC |
84 | gfp_flags = GFP_ATOMIC; | ||
85 | #endif | ||
86 | base = alloc_pages(gfp_flags, 0); | ||
57 | if (!base) | 87 | if (!base) |
58 | return NULL; | 88 | return -ENOMEM; |
89 | |||
90 | spin_lock_irqsave(&pgd_lock, flags); | ||
91 | /* | ||
92 | * Check for races, another CPU might have split this page | ||
93 | * up for us already: | ||
94 | */ | ||
95 | tmp = lookup_address(address, &level); | ||
96 | if (tmp != kpte) { | ||
97 | WARN_ON_ONCE(1); | ||
98 | goto out_unlock; | ||
99 | } | ||
59 | 100 | ||
60 | address = __pa(address); | 101 | address = __pa(address); |
61 | addr = address & LARGE_PAGE_MASK; | 102 | addr = address & LARGE_PAGE_MASK; |
62 | pbase = (pte_t *)page_address(base); | 103 | pbase = (pte_t *)page_address(base); |
104 | #ifdef CONFIG_X86_32 | ||
105 | paravirt_alloc_pt(&init_mm, page_to_pfn(base)); | ||
106 | #endif | ||
107 | |||
63 | for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE) | 108 | for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE) |
64 | pbase[i] = pfn_pte(addr >> PAGE_SHIFT, ref_prot); | 109 | set_pte(&pbase[i], pfn_pte(addr >> PAGE_SHIFT, ref_prot)); |
110 | |||
111 | /* | ||
112 | * Install the new, split up pagetable: | ||
113 | */ | ||
114 | __set_pmd_pte(kpte, address, mk_pte(base, ref_prot)); | ||
115 | base = NULL; | ||
116 | |||
117 | out_unlock: | ||
118 | spin_unlock_irqrestore(&pgd_lock, flags); | ||
119 | |||
120 | if (base) | ||
121 | __free_pages(base, 0); | ||
65 | 122 | ||
66 | return base; | 123 | return 0; |
67 | } | 124 | } |
68 | 125 | ||
69 | static int | 126 | static int |
70 | __change_page_attr(unsigned long address, struct page *page, pgprot_t prot) | 127 | __change_page_attr(unsigned long address, struct page *page, pgprot_t prot) |
71 | { | 128 | { |
129 | pgprot_t ref_prot2, oldprot; | ||
72 | struct page *kpte_page; | 130 | struct page *kpte_page; |
131 | int level, err = 0; | ||
73 | pte_t *kpte; | 132 | pte_t *kpte; |
74 | pgprot_t ref_prot2, oldprot; | ||
75 | int level; | ||
76 | 133 | ||
77 | repeat: | 134 | repeat: |
78 | kpte = lookup_address(address, &level); | 135 | kpte = lookup_address(address, &level); |
@@ -88,22 +145,12 @@ repeat: | |||
88 | if (level == 4) { | 145 | if (level == 4) { |
89 | set_pte_atomic(kpte, mk_pte(page, prot)); | 146 | set_pte_atomic(kpte, mk_pte(page, prot)); |
90 | } else { | 147 | } else { |
91 | /* | 148 | err = split_large_page(kpte, address); |
92 | * split_large_page will take the reference for this | 149 | if (!err) |
93 | * change_page_attr on the split page. | 150 | goto repeat; |
94 | */ | ||
95 | struct page *split; | ||
96 | |||
97 | ref_prot2 = pte_pgprot(pte_clrhuge(*kpte)); | ||
98 | split = split_large_page(address, ref_prot2); | ||
99 | if (!split) | ||
100 | return -ENOMEM; | ||
101 | pgprot_val(ref_prot2) &= ~_PAGE_NX; | ||
102 | set_pte_atomic(kpte, mk_pte(split, ref_prot2)); | ||
103 | goto repeat; | ||
104 | } | 151 | } |
105 | 152 | ||
106 | return 0; | 153 | return err; |
107 | } | 154 | } |
108 | 155 | ||
109 | /** | 156 | /** |