/* * linux/mm/vmalloc.c * * Copyright (C) 1993 Linus Torvalds * Support of BIGMEM added by Gerhard Wichert, Siemens AG, July 1999 * SMP-safe vmalloc/vfree/ioremap, Tigran Aivazian <tigran@veritas.com>, May 2000 */ #include <linux/malloc.h> #include <linux/vmalloc.h> #include <linux/spinlock.h> #include <linux/smp_lock.h> #include <asm/uaccess.h> #include <asm/pgalloc.h> rwlock_t vmlist_lock = RW_LOCK_UNLOCKED; struct vm_struct * vmlist; 20 static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size) { pte_t * pte; unsigned long end; 25 if (pmd_none(*pmd)) 26 return; 27 if (pmd_bad(*pmd)) { pmd_ERROR(*pmd); 29 pmd_clear(pmd); 30 return; } pte = pte_offset(pmd, address); address &= ~PMD_MASK; end = address + size; 35 if (end > PMD_SIZE) end = PMD_SIZE; 37 do { pte_t page; page = ptep_get_and_clear(pte); address += PAGE_SIZE; pte++; 42 if (pte_none(page)) 43 continue; 44 if (pte_present(page)) { struct page *ptpage = pte_page(page); 46 if (VALID_PAGE(ptpage) && (!PageReserved(ptpage))) __free_page(ptpage); 48 continue; } printk(KERN_CRIT "Whee.. Swapped out page in kernel page table\n"); 51 } while (address < end); } 54 static inline void free_area_pmd(pgd_t * dir, unsigned long address, unsigned long size) { pmd_t * pmd; unsigned long end; 59 if (pgd_none(*dir)) 60 return; 61 if (pgd_bad(*dir)) { pgd_ERROR(*dir); 63 pgd_clear(dir); 64 return; } pmd = pmd_offset(dir, address); address &= ~PGDIR_MASK; end = address + size; 69 if (end > PGDIR_SIZE) end = PGDIR_SIZE; 71 do { free_area_pte(pmd, address, end - address); address = (address + PMD_SIZE) & PMD_MASK; pmd++; 75 } while (address < end); } 78 void vmfree_area_pages(unsigned long address, unsigned long size) { pgd_t * dir; unsigned long end = address + size; dir = pgd_offset_k(address); 84 flush_cache_all(); 85 do { free_area_pmd(dir, address, end - address); address = (address + PGDIR_SIZE) & PGDIR_MASK; dir++; 89 } while (address && (address < end)); 90 flush_tlb_all(); } 93 static inline int alloc_area_pte (pte_t * pte, unsigned long address, unsigned long size, int gfp_mask, pgprot_t prot) { unsigned long end; address &= ~PMD_MASK; end = address + size; 100 if (end > PMD_SIZE) end = PMD_SIZE; 102 do { struct page * page; 104 if (!pte_none(*pte)) printk(KERN_ERR "alloc_area_pte: page already exists\n"); page = alloc_page(gfp_mask); 107 if (!page) 108 return -ENOMEM; set_pte(pte, mk_pte(page, prot)); address += PAGE_SIZE; pte++; 112 } while (address < end); 113 return 0; } 116 static inline int alloc_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size, int gfp_mask, pgprot_t prot) { unsigned long end; address &= ~PGDIR_MASK; end = address + size; 122 if (end > PGDIR_SIZE) end = PGDIR_SIZE; 124 do { pte_t * pte = pte_alloc_kernel(pmd, address); 126 if (!pte) 127 return -ENOMEM; 128 if (alloc_area_pte(pte, address, end - address, gfp_mask, prot)) 129 return -ENOMEM; address = (address + PMD_SIZE) & PMD_MASK; pmd++; 132 } while (address < end); 133 return 0; } 136 inline int vmalloc_area_pages (unsigned long address, unsigned long size, int gfp_mask, pgprot_t prot) { pgd_t * dir; unsigned long end = address + size; int ret; dir = pgd_offset_k(address); 144 flush_cache_all(); 145 lock_kernel(); 146 do { pmd_t *pmd; pmd = pmd_alloc_kernel(dir, address); ret = -ENOMEM; 151 if (!pmd) 152 break; ret = -ENOMEM; 155 if (alloc_area_pmd(pmd, address, end - address, gfp_mask, prot)) 156 break; address = (address + PGDIR_SIZE) & PGDIR_MASK; dir++; ret = 0; 162 } while (address && (address < end)); 163 unlock_kernel(); 164 flush_tlb_all(); 165 return ret; } 168 struct vm_struct * get_vm_area(unsigned long size, unsigned long flags) { unsigned long addr; struct vm_struct **p, *tmp, *area; area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL); 174 if (!area) 175 return NULL; size += PAGE_SIZE; addr = VMALLOC_START; write_lock(&vmlist_lock); 179 for (p = &vmlist; (tmp = *p) ; p = &tmp->next) { 180 if ((size + addr) < addr) { 181 write_unlock(&vmlist_lock); kfree(area); 183 return NULL; } 185 if (size + addr < (unsigned long) tmp->addr) 186 break; addr = tmp->size + (unsigned long) tmp->addr; 188 if (addr > VMALLOC_END-size) { 189 write_unlock(&vmlist_lock); kfree(area); 191 return NULL; } } area->flags = flags; area->addr = (void *)addr; area->size = size; area->next = *p; *p = area; 199 write_unlock(&vmlist_lock); 200 return area; } 203 void vfree(void * addr) { struct vm_struct **p, *tmp; 207 if (!addr) 208 return; 209 if ((PAGE_SIZE-1) & (unsigned long) addr) { printk(KERN_ERR "Trying to vfree() bad address (%p)\n", addr); 211 return; } write_lock(&vmlist_lock); 214 for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) { 215 if (tmp->addr == addr) { *p = tmp->next; vmfree_area_pages(VMALLOC_VMADDR(tmp->addr), tmp->size); 218 write_unlock(&vmlist_lock); kfree(tmp); 220 return; } } 223 write_unlock(&vmlist_lock); printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n", addr); } 227 void * __vmalloc (unsigned long size, int gfp_mask, pgprot_t prot) { void * addr; struct vm_struct *area; size = PAGE_ALIGN(size); 233 if (!size || (size >> PAGE_SHIFT) > num_physpages) { 234 BUG(); 235 return NULL; } area = get_vm_area(size, VM_ALLOC); 238 if (!area) 239 return NULL; addr = area->addr; 241 if (vmalloc_area_pages(VMALLOC_VMADDR(addr), size, gfp_mask, prot)) { vfree(addr); 243 return NULL; } 245 return addr; } 248 long vread(char *buf, char *addr, unsigned long count) { struct vm_struct *tmp; char *vaddr, *buf_start = buf; unsigned long n; /* Don't allow overflow */ 255 if ((unsigned long) addr + count < count) count = -(unsigned long) addr; read_lock(&vmlist_lock); 259 for (tmp = vmlist; tmp; tmp = tmp->next) { vaddr = (char *) tmp->addr; 261 if (addr >= vaddr + tmp->size - PAGE_SIZE) 262 continue; 263 while (addr < vaddr) { 264 if (count == 0) 265 goto finished; *buf = '\0'; buf++; addr++; count--; } n = vaddr + tmp->size - PAGE_SIZE - addr; 272 do { 273 if (count == 0) 274 goto finished; *buf = *addr; buf++; addr++; count--; 279 } while (--n > 0); } finished: 282 read_unlock(&vmlist_lock); 283 return buf - buf_start; }