/*
 * Written by: Patricia Gaughen, IBM Corporation
 *
 * Copyright (C) 2002, IBM Corp.
 *
 * All rights reserved.          
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
 * NON INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Send feedback to <gone@us.ibm.com>
 */

#include <linux/config.h>
#include <linux/mm.h>
#include <linux/bootmem.h>
#include <linux/mmzone.h>
#include <linux/highmem.h>
#include <asm/e820.h>
#include <asm/setup.h>

struct pfns {
	unsigned long start_pfn;
	unsigned long max_pfn;
};

plat_pg_data_t *plat_node_data[MAX_NUMNODES];
bootmem_data_t plat_node_bdata;
struct pfns plat_node_bootpfns[MAX_NUMNODES];

extern unsigned long find_max_low_pfn(void);
extern void find_max_pfn(void);
extern void pagetable_init (void);
extern void kmap_init(void);
extern void one_highpage_init(struct page *, int, int);
extern inline int page_is_ram (unsigned long);

extern unsigned long long nodes_mem_start[], nodes_mem_size[];
extern struct e820map e820;
extern char _end;
extern unsigned long highend_pfn, highstart_pfn;
extern unsigned long max_low_pfn;
extern unsigned long totalram_pages;
extern unsigned long totalhigh_pages;

static void __init find_max_pfn_node(int nid)
{
	unsigned long node_datasz;
	unsigned long start, end;

	start = plat_node_bootpfns[nid].start_pfn = PFN_UP(nodes_mem_start[nid]);
	end = PFN_DOWN(nodes_mem_start[nid]) + PFN_DOWN(nodes_mem_size[nid]);

	if (start >= end) {
		BUG();
	}
	if (end > max_pfn) {
		end = max_pfn;
	}
	plat_node_bootpfns[nid].max_pfn = end;

	node_datasz = PFN_UP(sizeof(plat_pg_data_t));
	PLAT_NODE_DATA(nid) = (plat_pg_data_t *)(__va(min_low_pfn << PAGE_SHIFT));
	min_low_pfn += node_datasz;
}

static void __init register_bootmem_low_pages(unsigned long system_max_low_pfn)
{
	int i;

	for (i = 0; i < e820.nr_map; i++) {
		unsigned long curr_pfn, last_pfn, size;
 		/*
		 * Reserve usable low memory
		 */
		if (e820.map[i].type != E820_RAM)
			continue;
		/*
		 * We are rounding up the start address of usable memory:
		 */
		curr_pfn = PFN_UP(e820.map[i].addr);
		if (curr_pfn >= system_max_low_pfn)
			continue;
		/*
		 * ... and at the end of the usable range downwards:
		 */
		last_pfn = PFN_DOWN(e820.map[i].addr + e820.map[i].size);

		if (last_pfn > system_max_low_pfn)
			last_pfn = system_max_low_pfn;

		/*
		 * .. finally, did all the rounding and playing
		 * around just make the area go away?
		 */
		if (last_pfn <= curr_pfn)
			continue;

		size = last_pfn - curr_pfn;
		free_bootmem_node(NODE_DATA(0), PFN_PHYS(curr_pfn), PFN_PHYS(size));
	}
}

unsigned long __init setup_memory(void)
{
	int nid;
	unsigned long bootmap_size, system_start_pfn, system_max_low_pfn;

	get_memcfg_numa();

	/*
	 * partially used pages are not usable - thus
	 * we are rounding upwards:
	 */
	system_start_pfn = min_low_pfn = PFN_UP(__pa(&_end));

	find_max_pfn();
	system_max_low_pfn = max_low_pfn = find_max_low_pfn();

#ifdef CONFIG_HIGHMEM
		highstart_pfn = highend_pfn = max_pfn;
		if (max_pfn > system_max_low_pfn) {
			highstart_pfn = system_max_low_pfn;
		}
		printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",
		       pages_to_mb(highend_pfn - highstart_pfn));
#endif
	printk(KERN_NOTICE "%ldMB LOWMEM available.\n",
			pages_to_mb(system_max_low_pfn));
	
	for (nid = 0; nid < numnodes; nid++)
	{	
		find_max_pfn_node(nid);

	}

	NODE_DATA(0)->bdata = &plat_node_bdata;

	/*
	 * Initialize the boot-time allocator (with low memory only):
	 */
	bootmap_size = init_bootmem_node(NODE_DATA(0), min_low_pfn, 0, system_max_low_pfn);

	register_bootmem_low_pages(system_max_low_pfn);

	/*
	 * Reserve the bootmem bitmap itself as well. We do this in two
	 * steps (first step was init_bootmem()) because this catches
	 * the (very unlikely) case of us accidentally initializing the
	 * bootmem allocator with an invalid RAM area.
	 */
	reserve_bootmem_node(NODE_DATA(0), HIGH_MEMORY, (PFN_PHYS(min_low_pfn) +
		 bootmap_size + PAGE_SIZE-1) - (HIGH_MEMORY));

	/*
	 * reserve physical page 0 - it's a special BIOS page on many boxes,
	 * enabling clean reboots, SMP operation, laptop functions.
	 */
	reserve_bootmem_node(NODE_DATA(0), 0, PAGE_SIZE);

	/*
	 * But first pinch a few for the stack/trampoline stuff
	 * FIXME: Don't need the extra page at 4K, but need to fix
	 * trampoline before removing it. (see the GDT stuff)
	 */
	reserve_bootmem_node(NODE_DATA(0), PAGE_SIZE, PAGE_SIZE);

	/*
	 * Find and reserve possible boot-time SMP configuration:
	 */
	find_smp_config();

#ifdef CONFIG_BLK_DEV_INITRD
	if (LOADER_TYPE && INITRD_START) {
		if (INITRD_START + INITRD_SIZE <= (system_max_low_pfn << PAGE_SHIFT)) {
			reserve_bootmem(INITRD_START, INITRD_SIZE);
			initrd_start =
				INITRD_START ? INITRD_START + PAGE_OFFSET : 0;
			initrd_end = initrd_start+INITRD_SIZE;
		}
		else {
			printk(KERN_ERR "initrd extends beyond end of memory "
			    "(0x%08lx > 0x%08lx)\ndisabling initrd\n",
			    INITRD_START + INITRD_SIZE,
			    system_max_low_pfn << PAGE_SHIFT);
			initrd_start = 0;
		}
	}
#endif

	return system_max_low_pfn;
}

/*
 * paging_init() sets up the page tables - note that the first 8MB are
 * already mapped by head.S.
 *
 * This routines also unmaps the page at virtual kernel address 0, so
 * that we can trap those pesky NULL-reference errors in the kernel.
 */
void __init paging_init(void)
{

	int nid;
	
	pagetable_init();

	__asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_pg_dir)));

#if CONFIG_X86_PAE
	/*
	 * We will bail out later - printk doesnt work right now so
	 * the user would just see a hanging kernel.
	 */
	if (cpu_has_pae)
		set_in_cr4(X86_CR4_PAE);
#endif

	__flush_tlb_all();

#ifdef CONFIG_HIGHMEM
	kmap_init();
#endif

	for (nid = 0; nid < numnodes; nid++) {
		unsigned long zones_size[MAX_NR_ZONES] = {0, 0, 0};
		unsigned int max_dma;

		unsigned long low = max_low_pfn;
		unsigned long high = plat_node_bootpfns[nid].max_pfn;
		unsigned long start = plat_node_bootpfns[nid].start_pfn;
		
		max_dma = virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT;

		if (start > low) {
#ifdef CONFIG_HIGHMEM
		  zones_size[ZONE_HIGHMEM] = high - start;
#endif
		} else {
			if (low < max_dma)
				zones_size[ZONE_DMA] = low;
			else {
				zones_size[ZONE_DMA] = max_dma;
				zones_size[ZONE_NORMAL] = low - max_dma;
#ifdef CONFIG_HIGHMEM
				zones_size[ZONE_HIGHMEM] = high - low;
#endif
			}
		}
		free_area_init_node(nid, NODE_DATA(nid), 0, zones_size, start, 0);
	}
	return;
}


int __init mem_init_free_pages(int bad_ppro)
{
	int reservedpages;
	int nid;
	unsigned long pfn;

	bad_ppro = ppro_with_ram_bug();

	/* this will put all low memory onto the freelists */
	totalram_pages += free_all_bootmem_node(NODE_DATA(0));

	reservedpages = 0;
	for (pfn = 0; pfn < max_low_pfn; pfn++)
		/*
		 * Only count reserved RAM pages
		 */
		if (page_is_ram(pfn) && PageReserved(mem_map+pfn))
			reservedpages++;
#ifdef CONFIG_HIGHMEM
	for (nid = 0; nid < numnodes; nid++) {
		unsigned long node_pfn, node_high_size, zone_start_pfn;
		struct page * zone_mem_map;
		
		node_high_size = NODE_DATA(nid)->node_zones[ZONE_HIGHMEM].size;
		zone_mem_map = NODE_DATA(nid)->node_zones[ZONE_HIGHMEM].zone_mem_map;
		zone_start_pfn = NODE_DATA(nid)->node_zones[ZONE_HIGHMEM].zone_start_pfn;

		printk("Initializing highpages for node %d\n", nid);
		for (node_pfn = 0; node_pfn < node_high_size; node_pfn++) {
			one_highpage_init((struct page *) (zone_mem_map + node_pfn), zone_start_pfn + node_pfn, bad_ppro);
		}
	}
	totalram_pages += totalhigh_pages;
#endif
	return reservedpages;
}

void __init mem_init_set_max_mapnr(void)
{
	unsigned long lmax_mapnr;
	int nid;
	
#ifdef CONFIG_HIGHMEM
	highmem_start_page = mem_map + NODE_DATA(0)->node_zones[ZONE_HIGHMEM].zone_start_mapnr;
	num_physpages = highend_pfn;
	num_mappedpages = max_low_pfn;

	for (nid = 0; nid < numnodes; nid++) {
		lmax_mapnr = PLAT_NODE_DATA_STARTNR(nid) + PLAT_NODE_DATA_SIZE(nid);
		if (lmax_mapnr > max_mapnr) {
			max_mapnr = lmax_mapnr;
		}
	}
	
#else
	max_mapnr = num_mappedpages = num_physpages = max_low_pfn;
#endif
}
