/*
 *  arch/ppc/mm/init.c
 *
 *  Teleworld Customer Device version, loosly derived from the
 *  code indicated below.
 *    Copyright (C) 1998 Teleworld, Inc. All Rights Reserved.
 *
 *----------
 *
 *  PowerPC version 
 *    Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
 *
 *  Adapted for the Power Macintosh by Paul Mackerras.
 *    Copyright (C) 1996 Paul Mackerras (paulus@cs.anu.edu.au)
 *
 *  Derived from "arch/i386/mm/init.c"
 *    Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
 *
 *  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.
 *
 */

#include <linux/config.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/swap.h>
#include <linux/stddef.h>
#include <linux/init.h>
#include <asm/prom.h>
#include <asm/io.h>
#include <asm/mmu_context.h>
#include <asm/pgtable.h>
#include <asm/mmu.h>
#include <asm/ppcops.h>

#define MMU_PAGE_SIZE	4096

atomic_t next_mmu_context;
int NTLB_WIRED = 0;
extern pgd_t swapper_pg_dir[];

extern void die_if_kernel(char *,struct pt_regs *,long);
extern void show_net_buffers(void);
extern char __init_begin, __init_end;
unsigned long dynomem;
int mem_init_done;
#ifndef __SMP__
struct pgtable_cache_struct quicklists;
#endif
struct inode_operations proc_ppc_htab_inode_operations = { 0 };

/*
 * BAD_PAGE is the page that is used for page faults when linux
 * is out-of-memory. Older versions of linux just did a
 * do_exit(), but using this instead means there is less risk
 * for a process dying in kernel mode, possibly leaving a inode
 * unused etc..
 *
 * BAD_PAGETABLE is the accompanying page-table: it is initialized
 * to point to BAD_PAGE entries.
 *
 * ZERO_PAGE is a special page that is used for zero-initialized
 * data and COW.
 */
unsigned long empty_bad_page_table;

extern int get_memconfig(void);
int PromMemConfig;

pte_t * __bad_pagetable(void)
{
	memset((void *)empty_bad_page_table, 0, PAGE_SIZE);
	return (pte_t *) empty_bad_page_table;
}

unsigned long empty_bad_page;

pte_t __bad_page(void)
{
	memset((void *)empty_bad_page, 0, PAGE_SIZE);
	return pte_mkdirty(mk_pte(empty_bad_page, PAGE_SHARED));
}

void __bad_pte(pmd_t *pmd)
{
	printk("Bad pmd in pte_alloc: %08lx\n", pmd_val(*pmd));
	pmd_val(*pmd) = (unsigned long) BAD_PAGETABLE;
}

pte_t *get_pte_slow(pmd_t *pmd, unsigned long offset)
{
        pte_t *pte;

        if (pmd_none(*pmd)) {
		if (!mem_init_done) {
			printk("Called get_pte_slow before memory ready!\n");
			for (;;) {}
		}
		else if ((pte = (pte_t *) get_zero_page_fast()) == NULL)
			if ((pte = (pte_t *) __get_free_page(GFP_KERNEL)))
				clear_page((unsigned long)pte);
                if (pte) {
                        pmd_val(*pmd) = (unsigned long)pte;
                        return pte + offset;
                }
		pmd_val(*pmd) = (unsigned long)BAD_PAGETABLE;
                return NULL;
        }
        if (pmd_bad(*pmd)) {
                __bad_pte(pmd);
                return NULL;
        }
        return (pte_t *) pmd_page(*pmd) + offset;
}

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)
				free_pmd_slow(get_pmd_fast()), freed++;
			if(pte_quicklist)
				free_pte_slow(get_pte_fast()), freed++;
		} while(pgtable_cache_size > low);
	}
	return freed;
}

void show_mem(void)
{
	int i,free = 0,total = 0,reserved = 0;
	int shared = 0, cached = 0;
	struct task_struct *p;

	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 (PageReserved(mem_map+i))
			reserved++;
		else if (PageSwapCache(mem_map+i))
			cached++;
		else if (!atomic_read(&mem_map[i].count))
			free++;
		else
			shared += atomic_read(&mem_map[i].count) - 1;
	}
	printk("%d pages of RAM\n",total);
	printk("%d free pages\n",free);
	printk("%d reserved pages\n",reserved);
	printk("%d pages shared\n",shared);
	printk("%d pages swap cached\n",cached);
#if 0
	printk("%d pages in page table cache\n",(int)pgtable_cache_size);
#endif
#ifdef CONFIG_NET
	show_net_buffers();
#endif
	printk("%-8s %3s %8s %8s %8s %9s %8s", "Process", "Pid",
	       "Ctx", "Ctx<<4", "Last Sys", "pc", "task");
#ifdef __SMP__
	printk(" %3s", "CPU");
#endif /* __SMP__ */
	printk("\n");
	for_each_task(p)
	{
		printk("%-8.8s %3d %8ld %8ld %8ld %c%08lx %08lx ",
		       p->comm,p->pid,
		       (p->mm)?p->mm->context:0,
		       (p->mm)?(p->mm->context<<4):0,
		       p->thread.last_syscall,
		       (p->thread.regs)?user_mode(p->thread.regs) ? 'u' : 'k' : '?',
		       (p->thread.regs)?p->thread.regs->nip:0,
		       (ulong)p);
		{
			int iscur = 0;
#ifdef __SMP__
			printk("%3d ", p->processor);
			if ( (p->processor != NO_PROC_ID) &&
			     (p == current_set[p->processor]) )
			{
				iscur = 1;
				printk("current");
			}
#else
			if ( p == current )
			{
				iscur = 1;
				printk("current");
			}
			
			if ( p == last_task_used_math )
			{
				if ( iscur )
					printk(",");
				printk("last math");
			}			
#endif /* __SMP__ */
			printk("\n");
		}
	}
}

void si_meminfo(struct sysinfo *val)
{
	int i;

	i = max_mapnr;
	val->totalram = 0;
	val->sharedram = 0;
	val->freeram = nr_free_pages << PAGE_SHIFT;
	val->bufferram = atomic_read(&buffermem);
	while (i-- > 0)  {
		if (PageReserved(mem_map+i))
			continue;
		val->totalram++;
		if (!atomic_read(&mem_map[i].count))
			continue;
		val->sharedram += atomic_read(&mem_map[i].count) - 1;
	}
	val->totalram <<= PAGE_SHIFT;
	val->sharedram <<= PAGE_SHIFT;
	return;
}

extern unsigned long free_area_init(unsigned long, unsigned long);

/*
 * paging_init() sets up the page tables - in fact we've already done this.
 */
unsigned long __init paging_init(unsigned long start_mem, unsigned long end_mem)
{
	/*
	 * Grab some memory for bad_page and bad_pagetable to use.
	 */
	empty_bad_page = start_mem;
	empty_bad_page_table = start_mem + PAGE_SIZE;
	start_mem += 2 * PAGE_SIZE;

	/* note: free_area_init uses its second argument
	   to size the mem_map array. */
	start_mem = free_area_init(start_mem, end_mem);
	return start_mem;
}

int __init calc_vmalloc_start(void)
{

	if (PromMemConfig > 16)
		return(0x80000000 + (32*1024*1024));
	else 
		return(0x80000000 + (16*1024*1024));
#if 0
	switch (PromMemConfig) {
	case 16:
		return(0x80000000 + (16*1024*1024));
	case 32:
		return(0x80000000 + (32*1024*1024));
	default:
	case 8:
		return(0x80000000 + (16*1024*1024));
	}
#endif
}

void __init mem_init(unsigned long start_mem, unsigned long end_mem)
{
	unsigned long a;
	unsigned long codesize, total_mem;
	int np;
	extern char etext[], _stext[];
	extern struct mm_struct init_mm;

	end_mem &= PAGE_MASK;
	high_memory = (void *) end_mem;
	max_mapnr = MAP_NR(high_memory);
	num_physpages = max_mapnr;	/* RAM is assumed contiguous */

	/* mark usable pages in the mem_map[] */
	start_mem = PAGE_ALIGN(start_mem);
	dynomem = start_mem;
	np = 0;
	for (a = start_mem; a < end_mem ; a += PAGE_SIZE) {
		clear_bit(PG_reserved, &mem_map[MAP_NR(a)].flags);
		atomic_set(&mem_map[MAP_NR(a)].count, 1);
		free_page(a);
		np++;
	}
	/* initialize region for kernel virtual allocation */
	init_mm.mmap->vm_start = VMALLOC_START;
	init_mm.mmap->vm_end = VMALLOC_START + (np * PAGE_SIZE);
	init_task.mm = 0;
	init_mm.context = NO_CONTEXT;
	total_mem = end_mem - PAGE_OFFSET;
	codesize = PAGE_ALIGN((unsigned)etext) - (unsigned)_stext;
	a = nr_free_pages << PAGE_SHIFT;
	printk("Memory: %luk/%luk available (%luk kernel code, %luk data)\n",
	       a >> 10, total_mem >> 10,
	       codesize >> 10, (total_mem - a - codesize) >> 10);
	printk("Kernel virtual start=0x%lx, end=0x%lx\n", init_mm.mmap->vm_start,
		init_mm.mmap->vm_end);

	mem_init_done = 1;
}

void __init free_initmem(void)
{
	unsigned long a;
	unsigned long num_freed_pages = 0;
	unsigned long num_prom_freed = 0;
    struct page *p;

#define FREESEC(START,END,CNT) do { \
	a = (unsigned long)(&START); \
	for (; a < (unsigned long)(&END); a += PAGE_SIZE) { \
	  	clear_bit(PG_reserved, &mem_map[MAP_NR(a)].flags); \
		atomic_set(&mem_map[MAP_NR(a)].count, 1); \
		free_page(a); \
		CNT++; \
	} \
} while (0)

	FREESEC(__init_begin,__init_end,num_freed_pages);

	/*
	 * We can free up the PROM memory area as well.
	 */
        a = (unsigned long) PAGE_OFFSET;
	for (p = &mem_map[0]; p < &mem_map[MAP_NR(KERNELBASE)]; p++) {
		clear_bit(PG_reserved, &p->flags);
		atomic_set(&p->count, 1);
		free_page(a);
                a += PAGE_SIZE;
		num_prom_freed++;
	}

	printk ("Freeing unused kernel memory: %ldk init",
		(num_freed_pages * PAGE_SIZE) >> 10);
	printk (" %ldk prom", (num_prom_freed * PAGE_SIZE) >> 10);
	printk("\n");
}

/* Kernel MMU setup & lowest level hardware support */

/*
 * We "wire" a TLB entry permanently for the kernel.
 * XXX If bigger than 16M physical, need to do multiple TLB entries
 *
 * The memory management structure of the 403 series is a bit
 * complicated, since there are three different translation
 * mechanisms that all interact, and have different cache policies:
 * TLB mapping (virtual mode), zones (virtual mode) and boundary
 * registers (real mode). Additionally, the Bus Interface Unit (BIU)
 * strips the top bit of all real addresses it gets, meaning that
 * there are two addresses for any particular real location. This
 * bit, however, *is* carried throughout all other calculations
 * (real page numbers in the TLB, for instance). This means that 
 * it's pretty easy to get cache synonyms (multiple copies of the
 * same line at different virtual addresses in the cache) if things
 * aren't managed carefully.
 *
 * Therefore, I've adopted the following set of policies:
 *
 * 1. The kernel is always mapped at 0x8000000 on up, both in
 *    virtual and real mode, so there is never any translation
 *    to worry about. User code occupies the lower 2GB starting
 *    at virtual 0.
 * 2. Kernel pages are mapped with PID 0 (meaning the pages are
 *    shared among all processes); each unique process has a unique
 *    PID. User-level access to kernel pages is restricted using
 *    the zone register. Kernel pages are loaded into zone 0, 
 *    which is set for no user access. User pages are always loaded
 *    into zone 1, which is set for both user and kernel access.
 *    Other zones are currently not used.
 * 3. Using the cacheability control registers (ICCR and DCCR),
 *    the kernel address space is set for caching, but no other
 *    addresses are. This means that in real mode, access to
 *    the lower 2GB of address space is non-cached. Kernel DRAM
 *    pages are cached in virtual mode; this setup means that whether
 *    the kernel is running in virtual or real mode, it uses identical
 *    addresses for DRAM, and these access are always cached. This is
 *    important because the exception handlers run in real mode,
 *    and get executed alot, so we want their accesses cached.
 * 4. Non-cached access to kernel DRAM (buffers, for instance), must
 *    occur through a separate TLB mapping that does non-cached access.
 *    By convention, this area starts at 0xc0000000, continuing for the
 *    size of DRAM. IO devices are mapped using the TLB non-cached,
 *    at arbitraily chosen addresses above 0x80000000.
 * 5. Various page sizes are used in the TLB for efficiency for
 *    kernel DRAM and IO pages; user pages are always 4K in size,
 *    unless the memory is special use (e.g., memory pools). The first
 *    NTLB_WIRED entries are reserved for permanent TLB entries (e.g.,
 *    kernel DRAM); otherwise, a random replacement strategy is used
 *    based on the CPU timebase.
 * 6. The kernel spends as little time in real mode as possible. The
 *    general philosophy of exception handlers is 1) save state, 
 *    2) go virtual (into C). This may need to be revisited for certain
 *    handlers (TLB miss, for example).
 *
 * -- jmb
 */

#define DRAM1	(1 * 1024 * 1024)
#define DRAM2	(2 * 1024 * 1024)
#define DRAM4	(4 * 1024 * 1024)
#define DRAM8	(8 * 1024 * 1024)
#define DRAM12	(12 * 1024 * 1024)
#define DRAM16	(16 * 1024 * 1024)


/* Signature should be 'TiVo'.  If so, params points to boot params */
void __init
MMU_init(int signature, char *params)
{
#define CONFIG_TCD_MEMLOG 1
#if CONFIG_TCD_MEMLOG
    	int initmemlog = 0;
#endif

	void tlbWirekernel(unsigned long va, unsigned long pa, int, int, int);
	void flush_instruction_cache(void);
	int i;
	unsigned long kaddr = (KERNELBASE & ~0xffffff);
	extern void s403_init(void);
	extern void save_boot_params(char *params);

	asm("tlbia");
	asm("sync;isync");

	if (signature == 'TiVo')
		save_boot_params(params);
        PromMemConfig = get_memconfig();

#if CONFIG_TCD_MEMLOG
	if( PromMemConfig == 32 )
	{
	    int len = strlen( params ) - 5;

	    for( i = 0; i < len; ++i )
	    {
		if( params[ i ] == 'm' )
		{
		    if( strncmp( &params[i], "memlog", 6 ) == 0 )
		    {
			initmemlog = 1;			
	    
			PromMemConfig = 16;	
		    }		    
		}		
	    }
	}	
#endif
	if (PromMemConfig > 16)
		i = 32;
	else 
		i = 16;

	switch (i) {
	case 16:
	default:
		tlbWirekernel(kaddr, kaddr, TLBHI_PGSZ_16M, NTLB_WIRED++, 1);
		break;
	case 32:
		tlbWirekernel(kaddr, kaddr, TLBHI_PGSZ_16M, NTLB_WIRED++, 1);
		tlbWirekernel(kaddr+DRAM16, kaddr+DRAM16, TLBHI_PGSZ_16M, NTLB_WIRED++, 1);
		break;
	}
#if 0
/* ??? Why is this here ??? scs */
        tlbWirekernel(0x90000000, kaddr, TLBHI_PGSZ_16M, NTLB_WIRED++, 0);
#endif

	/* place for kernel page tables */
	init_mm.pgd = (pgd_t *)swapper_pg_dir;

	/* Zone 0 - kernel (above 0x80000000), zone 1 - user */
	mtspr(SPRN_ZPR, 0x2aaaaaaa);
	mtspr(SPRN_DCWR, 0);		/* all caching is write-back */

	/* flush all data cache sets */
	for (i = 0; i < 256; i++) {
		asm("dcbf 0,%0" : : "r" (i << 4));
		asm("dccci 0,%0" : : "r" (i << 4));
	}
	mtspr(SPRN_DCCR, 0x00008000);

	flush_instruction_cache();
	mtspr(SPRN_ICCR, 0x00008000);

	atomic_set(&next_mmu_context, 0);

	/* on-board serial port */
	tlbWirekernel(IOBASE_SERIAL, TCD_IO_BASE_SERIAL, TLBHI_PGSZ_4K, NTLB_WIRED++, 0);

	/* Ethernet chip */
	tlbWirekernel(IOBASE_8390, TCD_IO_BASE_8390, TLBHI_PGSZ_4K, NTLB_WIRED++, 0);

#ifndef CONFIG_TCD_EVAL
	/* input section is 32MB*/
	tlbWirekernel(IOBASE_INPUT, TCD_IO_BASE_INPUT,
		TLBHI_PGSZ_16M, NTLB_WIRED++, 0);
	tlbWirekernel(IOBASE_INPUT+DRAM16, TCD_IO_BASE_INPUT+DRAM16,
		TLBHI_PGSZ_16M, NTLB_WIRED++, 0);
#if 1
#define XXX 0xf9800000
	tlbWirekernel(XXX, XXX, TLBHI_PGSZ_16M, NTLB_WIRED++, 0);
#endif

	/* other section is 8MB */
	tlbWirekernel(_IOBASE_OTHER_, _IOBASE_OTHER_, TLBHI_PGSZ_1M, NTLB_WIRED++, 0);
	tlbWirekernel(_IOBASE_OTHER_+DRAM1, _IOBASE_OTHER_+DRAM1, TLBHI_PGSZ_4M, NTLB_WIRED++, 0);
#else
	/* UART */
	tlbWirekernel(IOBASE_UART, TCD_IO_BASE_UART, TLBHI_PGSZ_4K, NTLB_WIRED++, 0);
#endif
#ifdef CONFIG_TCD_MEMLOG
	/* Map the upper 16MB to 0xA0000000 accessable to all processes */
	if( initmemlog )
	{	    
		unsigned long tlbhi = 0xA0000000 | TLBHI_PGSZ_16M | TLBHI_VALID;
		unsigned long tlblo = 0x01000000 | TLBLO_Z_USER | TLBLO_WR;
		unsigned long opid = mfspr(SPRN_PID);
		mtspr(SPRN_PID, 0);
		asm("tlbwe %0,%1,1" : : "r" (tlblo), "r" (NTLB_WIRED));
		asm("tlbwe %0,%1,0" : : "r" (tlbhi), "r" (NTLB_WIRED));
		asm("isync");
		mtspr(SPRN_PID, opid);
		++NTLB_WIRED;

		*(long*)0x01000000 = 0x0;
	}	
#endif
	asm("sync;isync");
}

/*
 * TLB flushing:
 *
 *  - flush_tlb_all() flushes all processes TLBs
 *  - flush_tlb_mm(mm) flushes the specified mm context TLB's
 *  - flush_tlb_page(vma, vmaddr) flushes one page
 *  - flush_tlb_range(mm, start, end) flushes a range of pages
 *
 */

void tlbFlush(void);
void tlbFlush1(unsigned long va, int pid);

/*
 * The context counter has overflowed.
 * We set mm->context to NO_CONTEXT for all mm's in the system.
 * We assume we can get to all mm's by looking as tsk->mm for
 * all tasks in the system.
 */
void
mmu_context_overflow(void)
{
	struct task_struct *thead = &init_task_union.task;
	struct task_struct *tsk = thead->next_task;

	printk(KERN_DEBUG "mmu_context_overflow\n");
	while (tsk != thead) {
		if (tsk && tsk->mm && (tsk->mm->context != 0))
			tsk->mm->context = NO_CONTEXT;
	}
	atomic_set(&next_mmu_context, 0);
	tlbFlush();

	/* make sure current always has a context */
	current->mm->context = atomic_inc_return(&next_mmu_context);
	set_context(current->mm->context);
}

#if 0
/*
 * Flush all tlb/hash table entries (except perhaps for those
 * mapping RAM starting at PAGE_OFFSET, since they never change).
 */
void
flush_tlb_all(void)
{
	tlbFlush();
}

/*
 * Flush all the (user) entries for the address space described
 * by mm.  We can't rely on mm->mmap describing all the entries
 * that might be in the hash table.
 */
void
flush_tlb_mm(struct mm_struct *mm)
{
	mm->context = NO_CONTEXT;
	if (mm == current->mm) {
		get_mmu_context(current->mm);
		set_context(current->mm->context);
	}
}

/*
 * for each page addr in the range, call MMU_invalidate_page().
 * For a large range, it's quicker just to give the mm a new context.
 */
void
flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
{
	flush_tlb_mm(mm);
}
#endif

/*
 * Cache flushing operations
 */

void flush_instruction_cache() {
	unsigned long p = KERNELBASE & PAGE_MASK;
	unsigned long stop = p + (16 * 1024);
	for (; p <= stop; p += 16)
		asm("iccci 0,%0" : : "r" (p) : "r0");
	asm("sync;isync");
}

void flush_data_cache(void) {
	unsigned long p = KERNELBASE & PAGE_MASK;
	unsigned long stop = p + (8 * 1024);
	for (; p <= stop; p += 16)
		asm("dccci 0,%0" : : "r" (p) : "r0");
	asm("sync;isync");
}

void store_cache_range(unsigned long p, unsigned long stop) {
        p =       p & 0xFFFFFFF0;
        stop = stop | 0x0000000F;
	for (; p <= stop; p += 16) {
		asm("dcbf 0,%0" : : "r" (p));
		asm("icbi  0,%0" : : "r" (p));
	}
}

/*
 * Invalidate any data in the caches for a dma operation.
 */
void dma_invalidate_cache_range(unsigned long p, unsigned long stop) {
        p =       p & 0xFFFFFFF0;
        stop = stop | 0x0000000F;
	for (; p <= stop; p += 16) {
		asm("dcbi 0,%0" : : "r" (p));
	}
        asm("sync");

}

void dma_flush_cache_range(unsigned long p, unsigned long stop) {
        p =       p & 0xFFFFFFF0;
	for (; p < stop; p += 16) {
		asm("dcbf 0,%0" : : "r" (p));
	}
	asm("sync");
}

void dma_flush_cache_all() {
	flush_instruction_cache();
	flush_data_cache();
}

/*
 * Dangling FPU stuff.
 */
void giveup_fpu(struct task_struct *tsk) {}
