/*
    Copyright 2001 (c) by Sebi Ryffel,
    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$
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision$
    Last Date of Change:   $Date$  by: $Author$

*/

#include "TMProtectionDomain.h"

#include "vm_pmap.h"

#include "Assert.h"
#include "Support.h"
#include "Error.h"
#include "Lock.h"

#include "vm_pmap_asm.h"
#include "MemoryLayout.h"
#include "MMHal.h"
#include "mmHalAsm.h"

#include "TMHal.h"

#include "tm-include.h"

#define PMAP_BUF_SIZE     4*1024*1024 /* 4MB or 1024 Pages */

#define PDIR_NUM_ENTRIES  1024
#define PDIR_SIZE         (PDIR_NUM_ENTRIES*4)
#define PTBL_NUM_ENTRIES  1024
#define PTBL_SIZE         (PTBL_NUM_ENTRIES*4)

/* memory remapping (need to translate virt <=> phys for Table entries */
#define V_TO_P(vAddr)				( (uint32)(vAddr) - (uint32)pmap_vbase + (uint32)pmap_pbase )
#define P_TO_V(pAddr)				( (uint32)(pAddr) - (uint32)pmap_pbase + (uint32)pmap_vbase )
#define V_TO_PDIR_IX(vAddr) ( (uint32)(vAddr) >> 22                                     )
#define V_TO_PTBL_IX(vAddr) (((uint32)(vAddr) & 0x003ff000 ) >> 12                      )
#define V_TO_PAGE_IX(vAddr) (((uint32)(vAddr) & 0x00000fff )                            )

#define ENTRY_PRESENT(base) ((uint32)(*base) & PTBL_FLAGS_P                             ) 
#define ENTRY_PRESENTF(flags) ((unsigned short)(flags) & PTBL_FLAGS_P                   ) 


/* Page Table Flags */
#define PTBL_FLAGS_P      0x001        /* Present Bit                                     */
#define PTBL_FLAGS_RW     0x002        /* Read / Write Bit: 0= read only, 1= Read / Write */
#define PTBL_FLAGS_US     0x004        /* User / Supervisor Bit: 0=Supervisor, 1=User     */
#define PTBL_FLAGS_WT     0x008        /* Write-Through Bit                               */
#define PTBL_FLAGS_CD     0x010        /* Cache Disabled Bit                              */
#define PTBL_FLAGS_A      0x020        /* Accessed Bit                                    */
#define PTBL_FLAGS_D      0x040        /* Dirty Bit                                       */
#define PTBL_FLAGS_PAT    0x080        /* Page Table Attribute Index Bit                  */
#define PTBL_FLAGS_G      0x100        /* Global Bit                                      */

/* Page Directory Flags */
#define PDIR_FLAGS_P      0x001        /* Present Bit                                     */
#define PDIR_FLAGS_RW     0x002        /* Read / Write Bit: 0=read only, 1=Read / Write   */
#define PDIR_FLAGS_US     0x004        /* User / Supervisor Bit: 0=Supervisor, 1=User     */
#define PDIR_FLAGS_WT     0x008        /* Write-Through Bit                               */
#define PDIR_FLAGS_CD     0x010        /* Cache Disabled Bit                              */
#define PDIR_FLAGS_A      0x020        /* Accessed Bit                                    */                
#define PDIR_FLAGS_PS     0x080        /* Page Size Bit (0 = 4KB)                         */
#define PDIR_FLAGS_G      0x100        /* Global Bit                                      */

#define PDIR_FLAGS        (PDIR_FLAGS_P | PDIR_FLAGS_RW | PDIR_FLAGS_US)  /* Default Flags*/

/* Segment Selectors from MMHal.c */
extern unsigned long KCSEL;
extern unsigned long KDSEL;
extern unsigned long UCSEL;
extern unsigned long UDSEL;
/* "New" Segment Selectors        */
unsigned long user_code_sel;
unsigned long user_data_sel;

static address_t pmap_pdir_seg_top,     /* Top of the Page Directories Segment        */ 
                 pmap_ptbl_seg_bottom,  /* Bottom of the Page Tables Segment          */
                 pmap_vbase,            /* Virtual address of the base of the Segment */
                 pmap_pbase;            /* Physical address                           */
static size_t pmap_buf_size;            /* size of the buffer                         */

/* queue to keep track of free entries for PDirs / PTbls */
struct pmap_freelist
{
  TAILQ_ENTRY(pmap_freelist) freeq;
};
typedef struct pmap_freelist *pmap_free_entry;
TAILQ_HEAD( pmap_freehead, pmap_freelist)
  pmap_freelist_pdir, /* List of the free PDIR Entries */
  pmap_freelist_ptbl; /* List of the free PTBL Entries */

/* TSS Buffer */
#define TSS_SEG_SIZE      104                                                   /* Size of TSS Segments in Bytes */
#define TSS_BUF_PAGES     PAGENUMBER(PAGEROUNDUP(MAXNBOFCPUS*TSS_SEG_SIZE))     /* Every CPU needs its own TSS */

static size_t tssbuff_size = TSS_BUF_PAGES * 4096;  /* Size of the TSS Buffer                                     */
static address_t tssbuff_base,                      /* Base address of the TSS Buffer                             */ 
                 tssbuff_hint;                      /* Pointer to the assumed first free Entry in the TSS Buffer  */
typedef struct tss_s
{
  int data[TSS_SEG_SIZE/sizeof(int)]; 
} tss_t;

/* TSS Flags */
#define K_TSS_B5    0x89            /* 10001001b */
#define K_TSS_B6    0x00            /* 00000000b */

/* Page Buffer */
#define PAGE_BUFF_PAGES   (32*MAXNBOFCPUS)                     /* size of the page buffer in pages, preferably a power of two */
static size_t pagebuff_size = PAGE_BUFF_PAGES * 4096;         /* size of the page buffer          */
static vm_pmap_pagebuffer_entry_t pagebuff[PAGE_BUFF_PAGES];  /* page buffer                      */
static int pagebuff_hint = 0;                                 /* Hint                             */
static LockDesc pagebuff_lock;                                /* Lock                             */

/*** internal functions for internal memory management  ***/
/***    DO NOT USE THESE FUNCTIONS OUTSIDE OF PMAP!     ***/
/***    that's why they are declared here and not in .h ***/
address_t pmap_alloc_pdir(void);
address_t pmap_alloc_ptbl(void);
void pmap_free_pdir(address_t pdir_addr);
void pmap_free_ptbl(address_t ptbl_addr);

void pmap_set_pdir_entry(address_t pdir_base_v, int i, address_t ptbl_base_p, unsigned short flags);
void pmap_get_pdir_entry(address_t pdir_base_v, int i, address_t* ptbl_base_p, unsigned short* pdir_flags);
void pmap_set_ptbl_entry(address_t ptbl_base_v, int i, address_t page_base_p, unsigned short flags);
void pmap_get_ptbl_entry(address_t ptbl_base_v, int i, address_t* page_base_p, unsigned short* flags);

void pmap_tssbuff_init(struct vm_segmap* segmap); 
int pmap_get_new_tss(unsigned short *tss_sel, unsigned short *tss_alias, struct protection_domain *d);
int tss_isfree(tss_t *p_tss); 

void vm_pmap_pagebuff_init(struct vm_segmap* segmap);

/* 
 * Initialization of the pmap layer
 */
void vm_pmap_init(struct vm_segmap* segmap) 
{
  
  // calculate pmap_buf_size
  //   4MB should be enough for 50 processes, each having 64MB of VM
  pmap_buf_size = PMAP_BUF_SIZE;

  // allocate the memory for the buffer
  pmap_buf_size = vm_allocate_bootup_memory(segmap, &pmap_vbase, pmap_buf_size, VM_TYPE_VM_STATIC);

  pmap_pbase = K0SEG_TO_PHYS(pmap_vbase);      // we are in the kernel data segment
  ASSERT(0 == ((int)pmap_pbase & PAGEMASK));   // pbase has to be aligned to the 4k page boundary

  // initialize pointers to boarder of segments
  pmap_pdir_seg_top = pmap_vbase;
  pmap_ptbl_seg_bottom = pmap_vbase + pmap_buf_size;;
  
  // initalize lists of free PDirs and PTbls
  TAILQ_INIT(&pmap_freelist_pdir);
  TAILQ_INIT(&pmap_freelist_ptbl);

  // initalize the TSS Buffer
  pmap_tssbuff_init(segmap);

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

  // initalize Segment Selectors
  KDSEL = GC_KDSEL;   /* CONSTANT VALUE from MemoryLayout.h */
  KCSEL = GC_KCSEL;   /*   Segments are created in CoreLoad */ 
  UDSEL = 0;          /* not used anymore, initalize to invalid value */
  UCSEL = 0;
 
  // Insert new Segment Selectors in the GDT
  user_code_sel = Get_New_GDTE();
  Set_GDTEntry(user_code_sel, 0x0, MM_LIMIT_MAX, MM_CODE_B5 | MM_B5_3, MM_CODE_B6 );  // from 0-4GB (in Pages)

  user_data_sel = Get_New_GDTE();
  Set_GDTEntry(user_data_sel, 0x0, MM_LIMIT_MAX, MM_DATA_B5 | MM_B5_3, MM_DATA_B6 );  // from 0-4GB

  user_code_sel = user_code_sel | MM_RPL_3;    /* RPL is equal to DPL:                                    */
  user_data_sel = user_data_sel | MM_RPL_3;    /*  when accessing this Segment, the CPL is reduced to RPL */

  // Initialize the Page Buffer
  vm_pmap_pagebuff_init(segmap);

}

/*
 * generate an execution environment for a new protection domain
 */
int vm_pmap_create(pmap_t pmap, struct protection_domain *d)
{
  unsigned short access;
  address_t pdir_base;

  printf("vm_pmap_create()\n");

  /* Setup Segment Selectors */
  if (d->permission & KERNEL_PERM_MSK)
  {
    // Kernel => there is only one Kernel Segment of each type
    pmap->code_sel = KCSEL;
    pmap->data_sel = KDSEL;
  }
  else
  {
    // USER => User Segments
 
    pmap->code_sel = user_code_sel; 
    pmap->data_sel = user_data_sel;

  }

  // Allocate a new Page Directory
  pdir_base = pmap_alloc_pdir();

  // Write PDIR Base to pmap
  pmap->pdir_base_v = pdir_base;
  pmap->pdir_base_p = (address_t)V_TO_P(pdir_base);
  pmap->tf_pdbr = pmap->pdir_base_p;

  return VM_OK;
}

int vm_pmap_destroy(pmap_t pmap)
{
  /* 1) free space used for the PTLB entries. To do that I have to    *
   *    find them first and the only way to do that is to search     *
   *    through the Page Directory...                                *
   * 2) free Page used by the Page Directory                         *
   * 3) set TT1 base & TT2 buffer in pmap to zero (just to be sure)  */
  int *pdir_entry;
  address_t ptbl_base_v;
  int pdir_ix;

  // free Page Tables
  for (pdir_ix = 0; pdir_ix < PDIR_NUM_ENTRIES; pdir_ix++ ) 
  {
    // get Page Directory entry
    pdir_entry = (int*)(pmap->pdir_base_v + 4*pdir_ix);
    if (ENTRY_PRESENT(pdir_entry)) 
    {
      // Page Directory entry is used => free Page Table
      ptbl_base_v = *pdir_entry & PAGEFRAMEMASK;
      pmap_free_ptbl(ptbl_base_v);
    }
  }

  // free Page Directory
  pmap_free_pdir(pmap->pdir_base_v);

  // Invalidate TLB
  __inv_tlb();

  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 PDIR entry and check if it already has a     *
   *    valid PTBL asociated. If not create it and write it's        *
   *    address to the PDIR                                          *
   * 2) write phys and protection & flags to the PTBL entry          */
  unsigned short pdir_flags, ptbl_flags, ptbl_flags_old; // Flags
  address_t pdir_base_v  = pmap->pdir_base_v;            // Base address
  int pdir_ix = V_TO_PDIR_IX(virt);                      // Index in Page Directory
  int ptbl_ix = V_TO_PTBL_IX(virt);                      // Index in Page Table
  address_t ptbl_base_v, ptbl_base_p, page_base_p_old;
 

  if (flags & PMAP_WRITE && !(flags & PMAP_READ) )
    return VM_ACCESS;   // ia32 does not support write-only pages

  // Read Page Directory Entry
  pmap_get_pdir_entry(pdir_base_v, pdir_ix, &ptbl_base_p, &pdir_flags);
  if (!ENTRY_PRESENTF(pdir_flags)) 
  {
    // Page Table does not exist => create it
    ptbl_base_v = pmap_alloc_ptbl();
    // write pdir entry
    pdir_flags = PDIR_FLAGS;
    ptbl_base_p = (address_t)V_TO_P(ptbl_base_v);
    pmap_set_pdir_entry(pdir_base_v, pdir_ix, ptbl_base_p, pdir_flags);

  }
  else
  {
    // Page Table already exists => get address
    ptbl_base_v = (address_t)P_TO_V(ptbl_base_p);
  }

  
  // test pdir entry
  unsigned short pdir_flags2; address_t ptbl_base2_p;
  pmap_get_pdir_entry(pdir_base_v, pdir_ix, &ptbl_base2_p, &pdir_flags2);
  ASSERT(ptbl_base_p == ptbl_base2_p);
  ASSERT(pdir_flags == pdir_flags2);


  // generate Flags
  ptbl_flags = (PTBL_FLAGS_P
                | (flags & (PMAP_READ|PMAP_WRITE) == 0 ? 0 : PTBL_FLAGS_US)        // not readable nor writable => supervisor page
                | (flags & PMAP_READ       ? 0 : 0 )
                | (flags & PMAP_WRITE      ? PTBL_FLAGS_RW : 0)
                | (flags & PMAP_EXEC       ? 0 : 0)
                | (flags & PMAP_CACHABLE   ? 0 : PTBL_FLAGS_CD)
                | (flags & PMAP_BUFFERABLE ? 0 : PTBL_FLAGS_WT )); // ??? 

  // Read Page Table Entry
  pmap_get_ptbl_entry(ptbl_base_v, ptbl_ix, &page_base_p_old, &ptbl_flags_old);
  if (ENTRY_PRESENTF(ptbl_flags_old))
  {
    // Page Table Entry exists already => compare it ???
  
    if (phys == page_base_p_old)
    {
      // Mapping is the same
      if (ptbl_flags_old != ptbl_flags)
      {
        // ERROR("Virtual address already mapped, but with different flags");
        // printf("  ptbl_flags_old=0x%03x, ptbl_flags=0x%03x\n",  ptbl_flags_old, ptbl_flags);
        // return VM_FAILED;
      }
    }
    else
    {
      // Page already mapped to other location 
      ERROR("Virtual address already mapped, do not overwrite!");
      printf("  virt=0x%08x, phys_old=0x%08x, phys=0x%08x\n", virt, page_base_p_old, phys);  
      return VM_FAILED;
    }
  
  }

  // create Page Table Entry
  pmap_set_ptbl_entry(ptbl_base_v, ptbl_ix, phys, ptbl_flags);

  // test ptbl entry
  unsigned short ptbl_flags2; address_t page_base2_p;
  pmap_get_ptbl_entry(ptbl_base_v, ptbl_ix, &page_base2_p, &ptbl_flags2);
  ASSERT(phys == page_base2_p);
  ASSERT(ptbl_flags == ptbl_flags2);

  return VM_OK;
}

int vm_pmap_region_enter(struct vm_pmap* vm_pmap,
                         address_t phys, address_t virt, size_t size, int flags)
{
  size_t s = 0;
  address_t phys_base, virt_base;
  int err;

  phys_base = (address_t)((int)phys & PAGEFRAMEMASK);   // Align addresses to 4k Page Boundaries
  virt_base = (address_t)((int)virt & PAGEFRAMEMASK);

  printf("===> pmap_region_entering: virt %p => phys %p (size = %luKB)\n", virt_base, phys_base, PAGEROUNDUP(size)>>10);
  while ( phys_base + s < phys + size) {
    err = vm_pmap_enter(vm_pmap, virt_base + s, phys_base + s, flags);
    switch ( err )
    {
    case VM_OK:
      break;
    default:
      printf("vm_region_map: pmap enter failed %i\n", err);
      return err;
    }
    s += PAGESIZE;  // increment s
  }

  return VM_OK;

}

int vm_pmap_page_remove(pmap_t pmap, address_t virt)
{
  /* 1) find apropriate Page Table entry
   * 2) invalidate Page Table entry                                        *
   *    we don't free Page Table pages, since it's very unlikly to happen  *
   *    that that all entries become 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                                      */
  
  address_t ptbl_base_p,page_base_p;
  unsigned short pdir_flags, ptbl_flags;

  pmap_get_pdir_entry(pmap->pdir_base_v, V_TO_PDIR_IX(virt), &ptbl_base_p, &pdir_flags); 
  
  if (ENTRY_PRESENTF(pdir_flags))
  {
    pmap_get_ptbl_entry(V_TO_P(ptbl_base_p), V_TO_PTBL_IX(virt), &page_base_p, &ptbl_flags);
    if (!ENTRY_PRESENTF(ptbl_flags))
    {
      // trying to free a Page which is not mapped
      return VM_FAILED;
    }
    // Clear Page Table Entry
    pmap_set_ptbl_entry(V_TO_P(ptbl_base_p), V_TO_PTBL_IX(virt), 0, 0);
  }
  else
  {
    // trying to free a Page which is not mapped
    return VM_FAILED;
  }

  // Invalidate TLB Entry
  __inv_tlb_entry(virt);

  return VM_OK;
}

int vm_pmap_region_remove(pmap_t pmap, address_t virt_start, address_t virt_end)
{

  /* 1) find apropriate Page Table entry                            *
   * 2) invalidate Page Table entry                                 *
   *    If the region contains one or more full segments we can     *
   *    invalidate the Page Directory entry and free the memory     *
   *    used for the Page Tables                                    */
  address_t pos;
  address_t ptbl_base_p,page_base_p;
  unsigned short pdir_flags, ptbl_flags;
  int pdir_ix, ptbl_ix;


  pos = virt_start;
  while (pos < virt_end) 
  {
    // Get next Page Directory entry
    pdir_ix = V_TO_PDIR_IX(pos);
    pmap_get_pdir_entry(pmap->pdir_base_v, pdir_ix, &ptbl_base_p, &pdir_flags); 

    if (!ENTRY_PRESENTF(pdir_flags))
    {
      // Page Table does not exist => move to next Page
      // This part of the region is not mapped
      pos += PAGESIZE;
      continue;
    }

    ptbl_ix = V_TO_PTBL_IX(pos);
    if (0 == ptbl_ix && virt_end - pos >= PTBL_NUM_ENTRIES*PAGESIZE)
    {
      // we are at the first entry in the page table and want to remove 
      // all entries of the Page Table
      //  => remove entry in the Page Directory

      // reset Page Directory entry
      pmap_set_pdir_entry(pmap->pdir_base_v, pdir_ix, 0, 0);
      // free whole Page Table
      pmap_free_ptbl(ptbl_base_p);

      pos += PTBL_NUM_ENTRIES*PAGESIZE;
    }
    else
    {
      // test Page Table entry
      pmap_get_ptbl_entry(V_TO_P(ptbl_base_p), V_TO_PTBL_IX(pos), &page_base_p, &ptbl_flags);
      if (ENTRY_PRESENTF(ptbl_flags))
      {
        pmap_set_ptbl_entry(V_TO_P(ptbl_base_p), ptbl_ix, 0, 0);
      }

      pos += PAGESIZE;
    }

  }

  // Invalidate TLB
  __inv_tlb();

  return VM_OK;
}

int vm_pmap_change_page_flags(pmap_t pmap, address_t virt, int flags)
{
  address_t ptbl_base_p,page_base_p;
  unsigned short pdir_flags, ptbl_flags, new_ptbl_flags;

  // compute architecture dependent flags out of independent flags
  if (flags & PMAP_WRITE && !(flags & PMAP_READ) )
  {
    // IA32 does not support write only pages => return error
    return VM_ACCESS;
  }
  new_ptbl_flags = (PTBL_FLAGS_P
                | (flags & (PMAP_READ|PMAP_WRITE) == 0 ? 0 : PTBL_FLAGS_US)        // not readable nor writable => supervisor page
                | (flags & PMAP_READ       ? 0 : 0 )
                | (flags & PMAP_WRITE      ? PTBL_FLAGS_RW : 0)
                | (flags & PMAP_EXEC       ? 0 : 0)
                | (flags & PMAP_CACHABLE   ? 0 : PTBL_FLAGS_CD)
                | (flags & PMAP_BUFFERABLE ? 0 : PTBL_FLAGS_WT )); // ??? 

  pmap_get_pdir_entry(pmap->pdir_base_v, V_TO_PDIR_IX(virt), &ptbl_base_p, &pdir_flags); 
  
  if (ENTRY_PRESENTF(pdir_flags))
  {
    pmap_get_ptbl_entry(V_TO_P(ptbl_base_p), V_TO_PTBL_IX(virt), &page_base_p, &ptbl_flags);
    if (!ENTRY_PRESENTF(ptbl_flags))
    {
      // trying to change the flags of a Page which is not mapped
      return VM_FAILED;
    }

    // set new Flags in Page Table Entry
    pmap_set_ptbl_entry(V_TO_P(ptbl_base_p), V_TO_PTBL_IX(virt), page_base_p, new_ptbl_flags);
  
  }
  else
  {
    // trying to change the flags of a Page which is not mapped
    return VM_FAILED;
  }

  // Invalidate TLB Entry
  __inv_tlb_entry(virt);
  
  return VM_OK;
}

int vm_pmap_change_region_flags(pmap_t pmap, address_t virt_start, address_t virt_end, int flags)
{
  address_t pos;
  address_t ptbl_base_p,page_base_p;
  unsigned short pdir_flags, ptbl_flags, new_ptbl_flags;
  int ptbl_ix;

  // compute architecture dependent flags out of independent flags
  if (flags & PMAP_WRITE && !(flags & PMAP_READ) )
  {
    // IA32 does not support write only pages => return error
    return VM_ACCESS;
  }
  new_ptbl_flags = (PTBL_FLAGS_P
                | PTBL_FLAGS_US
                | (flags & PMAP_READ       ? 0 : 0 )
                | (flags & PMAP_WRITE      ? PTBL_FLAGS_RW : 0)
                | (flags & PMAP_EXEC       ? 0 : 0)
                | (flags & PMAP_CACHABLE   ? 0 : PTBL_FLAGS_CD)
                | (flags & PMAP_BUFFERABLE ? 0 : PTBL_FLAGS_WT )); // ??? 

  pos = virt_start;
  while (virt_start < virt_end)   // for all Page Directory Entries in Region
  {

    // get Page Directory Entry
    pmap_get_pdir_entry(pmap->pdir_base_v, V_TO_PDIR_IX(pos), &ptbl_base_p, &pdir_flags); 
  
    if (!ENTRY_PRESENTF(pdir_flags))
    {
      // Page Directory Entry does not exist
      return VM_FAILED;
    }

    while ((ptbl_ix = V_TO_PTBL_IX(pos)) < PTBL_NUM_ENTRIES ) // for all Page Table Enties
    {
      
      pmap_get_ptbl_entry(V_TO_P(ptbl_base_p), ptbl_ix, &page_base_p, &ptbl_flags);
      if (!ENTRY_PRESENTF(ptbl_flags))
      {
        // trying to change the flags of a Page which is not mapped
        return VM_FAILED;
      }

      // set new Flags in Page Table Entry
      pmap_set_ptbl_entry(V_TO_P(ptbl_base_p), ptbl_ix, page_base_p, new_ptbl_flags);

      // advance one page
      pos += PAGESIZE;   

    }

  }
  
  // Invalidate TLB
  __inv_tlb();
 
  return VM_OK;
}

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;

  address_t map_pos_src = vm_pmap_pagebuff_get_page();
  address_t map_pos_dest = vm_pmap_pagebuff_get_page();

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

  byteCopy(map_pos_dest, map_pos_src, PAGESIZE);

  // remove mapping of dest & src page
  err1 = vm_pmap_page_remove(pmap, map_pos_dest);
  err2 = vm_pmap_page_remove(pmap, map_pos_src);
  vm_pmap_pagebuff_release_page(map_pos_dest);
  vm_pmap_pagebuff_release_page(map_pos_src);
  if (err1 != VM_OK || err2 != VM_OK)
  {
    return VM_FAILED;
  }
  return VM_OK;
}

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!!!    ***/

  int err;
  address_t map_pos = vm_pmap_pagebuff_get_page();
  
  // 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_pos, PAGETOADDR(src->pagenr),
                     PMAP_READ | PMAP_WRITE) != VM_OK)
  {
    // return error
    return VM_FAILED;
  }

  zeroOut(map_pos, PAGESIZE);

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

  return VM_OK;
}

/* print some infos about this pmap */
void vm_pmap_print(pmap_t pmap)
{
  int free_pages=0, mapped_pages=0, ptbls=0, free_tbl_slots = 0;
  int *pdir_entry, *ptbl_entry, *ptbl_base;
  pmap_free_entry tbl;
  printf("pmap of \"%s\": pdir_base_p=0x%08x\n", pmap->pdom->name, pmap->pdir_base_p);

  pdir_entry = (int*)pmap->pdir_base_v;
  // walk through Page Directory
  while (pdir_entry < ((int*)pmap->pdir_base_v + 1024))
  {
    if (!ENTRY_PRESENT(pdir_entry)) 
    {
      // empty entry
      free_pages += 1024;
    }
    else
    {
      ptbl_base = (int*)P_TO_V((int)*pdir_entry & PAGEFRAMEMASK); // Base of Page Table
      ptbl_entry = ptbl_base;
      ptbls++;
      while (ptbl_entry < ((int*)ptbl_base + 1024)) 
      {
        if (!ENTRY_PRESENT(ptbl_entry))
          free_pages++;
        else
          mapped_pages++;
        ptbl_entry++; // next Page Table Entry
        // printf("*ptbl_entry=0x%08x, ptbl_entry=0x%08x\n", *ptbl_entry, ptbl_entry);
      }
      
    }
    pdir_entry++;  // Next Page Directory Entry
  }

  // count free table slots
  free_tbl_slots = (pmap_ptbl_seg_bottom - pmap_pdir_seg_top)/PDIR_SIZE; // space between Page Directories an Page Tables
  // count free slots in pmap_freelist_pdir
  TAILQ_FOREACH(&pmap_freelist_pdir, tbl, freeq)
    free_tbl_slots++;
  // count free slots in pmap_freelist_ptbl
  TAILQ_FOREACH(&pmap_freelist_ptbl, tbl, freeq)
    free_tbl_slots++;


  printf("      free_pages:  %7i (%4iMB)    mapped_pages: %6i (%iMB)\n", free_pages, free_pages>>8, mapped_pages, mapped_pages>>8);
  printf("      page tables: %7i         free table slots: %6i\n", ptbls, free_tbl_slots);
  
}

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

/* 
 * Lookup physical address of page at virtual address virt in pmap pmap 
 */
address_t vm_pmap_lookup_paddr(pmap_t pmap, address_t virt)
{
  /* 1) Find apropriate PDIR entry and check if it has a     *
   *    valid PTBL asociated.                                *
   * 2) Read the PTBL entry                                  */
  unsigned short pdir_flags, ptbl_flags;
  address_t pdir_base_v  = pmap->pdir_base_v;            // Base address
  int pdir_ix = V_TO_PDIR_IX(virt);                      // Index in Page Directory
  int ptbl_ix = V_TO_PTBL_IX(virt);                      // Index in Page Table
  address_t ptbl_base_v, ptbl_base_p, page_base_p;
 
  // Read Page Directory Entry
  pmap_get_pdir_entry(pdir_base_v, pdir_ix, &ptbl_base_p, &pdir_flags);
  if (ENTRY_PRESENTF(pdir_flags)) 
  {
    // Page Table exists => get address
    ptbl_base_v = (address_t)P_TO_V(ptbl_base_p);
  }
  else
  {
    // Page Table does not exist => return NULL
    return NULL;
  }
  
  // Read Page Table Entry
  pmap_get_ptbl_entry(ptbl_base_v, ptbl_ix, &page_base_p, &ptbl_flags);
  if (ENTRY_PRESENTF(ptbl_flags))
  {
    // Page Table Entry exists => return physical address
    return page_base_p;
  }
  else
  {
    // Page Table Entry is not present => return NULL
    return NULL;
  }

}
#if 0
/*
 * This function tells, what kind of Page Fault has happend. This is either
 *  PMAP_READ, PMAP_WRITE or PMAP_EXEC
 */
int vm_pmap_lookup_type(pmap_t pmap, address_t virt)
{
  /* 1) Find apropriate PDIR entry and check if it has a     *
   *    valid PTBL asociated.                                *
   * 2) Read the PTBL entry                                  *
   *     check the flags                                     */
  unsigned short pdir_flags, ptbl_flags;
  address_t pdir_base_v  = pmap->pdir_base_v;            // Base address
  int pdir_ix = V_TO_PDIR_IX(virt);                      // Index in Page Directory
  int ptbl_ix = V_TO_PTBL_IX(virt);                      // Index in Page Table
  address_t ptbl_base_v, ptbl_base_p, page_base_p;
 
  // Read Page Directory Entry
  pmap_get_pdir_entry(pdir_base_v, pdir_ix, &ptbl_base_p, &pdir_flags);
  if (ENTRY_PRESENTF(pdir_flags)) 
  {
    // Page Table exists => get address
    ptbl_base_v = (address_t)P_TO_V(ptbl_base_p);
  }
  else
  {
    // Page Table does not exist => read error
    return PMAP_READ;
  }
  
  // Read Page Table Entry
  pmap_get_ptbl_entry(ptbl_base_v, ptbl_ix, &page_base_p, &ptbl_flags);
  if (ENTRY_PRESENTF(ptbl_flags))
  {
    // Page Table Entry exists => check flags
    if (ptbl_flags & PDIR_FLAGS_RW)
    {
      // page is read and writable => has to be an access violation
      PANIC("Page Fault: access violation");

    } 
    else 
    {

    }
  }
  else
  {
    // Page Table Entry is not present => read error
    return PMAP_READ;
  }

}
#endif

/*
 * Maps a Page from one Protection Domain to an other
 */
int vm_pmap_map_page_in_other_pmap(pmap_t from_pmap, address_t from_virt, pmap_t to_pmap, address_t to_virt, int flags)
{
  address_t phys;
  int err;

  // get the physical address of the address from_virt
  phys = vm_pmap_lookup_paddr(from_pmap, from_virt);
  if (phys == NULL)
    return VM_FAILED;

  // map it to other PD at address to_virt
  err = vm_pmap_enter(to_pmap, to_virt, phys,
                flags);
  if (err != VM_OK)
    return VM_FAILED;

  return VM_OK;
}


/* wite pdir_entry 
 * XXX implement in assembler ??? */
void pmap_set_pdir_entry(address_t pdir_base_v, int i, address_t ptbl_base_p, unsigned short pdir_flags) 
{
  
  int pdir_entry, *pint;
  address_t pdir_addr;
  
  // printf("pmap_set_pdir_entry(): ptbl_base_p=0x%08x\n", ptbl_base_p);
  ASSERT(0 == ((int)ptbl_base_p & PAGEMASK));  // Page Table has to be aligned to the 4k page boundaries

  pdir_entry = ptbl_base_p + pdir_flags;
  
  pdir_addr = (address_t) (pdir_base_v + i*4);
  pint = (int*)pdir_addr; 
  *pint = pdir_entry;
  
  // printf("pmap_set_pdir_entry(): entry=0x%08x addr=0x%08x\n", *pint, pint);
}

/* get pdir_entry */
void pmap_get_pdir_entry(address_t pdir_base_v, int i, address_t* ptbl_base_p, unsigned short* pdir_flags) 
{
  int *pint;
  address_t pdir_addr;
  
  pdir_addr = (address_t) (pdir_base_v + i*4);
  pint = (int*)pdir_addr; 

  *ptbl_base_p = *pint & PAGEFRAMEMASK;
  *pdir_flags = *pint & PAGEMASK;

}


/* write ptbl entry */
void pmap_set_ptbl_entry(address_t ptbl_base_v, int i, address_t page_base_p, unsigned short ptbl_flags) 
{
  
  int ptbl_entry, *pint;
  address_t ptbl_addr;

  ASSERT(0 == ((int)page_base_p & PAGEMASK) ); // Page has to be aligned to the 4k page boundaries

  ptbl_entry = page_base_p + ptbl_flags;

  ptbl_addr = (address_t) (ptbl_base_v + i*4);
  pint = (int)ptbl_addr; 
  *pint = ptbl_entry;
  
  // printf("pmap_set_ptbl_entry(): entry=0x%08x addr=0x%08x\n", *pint, pint);

}

/* read ptbl entry */
void pmap_get_ptbl_entry(address_t ptbl_base_v, int i, address_t* page_base_p, unsigned short* ptbl_flags) 
{
  
  int *pint;
  address_t ptbl_addr;

  ptbl_addr = (address_t) (ptbl_base_v + i*4);
  pint = (int)ptbl_addr; 
  *page_base_p = *pint & PAGEFRAMEMASK;
  *ptbl_flags = *pint & PAGEMASK;
}

address_t pmap_alloc_pdir(void)
{
  struct pmap_freelist *new_pdir_base;

  if (TAILQ_EMPTY(&pmap_freelist_pdir))
  {
    // no free entry available => create one
    new_pdir_base = pmap_pdir_seg_top;  // new pdir at top
    pmap_pdir_seg_top += PDIR_SIZE;     // enlarge pdir segment
    if (pmap_pdir_seg_top >= pmap_ptbl_seg_bottom)
    {
      // we used up all available memory for the internal structures
      PANIC("PMap: Panic, no more memory available! Terminating...\n");
    }
  }
  else
  {
    // there is at least one unused entry on the list => use it
    new_pdir_base = TAILQ_FIRST(&pmap_freelist_pdir);
    TAILQ_REMOVE(&pmap_freelist_pdir, new_pdir_base, freeq);
  }

  // Zero new Page Directory
  zeroOut((address_t)new_pdir_base, PDIR_SIZE);
  return (address_t)new_pdir_base;

}

address_t pmap_alloc_ptbl(void)
{
  struct pmap_freelist *new_ptbl_base;

  if (TAILQ_EMPTY(&pmap_freelist_ptbl))
  {
    // no free entry available => create one
    pmap_ptbl_seg_bottom -= PTBL_SIZE;     // enlarge ptbl segment
    new_ptbl_base = pmap_ptbl_seg_bottom;  // append ptbl at the bottom
    if (pmap_pdir_seg_top >= pmap_ptbl_seg_bottom)
    {
      // we used up all available memory for the internal structures
      PANIC("PMap: Panic, no more memory available! Terminating...\n");
    }
  }
  else
  {
    // there is at least one unused entry on the list => use it
    new_ptbl_base = TAILQ_FIRST(&pmap_freelist_ptbl);
    TAILQ_REMOVE(&pmap_freelist_ptbl, new_ptbl_base, freeq);
  }

  // Zero Page Table 
  zeroOut((address_t)new_ptbl_base, PTBL_SIZE);
  return (address_t)new_ptbl_base;
}

void pmap_free_pdir(address_t pdir_addr)
{
  if (pdir_addr + PDIR_SIZE == pmap_pdir_seg_top)
  {
    // the element is at the top of the PDIR segment, so we just need
    // to adjust the boundary and don't have to use the list
    pmap_pdir_seg_top -= PDIR_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_freelist_pdir, (pmap_free_entry)pdir_addr, freeq);
  }
}

void pmap_free_ptbl(address_t ptbl_addr)
{
  if (ptbl_addr == pmap_ptbl_seg_bottom)
  {
    // 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
    pmap_ptbl_seg_bottom += PTBL_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_freelist_ptbl, (pmap_free_entry)ptbl_addr, freeq);
  }
}

#if 0
/*
 * Initalize the page bitmap
 */
void pmap_pagebitmap_init(struct vm_segmap* segmap, unsigned short *pagebitmap_selector)
{

  address_t pb_addr;
  int i;

  // 1GB of virtual memory:
  //   8 Pages à 4096*8 Bit represent 262144 Pages containing 1GB of Memory
  size_t pb_size = PAGEBITMAP_PAGES * 4096;  
  

  // allocate memory for the pagebitmap
  pb_size = vm_allocate_bootup_memory(segmap, &pb_addr, pb_size, VM_TYPE_VM_STATIC);
  
  // make segment in gdte
  pagebitmap_selector = Get_New_GDTE();
  Set_GDTEntry(pagebitmap_selector, (unsigned long)pb_addr, PAGEBITMAP_PAGES, MM_DATA_B5, MM_DATA_B6);
  
  // zero all pages of page-bitmap
  for (i=0; i<PAGEBITMAP_PAGES; i++) 
    __zero_page(pagebitmap_selector, i*4096);

  printf("pmap_pagebitmap_init(): sel=0x%04x, pb_addr=0x%08x \n", pagebitmap_selector, pb_addr);

}
#endif

/*
 * Initalize TSS Buffer
 */
void pmap_tssbuff_init(struct vm_segmap* segmap) 
{
  int i;

  // allocate Memory for the buffer
  tssbuff_size = vm_allocate_bootup_memory(segmap, &tssbuff_base, tssbuff_size, VM_TYPE_VM_STATIC);

  // zero all pages of tsbuff
  for (i=0; i<TSS_BUF_PAGES; i++) 
    __zero_page(GC_OS_DATA_SEL, tssbuff_base + i*4096); // in the Kernel Data Segment at address tssbuff_base

  // set tssbuff_hint
  tssbuff_hint = tssbuff_base;

  printf("Initialized tssbuff: base=0x%08x, size=0x%08x, hint=0x%08x\n", tssbuff_base, tssbuff_size, tssbuff_hint);

}

/* returns true if tss is free */
int tss_isfree(tss_t *p_tss) 
{
  int i;
  for (i=0; i<TSS_SEG_SIZE/sizeof(int); i++) 
  {
    if (p_tss->data[i] != 0 )
      return 0; // TSS not free
  }
  return 1; // TSS is free
}

/*
 * returns a new TSS Segment
 */
int pmap_get_new_tss(unsigned short *tss_sel, unsigned short *tss_alias, struct protection_domain *d) 
{
  tss_t *p_tss = (tss_t*)tssbuff_hint;
  Thread* pdThread;

  // find free slot in TSS Buffer
  while (!tss_isfree(p_tss))
  {
    // printf("Test next TSS slot at 0x%08x\n",p_tss);
    p_tss += 1;  // try next tss
  }
 
  tssbuff_hint = (address_t)(p_tss + 1);
  
  // printf("tssbuff_base=0x%08x, tssbuff_hint=0x%08x tssbuff_size=%iKB\n", tssbuff_base, tssbuff_hint, tssbuff_size>>10);
  // printf("tssbuff_end=0x%08x\n", tssbuff_base + tssbuff_size);
  
  if ( tssbuff_hint > (tssbuff_base + tssbuff_size))  // is new tss inside buffer?
      return VM_FAILED; // no more space for new tss

  tss_sel = Get_New_GDTE();   // get new Selecotor for TSS Segment
  Set_GDTEntry(tss_sel, (unsigned long)V_TO_P(p_tss), TSS_SEG_SIZE, K_TSS_B5  , K_TSS_B6); // XXX Descriptor Privilege Level

  tss_alias = Get_New_GDTE(); // get new Selector for TSS Alias Segement
  Set_GDTEntry(tss_alias, (unsigned long)V_TO_P(p_tss), TSS_SEG_SIZE, MM_DATA_B5, MM_DATA_B6_BYTE);  // XXX Descriptor Privilege Level
  __createTSS(tss_alias);
  //__activateTSS(tss_sel);
 
  printf("found tss: tss_sel=0x%x, tss_alias=0x%x, tss_base=0x%08x\n", tss_sel, tss_alias, p_tss);
  // printf("           tssbuff_base=0x%08x, tssbuff_hint=0x%08x tssbuff_size=%iKB\n", tssbuff_base, tssbuff_hint, tssbuff_size>>10);

  return VM_OK;

}

/* Initialize the Page Buffer
 */
void vm_pmap_pagebuff_init(struct vm_segmap* segmap)
{
  address_t pagebuff_base, v_addr;
  unsigned long i;

  // Initialize the Lock
  lockInit(&pagebuff_lock);

  // Allocate the Buffer
  pagebuff_size = vm_allocate_bootup_memory(segmap, &pagebuff_base, pagebuff_size, VM_TYPE_VM_STATIC);
  if (pagebuff_size == 0)
    PANIC("Allocating the Page Buffer failed");
  
  // Initialize the page buffer
  v_addr = pagebuff_base;
  for (i = 0; i<PAGE_BUFF_PAGES; i++)
  {
    if (v_addr < pagebuff_base + pagebuff_size)
    {
      // page available
      pagebuff[i].free = 1;
      pagebuff[i].v_addr = v_addr;
    }
    else
    {
      // page not allocated
      pagebuff[i].free = 0;
    }
    v_addr += PAGESIZE;   // increment v_addr
  }

  // initialize the hint
  pagebuff_hint = 0;

}

/* Returns a free page from the Page Buffer */
address_t vm_pmap_pagebuff_get_page()
{
  int hint_orig = pagebuff_hint;
  int flags;
  address_t result;

  // get Lock
  ATOMIC_START(flags);
  lock(&pagebuff_lock);

  do
  {
    if (pagebuff[pagebuff_hint].free)
    {
      // found free page => return it
      pagebuff[pagebuff_hint].free = 0;
      result = pagebuff[pagebuff_hint].v_addr;
      pagebuff_hint = (pagebuff_hint+1) % PAGE_BUFF_PAGES;
      // release lock
      unlock(&pagebuff_lock);
      ATOMIC_END(flags);
      // printf("vm_pmap_pagebuff_get_page     %p\n", result);
      return  result;
    }
    else
    {
      // page not available => try next
      pagebuff_hint = (pagebuff_hint+1) % PAGE_BUFF_PAGES;
      if (pagebuff_hint == hint_orig)
      {
        // no free page found ??? 
        PANIC("pmap_pagebuff_getpage() failed.");
      }
    }

  } while (1);

}

/* releases the page */
void vm_pmap_pagebuff_release_page(address_t v_addr)
{
  int page = ((unsigned long)v_addr - (unsigned long)pagebuff[0].v_addr)/PAGESIZE;  // position in the page buffer
  int flags;
  
  ASSERT(page < PAGE_BUFF_PAGES);
 
  ATOMIC_START(flags);
  {
    lock(&pagebuff_lock);
    pagebuff[page].free = 1;
    unlock(&pagebuff_lock);
  }
  ATOMIC_END(flags);

  // printf("vm_pmap_pagebuff_release_page %p\n", v_addr);

}

/* removes all free pages of the page buffer from the kernel pmap */
void vm_pmap_pagebuff_unmap_free_pages()
{
  int i;
  
  for (i=0; i<PAGE_BUFF_PAGES; i++)
  {
    if (pagebuff[i].free)
    {
      vm_pmap_page_remove(&vm->kernel->vm_pmap, pagebuff[i].v_addr);
    }
  }

}

