/* 
 * Copyright (C) 2000, 2001, 2002 Jeff Dike (jdike@karaya.com)
 * Licensed under the GPL
 */

#include "linux/config.h"
#include "linux/types.h"
#include "linux/mm.h"
#include "linux/fs.h"
#include "linux/init.h"
#include "linux/bootmem.h"
#include "linux/swap.h"
#include "linux/slab.h"
#include "linux/vmalloc.h"
#include "linux/highmem.h"
#include "asm/page.h"
#include "asm/pgtable.h"
#include "asm/pgalloc.h"
#include "asm/bitops.h"
#include "asm/uaccess.h"
#include "asm/tlb.h"
#include "user_util.h"
#include "kern_util.h"
#include "mem_user.h"
#include "mem.h"
#include "kern.h"
#include "init.h"

unsigned long high_physmem;
unsigned long low_physmem;

unsigned long vm_start;
unsigned long vm_end;

unsigned long highmem;

pgd_t swapper_pg_dir[1024];

unsigned long *empty_zero_page = NULL;

unsigned long *empty_bad_page = NULL;

const char bad_pmd_string[] = "Bad pmd in pte_alloc: %08lx\n";

static unsigned long totalram_pages = 0;

extern char __init_begin, __init_end;
extern long physmem_size;

#ifdef CONFIG_SMP
mmu_gather_t mmu_gathers[NR_CPUS];
#endif

int kmalloc_ok = 0;

#define NREGIONS (phys_region_index(0xffffffff) - phys_region_index(0x0))
struct mem_region *regions[NREGIONS] = { [ 0 ... NREGIONS - 1 ] = NULL };
#define REGION_SIZE ((0xffffffff & ~REGION_MASK) + 1)

static unsigned long brk_end;

static void map_cb(void *unused)
{
	map(brk_end, __pa(brk_end), uml_reserved - brk_end, 1, 1, 0);
}

void unmap_physmem(void)
{
	unmap((void *) brk_end, uml_reserved - brk_end);
}

extern char __binary_start;

void mem_init(void)
{
	unsigned long start;

#ifdef CONFIG_HIGHMEM
	highmem_start_page = phys_page(__pa(high_physmem));
#endif
	max_mapnr = num_physpages = max_low_pfn;

        /* clear the zero-page */
        memset((void *) empty_zero_page, 0, PAGE_SIZE);

	/* Map in the area just after the brk now that kmalloc is about
	 * to be turned on.
	 */
	brk_end = (unsigned long) ROUND_UP(sbrk(0));
	map_cb(NULL);
	tracing_cb(map_cb, NULL);
	free_bootmem(__pa(brk_end), uml_reserved - brk_end);
	uml_reserved = brk_end;

	/* Fill in any hole at the start of the binary */
	start = (unsigned long) &__binary_start;
	if(uml_physmem != start){
		map(uml_physmem, __pa(uml_physmem), start - uml_physmem,
		    1, 1, 0);
	}

	/* this will put all low memory onto the freelists */
	totalram_pages = free_all_bootmem();
	totalram_pages += highmem >> PAGE_SHIFT;
	printk(KERN_INFO "Memory: %luk available\n", 
	       (unsigned long) nr_free_pages() << (PAGE_SHIFT-10));
	kmalloc_ok = 1;
}

#if CONFIG_HIGHMEM
pte_t *kmap_pte;
pgprot_t kmap_prot;

#define kmap_get_fixmap_pte(vaddr)					\
	pte_offset(pmd_offset(pgd_offset_k(vaddr), (vaddr)), (vaddr))

void __init kmap_init(void)
{
	unsigned long kmap_vstart;

	/* cache the first kmap pte */
	kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);
	kmap_pte = kmap_get_fixmap_pte(kmap_vstart);

	kmap_prot = PAGE_KERNEL;
}
#endif /* CONFIG_HIGHMEM */

static void __init fixrange_init(unsigned long start, unsigned long end, 
				 pgd_t *pgd_base)
{
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;
	int i, j;
	unsigned long vaddr;

	vaddr = start;
	i = __pgd_offset(vaddr);
	j = __pmd_offset(vaddr);
	pgd = pgd_base + i;

	for ( ; (i < PTRS_PER_PGD) && (vaddr < end); pgd++, i++) {
		pmd = (pmd_t *)pgd;
		for (; (j < PTRS_PER_PMD) && (vaddr != end); pmd++, j++) {
			if (pmd_none(*pmd)) {
				pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
				set_pmd(pmd, __pmd(_KERNPG_TABLE + 
						   (unsigned long) pte));
				if (pte != pte_offset(pmd, 0))
					BUG();
			}
			vaddr += PMD_SIZE;
		}
		j = 0;
	}
}

int init_maps(struct mem_region *region)
{
	struct page *p, *map;
	int i, n, len;

	if(region == &physmem_region){
		region->mem_map = mem_map;
		return(0);
	}
	else if(region->mem_map != NULL) return(0);

	n = region->len >> PAGE_SHIFT;
	len = n * sizeof(struct page);
	if(kmalloc_ok){
		map = kmalloc(len, GFP_KERNEL);
		if(map == NULL) map = vmalloc(len);
	}
	else map = alloc_bootmem_low_pages(len);

	if(map == NULL)
		return(-ENOMEM);
	for(i = 0; i < n; i++){
		p = &map[i];
		set_page_count(p, 0);
		SetPageReserved(p);
		INIT_LIST_HEAD(&p->list);
	}
	region->mem_map = map;
	return(0);
}

static int setup_one_range(int fd, char *driver, unsigned long start, int len, 
			   struct mem_region *region)
{
	int i;

	for(i = 0; i < NREGIONS; i++){
		if(regions[i] == NULL) break;		
	}
	if(i == NREGIONS){
		printk("setup_range : no free regions\n");
		return(-1);
	}
	init_range(i, fd, driver, start, len, region);
	return(i);
}

#ifdef CONFIG_HIGHMEM
static void init_highmem(void)
{
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;
	unsigned long vaddr;

	/*
	 * Permanent kmaps:
	 */
	vaddr = PKMAP_BASE;
	fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, swapper_pg_dir);

	pgd = swapper_pg_dir + __pgd_offset(vaddr);
	pmd = pmd_offset(pgd, vaddr);
	pte = pte_offset(pmd, vaddr);
	pkmap_page_table = pte;

	kmap_init();
}

void setup_highmem(unsigned long len)
{
	struct mem_region *region;
	struct page *page, *map;
	unsigned long phys;
	int i, cur, index;

	phys = physmem_size;
	do {
		cur = min(len, (unsigned long) REGION_SIZE);
		i = setup_one_range(-1, NULL, -1, cur, NULL);
		if(i == -1){
			printk("setup_highmem - setup_one_range failed\n");
			return;
		}
		region = regions[i];
		index = phys / PAGE_SIZE;
		region->mem_map = &mem_map[index];

		map = region->mem_map;
		for(i = 0; i < (cur >> PAGE_SHIFT); i++){
			page = &map[i];
			ClearPageReserved(page);
			set_bit(PG_highmem, &page->flags);
			atomic_set(&page->count, 1);
			__free_page(page);
		}
		phys += cur;
		len -= cur;
	} while(len > 0);
}
#endif

void paging_init(void)
{
	struct mem_region *region;
	unsigned long zones_size[MAX_NR_ZONES], start, end, vaddr;
	int i, index;

	empty_zero_page = (unsigned long *) alloc_bootmem_low_pages(PAGE_SIZE);
	empty_bad_page = (unsigned long *) alloc_bootmem_low_pages(PAGE_SIZE);
	for(i=0;i<sizeof(zones_size)/sizeof(zones_size[0]);i++) 
		zones_size[i] = 0;
	zones_size[0] = (high_physmem >> PAGE_SHIFT) - 
		(uml_physmem >> PAGE_SHIFT);
	zones_size[2] = highmem >> PAGE_SHIFT;
	free_area_init(zones_size);
	start = phys_region_index(__pa(uml_physmem));
	end = phys_region_index(__pa(high_physmem - 1));
	for(i = start; i <= end; i++){
		region = regions[i];
		index = (region->start - uml_physmem) / PAGE_SIZE;
		region->mem_map = &mem_map[index];
		if(i > start) free_bootmem(__pa(region->start), region->len);
	}

	/*
	 * Fixed mappings, only the page table structure has to be
	 * created - mappings will be set by set_fixmap():
	 */
	vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
	fixrange_init(vaddr, FIXADDR_TOP, swapper_pg_dir);

#if CONFIG_HIGHMEM
	init_highmem();
	setup_highmem(highmem);
#endif
}

static int meminfo_22 = 0;

static int meminfo_compat(char *str)
{
	meminfo_22 = 1;
	return(1);
}

__setup("22_meminfo", meminfo_compat);

void si_meminfo(struct sysinfo *val)
{
	val->totalram = totalram_pages;
	val->sharedram = 0;
	val->freeram = nr_free_pages();
	val->bufferram = atomic_read(&buffermem_pages);
	val->totalhigh = highmem >> PAGE_SHIFT;
	val->freehigh = nr_free_highpages();
	val->mem_unit = PAGE_SIZE;
	if(meminfo_22){
		val->freeram <<= PAGE_SHIFT;
		val->bufferram <<= PAGE_SHIFT;
		val->totalram <<= PAGE_SHIFT;
		val->sharedram <<= PAGE_SHIFT;
	}
}

pte_t __bad_page(void)
{
	clear_page(empty_bad_page);
        return pte_mkdirty(mk_pte((struct page *) empty_bad_page, 
				  PAGE_SHARED));
}

/* This can't do anything because nothing in the kernel image can be freed
 * since it's not in kernel physical memory.
 */

void free_initmem(void)
{
}

#ifdef CONFIG_BLK_DEV_INITRD

void free_initrd_mem(unsigned long start, unsigned long end)
{
	if (start < end)
		printk ("Freeing initrd memory: %ldk freed\n", 
			(end - start) >> 10);
	for (; start < end; start += PAGE_SIZE) {
		ClearPageReserved(virt_to_page(start));
		set_page_count(virt_to_page(start), 1);
		free_page(start);
		totalram_pages++;
	}
}
	
#endif

int do_check_pgt_cache(int low, int high)
{
        int freed = 0;
        if(pgtable_cache_size > high) {
                do {
                        if (pgd_quicklist) {
                                free_pgd_slow(get_pgd_fast());
                                freed++;
                        }
                        if (pmd_quicklist) {
                                pmd_free_slow(pmd_alloc_one_fast(NULL, 0));
                                freed++;
                        }
                        if (pte_quicklist) {
                                pte_free_slow(pte_alloc_one_fast(NULL, 0));
                                freed++;
                        }
                } while(pgtable_cache_size > low);
        }
        return freed;
}

void show_mem(void)
{
        int i, total = 0, reserved = 0;
        int shared = 0, cached = 0;
        int highmem = 0;

        printk("Mem-info:\n");
        show_free_areas();
        printk("Free swap:       %6dkB\n", nr_swap_pages<<(PAGE_SHIFT-10));
        i = max_mapnr;
        while(i-- > 0) {
                total++;
                if(PageHighMem(mem_map + i))
                        highmem++;
                if(PageReserved(mem_map + i))
                        reserved++;
                else if(PageSwapCache(mem_map + i))
                        cached++;
                else if(page_count(mem_map + i))
                        shared += page_count(mem_map + i) - 1;
        }
        printk("%d pages of RAM\n", total);
        printk("%d pages of HIGHMEM\n", highmem);
        printk("%d reserved pages\n", reserved);
        printk("%d pages shared\n", shared);
        printk("%d pages swap cached\n", cached);
        printk("%ld pages in page table cache\n", pgtable_cache_size);
        show_buffers();
}

unsigned long kmem_top = 0;

unsigned long get_kmem_end(void)
{
	if(kmem_top == 0) kmem_top = host_task_size - ABOVE_KMEM;
	return(kmem_top);
}

void set_kmem_end(unsigned long new)
{
	kmem_top = new;
}

static int __init uml_mem_setup(char *line, int *add)
{
	char *retptr;
	physmem_size = memparse(line,&retptr);
	return 0;
}
__uml_setup("mem=", uml_mem_setup,
"mem=<Amount of desired ram>\n"
"    This controls how much \"physical\" memory the kernel allocates\n"
"    for the system. The size is specified as a number followed by\n"
"    one of 'k', 'K', 'm', 'M', which have the obvious meanings.\n"
"    This is not related to the amount of memory in the physical\n"
"    machine. It can be more, and the excess, if it's ever used, will\n"
"    just be swapped out.\n        Example: mem=64M\n\n"
);

struct page *arch_validate(struct page *page, int mask, int order)
{
	unsigned long addr, zero = 0;
	int i;

 again:
	if(page == NULL) return(page);
	if(PageHighMem(page)) return(page);

	addr = (unsigned long) page_address(page);
	for(i = 0; i < (1 << order); i++){
		current->thread.fault_addr = (void *) addr;
		if(__do_copy_to_user((void *) addr, &zero, 
				     sizeof(zero),
				     &current->thread.fault_addr,
				     &current->thread.fault_catcher)){
			if(!(mask & __GFP_WAIT)) return(NULL);
			else break;
		}
		addr += PAGE_SIZE;
	}
	if(i == (1 << order)) return(page);
	page = alloc_pages(mask, order);
	goto again;
}

static struct list_head vm_reserved = LIST_HEAD_INIT(vm_reserved);

static struct vm_reserved head = {
	list :		LIST_HEAD_INIT(head.list),
	start :		0,
	end :		0xffffffff
};

static struct vm_reserved tail = {
	list :		LIST_HEAD_INIT(tail.list),
	start :		0,
	end :		0xffffffff
};

void set_usable_vm(unsigned long start, unsigned long end)
{
	list_add(&head.list, &vm_reserved);
	list_add(&tail.list, &head.list);
	head.end = start;
	tail.start = end;
}

int reserve_vm(unsigned long start, unsigned long end, void *e)
	       
{
	struct vm_reserved *entry = e, *reserved, *prev;
	struct list_head *ele;

	list_for_each(ele, &vm_reserved){
		reserved = list_entry(ele, struct vm_reserved, list);
		if(reserved->start >= end) goto found;
	}
	panic("Reserved vm out of range");
 found:
	prev = list_entry(ele->prev, struct vm_reserved, list);
	if(prev->end > start)
		panic("Can't reserve vm");
	if(entry == NULL)
		entry = kmalloc(sizeof(*entry), GFP_KERNEL);
	if(entry == NULL){
		printk("reserve_vm : Failed to allocate entry\n");
		return(-ENOMEM);
	}
	*entry = ((struct vm_reserved) 
		{ list :	LIST_HEAD_INIT(entry->list),
		  start :	start,
		  end :		end });
	list_add(&entry->list, &prev->list);
	return(0);
}

unsigned long get_vm(unsigned long len)
{
	struct vm_reserved *this, *next;
	struct list_head *ele;
	unsigned long start;
	int err;
	
	list_for_each(ele, &vm_reserved){
		this = list_entry(ele, struct vm_reserved, list);
		next = list_entry(ele->next, struct vm_reserved, list);
		if((this->start < next->start) && 
		   (this->end + len + PAGE_SIZE <= next->start))
			goto found;
	}
	return(0);
 found:
	start = (unsigned long) ROUND_UP(this->end) + PAGE_SIZE;
	err = reserve_vm(start, start + len, NULL);
	if(err) return(0);
	return(start);
}

int nregions(void)
{
	return(NREGIONS);
}

void setup_range(int fd, char *driver, unsigned long start, unsigned long len,
		 int need_vm, struct mem_region *region, void *reserved)
{
	int i, cur;

	do {
		cur = min(len, (unsigned long) REGION_SIZE);
		i = setup_one_range(fd, driver, start, cur, region);
		region = regions[i];
		if(need_vm && setup_region(region, reserved)){
			kfree(region);
			regions[i] = NULL;
			return;
		}
		start += cur;
		len -= cur;
	} while(len > 0);
}

struct iomem {
	char *name;
	int fd;
	unsigned long size;
};

struct iomem iomem_regions[NREGIONS] = { [ 0 ... NREGIONS - 1 ] = 
					 { name : 	NULL,
					   fd : 	-1,
					   size :	0 } };

int num_iomem_regions = 0;

void add_iomem(char *name, int fd, int size)
{
	if(num_iomem_regions == sizeof(iomem_regions)/sizeof(iomem_regions[0]))
		return;
	size = (size + PAGE_SIZE - 1) & PAGE_MASK;
	iomem_regions[num_iomem_regions++] = 
		((struct iomem) { name :	name,
				  fd :		fd,
				  size :	size } );
}

int setup_iomem(void)
{
	struct iomem *iomem;
	int i;

	for(i = 0; i < num_iomem_regions; i++){
		iomem = &iomem_regions[i];
		setup_range(iomem->fd, iomem->name, -1, iomem->size, 1, NULL, 
			    NULL);
	}
	return(0);
}

__initcall(setup_iomem);

#define PFN_UP(x)	(((x) + PAGE_SIZE-1) >> PAGE_SHIFT)
#define PFN_DOWN(x)	((x) >> PAGE_SHIFT)

static struct mem_region physmem_region;
static struct vm_reserved physmem_reserved;

void setup_physmem(unsigned long start, unsigned long reserve_end,
		   unsigned long len)
{
	struct mem_region *region = &physmem_region;
	struct vm_reserved *reserved = &physmem_reserved;
	unsigned long cur;
	int do_free = 1, bootmap_size;

	do {
		cur = min(len, (unsigned long) REGION_SIZE);
		if(region == NULL) 
			region = alloc_bootmem_low_pages(sizeof(*region));
		if(reserved == NULL) 
			reserved = alloc_bootmem_low_pages(sizeof(*reserved));
		if((region == NULL) || (reserved == NULL))
			panic("Couldn't allocate physmem region or vm "
			      "reservation\n");
		setup_range(-1, NULL, start, cur, 1, region, reserved);

		if(do_free){
			unsigned long reserve = reserve_end - start;
			int pfn = PFN_UP(__pa(reserve_end));
			int delta = (len - reserve) >> PAGE_SHIFT;

			bootmap_size = init_bootmem(pfn, pfn + delta);
			free_bootmem(__pa(reserve_end) + bootmap_size,
				     cur - bootmap_size - reserve);
			do_free = 0;
		}
		start += cur;
		len -= cur;
		region = NULL;
		reserved = NULL;
	} while(len > 0);
}

struct mem_region *phys_region(unsigned long phys)
{
	unsigned int n = phys_region_index(phys);

	if(regions[n] == NULL) 
		panic("Physical address in uninitialized region");
	return(regions[n]);
}

unsigned long phys_offset(unsigned long phys)
{
	return(phys_addr(phys));
}

struct page *phys_mem_map(unsigned long phys)
{
	return((struct page *) phys_region(phys)->mem_map);
}

struct page *pte_mem_map(pte_t pte)
{
	return(phys_mem_map(pte_val(pte)));
}

struct mem_region *page_region(struct page *page, int *index_out)
{
	int i;
	struct mem_region *region;
	struct page *map;

	for(i = 0; i < NREGIONS; i++){
		region = regions[i];
		if(region == NULL) continue;
		map = region->mem_map;
		if((page >= map) && (page < &map[region->len >> PAGE_SHIFT])){
			if(index_out != NULL) *index_out = i;
			return(region);
		}
	}
	panic("No region found for page");
	return(NULL);
}

struct page *page_mem_map(struct page *page)
{
	return((struct page *) page_region(page, NULL)->mem_map);
}

extern unsigned long region_pa(void *virt)
{
	struct mem_region *region;
	unsigned long addr = (unsigned long) virt;
	int i;

	for(i = 0; i < NREGIONS; i++){
		region = regions[i];
		if(region == NULL) continue;
		if((region->start <= addr) && 
		   (addr <= region->start + region->len))
			return(mk_phys(addr - region->start, i));
	}
	panic("region_pa : no region for virtual address");
	return(0);
}

extern void *region_va(unsigned long phys)
{
	return((void *) (phys_region(phys)->start + phys_addr(phys)));
}

unsigned long page_to_phys(struct page *page)
{
	int n;
	struct mem_region *region = page_region(page, &n);
	struct page *map = region->mem_map;
	return(mk_phys((page - map) << PAGE_SHIFT, n));
}

static int setup_mem_maps(void)
{
	struct mem_region *region;
	int i;

	for(i = 0; i < NREGIONS; i++){
		region = regions[i];
		if((region != NULL) && (region->fd > 0)) init_maps(region);
	}
	return(0);
}

__initcall(setup_mem_maps);

/*
 * Overrides for Emacs so that we follow Linus's tabbing style.
 * Emacs will notice this stuff at the end of the file and automatically
 * adjust the settings for this buffer only.  This must remain at the end
 * of the file.
 * ---------------------------------------------------------------------------
 * Local variables:
 * c-file-style: "linux"
 * End:
 */
