1
2
3
4
5
6
7#include <linux/stat.h>
8#include <linux/sched.h>
9#include <linux/kernel.h>
10#include <linux/mm.h>
11#include <linux/smp.h>
12#include <linux/smp_lock.h>
13#include <linux/shm.h>
14#include <linux/errno.h>
15#include <linux/mman.h>
16#include <linux/string.h>
17#include <linux/slab.h>
18#include <linux/swap.h>
19
20#include <asm/uaccess.h>
21#include <asm/system.h>
22#include <asm/pgtable.h>
23
24static inline pte_t *get_one_pte(struct mm_struct *mm, unsigned long addr)
25{
26 pgd_t * pgd;
27 pmd_t * pmd;
28 pte_t * pte = NULL;
29
30 pgd = pgd_offset(mm, addr);
31 if (pgd_none(*pgd))
32 goto end;
33 if (pgd_bad(*pgd)) {
34 printk("move_one_page: bad source pgd (%08lx)\n", pgd_val(*pgd));
35 pgd_clear(pgd);
36 goto end;
37 }
38
39 pmd = pmd_offset(pgd, addr);
40 if (pmd_none(*pmd))
41 goto end;
42 if (pmd_bad(*pmd)) {
43 printk("move_one_page: bad source pmd (%08lx)\n", pmd_val(*pmd));
44 pmd_clear(pmd);
45 goto end;
46 }
47
48 pte = pte_offset(pmd, addr);
49 if (pte_none(*pte))
50 pte = NULL;
51end:
52 return pte;
53}
54
55static inline pte_t *alloc_one_pte(struct mm_struct *mm, unsigned long addr)
56{
57 pmd_t * pmd;
58 pte_t * pte = NULL;
59
60 pmd = pmd_alloc(pgd_offset(mm, addr), addr);
61 if (pmd)
62 pte = pte_alloc(pmd, addr);
63 return pte;
64}
65
66static inline int copy_one_pte(pte_t * src, pte_t * dst)
67{
68 int error = 0;
69 pte_t pte = *src;
70
71 if (!pte_none(pte)) {
72 error++;
73 if (dst) {
74 pte_clear(src);
75 set_pte(dst, pte);
76 error--;
77 }
78 }
79 return error;
80}
81
82static int move_one_page(struct mm_struct *mm, unsigned long old_addr, unsigned long new_addr)
83{
84 int error = 0;
85 pte_t * src;
86
87 src = get_one_pte(mm, old_addr);
88 if (src)
89 error = copy_one_pte(src, alloc_one_pte(mm, new_addr));
90 return error;
91}
92
93static int move_page_tables(struct mm_struct * mm,
94 unsigned long new_addr, unsigned long old_addr, unsigned long len)
95{
96 unsigned long offset = len;
97
98 flush_cache_range(mm, old_addr, old_addr + len);
99 flush_tlb_range(mm, old_addr, old_addr + len);
100
101
102
103
104
105
106 while (offset) {
107 offset -= PAGE_SIZE;
108 if (move_one_page(mm, old_addr + offset, new_addr + offset))
109 goto oops_we_failed;
110 }
111 return 0;
112
113
114
115
116
117
118
119
120oops_we_failed:
121 flush_cache_range(mm, new_addr, new_addr + len);
122 while ((offset += PAGE_SIZE) < len)
123 move_one_page(mm, new_addr + offset, old_addr + offset);
124 zap_page_range(mm, new_addr, new_addr + len);
125 flush_tlb_range(mm, new_addr, new_addr + len);
126 return -1;
127}
128
129static inline unsigned long move_vma(struct vm_area_struct * vma,
130 unsigned long addr, unsigned long old_len, unsigned long new_len)
131{
132 struct vm_area_struct * new_vma;
133
134 new_vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
135 if (new_vma) {
136 unsigned long new_addr = get_unmapped_area(addr, new_len);
137
138 if (new_addr && !move_page_tables(current->mm, new_addr, addr, old_len)) {
139 *new_vma = *vma;
140 new_vma->vm_start = new_addr;
141 new_vma->vm_end = new_addr+new_len;
142 new_vma->vm_offset = vma->vm_offset + (addr - vma->vm_start);
143 new_vma->vm_dentry = dget(vma->vm_dentry);
144 if (new_vma->vm_ops && new_vma->vm_ops->open)
145 new_vma->vm_ops->open(new_vma);
146 insert_vm_struct(current->mm, new_vma);
147 merge_segments(current->mm, new_vma->vm_start, new_vma->vm_end);
148 do_munmap(addr, old_len);
149 current->mm->total_vm += new_len >> PAGE_SHIFT;
150 return new_addr;
151 }
152 kmem_cache_free(vm_area_cachep, new_vma);
153 }
154 return -ENOMEM;
155}
156
157
158
159
160
161asmlinkage unsigned long sys_mremap(unsigned long addr,
162 unsigned long old_len, unsigned long new_len,
163 unsigned long flags)
164{
165 struct vm_area_struct *vma;
166 unsigned long ret = -EINVAL;
167
168 lock_kernel();
169 if (addr & ~PAGE_MASK)
170 goto out;
171 old_len = PAGE_ALIGN(old_len);
172 new_len = PAGE_ALIGN(new_len);
173
174
175
176
177
178 ret = addr;
179 if (old_len > new_len) {
180 do_munmap(addr+new_len, old_len - new_len);
181 goto out;
182 }
183
184
185
186
187 ret = -EFAULT;
188 vma = find_vma(current->mm, addr);
189 if (!vma || vma->vm_start > addr)
190 goto out;
191
192 if (old_len > vma->vm_end - addr)
193 goto out;
194 if (vma->vm_flags & VM_LOCKED) {
195 unsigned long locked = current->mm->locked_vm << PAGE_SHIFT;
196 locked += new_len - old_len;
197 ret = -EAGAIN;
198 if (locked > current->rlim[RLIMIT_MEMLOCK].rlim_cur)
199 goto out;
200 }
201 ret = -ENOMEM;
202 if ((current->mm->total_vm << PAGE_SHIFT) + (new_len - old_len)
203 > current->rlim[RLIMIT_AS].rlim_cur)
204 goto out;
205
206
207 if (old_len == vma->vm_end - addr &&
208 (old_len != new_len || !(flags & MREMAP_MAYMOVE))) {
209 unsigned long max_addr = TASK_SIZE;
210 if (vma->vm_next)
211 max_addr = vma->vm_next->vm_start;
212
213 if (max_addr - addr >= new_len) {
214 int pages = (new_len - old_len) >> PAGE_SHIFT;
215 vma->vm_end = addr + new_len;
216 current->mm->total_vm += pages;
217 if (vma->vm_flags & VM_LOCKED)
218 current->mm->locked_vm += pages;
219 ret = addr;
220 goto out;
221 }
222 }
223
224
225
226
227
228 if (flags & MREMAP_MAYMOVE)
229 ret = move_vma(vma, addr, old_len, new_len);
230 else
231 ret = -ENOMEM;
232out:
233 unlock_kernel();
234 return ret;
235}
236