1
2
3
4
5
6
7#include <linux/malloc.h>
8#include <linux/vmalloc.h>
9
10#include <asm/uaccess.h>
11
12static struct vm_struct * vmlist = NULL;
13
14static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)
15{
16 pte_t * pte;
17 unsigned long end;
18
19 if (pmd_none(*pmd))
20 return;
21 if (pmd_bad(*pmd)) {
22 printk("free_area_pte: bad pmd (%08lx)\n", pmd_val(*pmd));
23 pmd_clear(pmd);
24 return;
25 }
26 pte = pte_offset(pmd, address);
27 address &= ~PMD_MASK;
28 end = address + size;
29 if (end > PMD_SIZE)
30 end = PMD_SIZE;
31 while (address < end) {
32 pte_t page = *pte;
33 pte_clear(pte);
34 address += PAGE_SIZE;
35 pte++;
36 if (pte_none(page))
37 continue;
38 if (pte_present(page)) {
39 free_page(pte_page(page));
40 continue;
41 }
42 printk("Whee.. Swapped out page in kernel page table\n");
43 }
44}
45
46static inline void free_area_pmd(pgd_t * dir, unsigned long address, unsigned long size)
47{
48 pmd_t * pmd;
49 unsigned long end;
50
51 if (pgd_none(*dir))
52 return;
53 if (pgd_bad(*dir)) {
54 printk("free_area_pmd: bad pgd (%08lx)\n", pgd_val(*dir));
55 pgd_clear(dir);
56 return;
57 }
58 pmd = pmd_offset(dir, address);
59 address &= ~PGDIR_MASK;
60 end = address + size;
61 if (end > PGDIR_SIZE)
62 end = PGDIR_SIZE;
63 while (address < end) {
64 free_area_pte(pmd, address, end - address);
65 address = (address + PMD_SIZE) & PMD_MASK;
66 pmd++;
67 }
68}
69
70void vmfree_area_pages(unsigned long address, unsigned long size)
71{
72 pgd_t * dir;
73 unsigned long start = address;
74 unsigned long end = address + size;
75
76 dir = pgd_offset_k(address);
77 flush_cache_all();
78 while (address >= start && address < end) {
79 free_area_pmd(dir, address, end - address);
80 address = (address + PGDIR_SIZE) & PGDIR_MASK;
81 dir++;
82 }
83 flush_tlb_all();
84}
85
86static inline int alloc_area_pte(pte_t * pte, unsigned long address, unsigned long size)
87{
88 unsigned long end;
89
90 address &= ~PMD_MASK;
91 end = address + size;
92 if (end > PMD_SIZE)
93 end = PMD_SIZE;
94 while (address < end) {
95 unsigned long page;
96 if (!pte_none(*pte))
97 printk("alloc_area_pte: page already exists\n");
98 page = __get_free_page(GFP_KERNEL);
99 if (!page)
100 return -ENOMEM;
101 set_pte(pte, mk_pte(page, PAGE_KERNEL));
102 address += PAGE_SIZE;
103 pte++;
104 }
105 return 0;
106}
107
108static inline int alloc_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size)
109{
110 unsigned long end;
111
112 address &= ~PGDIR_MASK;
113 end = address + size;
114 if (end > PGDIR_SIZE)
115 end = PGDIR_SIZE;
116 while (address < end) {
117 pte_t * pte = pte_alloc_kernel(pmd, address);
118 if (!pte)
119 return -ENOMEM;
120 if (alloc_area_pte(pte, address, end - address))
121 return -ENOMEM;
122 address = (address + PMD_SIZE) & PMD_MASK;
123 pmd++;
124 }
125 return 0;
126}
127
128int vmalloc_area_pages(unsigned long address, unsigned long size)
129{
130 pgd_t * dir;
131 unsigned long start = address;
132 unsigned long end = address + size;
133
134 dir = pgd_offset_k(address);
135 while (address >= start && address < end) {
136 pmd_t *pmd;
137 pgd_t olddir = *dir;
138
139 pmd = pmd_alloc_kernel(dir, address);
140 if (!pmd)
141 return -ENOMEM;
142 if (alloc_area_pmd(pmd, address, end - address))
143 return -ENOMEM;
144 if (pgd_val(olddir) != pgd_val(*dir))
145 set_pgdir(address, *dir);
146 address = (address + PGDIR_SIZE) & PGDIR_MASK;
147 dir++;
148 }
149 flush_cache_all();
150 flush_tlb_all();
151 return 0;
152}
153
154struct vm_struct * get_vm_area(unsigned long size)
155{
156 unsigned long addr;
157 struct vm_struct **p, *tmp, *area;
158
159 area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
160 if (!area)
161 return NULL;
162 size += PAGE_SIZE;
163 if (!size) {
164 kfree(area);
165 return NULL;
166 }
167 addr = VMALLOC_START;
168 for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
169 if ((size + addr) < addr) {
170 kfree(area);
171 return NULL;
172 }
173 if (size + addr < (unsigned long) tmp->addr)
174 break;
175 addr = tmp->size + (unsigned long) tmp->addr;
176 if (addr > VMALLOC_END-size) {
177 kfree(area);
178 return NULL;
179 }
180 }
181 area->addr = (void *)addr;
182 area->size = size;
183 area->next = *p;
184 *p = area;
185 return area;
186}
187
188void vfree(void * addr)
189{
190 struct vm_struct **p, *tmp;
191
192 if (!addr)
193 return;
194 if ((PAGE_SIZE-1) & (unsigned long) addr) {
195 printk("Trying to vfree() bad address (%p)\n", addr);
196 return;
197 }
198 for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
199 if (tmp->addr == addr) {
200 *p = tmp->next;
201 vmfree_area_pages(VMALLOC_VMADDR(tmp->addr), tmp->size);
202 kfree(tmp);
203 return;
204 }
205 }
206 printk("Trying to vfree() nonexistent vm area (%p)\n", addr);
207}
208
209void * vmalloc(unsigned long size)
210{
211 void * addr;
212 struct vm_struct *area;
213
214 size = PAGE_ALIGN(size);
215 if (!size || size > (max_mapnr << PAGE_SHIFT))
216 return NULL;
217 area = get_vm_area(size);
218 if (!area)
219 return NULL;
220 addr = area->addr;
221 if (vmalloc_area_pages(VMALLOC_VMADDR(addr), size)) {
222 vfree(addr);
223 return NULL;
224 }
225 return addr;
226}
227
228long vread(char *buf, char *addr, unsigned long count)
229{
230 struct vm_struct *tmp;
231 char *vaddr, *buf_start = buf;
232 unsigned long n;
233
234
235 if ((unsigned long) addr + count < count)
236 count = -(unsigned long) addr;
237
238 for (tmp = vmlist; tmp; tmp = tmp->next) {
239 vaddr = (char *) tmp->addr;
240 if (addr >= vaddr + tmp->size - PAGE_SIZE)
241 continue;
242 while (addr < vaddr) {
243 if (count == 0)
244 goto finished;
245 put_user('\0', buf);
246 buf++;
247 addr++;
248 count--;
249 }
250 n = vaddr + tmp->size - PAGE_SIZE - addr;
251 do {
252 if (count == 0)
253 goto finished;
254 put_user(*addr, buf);
255 buf++;
256 addr++;
257 count--;
258 } while (--n > 0);
259 }
260finished:
261 return buf - buf_start;
262}
263