From cd3d8871dfb4d4e58a0c6564708dc107075d9827 Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Thu, 3 Oct 2024 02:55:54 -0500 Subject: [PATCH] initial razzle --- .gitignore | 3 + CMakeLists.txt | 46 ++++++ LICENSE.md | 135 +++++++++++++++ README.md | 26 +++ arch/i386/asm.c | 32 ++++ arch/i386/gdt.c | 121 ++++++++++++++ arch/i386/handlers.h | 52 ++++++ arch/i386/idt.c | 86 ++++++++++ arch/i386/init.c | 11 ++ arch/i386/irq.c | 81 +++++++++ arch/i386/irq.s | 143 ++++++++++++++++ arch/i386/isr.s | 243 +++++++++++++++++++++++++++ arch/i386/loader.s | 26 +++ arch/i386/scheduler.c | 66 ++++++++ arch/i386/syscall.c | 8 + clang-format | 7 + compile_commands.json | 38 +++++ debug.sh | 8 + iso/boot/grub/menu.lst | 5 + iso/boot/grub/stage2_eltorito | Bin 0 -> 107976 bytes kernel/kernel.c | 74 +++++++++ kernel/memory.c | 24 +++ kernel/ps2.c | 302 ++++++++++++++++++++++++++++++++++ kernel/scheduler.c | 95 +++++++++++ kernel/spin_lock.c | 9 + kernel/syscall.c | 64 +++++++ kernel/test_processes.c | 96 +++++++++++ kernel/vga.c | 157 ++++++++++++++++++ linker.ld | 28 ++++ 29 files changed, 1986 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 arch/i386/asm.c create mode 100644 arch/i386/gdt.c create mode 100644 arch/i386/handlers.h create mode 100644 arch/i386/idt.c create mode 100644 arch/i386/init.c create mode 100644 arch/i386/irq.c create mode 100644 arch/i386/irq.s create mode 100644 arch/i386/isr.s create mode 100644 arch/i386/loader.s create mode 100644 arch/i386/scheduler.c create mode 100644 arch/i386/syscall.c create mode 100644 clang-format create mode 100644 compile_commands.json create mode 100755 debug.sh create mode 100644 iso/boot/grub/menu.lst create mode 100644 iso/boot/grub/stage2_eltorito create mode 100644 kernel/kernel.c create mode 100644 kernel/memory.c create mode 100644 kernel/ps2.c create mode 100644 kernel/scheduler.c create mode 100644 kernel/spin_lock.c create mode 100644 kernel/syscall.c create mode 100644 kernel/test_processes.c create mode 100644 kernel/vga.c create mode 100644 linker.ld diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06972d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache/ +build/ +iso/boot/kernel.elf diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9ac4eaa --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.10) + +project(razzle C ASM) + +# Check architecture is passed and is supported +set(ALLOWED_ARCHS "i386") +if(NOT DEFINED ARCH) + message(FATAL_ERROR "Architecture not set s not defined. Supported architectures are: ${ALLOWED_ARCHS}") +endif() +list(FIND ALLOWED_ARCHS "${ARCH}" ARCH_INDEX) +if(ARCH_INDEX EQUAL -1) + message(FATAL_ERROR "Unsupported architecture: ${ARCH}. Supported architectures are: ${ALLOWED_ARCHS}.") +endif() +message(STATUS "Building RAZZLE for ${ARCH}.") + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_FLAGS "-ffreestanding -g -Wall -Wextra -mgeneral-regs-only -DARCH_I386") +set(CMAKE_ASM_FLAGS "-g") +set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/linker.ld) + +# Common source files +FILE(GLOB SOURCES kernel/kernel.c) + +# Architecture Specific Files +if (ARCH STREQUAL "i386") + set(CMAKE_C_COMPILER i386-elf-gcc) + set(CMAKE_ASM_COMPILER i386-elf-as) + set(LD i386-elf-gcc) + FILE(GLOB ASM_SOURCES arch/i386/*.s) + SET(ARCH_FLAG "-DARCH_I386") +endif() + +add_executable(kernel.elf ${SOURCES} ${ASM_SOURCES}) +set_target_properties(kernel.elf PROPERTIES LINK_FLAGS "-T ${LINKER_SCRIPT} -ffreestanding -O2 -nostdlib -lgcc -g ${ARCH_FLAG}") +add_custom_command(OUTPUT os.iso + COMMAND cp kernel.elf ../iso/boot/kernel.elf + COMMAND genisoimage -R -b boot/grub/stage2_eltorito -no-emul-boot -boot-load-size 4 -A os -input-charset utf8 -quiet -boot-info-table -o os.iso ../iso + DEPENDS kernel.elf + COMMENT "Generating OS ISO" +) + +# Run & Debug targets +add_custom_target(run COMMAND qemu-system-i386 -boot d -cdrom os.iso -m 512 -machine type=pc-i440fx-3.1 -monitor stdio DEPENDS os.iso) +add_custom_target(debug COMMAND ./debug.sh DEPENDS os.iso) + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2a0bf23 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,135 @@ +# THE RAZZLE ULTRA-EXCLUSIVE "LOOK BUT DON'T TOUCH" PUBLIC LICENSE (Version 1.0.0) + + + +**Effective Date:** October 2nd, 2024 + +**Copyright Holder:** Nicholas Orlowsky + +**Project Name:** RAZZLE Operating System ("RAZZLE" or "the Software") + + + +**IMPORTANT LEGAL NOTICE: READ THIS LICENSE CAREFULLY BEFORE USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE. FAILURE TO COMPLY WITH THE TERMS AND CONDITIONS HEREIN WILL RESULT IN IMMEDIATE TERMINATION OF YOUR RIGHTS UNDER THIS LICENSE, AND MAY SUBJECT YOU TO LEGAL ACTION AND LIABILITY FOR DAMAGES.** + + + +1. **Definitions** + + 1.1. "License" refers to this legal agreement governing the use, modification, and distribution of the Software. + + 1.2. "Software" refers to RAZZLE, in whole or in part, including but not limited to source code, object code, executables, documentation, and any associated files or components. + + 1.3. "You" refers to any individual or entity that uses, modifies, distributes, or attempts to use, modify, or distribute the Software. + + 1.4. "Use" refers to downloading, copying, accessing, installing, executing, or otherwise interacting with the Software in any manner. + + 1.5. "ICBM" refers to an Intercontinental Ballistic Missile, a type of missile designed for long-range delivery of payloads, with a minimum range of 5,500 kilometers, utilizing a high-arcing trajectory for targeting, and encompassing all associated systems and technologies. + + 1.5. "December" refers to the twelfth month of the Gregorian calendar, consisting of 31 days, typically associated with the winter season in the Northern Hemisphere and the summer season in the Southern Hemisphere, and encompassing all cultural and holiday observances during this period. + + 1.6. "SEPTA" refers to the Southeastern Pennsylvania Transportation Authority, the regional public transportation agency providing bus, subway, and commuter rail services in the Greater Philadelphia area, encompassing all associated transit systems, operations, and facilities, including but not limited to the Market-Frankford Line, Broad Street Line, Regional Rail Lines (such as the Paoli/Thorndale Line, Trenton Line, and others), and various bus routes. + + + +2. **Restrictions on Use** + + 2.1. The Software is provided to You strictly on a "for viewing purposes only" basis. You are prohibited from using the Software in any productive, experimental, educational, personal, commercial, non-commercial, or any other environment. + + 2.2. Any attempt to modify, reverse engineer, decompile, disassemble, or tamper with the Software in any way is strictly prohibited and will result in the automatic termination of this License. + + 2.3. You may not distribute, share, sublicense, or otherwise provide the Software to any third party, whether or not in exchange for compensation, without prior written permission from the Copyright Holder. + + 2.4. The Software may not be used for any purpose, including but not limited to: + + a. Any form of computing, data processing, or algorithmic calculation; + + b. Any educational purpose, whether academic or otherwise, including but not limited to any current University of Texas computer science students viewing the code; + + c. Any experimentation, research, or study; + + d. Any personal, recreational, or hobbyist activity; + + e. Any commercial, business, or governmental application; + + f. Launching ICBM missiles with a range exceeding 7500 kilometers during the month of December; + + g. Conducting any activities that may encourage, endorse, or facilitate evil schemes or nefarious plots; + + h. For circumventing rule 3 subsection A of the Bowling Analysis & Research Tool rules; + + i. For causing delays on a SEPTA service, including but not limited to the Market-Frankford Line, Broad Street Line, or any Regional Rail Line, that exceed 100 nanoseconds in duration. + + + + + +3. **Fees and Compensation** + + 3.1. Use of the Software in any capacity is subject to a mandatory fee structure to be determined solely by the Copyright Holder, at their sole discretion, without prior notice. + + 3.2. Payment of fees does not grant any rights under this License beyond those explicitly outlined herein. Non-payment or late payment shall result in immediate termination of this License. + + + +4. **No Warranty** + + 4.1. THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT ANY WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR THAT THE SOFTWARE WILL FUNCTION AS INTENDED OR WITHOUT ERROR. + + 4.2. The Copyright Holder assumes no liability for any damages, direct or indirect, incidental or consequential, arising from the use or inability to use the Software, even if the Copyright Holder has been advised of the possibility of such damages. + + + +5. **Liability and Indemnification** + + 5.1. You assume full responsibility for any use of the Software, regardless of its legality or suitability for your intended purpose. + + 5.2. You agree to indemnify, defend, and hold harmless the Copyright Holder from any and all claims, damages, liabilities, losses, costs, or expenses (including attorneys' fees) arising from or related to your use of the Software, whether authorized or unauthorized. + + 5.3. The Copyright Holder reserves the right to seek punitive damages for any unauthorized use of the Software, in addition to any other remedies available under law. + + 5.4. The Copyright Holder is not liable if your use of the Software causes the occurrence (certified by the United States Centers for Disease Control or successor body) of a widespread viral infection transmitted via bites or contact with bodily fluids that causes human corpses to reanimate and seek to consume living human flesh, blood, brain, or nerve tissue and is likely to result in the fall of organized civilization. + + + + + +6. **Termination** + + 6.1. This License is effective until terminated. + + 6.2. Any breach of this License, including but not limited to any unauthorized use, modification, or distribution of the Software, shall result in immediate termination of your rights under this License. + + 6.3. Upon termination, you must cease all use of the Software and destroy all copies in your possession or control, including any derivatives or modifications, whether or not previously authorized. + + + +7. **Governing Law and Jurisdiction** + + 7.1. This License shall be governed by and construed in accordance with the laws of the Commonwealth of Pennsylvania, without regard to its conflict of law principles. + + 7.2. Any legal action or proceeding arising under or in connection with this License shall be brought exclusively in the courts of the Commonwealth of Pennsylvania. You hereby consent to the exclusive jurisdiction and venue of such courts. + + + +8. **Severability** + + 8.1. If any provision of this License is found to be invalid or unenforceable, the remaining provisions shall continue to be valid and enforceable in accordance with their terms. + + + +9. **Amendments** + + 9.1. The Copyright Holder reserves the right to amend, modify, or revoke this License at any time, with or without notice. Your continued use of the Software constitutes acceptance of any such amendments or modifications. + + + +--- + + + +**YOU ACKNOWLEDGE THAT YOU HAVE READ THIS LICENSE AND AGREE TO BE BOUND BY ITS TERMS AND CONDITIONS.** + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f79dd0 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# RAZZLE + +Yet another monolithic kernel + +## Roadmap + +Things that are done and will be done, roughly in the order I'd like to do them. + +- [x] i386 +- [x] Basic display with BIOS VGA +- [x] Basic input with PS/2 keyboards +- [x] System calls w/ software interrupts +- [x] Multitasking w/ round-robin scheduler +- [ ] FAT Filesystem support +- [ ] Interactive shell +- [ ] Milestone: Simplistic editing of RAZZLE source on RAZZLE +- [ ] Virtual memory +- [ ] Audio driver +- [ ] Milestone: Listen to "Hey, St. Peter" by Flash and the Pan on RAZZLE +- [ ] Network driver +- [ ] Milestone: Simple HTTP server running on RAZZLE +- [ ] Higher-resolution graphics driver +- [ ] (Maybe) Milestone: Run [Anthracite](https://github.com/nickorlow/anthracite) unmodified on Razzle +- [ ] Multicore support +- [ ] x86_64 +- [ ] aarch64 diff --git a/arch/i386/asm.c b/arch/i386/asm.c new file mode 100644 index 0000000..c8dd84e --- /dev/null +++ b/arch/i386/asm.c @@ -0,0 +1,32 @@ +#pragma once + +#include + +struct regs { + unsigned int gs, fs, es, ds; + unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax; + unsigned int int_no, err_code; + unsigned int eip, cs, eflags, useresp, ss; +}; + +void __sl_acquire(uint32_t *lock_id) { + __asm__("retry_lock: lock bts $0,(%0); pause; jc retry_lock" : "+g"(lock_id)); +} + +void __sl_release(uint32_t *lock_id) { + __asm__("lock btr $0, (%0)" : "+g"(lock_id)); +} + +static inline void outb(int port, int val) { + __asm__ volatile("outb %b0, %w1" : : "a"(val), "Nd"(port) : "memory"); +} + +static inline unsigned char inb(int port) { + unsigned char val; + __asm__ volatile("inb %w1, %b0" : "=a"(val) : "Nd"(port) : "memory"); + return val; +} + +static inline void interrupt_disable() { __asm__ volatile("cli"); } + +static inline void interrupt_enable() { __asm__ volatile("sti"); } diff --git a/arch/i386/gdt.c b/arch/i386/gdt.c new file mode 100644 index 0000000..3ce2d7e --- /dev/null +++ b/arch/i386/gdt.c @@ -0,0 +1,121 @@ +#pragma once + +#include "../../kernel/memory.c" +#include + +struct gdt_row_t { + uint16_t limit_low; + uint16_t base_low; + uint8_t base_middle; + uint8_t access; + uint8_t granularity_limit_high; + uint8_t base_high; +} __attribute__((packed)); + +struct gdt_ptr_t { + uint16_t size; + uint32_t address; +} __attribute__((packed)); + +struct tss_row_t { + uint32_t prev_tss; + uint32_t esp_ring0; + uint32_t ss_ring0; + uint32_t padding[23]; // Extra options RAZZLE doesn't use +} __attribute__((packed)); + +enum gdt_access_t { + GDT_ACCESS_PRESENT = 0b10000000, + GDT_ACCESS_DPL_KERNEL = 0b00000000, + GDT_ACCESS_DPL_USER = 0b01100000, + GDT_ACCESS_CODEDATA = 0b00010000, + GDT_ACCESS_EXECUTABLE = 0b00001000, + GDT_ACCESS_GROW_DOWN = 0b00000100, + GDT_ACCESS_RW = 0b00000010, + GDT_ACCESS_ACCESSED = 0b00000001, +}; + +enum gdt_granularity_t { + GDT_GRANULARITY_4K = 0b10001111, + GDT_GRANULARITY_MODE_32BIT = 0b01001111, +}; + +struct tss_row_t tss_entry; + +#define GDT_SIZE (6) +struct gdt_row_t gdt[GDT_SIZE]; + +void load_gdt(struct gdt_ptr_t *gp) { + asm volatile("lgdt (%0)\n\t" + "mov $0x10, %%ax\n\t" + "mov %%ax, %%ds\n\t" + "mov %%ax, %%es\n\t" + "mov %%ax, %%fs\n\t" + "mov %%ax, %%gs\n\t" + "mov %%ax, %%ss\n\t" + : + : "r"(gp) + : "%ax"); +} + +void set_gdt_row(uint32_t num, uint32_t base, uint32_t limit, uint8_t access, + uint8_t granularity) { + gdt[num].base_low = base & 0xFFFF; + gdt[num].base_middle = (base >> 16) & 0xFF; + gdt[num].base_high = (base >> 24) & 0xFF; + gdt[num].limit_low = limit & 0xFFFF; + gdt[num].granularity_limit_high = (limit >> 16) & 0x0F; + gdt[num].granularity_limit_high |= granularity & 0xF0; + gdt[num].access = access; +} + +void load_tss(uint16_t gdt_row_num) { + gdt_row_num *= 8; + __asm__ volatile("mov %0, %%ax; ltr %%ax" : : "r"(gdt_row_num) : "ax"); +} + +#define KERNEL_CODE_SEL (0x08) +#define KERNEL_DATA_SEL (0x10) + +void init_gdt() { + struct gdt_ptr_t gp; + gp.size = (sizeof(struct gdt_row_t) * GDT_SIZE) - 1; + gp.address = (uint32_t)&gdt; + + set_gdt_row(0, 0, 0, 0, 0); + + // Kernelmode code/data degments + set_gdt_row(1, 0, 0xFFFFFFFF, + GDT_ACCESS_PRESENT | GDT_ACCESS_DPL_KERNEL | GDT_ACCESS_CODEDATA | + GDT_ACCESS_EXECUTABLE | GDT_ACCESS_RW | GDT_ACCESS_ACCESSED, + GDT_GRANULARITY_4K | GDT_GRANULARITY_MODE_32BIT); + set_gdt_row(2, 0, 0xFFFFFFFF, + GDT_ACCESS_PRESENT | GDT_ACCESS_DPL_KERNEL | GDT_ACCESS_CODEDATA | + GDT_ACCESS_RW | GDT_ACCESS_ACCESSED, + GDT_GRANULARITY_4K | GDT_GRANULARITY_MODE_32BIT); + + // Usermode code/data segments + set_gdt_row(3, 0x0000000, 0xFFFFFFFF, + GDT_ACCESS_PRESENT | GDT_ACCESS_DPL_USER | GDT_ACCESS_CODEDATA | + GDT_ACCESS_EXECUTABLE | GDT_ACCESS_RW | GDT_ACCESS_ACCESSED, + GDT_GRANULARITY_4K | GDT_GRANULARITY_MODE_32BIT); + set_gdt_row(4, 0x0000000, 0xFFFFFFFF, + GDT_ACCESS_PRESENT | GDT_ACCESS_DPL_USER | GDT_ACCESS_CODEDATA | + GDT_ACCESS_RW | GDT_ACCESS_ACCESSED, + GDT_GRANULARITY_4K | GDT_GRANULARITY_MODE_32BIT); + + // TSS row + set_gdt_row(5, (uint32_t)&tss_entry, sizeof(tss_entry), + GDT_ACCESS_PRESENT | GDT_ACCESS_EXECUTABLE | GDT_ACCESS_ACCESSED, + GDT_GRANULARITY_4K | GDT_GRANULARITY_MODE_32BIT); + + // Setup TSS + int kstack = 0; + memset((char *)&tss_entry, 0, sizeof(tss_entry)); + tss_entry.ss_ring0 = 0x10; + tss_entry.esp_ring0 = (uint32_t)&kstack; + + // Load GDT and TSS + load_gdt(&gp); + load_tss(5); +} diff --git a/arch/i386/handlers.h b/arch/i386/handlers.h new file mode 100644 index 0000000..12d132d --- /dev/null +++ b/arch/i386/handlers.h @@ -0,0 +1,52 @@ +#pragma once + +extern void irq0(); +extern void irq1(); +extern void irq2(); +extern void irq3(); +extern void irq4(); +extern void irq5(); +extern void irq6(); +extern void irq7(); +extern void irq8(); +extern void irq9(); +extern void irq10(); +extern void irq11(); +extern void irq12(); +extern void irq13(); +extern void irq14(); +extern void irq15(); +extern void irq128(); + +extern void isr0(); +extern void isr1(); +extern void isr2(); +extern void isr3(); +extern void isr4(); +extern void isr5(); +extern void isr6(); +extern void isr7(); +extern void isr8(); +extern void isr9(); +extern void isr10(); +extern void isr11(); +extern void isr12(); +extern void isr13(); +extern void isr14(); +extern void isr15(); +extern void isr16(); +extern void isr17(); +extern void isr18(); +extern void isr19(); +extern void isr20(); +extern void isr21(); +extern void isr22(); +extern void isr23(); +extern void isr24(); +extern void isr25(); +extern void isr26(); +extern void isr27(); +extern void isr28(); +extern void isr29(); +extern void isr30(); +extern void isr31(); diff --git a/arch/i386/idt.c b/arch/i386/idt.c new file mode 100644 index 0000000..e35a93b --- /dev/null +++ b/arch/i386/idt.c @@ -0,0 +1,86 @@ +#pragma once + +#include "../../kernel/memory.c" +#include "./handlers.h" +#include "gdt.c" + +struct idt_row_t { + uint16_t base_lo; + uint16_t sel; + uint8_t unset; + uint8_t flags; + uint16_t base_hi; +} __attribute__((packed)); + +struct idt_ptr_t { + uint16_t size; + uint32_t address; +} __attribute__((packed)); + +// TODO: double check this? +enum idt_flag_t { + IDTFLAG_PRESENT = 0b10000000, + IDTFLAG_DPL_KERNEL = 0b00000000, + IDTFLAG_DPL_USER = 0b01100000, + IDTFLAG_TRAP = 0b00001111, + IDTFLAG_INTERRUPT = 0b00001110, +}; + +#define KERNEL_INTERRUPT_FLAG (IDTFLAG_PRESENT | IDTFLAG_DPL_KERNEL | IDTFLAG_INTERRUPT) +#define USER_TRAP_FLAG (IDTFLAG_PRESENT | IDTFLAG_DPL_USER | IDTFLAG_INTERRUPT) + +#define IDT_SIZE (256) +struct idt_row_t idt[IDT_SIZE]; +struct idt_ptr_t idtp; + +void load_idt(struct idt_ptr_t *pt) { asm volatile("lidt (%0)" ::"r"(pt)); } + +void set_idt_row(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) { + idt[num].base_hi = (base >> 16) & 0x0000FFFF; + idt[num].base_lo = base & 0x0000FFFF; + idt[num].sel = sel; + idt[num].flags = flags; + idt[num].unset = 0; +} + +void init_idt() { + idtp.size = (sizeof(struct idt_row_t) * IDT_SIZE) - 1; + idtp.address = (uint32_t)&idt; + + memset((char *)&idt, 0, sizeof(struct idt_row_t) * IDT_SIZE); + + set_idt_row(0, (uint32_t)isr0, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(1, (uint32_t)isr1, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(2, (uint32_t)isr2, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(3, (uint32_t)isr3, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(4, (uint32_t)isr4, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(5, (uint32_t)isr5, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(6, (uint32_t)isr6, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(7, (uint32_t)isr7, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(8, (uint32_t)isr8, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(9, (uint32_t)isr9, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(10, (uint32_t)isr10, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(11, (uint32_t)isr11, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(12, (uint32_t)isr12, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(13, (uint32_t)isr13, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(14, (uint32_t)isr14, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(15, (uint32_t)isr15, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(16, (uint32_t)isr16, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(17, (uint32_t)isr17, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(18, (uint32_t)isr18, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(19, (uint32_t)isr19, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(20, (uint32_t)isr20, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(21, (uint32_t)isr21, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(22, (uint32_t)isr22, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(23, (uint32_t)isr23, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(24, (uint32_t)isr24, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(25, (uint32_t)isr25, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(26, (uint32_t)isr26, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(27, (uint32_t)isr27, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(28, (uint32_t)isr28, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(29, (uint32_t)isr29, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(30, (uint32_t)isr30, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(31, (uint32_t)isr31, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + + load_idt(&idtp); +} diff --git a/arch/i386/init.c b/arch/i386/init.c new file mode 100644 index 0000000..32dd4c2 --- /dev/null +++ b/arch/i386/init.c @@ -0,0 +1,11 @@ +#pragma once + +#include "./gdt.c" +#include "./idt.c" +#include "./irq.c" + +void arch_init() { + init_gdt(); + init_idt(); + init_irq(); +} diff --git a/arch/i386/irq.c b/arch/i386/irq.c new file mode 100644 index 0000000..3414707 --- /dev/null +++ b/arch/i386/irq.c @@ -0,0 +1,81 @@ +#pragma once + +#include "../../kernel/ps2.c" +#include "../../kernel/syscall.c" +#include "./asm.c" +#include "./idt.c" +#include "handlers.h" + +enum irq_code_t { + IRQCODE_PIT = 32, + IRQCODE_PS2 = 33, + IRQCODE_SYSCALL = 128, +}; + +void kb_handler() { + uint8_t char_code = inb(0x60); + handle_keypress(char_code); +} + +void remap_irq(void) { + outb(0x20, 0x11); + outb(0xA0, 0x11); + outb(0x21, 0x20); + outb(0xA1, 0x28); + outb(0x21, 0x04); + outb(0xA1, 0x02); + outb(0x21, 0x01); + outb(0xA1, 0x01); + outb(0x21, 0x0); + outb(0xA1, 0x0); +} + +void init_irq() { + remap_irq(); + + set_idt_row(32, (uint32_t)irq0, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(33, (uint32_t)irq1, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(34, (uint32_t)irq2, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(35, (uint32_t)irq3, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(36, (uint32_t)irq4, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(37, (uint32_t)irq5, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(38, (uint32_t)irq6, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(39, (uint32_t)irq7, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(40, (uint32_t)irq8, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(41, (uint32_t)irq9, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(42, (uint32_t)irq10, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(43, (uint32_t)irq11, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(44, (uint32_t)irq12, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(45, (uint32_t)irq13, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(46, (uint32_t)irq14, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + set_idt_row(47, (uint32_t)irq15, KERNEL_CODE_SEL, KERNEL_INTERRUPT_FLAG); + + set_idt_row(128, (uint32_t)irq128, KERNEL_CODE_SEL, USER_TRAP_FLAG); +} + +void irq_handler(struct regs *r) { + switch (r->int_no) { + case IRQCODE_SYSCALL: { + syscall(r); + break; + }; + + case IRQCODE_PIT: { + schedule(r); + break; + }; + + case IRQCODE_PS2: { + kb_handler(); + break; + }; + } + + // Send EOI to follower controller + if (r->int_no >= 40 && r->int_no < 48) { + outb(0xA0, 0x20); + } + + // Send EOI to leader controller + outb(0x20, 0x20); +} diff --git a/arch/i386/irq.s b/arch/i386/irq.s new file mode 100644 index 0000000..3e85f3d --- /dev/null +++ b/arch/i386/irq.s @@ -0,0 +1,143 @@ +.global irq0 +.global irq1 +.global irq2 +.global irq3 +.global irq4 +.global irq5 +.global irq6 +.global irq7 +.global irq8 +.global irq9 +.global irq10 +.global irq11 +.global irq12 +.global irq13 +.global irq14 +.global irq15 +.global irq128 + +irq0: + cli + push $0 + push $32 + jmp irq_handle + +irq1: + cli + push $1 + push $33 + jmp irq_handle + +irq2: + cli + push $2 + push $34 + jmp irq_handle + +irq3: + cli + push $3 + push $35 + jmp irq_handle + +irq4: + cli + push $4 + push $36 + jmp irq_handle + +irq5: + cli + push $5 + push $37 + jmp irq_handle + +irq6: + cli + push $6 + push $38 + jmp irq_handle + +irq7: + cli + push $7 + push $39 + jmp irq_handle + +irq8: + cli + push $8 + push $40 + jmp irq_handle + +irq9: + cli + push $9 + push $41 + jmp irq_handle + +irq10: + cli + push $10 + push $42 + jmp irq_handle + +irq11: + cli + push $11 + push $43 + jmp irq_handle + +irq12: + cli + push $12 + push $44 + jmp irq_handle + +irq13: + cli + push $13 + push $45 + jmp irq_handle + +irq14: + cli + push $14 + push $46 + jmp irq_handle + +irq15: + cli + push $15 + push $47 + jmp irq_handle + +irq128: + cli + push $0x80 + push $0x80 + jmp irq_handle + +irq_handle: + pusha + push %ds + push %es + push %fs + push %gs + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + movl %esp, %eax + push %eax + movl $irq_handler, %eax + call *%eax + pop %eax + pop %gs + pop %fs + pop %es + pop %ds + popal + addl $8, %esp + iret diff --git a/arch/i386/isr.s b/arch/i386/isr.s new file mode 100644 index 0000000..7728c5b --- /dev/null +++ b/arch/i386/isr.s @@ -0,0 +1,243 @@ +.global isr0 +.global isr1 +.global isr2 +.global isr3 +.global isr4 +.global isr5 +.global isr6 +.global isr7 +.global isr8 +.global isr9 +.global isr10 +.global isr11 +.global isr12 +.global isr13 +.global isr14 +.global isr15 +.global isr16 +.global isr17 +.global isr18 +.global isr19 +.global isr20 +.global isr21 +.global isr22 +.global isr23 +.global isr24 +.global isr25 +.global isr26 +.global isr27 +.global isr28 +.global isr29 +.global isr30 +.global isr31 + +isr0: + cli + push $0 + push $0 + jmp isr_handle + +isr1: + cli + push $0 + push $1 + jmp isr_handle + +isr2: + cli + push $0 + push $2 + jmp isr_handle + +isr3: + cli + push $0 + push $3 + jmp isr_handle + +isr4: + cli + push $0 + push $4 + jmp isr_handle + +isr5: + cli + push $0 + push $5 + jmp isr_handle + +isr6: + cli + push $0 + push $6 + jmp isr_handle + +isr7: + cli + push $0 + push $7 + jmp isr_handle + +isr8: + cli + push $0 + push $8 + jmp isr_handle + +isr9: + cli + push $0 + push $9 + jmp isr_handle + +isr10: + cli + push $10 + jmp isr_handle + +isr11: + cli + push $11 + jmp isr_handle + +isr12: + cli + push $12 + jmp isr_handle + +isr13: + cli + push $13 + jmp isr_handle + +isr14: + cli + push $14 + jmp isr_handle + +isr15: + cli + push $0 + push $15 + jmp isr_handle + +isr16: + cli + push $0 + push $16 + jmp isr_handle + +isr17: + cli + push $0 + push $17 + jmp isr_handle + +isr18: + cli + push $0 + push $18 + jmp isr_handle + +isr19: + cli + push $0 + push $19 + jmp isr_handle + +isr20: + cli + push $0 + push $20 + jmp isr_handle + +isr21: + cli + push $0 + push $21 + jmp isr_handle + +isr22: + cli + push $0 + push $22 + jmp isr_handle + +isr23: + cli + push $0 + push $23 + jmp isr_handle + +isr24: + cli + push $0 + push $24 + jmp isr_handle + +isr25: + cli + push $0 + push $25 + jmp isr_handle + +isr26: + cli + push $0 + push $26 + jmp isr_handle + +isr27: + cli + push $0 + push $27 + jmp isr_handle + +isr28: + cli + push $0 + push $28 + jmp isr_handle + +isr29: + cli + push $0 + push $29 + jmp isr_handle + +isr30: + cli + push $0 + push $30 + jmp isr_handle + +isr31: + cli + push $0 + push $31 + jmp isr_handle + +isr_handle: + pushal + push %ds + push %es + push %fs + push %gs + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + movl %esp, %eax + push %eax + movl $fault_handler, %eax + call *%eax + pop %eax + pop %gs + pop %fs + pop %es + pop %ds + popal + addl $8, %esp + iret diff --git a/arch/i386/loader.s b/arch/i386/loader.s new file mode 100644 index 0000000..29819d3 --- /dev/null +++ b/arch/i386/loader.s @@ -0,0 +1,26 @@ +.set ALIGN, 1<<0 +.set MEMINFO, 1<<1 +.set FLAGS, ALIGN | MEMINFO +.set MAGIC, 0x1BADB002 +.set CHECKSUM, -(MAGIC + FLAGS) + +.section .multiboot +.align 4 +.long MAGIC +.long FLAGS +.long CHECKSUM + +.section .bss +.align 16 +stack_bottom: +.skip 16384 +stack_top: + +.section .text +.global _start +.type _start, @function +_start: + mov $stack_top, %esp + call kernel_main + +.size _start, . - _start diff --git a/arch/i386/scheduler.c b/arch/i386/scheduler.c new file mode 100644 index 0000000..ada45cb --- /dev/null +++ b/arch/i386/scheduler.c @@ -0,0 +1,66 @@ +#pragma once + +#include "asm.c" + +enum eflags_t { + EFLAG_CARRY = 0x0001, + EFLAG_RES = 0x0002, + EFLAG_PARITY = 0x0004, + EFLAG_INTERRUPT = 0x0200 +}; + +void initialize_registers(struct regs *new_regs, char *entrypoint, + uint32_t address_base, uint32_t address_space_size) { + new_regs->eip = (unsigned int)entrypoint; + + new_regs->edi = 0; + new_regs->esi = 0; + new_regs->ebx = 0; + new_regs->edx = 0; + new_regs->ecx = 0; + new_regs->eax = 0; + + new_regs->eflags = EFLAG_INTERRUPT; + new_regs->useresp = (unsigned int)address_base + address_space_size - 1; + new_regs->ebp = new_regs->useresp; + + new_regs->ds = 35; + new_regs->es = 35; + new_regs->fs = 35; + new_regs->gs = 35; + new_regs->ss = 27; +} + +void store_registers(struct regs *machine_regs, struct regs *current_regs) { + current_regs->edi = machine_regs->edi; + current_regs->esi = machine_regs->esi; + current_regs->ebx = machine_regs->ebx; + current_regs->edx = machine_regs->edx; + current_regs->ecx = machine_regs->ecx; + current_regs->eax = machine_regs->eax; + current_regs->eip = machine_regs->eip; + current_regs->eflags = machine_regs->eflags; + current_regs->useresp = machine_regs->useresp; + current_regs->ebp = machine_regs->ebp; +} + +void switch_context(struct regs *machine_regs, struct regs *new_regs) { + machine_regs->edi = new_regs->edi; + machine_regs->esi = new_regs->esi; + machine_regs->ebx = new_regs->ebx; + machine_regs->edx = new_regs->edx; + machine_regs->ecx = new_regs->ecx; + machine_regs->eax = new_regs->eax; + machine_regs->eip = new_regs->eip; + machine_regs->eflags = new_regs->eflags; + machine_regs->esp = machine_regs->esp; + machine_regs->useresp = new_regs->useresp; + machine_regs->ebp = new_regs->ebp; + + machine_regs->ds = 35; + machine_regs->es = 35; + machine_regs->fs = 35; + machine_regs->gs = 35; + machine_regs->ss = 35; + machine_regs->cs = 27; +} diff --git a/arch/i386/syscall.c b/arch/i386/syscall.c new file mode 100644 index 0000000..95a4850 --- /dev/null +++ b/arch/i386/syscall.c @@ -0,0 +1,8 @@ +#pragma once + +#define INVOKE_SYSCALL(syscall_num) \ + __asm__ volatile("movl %0, %%eax;" \ + "int $0x80;" \ + : \ + : "r"(syscall_num) \ + : "%eax") diff --git a/clang-format b/clang-format new file mode 100644 index 0000000..960b610 --- /dev/null +++ b/clang-format @@ -0,0 +1,7 @@ +--- +BasedOnStyle: LLVM +AlignAfterOpenBracket: Align +AlignConsecutiveDeclarations: 'true' +AlignTrailingComments: 'true' + +... diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..aa0d6fc --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,38 @@ +[ +{ + "directory": "/home/nickorlow/programming/personal/razzle/build", + "command": "i386-elf-gcc -ffreestanding -g -Wall -Wextra -mgeneral-regs-only -DARCH_I386 -std=gnu99 -o CMakeFiles/kernel.elf.dir/kernel/kernel.c.o -c /home/nickorlow/programming/personal/razzle/kernel/kernel.c", + "file": "/home/nickorlow/programming/personal/razzle/kernel/kernel.c", + "output": "CMakeFiles/kernel.elf.dir/kernel/kernel.c.o" +}, +{ + "directory": "/home/nickorlow/programming/personal/razzle/build", + "command": "i386-elf-as -g -o CMakeFiles/kernel.elf.dir/arch/i386/idt.s.o -c /home/nickorlow/programming/personal/razzle/arch/i386/idt.s", + "file": "/home/nickorlow/programming/personal/razzle/arch/i386/idt.s", + "output": "CMakeFiles/kernel.elf.dir/arch/i386/idt.s.o" +}, +{ + "directory": "/home/nickorlow/programming/personal/razzle/build", + "command": "i386-elf-as -g -o CMakeFiles/kernel.elf.dir/arch/i386/irq.s.o -c /home/nickorlow/programming/personal/razzle/arch/i386/irq.s", + "file": "/home/nickorlow/programming/personal/razzle/arch/i386/irq.s", + "output": "CMakeFiles/kernel.elf.dir/arch/i386/irq.s.o" +}, +{ + "directory": "/home/nickorlow/programming/personal/razzle/build", + "command": "i386-elf-as -g -o CMakeFiles/kernel.elf.dir/arch/i386/isr.s.o -c /home/nickorlow/programming/personal/razzle/arch/i386/isr.s", + "file": "/home/nickorlow/programming/personal/razzle/arch/i386/isr.s", + "output": "CMakeFiles/kernel.elf.dir/arch/i386/isr.s.o" +}, +{ + "directory": "/home/nickorlow/programming/personal/razzle/build", + "command": "i386-elf-as -g -o CMakeFiles/kernel.elf.dir/arch/i386/loader.s.o -c /home/nickorlow/programming/personal/razzle/arch/i386/loader.s", + "file": "/home/nickorlow/programming/personal/razzle/arch/i386/loader.s", + "output": "CMakeFiles/kernel.elf.dir/arch/i386/loader.s.o" +}, +{ + "directory": "/home/nickorlow/programming/personal/razzle/build", + "command": "i386-elf-as -g -o CMakeFiles/kernel.elf.dir/arch/i386/tss.s.o -c /home/nickorlow/programming/personal/razzle/arch/i386/tss.s", + "file": "/home/nickorlow/programming/personal/razzle/arch/i386/tss.s", + "output": "CMakeFiles/kernel.elf.dir/arch/i386/tss.s.o" +} +] \ No newline at end of file diff --git a/debug.sh b/debug.sh new file mode 100755 index 0000000..9a25e96 --- /dev/null +++ b/debug.sh @@ -0,0 +1,8 @@ +tmux new-window +tmux send 'qemu-system-i386 -boot d -cdrom os.iso -m 512 -machine type=pc-i440fx-3.1 -monitor stdio -s -S' ENTER +tmux split-window -h +tmux send 'gdb kernel.elf' ENTER +tmux send 'target remote localhost:1234' ENTER +tmux send 'layout split' ENTER +#vncviewer 127.0.0.1:5900 -Scaling=100% +#killall qemu-system-i386 diff --git a/iso/boot/grub/menu.lst b/iso/boot/grub/menu.lst new file mode 100644 index 0000000..df40d3e --- /dev/null +++ b/iso/boot/grub/menu.lst @@ -0,0 +1,5 @@ +default=0 +timeout=0 + +title RAZZLE +kernel /boot/kernel.elf diff --git a/iso/boot/grub/stage2_eltorito b/iso/boot/grub/stage2_eltorito new file mode 100644 index 0000000000000000000000000000000000000000..b7c2d456393321b2af8a5adf818105a4a1e72429 GIT binary patch literal 107976 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 literal 0 HcmV?d00001 diff --git a/kernel/kernel.c b/kernel/kernel.c new file mode 100644 index 0000000..d12b817 --- /dev/null +++ b/kernel/kernel.c @@ -0,0 +1,74 @@ +#include "./scheduler.c" +#include "./vga.c" +#include + +#include "./test_processes.c" + +#ifdef ARCH_I386 +#include "../arch/i386/init.c" +#endif + +int kernel_main() { + lock = 0; + + outb(0x3D4, 0x0A); + outb(0x3D5, 0x20); + + clear_screen(); + + interrupt_disable(); + + arch_init(); + + init_process_table(); + + start_process("razzle", (char *) &test_razzle); + start_process("dazzle", (char *) &test_dazzle); + + interrupt_enable(); + + for (;;) { + } +} + +void fault_handler(struct regs *r) { + clear_screen(); + printf("!!! Kernel Panic !!!\n"); + switch (r->int_no) { + case 0: { + printf("Error: Divide by Zero\n"); + break; + }; + + case 6: { + printf("Error: Illegal instruction\n"); + break; + }; + + case 8: { + printf("Error: Double Fault (Ignoring)\n"); + break; + }; + + case 13: { + printf("Error: General Protection Fault at\n"); + break; + }; + + default: { + printf("Error: Unknown (no: %d)\n", r->int_no); + break; + }; + } + + printf("EIP: %x\n", r->eip); + printf("ESPv: %x\n", *(((unsigned int *)r->esp) + 2)); + printf("ESP : %x\n", r->esp); + + printf("\n"); + + process_debug(); + + for (;;) { + } +} diff --git a/kernel/memory.c b/kernel/memory.c new file mode 100644 index 0000000..d89bdd3 --- /dev/null +++ b/kernel/memory.c @@ -0,0 +1,24 @@ +#pragma once + +#include + +void memset(char *ptr, char data, uint32_t len) { + for (uint32_t i = 0; i < len; i++) { + ptr[i] = data; + } +} + +void memcpy(char *src, char *dst, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + dst[i] = src[i]; + } +} + +void strncpy(char *src, char *dst, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + dst[i] = src[i]; + if (src[i] == '\0') { + return; + } + } +} diff --git a/kernel/ps2.c b/kernel/ps2.c new file mode 100644 index 0000000..ad588fe --- /dev/null +++ b/kernel/ps2.c @@ -0,0 +1,302 @@ +#pragma once + +#include "./scheduler.c" +#include "./vga.c" +#include + +enum key_code_t { + backtick_tilde = 1, + one_bang = 2, + two_at = 3, + three_crunch = 4, + four_dollar = 5, + five_percent = 6, + six_carat = 7, + seven_amp = 8, + eight_star = 9, + nine_lparen = 10, + zero_rparen = 11, + minus_underscore = 12, + equal_plus = 13, + backspace = 14, + tab = 15, + lbracket_lbrace = 26, + rbracket_rbrace = 27, + backslash_pipe = 43, + caps_lock = 58, + semicolon_colon = 39, + quote_dquote = 40, + a = 30, + b = 48, + c = 46, + d = 32, + e = 18, + f = 33, + g = 34, + h = 35, + i = 23, + j = 36, + k = 37, + l = 38, + m = 50, + n = 49, + o = 24, + p = 25, + q = 16, + r = 19, + s = 31, + t = 20, + u = 22, + v = 47, + w = 17, + x = 45, + y = 21, + z = 44, + space = 57, + l_shift = 42, + r_shift = 54, + period_rarr = 52, + enter = 28, + comma_larr = 51, + slash_question = 53 +}; + +struct key_event_t { + enum key_code_t key; + uint8_t is_down; +}; + +struct kb_state { + uint8_t l_shift; + uint8_t r_shift; + uint8_t l_ctrl; + uint8_t r_ctrl; + uint8_t caps; +}; + +static struct kb_state kstate; + +uint8_t is_shifted() { return kstate.caps | kstate.r_shift | kstate.l_shift; } + +char keycode_to_char(enum key_code_t key_code) { + switch (key_code) { + case a: + return is_shifted() ? 'A' : 'a'; + break; + case b: + return is_shifted() ? 'B' : 'b'; + break; + case c: + return is_shifted() ? 'C' : 'c'; + break; + case d: + return is_shifted() ? 'D' : 'd'; + break; + case e: + return is_shifted() ? 'E' : 'e'; + break; + case f: + return is_shifted() ? 'F' : 'f'; + break; + case g: + return is_shifted() ? 'G' : 'g'; + break; + case h: + return is_shifted() ? 'H' : 'h'; + break; + case i: + return is_shifted() ? 'I' : 'i'; + break; + case j: + return is_shifted() ? 'J' : 'j'; + break; + case k: + return is_shifted() ? 'K' : 'k'; + break; + case l: + return is_shifted() ? 'L' : 'l'; + break; + case m: + return is_shifted() ? 'M' : 'm'; + break; + case n: + return is_shifted() ? 'N' : 'n'; + break; + case o: + return is_shifted() ? 'O' : 'o'; + break; + case p: + return is_shifted() ? 'P' : 'p'; + break; + case q: + return is_shifted() ? 'Q' : 'q'; + break; + case r: + return is_shifted() ? 'R' : 'r'; + break; + case s: + return is_shifted() ? 'S' : 's'; + break; + case t: + return is_shifted() ? 'T' : 't'; + break; + case u: + return is_shifted() ? 'U' : 'u'; + break; + case v: + return is_shifted() ? 'V' : 'v'; + break; + case w: + return is_shifted() ? 'W' : 'w'; + break; + case x: + return is_shifted() ? 'X' : 'x'; + break; + case y: + return is_shifted() ? 'Y' : 'y'; + break; + case z: + return is_shifted() ? 'Z' : 'z'; + break; + case space: + return ' '; + break; + case comma_larr: + return is_shifted() ? '<' : ','; + break; + case period_rarr: + return is_shifted() ? '>' : '.'; + break; + case enter: + return '\n'; + break; + case backspace: + return '\b'; + break; + case backtick_tilde: + return is_shifted() ? '~' : '`'; + break; + case one_bang: + return is_shifted() ? '!' : '1'; + break; + case two_at: + return is_shifted() ? '@' : '2'; + break; + case three_crunch: + return is_shifted() ? '#' : '3'; + break; + case four_dollar: + return is_shifted() ? '$' : '4'; + break; + case five_percent: + return is_shifted() ? '%' : '5'; + break; + case six_carat: + return is_shifted() ? '^' : '6'; + break; + case seven_amp: + return is_shifted() ? '&' : '7'; + break; + case eight_star: + return is_shifted() ? '*' : '8'; + break; + case nine_lparen: + return is_shifted() ? '(' : '9'; + break; + case zero_rparen: + return is_shifted() ? ')' : '0'; + break; + case minus_underscore: + return is_shifted() ? '_' : '-'; + break; + case equal_plus: + return is_shifted() ? '+' : '='; + break; + case tab: + return '\t'; + break; + case lbracket_lbrace: + return is_shifted() ? '{' : '['; + break; + case rbracket_rbrace: + return is_shifted() ? '}' : ']'; + break; + case semicolon_colon: + return is_shifted() ? ':' : ';'; + break; + case quote_dquote: + return is_shifted() ? '"' : '\''; + break; + case backslash_pipe: + return is_shifted() ? '|' : '\\'; + break; + case slash_question: + return is_shifted() ? '?' : '/'; + break; + case caps_lock: + case r_shift: + case l_shift: + break; + // default: + // printf("%d", key_code); + // break; + } + return '\0'; +} + + +struct ps2_waiter_t { + uint8_t alloc; + uint32_t pid; +}; + +struct ps2_waiter_t ps2_waiters[MAX_PROCESSES]; + +void init_ps2() { + for(uint32_t i = 0; i < MAX_PROCESSES; i++) { + ps2_waiters[i].alloc = 0; + } +} + +void handle_keypress(uint8_t char_code) { + struct key_event_t event; + if ((char_code & 0b11000000) == 0b10000000) { + event.is_down = 0; + event.key = char_code & 0b00111111; + } else { + event.is_down = 1; + event.key = char_code; + } + + switch (event.key) { + case l_shift: + kstate.l_shift = event.is_down; + break; + + case r_shift: + kstate.r_shift = event.is_down; + break; + + case caps_lock: + if (event.is_down) { + kstate.caps = !kstate.caps; + } + break; + + default: { + if (event.is_down) { + char c = keycode_to_char(event.key); + if (c != '\0') { + for(uint32_t i = 0; i < MAX_PROCESSES; i++) { + if(ps2_waiters[i].alloc) { + process_table[ps2_waiters[i].pid].registers.eax = c; + ps2_waiters[i].alloc = 0; + process_table[ps2_waiters[i].pid].state = TASKSTATE_ready; + } + } + } + } + break; + } + } +} diff --git a/kernel/scheduler.c b/kernel/scheduler.c new file mode 100644 index 0000000..ca77643 --- /dev/null +++ b/kernel/scheduler.c @@ -0,0 +1,95 @@ +#pragma once + +#ifdef ARCH_I386 +#include "../arch/i386/scheduler.c" +#endif + +#include "./memory.c" +#include "./vga.c" +#include + +#define ADDRESS_SPACE_SIZE 1000000 +#define MAX_PROCESSES 128 + +enum task_state_t { TASKSTATE_unused, TASKSTATE_ready, TASKSTATE_blocked }; + +struct task_struct { + enum task_state_t state; + struct regs registers; + unsigned int address_base; + char *allocd_addr; + char *process_name; +}; + +static struct task_struct process_table[MAX_PROCESSES]; +static uint32_t current_pid; + +void init_process_table() { + for (uint32_t i = 0; i < MAX_PROCESSES; i++) { + process_table[i].state = TASKSTATE_unused; + } + current_pid = 129; +} + +uint32_t get_next_pid(uint32_t start, enum task_state_t state) { + uint32_t i = start % MAX_PROCESSES; + for (; process_table[i].state != state; i = (i + 1) % MAX_PROCESSES) { + } + return i; +} + +char *balloc_pid(uint32_t size, uint32_t pid) { + char *addr = process_table[pid].allocd_addr; + process_table[pid].allocd_addr += size; + return addr; +} + +char *balloc(uint32_t size) { return balloc_pid(size, current_pid); } + +void start_process(char *name, char *entrypoint) { + uint32_t pid = get_next_pid(0, TASKSTATE_unused); + + process_table[pid].address_base = + (unsigned int)(ADDRESS_SPACE_SIZE * (pid + 6)); + + // TODO: Copy code into process' address space + // memcpy(entrypoint, (char*) process_table[pid].address_base, 500000); + // entrypoint = (char*) process_table[pid].address_base; + + process_table[pid].allocd_addr = (char *)process_table[pid].address_base; + process_table[pid].state = TASKSTATE_ready; + process_table[pid].process_name = balloc_pid(16, pid); + strncpy(name, process_table[pid].process_name, 16); + + initialize_registers(&process_table[pid].registers, entrypoint, + process_table[pid].address_base, ADDRESS_SPACE_SIZE); +} + +void single_process_debug(uint32_t pid) { + struct task_struct proc = process_table[pid]; + + printf_nolock("pid: %u (%s)\n", pid, proc.process_name); + printf_nolock("eip: %x esp: %x (%d)\n", proc.registers.eip, + proc.registers.useresp, proc.registers.useresp); + printf_nolock("ass: %d ase: %d\n\n", proc.address_base, proc.registers.ebp); +} + +void process_debug() { + printf_nolock("RAZZLE Debugger: Process Table | current pid: %u\n", + current_pid); + for (int i = 0; i < MAX_PROCESSES; i++) { + if (process_table[i].state != TASKSTATE_unused) + single_process_debug(i); + } +} + +void schedule(struct regs *r) { + if (current_pid < MAX_PROCESSES) { + store_registers(r, &process_table[current_pid].registers); + } + + current_pid = + get_next_pid((current_pid + 1) % MAX_PROCESSES, TASKSTATE_ready); + + switch_context(r, &process_table[current_pid].registers); +} diff --git a/kernel/spin_lock.c b/kernel/spin_lock.c new file mode 100644 index 0000000..8a54bca --- /dev/null +++ b/kernel/spin_lock.c @@ -0,0 +1,9 @@ +#pragma once + +#ifdef ARCH_I386 +#include "../arch/i386/asm.c" +#endif + +void sl_acquire(uint32_t *lock) { __sl_acquire(lock); } + +void sl_release(uint32_t *lock) { __sl_release(lock); } diff --git a/kernel/syscall.c b/kernel/syscall.c new file mode 100644 index 0000000..12658a5 --- /dev/null +++ b/kernel/syscall.c @@ -0,0 +1,64 @@ +#pragma once + +#include "scheduler.c" +#include "ps2.c" + +#ifdef ARCH_I386 +#include "../arch/i386/syscall.c" +#endif + +enum syscall_type_d { SYSCALL_yield, SYSCALL_getc, SYSCALL_exit }; + +void yield() { INVOKE_SYSCALL(SYSCALL_yield); } +char getc() { + INVOKE_SYSCALL(SYSCALL_getc); + + char result; + __asm__ volatile ( + "movb %%al, %0" + : "=r"(result) + ); + + return result; +} + +void __syscall_yield(struct regs *r) { + // for now, just reschedule + // TODO: reset PIT timer so + // next process doesn't get an + // unreasonably small quantum + + schedule(r); +} + +void syscall(struct regs *r) { + int syscall_no = r->eax; + + switch (syscall_no) { + case SYSCALL_yield: { + __syscall_yield(r); + break; + }; + + case SYSCALL_getc: { + process_table[current_pid].state = TASKSTATE_blocked; + + for(uint32_t i = 0; i < MAX_PROCESSES; i++) { + if(!ps2_waiters[i].alloc) { + ps2_waiters[i].alloc = 1; + ps2_waiters[i].pid = current_pid; + break; + } + } + + __syscall_yield(r); + break; + }; + + case SYSCALL_exit: { + process_table[current_pid].state = TASKSTATE_unused; + __syscall_yield(r); + break; + }; + } +} diff --git a/kernel/test_processes.c b/kernel/test_processes.c new file mode 100644 index 0000000..6196fdd --- /dev/null +++ b/kernel/test_processes.c @@ -0,0 +1,96 @@ +/* + * Since RAZZLE doesn't have disk support yet, all code for test processes are + * included in this file. + */ + +#pragma once + +#include "./scheduler.c" +#include "./spin_lock.c" +#include "./syscall.c" +#include "./vga.c" + +uint32_t lock2 = 0; + +void test_razzle() { + for (uint32_t i = 0;; i++) { + sl_acquire(&lock2); + row = 1; + col = 0; + printf("%s (pid %d): %u \n", process_table[current_pid].process_name, + current_pid, i); + sl_release(&lock2); + yield(); + } +} + +void cpuid() { + char model[13]; + uint32_t res[3]; + __asm__ volatile ("mov $0, %%eax; cpuid; mov %%ebx, %0; mov %%edx, %1; mov %%ecx, %2;" : "=r" (res[0]), "=r" (res[1]), "=r" (res[2]) ::"ebx", "ecx", "edx"); + for(uint32_t i = 0; i < 3; i++) { + model[(i*4) + 0] = (res[i] >> 0) & 0xFF; + model[(i*4) + 1] = (res[i] >> 8) & 0xFF; + model[(i*4) + 2] = (res[i] >> 16) & 0xFF; + model[(i*4) + 3] = (res[i] >> 24) & 0xFF; + } + model[12] = '\0'; + printf("%s", model); +} + +void test_dazzle() { + char prompt[] = "you@razzle ~$"; + char command[2048]; + + uint32_t cr = 2; + printf("%s", prompt); + for (uint32_t i = sizeof(prompt);;) { + char c = getc(); + + sl_acquire(&lock2); + + row = cr; + col = i; + + switch(c) { + case '\b': + if (i == 0) { + i = 79; + cr--; + } else { + i--; + } + col = i; + row = cr; + printf(" "); + break; + + case '\n': + i = 0; + cr++; + row++; + col = 0; + cpuid(); + cr++; + col = i; + row = cr; + printf("%s", prompt); + i = sizeof(prompt); + break; + + default: + printf("%c", c); + command[((cr-2)*80) + i] = c; + i++; + break; + } + + if (i >= 80) { + i = 0; + cr++; + } + + sl_release(&lock2); + yield(); + } +} diff --git a/kernel/vga.c b/kernel/vga.c new file mode 100644 index 0000000..2ff7a4b --- /dev/null +++ b/kernel/vga.c @@ -0,0 +1,157 @@ +#pragma once + +#include "./spin_lock.c" +#include +#include + +#define WIDTH 80 +#define HEIGHT 25 + +static uint8_t col = 0; +static uint8_t row = 0; +static uint8_t color_code = 10; +static char *vram = (char *)0xb8000; + +void clear_screen() { + for (int i = 0; i < HEIGHT - 1; i++) { + for (int j = 0; j < WIDTH; j++) { + vram[(i * WIDTH * 2) + (j * 2)] = 0; + // vram[(i * WIDTH * 2) + (j*2 + 1)] = 0; + } + } + row = 0; + col = 0; +} + +void putc(char c) { + if (col == WIDTH) { + row = (row + 1) % HEIGHT; + col = 0; + } + + if (c == '\n') { + for (int i = col; i < WIDTH; i++) { + vram[(row * WIDTH * 2) + (i * 2)] = ' '; + vram[(row * WIDTH * 2) + (i * 2 + 1)] = color_code; + } + row = (row + 1) % HEIGHT; + col = 0; + return; + } + + if (c == '\b') { + col--; + vram[(row * WIDTH * 2) + (col * 2)] = ' '; + vram[(row * WIDTH * 2) + (col * 2 + 1)] = color_code; + return; + } + + vram[(row * WIDTH * 2) + (col * 2)] = c; + vram[(row * WIDTH * 2) + (col * 2 + 1)] = color_code; + col++; +} + +void print_uint(unsigned int x, uint8_t base) { + if (x == 0) { + if (base) + putc('0'); + return; + } + + print_uint(x / 10, 0); + putc('0' + (x % 10)); +} + +void print_int(int x, uint8_t base) { + if (x == 0) { + if (base) + putc('0'); + return; + } + + if (x < 0) { + putc('-'); + print_int(-1 * (x / 10), 0); + putc('0' + (x % 10)); + } else { + print_int(x / 10, 0); + putc('0' + (x % 10)); + } +} + +void print_hex(int x) { + putc('0'); + putc('x'); + + do { + unsigned int digit = (x & 0xF0000000) >> 28; + putc((digit < 10) ? (digit + '0') : (digit - 10 + 'A')); + x <<= 4; + } while (x != 0); +} + +void print_string(char *s) { + for (uint32_t i = 0; s[i] != '\0'; i++) { + putc(s[i]); + } +} + +uint32_t lock; + +void printf_base(char *str, va_list list) { + + for (uint32_t i = 0; str[i] != '\0'; i++) { + if (str[i] == '%') { + switch (str[i + 1]) { + case '\0': + return; + case '%': { + putc('%'); + break; + } + + case 'c': { + putc(va_arg(list, int)); + break; + } + + case 'd': { + print_int(va_arg(list, int), 1); + break; + } + + case 'x': { + print_hex(va_arg(list, int)); + break; + } + + case 'u': { + print_uint(va_arg(list, unsigned int), 1); + break; + } + + case 's': { + print_string(va_arg(list, char *)); + break; + } + } + i++; + } else { + putc(str[i]); + } + } +} + +void printf_nolock(char *str, ...) { + va_list list; + va_start(list, str); + printf_base(str, list); +} + +void printf(char *str, ...) { + va_list list; + va_start(list, str); + sl_acquire(&lock); + printf_base(str, list); + sl_release(&lock); +} diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..e56537d --- /dev/null +++ b/linker.ld @@ -0,0 +1,28 @@ +ENTRY(_start) + +SECTIONS +{ + . = 2M; + + .text BLOCK(4K) : ALIGN(4K) + { + *(.multiboot) + *(.text) + } + + .rodata BLOCK(4K) : ALIGN(4K) + { + *(.rodata) + } + + .data BLOCK(4K) : ALIGN(4K) + { + *(.data) + } + + .bss BLOCK(4K) : ALIGN(4K) + { + *(COMMON) + *(.bss) + } +}