Goto sanos source index

//
// osldr.c
//
// Operating system loader
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os.h>
#include <string.h>
#include <sys/types.h>

#include <os/pdir.h>
#include <os/tss.h>
#include <os/seg.h>
#include <os/syspage.h>
#include <os/timer.h>
#include <os/fpu.h>
#include <os/object.h>
#include <os/sched.h>
#include <os/dfs.h>

void kprintf(const char *fmt,...);
void clear_screen();
void init_video();

#define VIDEO_BASE           0xB8000
#define HEAP_START           (1024 * 1024)

unsigned long mem_end;          // Size of memory
char *heap;                     // Heap pointer point to next free page in memory
pte_t *pdir;                    // Page directory
struct syspage *syspage;        // System page with tss and gdt
pte_t *syspagetable;            // System page table
struct tcb *inittcb;            // Initial thread control block for kernel
unsigned long krnlentry;        // Virtual address of kernel entry point
unsigned short tssval;          // TSS selector value
unsigned short ldtnull;         // LDT null selector
struct selector gdtsel;         // GDT selector
struct selector idtsel;         // IDT selector
int bootdrive;                  // Boot drive
int bootpart;                   // Boot partition
char *krnlopts;                 // Kernel options
char *initrd;                   // Initial RAM disk location in heap
int initrd_size;                // Initial RAM disk size (in bytes)

int unzip(void *src, unsigned long srclen, void *dst, unsigned long dstlen, char *heap, int heapsize);
void load_kernel();

void init_bootfd(int bootdrv);
void uninit_bootfd();
int bootfd_read(void *buffer, size_t count, blkno_t blkno);

void init_boothd(int bootdrv);
void uninit_boothd();
int boothd_read(void *buffer, size_t count, blkno_t blkno);

int bootrd_read(void *buffer, size_t count, blkno_t blkno)
{
  memcpy(buffer, initrd + blkno * 512, count);
  return count;
}

int boot_read(void *buffer, size_t count, blkno_t blkno)
{
  if ((bootdrive & 0xF0) == 0xF0)
    return bootrd_read(buffer, count, blkno);
  else if (bootdrive & 0x80)
    return boothd_read(buffer, count, blkno);
  else
    return bootfd_read(buffer, count, blkno);
}

void panic(char *msg)
{
  kprintf("panic: %s\n", msg);
  while (1);
}

char *check_heap(int numpages)
{
  // Check for out of memory
  if ((unsigned long) heap + numpages * PAGESIZE >= mem_end) panic("out of memory");
  return heap;
}

char *alloc_heap(int numpages)
{
  char *p = check_heap(numpages);

  // Zero allocated pages
  memset(p, 0, numpages * PAGESIZE);

  // Update heap pointer
  heap += numpages * PAGESIZE;

  return p;
}

unsigned long memsize()
{
  volatile unsigned long *mem;
  unsigned long addr;
  unsigned long value;
  unsigned long cr0save;
  unsigned long cr0new;

  // Start at 1MB
  addr = 1024 * 1024;

  // Save a copy of CR0
  __asm { mov eax, cr0 };
  __asm { mov [cr0save], eax };

  // Invalidate the cache (write-back and invalidate the cache)
  __asm { wbinvd };

  // Plug cr0 with just PE/CD/NW (cache disable(486+), no-writeback(486+), 32bit mode(386+))
  cr0new = cr0save | 0x00000001 | 0x40000000 | 0x20000000;
  __asm { mov eax, [cr0new] };
  __asm { mov cr0, eax };

  // Probe for each megabyte
  while (addr < 0xFFF0000000000)
  {
    addr += 1024 * 1024;
    mem= (unsigned long *) addr;

    value = *mem;
    *mem = 0x55AA55AA;

    if (*mem != 0x55AA55AA) break;

    *mem = 0xAA55AA55;
    if(*mem != 0xAA55AA55) break;

    *mem = value;
  }

  // Restore 
  __asm { mov eax, [cr0save] };
  __asm { mov cr0, eax };

  return addr;
}

void setup_descriptors()
{
  struct syspage *syspage;
  struct tss *tss;

  // Get syspage virtual address
  syspage = (struct syspage *) SYSPAGE_ADDRESS;

  // Initialize tss
  tss = &syspage->tss;
  tss->cr3 = (unsigned long) pdir;
  tss->eip = krnlentry;
  tss->cs = SEL_KTEXT;
  tss->ss0 =  SEL_KDATA;
  tss->ds =  SEL_KDATA;
  tss->es =  SEL_KDATA;
  tss->ss = SEL_KDATA;
  tss->esp = INITTCB_ADDRESS + TCBESP;
  tss->esp0 = INITTCB_ADDRESS + TCBESP;

  // Setup kernel text segment (4GB read and execute, ring 0)
  seginit(&syspage->gdt[GDT_KTEXT], 0, 0x100000, D_CODE | D_DPL0 | D_READ | D_PRESENT, D_BIG | D_BIG_LIM);

  // Setup kernel data segment (4GB read and write, ring 0)
  seginit(&syspage->gdt[GDT_KDATA], 0, 0x100000, D_DATA | D_DPL0 | D_WRITE | D_PRESENT, D_BIG | D_BIG_LIM);

  // Setup user text segment (2GB read and execute, ring 3)
  seginit(&syspage->gdt[GDT_UTEXT], 0, BTOP(OSBASE), D_CODE | D_DPL3 | D_READ | D_PRESENT, D_BIG | D_BIG_LIM);

  // Setup user data segment (2GB read and write, ring 3)
  seginit(&syspage->gdt[GDT_UDATA], 0, BTOP(OSBASE), D_DATA | D_DPL3 | D_WRITE | D_PRESENT, D_BIG | D_BIG_LIM);

  // Setup TSS segment
  seginit(&syspage->gdt[GDT_TSS], (unsigned long) &syspage->tss, sizeof(struct tss), D_TSS | D_DPL0 | D_PRESENT, 0);

  // Setup TIB segment
  seginit(&syspage->gdt[GDT_TIB], 0, PAGESIZE, D_DATA | D_DPL3 | D_WRITE | D_PRESENT, 0);

  // Set GDT to the new segment descriptors
  gdtsel.limit = (sizeof(struct segment) * MAXGDT) - 1;
  gdtsel.dt = syspage->gdt;
  __asm { lgdt gdtsel }

  // Make all LDT selectors invalid
  ldtnull = 0;
  __asm { lldt [ldtnull] };

  // Set IDT to IDT in syspage
  idtsel.limit = (sizeof(struct gate) * MAXIDT) - 1;
  idtsel.dt = syspage->idt;
  __asm { lidt idtsel }

  // Load task register with new TSS segment
  tssval = SEL_TSS;
  __asm { ltr tssval };
}

void copy_ramdisk(char *bootimg)
{
  struct superblock *super = (struct superblock *) (bootimg + 512);
  int i;
  int rdpages;
    
  // Copy ram disk to heap
  if (!bootimg) panic("no boot image");
  if (super->signature != DFS_SIGNATURE) panic("invalid DFS signature on initial RAM disk");
  
  initrd_size = (1 << super->log_block_size) * super->block_count;
  rdpages = PAGES(initrd_size);
  initrd = alloc_heap(rdpages);

  if (super->compress_size != 0)
  {
    char *zheap;
    unsigned int zofs;
    unsigned int zsize;

    kprintf("Uncompressing boot image\n");

    // Copy uncompressed part of image
    memcpy(initrd, bootimg, super->compress_offset);

    // Uncompress compressed part of image
    zheap = check_heap(64 * 1024 / PAGESIZE);
    zofs = super->compress_offset;
    zsize = super->compress_size;
    unzip(bootimg + zofs, zsize, initrd + zofs, initrd_size - zofs, zheap, 64 * 1024);
  }
  else
  {
    memcpy(initrd, bootimg, initrd_size);
  }

  // Map initial initial ram disk into syspages
  for (i = 0; i < rdpages; i++)
  {
    syspagetable[PTEIDX(INITRD_ADDRESS) + i] = ((unsigned long) initrd + i * PAGESIZE) | PT_PRESENT | PT_WRITABLE;
  }

  kprintf("%d KB boot image found\n", initrd_size / 1024);
}

void __stdcall start(void *hmod, struct bootparams *bootparams, int reserved)
{
  pte_t *pt;
  int i;
  char *bootimg = bootparams->bootimg;

  // Initialize video 
  init_video();
  //clear_screen();
  //kprintf("OSLDR\n");

  // Determine size of RAM
  mem_end = memsize();
  //kprintf("%d MB RAM\n", mem_end / (1024 * 1024));

  // Page allocation starts at 1MB
  heap = (char *) HEAP_START;

  // Allocate page for page directory
  pdir = (pte_t *) alloc_heap(1);

  // Make recursive entry for access to page tables
  pdir[PDEIDX(PTBASE)] = (unsigned long) pdir | PT_PRESENT | PT_WRITABLE;

  // Allocate system page
  syspage = (struct syspage *) alloc_heap(1);

  // Allocate initial thread control block
  inittcb = (struct tcb *) alloc_heap(PAGES_PER_TCB);

  // Allocate system page directory page
  syspagetable = (pte_t *) alloc_heap(1);

  // Map system page, page directory and and video buffer
  pdir[PDEIDX(SYSBASE)] = (unsigned long) syspagetable | PT_PRESENT | PT_WRITABLE;
  syspagetable[PTEIDX(SYSPAGE_ADDRESS)] = (unsigned long) syspage | PT_PRESENT | PT_WRITABLE;
  syspagetable[PTEIDX(PAGEDIR_ADDRESS)] = (unsigned long) pdir | PT_PRESENT | PT_WRITABLE;
  syspagetable[PTEIDX(VIDBASE_ADDRESS)] = VIDEO_BASE | PT_PRESENT | PT_WRITABLE;

  // Map initial TCB
  for (i = 0; i < PAGES_PER_TCB; i++)
  {
    syspagetable[PTEIDX(INITTCB_ADDRESS) + i] = ((unsigned long) inittcb + i * PAGESIZE) | PT_PRESENT | PT_WRITABLE;
  }

  // Map first 4MB to physical memory
  pt = (pte_t *) alloc_heap(1);
  pdir[0] = (unsigned long) pt | PT_PRESENT | PT_WRITABLE | PT_USER;
  for (i = 0; i < PTES_PER_PAGE; i++) pt[i] = (i * PAGESIZE) | PT_PRESENT | PT_WRITABLE;

  // Copy kernel from boot device
  bootdrive = bootparams->bootdrv;
  if ((bootdrive & 0xF0) == 0xF0)
  {
    copy_ramdisk(bootimg);
    load_kernel(bootdrive);
  }
  else if (bootdrive & 0x80)
  {
    init_boothd(bootdrive);
    load_kernel(bootdrive);
    uninit_boothd();
  }
  else
  {
    init_bootfd(bootdrive);
    load_kernel(bootdrive);
    uninit_bootfd();
  }

  // Set page directory (CR3) enable paging (PG bit in CR0)
  __asm
  {
    mov eax, pdir
    mov cr3, eax
    mov eax, cr0
    or eax, 0x80000000
    mov cr0, eax
  }

  // Setup new descriptors in syspage
  setup_descriptors();

  // Setup boot parameters
  memcpy(&syspage->bootparams, bootparams, sizeof(struct bootparams));
  syspage->ldrparams.heapstart = HEAP_START;
  syspage->ldrparams.heapend = (unsigned long) heap;
  syspage->ldrparams.memend = mem_end;
  syspage->ldrparams.bootdrv = bootdrive;
  syspage->ldrparams.bootpart = bootpart;
  syspage->ldrparams.initrd_size = initrd_size;
  memcpy(syspage->biosdata, (void *) 0x0400, 256);

  // Kernel options are located boot parameter block
  krnlopts = syspage->bootparams.krnlopts;

  // Reload segment registers
  __asm
  {
    mov ax, SEL_KDATA
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    push SEL_KTEXT
    push offset startpg
    retf
    startpg:
  }

  // Switch to inital kernel stack and jump to kernel
  __asm
  {
    mov esp, INITTCB_ADDRESS + TCBESP
    push 0
    push krnlopts
    push OSBASE
    call [krnlentry]
    cli
    hlt
  }
}