/*
    Copyright 2001 (c) by Authors,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
 
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/VMemory/sa1100/Attic/PMap.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $  by: $Author: baderma $
 
*/


/*** XXX when porting this code to a 64bit machine care needs to be taken  ***
 *** since the conversion of address_t to int is done with uint32          ***/

#include "Support.h"
#include "Topsy.h"
#include "Syscall.h"
#include "Lock.h"
#include "tt.h"
#include "tailq.h"
#include "Assert.h"
#include "PageCopyZero.h"
#include "vm.h"
#include "vm_pmap_hal.h"		// constants & data structures
#include "MemoryLayout.h"		// PAGESIZE, PAGEROUND, ...
#include "vm_alloc.h"				// allocate continous memory
#include "vm_pmap.h"				// flags & prototypes
#include "vm_extern.h"			// return values (like VM_OK, VM_FAILED, ...)


/* memory remapping (need to translate virt <=> phys for TT? entries */
#define V_TO_P(vAddr)				( (uint32)(vAddr) - (uint32)vBase + (uint32)pBase )
#define P_TO_V(pAddr)				( (uint32)(pAddr) - (uint32)pBase + (uint32)vBase )
static	address_t		vBase,
pBase,
TT1SegTop,
TT2SegBottom;
static size_t				TTBufSegSize;

/* queue to keep track of free entries for TT1 / TT2 */
struct pmap_freelist
{
  TAILQ_ENTRY(pmap_freelist) freeq;
};
typedef struct pmap_freelist *pmapFreeEntry;
TAILQ_HEAD( pmap_freehead, pmap_freelist)
pmap_free_tt1,
pmap_free_tt2;


/*** internal functions for internal memory management ***/
/***    DO NOT USE THESE FUNCTIONS OUTSIDE OF PMAP!    ***/
/***    that's why they are declared here not in .h    ***/
address_t pmapAllocTT1(void);
address_t pmapAllocTT2(void);
void pmapFreeTT1(address_t tt1_addr);
void pmapFreeTT2(address_t tt2_addr);


/* initialize all internal structures */
void vm_pmap_init(struct vm_segmap* segmap)
{
  address_t					vBase_old;


  // get the boundaries of my memory region (needs to be continous)
  TTBufSegSize = 1024*1024;	// 1MB should be enough for about 50 processes

  /* XXX this is a big hack. somehow there seems to be bad ram on our board */
  vm_allocate_bootup_memory(segmap, &vBase, 14 * PAGESIZE, VM_TYPE_VM_STATIC);
  vm_allocate_bootup_memory(segmap, &vBase, TTBufSegSize, VM_TYPE_VM_STATIC);
  /* this damned TT1 has to be at 16kB Boundary so round up and waste */
  /* a few pages (0 up to 3) if necessary but since TT1SIZE = 16k we  */
  /* remain aligned once the Base is aligned                          */
  vBase_old = vBase;
  vBase = (address_t)( ((uint32)vBase + 0x3FFF) & 0xFFFFC000);
  // adjust size because of wasted pages
  TTBufSegSize -= (vBase - vBase_old);
  pBase = vBase;					// might change later, in which case I have to
  // recompute TT1SegTop & TT2SegBottom XXX
  // Initialize pointer to the boarder of the two buffers
  // TT1 are allocated from bottom up and TT2 from top down
  // The alloc function checks if they overlap and panics if they do
  TT1SegTop = vBase;
  TT2SegBottom = vBase + TTBufSegSize;
  // initialize & setup list of free TT1 & TT2 entries
  TAILQ_INIT(&pmap_free_tt1);
  TAILQ_INIT(&pmap_free_tt2);
  // set all domains to no access, except for domain 0 which is used
  // for regular pages which are checked agains page permissions
  setDAC(DOMAINACCESS_PERM);

  // set up vm structure
  vm->stat.pagesize = PAGESIZE;
  vm->stat.pagemask = PAGEFRAMEMASK;
  vm->stat.pageshift = PAGEBITS;
  vm->stat.faults = 0;
  vm->stat.pmapfaults = 0;

  printf("===> pmap_init: p_Base = %p\n", pBase);
}


/* create a new pmap for a new process / protection-domain */
/*** pmap is already allocated by the upper level        ***/
int vm_pmap_create(pmap_t pmap, struct protection_domain *d)
{
  /* 1) allocate a new TT1 (16kB) The rest is allocated on demand    *
   * 2) initialize all entries of TT1 (set no access)                *
   * 3) set TT1 base in pmap structure and in CP15 register          */
  address_t		TT1Base;
  int					i;


  // allocate space needed for the TT1 entry
  TT1Base = pmapAllocTT1();
  // TT2 will be allocated on demand in pmapEnter

  // initialize all TT1 entries to zero, this will cause a translation
  // fault when accessed, since bits [1:0] are 0
  //-CJ	for (i = 0; i < TT1_NUM_OF_ENTRIES; i++) {
  for (i = TT1_NUM_OF_ENTRIES; i >= 0; i--)
  {
    //-CJ		printf("vm_pmap_create: setTTL1Entry( %p, %d, 0)\n", TT1Base, i);
    setTTL1Entry(TT1Base, i, 0L);
  }

  // write TT1 Base to pmap
  pmap->tt1base_v = TT1Base;
  pmap->tt1base_p = (address_t)V_TO_P(TT1Base);

  // XXX CP 15 must be written by restore context, when the thread gets runnung

  return VM_OK;
}


/* destroy a pmap of a killed process / protection-domain */
int vm_pmap_destroy(pmap_t pmap)
{
  /* 1) free space used for the TT2 entries. To do that I have to    *
   *    find them first and the only way to do that is to search     *
   *    through the TT1...                                           *
   *    All unused entries have bits [31:0] set to 0. An active      *
   *    entry has bits [31:10] set to the base of the TT2 and the    *
   *    remainder is set according to the specification for coarse   *
   *    page tables.                                                 *
   * 2) zero out pages used by TT1 and free them                     *
   * 3) set TT1 base & TT2 buffer in pmap to zero (just to be sure)  */
  int				i;
  uint32		TT1Entry;


  // flush TLB to make sure we have no stale entries
  flushTLBAll();

  // find all TT2 entries and free them
  for (i = 0; i < TT1_NUM_OF_ENTRIES; i++)
  {
    getTTL1Entry(pmap->tt1base_v, i, &TT1Entry);
    if (TT1Entry & TT1_COARSE_ADDR_MASK)
    {
      // we have a valid base address
      // extra sanity check
      if ( (TT1Entry & TT1_FORMAT_MASK) != TT1_COARSE_FMT)
      {
        return VM_FAILED;
      }
      // free TT2 entry
      pmapFreeTT2((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK));
    }
  }

  // clear TT1 entry and free it
  for (i = 0; i < 4; i++)
  {
    vm_pmap_page_zero(pmap, (address_t)(pmap->tt1base_v + i * PAGESIZE) );
  }
  // free TT1 entry
  pmapFreeTT1(pmap->tt1base_v);

  // mark entry in pmap as invalid (jus to be sure)
  pmap->tt1base_v = 0L;

  return VM_OK;
}

/* add a virt => phys mapping to the specified pmap (flags see vm_pmap.h ) */
int vm_pmap_enter(pmap_t pmap, address_t virt, address_t phys, int flags)
{
  /* 1) find apropriate TT1 entry and check if it already has a     *
   *    valid TT2 asociated. If not create it and write it's        *
   *    address to the TT1                                          *
   * 2) write phys and protection & flags to the TT2 entry          */
  uint32			TT1Entry;
  address_t		TT2Buffer;
  short				i;
  int					tt1flags;
  int					tt2flags;


  // compute architecture dependent flags out of independent flags
  tt1flags = TT1_IMPDEF_FLAGS;		// domain is 0 => so tt1flags consist
  // only of IMP flags
  if (flags & PMAP_WRITE && !(flags & PMAP_READ) )
  {
    // SA does not support write only pages => return error
    return VM_ACCESS;
  }
  // tt2flags are generated already at the correct bitoffset => no shifting
  tt2flags = (flags & PMAP_CACHABLE ? TT2_CB_CACHABLE : 0) |
             (flags & PMAP_BUFFERABLE ? TT2_CB_BUFFERABLE : 0) |
             (flags & PMAP_READ ? TT2_AC_READ : TT2_AC_NONE) |
             (flags & PMAP_WRITE ? TT2_AC_READWRITE : 0);
  getTTL1Entry(pmap->tt1base_v, ((uint32)virt & TT1_LEVEL_MASK) >>
               TT1_LEVEL_SHIFT, &TT1Entry);
  if (TT1Entry == 0L)
  {
    // no TT2 has ever been allocated for this segment
    // allocate space needed for the TT2 entry
    TT2Buffer = pmapAllocTT2();
    // clear all page entries
    for (i = 0; i < TT2_NUM_OF_ENTRIES; i++)
    {
      setTTL2Entry(TT2Buffer, i, 0L);
    }
    // set up TT1 entry
    TT1Entry = (V_TO_P(TT2Buffer) & TT1_COARSE_ADDR_MASK) | ( (tt1flags <<
               TT1_FLAGS_SHIFT) & TT1_FLAGS_MASK) | TT1_COARSE_FMT;
    // it's now save to update the TT1 entry
    setTTL1Entry(pmap->tt1base_v, ((uint32)virt & TT1_LEVEL_MASK) >>
                 TT1_LEVEL_SHIFT, TT1Entry);
    // now we can at last make the desired entry :)
    setTTL2Entry(TT2Buffer, ((uint32)virt & TT2_LEVEL_MASK) >>
                 TT2_LEVEL_SHIFT, ((uint32)phys & TT2_SP_ADDR_MASK) |
                 (tt2flags & TT2_SP_FLAGS_MASK) | TT2_SP_FMT);
    return VM_OK;
  }
  else if ( (TT1Entry & TT1_FORMAT_MASK) == TT1_COARSE_FMT &&
            (TT1Entry & TT1_COARSE_ADDR_MASK) )
  {
    // there exists already a map for this segment which is in use
    setTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                 ((uint32)virt & TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT, ((uint32)phys &
                     TT2_SP_ADDR_MASK) | (tt2flags & TT2_SP_FLAGS_MASK) |
                 TT2_SP_FMT);
    return VM_OK;
  }
  else
  {
    // invalid format, should never get here!
    // delete the mapping and let the upper layer recover from this
    setTTL1Entry(pmap->tt1base_v, ((uint32)virt & TT1_LEVEL_MASK) >>
                 TT1_LEVEL_SHIFT, 0L);
    return VM_FAILED;
  }
}

/* vm_pmap_region_enter: portable version (just calls vm_pmap_enter) */
int vm_pmap_region_enter(struct vm_pmap* vm_pmap, address_t phys, address_t virt, size_t size, int flags)
{
  size_t s;
  int err;

  //-BL
  printf("===> pmap_region_entering: virt %p => phys %p (size = %lu)\n", virt, phys, size);
  for ( s = 0; s < size; s += PAGESIZE )
  {
    err = vm_pmap_enter(vm_pmap, virt + s, phys + s, flags);
    switch ( err )
    {
    case VM_OK:
      break;
    default:
      printf("vm_region_map: pmap enter failed %i\n", err);
      return err;
    }
  }
  return VM_OK;
}


/* remove a page mapping from the specified pmap */
int vm_pmap_page_remove(pmap_t pmap, address_t virt)
{
  /* 1) find apropriate TT2 entry
   * 2) invalidate TT2 entry                                        *
   *    we don't free TT2 pages, since it's very unlikly to happen  *
   *    that that all entries becom free and it would take much     *
   *    time to detect this here. Free is done in pmapRemove_Region *
   *    if apropriate or at last in pmapDestroy                     *
   * 3) invalidate TLB entry (in both data & instruction, since we  *
   *    don't know which it is)                                     */
  uint32		TT1Entry;


  // remove the entry from the TLB to make sure we have no
  // stale entries (could I use unified access to remove data
  // and instruction entries in one call? Doku does imply that
  // this can't be done, so for now I have to try both (I&D)
  //untill I know better XXX
  flushTLBSingleD(virt);
  flushTLBSingleI(virt);

  getTTL1Entry(pmap->tt1base_v, ((uint32)virt & TT1_LEVEL_MASK) >>
               TT1_LEVEL_SHIFT, &TT1Entry);
  if ( (TT1Entry & TT1_COARSE_ADDR_MASK) && ( (TT1Entry & TT1_FORMAT_MASK)
       == TT1_COARSE_FMT) )
  {
    // there is a valid base address for the TT2 => set it to zero and
    // thus remove the mapping (generates translation fault when accessed)
    setTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                 ((uint32)virt &	TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT, 0L);
    return VM_OK;
  }
  else
  {
    // trying to free a page which has no valid TT2 entry assigned
    return VM_FAILED;
  }
}


/* remove all page mappings in the virtual range from the specified pmap */
/*** the region contains the end element                               ***/
int vm_pmap_region_remove(pmap_t pmap, address_t virt_start, address_t virt_end)
{
  /* 1) find apropriate TT2 entry                                   *
   * 2) invalidate TT2 entry                                        *
   *    If the region contains one or more full segments we can     *
   *    instead of invaildating 256 TT2 entries just invalidate     *
   *    one TT1 entry and we can also free the memory used for the  *
   *    TT2 entry.                                                  */
  uint32			RegionSize,
  NumOfSegs,
  TT1Entry;
  address_t		virt_stop;
  short				i;


  // flush TLB to make sure we have no stale entries
  // we flussh the intire TLB (the overhead of checking which entries
  // in which TLB to invalidate is much too large and time consuming,
  // flushing all entries is faster)
  flushTLBAll();

  /*** head partial segments remove ***/
  if ( ((uint32)virt_start & TT2_LEVEL_MASK) != 0L)
  {
    // virt_start does not point to the beginning of a segment
    // => have to invalidate each single page within this segment
    // calculate the end of the partial remove (either virt_end or
    // the end of the segment pointed to by virt_start
    virt_stop = (address_t) ( ( ((uint32)virt_start & TT1_LEVEL_MASK) +
                                (1 << 20) )  - PAGESIZE);
    if (virt_stop > virt_end)
    {
      virt_stop = virt_end;
    }
    getTTL1Entry(pmap->tt1base_v, ((uint32)virt_start & TT1_LEVEL_MASK) >>
                 TT1_LEVEL_SHIFT, &TT1Entry);
    if ( (TT1Entry & TT1_COARSE_ADDR_MASK) == 0L ||
         (TT1Entry & TT1_FORMAT_MASK) != TT1_COARSE_FMT)
    {
      // trying to removed a unmapped or previosly removed segment
      return VM_FAILED;
    }
    do
    {
      setTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                   ((uint32)virt_start  & TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT , 0L);
    }
    while (virt_stop >= (virt_start += PAGESIZE)) ;
  }
  /*** middle full segments remove ***/
  RegionSize = virt_end - virt_start + PAGESIZE;
  if (RegionSize >= REGIONSIZE)
  {
    NumOfSegs = RegionSize / REGIONSIZE;
    for (i = 0;	i < NumOfSegs; virt_start += REGIONSIZE)
    {
      // virt_start is at the very beginning of a segment and the region
      // spawns at least one segment
      getTTL1Entry(pmap->tt1base_v, ( ((uint32)virt_start & TT1_LEVEL_MASK) >>
                                      TT1_LEVEL_SHIFT), &TT1Entry);
      if ( (TT1Entry & TT1_COARSE_ADDR_MASK) == 0L ||
           (TT1Entry & TT1_FORMAT_MASK) != TT1_COARSE_FMT)
      {
        // trying to removed a unmapped or previosly removed segment
        return VM_FAILED;
      }
      // First set the TT1 entry to 0 to cause a translation fault when
      // accesed. If it's accesesd we need to set it up from scratch again
      setTTL1Entry(pmap->tt1base_v, ( ((uint32)virt_start & TT1_LEVEL_MASK) >>
                                      TT1_LEVEL_SHIFT), 0L);
      // Now I can free space occupied by the associated TT2 entry
      pmapFreeTT2((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK));
    }
  }

  /*** tail partial segments remove ***/
  if (virt_start <= virt_end)
  {
    // we have som trailing pages not spawning a full segment
    // => have to invalidate each single page within this segment
    getTTL1Entry(pmap->tt1base_v, ((uint32)virt_start & TT1_LEVEL_MASK) >>
                 TT1_LEVEL_SHIFT, &TT1Entry);
    if ( (TT1Entry & TT1_COARSE_ADDR_MASK) == 0L ||
         (TT1Entry & TT1_FORMAT_MASK) != TT1_COARSE_FMT )
    {
      // trying to removed a unmapped or previosly removed segment
      return VM_FAILED;
    }
    do
    {
      setTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                   ((uint32)virt_start  & TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT, 0L);
    }
    while (virt_end >= (virt_start += PAGESIZE)) ;
  }

  return VM_OK;
}


/* this function tells the page fault handle what kind of fault has */
/* happened.  On other architectures (eg. MIPS) it's easy to tell   */
/* this by looking at the cause register. SA has no such register,  */
/* so we have to find out by looking at the access permission of    */
/* page and check if if the page is valid.                          */
/* We return PMAP_WRITE if it was a write miss or PMAP_READ if it   */
/* was a read miss.                                                 */
/* this function is called in a page fault interrupt, so be fast!   */
int vm_pmap_lookup_type(pmap_t pmap, address_t virt)
{
  uint32		TT1Entry,
  TT2Entry;


  // get & verify TT1 entry
  getTTL1Entry(pmap->tt1base_v, ((uint32)virt & TT1_LEVEL_MASK) >>
               TT1_LEVEL_SHIFT, &TT1Entry);
  if ( (TT1Entry & TT1_COARSE_ADDR_MASK) == 0L ||
       (TT1Entry & TT1_FORMAT_MASK) != TT1_COARSE_FMT )
  {
    // invalid / unmapped address, so we presume we have a read miss
    return PMAP_READ;
  }
  // get & verify TT2 entry
  getTTL2Entry((address_t) P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
               ((uint32)virt  & TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT, &TT2Entry);
  if ( (TT2Entry & TT2_FORMAT_MASK) != TT2_SP_FMT )
  {
    // invalid / unmapped address, so we presume we have a read miss
    return PMAP_READ;
  }
  switch ( (TT2Entry & TT2_SP_PROT_MASK) )
  {
  case TT2_AC_READ:
    // page was read only, so we must have a write miss
    return PMAP_WRITE;
  case TT2_AC_NONE:
    // page is set to no access, so we presume we have a read miss
    return PMAP_READ;
  case TT2_AC_READWRITE:
    // page was read&write => can't fault here ==>> PANIIIIIC
    PANIC("VM_PMAP: faulted on a read&write page => impossible!\n");
    break;
  }
  // to happy happy
  return PMAP_READ;
}


/* lookup a physical address when the virtual address is known */
address_t vm_pmap_lookup_paddr(pmap_t pmap, address_t virt)
{
  uint32		TT1Entry,
  TT2Entry;


  // get & verify TT1 entry
  getTTL1Entry(pmap->tt1base_v, ((uint32)virt & TT1_LEVEL_MASK) >>
               TT1_LEVEL_SHIFT, &TT1Entry);
  if ( (TT1Entry & TT1_COARSE_ADDR_MASK) == 0L ||
       (TT1Entry & TT1_FORMAT_MASK) != TT1_COARSE_FMT )
  {
    // invalid / unmapped address
    return NULL;
  }
  // get & verify TT2 entry
  getTTL2Entry((address_t) P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
               ((uint32)virt  & TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT, &TT2Entry);
  if ( (TT2Entry & TT2_FORMAT_MASK) != TT2_SP_FMT )
  {
    // invalid / unmapped address
    return NULL;
  }

  return (address_t)( ((uint32)TT2Entry & TT2_SP_ADDR_MASK) );
  //+ ((uint32)virt & PAGE_LEVEL_MASK) );
}



/* change protection and B & C bits of a page */
int vm_pmap_change_page_flags(pmap_t pmap, address_t virt, int flags)
{
  /* 1) find apropriate TT2 entry                                   *
   * 2) read the entry                                              *
   * 3) modify protection bits and write the entry                  */
  uint32		TT1Entry,
  TT2Entry;
  int				tt2flags;


  // compute architecture dependent flags out of independent flags
  if (flags & PMAP_WRITE && !(flags & PMAP_READ) )
  {
    // SA does not support write only pages => return error
    return VM_ACCESS;
  }
  // remove the entry from the TLB to make sure we don't have
  // any entries whith wrong flags (could I use unified access
  // to remove data and instruction entries in one call? Doku
  // does imply that this can't be done, so for now I have to
  // try both (I&D) untill I know better XXX
  flushTLBSingleD(virt);
  flushTLBSingleI(virt);

  // tt2flags are generated already at the correct bitoffset => no shifting
  tt2flags = (flags & PMAP_CACHABLE ? TT2_CB_CACHABLE : 0) |
             (flags & PMAP_BUFFERABLE ? TT2_CB_BUFFERABLE : 0) |
             (flags & PMAP_READ ? TT2_AC_READ : TT2_AC_NONE) |
             (flags & PMAP_WRITE ? TT2_AC_READWRITE : 0);
  getTTL1Entry(pmap->tt1base_v, ((uint32)virt & TT1_LEVEL_MASK) >>
               TT1_LEVEL_SHIFT, &TT1Entry);
  if ( (TT1Entry & TT1_COARSE_ADDR_MASK) && (TT1Entry & TT1_FORMAT_MASK)
       == TT1_COARSE_FMT)
  {
    // there exists a valid TT1 for this address
    getTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                 ((uint32)virt &	TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT, &TT2Entry);
    if ( (TT2Entry & TT2_FORMAT_MASK) == TT2_SP_FMT)
    {
      // there exists a valid TT2 for this address
      TT2Entry = (tt2flags & TT2_SP_FLAGS_MASK) |
                 (TT2Entry & (TT2_FORMAT_MASK | TT2_SP_ADDR_MASK) );
      setTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                   ((uint32)virt & TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT,	TT2Entry);
      return VM_OK;
    }
    else
    {
      return VM_FAILED;
    }
  }
  else
  {
    return VM_FAILED;
  }
}


/* change protection and B & C bits of all pages in the virtual range */
int vm_pmap_change_region_flags(pmap_t pmap, address_t virt_start,
                                address_t virt_end, int flags)
{
  /* have to go through all entries, one by one:                    *
   * 1) find an apropriate TT2 entry                                *
   * 2) read the entry                                              *
   * 3) modify the protection bits and write the entry              *
   * 4) go back to 1) as long as end has not been reached           */
  uint32		TT1Entry,
  TT2Entry,
  TT1Offset;
  int				tt2flags;


  // flush TLB to make sure we have no entries with wrong flags
  // we flussh the intire TLB (the overhead of checking which entries
  // in which TLB to invalidate is much too large and time consuming,
  // flushing all entries is faster)
  flushTLBAll();

  TT1Offset = 0L;
  // compute architecture dependent flags out of independent flags
  if (flags & PMAP_WRITE && !(flags & PMAP_READ) )
  {
    // SA does not support write only pages => return error
    return VM_ACCESS;
  }
  // tt2flags are generated already at the correct bitoffset => no shifting
  tt2flags = (flags & PMAP_CACHABLE ? TT2_CB_CACHABLE : 0) |
             (flags & PMAP_BUFFERABLE ? TT2_CB_BUFFERABLE : 0) |
             (flags & PMAP_READ ? TT2_AC_READ : TT2_AC_NONE) |
             (flags & PMAP_WRITE ? TT2_AC_READWRITE : 0);
  do
  {
    // fetch TT1 only once for each segment (saves us some memory accesses)
    if (TT1Offset != ((uint32)virt_start & TT1_LEVEL_MASK) )
    {
      TT1Offset = (uint32)virt_start & TT1_LEVEL_MASK;
      getTTL1Entry(pmap->tt1base_v, TT1Offset >> TT1_LEVEL_SHIFT, &TT1Entry);
    }
    if ( (TT1Entry & TT1_COARSE_ADDR_MASK) && (TT1Entry & TT1_FORMAT_MASK)
         == TT1_COARSE_FMT)
    {
      // there exists a valid TT1 for this address
      getTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                   ((uint32)virt_start &	TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT,	&TT2Entry);
      if ( (TT2Entry & TT2_FORMAT_MASK) == TT2_SP_FMT)
      {
        // there exists a valid TT2 for this address
        TT2Entry = (tt2flags & TT2_SP_FLAGS_MASK) |
                   (TT2Entry & (TT2_FORMAT_MASK | TT2_SP_ADDR_MASK) );
        setTTL2Entry((address_t)P_TO_V(TT1Entry & TT1_COARSE_ADDR_MASK),
                     ((uint32)virt_start  & TT2_LEVEL_MASK) >> TT2_LEVEL_SHIFT,
                     TT2Entry);
        return VM_OK;
      }
      else
      {
        return VM_FAILED;
      }
    }
    else
    {
      return VM_FAILED;
    }
  }
  while (virt_end >= (virt_start += PAGESIZE) ) ;
}


/* low level copy contents of a page bypassing the caches */
int vm_pmap_page_copy(pmap_t pmap, struct vm_page* src, struct vm_page* dest)
{
  /* 1) first of all I need to map both pages into my PD. To do     *
   *    this, I map them by hand to a special address reserved for  *
   *    that purpose. Since the source needs to be non cachable to  *
   *    avoid trashing we don't pass the cachable flag when mapping *
   *    the page.                                                   *
   * 2) after mapping a low level copy function does the work       *
   * 3) last but not least the mappings in the PMap Layer have to   *
   *    be removed and the TLB entries for both pages have to be    *
   *    invalidated                                                 */
  /*** WARNING: don't use vm_pmap_page_copy while another         ***
   ***          vm_pmap_page_copy is running in the same PD!!!    ***/
  uint32		err1,
  err2;


  // I need to map both pages temporarly. Let's do this the PMap way
  if ( vm_pmap_enter(pmap, MAP_PAGE_POS1, PAGETOADDR(dest->pagenr),
                     PMAP_READ | PMAP_WRITE) != VM_OK)
  {
    // return error
    return VM_FAILED;
  }
  if (vm_pmap_enter(pmap, MAP_PAGE_POS2, PAGETOADDR(src->pagenr),
                    PMAP_READ) != VM_OK)
  {
    // remove mapping of dest first, return error
    vm_pmap_page_remove(pmap, MAP_PAGE_POS1);
    return VM_FAILED;
  }

  // copy is done with optimized assembler routines using multiple-load
  // and multiple-store (ldm & stm) which load/store several words at once
  bcopy_page(MAP_PAGE_POS1, MAP_PAGE_POS2);

  // remove mapping of dest & src page (vm_pmap_page_remove invalidates
  // entries in the TT and TLB)
  err1 = vm_pmap_page_remove(pmap, MAP_PAGE_POS1);
  err2 = vm_pmap_page_remove(pmap, MAP_PAGE_POS2);
  if (err1 != VM_OK || err2 != VM_OK)
  {
    return VM_FAILED;
  }
  return VM_OK;
}


/* low level zero fill a page */
int vm_pmap_page_zero(pmap_t pmap, struct vm_page* src)
{
  /* 1) first of all I need to map the page into my PD. To do       *
   *    this, I map them by hand to a special address reserved for  *
   *    that purpose.                                               *
   * 2) after mapping a low level copy function does the work       *
   * 3) last but not least the mappings in the PMap Layer have to   *
   *    be removed (and the TLB entries for both pages have to be   *
   *    invalidated)                                                */
  /*** WARNING: don't use vm_pmap_page_zero while another         ***
   ***          vm_pmap_page_zero is running in the same PD!!!    ***/


  // I need to map the page temporarly. Let's do this the PMap way.
  // the cachable flag would have no effects since the cache does not
  // update on write misses, but we leave it out anyway and map the page
  // read/write (write only is not supported by the StrongArm)
  if ( vm_pmap_enter(pmap, MAP_PAGE_POS3, PAGETOADDR(src->pagenr),
                     PMAP_READ | PMAP_WRITE) != VM_OK)
  {
    // return error
    return VM_FAILED;
  }

  // zero is done with optimized assembler routines using multiple-store
  // (stm) which stores several words at once
  bzero_page(MAP_PAGE_POS3);

  // remove mapping of dest & src page (vm_pmap_page_remove invalidates
  // entries in the TT and TLB)
  if (vm_pmap_page_remove(pmap, MAP_PAGE_POS3) != VM_OK)
  {
    return VM_FAILED;
  }

  return VM_OK;
}


address_t pmapAllocTT1(void)
{
  pmapFreeEntry	TopElem;


  if (TAILQ_EMPTY(&pmap_free_tt1) )
  {
    // no free entry available => create one
    TT1SegTop += TT1_SIZE;
    if (TT1SegTop >= TT2SegBottom)
    {
      // we used up all available memory for the internal structures
      // => AIIIIIIEEEEE panic
      PANIC("PMap: Panic, no more memory available! Terminating...\n");
    }
    // we have aneough sapce, uff...
    //-BL printf("===> pmapAllocTT1 @ %p\n", TT1SegTop - TT1_SIZE);
    TopElem = (TT1SegTop - TT1_SIZE);
    // make sure we are on 16k Boundary
    ASSERT(	(uint32)TopElem == (((uint32)TopElem + 0x3FFF) & 0xFFFFC000) );
    return TopElem;
  }
  else
  {
    // there is at least one unused entry on the list => use it
    TopElem = TAILQ_FIRST(&pmap_free_tt1);
    TAILQ_REMOVE(&pmap_free_tt1, TopElem, freeq);
    //-BL printf("===> pmapAllocTT1 @ %p\n", TopElem);
    // make sure we are on 16k Boundary
    ASSERT(	(uint32)TopElem == (((uint32)TopElem + 0x3FFF) & 0xFFFFC000) );
    return (address_t)TopElem;
  }
}


address_t pmapAllocTT2(void)
{
  pmapFreeEntry	TopElem;


  if (TAILQ_EMPTY(&pmap_free_tt2) )
  {
    // no free entry available => create one
    TT2SegBottom -= TT2_SIZE;
    if (TT2SegBottom <= TT1SegTop)
    {
      // we used up all available memory for the internal structures
      // => AIIIIIIEEEEE panic
      PANIC("PMap: Panic, no more memory available! Terminating...\n");
    }
    // we have aneough sapce, uff...
    //-BL printf("===> pmapAllocTT2 @ %p\n", TT2SegBottom);
    return TT2SegBottom;
  }
  else
  {
    // there is at least one unused entry on the list => use it
    TopElem = TAILQ_FIRST(&pmap_free_tt2);
    TAILQ_REMOVE(&pmap_free_tt2, TopElem, freeq);
    //-BL printf("===> pmapAllocTT2 @ %p\n", TopElem);
    return (address_t)TopElem;
  }
}


void pmapFreeTT1(address_t tt1_addr)
{
  if (tt1_addr + TT1_SIZE == TT1SegTop)
  {
    // the element is at the top of the TT1 segment, so we just need
    // to adjust the boundary and don't have to use the list
    TT1SegTop -= TT1_SIZE;
  }
  else
  {
    // the element is somewhere in the middle of the segment, so we
    // need to add it to the free list to be able to use it later
    TAILQ_ADDHEAD(&pmap_free_tt1, (pmapFreeEntry)tt1_addr, freeq);
  }
}

void pmapFreeTT2(address_t tt2_addr)
{
  if (tt2_addr == TT2SegBottom)
  {
    // the element is at the bottom of the TT2 segment, so we just need
    // to adjust the boundary and don't have to use the list
    TT2SegBottom += TT2_SIZE;
  }
  else
  {
    // the element is somewhere in the middle of the segment, so we
    // need to add it to the free list to be able to use it later
    TAILQ_ADDHEAD(&pmap_free_tt2, (pmapFreeEntry)tt2_addr, freeq);
  }
}

void vm_pmap_print_all(void)
{
  WARNING("vm_pmap_print_all: not yet implemented");
}

void vm_pmap_print(pmap_t pmap)
{
  WARNING("vm_pmap_print: not yet implemented");
}
