blob: 107b03c7113c8daae8c0f7ee6ea0d54c168ce82e [file] [log] [blame]
Ingo Molnarb4416a12008-01-30 13:33:41 +01001/*
2 * Copyright 2002 Andi Kleen, SuSE Labs.
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 * Thanks to Ben LaHaise for precious feedback.
Ingo Molnarb4416a12008-01-30 13:33:41 +01004 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07005
Linus Torvalds1da177e2005-04-16 15:20:36 -07006#include <linux/highmem.h>
7#include <linux/module.h>
Ingo Molnarb4416a12008-01-30 13:33:41 +01008#include <linux/sched.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -07009#include <linux/slab.h>
Ingo Molnarb4416a12008-01-30 13:33:41 +010010#include <linux/mm.h>
11
Ingo Molnard6ee09a2008-01-30 13:33:59 +010012void clflush_cache_range(void *addr, int size)
13{
14 int i;
15
16 for (i = 0; i < size; i += boot_cpu_data.x86_clflush_size)
17 clflush(addr+i);
18}
19
Linus Torvalds1da177e2005-04-16 15:20:36 -070020#include <asm/processor.h>
21#include <asm/tlbflush.h>
Ingo Molnar5e5224a2008-01-30 13:34:00 +010022#include <asm/sections.h>
Ingo Molnarb4416a12008-01-30 13:33:41 +010023#include <asm/uaccess.h>
Ingo Molnar5e5224a2008-01-30 13:34:00 +010024#include <asm/pgalloc.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070025
Ingo Molnarf0646e42008-01-30 13:33:43 +010026pte_t *lookup_address(unsigned long address, int *level)
Ingo Molnarb4416a12008-01-30 13:33:41 +010027{
Linus Torvalds1da177e2005-04-16 15:20:36 -070028 pgd_t *pgd = pgd_offset_k(address);
29 pud_t *pud;
30 pmd_t *pmd;
Ingo Molnarb4416a12008-01-30 13:33:41 +010031
Linus Torvalds1da177e2005-04-16 15:20:36 -070032 if (pgd_none(*pgd))
33 return NULL;
34 pud = pud_offset(pgd, address);
Ingo Molnara5f55032008-01-30 13:33:59 +010035 if (pud_none(*pud))
Ingo Molnarb4416a12008-01-30 13:33:41 +010036 return NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -070037 pmd = pmd_offset(pud, address);
Ingo Molnara5f55032008-01-30 13:33:59 +010038 if (pmd_none(*pmd))
Ingo Molnarb4416a12008-01-30 13:33:41 +010039 return NULL;
Ingo Molnarf0646e42008-01-30 13:33:43 +010040 *level = 3;
Linus Torvalds1da177e2005-04-16 15:20:36 -070041 if (pmd_large(*pmd))
42 return (pte_t *)pmd;
Ingo Molnarf0646e42008-01-30 13:33:43 +010043 *level = 4;
Ingo Molnarb4416a12008-01-30 13:33:41 +010044
Ingo Molnara5f55032008-01-30 13:33:59 +010045 return pte_offset_kernel(pmd, address);
Ingo Molnarb4416a12008-01-30 13:33:41 +010046}
47
48static struct page *
49split_large_page(unsigned long address, pgprot_t prot, pgprot_t ref_prot)
50{
Linus Torvalds1da177e2005-04-16 15:20:36 -070051 unsigned long addr;
Ingo Molnarb4416a12008-01-30 13:33:41 +010052 struct page *base;
Linus Torvalds1da177e2005-04-16 15:20:36 -070053 pte_t *pbase;
Ingo Molnarb4416a12008-01-30 13:33:41 +010054 int i;
55
56 base = alloc_pages(GFP_KERNEL, 0);
57 if (!base)
Linus Torvalds1da177e2005-04-16 15:20:36 -070058 return NULL;
Nick Piggin4fa4f532006-03-22 00:08:33 -080059
Linus Torvaldse3ebadd2007-05-07 08:44:24 -070060 address = __pa(address);
Ingo Molnarb4416a12008-01-30 13:33:41 +010061 addr = address & LARGE_PAGE_MASK;
Linus Torvalds1da177e2005-04-16 15:20:36 -070062 pbase = (pte_t *)page_address(base);
63 for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE) {
Ingo Molnarb4416a12008-01-30 13:33:41 +010064 pbase[i] = pfn_pte(addr >> PAGE_SHIFT,
Linus Torvalds1da177e2005-04-16 15:20:36 -070065 addr == address ? prot : ref_prot);
66 }
67 return base;
Ingo Molnarb4416a12008-01-30 13:33:41 +010068}
Linus Torvalds1da177e2005-04-16 15:20:36 -070069
Linus Torvalds1da177e2005-04-16 15:20:36 -070070static int
71__change_page_attr(unsigned long address, unsigned long pfn, pgprot_t prot,
Ingo Molnarb4416a12008-01-30 13:33:41 +010072 pgprot_t ref_prot)
73{
Linus Torvalds1da177e2005-04-16 15:20:36 -070074 struct page *kpte_page;
Ingo Molnarb4416a12008-01-30 13:33:41 +010075 pte_t *kpte;
Ingo Molnard6ee09a2008-01-30 13:33:59 +010076 pgprot_t ref_prot2, oldprot;
Ingo Molnarf0646e42008-01-30 13:33:43 +010077 int level;
Andi Kleen65d2f0b2007-07-21 17:09:51 +020078
Ingo Molnarf0646e42008-01-30 13:33:43 +010079 kpte = lookup_address(address, &level);
Ingo Molnarb4416a12008-01-30 13:33:41 +010080 if (!kpte)
81 return 0;
82
Andi Kleena3ae91b2008-01-30 13:33:50 +010083 kpte_page = virt_to_page(kpte);
Ingo Molnard6ee09a2008-01-30 13:33:59 +010084 oldprot = pte_pgprot(*kpte);
Andi Kleen65d2f0b2007-07-21 17:09:51 +020085 BUG_ON(PageLRU(kpte_page));
86 BUG_ON(PageCompound(kpte_page));
Ingo Molnard6ee09a2008-01-30 13:33:59 +010087 ref_prot = canon_pgprot(ref_prot);
88 prot = canon_pgprot(prot);
89
Ingo Molnarb4416a12008-01-30 13:33:41 +010090 if (pgprot_val(prot) != pgprot_val(ref_prot)) {
Andi Kleen895bdc22008-01-30 13:33:52 +010091 if (level == 4) {
Linus Torvalds1da177e2005-04-16 15:20:36 -070092 set_pte(kpte, pfn_pte(pfn, prot));
93 } else {
Ingo Molnarb4416a12008-01-30 13:33:41 +010094 /*
Nick Piggin4fa4f532006-03-22 00:08:33 -080095 * split_large_page will take the reference for this
96 * change_page_attr on the split page.
Ingo Molnarb4416a12008-01-30 13:33:41 +010097 */
Arjan van de Venc7282522006-01-06 00:12:03 -080098 struct page *split;
Ingo Molnarb4416a12008-01-30 13:33:41 +010099
Andi Kleen5e6b0bf2006-09-26 10:52:37 +0200100 ref_prot2 = pte_pgprot(pte_clrhuge(*kpte));
Linus Torvaldse3ebadd2007-05-07 08:44:24 -0700101 split = split_large_page(address, prot, ref_prot2);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700102 if (!split)
103 return -ENOMEM;
Huang, Ying84e0fdb2007-10-17 18:04:35 +0200104 pgprot_val(ref_prot2) &= ~_PAGE_NX;
Andi Kleen5e6b0bf2006-09-26 10:52:37 +0200105 set_pte(kpte, mk_pte(split, ref_prot2));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700106 kpte_page = split;
Andi Kleen5e6b0bf2006-09-26 10:52:37 +0200107 }
Ingo Molnarb4416a12008-01-30 13:33:41 +0100108 } else {
Andi Kleen895bdc22008-01-30 13:33:52 +0100109 if (level == 4) {
Ingo Molnarb4416a12008-01-30 13:33:41 +0100110 set_pte(kpte, pfn_pte(pfn, ref_prot));
Ingo Molnarb4416a12008-01-30 13:33:41 +0100111 } else
112 BUG();
113 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700114
Linus Torvalds1da177e2005-04-16 15:20:36 -0700115 return 0;
Ingo Molnarb4416a12008-01-30 13:33:41 +0100116}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100118/**
119 * change_page_attr_addr - Change page table attributes in linear mapping
120 * @address: Virtual address in linear mapping.
121 * @numpages: Number of pages to change
122 * @prot: New page table attribute (PAGE_*)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700123 *
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100124 * Change page attributes of a page in the direct mapping. This is a variant
125 * of change_page_attr() that also works on memory holes that do not have
126 * mem_map entry (pfn_valid() is false).
Ingo Molnarb4416a12008-01-30 13:33:41 +0100127 *
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100128 * See change_page_attr() documentation for more details.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700129 */
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100130
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131int change_page_attr_addr(unsigned long address, int numpages, pgprot_t prot)
132{
Ingo Molnarb4416a12008-01-30 13:33:41 +0100133 int err = 0, kernel_map = 0, i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134
Ingo Molnarb4416a12008-01-30 13:33:41 +0100135 if (address >= __START_KERNEL_map &&
136 address < __START_KERNEL_map + KERNEL_TEXT_SIZE) {
137
Jan Beulichd01ad8d2007-05-02 19:27:10 +0200138 address = (unsigned long)__va(__pa(address));
139 kernel_map = 1;
140 }
141
Linus Torvalds1da177e2005-04-16 15:20:36 -0700142 down_write(&init_mm.mmap_sem);
143 for (i = 0; i < numpages; i++, address += PAGE_SIZE) {
144 unsigned long pfn = __pa(address) >> PAGE_SHIFT;
145
Jan Beulichd01ad8d2007-05-02 19:27:10 +0200146 if (!kernel_map || pte_present(pfn_pte(0, prot))) {
Ingo Molnarb4416a12008-01-30 13:33:41 +0100147 err = __change_page_attr(address, pfn, prot,
148 PAGE_KERNEL);
Jan Beulichd01ad8d2007-05-02 19:27:10 +0200149 if (err)
150 break;
151 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700152 /* Handle kernel mapping too which aliases part of the
153 * lowmem */
Linus Torvaldse3ebadd2007-05-07 08:44:24 -0700154 if (__pa(address) < KERNEL_TEXT_SIZE) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700155 unsigned long addr2;
Andi Kleendf9928482006-09-26 10:52:37 +0200156 pgprot_t prot2;
Ingo Molnarb4416a12008-01-30 13:33:41 +0100157
Linus Torvaldse3ebadd2007-05-07 08:44:24 -0700158 addr2 = __START_KERNEL_map + __pa(address);
Andi Kleendf9928482006-09-26 10:52:37 +0200159 /* Make sure the kernel mappings stay executable */
160 prot2 = pte_pgprot(pte_mkexec(pfn_pte(0, prot)));
161 err = __change_page_attr(addr2, pfn, prot2,
162 PAGE_KERNEL_EXEC);
Ingo Molnarb4416a12008-01-30 13:33:41 +0100163 }
164 }
165 up_write(&init_mm.mmap_sem);
166
Linus Torvalds1da177e2005-04-16 15:20:36 -0700167 return err;
168}
169
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100170/**
171 * change_page_attr - Change page table attributes in the linear mapping.
172 * @page: First page to change
173 * @numpages: Number of pages to change
174 * @prot: New protection/caching type (PAGE_*)
175 *
176 * Returns 0 on success, otherwise a negated errno.
177 *
178 * This should be used when a page is mapped with a different caching policy
179 * than write-back somewhere - some CPUs do not like it when mappings with
180 * different caching policies exist. This changes the page attributes of the
181 * in kernel linear mapping too.
182 *
183 * Caller must call global_flush_tlb() later to make the changes active.
184 *
185 * The caller needs to ensure that there are no conflicting mappings elsewhere
186 * (e.g. in user space) * This function only deals with the kernel linear map.
187 *
188 * For MMIO areas without mem_map use change_page_attr_addr() instead.
189 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700190int change_page_attr(struct page *page, int numpages, pgprot_t prot)
191{
192 unsigned long addr = (unsigned long)page_address(page);
Ingo Molnarb4416a12008-01-30 13:33:41 +0100193
Linus Torvalds1da177e2005-04-16 15:20:36 -0700194 return change_page_attr_addr(addr, numpages, prot);
195}
Ingo Molnarb4416a12008-01-30 13:33:41 +0100196EXPORT_SYMBOL(change_page_attr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700197
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100198static void flush_kernel_map(void *arg)
199{
200 /*
201 * Flush all to work around Errata in early athlons regarding
202 * large page flushing.
203 */
204 __flush_tlb_all();
205
206 if (boot_cpu_data.x86_model >= 4)
207 wbinvd();
208}
209
Linus Torvalds1da177e2005-04-16 15:20:36 -0700210void global_flush_tlb(void)
Ingo Molnarb4416a12008-01-30 13:33:41 +0100211{
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100212 BUG_ON(irqs_disabled());
Linus Torvalds1da177e2005-04-16 15:20:36 -0700213
Ingo Molnard6ee09a2008-01-30 13:33:59 +0100214 on_each_cpu(flush_kernel_map, NULL, 1, 1);
Ingo Molnarb4416a12008-01-30 13:33:41 +0100215}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700216EXPORT_SYMBOL(global_flush_tlb);