1
2
3
4
5
6
7
8
9#include <linux/config.h>
10#include <linux/slab.h>
11#include <linux/vmalloc.h>
12#include <linux/spinlock.h>
13#include <linux/highmem.h>
14#include <linux/smp_lock.h>
15
16#include <asm/uaccess.h>
17#include <asm/pgalloc.h>
18
19rwlock_t vmlist_lock = RW_LOCK_UNLOCKED;
20struct vm_struct * vmlist;
21
22static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)
23{
24 pte_t * pte;
25 unsigned long end;
26
27 if (pmd_none(*pmd))
28 return;
29 if (pmd_bad(*pmd)) {
30 pmd_ERROR(*pmd);
31 pmd_clear(pmd);
32 return;
33 }
34 pte = pte_offset(pmd, address);
35 address &= ~PMD_MASK;
36 end = address + size;
37 if (end > PMD_SIZE)
38 end = PMD_SIZE;
39 do {
40 pte_t page;
41 page = ptep_get_and_clear(pte);
42 address += PAGE_SIZE;
43 pte++;
44 if (pte_none(page))
45 continue;
46 if (pte_present(page)) {
47 struct page *ptpage = pte_page(page);
48 if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))
49 __free_page(ptpage);
50 continue;
51 }
52 printk(KERN_CRIT "Whee.. Swapped out page in kernel page table\n");
53 } while (address < end);
54}
55
56static inline void free_area_pmd(pgd_t * dir, unsigned long address, unsigned long size)
57{
58 pmd_t * pmd;
59 unsigned long end;
60
61 if (pgd_none(*dir))
62 return;
63 if (pgd_bad(*dir)) {
64 pgd_ERROR(*dir);
65 pgd_clear(dir);
66 return;
67 }
68 pmd = pmd_offset(dir, address);
69 address &= ~PGDIR_MASK;
70 end = address + size;
71 if (end > PGDIR_SIZE)
72 end = PGDIR_SIZE;
73 do {
74 free_area_pte(pmd, address, end - address);
75 address = (address + PMD_SIZE) & PMD_MASK;
76 pmd++;
77 } while (address < end);
78}
79
80void vmfree_area_pages(unsigned long address, unsigned long size)
81{
82 pgd_t * dir;
83 unsigned long end = address + size;
84
85 dir = pgd_offset_k(address);
86 flush_cache_all();
87 do {
88 free_area_pmd(dir, address, end - address);
89 address = (address + PGDIR_SIZE) & PGDIR_MASK;
90 dir++;
91 } while (address && (address < end));
92 flush_tlb_all();
93}
94
95static inline int alloc_area_pte (pte_t * pte, unsigned long address,
96 unsigned long size, int gfp_mask,
97 pgprot_t prot, struct page ***pages)
98{
99 unsigned long end;
100
101 address &= ~PMD_MASK;
102 end = address + size;
103 if (end > PMD_SIZE)
104 end = PMD_SIZE;
105 do {
106 struct page * page;
107
108 if (!pages) {
109 spin_unlock(&init_mm.page_table_lock);
110 page = alloc_page(gfp_mask);
111 spin_lock(&init_mm.page_table_lock);
112 } else {
113 page = (**pages);
114 (*pages)++;
115
116
117 if (page)
118 atomic_inc(&page->count);
119
120 }
121 if (!pte_none(*pte))
122 printk(KERN_ERR "alloc_area_pte: page already exists\n");
123 if (!page)
124 return -ENOMEM;
125 set_pte(pte, mk_pte(page, prot));
126 address += PAGE_SIZE;
127 pte++;
128 } while (address < end);
129 return 0;
130}
131
132static inline int alloc_area_pmd(pmd_t * pmd, unsigned long address,
133 unsigned long size, int gfp_mask,
134 pgprot_t prot, struct page ***pages)
135{
136 unsigned long end;
137
138 address &= ~PGDIR_MASK;
139 end = address + size;
140 if (end > PGDIR_SIZE)
141 end = PGDIR_SIZE;
142 do {
143 pte_t * pte = pte_alloc(&init_mm, pmd, address);
144 if (!pte)
145 return -ENOMEM;
146 if (alloc_area_pte(pte, address, end - address,
147 gfp_mask, prot, pages))
148 return -ENOMEM;
149 address = (address + PMD_SIZE) & PMD_MASK;
150 pmd++;
151 } while (address < end);
152 return 0;
153}
154
155static inline int __vmalloc_area_pages (unsigned long address,
156 unsigned long size,
157 int gfp_mask,
158 pgprot_t prot,
159 struct page ***pages)
160{
161 pgd_t * dir;
162 unsigned long start = address;
163 unsigned long end = address + size;
164
165 dir = pgd_offset_k(address);
166 spin_lock(&init_mm.page_table_lock);
167 do {
168 pmd_t *pmd;
169
170 pmd = pmd_alloc(&init_mm, dir, address);
171 if (!pmd)
172 goto err;
173
174 if (alloc_area_pmd(pmd, address, end - address, gfp_mask, prot, pages))
175 goto err;
176
177 address = (address + PGDIR_SIZE) & PGDIR_MASK;
178 dir++;
179 } while (address && (address < end));
180 spin_unlock(&init_mm.page_table_lock);
181 flush_cache_all();
182 return 0;
183err:
184 spin_unlock(&init_mm.page_table_lock);
185 flush_cache_all();
186 if (address > start)
187 vmfree_area_pages(start, address - start);
188 return -ENOMEM;
189}
190
191int vmalloc_area_pages(unsigned long address, unsigned long size,
192 int gfp_mask, pgprot_t prot)
193{
194 return __vmalloc_area_pages(address, size, gfp_mask, prot, NULL);
195}
196
197struct vm_struct * get_vm_area(unsigned long size, unsigned long flags)
198{
199 unsigned long addr, next;
200 struct vm_struct **p, *tmp, *area;
201
202 area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
203 if (!area)
204 return NULL;
205
206 size += PAGE_SIZE;
207 if (!size) {
208 kfree (area);
209 return NULL;
210 }
211
212 addr = VMALLOC_START;
213 write_lock(&vmlist_lock);
214 for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
215 if ((size + addr) < addr)
216 goto out;
217 if (size + addr <= (unsigned long) tmp->addr)
218 break;
219 next = tmp->size + (unsigned long) tmp->addr;
220 if (next > addr)
221 addr = next;
222 if (addr > VMALLOC_END-size)
223 goto out;
224 }
225 area->flags = flags;
226 area->addr = (void *)addr;
227 area->size = size;
228 area->next = *p;
229 *p = area;
230 write_unlock(&vmlist_lock);
231 return area;
232
233out:
234 write_unlock(&vmlist_lock);
235 kfree(area);
236 return NULL;
237}
238
239void __vfree(void * addr, int free_area_pages)
240{
241 struct vm_struct **p, *tmp;
242
243 if (!addr)
244 return;
245 if ((PAGE_SIZE-1) & (unsigned long) addr) {
246 printk(KERN_ERR "Trying to vfree() bad address (%p)\n", addr);
247 return;
248 }
249 write_lock(&vmlist_lock);
250 for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
251 if (tmp->addr == addr) {
252 *p = tmp->next;
253 if (free_area_pages)
254 vmfree_area_pages(VMALLOC_VMADDR(tmp->addr), tmp->size);
255 write_unlock(&vmlist_lock);
256 kfree(tmp);
257 return;
258 }
259 }
260 write_unlock(&vmlist_lock);
261 printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n", addr);
262}
263
264void vfree(void * addr)
265{
266 __vfree(addr,1);
267}
268
269void * __vmalloc (unsigned long size, int gfp_mask, pgprot_t prot)
270{
271 void * addr;
272 struct vm_struct *area;
273
274 size = PAGE_ALIGN(size);
275 if (!size || (size >> PAGE_SHIFT) > num_physpages)
276 return NULL;
277 area = get_vm_area(size, VM_ALLOC);
278 if (!area)
279 return NULL;
280 addr = area->addr;
281 if (__vmalloc_area_pages(VMALLOC_VMADDR(addr), size, gfp_mask,
282 prot, NULL)) {
283 __vfree(addr, 0);
284 return NULL;
285 }
286 return addr;
287}
288
289void * vmap(struct page **pages, int count,
290 unsigned long flags, pgprot_t prot)
291{
292 void * addr;
293 struct vm_struct *area;
294 unsigned long size = count << PAGE_SHIFT;
295
296 if (!size || size > (max_mapnr << PAGE_SHIFT))
297 return NULL;
298 area = get_vm_area(size, flags);
299 if (!area) {
300 return NULL;
301 }
302 addr = area->addr;
303 if (__vmalloc_area_pages(VMALLOC_VMADDR(addr), size, 0,
304 prot, &pages)) {
305 __vfree(addr, 0);
306 return NULL;
307 }
308 return addr;
309}
310
311long vread(char *buf, char *addr, unsigned long count)
312{
313 struct vm_struct *tmp;
314 char *vaddr, *buf_start = buf;
315 unsigned long n;
316
317
318 if ((unsigned long) addr + count < count)
319 count = -(unsigned long) addr;
320
321 read_lock(&vmlist_lock);
322 for (tmp = vmlist; tmp; tmp = tmp->next) {
323 vaddr = (char *) tmp->addr;
324 if (addr >= vaddr + tmp->size - PAGE_SIZE)
325 continue;
326 while (addr < vaddr) {
327 if (count == 0)
328 goto finished;
329 *buf = '\0';
330 buf++;
331 addr++;
332 count--;
333 }
334 n = vaddr + tmp->size - PAGE_SIZE - addr;
335 do {
336 if (count == 0)
337 goto finished;
338 *buf = *addr;
339 buf++;
340 addr++;
341 count--;
342 } while (--n > 0);
343 }
344finished:
345 read_unlock(&vmlist_lock);
346 return buf - buf_start;
347}
348
349long vwrite(char *buf, char *addr, unsigned long count)
350{
351 struct vm_struct *tmp;
352 char *vaddr, *buf_start = buf;
353 unsigned long n;
354
355
356 if ((unsigned long) addr + count < count)
357 count = -(unsigned long) addr;
358
359 read_lock(&vmlist_lock);
360 for (tmp = vmlist; tmp; tmp = tmp->next) {
361 vaddr = (char *) tmp->addr;
362 if (addr >= vaddr + tmp->size - PAGE_SIZE)
363 continue;
364 while (addr < vaddr) {
365 if (count == 0)
366 goto finished;
367 buf++;
368 addr++;
369 count--;
370 }
371 n = vaddr + tmp->size - PAGE_SIZE - addr;
372 do {
373 if (count == 0)
374 goto finished;
375 *addr = *buf;
376 buf++;
377 addr++;
378 count--;
379 } while (--n > 0);
380 }
381finished:
382 read_unlock(&vmlist_lock);
383 return buf - buf_start;
384}
385