root/mm/vmalloc.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. set_pgdir
  2. free_area_pte
  3. free_area_pmd
  4. free_area_pages
  5. alloc_area_pte
  6. alloc_area_pmd
  7. alloc_area_pages
  8. remap_area_pte
  9. remap_area_pmd
  10. remap_area_pages
  11. get_vm_area
  12. vfree
  13. vmalloc
  14. vremap
  15. vread

   1 /*
   2  *  linux/mm/vmalloc.c
   3  *
   4  *  Copyright (C) 1993  Linus Torvalds
   5  */
   6 
   7 #include <asm/system.h>
   8 
   9 #include <linux/signal.h>
  10 #include <linux/sched.h>
  11 #include <linux/head.h>
  12 #include <linux/kernel.h>
  13 #include <linux/errno.h>
  14 #include <linux/types.h>
  15 #include <linux/malloc.h>
  16 #include <linux/mm.h>
  17 
  18 #include <asm/segment.h>
  19 #include <asm/pgtable.h>
  20 
  21 struct vm_struct {
  22         unsigned long flags;
  23         void * addr;
  24         unsigned long size;
  25         struct vm_struct * next;
  26 };
  27 
  28 static struct vm_struct * vmlist = NULL;
  29 
  30 static inline void set_pgdir(unsigned long address, pgd_t entry)
     /* [previous][next][first][last][top][bottom][index][help] */
  31 {
  32         struct task_struct * p;
  33 
  34         for_each_task(p) {
  35                 if (!p->mm)
  36                         continue;
  37                 *pgd_offset(p->mm,address) = entry;
  38         }
  39 }
  40 
  41 static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
  42 {
  43         pte_t * pte;
  44         unsigned long end;
  45 
  46         if (pmd_none(*pmd))
  47                 return;
  48         if (pmd_bad(*pmd)) {
  49                 printk("free_area_pte: bad pmd (%08lx)\n", pmd_val(*pmd));
  50                 pmd_clear(pmd);
  51                 return;
  52         }
  53         pte = pte_offset(pmd, address);
  54         address &= ~PMD_MASK;
  55         end = address + size;
  56         if (end > PMD_SIZE)
  57                 end = PMD_SIZE;
  58         while (address < end) {
  59                 pte_t page = *pte;
  60                 pte_clear(pte);
  61                 address += PAGE_SIZE;
  62                 pte++;
  63                 if (pte_none(page))
  64                         continue;
  65                 if (pte_present(page)) {
  66                         free_page(pte_page(page));
  67                         continue;
  68                 }
  69                 printk("Whee.. Swapped out page in kernel page table\n");
  70         }
  71 }
  72 
  73 static inline void free_area_pmd(pgd_t * dir, unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
  74 {
  75         pmd_t * pmd;
  76         unsigned long end;
  77 
  78         if (pgd_none(*dir))
  79                 return;
  80         if (pgd_bad(*dir)) {
  81                 printk("free_area_pmd: bad pgd (%08lx)\n", pgd_val(*dir));
  82                 pgd_clear(dir);
  83                 return;
  84         }
  85         pmd = pmd_offset(dir, address);
  86         address &= ~PGDIR_MASK;
  87         end = address + size;
  88         if (end > PGDIR_SIZE)
  89                 end = PGDIR_SIZE;
  90         while (address < end) {
  91                 free_area_pte(pmd, address, end - address);
  92                 address = (address + PMD_SIZE) & PMD_MASK;
  93                 pmd++;
  94         }
  95 }
  96 
  97 static void free_area_pages(unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
  98 {
  99         pgd_t * dir;
 100         unsigned long end = address + size;
 101 
 102         dir = pgd_offset(&init_mm, address);
 103         flush_cache_all();
 104         while (address < end) {
 105                 free_area_pmd(dir, address, end - address);
 106                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
 107                 dir++;
 108         }
 109         flush_tlb_all();
 110 }
 111 
 112 static inline int alloc_area_pte(pte_t * pte, unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 113 {
 114         unsigned long end;
 115 
 116         address &= ~PMD_MASK;
 117         end = address + size;
 118         if (end > PMD_SIZE)
 119                 end = PMD_SIZE;
 120         while (address < end) {
 121                 unsigned long page;
 122                 if (!pte_none(*pte))
 123                         printk("alloc_area_pte: page already exists\n");
 124                 page = __get_free_page(GFP_KERNEL);
 125                 if (!page)
 126                         return -ENOMEM;
 127                 set_pte(pte, mk_pte(page, PAGE_KERNEL));
 128                 address += PAGE_SIZE;
 129                 pte++;
 130         }
 131         return 0;
 132 }
 133 
 134 static inline int alloc_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 135 {
 136         unsigned long end;
 137 
 138         address &= ~PGDIR_MASK;
 139         end = address + size;
 140         if (end > PGDIR_SIZE)
 141                 end = PGDIR_SIZE;
 142         while (address < end) {
 143                 pte_t * pte = pte_alloc_kernel(pmd, address);
 144                 if (!pte)
 145                         return -ENOMEM;
 146                 if (alloc_area_pte(pte, address, end - address))
 147                         return -ENOMEM;
 148                 address = (address + PMD_SIZE) & PMD_MASK;
 149                 pmd++;
 150         }
 151         return 0;
 152 }
 153 
 154 static int alloc_area_pages(unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 155 {
 156         pgd_t * dir;
 157         unsigned long end = address + size;
 158 
 159         dir = pgd_offset(&init_mm, address);
 160         flush_cache_all();
 161         while (address < end) {
 162                 pmd_t *pmd = pmd_alloc_kernel(dir, address);
 163                 if (!pmd)
 164                         return -ENOMEM;
 165                 if (alloc_area_pmd(pmd, address, end - address))
 166                         return -ENOMEM;
 167                 set_pgdir(address, *dir);
 168                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
 169                 dir++;
 170         }
 171         flush_tlb_all();
 172         return 0;
 173 }
 174 
 175 static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
     /* [previous][next][first][last][top][bottom][index][help] */
 176         unsigned long offset)
 177 {
 178         unsigned long end;
 179 
 180         address &= ~PMD_MASK;
 181         end = address + size;
 182         if (end > PMD_SIZE)
 183                 end = PMD_SIZE;
 184         do {
 185                 if (!pte_none(*pte))
 186                         printk("remap_area_pte: page already exists\n");
 187                 set_pte(pte, mk_pte(offset, PAGE_KERNEL));
 188                 address += PAGE_SIZE;
 189                 offset += PAGE_SIZE;
 190                 pte++;
 191         } while (address < end);
 192 }
 193 
 194 static inline int remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
     /* [previous][next][first][last][top][bottom][index][help] */
 195         unsigned long offset)
 196 {
 197         unsigned long end;
 198 
 199         address &= ~PGDIR_MASK;
 200         end = address + size;
 201         if (end > PGDIR_SIZE)
 202                 end = PGDIR_SIZE;
 203         offset -= address;
 204         do {
 205                 pte_t * pte = pte_alloc_kernel(pmd, address);
 206                 if (!pte)
 207                         return -ENOMEM;
 208                 remap_area_pte(pte, address, end - address, address + offset);
 209                 address = (address + PMD_SIZE) & PMD_MASK;
 210                 pmd++;
 211         } while (address < end);
 212         return 0;
 213 }
 214 
 215 static int remap_area_pages(unsigned long address, unsigned long offset, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 216 {
 217         pgd_t * dir;
 218         unsigned long end = address + size;
 219 
 220         offset -= address;
 221         dir = pgd_offset(&init_mm, address);
 222         flush_cache_all();
 223         while (address < end) {
 224                 pmd_t *pmd = pmd_alloc_kernel(dir, address);
 225                 if (!pmd)
 226                         return -ENOMEM;
 227                 if (remap_area_pmd(pmd, address, end - address, offset + address))
 228                         return -ENOMEM;
 229                 set_pgdir(address, *dir);
 230                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
 231                 dir++;
 232         }
 233         flush_tlb_all();
 234         return 0;
 235 }
 236 
 237 static struct vm_struct * get_vm_area(unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 238 {
 239         void *addr;
 240         struct vm_struct **p, *tmp, *area;
 241 
 242         area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
 243         if (!area)
 244                 return NULL;
 245         addr = (void *) VMALLOC_START;
 246         area->size = size + PAGE_SIZE;
 247         area->next = NULL;
 248         for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
 249                 if (size + (unsigned long) addr < (unsigned long) tmp->addr)
 250                         break;
 251                 addr = (void *) (tmp->size + (unsigned long) tmp->addr);
 252         }
 253         area->addr = addr;
 254         area->next = *p;
 255         *p = area;
 256         return area;
 257 }
 258 
 259 void vfree(void * addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 260 {
 261         struct vm_struct **p, *tmp;
 262 
 263         if (!addr)
 264                 return;
 265         if ((PAGE_SIZE-1) & (unsigned long) addr) {
 266                 printk("Trying to vfree() bad address (%p)\n", addr);
 267                 return;
 268         }
 269         for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
 270                 if (tmp->addr == addr) {
 271                         *p = tmp->next;
 272                         free_area_pages(VMALLOC_VMADDR(tmp->addr), tmp->size);
 273                         kfree(tmp);
 274                         return;
 275                 }
 276         }
 277         printk("Trying to vfree() nonexistent vm area (%p)\n", addr);
 278 }
 279 
 280 void * vmalloc(unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 281 {
 282         void * addr;
 283         struct vm_struct *area;
 284 
 285         size = PAGE_ALIGN(size);
 286         if (!size || size > (MAP_NR(high_memory) << PAGE_SHIFT))
 287                 return NULL;
 288         area = get_vm_area(size);
 289         if (!area)
 290                 return NULL;
 291         addr = area->addr;
 292         if (alloc_area_pages(VMALLOC_VMADDR(addr), size)) {
 293                 vfree(addr);
 294                 return NULL;
 295         }
 296         return addr;
 297 }
 298 
 299 /*
 300  * Remap an arbitrary physical address space into the kernel virtual
 301  * address space. Needed when the kernel wants to access high addresses
 302  * directly.
 303  */
 304 void * vremap(unsigned long offset, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 305 {
 306         void * addr;
 307         struct vm_struct * area;
 308 
 309         if (offset < high_memory)
 310                 return NULL;
 311         if (offset & ~PAGE_MASK)
 312                 return NULL;
 313         size = PAGE_ALIGN(size);
 314         if (!size || size > offset + size)
 315                 return NULL;
 316         area = get_vm_area(size);
 317         if (!area)
 318                 return NULL;
 319         addr = area->addr;
 320         if (remap_area_pages(VMALLOC_VMADDR(addr), offset, size)) {
 321                 vfree(addr);
 322                 return NULL;
 323         }
 324         return addr;
 325 }
 326 
 327 int vread(char *buf, char *addr, int count)
     /* [previous][next][first][last][top][bottom][index][help] */
 328 {
 329         struct vm_struct **p, *tmp;
 330         char *vaddr, *buf_start = buf;
 331         int n;
 332 
 333         for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
 334                 vaddr = (char *) tmp->addr;
 335                 while (addr < vaddr) {
 336                         if (count == 0)
 337                                 goto finished;
 338                         put_user('\0', buf++), addr++, count--;
 339                 }
 340                 n = tmp->size - PAGE_SIZE;
 341                 if (addr > vaddr)
 342                         n -= addr - vaddr;
 343                 while (--n >= 0) {
 344                         if (count == 0)
 345                                 goto finished;
 346                         put_user(*addr++, buf++), count--;
 347                 }
 348         }
 349 finished:
 350         return buf - buf_start;
 351 }

/* [previous][next][first][last][top][bottom][index][help] */