/*
    Copyright 2001 (c) by Claudio Jeker,
    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/Attic/vm_page.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3048 $
    Last Date of Change:   $Date: 2007-07-20 18:11:54 +0200 (Fre, 20 Jul 2007) $  by: $Author: sryffel $
 
*/

/* vm_page.c: API Functions for vm object handler */
#include "Support.h"
#include "Assert.h"

#include "vm.h"
#include "vm_pmap.h"
#include "vm_alloc.h"
#include "vm_page.h"

#include "MemoryLayout.h"


/* There exist multiple global page queues where a page can be located:
	 - free list:  list of pages that are not used but not zeroed out
	 - zero list:  list of pages that are free and zeroed out
	 - used list:  list of pages that are in use
	 - wired list: list of pages that are specially wired and in use 
								 (e.g. for drivers)
	 - unused list:list of pages that are left because of rounding errors.
 
*/

struct vm_pageq_header
      vm_page_free,
      vm_page_zero,
      vm_page_used,
      vm_page_wired,
      vm_page_unused;

struct vm_page_state
{
  struct vm_page* pagearray;
  struct vm_pageq_header* freeq;
  struct vm_pageq_header* zeroq;
  struct vm_pageq_header* usedq;
  struct vm_pageq_header* wiredq;
  struct vm_pageq_header* unusedq;
}
vm_page_state;

static int page_isfree(struct vm_segmap* segmap, unsigned long pagenr)
{
  address_t paddr;
  int i;
  int end;

  paddr = PAGETODMAPPED(pagenr); /* segmap is direct mapped */

  if ( segmap->text <= paddr && segmap->text + segmap->textsize > paddr )
    return 0;
  if ( segmap->data <= paddr && segmap->data + segmap->datasize > paddr )
    return 0;
  if ( segmap->bss  <= paddr && segmap->bss  + segmap->bsssize  > paddr )
    return 0;
  if ( segmap->bootstackbottom <= paddr &&
       segmap->bootstackbottom + segmap->bootstacksize > paddr )
    return 0;
  if ( segmap->optsegment <= paddr &&
       segmap->optsegment + segmap->optsegmentsize > paddr )
    return 0;

  end = segmap->bootalloccnt - 1; /* last valid entry is the hint */
  for ( i = 0; i < end; i++ )
  {
    if (segmap->bootalloc[i].virt <= paddr &&
        segmap->bootalloc[i].virt + segmap->bootalloc[i].size > paddr )
      return 0;
  }

  return 1;
}

void vm_page_init(struct vm_segmap* segmap)
/* called only once at startup, allocates one vm_page struct per physical
	 page. If this function fails we get a nice PANIC else we have allmost
	 all pages on the free list.
 */
{
  struct vm_page* pa;
  struct vm_physmap *physmap;
  unsigned long pagenr;
  size_t pageqsize;
  size_t allocatedsize;
  unsigned long freepages;
  unsigned long fp;
  unsigned long up;
  unsigned long n;
  int physcnt;
  int i;
  int j;

  /*  STEP 1: initialize the queues. */
  TAILQ_INIT(&vm_page_free);
  TAILQ_INIT(&vm_page_zero);
  TAILQ_INIT(&vm_page_used);
  TAILQ_INIT(&vm_page_wired);
  TAILQ_INIT(&vm_page_unused);

  vm->page = &vm_page_state;
  vm->page->freeq = &vm_page_free;
  vm->page->zeroq = &vm_page_zero;
  vm->page->usedq = &vm_page_used;
  vm->page->wiredq = &vm_page_wired;
  vm->page->unusedq = &vm_page_unused;

  /* STEP 2: allocate vm_page structures.
  	 We count all pages also the already used ones. So it is possible to 
  	 free them.
   */
  
  if (NULL==segmap)
    PANIC("segmap is NULL");
  
  physmap = segmap->physmap;
  physcnt = segmap->physcnt;
  for ( i = 0, freepages = 0; i < physcnt; i++ )
    freepages += physmap[i].end - physmap[i].start;

  vm->stat.npages = freepages;

  /* OK, now I know the number of main memory pages of the system.
  	 NOTE: It is not clever to mix different memory types into the main
  	 memory. e.g. the SA1100 supports SDRAM and SRAM and these two regions 
  	 should not be mixed together. It is more useful to map the SRAM seperatly
  	 for special reasons (e.g. routing table) via a special object manager.
   */

  /* Now we need to calculate the estimated room needed to store all vm_page
  	 descriptors. This is not that complicated, the tricky part is to find 
  	 a place to add the structures. At the moment we know for sure, that all
  	 used memory is in the segmap. These are:
  	 - kernel text:
  	 - kernel data:			these three are concatenated together
  	 - kernel bss:
  	 - boot up stack: (together with the exception stack) 1 page
  	 - additional code & data segments also concatenated together
  	 	 or done in an other way (the boot loader has to enter this data into
  		 the segmap)
  	 - pmap segment...: A linear memory region for all the pmap structures
  	 - and other segments that were allocated via vm_allocate_bootup_memory()
   */

  pageqsize = freepages * sizeof(struct vm_page);

  /* allocate the needed memory this is done on page granularity
  			allocated size = (pagesize + PAGESIZE) & PAGEFRAMEMASK;
   */

  allocatedsize = vm_allocate_bootup_memory(segmap, (address_t)&vm->page->pagearray, pageqsize, VM_TYPE_VM_STATIC);
  /* whipe out the complete region also the unused part. */
  zeroOut(vm->page->pagearray, allocatedsize);

  printf("vm_page_init: freepages %lu pageqsize %lu allocatedsize %lu\n",
         freepages, pageqsize, allocatedsize);

  /* STEP 3: fill up the array */
  fp = 0;
  up = 0;
  for ( i = 0, pa = vm->page->pagearray; i < physcnt; i++ )
  {
    n = physmap[i].end - physmap[i].start;

    printf("vm_page_init: freepages %lu n %lu\n", freepages, n);

    ASSERT(n <= freepages ); /* lost some pages, this may not happen */

    /* set up page array pointers */
    pagenr = physmap[i].start;

    for ( j = 0; j < n; j++ )
    {
      pa[j].pagenr = pagenr;
      if ( page_isfree(segmap, pagenr) )
      {
        TAILQ_ADDTAIL(&vm_page_free, &pa[j], pageq);
        pa[j].pqflags = PQ_FREE;
        pa[j].refcount = 0;
        fp++;
        /* XXX init reflock */
      }
      else
      {
        TAILQ_ADDTAIL(&vm_page_used, &pa[j], pageq);
        pa[j].pqflags = PQ_USED;
        pa[j].refcount = 1;
        up++;
        /* XXX init reflock */
      }
      pagenr++;
    }
    /* finish */
    pa += n;
    freepages -= n;
  }
  if ( freepages > 0 )
  {
    /* there is some space left, fill it up with unused entries */
    for ( j = 0; j < freepages; j++ )
      TAILQ_ADDTAIL(&vm_page_unused, &pa[j], pageq);
    pa += freepages;
  }
  for ( j = (allocatedsize - pageqsize)/sizeof(struct vm_page); j > 0; )
  {
    --j;
    TAILQ_ADDTAIL(&vm_page_unused, &pa[j], pageq);
  }

  vm->stat.free = fp;
  vm->stat.active = up;
  vm->stat.wired = 0;
  vm->stat.reserved = 0;
  vm_page_print_all(0);

  /* done */
}

static int is_real_page(unsigned long pagenr)
{
  int i;

  for ( i = 0; i < vm->physcnt; i++ )
    if ( vm->physmap[i].start <= pagenr && pagenr < vm->physmap[i].end )
      return 1;
  return 0;
}

struct vm_page* vm_page_map(unsigned int pagenr)
{
  int i;
  unsigned long p;
  struct vm_page* page;

  if ( is_real_page(pagenr) == 0 )
  {
    return NULL; /* only real memory can be remapped */
  }

  for ( i = 0, p = 0; i < vm->physcnt; i++ )
  {
    if ( vm->physmap[i].start <= pagenr && pagenr < vm->physmap[i].end )
    {
      p += (pagenr - vm->physmap[i].start);
      break;
    }
    p += vm->physmap[i].end + vm->physmap[i].start;
  }
  ASSERT(p <= vm->stat.free );

  page = &vm->page->pagearray[p];

  if ( page->pagenr > pagenr )
  {
    while ( p-- > 0 )
    {
      page = &vm->page->pagearray[p];
      if ( page->pagenr == pagenr )
        break;
    }
    return NULL;
  }
  else if ( page->pagenr < pagenr )
  {
    while ( p++ < vm->stat.free )
    {
      page = &vm->page->pagearray[p];
      if ( page->pagenr == pagenr )
        break;
    }
    return NULL;
  }

  //-CJ this is no longer valid, we use vm_page_map to
  //-CJ	wire the kernel pages into the user
  //-CJ	if ( vm_page_getref(page) != 1 )
  //-CJ		printf("vm_page_map: page %#lx has refcount non equal to one", page->pagenr);

  return page;
}

static struct vm_page* allready_wired(unsigned long nr)
      /* check if the page with pagenr nr is already wired.
      	 This funktion performs linear searches and is therefor slow if
      	 a lot of pages are wired. But because this page is only used while
      	 allocating wired pages this is not that grave.
       */
{
  struct vm_page* p;

  TAILQ_FOREACH(&vm_page_wired, p, pageq)
  {
    if ( p->pagenr == nr )
    {
      ASSERT( p->pqflags |= PQ_WIRED );
      ASSERT( p->refcount > 0 );
      return p;
    }
  }
  return NULL;
}

static int allocate_new(void)
{
  struct vm_page* pa;
  int i;

  if ( steal_alloc((address_t*)&pa, PAGESIZE, 0) != SA_OK )
    return VM_NOMEM;

  zeroOut(pa, PAGESIZE);
  for ( i = PAGESIZE/sizeof(*pa); i > 0; )
  {
    --i;
    TAILQ_ADDTAIL(&vm_page_unused, &pa[i], pageq);
  }
  return VM_OK;
}

struct vm_page* vm_page_wire(address_t phys)
      /* allocate a vm_page and wire that page to a physical address.
      	 This function can be used by drivers to allocate direct mapped memory
       */
{
  struct vm_page* p;
  unsigned long pagenr;

  pagenr = PAGENUMBER(phys);

  if ( is_real_page(pagenr) == 1 )
  {
    return vm_page_map(pagenr);
    //-CJ XXX quick and dirty fix
    //		WARNING("tried to wire real page\n");
    //		return NULL;
  }

  p = allready_wired(pagenr);
  if ( p == NULL )
  {
    p = TAILQ_FIRST(&vm_page_unused);
    if (p == NULL )
    {
      /* we should allocate some new page structures but ... */
      if ( allocate_new() != VM_OK )
      {
        WARNING("Out of wired page structures");
        return NULL;
      }
      p = TAILQ_FIRST(&vm_page_unused);
      ASSERT( p != NULL );
    }
    TAILQ_REMOVE(&vm_page_unused, p, pageq);

    p->pagenr = pagenr;
    p->pqflags = PQ_WIRED;
    p->refcount = 1;
    vm->stat.wired++;

    TAILQ_ADDTAIL(&vm_page_wired, p, pageq);
  }
  else
  {
    p->refcount++;
  }

  return p;
}

/* Object API:
 *	the following functions should be used by the vm object to access
 *	vm_pages. The only value of a page that can be savely accessed from
 *	a vm_pref is the page number via the VM_PAGE_GETNR makro.
 *	Because this is a read only value we do not need a lock.
 */
unsigned long vm_page_getnr(struct vm_page* p)
/* get the physical page nr for this page */
{
  return p->pagenr;
}

address_t vm_page_getaddr(struct vm_page* p)
{
  return PAGETOADDR(p->pagenr);
}

int vm_page_ref(struct vm_page* p)
/* add reference to page that's the same as 'increase refcount' */
{
  /* XXX locking */
  return ++p->refcount;
}


int vm_page_unref(struct vm_page* p)
/* remove reference to page or just decrease the refcount */
{
  /* XXX locking */
  return --p->refcount;
}


int vm_page_getref(struct vm_page* p)
/* return current refcount */
{
  /* XXX locking */
  return p->refcount;
}


struct vm_page* vm_page_get(unsigned int flags)
      /* returns a free page if possible */
{
  struct vm_page *p;

  /* XXX locking */
  if ( (flags & PQ_ZERO) == PQ_ZERO )
  {
    p = TAILQ_FIRST(&vm_page_zero);
    if ( p != NULL )
    {
      ASSERT( p->pqflags & PQ_ZERO );
      TAILQ_REMOVE(&vm_page_free, p, pageq);
      TAILQ_ADDTAIL(&vm_page_used, p, pageq);
      p->pqflags = PQ_USED;
      vm->stat.active++;
      vm->stat.free--;
      return p;
    }
  }
  p = TAILQ_FIRST(&vm_page_free);
  if (p != NULL )
  {
    ASSERT( p->pqflags & PQ_FREE );
    TAILQ_REMOVE(&vm_page_free, p, pageq);
    TAILQ_ADDTAIL(&vm_page_used, p, pageq);
    p->pqflags = PQ_USED;
    if ( (flags & PQ_ZERO) == PQ_ZERO )
    {
      vm_pmap_page_zero(&vm->kernel->vm_pmap, p);
    }
    vm->stat.active++;
    vm->stat.free--;
    return p;
  }
  p = TAILQ_FIRST(&vm_page_zero);
  if (p == NULL )
    return NULL;
  ASSERT( p->pqflags & PQ_ZERO );
  TAILQ_REMOVE(&vm_page_zero, p, pageq);
  TAILQ_ADDTAIL(&vm_page_used, p, pageq);
  p->pqflags = PQ_USED;
  vm->stat.active++;
  vm->stat.free--;
  return p;
}


int vm_page_unget(struct vm_page* p)
/* if refcount drops to zero this function should be called */
{
  /* XXX locking */
  if ( p->pqflags |= PQ_USED )
  {
    TAILQ_REMOVE(&vm_page_used, p, pageq);
    TAILQ_ADDTAIL(&vm_page_free, p, pageq);
    p->refcount = 0; /* just to be sure */
    p->pqflags = PQ_FREE;

    vm->stat.active--;
  }
  else if ( p->pqflags |= PQ_WIRED )
  {
    TAILQ_REMOVE(&vm_page_wired, p, pageq);
    TAILQ_ADDTAIL(&vm_page_unused, p, pageq);
    p->refcount = 0; /* just to be sure */
    p->pqflags = 0;

    vm->stat.wired--;
  }
  else
  {
    vm_page_print(p);
    PANIC("page was neither USED nor WIRED");
  }
  vm->stat.free++;

  return 1;
}


unsigned int vm_page_getflags(struct vm_page* p)
/* get the page queue flags of the page */
{
  /* XXX locking */
  return p->pqflags;
}

struct vm_page* vm_page_copy(struct vm_page* p)
{
  struct vm_page* np;

  np = vm_page_get(0); /* get an empty page */
  if ( np == NULL )
  {
    PANIC("vm_page_copy: No more pages left");
    return NULL;
  }

  vm_pmap_page_copy(&vm->kernel->vm_pmap, p, np);

  vm_page_ref(np);

  return np;
}

void vm_page_print(struct vm_page* p)
{
  printf("\tpage nr %#7lx (%4lu): refcount %u flags %#x\n", p->pagenr, p->pagenr, p->refcount, p->pqflags);
}

void vm_page_print_all(int flags)
{
  struct vm_page* p;
  unsigned long num_used;
  unsigned long num_wired;
  unsigned long num_zero;
  unsigned long num_free;
  unsigned long num_unused;

  num_used = 0;
  num_wired = 0;
  num_zero = 0;
  num_free = 0;
  num_unused = 0;

  if ( flags & VM_SHOW_PAGES )
    printf("vm_page: used pages\n=========================================\n");
  TAILQ_FOREACH(&vm_page_used, p, pageq)
  {
    if ( flags & VM_SHOW_PAGES )
      vm_page_print(p);
    num_used++;
  }

  if ( flags & VM_SHOW_PAGES )
    printf("vm_page: wired pages\n=========================================\n");
  TAILQ_FOREACH(&vm_page_wired, p, pageq)
  {
    if ( flags & VM_SHOW_PAGES )
      vm_page_print(p);
    num_wired++;
  }

  TAILQ_FOREACH(&vm_page_zero, p, pageq)
  {
    num_zero++;
  }

  TAILQ_FOREACH(&vm_page_free, p, pageq)
  {
    num_free++;
  }

  TAILQ_FOREACH(&vm_page_unused, p, pageq)
  {
    num_unused++;
  }

  printf("vm_page statistics:\n");
  printf("  total used pages: %7lu    used: %7lu wired: %7lu\n", num_used + num_wired, num_used, num_wired);
  printf("  total free pages: %7lu    free: %7lu  zero: %7lu\n", num_free + num_zero, num_free, num_zero);
  printf("  total pages:      %7lu     unused descriptors: %7lu\n",
         num_used + num_wired + num_free + num_zero, num_unused);
  printf("  total phys pages: %7lu\n", vm->stat.free);
}

