/* $Id: map-generic.c,v 1.2 2005/06/16 13:46:21 cgoh Exp $

 *
 * <filename>: <Description>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Equator Technologies
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/sem.h>
#include <linux/wrapper.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>

#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/mmu_context.h>
#include <asm/cache.h>
#include <asm/assert.h>
#include <asm/bitops.h>
#include <asm/cachectl.h>

#include <eti/mm.h>

#include <libdev/etiTLB.h>
#include <libdev/etiCache.h>


/******************************************************************************/

pte_t* virt_to_pte(
		struct mm_struct* mm,
		unsigned long     addr,
		pte_t*            result
	)
{
    pgd_t *pgd_p;
    pmd_t *pmd_p;
    pte_t *pte_p;

    pgd_p = pgd_offset(mm,    addr);    if (pgd_none    (*pgd_p)) { return NULL; }
    pmd_p = pmd_offset(pgd_p, addr);    if (pmd_none    (*pmd_p)) { return NULL; }
    pte_p = pte_offset(pmd_p, addr);    if (!pte_present(*pte_p)) { return NULL; }

   *result = *pte_p;

    return pte_p;
}

/******************************************************************************/

#define MAX_CLUSTER_SIZE     (PGSIZE(3))  /* 1 MB */
#define PGSIZE(pgsize)       (PAGE_SIZE << (tlbSIZE_FACTOR*(pgsize)))
#define PGMASK(pgsize)       (~(PGSIZE(pgsize)-1))
#define TLB_PGSIZE(pgsize)   ((pgsize)+1)


static void defaultCacheTLBE(u32 address){}


static void (*CacheTLBE)(u32 address)= defaultCacheTLBE;


void*  setCacheTLBE(void* newCacher)
{
    etiSWAP(CacheTLBE,newCacher,void*);

    return newCacher;
}


static void flush_all_pagesizes( struct mm_struct *mm )
{
    int    i,j,k;
    pgd_t *pgd_p= pgd_offset(mm, 0);	

    for (i=0; i<USER_PTRS_PER_PGD; i++) {
        if (!pgd_none(*pgd_p)) {
	    pmd_t *pmd_p= pmd_offset( pgd_p, 0 );

	    for (j=0; j<PTRS_PER_PMD; j++) {
        	if (!pmd_none(*pmd_p)) {
		    pte_t *pte_p= pte_offset( pmd_p, 0 );

	            for (k=0; k<PTRS_PER_PTE; k++) {
			if (pte_present(*pte_p)) {
	                    set_pte( pte_p, pte_invalidate_pgsize(*pte_p) );
			}
                        pte_p++;
		    }
		}
                pmd_p++;
 	    }
	}
        pgd_p++;
    }
}


static void flush_pagesize( struct mm_struct *mm, unsigned long address )
{
    int    k;

    pgd_t *pgd_p;
    pmd_t *pmd_p;
    pte_t *pte_p;
    pte_t  pte_v;

    pgd_p = pgd_offset(mm,    address);   if (pgd_none (*pgd_p)) { return; }
    pmd_p = pmd_offset(pgd_p, address);   if (pmd_none (*pmd_p)) { return; }
    pte_p = pte_offset(pmd_p,       0);   

    for (k=0; k<PTRS_PER_PTE; k++) {
        pte_v= *pte_p;

	if (pte_present(pte_v) && pte_pgsizevalid(pte_v)) {
            u32 pgsize       = pte_pgsize(pte_v);
       	    u32 lowrange     = address  & PGMASK(pgsize);
	    u32 amount       = 1<<(pgsize*tlbSIZE_FACTOR);
	    pte_t *pte_low   = pte_offset(pmd_p, lowrange);
	    pte_t *pte_high  = pte_low + amount;

            if (pte_low <= pte_p && pte_p < pte_high) {
                while (pte_low < pte_high) {
                    if (pte_present(*pte_low)) {
                        set_pte( pte_low, pte_invalidate_pgsize(*pte_low) );
                    }
	            pte_low++;
	        }
	    }
	}
        pte_p++;
    }
}



typedef pte_t (*PteFun)(pte_t);

static void traverseCluster ( struct mm_struct *mm, unsigned long address, PteFun modify )
{
    pgd_t *pgd_p;
    pmd_t *pmd_p;
    pte_t *pte_p;
    pte_t  pte_v;

    pgd_p = pgd_offset(mm,    address);   if (pgd_none         (*pgd_p)) { return; }
    pmd_p = pmd_offset(pgd_p, address);   if (pmd_none         (*pmd_p)) { return; }
    pte_p = pte_offset(pmd_p, address);   if (!pte_present     (*pte_p)) { return; }
    pte_v = *pte_p;                       if (!pte_pgsizevalid ( pte_v)) { return; }

    {
        u32    pgsize    = pte_pgsize(pte_v);
	u32    lowrange  = address  & PGMASK(pgsize);
	u32    amount    = 1<<(pgsize*tlbSIZE_FACTOR);

	pte_t *pte_low   = pte_offset(pmd_p, lowrange);
	pte_t *pte_high  = pte_low + amount;

        while (pte_low < pte_high) {
	    set_pte( pte_low, modify(*pte_low) );
	    pte_low++;
	}
   }
}


static pte_t pte_invalidate_pgsize_f ( pte_t pte ) { return pte_invalidate_pgsize(pte); }
static pte_t pte_mkyoung_f           ( pte_t pte ) { return pte_mkyoung(pte);           }
static pte_t pte_mkdirty_f           ( pte_t pte ) { return pte_mkdirty(pte);           }


static inline void setPageClusterYoung ( struct mm_struct *mm, unsigned long address )
{ traverseCluster(mm,address,pte_mkyoung_f); }

static inline void setPageClusterDirty ( struct mm_struct *mm, unsigned long address )
{ traverseCluster(mm,address,pte_mkdirty_f); }



/******************************************************************************/


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

typedef enum {
    T_Locked,           /* Occupied, but not available for replacement.       */
    T_Occupied,         /* (Temporarily) occupied, but may be replaced.       */
    T_Free              /* Entry is free, and might be used an next tlb miss. */
} TLBE_status;


typedef struct {
    TLBE_status          status;
    struct mm_struct *   mm;
    tlbEntry               entry;
} TLBE_Info;


typedef struct {
    struct mm_struct *   mm;
    void *               pagetable_cache;
} Asid_Info;




static TLBE_Info   dtlb_info[ tlbNUM_DTLB_ENTRIES ];
static TLBE_Info   itlb_info[ tlbNUM_ITLB_ENTRIES ];
static Asid_Info   asid_info[ tlbNUM_ASIDS ];
static int         asid_rover;


    /* ----------------------------------- */


static inline unsigned long long random_number()
{
    static unsigned long long _rand_next= 23;
    _rand_next = _rand_next * 6364136223846793005LL + 1;
    return (int)(_rand_next >> 32);
}

static inline int random_asid()
{
    return random_number() & (tlbNUM_ASIDS-1);
}

static inline int random_dtlb_index()
{
    return random_number() & (tlbNUM_DTLB_ENTRIES-1);
}

static inline int random_itlb_index()
{
    return random_number() & (tlbNUM_ITLB_ENTRIES-1);
}


    /* ----------------------------------- */


static void flush_dtlb_entry( int index )
{
    tlbEntry NullEntry = { 0 };

    tlbWriteDTLB(index, NullEntry);
    dtlb_info[index].status = T_Free;
    dtlb_info[index].mm     = NULL;
}


static void flush_itlb_entry( int index )
{
    tlbEntry NullEntry=  { 0 };

    tlbWriteITLB(index, NullEntry);
    itlb_info[index].status = T_Free;
    itlb_info[index].mm     = NULL;
}


    /* ----------------------------------- */


/*
 * TLB replacement policy:
 */

static inline void kickout_dtlb_entry( )
{
    int index;
    
    do { index= random_dtlb_index(); } 
    while ( dtlb_info[index].status == T_Locked );
    
    flush_dtlb_entry(index);
}


static inline void kickout_itlb_entry( )
{
    int index;
    
    do { index= random_itlb_index(); } 
    while ( itlb_info[index].status == T_Locked );
    
    flush_itlb_entry(index);
}


    /* ----------------------------------- */


/*
 * TLB entry allocation:
 */

UInt TLB_allocate_dtlb_entry( )
{
    int index;
    
    do { index= random_dtlb_index(); } 
    while ( dtlb_info[index].status == T_Locked );
    
    flush_dtlb_entry(index);

    dtlb_info[index].status = T_Locked;

    return index;
}


UInt TLB_allocate_itlb_entry( )
{
    int index;
    
    do { index= random_itlb_index(); } 
    while ( itlb_info[index].status == T_Locked );
    
    flush_itlb_entry(index);

    itlb_info[index].status = T_Locked;

    return index;
}


    /* ----------------------------------- */


/*
 * Remove all tlb entries 
 * related to mm:
 */

static void flush_dtlb( struct mm_struct *mm )
{
    int index;
    
    for (index=0; index<tlbNUM_DTLB_ENTRIES; index++) {
        if ( dtlb_info[index].mm == mm ) {
	    flush_dtlb_entry(index);
	}
    }
}


static void flush_itlb( struct mm_struct *mm )
{
    int index;
    
    for (index=0; index<tlbNUM_ITLB_ENTRIES; index++) {
        if ( itlb_info[index].mm == mm ) {
	    flush_itlb_entry(index);
	}
    }
}

    /* ----------------------------------- */


static int in_range( tlbEntry entry, unsigned long address )
{
    unsigned long vpn      = tlbVPN(entry);
    unsigned long size_code= tlbSIZE(entry);
    unsigned long size_bits= 2*size_code;
    
    address >>= (12 + size_bits);  /* scale to vpn */
    vpn     >>=       size_bits ;
    
    return address == vpn;
}

    /* ----------------------------------- */


static void flush_dtlb_page( struct mm_struct *mm, unsigned long page )
{
    int index;
    
    for (index=0; index<tlbNUM_DTLB_ENTRIES; index++) {
	if ( dtlb_info[index].mm == mm 
	   && in_range(dtlb_info[index].entry,page)
	   ){
		flush_dtlb_entry(index);
	}
    }
}


static void flush_itlb_page( struct mm_struct *mm, unsigned long page )
{
    int index;

    for (index=0; index<tlbNUM_ITLB_ENTRIES; index++) {
        if ( itlb_info[index].mm == mm 
	   && in_range(itlb_info[index].entry,page)
	   ) {
	    flush_itlb_entry(index);
	}
    }
}


    /* ----------------------------------- */


/*
 * Write a TLB entry for mm;
 * replace an existing if no
 * free entry exists:
 */

static int write_dtlb_entry( struct mm_struct *mm, tlbEntry entry )
{
    int index;
        
    for (index=0; index<tlbNUM_DTLB_ENTRIES; index++) {
        if ( dtlb_info[index].status == T_Free ) {
	    tlbWriteDTLB(index,entry);
	    dtlb_info[index].status = T_Occupied;
	    dtlb_info[index].entry  = entry;
	    dtlb_info[index].mm     = mm;

	    return index;
	}
    }
    
    kickout_dtlb_entry();
    
    return write_dtlb_entry( mm, entry );
}


static int write_itlb_entry( struct mm_struct *mm, tlbEntry entry )
{
    int index;
    
    for (index=0; index<tlbNUM_ITLB_ENTRIES; index++) {
        if ( itlb_info[index].status == T_Free ) {
	    tlbWriteITLB(index,entry);
	    itlb_info[index].status = T_Occupied;
	    itlb_info[index].entry  = entry;
	    itlb_info[index].mm     = mm;

	    return index;
	}
    }
    
    kickout_itlb_entry();

    return write_itlb_entry( mm, entry );
}

    /* ----------------------------------- */


void flush_tlb_mm(struct mm_struct *mm)
{
    mm_context_t *mm_ctx = &mm->context;

    if (mm_ctx->asid != NO_ASID) {
	    flush_dtlb(mm);
	    flush_itlb(mm);
	    flush_all_pagesizes(mm);
    }
}


void flush_tlb_page_mm(struct mm_struct *mm, unsigned long page)
{
    flush_dtlb_page ( mm, page );
    flush_itlb_page ( mm, page );
    flush_pagesize  ( mm, page );
}


void flush_tlb_range(struct mm_struct *mm, unsigned long start, unsigned long end)
{
   /* tricks to avoid trouble at end of integer range */
    start &= PAGE_MASK;
    end   += PAGE_SIZE-1;
    end   &= PAGE_MASK;

    while (start != end) { 
        flush_dtlb_page ( mm, start );
        flush_itlb_page ( mm, start );
        flush_pagesize  ( mm, start );

	    start += PAGE_SIZE;
    }
}



void flush_tlb_all(void) 
{
    int asid;

    for (asid= 0; asid < tlbNUM_ASIDS; asid++) {
	if (asid_info[asid].mm != NULL) {
	    flush_tlb_mm( asid_info[asid].mm );
	}
    }
}



   /* ----------------------------------- */



void TLB_init()
{
    int      index;
    tlbEntry entry;

    for (index=0; index<tlbNUM_DTLB_ENTRIES; index++) {
        entry= tlbReadDTLB(index);

        if (tlbVALID(entry)) {
	    dtlb_info[index].status= T_Locked;
	} else {
	    dtlb_info[index].status= T_Free;
        }
    }

    for (index=0; index<tlbNUM_ITLB_ENTRIES; index++) {
        entry= tlbReadITLB(index);

        if (tlbVALID(entry)) {
	    itlb_info[index].status= T_Locked;
	} else {
	    itlb_info[index].status= T_Free;
        }
    }
}



void TLB_destroy_context( struct mm_struct *mm )
{    
    mm_context_t *mm_ctx = &mm->context;
    
    if (mm_ctx->asid != NO_ASID) {
        asid_info[mm_ctx->asid].mm= NULL;
        asid_rover = mm_ctx->asid;

        mm_ctx->asid = NO_ASID;
    }
}



void TLB_get_context( struct mm_struct *mm )
{    
    mm_context_t *mm_ctx = &mm->context;

    if (mm_ctx->asid == NO_ASID) {
       /*
        * Search for available ASID;
        * if none, then kick out a random one
        * and retry:
        */
        int a;

        for (a= 0; a < tlbNUM_ASIDS; a++) {
            int asid= (asid_rover+a) % tlbNUM_ASIDS;

            if (asid_info[asid].mm == NULL) {
                mm_ctx->asid = asid;
                asid_info[asid].mm = mm;
                return;
            }
        }

        TLB_destroy_context( asid_info[random_asid()].mm );
        
        TLB_get_context(mm);
    }
}


/******************************************************************************/
 

void TLB_lock_dtlb_entry(int index)   { dtlb_info[index].status= T_Locked; }
void TLB_unlock_dtlb_entry(int index) { dtlb_info[index].status= T_Occupied; }

void TLB_lock_itlb_entry(int index)   { itlb_info[index].status= T_Locked; }
void TLB_unlock_itlb_entry(int index) { itlb_info[index].status= T_Occupied; }


static tlbDTSId_t trDTS( unsigned long dts )
{
    switch (dts) {
    case _PAGE_SDRAM : return tlbSDRAM;
    case _PAGE_PCI   : return tlbPCIA;
    case _PAGE_PIO   : return tlbPIO;
    default          : ASSERT(0, ("UNKNOWN DTS ID %d\n", dts) ); return tlbSDRAM;
    }
}



int TLB_update_itlb(unsigned long address, int usermode)
{
    u32      result;
    tlbEntry entry= {0};
    pte_t    pte_v;
    
    struct mm_struct *mm = current->mm;

    if (!in_interrupt()) { CacheTLBE(address); }

    if ( !virt_to_pte(mm, address, &pte_v) ) { return tlbNUM_ITLB_ENTRIES; }

   /*
    * The following is a quick heuristic on whether the
    * found pte can be just placed into the tlb.
    */
    if (usermode) {
        if (!(pte_val(pte_v) & _PAGE_READ )) { return tlbNUM_ITLB_ENTRIES; }
    }

    result= tlbQueryITLB(address);

    if (result == tlbNUM_ITLB_ENTRIES) {
	u32  flags;

        u32  pgsize  = pte_pgsize(pte_v);
        u32  vaddr   = address         & PGMASK(pgsize);
#ifdef USE_SAMSUNG_PATCH  //ABH 050608 harriet sh memory leak fix
	u32  phaddr   = pte_val(pte_v)  & PGMASK(pgsize);
	u32  baddr   = phys_to_bus(phaddr);
#else
        u32  baddr   = pte_val(pte_v)  & PGMASK(pgsize); 
#endif

        u32  dts     = pte_val(pte_v)  & _PAGE_DTSID_MASK;


        tlbVPN(entry)  = tlbPN(vaddr);
        tlbASID(entry) = mm->context.asid;
        tlbSIZE(entry) = TLB_PGSIZE(pgsize);
        tlbLO(entry)   = (pte_val(pte_v) & _PAGE_ITLBL_MASK) << _PAGE_ITLBL_SHIFT;
        tlbPPN(entry)  = tlbPN(baddr);
        tlbDTS(entry)  = trDTS(dts);

        save_flags(flags); __cli();
        {
	    result= write_itlb_entry(mm,entry);
        }
        restore_flags(flags);

       /*
        * In case TLB pages larger than PAGE_SIZE
        * are used, then the 'young' attribute
        * has to be distributed over the entire TLB page:
        */
        setPageClusterYoung(mm,address);
    }

    return result;
}


int TLB_update_dtlb(unsigned long address, int iswrite, int usermode)
{
    u32      result;
    tlbEntry entry= {0};
    pte_t    pte_v;

    struct mm_struct *mm = current->mm;

    if (!in_interrupt()) { CacheTLBE(address); }

    if ( !virt_to_pte(mm, address, &pte_v) ) { return tlbNUM_DTLB_ENTRIES; }

   /*
    * The following is a quick heuristic on whether the
    * found pte can be just placed into the tlb.
    */
    if (usermode) {
        if (iswrite) {
            if (!(pte_val(pte_v) & _PAGE_WRITE)) { return tlbNUM_DTLB_ENTRIES; }
        } else {
            if (!(pte_val(pte_v) & _PAGE_READ )) { return tlbNUM_DTLB_ENTRIES; }
        }
    } else {
        if (iswrite) {
            if (!(pte_val(pte_v) & _PAGE_KERNEL_WRITE)) { return tlbNUM_DTLB_ENTRIES; }
        } else {
            /* reading always allowed by kernel */
        }
    }

    result= tlbQueryDTLB(address);

    if (result == tlbNUM_DTLB_ENTRIES) {
	u32  flags;

        u32  pgsize  = pte_pgsize(pte_v);
        u32  vaddr   = address         & PGMASK(pgsize);

        u32  dts     = pte_val(pte_v)  & _PAGE_DTSID_MASK;
#ifdef USE_SAMSUNG_PATCH  //ABH 050608 harriet sh memory leak fix
	u32  phaddr   = pte_val(pte_v)  & PGMASK(pgsize);
	u32  baddr      = (dts == _PAGE_SDRAM) ? phys_to_bus(phaddr) : phaddr;
#else
        u32  baddr   = pte_val(pte_v)  & PGMASK(pgsize);      
#endif


        tlbVPN(entry)    = tlbPN(vaddr);
        tlbASID(entry)   = mm->context.asid;
        tlbSIZE(entry)   = TLB_PGSIZE(pgsize);
        tlbLO(entry)     = (pte_val(pte_v) & _PAGE_DTLBL_MASK) << _PAGE_DTLBL_SHIFT;
        tlbPPN(entry)    = tlbPN(baddr);
        tlbDTS(entry)    = trDTS(dts);
        tlbSHARED(entry) = (address >= VMALLOC_START) && (address < VMALLOC_END);

	save_flags(flags); __cli();
	{
	    result= write_dtlb_entry(mm,entry);
	}
	restore_flags(flags);

       /*
        * In case TLB pages larger than PAGE_SIZE
        * are used, then the 'dirty' and 'young' attributes
        * have to be distributed over the entire TLB page:
        */
        setPageClusterYoung(mm,address);
        if (iswrite) { setPageClusterDirty(mm,address); }
    }

    return result;
}


/******************************************************************************/

/* Cache flushing:
 *
 *  - flush_cache_all() flushes entire cache
 *  - flush_cache_mm(mm) flushes the specified mm context's cache lines
 *  - flush_cache_page(mm, vmaddr) flushes a single page
 *  - flush_cache_range(mm, start, end) flushes a range of pages
 *  - flush_page_to_ram(page) write back kernel page to ram
 */

void flush_cache_all(void) 
{
   dcacheFlushAll( );
   icacheInvalidateAll( );
}

void flush_cache_mm( struct mm_struct *mm ) 
{
   flush_cache_all( );
}

void flush_cache_range(
			struct mm_struct *mm,
		       	unsigned long     start,
			unsigned long     end
		) 
{
   flush_cache_all( );
}

void flush_cache_page(struct vm_area_struct *vma, unsigned long page)
{
   pte_t pte_v;

   if ( virt_to_pte(vma->vm_mm, page, &pte_v) ) {
      unsigned int  pgsize = pte_pgsize(pte_v);  
      unsigned long vaddr = bus_to_virt(pte_val(pte_v) & PGMASK(pgsize));

      if (vma->vm_flags & VM_EXEC)
          icacheInvalidateRange((void*)vaddr, PGSIZE(pgsize));
      dcacheFlushRange((void*)vaddr, PGSIZE(pgsize));
   }
}

void flush_page_to_ram(unsigned long page)
{
   icacheInvalidateRange((void*)page, PAGE_SIZE);
   dcacheFlushRange     ((void*)page, PAGE_SIZE);
}


/* DMA cache operations. */
void dma_cache_wback_inv(unsigned long start, unsigned long size)
{
    dcacheFlushRange(start, size);
    dcacheInvalidateRange(start, size);
}   

void dma_cache_wback(unsigned long start, unsigned long size)
{
    dcacheFlushRange(start, size);
}   

void dma_cache_inv(unsigned long start, unsigned long size)
{
    dcacheInvalidateRange(start, size);
}


asmlinkage int sys_cacheflush(void *addr, int bytes, int cache)
{
    flush_cache_all( );
    return 0;
}

asmlinkage int sys_cachectl()
{
    return -ENOSYS;
}

/******************************************************************************/

static struct vm_struct * get_aligned_vm_area(unsigned long phys_addr, unsigned long size)
{
        struct vm_struct *result;
        unsigned long     alignment= MAX_CLUSTER_SIZE;
        unsigned long     vaddr,alvaddr;

       /*
        * Determine a suitable alignment
        * for page size clustering, given
        * the TLB parameters on the BSP:
        */
        size= etiMAX(size,PAGE_SIZE);

        while (alignment > size) {
            alignment >>= tlbSIZE_FACTOR;
        }

        result = get_vm_area(size+alignment);


       /*
        * Align allocated virtual address 
        * with specified physical address:
        */
        vaddr   = (unsigned long)result->addr;
        alvaddr = (vaddr     & ~(alignment-1))
                | (phys_addr &  (alignment-1));
        
        alvaddr = etiROUNDDOWN( alvaddr, PAGE_SIZE );
        
        if (alvaddr < vaddr) { alvaddr += alignment; }


       /*
        * Shrink wrap, and return:
        */
        result->addr  = (void*)(alvaddr);
        result->size  = size;

	return result;
}

static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
	unsigned long phys_addr, unsigned long flags)
{
	unsigned long end;

	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
	do {
		if (!pte_none(*pte))
			printk("remap_area_pte: page already exists\n");
		set_pte(pte, mk_pte_phys(phys_addr, __pgprot(_PAGE_PRESENT | 
					_PAGE_DIRTY | _PAGE_ACCESSED | flags)));
		address += PAGE_SIZE;
		phys_addr += PAGE_SIZE;
		pte++;
	} while (address < end);
}

static inline int remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
	unsigned long phys_addr, unsigned long flags)
{
	unsigned long end;

	address &= ~PGDIR_MASK;
	end = address + size;
	if (end > PGDIR_SIZE)
		end = PGDIR_SIZE;
	phys_addr -= address;
	do {
		pte_t * pte = pte_alloc_kernel(pmd, address);
		if (!pte)
			return -ENOMEM;
		remap_area_pte(pte, address, end - address, address + phys_addr, flags);
		address = (address + PMD_SIZE) & PMD_MASK;
		pmd++;
	} while (address < end);
	return 0;
}

static int remap_area_pages(unsigned long address, unsigned long phys_addr,
				 unsigned long size, unsigned long flags)
{
	pgd_t * dir;
	unsigned long end = address + size;

	phys_addr -= address;
	dir = pgd_offset_k(address);
	flush_cache_all();
	while (address < end) {
		pmd_t *pmd = pmd_alloc_kernel(dir, address);
		if (!pmd)
			return -ENOMEM;
		if (remap_area_pmd(pmd, address, end - address,
					 phys_addr + address, flags))
			return -ENOMEM;
		set_pgdir(address, *dir);
		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	}
	flush_tlb_all();
	return 0;
}

static void *___ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
	unsigned long      p   = phys_addr & PAGE_MASK;
	unsigned long      v;
        struct vm_struct  *area;


	size = PAGE_ALIGN(phys_addr + size) - p;
	if (size == 0) { return NULL; }


        area = get_aligned_vm_area(phys_addr,size);  
    
        if (area == 0) { return NULL; }

        v = VMALLOC_VMADDR(area->addr);

	if (remap_area_pages(v, p, size, flags)) {
            vfree((void*)v);
            return Null;
        } else {
	    return (void *) (v + (phys_addr & ~PAGE_MASK));
        }
}


void iounmap(void *virt_addr) 
{
        unsigned long a= ((unsigned long) virt_addr) & PAGE_MASK;
    
        vfree((void *)a);
}

                /* ----------------- */


void *__ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
    return ___ioremap(phys_addr,size,flags|_PAGE_PCI);
}

void *__pioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
    return ___ioremap(phys_addr,size,flags|_PAGE_PIO);
}

void *ioremap(unsigned long phys_addr, unsigned long size)
{
	return __ioremap(phys_addr,size,__PAGE_KERNEL_UNCACHED);
}




/******************************************************************************/

void pgd_init(unsigned long page) 
{
	unsigned long *p = (unsigned long *) page;
	int i;

 	for(i = 0; i < PTRS_PER_PGD; i++) {
 		p[i] = (unsigned long) invalid_pte_table;
	}
}
