diff options
-rw-r--r-- | arch/x86/mm/pageattr_32.c | 89 |
1 files changed, 49 insertions, 40 deletions
diff --git a/arch/x86/mm/pageattr_32.c b/arch/x86/mm/pageattr_32.c index 1011b21f8db0..14c923b3b07f 100644 --- a/arch/x86/mm/pageattr_32.c +++ b/arch/x86/mm/pageattr_32.c | |||
@@ -37,33 +37,6 @@ pte_t *lookup_address(unsigned long address, int *level) | |||
37 | return pte_offset_kernel(pmd, address); | 37 | return pte_offset_kernel(pmd, address); |
38 | } | 38 | } |
39 | 39 | ||
40 | static struct page * | ||
41 | split_large_page(unsigned long address, pgprot_t ref_prot) | ||
42 | { | ||
43 | unsigned long addr; | ||
44 | struct page *base; | ||
45 | pte_t *pbase; | ||
46 | int i; | ||
47 | |||
48 | base = alloc_pages(GFP_KERNEL, 0); | ||
49 | if (!base) | ||
50 | return NULL; | ||
51 | |||
52 | /* | ||
53 | * page_private is used to track the number of entries in | ||
54 | * the page table page that have non standard attributes. | ||
55 | */ | ||
56 | address = __pa(address); | ||
57 | addr = address & LARGE_PAGE_MASK; | ||
58 | pbase = (pte_t *)page_address(base); | ||
59 | paravirt_alloc_pt(&init_mm, page_to_pfn(base)); | ||
60 | |||
61 | for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE) | ||
62 | set_pte(&pbase[i], pfn_pte(addr >> PAGE_SHIFT, ref_prot)); | ||
63 | |||
64 | return base; | ||
65 | } | ||
66 | |||
67 | static void set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) | 40 | static void set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) |
68 | { | 41 | { |
69 | unsigned long flags; | 42 | unsigned long flags; |
@@ -88,14 +61,58 @@ static void set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) | |||
88 | spin_unlock_irqrestore(&pgd_lock, flags); | 61 | spin_unlock_irqrestore(&pgd_lock, flags); |
89 | } | 62 | } |
90 | 63 | ||
64 | static int | ||
65 | split_large_page(pte_t *kpte, unsigned long address, pgprot_t ref_prot) | ||
66 | { | ||
67 | int i, level; | ||
68 | unsigned long addr; | ||
69 | pte_t *pbase, *tmp; | ||
70 | struct page *base; | ||
71 | |||
72 | base = alloc_pages(GFP_KERNEL, 0); | ||
73 | if (!base) | ||
74 | return -ENOMEM; | ||
75 | |||
76 | down_write(&init_mm.mmap_sem); | ||
77 | /* | ||
78 | * Check for races, another CPU might have split this page | ||
79 | * up for us already: | ||
80 | */ | ||
81 | tmp = lookup_address(address, &level); | ||
82 | if (tmp != kpte) | ||
83 | goto out_unlock; | ||
84 | |||
85 | address = __pa(address); | ||
86 | addr = address & LARGE_PAGE_MASK; | ||
87 | pbase = (pte_t *)page_address(base); | ||
88 | paravirt_alloc_pt(&init_mm, page_to_pfn(base)); | ||
89 | |||
90 | for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE) | ||
91 | set_pte(&pbase[i], pfn_pte(addr >> PAGE_SHIFT, ref_prot)); | ||
92 | |||
93 | /* | ||
94 | * Install the new, split up pagetable: | ||
95 | */ | ||
96 | set_pmd_pte(kpte, address, mk_pte(base, ref_prot)); | ||
97 | base = NULL; | ||
98 | |||
99 | out_unlock: | ||
100 | up_write(&init_mm.mmap_sem); | ||
101 | |||
102 | if (base) | ||
103 | __free_pages(base, 0); | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
91 | static int __change_page_attr(struct page *page, pgprot_t prot) | 108 | static int __change_page_attr(struct page *page, pgprot_t prot) |
92 | { | 109 | { |
93 | pgprot_t ref_prot = PAGE_KERNEL; | 110 | pgprot_t ref_prot = PAGE_KERNEL; |
94 | struct page *kpte_page; | 111 | struct page *kpte_page; |
95 | unsigned long address; | 112 | unsigned long address; |
113 | int level, err = 0; | ||
96 | pgprot_t oldprot; | 114 | pgprot_t oldprot; |
97 | pte_t *kpte; | 115 | pte_t *kpte; |
98 | int level; | ||
99 | 116 | ||
100 | BUG_ON(PageHighMem(page)); | 117 | BUG_ON(PageHighMem(page)); |
101 | address = (unsigned long)page_address(page); | 118 | address = (unsigned long)page_address(page); |
@@ -127,19 +144,11 @@ repeat: | |||
127 | if (level == 3) { | 144 | if (level == 3) { |
128 | set_pte_atomic(kpte, mk_pte(page, prot)); | 145 | set_pte_atomic(kpte, mk_pte(page, prot)); |
129 | } else { | 146 | } else { |
130 | struct page *split; | 147 | err = split_large_page(kpte, address, ref_prot); |
131 | 148 | if (!err) | |
132 | split = split_large_page(address, ref_prot); | 149 | goto repeat; |
133 | if (!split) | ||
134 | return -ENOMEM; | ||
135 | |||
136 | /* | ||
137 | * There's a small window here to waste a bit of RAM: | ||
138 | */ | ||
139 | set_pmd_pte(kpte, address, mk_pte(split, ref_prot)); | ||
140 | goto repeat; | ||
141 | } | 150 | } |
142 | return 0; | 151 | return err; |
143 | } | 152 | } |
144 | 153 | ||
145 | /* | 154 | /* |