aboutsummaryrefslogtreecommitdiffstats
path: root/xenolinux-2.4.21-pre4-sparse/arch/xeno/mm/pageattr.c
blob: 8d3b3e38567a494f1044550c2bb6d126ca5eb8a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/* 
 * Copyright 2002 Andi Kleen, SuSE Labs. 
 * Thanks to Ben LaHaise for precious feedback.
 */ 

#include <linux/config.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/highmem.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <asm/processor.h>

/* Should move most of this stuff into the appropiate includes */
#define LARGE_PAGE_MASK (~(LARGE_PAGE_SIZE-1))
#define LARGE_PAGE_SIZE (1UL << PMD_SHIFT)

static inline pte_t *lookup_address(unsigned long address) 
{ 
	pmd_t *pmd;	
	pgd_t *pgd = pgd_offset(&init_mm, address); 

	if (pgd_none(*pgd))
		return NULL; 
	if (pgd_val(*pgd) & _PAGE_PSE)
		return (pte_t *)pgd; 
	pmd = pmd_offset(pgd, address); 	       
	if (pmd_none(*pmd))
		return NULL; 
	if (pmd_val(*pmd) & _PAGE_PSE) 
		return (pte_t *)pmd; 
    return pte_offset(pmd, address);
} 

static struct page *split_large_page(unsigned long address, pgprot_t prot)
{ 
	int i; 
	unsigned long addr;
	struct page *base = alloc_pages(GFP_KERNEL, 0);
	pte_t *pbase;
	if (!base) 
		return NULL;
	address = __pa(address);
	addr = address & LARGE_PAGE_MASK; 
	pbase = (pte_t *)page_address(base);
	for (i = 0; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE) {
		pbase[i] = mk_pte_phys(addr, 
				      addr == address ? prot : PAGE_KERNEL);
	}
	return base;
} 

static void flush_kernel_map(void * address) 
{ 
	if (!test_bit(X86_FEATURE_SELFSNOOP, boot_cpu_data.x86_capability)) {
		/* Could use CLFLUSH here if the CPU supports it (Hammer,P4) */
		if (boot_cpu_data.x86_model >= 4) 
			asm volatile("wbinvd":::"memory"); 	
	} 

	/* Do global flush here to work around large page flushing errata 
	   in some early Athlons */
	__flush_tlb_all(); 	
}

static void set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) 
{ 
	set_pte_atomic(kpte, pte); 	/* change init_mm */
#ifndef CONFIG_X86_PAE
	{
		struct list_head *l;
		spin_lock(&mmlist_lock);
		list_for_each(l, &init_mm.mmlist) { 
			struct mm_struct *mm = list_entry(l, struct mm_struct, mmlist);
			pmd_t *pmd = pmd_offset(pgd_offset(mm, address), address);
			set_pte_atomic((pte_t *)pmd, pte);
		} 
		spin_unlock(&mmlist_lock);
	}
#endif
}

/* no more special protections in this 2/4MB area - revert to a
   large page again. */
static inline void revert_page(struct page *kpte_page, unsigned long address)
{
	pte_t *linear = (pte_t *) 
		pmd_offset(pgd_offset(&init_mm, address), address);
	set_pmd_pte(linear,  address,
		mk_pte_phys(__pa(address & LARGE_PAGE_MASK),
			    MAKE_GLOBAL(_KERNPG_TABLE|_PAGE_PSE)));
}	
 
/*
 * Change the page attributes of an page in the linear mapping.
 *
 * 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.
 * 
 * The caller needs to ensure that there are no conflicting mappings elsewhere.
 * This function only deals with the kernel linear map.
 * When page is in highmem it must never be kmap'ed.
 */
static int 
__change_page_attr(struct page *page, pgprot_t prot, struct page **oldpage) 
{ 
	pte_t *kpte; 
	unsigned long address;
	struct page *kpte_page;

#ifdef CONFIG_HIGHMEM
	if (page >= highmem_start_page) 
		BUG(); 
#endif
	address = (unsigned long)page_address(page);
	kpte = lookup_address(address);
	if (!kpte) 
		return -EINVAL; 
	kpte_page = virt_to_page(((unsigned long)kpte) & PAGE_MASK);
	if (pgprot_val(prot) != pgprot_val(PAGE_KERNEL)) { 
		if ((pte_val(*kpte) & _PAGE_PSE) == 0) {
			pte_t old = *kpte;
			pte_t standard = mk_pte(page, PAGE_KERNEL); 

			set_pte_atomic(kpte, mk_pte(page, prot)); 
			if (pte_same(old,standard))
				atomic_inc(&kpte_page->count);
		} else {
			struct page *split = split_large_page(address, prot); 
			if (!split)
				return -ENOMEM;
			atomic_inc(&kpte_page->count); 	
			set_pmd_pte(kpte,address,mk_pte(split, PAGE_KERNEL));
		}	
	} else if ((pte_val(*kpte) & _PAGE_PSE) == 0) { 
		set_pte_atomic(kpte, mk_pte(page, PAGE_KERNEL));
		atomic_dec(&kpte_page->count); 
	}

	if (cpu_has_pse && (atomic_read(&kpte_page->count) == 1)) { 
		*oldpage = kpte_page;
		revert_page(kpte_page, address);
	} 
	return 0;
} 

int change_page_attr(struct page *page, int numpages, pgprot_t prot)
{
	int err = 0; 
	struct page *fpage; 
	int i; 

	down_write(&init_mm.mmap_sem);
	for (i = 0; i < numpages; i++, page++) { 
		fpage = NULL;
		err = __change_page_attr(page, prot, &fpage); 
		if (err) 
			break; 
		if (fpage || i == numpages-1) { 
			void *address = page_address(page);
#ifdef CONFIG_SMP 
			smp_call_function(flush_kernel_map, address, 1, 1);
#endif	
			flush_kernel_map(address);
			if (fpage)
				__free_page(fpage);
		} 
	} 	
	up_write(&init_mm.mmap_sem); 
	return err;
}

EXPORT_SYMBOL(change_page_attr);