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         while (address < end) {
 104                 free_area_pmd(dir, address, end - address);
 105                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
 106                 dir++;
 107         }
 108         invalidate();
 109 }
 110 
 111 static inline int alloc_area_pte(pte_t * pte, unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 112 {
 113         unsigned long end;
 114 
 115         address &= ~PMD_MASK;
 116         end = address + size;
 117         if (end > PMD_SIZE)
 118                 end = PMD_SIZE;
 119         while (address < end) {
 120                 unsigned long page;
 121                 if (!pte_none(*pte))
 122                         printk("alloc_area_pte: page already exists\n");
 123                 page = __get_free_page(GFP_KERNEL);
 124                 if (!page)
 125                         return -ENOMEM;
 126                 set_pte(pte, mk_pte(page, PAGE_KERNEL));
 127                 address += PAGE_SIZE;
 128                 pte++;
 129         }
 130         return 0;
 131 }
 132 
 133 static inline int alloc_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 134 {
 135         unsigned long end;
 136 
 137         address &= ~PGDIR_MASK;
 138         end = address + size;
 139         if (end > PGDIR_SIZE)
 140                 end = PGDIR_SIZE;
 141         while (address < end) {
 142                 pte_t * pte = pte_alloc_kernel(pmd, address);
 143                 if (!pte)
 144                         return -ENOMEM;
 145                 if (alloc_area_pte(pte, address, end - address))
 146                         return -ENOMEM;
 147                 address = (address + PMD_SIZE) & PMD_MASK;
 148                 pmd++;
 149         }
 150         return 0;
 151 }
 152 
 153 static int alloc_area_pages(unsigned long address, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 154 {
 155         pgd_t * dir;
 156         unsigned long end = address + size;
 157 
 158         dir = pgd_offset(&init_mm, address);
 159         while (address < end) {
 160                 pmd_t *pmd = pmd_alloc_kernel(dir, address);
 161                 if (!pmd)
 162                         return -ENOMEM;
 163                 if (alloc_area_pmd(pmd, address, end - address))
 164                         return -ENOMEM;
 165                 set_pgdir(address, *dir);
 166                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
 167                 dir++;
 168         }
 169         invalidate();
 170         return 0;
 171 }
 172 
 173 static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
     /* [previous][next][first][last][top][bottom][index][help] */
 174         unsigned long offset)
 175 {
 176         unsigned long end;
 177 
 178         address &= ~PMD_MASK;
 179         end = address + size;
 180         if (end > PMD_SIZE)
 181                 end = PMD_SIZE;
 182         do {
 183                 if (!pte_none(*pte))
 184                         printk("remap_area_pte: page already exists\n");
 185                 set_pte(pte, mk_pte(offset, PAGE_KERNEL));
 186                 address += PAGE_SIZE;
 187                 offset += PAGE_SIZE;
 188                 pte++;
 189         } while (address < end);
 190 }
 191 
 192 static inline int remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
     /* [previous][next][first][last][top][bottom][index][help] */
 193         unsigned long offset)
 194 {
 195         unsigned long end;
 196 
 197         address &= ~PGDIR_MASK;
 198         end = address + size;
 199         if (end > PGDIR_SIZE)
 200                 end = PGDIR_SIZE;
 201         offset -= address;
 202         do {
 203                 pte_t * pte = pte_alloc_kernel(pmd, address);
 204                 if (!pte)
 205                         return -ENOMEM;
 206                 remap_area_pte(pte, address, end - address, address + offset);
 207                 address = (address + PMD_SIZE) & PMD_MASK;
 208                 pmd++;
 209         } while (address < end);
 210         return 0;
 211 }
 212 
 213 static int remap_area_pages(unsigned long address, unsigned long offset, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 214 {
 215         pgd_t * dir;
 216         unsigned long end = address + size;
 217 
 218         offset -= address;
 219         dir = pgd_offset(&init_mm, address);
 220         while (address < end) {
 221                 pmd_t *pmd = pmd_alloc_kernel(dir, address);
 222                 if (!pmd)
 223                         return -ENOMEM;
 224                 if (remap_area_pmd(pmd, address, end - address, offset + address))
 225                         return -ENOMEM;
 226                 set_pgdir(address, *dir);
 227                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
 228                 dir++;
 229         }
 230         invalidate();
 231         return 0;
 232 }
 233 
 234 static struct vm_struct * get_vm_area(unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 235 {
 236         void *addr;
 237         struct vm_struct **p, *tmp, *area;
 238 
 239         area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
 240         if (!area)
 241                 return NULL;
 242         addr = (void *) VMALLOC_START;
 243         area->size = size + PAGE_SIZE;
 244         area->next = NULL;
 245         for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
 246                 if (size + (unsigned long) addr < (unsigned long) tmp->addr)
 247                         break;
 248                 addr = (void *) (tmp->size + (unsigned long) tmp->addr);
 249         }
 250         area->addr = addr;
 251         area->next = *p;
 252         *p = area;
 253         return area;
 254 }
 255 
 256 void vfree(void * addr)
     /* [previous][next][first][last][top][bottom][index][help] */
 257 {
 258         struct vm_struct **p, *tmp;
 259 
 260         if (!addr)
 261                 return;
 262         if ((PAGE_SIZE-1) & (unsigned long) addr) {
 263                 printk("Trying to vfree() bad address (%p)\n", addr);
 264                 return;
 265         }
 266         for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) {
 267                 if (tmp->addr == addr) {
 268                         *p = tmp->next;
 269                         free_area_pages(VMALLOC_VMADDR(tmp->addr), tmp->size);
 270                         kfree(tmp);
 271                         return;
 272                 }
 273         }
 274         printk("Trying to vfree() nonexistent vm area (%p)\n", addr);
 275 }
 276 
 277 void * vmalloc(unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 278 {
 279         void * addr;
 280         struct vm_struct *area;
 281 
 282         size = PAGE_ALIGN(size);
 283         if (!size || size > (MAP_NR(high_memory) << PAGE_SHIFT))
 284                 return NULL;
 285         area = get_vm_area(size);
 286         if (!area)
 287                 return NULL;
 288         addr = area->addr;
 289         if (alloc_area_pages(VMALLOC_VMADDR(addr), size)) {
 290                 vfree(addr);
 291                 return NULL;
 292         }
 293         return addr;
 294 }
 295 
 296 /*
 297  * Remap an arbitrary physical address space into the kernel virtual
 298  * address space. Needed when the kernel wants to access high addresses
 299  * directly.
 300  */
 301 void * vremap(unsigned long offset, unsigned long size)
     /* [previous][next][first][last][top][bottom][index][help] */
 302 {
 303         void * addr;
 304         struct vm_struct * area;
 305 
 306         if (offset < high_memory)
 307                 return NULL;
 308         if (offset & ~PAGE_MASK)
 309                 return NULL;
 310         size = PAGE_ALIGN(size);
 311         if (!size || size > offset + size)
 312                 return NULL;
 313         area = get_vm_area(size);
 314         if (!area)
 315                 return NULL;
 316         addr = area->addr;
 317         if (remap_area_pages(VMALLOC_VMADDR(addr), offset, size)) {
 318                 vfree(addr);
 319                 return NULL;
 320         }
 321         return addr;
 322 }
 323 
 324 int vread(char *buf, char *addr, int count)
     /* [previous][next][first][last][top][bottom][index][help] */
 325 {
 326         struct vm_struct **p, *tmp;
 327         char *vaddr, *buf_start = buf;
 328         int n;
 329 
 330         for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
 331                 vaddr = (char *) tmp->addr;
 332                 while (addr < vaddr) {
 333                         if (count == 0)
 334                                 goto finished;
 335                         put_user('\0', buf++), addr++, count--;
 336                 }
 337                 n = tmp->size - PAGE_SIZE;
 338                 if (addr > vaddr)
 339                         n -= addr - vaddr;
 340                 while (--n >= 0) {
 341                         if (count == 0)
 342                                 goto finished;
 343                         put_user(*addr++, buf++), count--;
 344                 }
 345         }
 346 finished:
 347         return buf - buf_start;
 348 }

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