/*
       *  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;
      }