1
2
3
4
5
6
7#include <linux/malloc.h>
8#include <linux/vmalloc.h>
9
10#include <asm/uaccess.h>
11#include <asm/system.h>
12
13static struct vm_struct * vmlist = NULL;
14
15static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)
16{
17 pte_t * pte;
18 unsigned long end;
19
20 if (pmd_none(*pmd))
21 return;
22 if (pmd_bad(*pmd)) {
23 printk("free_area_pte: bad pmd (%08lx)\n", pmd_val(*pmd));
24 pmd_clear(pmd);
25 return;
26 }
27 pte = pte_offset(pmd, address);
28 address &= ~PMD_MASK;
29 end = address + size;
30 if (end > PMD_SIZE)
31 end = PMD_SIZE;
32 while (address < end) {
33 pte_t page = *pte;
34 pte_clear(pte);
35 address += PAGE_SIZE;
36 pte++;
37 if (pte_none(page))
38 continue;
39 if (pte_present(page)) {
40 free_page(pte_page(page));
41 continue;
42 }
43 printk("Whee.. Swapped out page in kernel page table\n");
44 }
45}
46
47static inline void free_area_pmd(pgd_t * dir, unsigned long address, unsigned long size)
48{
49 pmd_t * pmd;
50 unsigned long end;
51
52 if (pgd_none(*dir))
53 return;
54 if (pgd_bad(*dir)) {
55 printk("free_area_pmd: bad pgd (%08lx)\n", pgd_val(*dir));
56 pgd_clear(dir);
57 return;
58 }
59 pmd = pmd_offset(dir, address);
60 address &= ~PGDIR_MASK;
61 end = address + size;
62 if (end > PGDIR_SIZE)
63 end = PGDIR_SIZE;
64 while (address < end) {
65 free_area_pte(pmd, address, end - address);
66 address = (address + PMD_SIZE) & PMD_MASK;
67 pmd++;
68 }
69}
70
71void vmfree_area_pages(unsigned long address, unsigned long size)
72{
73 pgd_t * dir;
74 unsigned long end = address + size;
75
76 dir = pgd_offset_k(address);
77 flush_cache_all();
78 while (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 end = address + size;
132
133 dir = pgd_offset_k(address);
134 flush_cache_all();
135 while (address < end) {
136 pmd_t *pmd = pmd_alloc_kernel(dir, address);
137 if (!pmd)
138 return -ENOMEM;
139 if (alloc_area_pmd(pmd, address, end - address))
140 return -ENOMEM;
141 set_pgdir(address, *dir);
142 address = (address + PGDIR_SIZE) & PGDIR_MASK;
143 dir++;
144 }
145 flush_tlb_all();
146 return 0;
147}
148
149struct vm_struct * get_vm_area(unsigned long size)
150{
151 void *addr;
152 struct vm_struct **p, *tmp, *area;
153
154 area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
155 if (!area)
156 return NULL;
157 addr = (void *) VMALLOC_START;
158 area->size = size + PAGE_SIZE;
159 area->next = NULL;
160 for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
161 if (size + (unsigned long) addr < (unsigned long) tmp->addr)
162 break;
163 addr = (void *) (tmp->size + (unsigned long) tmp->addr);
164 }
165 area->addr = addr;
166 area->next = *p;
167 *p = area;
168 return area;
169}
170
171void vfree(void * addr)
172{
173 struct vm_struct **p, *tmp;
174
175 if (!addr)
176 return;
177 if ((PAGE_SIZE-1) & (unsigned long) addr) {
178 printk("Trying to vfree() bad address (%p)\n", addr);
179 return;
180 }
181 for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
182 if (tmp->addr == addr) {
183 *p = tmp->next;
184 vmfree_area_pages(VMALLOC_VMADDR(tmp->addr), tmp->size);
185 kfree(tmp);
186 return;
187 }
188 }
189 printk("Trying to vfree() nonexistent vm area (%p)\n", addr);
190}
191
192void * vmalloc(unsigned long size)
193{
194 void * addr;
195 struct vm_struct *area;
196
197 size = PAGE_ALIGN(size);
198 if (!size || size > (max_mapnr << PAGE_SHIFT))
199 return NULL;
200 area = get_vm_area(size);
201 if (!area)
202 return NULL;
203 addr = area->addr;
204 if (vmalloc_area_pages(VMALLOC_VMADDR(addr), size)) {
205 vfree(addr);
206 return NULL;
207 }
208 return addr;
209}
210
211int vread(char *buf, char *addr, int count)
212{
213 struct vm_struct **p, *tmp;
214 char *vaddr, *buf_start = buf;
215 int n;
216
217 for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
218 vaddr = (char *) tmp->addr;
219 while (addr < vaddr) {
220 if (count == 0)
221 goto finished;
222 put_user('\0', buf);
223 buf++;
224 addr++;
225 count--;
226 }
227 n = tmp->size - PAGE_SIZE;
228 if (addr > vaddr)
229 n -= addr - vaddr;
230 while (--n >= 0) {
231 if (count == 0)
232 goto finished;
233 put_user(*addr, buf);
234 buf++;
235 addr++;
236 count--;
237 }
238 }
239finished:
240 return buf - buf_start;
241}
242