/* 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 /* Include definitions of the multiboot standard */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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; struct sos_thread * cur_thr = sos_thread_get_current(); 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) || (cur_thr->fixup_uaccess.return_vaddr)) { __label__ unforce_address_space; sos_bool_t need_to_prepare_user_space_access; sos_ui32_t errcode = sos_cpu_context_get_EX_info(ctxt); /* Do we already try to access an address space ? */ need_to_prepare_user_space_access = (sos_thread_get_current()->squatted_address_space == NULL); if (need_to_prepare_user_space_access) /* No: Need to configure the interrupted thread user space */ sos_thread_prepare_user_space_access(NULL, 0); if (SOS_OK == sos_umem_vmm_try_resolve_page_fault(faulting_vaddr, errcode & (1 << 1), TRUE)) goto unforce_address_space; /* If the page fault occured in kernel mode, return to kernel to the fixup address */ if (! sos_cpu_context_is_in_user_mode(ctxt)) { cur_thr->fixup_uaccess.faulted_uaddr = faulting_vaddr; sos_cpu_context_set_EX_return_address(ctxt, cur_thr->fixup_uaccess.return_vaddr); goto unforce_address_space; } if (need_to_prepare_user_space_access) sos_thread_end_user_space_access(); sos_bochs_printf("\e[35mTHR %p: Unresolved USER page Fault at instruction 0x%x on access to address 0x%x (info=%x)!\e[m\n", (void*)sos_thread_get_current(), 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(); unforce_address_space: if (need_to_prepare_user_space_access) sos_thread_end_user_space_access(); return; } /* 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 */ } /* ====================================================================== * An operating system MUST always have a ready thread ! Otherwise: * what would the CPU have to execute ?! */ static void idle_thread(void* unused) __attribute__((noreturn)); static void idle_thread(void* unused) { 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 */ #define LOAD_DISPLAY_BASELINE 4 #define LOAD_DISPLAY_STARTROW 34 static void stat_thread(void * unused) __attribute__((noreturn)); static void stat_thread(void * unused) { 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(LOAD_DISPLAY_BASELINE+0, LOAD_DISPLAY_STARTROW, 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(LOAD_DISPLAY_BASELINE+1, LOAD_DISPLAY_STARTROW, 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(LOAD_DISPLAY_BASELINE+2, LOAD_DISPLAY_STARTROW, 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(LOAD_DISPLAY_BASELINE+3, LOAD_DISPLAY_STARTROW, SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, "Kernel CPU %% (+ Idle): %s %s %s ", str1, str5, str15); sos_restore_IRQs(flags); } } /* ====================================================================== * Start the "init" (userland) process */ static sos_ret_t start_init(struct sos_fs_manager_instance * rootfs) { sos_ret_t retval; struct sos_umem_vmm_as *as_init; struct sos_process *proc_init; struct sos_thread *new_thr; sos_uaddr_t ustack, start_uaddr; int fake_args[32]; /* Must be large enough to contain argv/envp for init */ sos_ret_t ret; struct sos_fs_opened_file * init_root, * init_cwd, * unused_of; /* Create the new process */ proc_init = sos_process_create("init", FALSE); if (! proc_init) return -SOS_ENOMEM; as_init = sos_process_get_address_space(proc_init); /* * Setup the root and CWD directories of the process. The root of * this process will correspond to the "global" root of the whole * system since all the future processes will duplicate it ! */ retval = sos_fs_new_opened_file(proc_init, rootfs->root, SOS_FS_OPEN_READ | SOS_FS_OPEN_WRITE, & init_root); if (SOS_OK != retval) { sos_process_unref(proc_init); return -SOS_ENOENT; } /* Duplicate the root file to set the current working directory of the init process */ retval = sos_fs_duplicate_opened_file(init_root, proc_init, & init_cwd); if (SOS_OK != retval) { sos_fs_close(init_root); sos_process_unref(proc_init); return -SOS_ENOENT; } /* Now update the process ! */ if ( ( SOS_OK != sos_process_chroot(proc_init, init_root, & unused_of) ) || ( SOS_OK != sos_process_chdir(proc_init, init_cwd, & unused_of) ) ) { sos_fs_close(init_root); sos_fs_close(init_cwd); sos_process_chroot(proc_init, NULL, & unused_of); sos_process_chdir(proc_init, NULL, & unused_of); sos_process_unref(proc_init); return -SOS_ENOENT; } /* Map the 'init' program in user space */ start_uaddr = sos_binfmt_elf32_map(as_init, "init"); if (0 == start_uaddr) { sos_process_unref(proc_init); return -SOS_ENOENT; } /* Allocate the user stack */ ustack = (SOS_PAGING_UPPER_USER_ADDRESS - SOS_DEFAULT_USER_STACK_SIZE) + 1; retval = sos_dev_zero_map(as_init, &ustack, SOS_DEFAULT_USER_STACK_SIZE, SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE, /* PRIVATE */ 0); if (SOS_OK != retval) { sos_process_unref(proc_init); return -SOS_ENOMEM; } /* Compute the address of the stack that will be used to initialize the user thread */ ustack = SOS_ALIGN_INF((ustack + SOS_DEFAULT_USER_STACK_SIZE - sizeof(fake_args)), 4); /* Build fake argv/envp arguments for the init process. See userland/crt.c for the format. */ fake_args[0] = 0x1; /* argc */ fake_args[1] = 4 * sizeof(fake_args[0]); /* offset of argv[0] */ fake_args[2] = 0x0; /* delimiter between argv and envp */ fake_args[3] = 0x0; /* end of envp */ strzcpy ((char *) & fake_args[4], /* argv[0] contents */ "init", 5); /* Copy the arguments to the user thread stack */ ret = sos_memcpy_to_specified_userspace (as_init, ustack, (sos_vaddr_t) fake_args, sizeof(fake_args)); if (sizeof(fake_args) != ret) { sos_bochs_printf ("sos_memcpy_to_specified_userspace() failed, returned %d\n", ret); sos_process_unref(proc_init); return ret; } /* Now create the user thread */ new_thr = sos_create_user_thread(NULL, proc_init, start_uaddr, 0, 0, ustack, SOS_SCHED_PRIO_TS_LOWEST); if (! new_thr) { sos_process_unref(proc_init); return -SOS_ENOMEM; } sos_process_unref(proc_init); return SOS_OK; } /* ====================================================================== * The C entry point of our operating system */ void sos_main(unsigned long magic, unsigned long arg) __attribute__((noreturn)); void sos_main(unsigned long magic, unsigned long arg) { sos_paddr_t sos_kernel_core_base_paddr, sos_kernel_core_top_paddr; struct sos_time tick_resolution; struct sos_fs_manager_instance * rootfs; /* Size of RAM above 1MB. Might be undefined ! */ unsigned long int upper_mem = 0; /* Setup bochs and console, and clear the console */ sos_bochs_subsystem_setup(); sos_x86_videomem_setup(); sos_x86_videomem_cls(SOS_X86_VIDEO_BG_BLUE); /* Greetings from SOS */ if (magic == MULTIBOOT_BOOTLOADER_MAGIC) { /* 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 = (multiboot_info_t *) arg; /* 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" ;) */ upper_mem = mbi->mem_upper; 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 10", ',', (unsigned)(upper_mem >> 10) + 1, (unsigned)upper_mem); } else if (magic == 0x42244224) { /* Loaded with SOS bootsect */ upper_mem = arg; sos_x86_videomem_printf(1, 0, SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, "Welcome to %s%c RAM is %dMB (upper mem = 0x%x kB)", "SOS article 10", ',', (unsigned)(upper_mem >> 10) + 1, (unsigned)upper_mem); } else /* Not loaded with grub, not from an enhanced bootsect */ sos_x86_videomem_printf(1, 0, SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, "Welcome to SOS article 10"); sos_bochs_putstring("Message in a bochs: This is SOS article 10.\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 to know the RAM size */ if (upper_mem == 0) { sos_x86_videomem_putstring(20, 0, SOS_X86_VIDEO_FG_LTRED | SOS_X86_VIDEO_BG_BLUE | SOS_X86_VIDEO_FG_BLINKING, "I don't know RAM size ! Load me 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 */ SOS_ASSERT_FATAL(SOS_OK == sos_physmem_subsystem_setup((upper_mem<<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); /* * Initialise user address space management subsystem */ sos_umem_vmm_subsystem_setup(); sos_dev_zero_subsystem_setup(); /* Initialize the page and block cache subsystems */ sos_fs_pagecache_subsystem_setup(); sos_blockdev_subsystem_setup(); sos_dev_mem_chardev_setup(); /* * 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"); SOS_ASSERT_FATAL(SOS_OK == sos_fs_virtfs_subsystem_setup()); SOS_ASSERT_FATAL(SOS_OK == sos_fs_fat_subsystem_setup()); SOS_ASSERT_FATAL(SOS_OK == sos_fs_subsystem_setup(NULL, "virtfs", NULL, & rootfs)); tty_subsystem_setup(); sos_ttyS0_subsystem_setup(); sos_console_subsystem_setup(); sos_ide_subsystem_setup(); /* Start the 'init' process, which in turns launches the other programs */ start_init(rootfs); /* * 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 !"); }