/*
    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_bootup.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 3034 $
    Last Date of Change:   $Date: 2007-05-29 23:42:27 +0200 (Die, 29 Mai 2007) $  by: $Author: sryffel $
 
*/
#include "Support.h"
#include "Assert.h"
#include "MemoryLayout.h" /* XXX */
#include "TMScheduler.h"
#include "TMProtectionDomain.h"

#include "vm.h"
#include "vm_calls.h"
#include "vm_domain.h"
#include "vm_object.h"
#include "vm_hash.h"
#include "kalloc_api.h"
#include "steal_alloc.h"
#include "vm_hal.h"				// mapSpecialRegions

#include "vm_bootup.h"

/*
	 Bootstrap the vm subsystem. This has to be done in the correct order
	 or it will not work.
 
	 Following steps are made:
	 1. Get the physical memory map. The layout of the physical main memory is in 
	 		this structure. The main memory has normally some gaps between the banks
			of memory, so we need to know where these gaps are and how much main
			memory is available. The physical memory map SHOULD NOT mix multiple
			memory types together. e.g. The StrongARM supports both SRAM and SDRAM.
			Decide which of the ones should be the main memory for Topsy and 
			map / wire the other memory types via a special object manager.
			So it is possible to distinguish between the memory types.
	 2. Get the segmap. The segmap contains info about all pre allocated Memory.
	 3. Initialization of the pmap layer. This step allocates the needed Memory
	 		for the pmap forward or reverse table. The real mappings for the
			Kernel Protection Domain are added later.
	 4. Initialization of the page layer. This step allocates the vm_page 
	 		structures. This is done as soon as possible.
	 5. Install memory exception handler.
	 6. Enter the already used memory into the pmap layer.
	 7. Turn MMU engine on... and there we go...
	 8.	Kernel heap memory init so km_alloc and km_free are possible.
			This has to be done with running MMU because virtual addresses are needed.
	 9. Load the VM Framework. Object handlers need to be...
	 A. Create and map Kernel Protection Domain.
 
	 NOTE: Until the scheduler is up and running page faults have to be triggered
	 			 specially because the virtual memory management can not do lookups.
				 So if the pmap_lookup function needs to call the virtual memory
				 management lookup routine while in boot up Topsy should PANIC!
 
	 IMPORTANT: The lower part of the BOOTSTACK is used to store the physical
	 						memory map and the segmap. So do not push to much onto the stack
							or store to many entries in the segmap or there will be a
							HEAP/STACK overflow.
 
	 EVEN MORE IMPORTANT:
	 		All the init functions called here need to be loaded into the kernel.
			So if you really would like to use separate protection domains for all
			different services of the kernel (VM, TM, IO, NET and more) these
			functions need to be added to the kernel image whereas the real functions
			are loaded into the images for the protection domain.
			So for all programmers, please separate between startup functions and
			the real thing(tm). This is important for VM and TM the other services 
			can be started as soon as we switch to the real thing(tm).
			Running VM in a separate protection domain with minimal sharing is a big
			adventure because the setup is not simple. 
			At the moment we leave this as an exercise to the reader (:
 
 */

struct vm* vm;
struct vm vm_state;

static address_t vm_create_stack(pdom_t kernel);
void vm_pmap_enter_segmap(struct vm_segmap* segmap, struct vm_pmap* vm_pmap);

int vm_page_inited = 0;
struct protection_domain kernel;

struct vm_segmap* vm_init(address_t* mmStack, address_t* tmStack)
{
  struct vm_segmap *segmap;
  ThreadPtr starter;
  extern	pdom_t mainKernelPD;
  extern void ExceptionVectorTable(void);
  extern void ExceptionAddressTable(void);


  vm = &vm_state;
  /* STEP 1 */
  vm->physcnt = vm_get_physmap(&vm->physmap);
  /* the location of the physmap is fixed at BOOTSTACKBOTTOM but
  	 be a nice programmer (:
   */

  /* STEP 2 */
  segmap = vm_get_segmap(vm->physmap, vm->physcnt);
  //-CJ	vm_print_segmap(segmap);

  /* STEP 3 */
  vm_pmap_init(segmap);

  /* STEP 4 */
  vm_page_init(segmap);
  vm_page_inited = 1;

  /* STEP 5 */
  //-CJ		mmInstallErrorHandlers();
  vm_install_exception_handlers();

  /* STEP 6 */
  mainKernelPD = vm_domain_init_kernel(&kernel);

  starter = schedulerRunning();
  starter->pdPtr = mainKernelPD;
  vm_print_segmap(segmap);

  vm_pmap_enter_segmap(segmap, &mainKernelPD->vm_space.vm_pmap);
  /* low level map of io regions etc... */
  mapSpecialRegions(NULL, &mainKernelPD->vm_space.vm_pmap,
                    MSR_IN_KERNEL | MSR_MAP_LLV);

  vm_pmap_print(&mainKernelPD->vm_space.vm_pmap);

  printf("MMU or HELL...\n");
  /* STEP 7 */
  vm_MMU_or_HELL(&mainKernelPD->vm_space.vm_pmap); /* that's self explaining... */

  printf("definitifely MMU...\n");
  /* SETP 8 */

  steal_init(&mainKernelPD->vm_space.vm_pmap, (address_t)STEAL_START,
             PAGENUMBER(STEAL_SIZE));

  km_init(segmap, &mainKernelPD->vm_space.vm_pmap);
  /* IMPORTANT: after STEP 8 calls to
  	 vm_allocate_bootup_memory are frobidden! 
   */

  //-CJ	vm_print_segmap(segmap);

  /* STEP 9 */
  /* setup the vm framework. Here is a possible location to created the VM PD */
  /* a minimal framework needs to be loaded */
  vm_hash_init();
  /* load the object handlers ... */
  vm_manager_init();

  /* STEP A */
  /* ... and then start mapping the kernel ... */
  mainKernelPD = vm_domain_create_kernel(segmap, &kernel);
#if 0
  // why here, why so complicated ???
  /* STEP B */
  /* ... now create the needed thread structures */
  *mmStack = vm_create_stack(mainKernelPD);
  printf("  mm thread stack : anonymous  -> address %p, size 0x%x offset 0\n", *mmStack, TM_DEFAULTKERNELTHREADSTACKSIZE);
  *tmStack = vm_create_stack(mainKernelPD);
  printf("  tm thread stack : anonymous  -> address %p, size 0x%x offset 0\n", *tmStack, TM_DEFAULTKERNELTHREADSTACKSIZE);
#endif
  /* vm_hash_rehash */

  /* ... almost the real thing (tm) */
  return segmap; /* XXX needed for the old mmInit() */
}

static address_t vm_create_stack(pdom_t kernel)
{
  Region_t r;
  struct vm_page* page;
  address_t phys;
  address_t stackbase;
  address_t stackend;
  int err;

  stackbase = (address_t)TM_KERNEL_MODE_STACK_NEAR; /* XXX hint */

  ENSURE( PAGEREMAINDER(stackbase) == 0 );

  r.obj_arg.argsize = 0;
  r.obj_arg.arg[0] = 0;
  r.start = 0;
  r.end = (address_t)TM_DEFAULTKERNELTHREADSTACKSIZE - 1;
  /* VM uses the last valid address and not the first invalid */

  r.cur = VM_ACCESS_READ|VM_ACCESS_WRITE;
  r.max = VM_ACCESS_READ|VM_ACCESS_WRITE;
  r.type = (unsigned short)VM_MANAGER_ANON;

  /* first steal a page */
  page = vm_page_get(PQ_ZERO);
  phys = vm_page_getaddr(page);
  if ( vm_page_ref(page) != 1 )
    WARNING("Page has refcount non equal to one");

  /* now add a region where we like to have the stack */
  err = vm_region_add_near(&kernel->vm_space.vm_map,
                           &kernel->vm_space.vm_pmap, &r, stackbase, 1);
  switch ( err )
  {
  case VM_OK:
    break;
  default:
    printf("    mapping FAILED error %i\n", err);
    PANIC("vm_create_stack: vm_region_add_near failed");
  }

  /* last but not least map the one stolen page to the object (as last page) */
  stackbase = r.start;
  stackend = r.start + TM_DEFAULTKERNELTHREADSTACKSIZE - 1;

  /* then update the pmap layer */
  //-CJ	printf("vm_stack_alloc: vm_pmap_region_enter %p - %p -> %p\n",
  //-CJ			(address_t)PAGEROUNDDOWN(stackend), stackend, phys);
  err = vm_pmap_region_enter(&kernel->vm_space.vm_pmap, phys,
                             (address_t)PAGEROUNDDOWN(stackend), PAGESIZE,
                             VM_ACCESS_READ|VM_ACCESS_WRITE);
  if ( err != VM_OK )
    PANIC("vm_create_stack: vm_pmap_region_enter failed");

  err = vm_region_map(&kernel->vm_space.vm_map,
                      &kernel->vm_space.vm_pmap, &r, (address_t)PAGEROUNDDOWN(stackend),
                      stackend, phys);
  switch ( err )
  {
  case VM_OK:
    break;
  default:
    printf("    mapping FAILED error %i\n", err);
    PANIC("km_set_heap_region: vm_region_map failed");
  }

  return stackbase;
}

void vm_pmap_enter_segmap(struct vm_segmap* segmap, struct vm_pmap* vm_pmap)
{
  address_t start;
  address_t phys;
  size_t size;
  int i;
  int end;
  int err;

  start = segmap->text;
  size = segmap->textsize;
  err = vm_pmap_region_enter(vm_pmap, DMAPPEDTOPHYS(start), start, size, VM_ACCESS_READ);
  if ( err != VM_OK )
    PANIC("vm_pmap_region_enter failed");
  if ( segmap->data + segmap->datasize == segmap->bss ||
       segmap->bss + segmap->bsssize == segmap->data )
  {
    start = segmap->data < segmap->bss?segmap->data:segmap->bss;
    size = segmap->datasize + segmap->bsssize;
    err = vm_pmap_region_enter(vm_pmap, DMAPPEDTOPHYS(start), start, size, VM_ACCESS_READ|VM_ACCESS_WRITE);
    if ( err != VM_OK )
      PANIC("vm_pmap_region_enter failed");
  }
  else
  {
    start = segmap->data;
    size = segmap->datasize;
    err = vm_pmap_region_enter(vm_pmap, DMAPPEDTOPHYS(start), start, size, VM_ACCESS_READ|VM_ACCESS_WRITE);
    if ( err != VM_OK )
      PANIC("vm_pmap_region_enter failed");
    start = segmap->bss;
    size = segmap->bsssize;
    err = vm_pmap_region_enter(vm_pmap, DMAPPEDTOPHYS(start), start, size, VM_ACCESS_READ|VM_ACCESS_WRITE);
    if ( err != VM_OK )
      PANIC("vm_pmap_region_enter failed");
  }
  start = segmap->bootstackbottom;
  size = segmap->bootstacksize;
  err = vm_pmap_region_enter(vm_pmap, DMAPPEDTOPHYS(start), start, size, VM_ACCESS_READ|VM_ACCESS_WRITE);
  if ( err != VM_OK )
    PANIC("vm_pmap_region_enter failed");

  start = segmap->optsegment;
  size = segmap->optsegmentsize;
  if ( start != NULL && size != 0 )
  {
    err = vm_pmap_region_enter(vm_pmap, DMAPPEDTOPHYS(start), start, size, VM_ACCESS_READ|VM_ACCESS_WRITE);
    if ( err != VM_OK )
      PANIC("vm_pmap_region_enter failed");
  }
  end = segmap->bootalloccnt - 1; /* last valid entry is the hint */
  for ( i = 0; i < end; i++ )
  {
    start = segmap->bootalloc[i].virt;
    size = segmap->bootalloc[i].size;
    phys = segmap->bootalloc[i].phys;
    err = vm_pmap_region_enter(vm_pmap, phys, start, size, VM_ACCESS_READ|VM_ACCESS_WRITE);
    if ( err != VM_OK )
      PANIC("vm_pmap_region_enter failed");
  }

  return;
}


/* ATTENTION: do not use this function after vm_init() or more precicely vm_page_init() */
size_t vm_allocate_bootup_memory(struct vm_segmap* segmap, address_t* address, size_t size, int type)
{
  unsigned long rounded;
  unsigned long start;
  unsigned long end;
  address_t hint;
  int num;
  int i;

  /* The segmap structure:
  	 - 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) at the moment there is space for one optional segment in 
  		 the segmap.
  	 - and other segments that were allocated via vm_allocate_bootup_memory()
  		 The last entry is a hint where to find more free pages.
   */
  ASSERT( vm_page_inited == 0 ); /* for dumb (l)users */

  rounded = (size + PAGESIZE - 1) & PAGEFRAMEMASK;
  ASSERT( rounded >= size );

  /* find a contiguous region of pages */
  /* store the addresses in the dynamic part of the segmap */
  /* return the start address of the region */
  num = PAGENUMBER(rounded);

  /* get hint, then verify with the physical map. */
  hint = segmap->bootalloc[segmap->bootalloccnt - 1].phys;
  /* now see if this still fits in the physmap */
  start = DMAPPEDTOPAGE(hint);
  end = start + num - 1; /* like an array, the last element is at size - 1 */

  for ( i = 0; i < segmap->physcnt; i++ )
  {
    if ( start <= segmap->physmap[i].end && start >= segmap->physmap[i].start )
    {
      /* hint is in this physmap, now check if the end is still in the same
      	 physmap.
       */
      if ( end <= segmap->physmap[i].end )
      {
        /* all ok, there is enough space */
        break;
      }
      /* not enough space, new start needed, then recheck */
      start = segmap->physmap[i + 1].start;
      end = start + num - 1;
    }
  }

  if ( i >= segmap->physcnt )
  {
    PANIC("No more memory left");
    *address = NULL;
    return 0;
  }

  /* Found a block with enough space.
  	 Enter it in the segmap and return the address.
   */
  hint = PAGETODMAPPED(start);
  ASSERT( hint + rounded - 1 < PAGETODMAPPED(end + 1) );

  segmap->bootalloc[segmap->bootalloccnt - 1].virt = hint;
  segmap->bootalloc[segmap->bootalloccnt - 1].phys = DMAPPEDTOPHYS(hint);
  segmap->bootalloc[segmap->bootalloccnt - 1].size = rounded;
  segmap->bootalloc[segmap->bootalloccnt - 1].type = type;
  segmap->bootalloc[segmap->bootalloccnt].virt = NULL;
  segmap->bootalloc[segmap->bootalloccnt].phys = PAGETODMAPPED(end + 1);
  segmap->bootalloc[segmap->bootalloccnt].size = 0;
  segmap->bootalloc[segmap->bootalloccnt].type = 0;
  segmap->bootalloccnt++;

  //-CJ	vm_print_segmap(segmap);
  ASSERT((address_t)&i > (address_t)&segmap->bootalloc[segmap->bootalloccnt - 1].type); /* HEAP-STACK overflow */

  // translate to an address in the Kernel Data Segment
  *address = hint;
  return rounded;
}

void vm_print_segmap(struct vm_segmap* s)
{
  int i;

  printf("vm segmap:\n - physical mappings:\n");
  for ( i = 0; i < s->physcnt; i++ )
  {
    printf("    %p (%#04lx) - %p (%#04lx) (%iMB)\n",
           PAGETODMAPPED(s->physmap[i].start), (unsigned long)s->physmap[i].start,
           PAGETODMAPPED(s->physmap[i].end), (unsigned long)s->physmap[i].end,
           (s->physmap[i].end - s->physmap[i].start) >> 8 );
  }
  printf(" - code mappings:\n");
  printf("            text:  %p - %p (%4iKB)\n", s->text, s->text + s->textsize,
         (unsigned long)s->textsize >> 10);
  printf("            data:  %p - %p (%4iKB)\n", s->data, s->data + s->datasize,
         (unsigned long)s->datasize >> 10);
  printf("             bss:  %p - %p (%4iKB)\n", s->bss , s->bss  + s->bsssize,
         (unsigned long)s->bsssize >> 10);
  printf("       bootstack:  %p - %p (%4iKB)\n",
         s->bootstackbottom , s->bootstackbottom + s->bootstacksize,
         (unsigned long)s->bootstacksize >> 10);
  printf("    opt. segment:  %p - %p (%4iKB)\n",
         s->optsegment , s->optsegment + s->optsegmentsize,
         (unsigned long)s->optsegmentsize >> 10);

  printf(" - allocated while bootup:\n");
  for ( i = 0; i < s->bootalloccnt - 1; i++ )
  {
    printf("    %p - %p (%4iKB) type %d -> %p\n",
           s->bootalloc[i].virt, s->bootalloc[i].virt + s->bootalloc[i].size,
           (unsigned long)s->bootalloc[i].size >> 10, s->bootalloc[i].type, s->bootalloc[i].phys);
  }
  printf(" - hint for alloc: %p\n", s->bootalloc[s->bootalloccnt-1].phys);

}
