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 <stdarg.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>
#include <os/dev.h>
#include <os/vga.h>
#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 bootdev_cyls; // Boot device cylinders
int bootdev_heads; // Boot device heads
int bootdev_sects; // Boot device sector per track
int xres, yres, bpp; // Video resolution
char scratch[4096]; // Scratch buffer
int vsprintf(char *buf, const char *fmt, va_list args);
void bios_print_string(char *str);
int bios_get_drive_params(int drive, int *cyls, int *heads, int *sects);
int bios_read_disk(int drive, int cyl, int head, int sect, int nsect, void *buffer);
int vesa_get_info(struct vesa_info *info);
int vesa_get_mode_info(int mode, struct vesa_mode_info *info);
int vesa_set_mode(int mode);
int unzip(void *src, unsigned long srclen, void *dst, unsigned long dstlen, char *heap, int heapsize);
void load_kernel();
void kprintf(const char *fmt,...) {
va_list args;
char buffer[1024];
va_start(args, fmt);
vsprintf(buffer, fmt, args);
va_end(args);
bios_print_string(buffer);
}
void panic(char *msg) {
kprintf("panic: %s\n", msg);
while (1);
}
void init_biosdisk() {
int status;
status = bios_get_drive_params(bootdrive, &bootdev_cyls, &bootdev_heads, &bootdev_sects);
if (status != 0) panic("Unable to initialize boot device");
}
int biosdisk_read(void *buffer, size_t count, blkno_t blkno) {
char *buf = buffer;
int sects = count / SECTORSIZE;
int left = sects;
while (left > 0) {
int nsect, status;
// Compute CHS address
int sect = blkno % bootdev_sects + 1;
int track = blkno / bootdev_sects;
int head = track % bootdev_heads;
int cyl = track / bootdev_heads;
if (cyl >= bootdev_cyls) return -ERANGE;
// Compute number of sectors to read
nsect = left;
if (nsect > sizeof(scratch) / SECTORSIZE) nsect = sizeof(scratch) / SECTORSIZE;
if (nsect > 0x7f) nsect = 0x7f;
if ((sect - 1) + nsect > bootdev_sects) nsect = bootdev_sects - (sect - 1);
// Read sectors from disk into temporary buffer in low memory.
status = bios_read_disk(bootdrive, cyl, head, sect, nsect, scratch);
if (status != 0) {
kprintf("biosdisk: read error %d\n", status);
return -EIO;
}
// Move data to high memory
memcpy(buf, scratch, nsect * SECTORSIZE);
// Prepeare next read
blkno += nsect;
buf += nsect * SECTORSIZE;
left -= nsect;
}
return count;
}
int bootrd_read(void *buffer, size_t count, blkno_t blkno) {
memcpy(buffer, initrd + blkno * SECTORSIZE, 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 {
return biosdisk_read(buffer, count, blkno);
}
}
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 < 0xFFF00000) {
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_memory(struct memmap *memmap) {
int i;
if (memmap->count != 0) {
// Determine largest available RAM address from BIOS memory map
mem_end = 0;
for (i = 0; i < memmap->count; i++) {
if (memmap->entry[i].type == MEMTYPE_RAM) {
mem_end = (unsigned long) (memmap->entry[i].addr + memmap->entry[i].size);
}
}
} else {
// No BIOS memory map, probe memory and create a simple map with 640K:1M hole
mem_end = memsize();
memmap->count = 3;
memmap->entry[0].addr = 0;
memmap->entry[0].size = 0xA0000;
memmap->entry[0].type = MEMTYPE_RAM;
memmap->entry[1].addr = 0xA0000;
memmap->entry[1].size = 0x60000;
memmap->entry[1].type = MEMTYPE_RESERVED;
memmap->entry[2].addr = 0x100000;
memmap->entry[2].size = mem_end - 0x100000;
memmap->entry[2].type = MEMTYPE_RAM;
}
}
void setup_page_tables() {
pte_t *pt;
int i;
// 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;
}
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 + SECTORSIZE);
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);
}
int get_video_config(char *opts) {
int param[3];
int i;
if (!opts || strncmp(opts, "video=", 6) != 0) return 0;
opts += 6;
for (i = 0; i < 3; ++i) {
int num = 0;
while (*opts >= '0' && *opts <= '9') {
num = num * 10 + (*opts++ - '0');
}
if (num == 0) return 0;
param[i] = num;
if (*opts == 'x') opts++;
}
xres = param[0];
yres = param[1];
bpp = param[2];
return 1;
}
void setup_video_mode() {
static struct vesa_info info;
int rc;
unsigned short *modes;
unsigned short mode;
// Get requested video resolution from kernel options.
if (!get_video_config(syspage->bootparams.krnlopts)) return;
// Get VESA information
memcpy(info.vesa_signature, "VBE2", 4);
rc = vesa_get_info(&info);
if (rc != 0x004F) return;
// Find matching VESA mode.
modes = (unsigned short *) VESA_ADDR(info.video_mode_ptr);
while ((mode = *modes++) != 0xFFFF) {
struct vesa_mode_info *mi = (struct vesa_mode_info *) scratch;
rc = vesa_get_mode_info(mode, mi);
if (rc != 0x004F) continue;
// Check if this is a graphics mode with linear frame buffer support
if ((mi->attributes & 0x90) != 0x90) continue;
// Check if this is a packed pixel or direct color mode
if (mi->memory_model != 4 && mi->memory_model != 6) continue;
// Check if resoution matches
if (mi->x_resolution == xres && mi->y_resolution == yres && mi->bits_per_pixel == bpp) {
// Copy video mode information to syspage.
memcpy(syspage->vgainfo, mi, sizeof(struct vesa_mode_info));
// Switch to graphics mode.
//kprintf("Mode %d: %dx%dx%d\n", mode, mi->x_resolution, mi->y_resolution, mi->bits_per_pixel);
vesa_set_mode(mode);
}
}
}
void __stdcall start(void *hmod, struct bootparams *bootparams, int reserved) {
char *bootimg = bootparams->bootimg;
kprintf(", osldr");
// Determine size of RAM
setup_memory(&bootparams->memmap);
//kprintf("%d MB RAM\n", mem_end / (1024 * 1024));
// Page allocation starts at 1MB
heap = (char *) HEAP_START;
// Setup page tables
setup_page_tables();
// Copy kernel from boot device
bootdrive = bootparams->bootdrv;
if ((bootdrive & 0xF0) == 0xF0) {
copy_ramdisk(bootimg);
load_kernel(bootdrive);
} else {
init_biosdisk();
load_kernel(bootdrive);
}
// 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);
// Set up video mode.
setup_video_mode();
// Kernel options are located in the boot parameter block
krnlopts = syspage->bootparams.krnlopts;
// Set page directory (CR3) and enable paging (PG bit in CR0)
__asm {
mov eax, dword ptr [pdir]
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
}
// Setup new descriptors in syspage
setup_descriptors();
// 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 dword ptr [krnlopts]
push OSBASE
call dword ptr [krnlentry]
cli
hlt
}
}