// two faults, first will be on higher address, not at breakpoint addr
// check for two supervisor pages contiguous

/*
 * main.c:  Main NULL kernel code
 *
 * (C) 1999 Ramon van Handel, The FreeMWare team
 *
 * HISTORY
 * Date      Author      Rev    Notes
 * 09/01/99  ramon       1.0    First release
 * 14/07/99  ramon       1.1    Adapted for FreeMWare NULL kernel
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#include <typewrappers.h>
#include "taskstate.h"
#include "gdt.h"
#include "decl.h"
#include "syscall.h"
#include "paging.h"


PUBLIC VOID task0(VOID);

UBYTE kstack[1024]; // kernel stack area
UBYTE ustack[1024]; // user stack area


struct TSS tss_old; // receive old state upon task switch
struct TSS tss_new; // state of new user task

#define Selector(sel, ti, rpl) (((sel)<<3) | ((ti)<<2) | (rpl))
#define RPL0 0
#define RPL1 1
#define RPL2 2
#define RPL3 3

extern VOID __syscall(VOID);
VOID syscall(DATA num, DATA parm1, DATA parm2);
extern VOID __page_fault(VOID);
extern VOID page_fault(UWORD32 error);
VOID kprintf(unsigned char *s, ...);
void video_out(UBYTE c);

static UWORD16 *video_ptr = (UWORD16 *) 0xb8000;

UBYTE global_byte;


PUBLIC VOID kmain(VOID)
{
  unsigned i;

  UWORD16 oldtssselector;

  // Shut off NMI, otherwise our idle HLT loop will trigger
  // a reset through the NMI logic.
  asm volatile (
    "movb $0x80, %%al \n"
    "outb %%al, $0x70 \n"
    "inb  $0x71, %%al \n"
    :
    :
    : "eax" /* gets clobbered */
    );

  // Create a TSS descriptor, so we can point the TR here.
  // A jump to the user task state will need to write the
  // current contents here (unused otherwise for this code)
  GDT[5].desc.base_low  = ((UWORD32)&tss_old) & 0xffff;
  GDT[5].desc.base_med  = ((UWORD32)&tss_old>>16)&0xff;
  GDT[5].desc.base_high = ((UWORD32)&tss_old>>24)&0xff;

  // Create a TSS descriptor for the user task.
  // This is how we will start the user task.
  GDT[6].desc.base_low  = ((UWORD32)&tss_new) & 0xffff;
  GDT[6].desc.base_med  = ((UWORD32)&tss_new>>16)&0xff;
  GDT[6].desc.base_high = ((UWORD32)&tss_new>>24)&0xff;

  // Fill in the user TSS
  tss_new.back = 0;
  tss_new.esp0 = ((UWORD32) kstack) + sizeof(kstack); // grows downward
  tss_new.ss0  = Selector(2, 0, RPL0);
  tss_new.cr3 = (UWORD32) &pageDirectory[0]; // must be page aligned
  tss_new.eip = (UWORD32) codePage;
  tss_new.eflags = 0x02; // all cleared
  tss_new.eax = 0;
  tss_new.ecx = 0;
  tss_new.edx = 0;
  tss_new.ebx = 0;
  tss_new.esp = ((UWORD32) ustack) + sizeof(ustack); // grows downward
  tss_new.ebp = 0;
  tss_new.esi = 0;
  tss_new.edi = 0;
  tss_new.es = Selector(4, 0, RPL3);
  tss_new.cs = Selector(3, 0, RPL3);
  tss_new.ss = Selector(4, 0, RPL3);
  tss_new.ds = Selector(4, 0, RPL3);
  tss_new.fs = Selector(4, 0, RPL3);
  tss_new.gs = Selector(4, 0, RPL3);
  tss_new.ldt = 0; // NULL selector
  tss_new.trap = 0;
  tss_new.io = 0;

  /* Initialise the interrupts */
  initIntr();

  // page fault handler
  setVector(__page_fault, 14, (D_INT + D_PRESENT));

  /* Set up the system call ISR as a trap gate at int 0x80 */
  setVector(__syscall, 0x80, (D_TRAP + D_PRESENT + D_DPL3));

  /* Clear the screen, so we don't get it all garbled */
  for(i=0; i<80*25; i++)
    ((UWORD16 *)(0xb8000))[i] = 0;

  // Load the TR with old TSS address
  oldtssselector = Selector(5,0,0);
  asm( "ltr %0" : : "r" (oldtssselector) );

  // Set up paging system, identity mapped

  // move user task0 code into it's own page
  for (i=0; i<4096; i++) {
    codePage[i] = ((UBYTE *) task0)[i];
    }

  // Page Directory.  Note that we only need 1 page directory entry,
  // and one set of page tables to cover the 1st 4Meg.  Good enough...
  pageDirectory[0].base = ((UWORD32)&pageTable) >> 12;
  pageDirectory[0].avail = 0;
  pageDirectory[0].G = 0; // not global
  pageDirectory[0].PS = 0; // 4K pages
  pageDirectory[0].D = 0; // (unused in pde)
  pageDirectory[0].A = 0; // not accessed
  pageDirectory[0].PCD = 0; // normal caching
  pageDirectory[0].PWT = 0; // normal write-back
  pageDirectory[0].US = 1; // user can access
  pageDirectory[0].RW = 1; // read or write
  pageDirectory[0].P = 1; // in memory

  // Page Table
  for (i=0; i<1024; i++) {
    pageTable[i].base = i;
    pageTable[i].avail = 0;
    pageTable[i].G = 0; // not global
    pageTable[i].PS = 0; // (unused in pte)
    pageTable[i].D = 0; // clean
    pageTable[i].A = 0; // not accessed
    pageTable[i].PCD = 0; // normal caching
    pageTable[i].PWT = 0; // normal write-back
    pageTable[i].US = 1; // user can access
    pageTable[i].RW = 1; // read or write
    pageTable[i].P = 1; // in memory
    }

  kprintf("enabling paging...\n");

  // kprintf("PD0 is %x\n", *(UWORD32 *) &pageDirectory[0]);
  // kprintf("PT0E0 is %x\n", *(UWORD32 *) &pageTable[0]);
  // kprintf("PT0E1 is %x\n", *(UWORD32 *) &pageTable[1]);

  // Set CR3 to the page directory,
  // flip the paging bit on in CR0,
  // and far jump to flush the CPU queues.
  asm volatile (
    "  mov  %%eax, %%cr3 \n"
    "  mov  %%cr0, %%eax \n"
    "  or   $0x80000000, %%eax \n"
    "  mov  %%eax, %%cr0 \n"
    "  ljmp $0x08, $enter_paging \n"
    "enter_paging: \n"
       : /* no outputs */
       : "a" ((UWORD32) &pageDirectory[0])
       : "eax", "memory" ); // clobbers EAX, memory

  kprintf("paging enable successful\n");

  // kick off the user task, jump to TSS selector
  asm volatile ( "ljmp $0x30, $0x00000000" );

  // should never get here
  asm( "hlt" );
}

asm (
    ".text; .globl __syscall   \n"  /* Assembly stub for the systemcall */
    "__syscall:                \n"
    "    pushw %ds             \n"  /* Save user data segment           */
    "    pushw %ss             \n"  /* Load kernel data segment from    */
    "    popw %ds              \n"  /* stack seg (it's always valid)    */
    "    pushal                \n"
    "    pushl %eax            \n"  /* Push the registers that are      */
    "    pushl %ecx            \n"  /* clobbered by GCC on the stack    */
    "    pushl %edx            \n"
    "    cld                   \n"  /* GCC likes this                   */
    "    call syscall          \n"  /* Go to the system call handler    */
    "    popl %edx             \n"  /* Restore the registers            */
    "    popl %ecx             \n"
    "    popl %eax             \n"
    "    popal                 \n"
    "    popw %ds              \n"  /* Restore user data segment        */
    "    iret                  \n"  /* Back to the application          */
);


VOID syscall(DATA num, DATA parm1, DATA parm2)
{
  UBYTE *code_ptr, orig_byte;

  switch(num) {
    case SYSCALL_CHECKPOINT:
      // user task is telling us it reached a certain checkpoint
      kprintf("checkpoint 0x%x reached\n", parm1);
      break;

    case SYSCALL_PAGE_PROTECT:
      // This is some hack code to see if we can get different
      // permissions into the I&D TLB caches.  The idea is to
      // get user privilege into the I TLB, and supervisor
      // privilege into the D TLB.  That way we can execute
      // modified code, without the code being able to detect
      // the change (we will get an exception if it tries).

      // The steps taken are:
      //   - put a RET instruction at the beginning of the code page
      //   - invalidate the page entry for the code page (I&D)
      //   - Call the RET instruction (just comes right back)
      //     This loades the I TLB with user level privileges
      //   - Replace the RET instruction with the original byte
      //   - Change the page table entry for the code page
      //     to supervisor privilege.
      //   - Fire up the user task, running at CPL3

      // The user task should be able to execute OK, but not
      // look at bytes in the local page.

      // create a private page mapping to get to the code page
      // without disturbing the usual one.  Use linear addr 0
      // to map to it.
      pageTable[((UWORD32) privatePage)>>12].base =
        pageTable[((UWORD32) codePage)>>12].base;
      // dump the old mapping

      asm volatile ("invlpg privatePage\n" : : : "memory" );

      code_ptr = (UBYTE *) privatePage;
      orig_byte = *code_ptr;
      *code_ptr = 0xc3;

      asm volatile (
        "invlpg codePage \n"
        "call   codePage \n"
        : : : "memory"
        );
      // change to supervisor, hopefully code TLB still loaded
      // with user level access
      pageTable[((UWORD32) codePage)>>12].US = 0; // supervisor
      pageTable[((UWORD32) codePage)>>12].RW = 1; // R/W
      pageTable[((UWORD32) codePage)>>12].P = 1; // present
      *code_ptr = orig_byte;
      break;

    case SYSCALL_EXIT:
      kprintf("exit called\n");
      asm( "hlt" );
      break;

    default:
      kprintf("unknown system call %x\n");
      break;
    };
}


asm (
    ".text; .globl __page_fault  \n"
    "__page_fault:               \n"
    "    pushw %ds               \n"   /* Save user data segment        */
    "    pushw %ss               \n"   /* Load kernel data segment from */
    "    popw %ds                \n"   /* stack seg (it's always valid) */
    "    pushl %eax              \n"   /* Push the user registers onto  */
    "    pushl %ecx              \n"   /* the stack                     */
    "    pushl %edx              \n"
    "    pushl %ebp              \n"
    "    pushl %edi              \n"
    "    pushl %esi              \n"
    "    pushl %ebx              \n"

    "    mov  30(%esp), %eax \n"  // get error code
    "    pushl %eax       \n"  // push it
    "    cld              \n"
    "    call  page_fault \n"
    "    popl  %eax       \n"

    "    popl %ebx               \n"   /* Restore the registers... off  */
    "    popl %esi               \n"   /* the new stack                 */
    "    popl %edi               \n"
    "    popl %ebp               \n"
    "    popl %edx               \n"
    "    popl %ecx               \n"
    "    popl %eax               \n"
    "    popw %ds                \n"   /* Restore user data segment     */
    "    add  $4, %esp           \n"   // pop error
    "    iret                    \n"   /* Back to the application       */
);


void page_fault(UWORD32 error)
{
  UWORD32 cr2;

  // Simple page fault exception handler.  I just fix the permissions
  // of the code page back to user, r/w, and present.

  asm volatile ("movl %%cr2,%0" : "=r" (cr2));
  kprintf("Page Fault: error=0x%x\n", error);
  kprintf("Page Fault:   cr2=0x%x\n", cr2);

  // Fix the permissions so the user task can again access
  // it's code page with reads and writes.
  pageTable[((UWORD32) codePage)>>12].US = 1;
  pageTable[((UWORD32) codePage)>>12].RW = 1;
  pageTable[((UWORD32) codePage)>>12].P = 1;

  asm volatile ("invlpg codePage\n" : : : "memory" );
}


//
// I ripped this off from bochs' BIOS.  Needed something quick.
// It's not too spectacular.  (KPL)
//


VOID kprintf(unsigned char *s, ...)
{
  char c;
  unsigned in_format;
  unsigned format_width, i;
  UWORD32  *arg_ptr;
  unsigned arg, digit, nibble;

  arg_ptr = (UWORD32 *) &s;

  in_format = 0;
  format_width = 0;

  while ((c = *s++)) {
    if ( c == '\n' ) {
      unsigned y;
      // increment to the next line
      y = (((UWORD32)video_ptr) - 0xb8000) / 160;
      y++;
      video_ptr = (UWORD16 *) (0xb8000 + (y*160));
      }
    else if ( c == '%' ) {
      in_format = 1;
      format_width = 0;
      }
    else if (in_format) {
      if ( (c>='0') && (c<='9') ) {
        format_width = (format_width * 10) + (c - '0');
        }
      else if (c == 'x') {
        arg_ptr++; // increment to next arg
        arg = *arg_ptr;
        if (format_width == 0)
          format_width = 8;
        i = 0;
        digit = format_width - 1;
        for (i=0; i<format_width; i++) {
          nibble = (arg >> (4 * digit)) & 0x000f;
          if (nibble <= 9)
            *video_ptr++ = 0x0700 | (nibble + '0');
          else
            *video_ptr++ = 0x0700 | ((nibble-10) + 'A');
          digit--;
          }
        in_format = 0;
        }
      }
    else {
      *video_ptr++ = (0x0700 | c);
      }
    }
}
