515 lines
16 KiB
C
515 lines
16 KiB
C
/* Copyright (C) 2004 The SOS Team
|
||
|
||
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
||
USA.
|
||
*/
|
||
|
||
/* Include definitions of the multiboot standard */
|
||
#include <bootstrap/multiboot.h>
|
||
#include <hwcore/idt.h>
|
||
#include <hwcore/gdt.h>
|
||
#include <hwcore/irq.h>
|
||
#include <hwcore/exception.h>
|
||
#include <hwcore/i8254.h>
|
||
#include <sos/list.h>
|
||
#include <sos/physmem.h>
|
||
#include <hwcore/paging.h>
|
||
#include <hwcore/mm_context.h>
|
||
#include <hwcore/swintr.h>
|
||
#include <sos/kmem_vmm.h>
|
||
#include <sos/kmalloc.h>
|
||
#include <sos/time.h>
|
||
#include <sos/thread.h>
|
||
#include <sos/process.h>
|
||
#include <sos/klibc.h>
|
||
#include <sos/assert.h>
|
||
#include <drivers/x86_videomem.h>
|
||
#include <drivers/bochs.h>
|
||
#include <sos/calcload.h>
|
||
|
||
|
||
/* Helper function to display each bits of a 32bits integer on the
|
||
screen as dark or light carrets */
|
||
void display_bits(unsigned char row, unsigned char col,
|
||
unsigned char attribute,
|
||
sos_ui32_t integer)
|
||
{
|
||
int i;
|
||
/* Scan each bit of the integer, MSb first */
|
||
for (i = 31 ; i >= 0 ; i--)
|
||
{
|
||
/* Test if bit i of 'integer' is set */
|
||
int bit_i = (integer & (1 << i));
|
||
/* Ascii 219 => dark carret, Ascii 177 => light carret */
|
||
unsigned char ascii_code = bit_i?219:177;
|
||
sos_x86_videomem_putchar(row, col++,
|
||
attribute,
|
||
ascii_code);
|
||
}
|
||
}
|
||
|
||
|
||
/* Clock IRQ handler */
|
||
static void clk_it(int intid)
|
||
{
|
||
static sos_ui32_t clock_count = 0;
|
||
|
||
display_bits(0, 48,
|
||
SOS_X86_VIDEO_FG_LTGREEN | SOS_X86_VIDEO_BG_BLUE,
|
||
clock_count);
|
||
clock_count++;
|
||
|
||
/* Execute the expired timeout actions (if any) */
|
||
sos_time_do_tick();
|
||
|
||
/* Update scheduler statistics and status */
|
||
sos_sched_do_timer_tick();
|
||
}
|
||
|
||
|
||
/* ======================================================================
|
||
* Page fault exception handling
|
||
*/
|
||
|
||
|
||
/* Page fault exception handler with demand paging for the kernel */
|
||
static void pgflt_ex(int intid, struct sos_cpu_state *ctxt)
|
||
{
|
||
static sos_ui32_t demand_paging_count = 0;
|
||
sos_vaddr_t faulting_vaddr = sos_cpu_context_get_EX_faulting_vaddr(ctxt);
|
||
sos_paddr_t ppage_paddr;
|
||
|
||
if (sos_cpu_context_is_in_user_mode(ctxt))
|
||
{
|
||
/* User-mode page faults are considered unresolved for the
|
||
moment */
|
||
sos_bochs_printf("Unresolved USER page Fault at instruction 0x%x on access to address 0x%x (info=%x)!\n",
|
||
sos_cpu_context_get_PC(ctxt),
|
||
(unsigned)faulting_vaddr,
|
||
(unsigned)sos_cpu_context_get_EX_info(ctxt));
|
||
sos_bochs_printf("Terminating User thread\n");
|
||
sos_thread_exit();
|
||
}
|
||
|
||
/* Check if address is covered by any VMM range */
|
||
if (! sos_kmem_vmm_is_valid_vaddr(faulting_vaddr))
|
||
{
|
||
/* No: The page fault is out of any kernel virtual region. For
|
||
the moment, we don't handle this. */
|
||
sos_display_fatal_error("Unresolved page Fault at instruction 0x%x on access to address 0x%x (info=%x)!",
|
||
sos_cpu_context_get_PC(ctxt),
|
||
(unsigned)faulting_vaddr,
|
||
(unsigned)sos_cpu_context_get_EX_info(ctxt));
|
||
SOS_ASSERT_FATAL(! "Got page fault (note: demand paging is disabled)");
|
||
}
|
||
|
||
|
||
/*
|
||
* Demand paging in kernel space
|
||
*/
|
||
|
||
/* Update the number of demand paging requests handled */
|
||
demand_paging_count ++;
|
||
display_bits(0, 0,
|
||
SOS_X86_VIDEO_FG_LTRED | SOS_X86_VIDEO_BG_BLUE,
|
||
demand_paging_count);
|
||
|
||
/* Allocate a new page for the virtual address */
|
||
ppage_paddr = sos_physmem_ref_physpage_new(FALSE);
|
||
if (! ppage_paddr)
|
||
SOS_ASSERT_FATAL(! "TODO: implement swap. (Out of mem in demand paging because no swap for kernel yet !)");
|
||
SOS_ASSERT_FATAL(SOS_OK == sos_paging_map(ppage_paddr,
|
||
SOS_PAGE_ALIGN_INF(faulting_vaddr),
|
||
FALSE,
|
||
SOS_VM_MAP_PROT_READ
|
||
| SOS_VM_MAP_PROT_WRITE
|
||
| SOS_VM_MAP_ATOMIC));
|
||
sos_physmem_unref_physpage(ppage_paddr);
|
||
|
||
/* Ok, we can now return to interrupted context */
|
||
}
|
||
|
||
|
||
/* ======================================================================
|
||
* Demonstrate the use of SOS kernel threads
|
||
* - Kernel Threads are created with various priorities and their
|
||
* state is printed on both the console and the bochs' 0xe9 port
|
||
* - For tests regarding threads' synchronization, see mouse_sim.c
|
||
*/
|
||
|
||
struct thr_arg
|
||
{
|
||
char character;
|
||
int color;
|
||
|
||
int col;
|
||
int row;
|
||
};
|
||
|
||
|
||
static void demo_thread(void *arg)
|
||
{
|
||
struct thr_arg *thr_arg = (struct thr_arg*)arg;
|
||
int progress = 0;
|
||
|
||
sos_bochs_printf("start %c", thr_arg->character);
|
||
while (1)
|
||
{
|
||
progress ++;
|
||
display_bits(thr_arg->row, thr_arg->col+1, thr_arg->color, progress);
|
||
|
||
sos_bochs_putchar(thr_arg->character);
|
||
|
||
/* Yield the CPU to another thread sometimes... */
|
||
if ((random() % 100) == 0)
|
||
{
|
||
sos_bochs_printf("[37myield(%c)[m\n", thr_arg->character);
|
||
sos_x86_videomem_putchar(thr_arg->row, thr_arg->col, 0x1e, 'Y');
|
||
SOS_ASSERT_FATAL(SOS_OK == sos_thread_yield());
|
||
sos_x86_videomem_putchar(thr_arg->row, thr_arg->col, 0x1e, 'R');
|
||
}
|
||
|
||
/* Go to sleep some other times... */
|
||
else if ((random() % 200) == 0)
|
||
{
|
||
struct sos_time t = (struct sos_time){ .sec=0, .nanosec=50000000 };
|
||
sos_bochs_printf("[37msleep1(%c)[m\n", thr_arg->character);
|
||
sos_x86_videomem_putchar(thr_arg->row, thr_arg->col, 0x1e, 's');
|
||
SOS_ASSERT_FATAL(SOS_OK == sos_thread_sleep(& t));
|
||
SOS_ASSERT_FATAL(sos_time_is_zero(& t));
|
||
sos_x86_videomem_putchar(thr_arg->row, thr_arg->col, 0x1e, 'R');
|
||
}
|
||
|
||
/* Go to sleep for a longer time some other times... */
|
||
else if ((random() % 300) == 0)
|
||
{
|
||
struct sos_time t = (struct sos_time){ .sec=0, .nanosec=300000000 };
|
||
sos_bochs_printf("[37msleep2(%c)[m\n", thr_arg->character);
|
||
sos_x86_videomem_putchar(thr_arg->row, thr_arg->col, 0x1e, 'S');
|
||
SOS_ASSERT_FATAL(SOS_OK == sos_thread_sleep(& t));
|
||
SOS_ASSERT_FATAL(sos_time_is_zero(& t));
|
||
sos_x86_videomem_putchar(thr_arg->row, thr_arg->col, 0x1e, 'R');
|
||
}
|
||
|
||
/* Infinite loop otherwise */
|
||
}
|
||
}
|
||
|
||
|
||
static void test_thread()
|
||
{
|
||
/* "static" variables because we want them to remain even when the
|
||
function returns */
|
||
static struct thr_arg arg_b, arg_c, arg_d, arg_e, arg_R, arg_S;
|
||
sos_ui32_t flags;
|
||
|
||
sos_disable_IRQs(flags);
|
||
|
||
arg_b = (struct thr_arg) { .character='b', .col=0, .row=21, .color=0x14 };
|
||
sos_create_kernel_thread("YO[b]", demo_thread, (void*)&arg_b, SOS_SCHED_PRIO_TS_LOWEST);
|
||
|
||
arg_c = (struct thr_arg) { .character='c', .col=46, .row=21, .color=0x14 };
|
||
sos_create_kernel_thread("YO[c]", demo_thread, (void*)&arg_c, SOS_SCHED_PRIO_TS_LOWEST);
|
||
|
||
arg_d = (struct thr_arg) { .character='d', .col=0, .row=20, .color=0x14 };
|
||
sos_create_kernel_thread("YO[d]", demo_thread, (void*)&arg_d, SOS_SCHED_PRIO_TS_LOWEST-1);
|
||
|
||
arg_e = (struct thr_arg) { .character='e', .col=0, .row=19, .color=0x14 };
|
||
sos_create_kernel_thread("YO[e]", demo_thread, (void*)&arg_e, SOS_SCHED_PRIO_TS_LOWEST-2);
|
||
|
||
arg_R = (struct thr_arg) { .character='R', .col=0, .row=17, .color=0x1c };
|
||
sos_create_kernel_thread("YO[R]", demo_thread, (void*)&arg_R, SOS_SCHED_PRIO_RT_LOWEST);
|
||
|
||
arg_S = (struct thr_arg) { .character='S', .col=0, .row=16, .color=0x1c };
|
||
sos_create_kernel_thread("YO[S]", demo_thread, (void*)&arg_S, SOS_SCHED_PRIO_RT_LOWEST-1);
|
||
|
||
sos_restore_IRQs(flags);
|
||
}
|
||
|
||
|
||
/* ======================================================================
|
||
* An operating system MUST always have a ready thread ! Otherwise:
|
||
* what would the CPU have to execute ?!
|
||
*/
|
||
static void idle_thread()
|
||
{
|
||
sos_ui32_t idle_twiddle = 0;
|
||
|
||
while (1)
|
||
{
|
||
/* Remove this instruction if you get an "Invalid opcode" CPU
|
||
exception (old 80386 CPU) */
|
||
asm("hlt\n");
|
||
|
||
idle_twiddle ++;
|
||
display_bits(0, 0, SOS_X86_VIDEO_FG_GREEN | SOS_X86_VIDEO_BG_BLUE,
|
||
idle_twiddle);
|
||
|
||
/* Lend the CPU to some other thread */
|
||
sos_thread_yield();
|
||
}
|
||
}
|
||
|
||
|
||
/* ======================================================================
|
||
* Kernel thread showing some CPU usage statistics on the console every 1s
|
||
*/
|
||
static void stat_thread()
|
||
{
|
||
while (1)
|
||
{
|
||
sos_ui32_t flags;
|
||
sos_ui32_t load1, load5, load15;
|
||
char str1[11], str5[11], str15[11];
|
||
struct sos_time t;
|
||
t.sec = 1;
|
||
t.nanosec = 0;
|
||
|
||
sos_thread_sleep(& t);
|
||
|
||
sos_disable_IRQs(flags);
|
||
|
||
/* The IDLE task is EXcluded in the following computation */
|
||
sos_load_get_sload(&load1, &load5, &load15);
|
||
sos_load_to_string(str1, load1);
|
||
sos_load_to_string(str5, load5);
|
||
sos_load_to_string(str15, load15);
|
||
sos_x86_videomem_printf(16, 34,
|
||
SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE,
|
||
"Kernel (- Idle): %s %s %s ",
|
||
str1, str5, str15);
|
||
|
||
sos_load_get_uload(&load1, &load5, &load15);
|
||
sos_load_to_string(str1, load1);
|
||
sos_load_to_string(str5, load5);
|
||
sos_load_to_string(str15, load15);
|
||
sos_x86_videomem_printf(17, 34,
|
||
SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE,
|
||
"User: %s %s %s ",
|
||
str1, str5, str15);
|
||
|
||
sos_load_get_uratio(&load1, &load5, &load15);
|
||
sos_load_to_string(str1, load1);
|
||
sos_load_to_string(str5, load5);
|
||
sos_load_to_string(str15, load15);
|
||
sos_x86_videomem_printf(18, 34,
|
||
SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE,
|
||
"User CPU %%: %s %s %s ",
|
||
str1, str5, str15);
|
||
|
||
/* The IDLE task is INcluded in the following computation */
|
||
sos_load_get_sratio(&load1, &load5, &load15);
|
||
sos_load_to_string(str1, load1);
|
||
sos_load_to_string(str5, load5);
|
||
sos_load_to_string(str15, load15);
|
||
sos_x86_videomem_printf(19, 34,
|
||
SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE,
|
||
"Kernel CPU %% (+ Idle): %s %s %s ",
|
||
str1, str5, str15);
|
||
sos_restore_IRQs(flags);
|
||
}
|
||
}
|
||
|
||
|
||
/* ======================================================================
|
||
* The C entry point of our operating system
|
||
*/
|
||
void sos_main(unsigned long magic, unsigned long addr)
|
||
{
|
||
unsigned i;
|
||
sos_paddr_t sos_kernel_core_base_paddr, sos_kernel_core_top_paddr;
|
||
struct sos_time tick_resolution;
|
||
|
||
/* Grub sends us a structure, called multiboot_info_t with a lot of
|
||
precious informations about the system, see the multiboot
|
||
documentation for more information. */
|
||
multiboot_info_t *mbi;
|
||
mbi = (multiboot_info_t *) addr;
|
||
|
||
/* Setup bochs and console, and clear the console */
|
||
sos_bochs_setup();
|
||
|
||
sos_x86_videomem_setup();
|
||
sos_x86_videomem_cls(SOS_X86_VIDEO_BG_BLUE);
|
||
|
||
/* Greetings from SOS */
|
||
if (magic == MULTIBOOT_BOOTLOADER_MAGIC)
|
||
/* Loaded with Grub */
|
||
sos_x86_videomem_printf(1, 0,
|
||
SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE,
|
||
"Welcome From GRUB to %s%c RAM is %dMB (upper mem = 0x%x kB)",
|
||
"SOS article 7", ',',
|
||
(unsigned)(mbi->mem_upper >> 10) + 1,
|
||
(unsigned)mbi->mem_upper);
|
||
else
|
||
/* Not loaded with grub */
|
||
sos_x86_videomem_printf(1, 0,
|
||
SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE,
|
||
"Welcome to SOS article 7");
|
||
|
||
sos_bochs_putstring("Message in a bochs: This is SOS article 7.\n");
|
||
|
||
/* Setup CPU segmentation and IRQ subsystem */
|
||
sos_gdt_subsystem_setup();
|
||
sos_idt_subsystem_setup();
|
||
|
||
/* Setup SOS IRQs and exceptions subsystem */
|
||
sos_exception_subsystem_setup();
|
||
sos_irq_subsystem_setup();
|
||
|
||
/* Configure the timer so as to raise the IRQ0 at a 100Hz rate */
|
||
sos_i8254_set_frequency(100);
|
||
|
||
/* Setup the kernel time subsystem to get prepared to take the timer
|
||
ticks into account */
|
||
tick_resolution = (struct sos_time) { .sec=0, .nanosec=10000000UL };
|
||
sos_time_subsysem_setup(& tick_resolution);
|
||
|
||
/* We need a multiboot-compliant boot loader to get the size of the RAM */
|
||
if (magic != MULTIBOOT_BOOTLOADER_MAGIC)
|
||
{
|
||
sos_x86_videomem_putstring(20, 0,
|
||
SOS_X86_VIDEO_FG_LTRED
|
||
| SOS_X86_VIDEO_BG_BLUE
|
||
| SOS_X86_VIDEO_FG_BLINKING,
|
||
"I'm not loaded with Grub !");
|
||
/* STOP ! */
|
||
for (;;)
|
||
continue;
|
||
}
|
||
|
||
/*
|
||
* Some interrupt handlers
|
||
*/
|
||
|
||
/* Binding some HW interrupts and exceptions to software routines */
|
||
sos_irq_set_routine(SOS_IRQ_TIMER,
|
||
clk_it);
|
||
|
||
/*
|
||
* Setup physical memory management
|
||
*/
|
||
|
||
/* Multiboot says: "The value returned for upper memory is maximally
|
||
the address of the first upper memory hole minus 1 megabyte.". It
|
||
also adds: "It is not guaranteed to be this value." aka "YMMV" ;) */
|
||
sos_physmem_subsystem_setup((mbi->mem_upper<<10) + (1<<20),
|
||
& sos_kernel_core_base_paddr,
|
||
& sos_kernel_core_top_paddr);
|
||
|
||
/*
|
||
* Switch to paged-memory mode
|
||
*/
|
||
|
||
/* Disabling interrupts should seem more correct, but it's not really
|
||
necessary at this stage */
|
||
SOS_ASSERT_FATAL(SOS_OK ==
|
||
sos_paging_subsystem_setup(sos_kernel_core_base_paddr,
|
||
sos_kernel_core_top_paddr));
|
||
|
||
/* Bind the page fault exception */
|
||
sos_exception_set_routine(SOS_EXCEPT_PAGE_FAULT,
|
||
pgflt_ex);
|
||
|
||
/*
|
||
* Setup kernel virtual memory allocator
|
||
*/
|
||
|
||
if (sos_kmem_vmm_subsystem_setup(sos_kernel_core_base_paddr,
|
||
sos_kernel_core_top_paddr,
|
||
bootstrap_stack_bottom,
|
||
bootstrap_stack_bottom
|
||
+ bootstrap_stack_size))
|
||
sos_bochs_printf("Could not setup the Kernel virtual space allocator\n");
|
||
|
||
if (sos_kmalloc_subsystem_setup())
|
||
sos_bochs_printf("Could not setup the Kmalloc subsystem\n");
|
||
|
||
/*
|
||
* Initialize the MMU context subsystem
|
||
*/
|
||
sos_mm_context_subsystem_setup();
|
||
|
||
/*
|
||
* Initialize the CPU context subsystem
|
||
*/
|
||
sos_cpu_context_subsystem_setup();
|
||
|
||
/*
|
||
* Bind the syscall handler to its software interrupt handler
|
||
*/
|
||
sos_swintr_subsystem_setup();
|
||
|
||
|
||
/*
|
||
* Initialize the Kernel thread and scheduler subsystems
|
||
*/
|
||
|
||
/* Initialize kernel thread subsystem */
|
||
sos_thread_subsystem_setup(bootstrap_stack_bottom,
|
||
bootstrap_stack_size);
|
||
|
||
/* Initialize the scheduler */
|
||
sos_sched_subsystem_setup();
|
||
|
||
/* Declare the IDLE thread */
|
||
SOS_ASSERT_FATAL(sos_create_kernel_thread("idle", idle_thread, NULL,
|
||
SOS_SCHED_PRIO_TS_LOWEST) != NULL);
|
||
|
||
/* Prepare the stats subsystem */
|
||
sos_load_subsystem_setup();
|
||
|
||
/* Declare a thread that prints some stats */
|
||
SOS_ASSERT_FATAL(sos_create_kernel_thread("stat_thread", stat_thread,
|
||
NULL,
|
||
SOS_SCHED_PRIO_TS_LOWEST) != NULL);
|
||
|
||
|
||
/*
|
||
* Initialize process stuff
|
||
*/
|
||
sos_process_subsystem_setup();
|
||
|
||
|
||
/* Enabling the HW interrupts here, this will make the timer HW
|
||
interrupt call the scheduler */
|
||
asm volatile ("sti\n");
|
||
|
||
/* Run some tests involving USER processes and threads */
|
||
extern void test_art7();
|
||
test_art7();
|
||
|
||
/* Now run some Kernel threads just for fun ! */
|
||
extern void MouseSim();
|
||
MouseSim();
|
||
test_thread();
|
||
|
||
/*
|
||
* We can safely exit from this function now, for there is already
|
||
* an idle Kernel thread ready to make the CPU busy working...
|
||
*
|
||
* However, we must EXPLICITELY call sos_thread_exit() because a
|
||
* simple "return" will return nowhere ! Actually this first thread
|
||
* was initialized by the Grub bootstrap stage, at a time when the
|
||
* word "thread" did not exist. This means that the stack was not
|
||
* setup in order for a return here to call sos_thread_exit()
|
||
* automagically. Hence we must call it manually. This is the ONLY
|
||
* kernel thread where we must do this manually.
|
||
*/
|
||
sos_bochs_printf("Bye from primary thread !\n");
|
||
sos_thread_exit();
|
||
SOS_FATAL_ERROR("No trespassing !");
|
||
}
|