pageattr_64.c 5.2 KB
Newer Older
1 2
/*
 * Copyright 2002 Andi Kleen, SuSE Labs.
L
Linus Torvalds 已提交
3
 * Thanks to Ben LaHaise for precious feedback.
4
 */
L
Linus Torvalds 已提交
5 6 7

#include <linux/highmem.h>
#include <linux/module.h>
8
#include <linux/sched.h>
L
Linus Torvalds 已提交
9
#include <linux/slab.h>
10 11
#include <linux/mm.h>

I
Ingo Molnar 已提交
12 13 14 15 16 17 18 19
void clflush_cache_range(void *addr, int size)
{
	int i;

	for (i = 0; i < size; i += boot_cpu_data.x86_clflush_size)
		clflush(addr+i);
}

L
Linus Torvalds 已提交
20 21
#include <asm/processor.h>
#include <asm/tlbflush.h>
22
#include <asm/uaccess.h>
L
Linus Torvalds 已提交
23 24
#include <asm/io.h>

25
pte_t *lookup_address(unsigned long address, int *level)
26
{
L
Linus Torvalds 已提交
27 28 29
	pgd_t *pgd = pgd_offset_k(address);
	pud_t *pud;
	pmd_t *pmd;
30

L
Linus Torvalds 已提交
31 32 33
	if (pgd_none(*pgd))
		return NULL;
	pud = pud_offset(pgd, address);
34
	if (pud_none(*pud))
35
		return NULL;
L
Linus Torvalds 已提交
36
	pmd = pmd_offset(pud, address);
37
	if (pmd_none(*pmd))
38
		return NULL;
39
	*level = 3;
L
Linus Torvalds 已提交
40 41
	if (pmd_large(*pmd))
		return (pte_t *)pmd;
42
	*level = 4;
43

44
	return pte_offset_kernel(pmd, address);
45
}
L
Linus Torvalds 已提交
46

47 48 49
static struct page *
split_large_page(unsigned long address, pgprot_t prot, pgprot_t ref_prot)
{
L
Linus Torvalds 已提交
50
	unsigned long addr;
51
	struct page *base;
L
Linus Torvalds 已提交
52
	pte_t *pbase;
53 54 55 56
	int i;

	base = alloc_pages(GFP_KERNEL, 0);
	if (!base)
L
Linus Torvalds 已提交
57
		return NULL;
58

59
	address = __pa(address);
60
	addr = address & LARGE_PAGE_MASK;
L
Linus Torvalds 已提交
61 62
	pbase = (pte_t *)page_address(base);
	for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE) {
63
		pbase[i] = pfn_pte(addr >> PAGE_SHIFT,
L
Linus Torvalds 已提交
64 65 66
				   addr == address ? prot : ref_prot);
	}
	return base;
67
}
L
Linus Torvalds 已提交
68 69 70

static int
__change_page_attr(unsigned long address, unsigned long pfn, pgprot_t prot,
71 72
		   pgprot_t ref_prot)
{
L
Linus Torvalds 已提交
73
	struct page *kpte_page;
74
	pte_t *kpte;
I
Ingo Molnar 已提交
75
	pgprot_t ref_prot2, oldprot;
76
	int level;
77

78
	kpte = lookup_address(address, &level);
79 80 81
	if (!kpte)
		return 0;

82
	kpte_page = virt_to_page(kpte);
I
Ingo Molnar 已提交
83
	oldprot = pte_pgprot(*kpte);
84 85
	BUG_ON(PageLRU(kpte_page));
	BUG_ON(PageCompound(kpte_page));
I
Ingo Molnar 已提交
86 87 88
	ref_prot = canon_pgprot(ref_prot);
	prot = canon_pgprot(prot);

89
	if (pgprot_val(prot) != pgprot_val(ref_prot)) {
90
		if (level == 4) {
L
Linus Torvalds 已提交
91 92
			set_pte(kpte, pfn_pte(pfn, prot));
		} else {
93
			/*
94 95
			 * split_large_page will take the reference for this
			 * change_page_attr on the split page.
96
			 */
97
			struct page *split;
98

99
			ref_prot2 = pte_pgprot(pte_clrhuge(*kpte));
100
			split = split_large_page(address, prot, ref_prot2);
L
Linus Torvalds 已提交
101 102
			if (!split)
				return -ENOMEM;
103
			pgprot_val(ref_prot2) &= ~_PAGE_NX;
104
			set_pte(kpte, mk_pte(split, ref_prot2));
L
Linus Torvalds 已提交
105
			kpte_page = split;
106
		}
107
	} else {
108
		if (level == 4) {
109 110 111 112
			set_pte(kpte, pfn_pte(pfn, ref_prot));
		} else
			BUG();
	}
L
Linus Torvalds 已提交
113 114

	return 0;
115
}
L
Linus Torvalds 已提交
116

I
Ingo Molnar 已提交
117 118 119 120 121
/**
 * change_page_attr_addr - Change page table attributes in linear mapping
 * @address: Virtual address in linear mapping.
 * @numpages: Number of pages to change
 * @prot:    New page table attribute (PAGE_*)
122
 *
I
Ingo Molnar 已提交
123 124 125
 * Change page attributes of a page in the direct mapping. This is a variant
 * of change_page_attr() that also works on memory holes that do not have
 * mem_map entry (pfn_valid() is false).
126
 *
I
Ingo Molnar 已提交
127
 * See change_page_attr() documentation for more details.
L
Linus Torvalds 已提交
128
 */
I
Ingo Molnar 已提交
129

L
Linus Torvalds 已提交
130 131
int change_page_attr_addr(unsigned long address, int numpages, pgprot_t prot)
{
132 133 134 135
	int err = 0, kernel_map = 0, i;

	if (address >= __START_KERNEL_map &&
			address < __START_KERNEL_map + KERNEL_TEXT_SIZE) {
L
Linus Torvalds 已提交
136

137 138 139 140
		address = (unsigned long)__va(__pa(address));
		kernel_map = 1;
	}

L
Linus Torvalds 已提交
141 142 143 144
	down_write(&init_mm.mmap_sem);
	for (i = 0; i < numpages; i++, address += PAGE_SIZE) {
		unsigned long pfn = __pa(address) >> PAGE_SHIFT;

145
		if (!kernel_map || pte_present(pfn_pte(0, prot))) {
146 147
			err = __change_page_attr(address, pfn, prot,
						PAGE_KERNEL);
148 149 150
			if (err)
				break;
		}
L
Linus Torvalds 已提交
151 152
		/* Handle kernel mapping too which aliases part of the
		 * lowmem */
153
		if (__pa(address) < KERNEL_TEXT_SIZE) {
L
Linus Torvalds 已提交
154
			unsigned long addr2;
155
			pgprot_t prot2;
156

157
			addr2 = __START_KERNEL_map + __pa(address);
158 159 160 161
			/* Make sure the kernel mappings stay executable */
			prot2 = pte_pgprot(pte_mkexec(pfn_pte(0, prot)));
			err = __change_page_attr(addr2, pfn, prot2,
						 PAGE_KERNEL_EXEC);
162 163 164 165
		}
	}
	up_write(&init_mm.mmap_sem);

L
Linus Torvalds 已提交
166 167 168
	return err;
}

I
Ingo Molnar 已提交
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
/**
 * change_page_attr - Change page table attributes in the linear mapping.
 * @page: First page to change
 * @numpages: Number of pages to change
 * @prot: New protection/caching type (PAGE_*)
 *
 * Returns 0 on success, otherwise a negated errno.
 *
 * This should be used when a page is mapped with a different caching policy
 * than write-back somewhere - some CPUs do not like it when mappings with
 * different caching policies exist. This changes the page attributes of the
 * in kernel linear mapping too.
 *
 * Caller must call global_flush_tlb() later to make the changes active.
 *
 * The caller needs to ensure that there are no conflicting mappings elsewhere
 * (e.g. in user space) * This function only deals with the kernel linear map.
 *
 * For MMIO areas without mem_map use change_page_attr_addr() instead.
 */
L
Linus Torvalds 已提交
189 190 191
int change_page_attr(struct page *page, int numpages, pgprot_t prot)
{
	unsigned long addr = (unsigned long)page_address(page);
192

L
Linus Torvalds 已提交
193 194
	return change_page_attr_addr(addr, numpages, prot);
}
195
EXPORT_SYMBOL(change_page_attr);
L
Linus Torvalds 已提交
196

I
Ingo Molnar 已提交
197
static void flush_kernel_map(void *arg)
198
{
I
Ingo Molnar 已提交
199
	/*
I
Ingo Molnar 已提交
200 201
	 * Flush all to work around Errata in early athlons regarding
	 * large page flushing.
I
Ingo Molnar 已提交
202
	 */
I
Ingo Molnar 已提交
203
	__flush_tlb_all();
204

I
Ingo Molnar 已提交
205 206 207
	if (boot_cpu_data.x86_model >= 4)
		wbinvd();
}
208

I
Ingo Molnar 已提交
209 210 211 212 213
void global_flush_tlb(void)
{
	BUG_ON(irqs_disabled());

	on_each_cpu(flush_kernel_map, NULL, 1, 1);
214
}
L
Linus Torvalds 已提交
215
EXPORT_SYMBOL(global_flush_tlb);