├── .gitignore ├── isofs └── boot │ └── grub │ └── grub.cfg ├── src ├── util.cpp ├── memory.h ├── types.h ├── assert.cpp ├── util.h ├── assert.h ├── interrupts.h ├── ports.h ├── idt.cpp ├── idt.h ├── misc.asm ├── gdt.h ├── gdt.cpp ├── kmain.cpp ├── boot.asm ├── keyboard.h ├── cppsupport.cpp ├── multiboot.h ├── display.h ├── interrupts.cpp ├── display.cpp ├── memory.cpp └── keyboard.cpp ├── .ycm_extra_conf.py ├── README.md ├── LICENSE ├── linker.ld └── SConstruct /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.exe 3 | *.iso 4 | .sconsign.dblite 5 | .ycm_extra_conf.pyc 6 | -------------------------------------------------------------------------------- /isofs/boot/grub/grub.cfg: -------------------------------------------------------------------------------- 1 | set default=0 2 | set timeout=0 3 | 4 | menuentry "spideros!" { 5 | multiboot /system/spideros.exe 6 | } 7 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | void memset(void* ptr, i8 val, u32 size) { 4 | i8* p = static_cast(ptr); 5 | for (u32 i = 0; i < size; i++) { 6 | p[i] = val; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_H_ 2 | #define MEMORY_H_ 3 | 4 | #include "types.h" 5 | 6 | namespace memory { 7 | 8 | void init(u32 mmapAddr, u32 mmapLen); 9 | 10 | } // namespace memory 11 | 12 | #endif /* MEMORY_H_ */ 13 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPES_H 2 | #define TYPES_H 3 | 4 | typedef signed char i8; 5 | typedef signed short i16; 6 | typedef signed int i32; 7 | 8 | typedef unsigned char u8; 9 | typedef unsigned short u16; 10 | typedef unsigned int u32; 11 | 12 | #endif // TYPES_H 13 | -------------------------------------------------------------------------------- /src/assert.cpp: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "display.h" 3 | 4 | void assertFail(const char* expr, const char* file, const char* line, 5 | const char* function) { 6 | display::println("\nAssertion failed at {}:{}:{}: expression: {}", file, line, 7 | function, expr); 8 | } 9 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H_ 2 | #define UTIL_H_ 3 | 4 | #include "types.h" 5 | 6 | void memset(void* ptr, i8 val, u32 size); 7 | 8 | template 9 | T min(const T& a, const T& b) { 10 | return a < b ? a : b; 11 | } 12 | 13 | template 14 | T max(const T& a, const T& b) { 15 | return a > b ? a : b; 16 | } 17 | 18 | #endif /* UTIL_H_ */ 19 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def FlagsForFile(filename, **kwargs): 4 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 5 | flags = os.popen('scons -Q ycm=1').read().split() 6 | 7 | # Force YCM to recognize all files as C++, including header files 8 | flags.extend(['-x', 'c++']) 9 | 10 | return { 11 | 'flags': flags, 12 | 'do_cache': True 13 | } 14 | -------------------------------------------------------------------------------- /src/assert.h: -------------------------------------------------------------------------------- 1 | #ifndef ASSERT_H 2 | #define ASSERT_H 3 | 4 | // For some reason these macros are what you need to turn __LINE__ into a string 5 | // literal instead of an integer literal. 6 | #define STRING(x) #x 7 | #define MACRO_STRING(x) STRING(x) 8 | 9 | // Define NDEBUG to disable assertions. 10 | #ifdef NDEBUG 11 | 12 | #define assert(expr) 13 | 14 | #else 15 | 16 | #define assert(expr) \ 17 | if (!(expr)) \ 18 | assertFail(#expr, __FILE__, MACRO_STRING(__LINE__), __PRETTY_FUNCTION__); 19 | 20 | #endif // NDEBUG 21 | 22 | void assertFail(const char* expr, const char* file, const char* line, 23 | const char* function); 24 | 25 | #endif // ASSERT_H 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | spideros 2 | ======== 3 | A hobby operating system written in C++14. An experiment to see what use the new 4 | C++14 standard features might be in an operating system kernel. 5 | 6 | 7 | Building 8 | -------- 9 | Install [scons](http://scons.org/) and run `scons`. 10 | 11 | 12 | Running in a VM 13 | --------------- 14 | Install [qemu](http://wiki.qemu.org/) and run `qemu-system-i386 spideros.iso`. 15 | 16 | 17 | Running on Real Hardware 18 | ------------------------ 19 | I haven't tried this yet... but feel free to give it a go. `scons` generates a 20 | `spideros.iso` which you should be able to burn to a CD or USB stick and boot 21 | from. 22 | 23 | 24 | License 25 | ------- 26 | spideros uses the [ISC license](http://en.wikipedia.org/wiki/ISC_license). 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Scott Olson 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(boot) 2 | 3 | SECTIONS 4 | { 5 | . = 0x00100000; 6 | linker_kernelStart = .; 7 | 8 | .text ALIGN (0x1000) : 9 | { 10 | *(.text) 11 | *(.gnu.linkonce.t*) 12 | } 13 | 14 | .rodata ALIGN (0x1000) : 15 | { 16 | linker_constructorsStart = .; 17 | *(.ctor*) 18 | linker_constructorsEnd = .; 19 | 20 | *(.rodata*) 21 | *(.gnu.linkonce.r*) 22 | } 23 | 24 | .data ALIGN (0x1000) : 25 | { 26 | *(.data) 27 | *(.gnu.linkonce.d*) 28 | } 29 | 30 | .bss : 31 | { 32 | *(COMMON) 33 | *(.bss) 34 | *(.gnu.linkonce.b*) 35 | } 36 | 37 | linker_kernelEnd = .; 38 | 39 | /DISCARD/ : 40 | { 41 | *(.comment) 42 | *(.eh_frame) /* discard this, unless you are implementing runtime support for C++ exceptions. */ 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/interrupts.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERRUPTS_H 2 | #define INTERRUPTS_H 3 | 4 | #include "types.h" 5 | 6 | namespace interrupts { 7 | 8 | // Enable interrupts on the CPU. 9 | inline void enable() { 10 | asm volatile("sti"); 11 | } 12 | 13 | // Enable interrupts on the CPU. 14 | inline void disable() { 15 | asm volatile("cli"); 16 | } 17 | 18 | struct Registers { 19 | u32 gs, fs, es, ds; // Segment registers. 20 | u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; // From 'pusha' instruction. 21 | u32 interruptNum; 22 | u32 eip, cs, eflags, useresp, ss; // Pushed by CPU on interrupt. 23 | }; 24 | 25 | using IrqHandlerFn = void (*)(Registers*); 26 | void setIrqHandler(u32 irqNum, IrqHandlerFn handlerFn); 27 | 28 | void init(); 29 | void remapPic(); 30 | 31 | extern "C" void interruptHandler(Registers* regs); 32 | 33 | } // namespace interrupts 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/ports.h: -------------------------------------------------------------------------------- 1 | #ifndef PORTS_H 2 | #define PORTS_H 3 | 4 | #include "types.h" 5 | 6 | namespace ports { 7 | 8 | inline void outb(u16 port, u8 val) { 9 | asm volatile("outb %0, %1" : : "a"(val), "d"(port)); 10 | } 11 | 12 | inline void outw(u16 port, u16 val) { 13 | asm volatile("outw %0, %1" : : "a"(val), "d"(port)); 14 | } 15 | 16 | inline void outl(u16 port, u32 val) { 17 | asm volatile("outl %0, %1" : : "a"(val), "d"(port)); 18 | } 19 | 20 | inline u8 inb(u16 port) { 21 | u8 val; 22 | asm volatile("inb %1, %0" : "=a"(val) : "d"(port)); 23 | return val; 24 | } 25 | 26 | inline u16 inw(u16 port) { 27 | u16 val; 28 | asm volatile("inw %1, %0" : "=a"(val) : "d"(port)); 29 | return val; 30 | } 31 | 32 | inline u32 inl(u16 port) { 33 | u32 val; 34 | asm volatile("inl %1, %0" : "=a"(val) : "d"(port)); 35 | return val; 36 | } 37 | 38 | } // namespace ports 39 | 40 | #endif // PORTS_H 41 | -------------------------------------------------------------------------------- /src/idt.cpp: -------------------------------------------------------------------------------- 1 | #include "idt.h" 2 | #include "interrupts.h" 3 | #include "util.h" 4 | 5 | namespace idt { 6 | 7 | Gate idt[IDT_ENTRIES]; 8 | 9 | void init() { 10 | memset(idt, 0, sizeof(idt)); 11 | 12 | IdtPtr idt_ptr; 13 | idt_ptr.size = sizeof(idt) - 1; 14 | idt_ptr.offset = reinterpret_cast(&idt); 15 | 16 | // Make the CPU load the new IDT. 17 | asm volatile("lidt (%0)" : : "a"(&idt_ptr)); 18 | } 19 | 20 | void setGate(u32 n, HandlerFn fn, u16 selector, u8 priv, u8 sys, u8 gateType) { 21 | u32 offset = reinterpret_cast(fn); 22 | idt[n].offsetLow = offset & 0xffff; // offset bits 0..15 23 | idt[n].offsetHigh = offset >> 16 & 0xffff; // offset bits 16..31 24 | idt[n].selector = selector; 25 | idt[n].typeAttr = (1 << 7) // First bit must be set for all valid descriptors. 26 | | ((priv & 0x3) << 5) // Two bits for the ring level. 27 | | ((sys & 0x1) << 4) // One bit for system segment. 28 | | (gateType & 0xf); // Four bits for gate type. 29 | } 30 | 31 | } // namespace idt 32 | -------------------------------------------------------------------------------- /src/idt.h: -------------------------------------------------------------------------------- 1 | #ifndef IDT_H 2 | #define IDT_H 3 | 4 | #include "types.h" 5 | 6 | // See http://wiki.osdev.org/IDT, or none of this will make any sense 7 | 8 | namespace idt { 9 | 10 | // Pointer to the Interrupt Descriptor Table for the lidt operation 11 | struct [[gnu::packed]] IdtPtr { 12 | u16 size; 13 | u32 offset; 14 | }; 15 | 16 | // Structure for entries in the Interrupt Descriptor Table 17 | struct [[gnu::packed]] Gate { 18 | u16 offsetLow; // offset 0..15 19 | u16 selector; // code segment selector in GDT 20 | u8 zero = 0; // unused, must be set to 0 21 | u8 typeAttr; // type and attributes 22 | u16 offsetHigh; // offset 16..31 23 | }; 24 | 25 | // Number of entries in the IDT. 26 | const unsigned IDT_ENTRIES = 256; 27 | 28 | using HandlerFn = void (*)(); 29 | 30 | enum { 31 | TASK = 0x5, 32 | INTR16 = 0x6, 33 | INTR32 = 0xe, 34 | TRAP16 = 0x7, 35 | TRAP32 = 0xf 36 | }; 37 | 38 | void init(); 39 | void setGate(u32 n, HandlerFn fn, u16 selector, u8 priv, u8 sys, u8 gateType); 40 | 41 | } // namespace idt 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/misc.asm: -------------------------------------------------------------------------------- 1 | bits 32 2 | 3 | section .text 4 | 5 | ;;; void loadGDT(gdt::GdtPtr*) 6 | global loadGDT 7 | loadGDT: 8 | push eax 9 | mov eax, [esp+0x8] 10 | lgdt [eax] 11 | pop eax 12 | ;; Reload CS register containing code selector. 13 | ;; We can't directly alter CS, so we far jump to change it 14 | jmp 0x08:.reload_CS ; 0x08 points at the new code selector (2nd in our GDT) 15 | .reload_CS: 16 | ;; Reload data segment registers. 17 | mov ax, 0x10 ; 0x10 points at the new data selector (3rd in our GDT) 18 | mov ds, ax 19 | mov es, ax 20 | mov fs, ax 21 | mov gs, ax 22 | mov ss, ax 23 | ret 24 | 25 | extern interruptHandler 26 | 27 | global interruptCommon 28 | interruptCommon: 29 | pusha 30 | push ds 31 | push es 32 | push fs 33 | push gs 34 | mov ax, 0x10 35 | mov ds, ax 36 | mov es, ax 37 | mov fs, ax 38 | mov gs, ax 39 | mov eax, esp 40 | push eax 41 | call interruptHandler 42 | pop eax 43 | pop gs 44 | pop fs 45 | pop es 46 | pop ds 47 | popa 48 | add esp, 4 49 | iret 50 | -------------------------------------------------------------------------------- /src/gdt.h: -------------------------------------------------------------------------------- 1 | #ifndef GDT_H 2 | #define GDT_H 3 | 4 | #include "types.h" 5 | 6 | // See http://wiki.osdev.org/GDT, or none of this will make any sense. 7 | 8 | namespace gdt { 9 | 10 | // Pointer to the Global Descriptor Table for the lgdt operation. 11 | struct [[gnu::packed]] GdtPtr { 12 | u16 size; 13 | u32 offset; 14 | }; 15 | 16 | // Structure for entries in the Global Descriptor Table. 17 | struct [[gnu::packed]] Entry { 18 | u16 limitLow; // limit 0..15 19 | u16 baseLow; // base 0..15 20 | u8 baseMid; // base 16..23 21 | u8 accessByte; 22 | u8 flags_limitHigh; // limit 16..19 23 | u8 baseHigh; // base 24..31 24 | }; 25 | 26 | // Number of entries in the GDT. 27 | const unsigned GDT_ENTRIES = 256; 28 | 29 | enum { 30 | // Access byte properties. 31 | GDT_PRESENT = 0x80, 32 | 33 | GDT_DPL0 = 0x00, // DPL = descriptor privilege level (ring level). 34 | GDT_DPL1 = 0x20, 35 | GDT_DPL2 = 0x40, 36 | GDT_DPL3 = 0x60, 37 | 38 | GDT_CODE = 0x08, 39 | GDT_READABLE = 0x02, 40 | GDT_CONFORMING = 0x04, 41 | 42 | GDT_DATA = 0x00, 43 | GDT_WRITABLE = 0x02, 44 | GDT_GROW_DOWN = 0x04, 45 | 46 | // Flags 47 | GDT_GRANULAR = 0x80, 48 | GDT_32BIT = 0x40, 49 | GDT_16BIT = 0x00 50 | }; 51 | 52 | void init(); 53 | void addEntry(u32 base, u32 limit, u8 accessByte, u8 flags); 54 | extern "C" void loadGDT(GdtPtr* gp); 55 | 56 | } // namespace gdt 57 | 58 | #endif // GDT_H 59 | -------------------------------------------------------------------------------- /src/gdt.cpp: -------------------------------------------------------------------------------- 1 | #include "gdt.h" 2 | #include "assert.h" 3 | #include "util.h" 4 | 5 | // See http://wiki.osdev.org/GDT, or none of this will make any sense 6 | 7 | namespace gdt { 8 | 9 | Entry gdt[GDT_ENTRIES]; 10 | unsigned nextEntry; 11 | 12 | void init() { 13 | // Zero the GDT entries initially since we won't use all of them. 14 | memset(gdt, 0, sizeof(gdt)); 15 | 16 | // Skip the first entry at index 0, it must be left zeroed. 17 | nextEntry = 1; 18 | 19 | addEntry(0, 0xFFFFF, GDT_PRESENT | GDT_DPL0 | GDT_CODE | GDT_READABLE, 20 | GDT_GRANULAR | GDT_32BIT); 21 | 22 | addEntry(0, 0xFFFFF, GDT_PRESENT | GDT_DPL0 | GDT_DATA | GDT_WRITABLE, 23 | GDT_GRANULAR | GDT_32BIT); 24 | 25 | GdtPtr gdt_ptr; 26 | gdt_ptr.size = sizeof(gdt) - 1; 27 | gdt_ptr.offset = reinterpret_cast(&gdt); 28 | loadGDT(&gdt_ptr); 29 | } 30 | 31 | void addEntry(u32 base, u32 limit, u8 accessByte, u8 flags) { 32 | assert(nextEntry < GDT_ENTRIES); 33 | 34 | gdt[nextEntry].baseLow = base & 0xFFFF; // base 0..15 35 | gdt[nextEntry].baseMid = base >> 16 & 0xFF; // base 16..23 36 | gdt[nextEntry].baseHigh = base >> 24 & 0xFF; // base 24..31 37 | gdt[nextEntry].limitLow = limit & 0xFFFF; // limit 0..15 38 | gdt[nextEntry].flags_limitHigh = flags 39 | | (limit >> 16 & 0xF); // limit 16..19 40 | gdt[nextEntry].accessByte = accessByte 41 | | 0x10; // This bit must be 1. 42 | 43 | nextEntry++; 44 | } 45 | 46 | } // namespace gdt 47 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | env = Environment( 4 | CXX = 'clang++', 5 | CPPFLAGS = [ 6 | '-Wall', 7 | '-Wextra', 8 | '-std=c++14', 9 | '-m32', 10 | '-march=i386', 11 | '-nostdlib', 12 | '-nostdinc', 13 | '-ffreestanding', 14 | '-fno-builtin', 15 | '-fno-exceptions', 16 | '-fno-rtti', 17 | '-fno-stack-protector', 18 | ], 19 | 20 | AS = 'nasm', 21 | ASFLAGS = [ 22 | '-felf32', 23 | ], 24 | 25 | ENV = { 26 | # Allow clang++ to use color. 27 | 'TERM': os.environ['TERM'] 28 | }, 29 | ) 30 | 31 | # Allow clang static analyzer to override the compiler. 32 | env['CC'] = os.getenv('CC') or env['CC'] 33 | env['CXX'] = os.getenv('CXX') or env['CXX'] 34 | env['ENV'].update(x for x in os.environ.items() if x[0].startswith('CCC_')) 35 | 36 | spideros_exe = env.Program( 37 | target = 'isofs/system/spideros.exe', 38 | source = [ 39 | 'src/assert.cpp', 40 | 'src/boot.asm', 41 | 'src/cppsupport.cpp', 42 | 'src/display.cpp', 43 | 'src/gdt.cpp', 44 | 'src/idt.cpp', 45 | 'src/interrupts.cpp', 46 | 'src/keyboard.cpp', 47 | 'src/kmain.cpp', 48 | 'src/memory.cpp', 49 | 'src/misc.asm', 50 | 'src/util.cpp', 51 | ], 52 | LINK = 'ld', 53 | LINKFLAGS = [ 54 | '-melf_i386', 55 | '-nostdlib', 56 | '-Tlinker.ld', 57 | ], 58 | ) 59 | Depends(spideros_exe, 'linker.ld') 60 | 61 | env.Command('spideros.iso', spideros_exe, 'grub-mkrescue -o $TARGET isofs') 62 | 63 | if ARGUMENTS.get('debug') == '1': 64 | env.Append(CPPFLAGS = ['-g'], ASFLAGS = ['-g']) 65 | 66 | # Print C++ flags for the YouCompleteMe vim plugin. 67 | if ARGUMENTS.get('ycm'): 68 | print(env.subst('$CXXFLAGS $CCFLAGS $_CCCOMCOM')) 69 | exit() 70 | -------------------------------------------------------------------------------- /src/kmain.cpp: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | #include "gdt.h" 3 | #include "idt.h" 4 | #include "interrupts.h" 5 | #include "keyboard.h" 6 | #include "memory.h" 7 | #include "multiboot.h" 8 | #include "ports.h" 9 | #include "types.h" 10 | 11 | namespace { 12 | template 13 | void runInit(const char* stage, Fn initFn) { 14 | display::print("Initializing {}...", stage); 15 | initFn(); 16 | display::println("done."); 17 | } 18 | } 19 | 20 | extern "C" void kmain(const multiboot::Info* mbinfo, u32 magic) { 21 | display::init(); 22 | 23 | if (magic != multiboot::BOOTLOADER_MAGIC) { 24 | // Something went not according to specs. Do not rely on the 25 | // multiboot data structure. 26 | display::println("error: The bootloader's magic number didn't match. Something must have gone wrong."); 27 | return; 28 | } 29 | 30 | // Print to screen to see everything is working. 31 | display::clearScreen(); 32 | display::println("Welcome to spideros"); 33 | display::println("==================="); 34 | 35 | if (mbinfo->hasFlag(multiboot::BOOTLOADER_NAME)) { 36 | display::println("Bootloader:\t{}", (const char*) mbinfo->bootloaderName); 37 | } 38 | 39 | if (mbinfo->hasFlag(multiboot::COMMAND_LINE)) { 40 | display::println("Command line:\t{}", (const char*) mbinfo->commandLine); 41 | } 42 | 43 | display::println(""); 44 | runInit("memory manager", [mbinfo] { 45 | memory::init(mbinfo->mmapAddr, mbinfo->mmapLen); 46 | }); 47 | runInit("GDT", gdt::init); 48 | runInit("IDT", idt::init); 49 | runInit("interrupt handlers", interrupts::init); 50 | runInit("keyboard", keyboard::init); 51 | display::println(""); 52 | 53 | display::print("1 + 2 = {}.\n", 1 + 2); 54 | 55 | display::print("Type away: "); 56 | 57 | // Handle timer interrupts. 58 | interrupts::setIrqHandler(0, [](interrupts::Registers*) { 59 | // static int ticks = 0; 60 | // ticks++; 61 | // if (ticks % 100 == 0) { 62 | // display::println("Ticks: ", ticks); 63 | // } 64 | }); 65 | 66 | interrupts::enable(); 67 | while (true) { 68 | display::print("{}", keyboard::readChar()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/boot.asm: -------------------------------------------------------------------------------- 1 | global boot ; making entry point visible to linker 2 | 3 | extern kmain ; kmain is defined in kmain.cpp 4 | extern __cxa_finalize 5 | 6 | ; Defined by the linker (see linker.ld). Contain the addresses of the start and 7 | ; end of the array of function pointers for static object constructors. 8 | extern linker_constructorsStart 9 | extern linker_constructorsEnd 10 | 11 | ; setting up the Multiboot header - see GRUB docs for details 12 | MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries 13 | MEMINFO equ 1<<1 ; provide memory map 14 | FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field 15 | MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header 16 | CHECKSUM equ -(MAGIC + FLAGS) ; checksum required 17 | 18 | section .text 19 | 20 | align 4 21 | dd MAGIC 22 | dd FLAGS 23 | dd CHECKSUM 24 | 25 | ; reserve initial kernel stack space 26 | STACKSIZE equ 0x4000 ; that's 16k. 27 | 28 | boot: 29 | mov esp, stack + STACKSIZE ; set up the stack 30 | push eax ; Multiboot magic number 31 | push ebx ; Multiboot info structure 32 | 33 | mov ebx, linker_constructorsStart ; call the constructors 34 | jmp .ctors_until_end 35 | .call_constructor: 36 | call [ebx] 37 | add ebx,4 38 | .ctors_until_end: 39 | cmp ebx, linker_constructorsEnd 40 | jb .call_constructor 41 | 42 | call kmain ; call kernel proper 43 | 44 | sub esp, 4 ; call the destructors 45 | mov [esp], dword 0x0 46 | call __cxa_finalize ; i.e. __cxa_finalize(nullptr) 47 | add esp, 4 48 | 49 | cli ; disable interrupts 50 | .hang: 51 | hlt ; halt machine should kernel return 52 | jmp .hang 53 | 54 | section .bss 55 | 56 | align 4 57 | stack: 58 | resb STACKSIZE ; reserve 16k stack on a doubleword boundary 59 | -------------------------------------------------------------------------------- /src/keyboard.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYBOARD_H_ 2 | #define KEYBOARD_H_ 3 | 4 | #include "display.h" 5 | #include "interrupts.h" 6 | #include "types.h" 7 | 8 | namespace keyboard { 9 | 10 | // The physical keys on the keyboard. Characters accessed by holding shift are 11 | // _not_ included. 104-key US layout is assumed. 12 | enum class Key { 13 | UNKNOWN = 0, 14 | 15 | _1, 16 | _2, 17 | _3, 18 | _4, 19 | _5, 20 | _6, 21 | _7, 22 | _8, 23 | _9, 24 | _0, 25 | 26 | A, 27 | B, 28 | C, 29 | D, 30 | E, 31 | F, 32 | G, 33 | H, 34 | I, 35 | J, 36 | K, 37 | L, 38 | M, 39 | N, 40 | O, 41 | P, 42 | Q, 43 | R, 44 | S, 45 | T, 46 | U, 47 | V, 48 | W, 49 | X, 50 | Y, 51 | Z, 52 | 53 | TAB, 54 | SPACE, 55 | 56 | DASH, 57 | EQUAL, 58 | BACKSPACE, 59 | ENTER, 60 | SEMICOLON, 61 | SINGLE_QUOTE, 62 | BACKTICK, 63 | BACKSLASH, 64 | COMMA, 65 | PERIOD, 66 | SLASH, 67 | LEFT_SQUARE_BRACKET, 68 | RIGHT_SQUARE_BRACKET, 69 | 70 | KEYPAD_MULTIPLY, 71 | KEYPAD_DIVIDE, 72 | KEYPAD_ADD, 73 | KEYPAD_SUBTRACT, 74 | KEYPAD_DECIMAL, 75 | KEYPAD_ENTER, 76 | KEYPAD_1, 77 | KEYPAD_2, 78 | KEYPAD_3, 79 | KEYPAD_4, 80 | KEYPAD_5, 81 | KEYPAD_6, 82 | KEYPAD_7, 83 | KEYPAD_8, 84 | KEYPAD_9, 85 | KEYPAD_0, 86 | 87 | F1, 88 | F2, 89 | F3, 90 | F4, 91 | F5, 92 | F6, 93 | F7, 94 | F8, 95 | F9, 96 | F10, 97 | F11, 98 | F12, 99 | 100 | CAPS_LOCK, 101 | NUM_LOCK, 102 | SCROLL_LOCK, 103 | 104 | ESCAPE, 105 | PRINT_SCREEN, 106 | PAUSE, 107 | MENU, 108 | 109 | LEFT_SHIFT, 110 | RIGHT_SHIFT, 111 | LEFT_ALT, 112 | RIGHT_ALT, 113 | LEFT_CONTROL, 114 | RIGHT_CONTROL, 115 | LEFT_SUPER, 116 | RIGHT_SUPER, 117 | 118 | HOME, 119 | END, 120 | INSERT, 121 | DELETE, 122 | PAGE_UP, 123 | PAGE_DOWN, 124 | UP, 125 | DOWN, 126 | LEFT, 127 | RIGHT, 128 | }; 129 | 130 | struct KeyEvent { 131 | enum Action { UP, DOWN }; 132 | 133 | Key key; 134 | Action action; 135 | 136 | // Set to '\0' if the key doesn't correspond to a character. 137 | char character; 138 | 139 | // Modifiers. True if pressed at the time of this event. 140 | bool alt; 141 | bool control; 142 | bool shift; 143 | bool super; 144 | 145 | // Toggles. True if turned on at the time of this event. 146 | bool capsLock; 147 | bool numLock; 148 | bool scrollLock; 149 | }; 150 | 151 | void init(); 152 | char readChar(); 153 | KeyEvent readEvent(); 154 | void flushBuffer(); 155 | const char* keyName(Key key); 156 | char lowerCaseChar(Key key); 157 | char upperCaseChar(Key key); 158 | 159 | inline void print(keyboard::Key key) { 160 | display::print(keyName(key)); 161 | } 162 | 163 | } // namespace keyboard 164 | 165 | #endif /* KEYBOARD_H_ */ 166 | -------------------------------------------------------------------------------- /src/cppsupport.cpp: -------------------------------------------------------------------------------- 1 | // This file implements low-level functions required for certain C++ features to 2 | // work. This file is not to contain C++ standard library features. 3 | 4 | extern "C" { 5 | 6 | // Required when using pure virtual functions. 7 | void __cxa_pure_virtual() { 8 | // This is called in case a pure virtual function call cannot be made. This 9 | // should never happen. 10 | // TODO: Should we throw an error somehow when that happens? 11 | } 12 | 13 | // Below, the runtime API for the Itanium C++ ABI DSO Object Desctruction API is 14 | // implemented. Refer to 15 | // http://sourcery.mentor.com/public/cxx-abi/abi.html#dso-dtor. Basically, this 16 | // is required for global object destructors to be called after kmain returns. 17 | 18 | const unsigned int ATEXIT_MAX_FUNCS = 128; // TODO: Why 128? I don't know. 19 | 20 | struct atexit_func_entry { 21 | void (*destructor)(void*); 22 | void* obj_ptr; 23 | void* dso_handle; 24 | }; 25 | 26 | atexit_func_entry __atexit_funcs[ATEXIT_MAX_FUNCS]; 27 | unsigned __atexit_func_count = 0; 28 | 29 | void* __dso_handle = 0; // TODO: According to OSDev, optimally, you 30 | // should remove the '= 0' part and define this in your 31 | // asm script. 32 | 33 | int __cxa_atexit(void (*destructor)(void*), void* obj_ptr, void* dso) { 34 | if (__atexit_func_count >= ATEXIT_MAX_FUNCS) { 35 | return -1; // Failure. 36 | } 37 | 38 | __atexit_funcs[__atexit_func_count].destructor = destructor; 39 | __atexit_funcs[__atexit_func_count].obj_ptr = obj_ptr; 40 | __atexit_funcs[__atexit_func_count].dso_handle = dso; 41 | __atexit_func_count++; 42 | 43 | return 0; // Success. 44 | } 45 | 46 | void __cxa_finalize(void* dso) { 47 | // According to the Itanium C++ ABI, if NULL is passed, all destructors should 48 | // be called. This will definitely happen after kmain returns. Note that the 49 | // destructors must be called in the opposite of the order in which they were 50 | // added to the list. 51 | if (dso == nullptr) { 52 | for (int i = __atexit_func_count; i > 0; --i) { 53 | auto func = __atexit_funcs[i].destructor; 54 | if (func) { 55 | (*func)(__atexit_funcs[i].obj_ptr); 56 | } 57 | } 58 | __atexit_func_count = 0; 59 | return; 60 | } 61 | 62 | // Walk the list calling and removing the destructors if dso matches their 63 | // dso_handle. 64 | for(int i = __atexit_func_count; i > 0; --i) { 65 | if (__atexit_funcs[i].dso_handle == dso) { 66 | (*__atexit_funcs[i].destructor)(__atexit_funcs[i].obj_ptr); 67 | 68 | // Copy all the higher destructors down one spot to fill the hole in the 69 | // list due to removing this one. 70 | for (int j = __atexit_func_count - 1; j > i; j--) { 71 | __atexit_funcs[j-1] = __atexit_funcs[j]; 72 | } 73 | 74 | // There is now one less destructor in the world. 75 | __atexit_func_count--; 76 | } 77 | } 78 | } 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/multiboot.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTIBOOT_H 2 | #define MULTIBOOT_H 3 | // Refer to http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#multiboot_002eh 4 | 5 | #include "types.h" 6 | 7 | namespace multiboot { 8 | 9 | // The magic number passed by the bootloader to the operating system. 10 | const u32 BOOTLOADER_MAGIC = 0x2BADB002; 11 | 12 | struct AoutSymbolTable { 13 | u32 tabsize, strsize, addr, reserved; 14 | }; 15 | 16 | struct ElfSectionHeaderTable { 17 | u32 num, size, addr, shndx; 18 | }; 19 | 20 | // The different possible flags in the flags member 21 | enum Flag { 22 | MEMORY = 1 << 0, 23 | BOOT_DEVICE = 1 << 1, 24 | COMMAND_LINE = 1 << 2, 25 | MODULES = 1 << 3, 26 | AOUT_SYMBOL_TABLE = 1 << 4, // These two are 27 | ELF_SECTION_HEADER_TABLE = 1 << 5, // mutually exclusive. 28 | MEMORY_MAP = 1 << 6, 29 | DRIVE_INFO = 1 << 7, 30 | CONFIG_TABLE = 1 << 8, 31 | BOOTLOADER_NAME = 1 << 9, 32 | APM_TABLE = 1 << 10, 33 | VIDEO_INFO = 1 << 11 34 | }; 35 | 36 | // Structure of the information recieved from the multiboot-compliant bootloader 37 | // (e.g. GRUB) 38 | // TODO: Add accessor functions returning the correct type for each member. 39 | struct Info { 40 | bool hasFlag(Flag f) const { 41 | return flags & f; 42 | } 43 | 44 | // Determines which fields below are present. 45 | u32 flags; 46 | 47 | // Available memory from BIOS (in kilobytes) 48 | u32 memoryLower, memoryHigher; 49 | 50 | // Boot device which was used to boot the kernel. 51 | u32 bootDevice; 52 | 53 | // The command line passed to the kernel by the bootloader. 54 | // Example: /system/spideros.exe 55 | u32 commandLine; 56 | 57 | // Boot module list 58 | u32 modulesCount, modulesAddr; 59 | 60 | union { 61 | AoutSymbolTable aoutSymbolTable; 62 | ElfSectionHeaderTable elfSectionHeaderTable; 63 | }; 64 | 65 | // Memory mapping buffer 66 | u32 mmapLen, mmapAddr; 67 | 68 | // Drive info buffer 69 | u32 drivesLen, drivesAddr; 70 | 71 | // ROM configuration table 72 | u32 configTable; 73 | 74 | // Bootloader name (e.g. GNU GRUB 0.97) 75 | u32 bootloaderName; 76 | 77 | // APM (Advanced Power Management) table 78 | u32 apmTable; 79 | 80 | // Video 81 | u32 vbeControlInfo, vbeModeInfo; 82 | u16 vbeMode, vbeInterfaceSeg, vbeInterfaceOff, vbeInterfaceLen; 83 | }; 84 | 85 | // Structure of an entry in the multiboot memory mapping buffer 86 | struct MmapEntry { 87 | // Size of the structure (not counting the size member itself) 88 | u32 size; 89 | 90 | // Memory region starting address (addrHigh only for 64-bit) 91 | u32 addr, addrHigh; 92 | 93 | // Memory region length (lenHigh only for 64-bit) 94 | u32 len, lenHigh; 95 | 96 | // Type of memory (1 means available, 2 means reserved) 97 | u32 type; 98 | }; 99 | 100 | // Structure of a module in the multiboot boot module list 101 | struct ModuleList { 102 | // The memory goes from moduleStart to moduleEnd-1 inclusive. 103 | u32 moduleStart, moduleEnd; 104 | 105 | // Command line given to the module 106 | u32 commandLine; 107 | 108 | // Padding to take it to 16 bytes (must be zero) 109 | u32 padding; 110 | }; 111 | 112 | } // namespace multiboot 113 | 114 | #endif // MULTIBOOT_H 115 | -------------------------------------------------------------------------------- /src/display.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPLAY_H 2 | #define DISPLAY_H 3 | 4 | #include "types.h" 5 | 6 | namespace display { 7 | 8 | const int CONSOLE_WIDTH = 80; 9 | const int CONSOLE_HEIGHT = 25; 10 | 11 | extern int cursorX, cursorY; 12 | extern u8 color; 13 | 14 | enum class Color : u8 { 15 | // Bright bit unset. 16 | BLACK = 0, 17 | BLUE, 18 | GREEN, 19 | CYAN, 20 | RED, 21 | MAGENTA, 22 | BROWN, 23 | LIGHT_GREY, 24 | 25 | // Bright bit set. 26 | DARK_GREY, 27 | LIGHT_BLUE, 28 | LIGHT_GREEN, 29 | LIGHT_CYAN, 30 | LIGHT_RED, 31 | LIGHT_MAGENTA, 32 | YELLOW, 33 | WHITE 34 | }; 35 | 36 | // A cell in text video memory. Do not reorder the fields. 37 | struct Cell { 38 | u8 character; 39 | u8 color; 40 | 41 | Cell(u8 color, u8 character) : character(character), color(color) {} 42 | }; 43 | 44 | void init(); 45 | void clearScreen(); 46 | void scroll(); 47 | void updateCursor(); 48 | Cell& cellAt(int x, int y); 49 | void setColor(Color fg, Color bg); 50 | 51 | // Printing functions. 52 | void printInt(u32 n, int radix); 53 | void printAt(char c, int x, int y); 54 | void printChar(char c); 55 | void printString(const char* s); 56 | 57 | inline void printFmt(const char* s, const char*) { 58 | printString(s); 59 | } 60 | 61 | inline void printFmt(char c, const char*) { 62 | printChar(c); 63 | } 64 | 65 | inline void printFmt(u32 x, const char* format) { 66 | int base = 10; 67 | if (*format == 'x') { 68 | base = 16; 69 | } 70 | printInt(x, base); 71 | } 72 | 73 | inline void printFmt(i32 n, const char*) { 74 | if (n < 0) { 75 | printChar('-'); 76 | // Use a cast instead of the expression -n, because -n can 77 | // overflow. E.g. if n == INT_MIN, -n returns a negative, because 78 | // -INT_MIN == INT_MIN. 79 | printInt(static_cast(n), 10); 80 | } else { 81 | printInt(n, 10); 82 | } 83 | } 84 | 85 | inline void print(const char* format) { 86 | printString(format); 87 | } 88 | 89 | template 90 | void print(const char* format, First firstArg, Rest... restArgs) { 91 | const u32 SPECIFIER_MAX_LENGTH = 64; 92 | char specifier[SPECIFIER_MAX_LENGTH]; 93 | u32 specifierIndex = 0; 94 | bool parsingSpecifier = false; 95 | bool escaped = false; 96 | 97 | for (const char* p = format; *p != '\0'; ++p) { 98 | char c = *p; 99 | if (escaped) { 100 | if (c == '\\' || c == '{') { 101 | printChar(c); 102 | } else { 103 | printChar('\\'); 104 | printChar(c); 105 | } 106 | escaped = false; 107 | } else if (c == '{' && !parsingSpecifier) { 108 | parsingSpecifier = true; 109 | } else if (parsingSpecifier) { 110 | if (c == '}') { 111 | specifier[specifierIndex] = '\0'; 112 | printFmt(firstArg, specifier); 113 | print(p + 1, restArgs...); 114 | return; 115 | } else { 116 | specifier[specifierIndex] = c; 117 | ++specifierIndex; 118 | } 119 | } else if (c == '\\') { 120 | escaped = true; 121 | } else { 122 | printChar(c); 123 | } 124 | } 125 | 126 | if (escaped) { 127 | printChar('\\'); 128 | } else if (parsingSpecifier) { 129 | // TODO: Unmatched { hit end of string. Should panic. 130 | } 131 | } 132 | 133 | template 134 | void println(const char* format, Args... args) { 135 | print(format, args...); 136 | printChar('\n'); 137 | } 138 | 139 | } // namepspace display 140 | 141 | #endif // DISPLAY_H 142 | -------------------------------------------------------------------------------- /src/interrupts.cpp: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "interrupts.h" 3 | #include "display.h" 4 | #include "idt.h" 5 | #include "ports.h" 6 | 7 | // TODO: Deal with the magic numbers in this file. 8 | 9 | namespace interrupts { 10 | 11 | template 12 | [[gnu::naked]] void interrupt() { 13 | asm volatile("cli"); 14 | asm volatile("push %0" : : "i"(n)); 15 | asm volatile("jmp interruptCommon"); 16 | } 17 | 18 | // Set the IDT gates for interrupts from 0 up to N - 1 19 | template 20 | [[gnu::always_inline]] void setIdtGates() { 21 | setIdtGates(); 22 | idt::setGate(N - 1, interrupt, 0x8, 0, 0, idt::INTR32); 23 | } 24 | 25 | template<> void setIdtGates<0>() {} 26 | 27 | void init() { 28 | remapPic(); 29 | setIdtGates<48>(); 30 | } 31 | 32 | // Master PIC command and data port numbers. 33 | const u16 PIC1_COMMAND_PORT = 0x20; 34 | const u16 PIC1_DATA_PORT = 0x21; 35 | 36 | // Slave PIC command and data port numbers. 37 | const u16 PIC2_COMMAND_PORT = 0xA0; 38 | const u16 PIC2_DATA_PORT = 0xA1; 39 | 40 | // End-of-interupt command to send to the PICs when we are finished handling an 41 | // interrupt to resume regularly scheduled programming. 42 | const u8 PIC_EOI = 0x20; 43 | 44 | // Normally, IRQs 0 to 7 are mapped to entries 8 to 15. This is a problem in 45 | // protected mode, because IDT entry 8 is a Double Fault. Without remapping, 46 | // every time irq0 fires, you get a Double Fault Exception, which is not 47 | // actually what's happening. We send commands to the Programmable Interrupt 48 | // Controller (PICs, also called the 8259s) in order to remap irq0 to 15 to IDT 49 | // entries 32 to 47. 50 | void remapPic() { 51 | // Tell the PICs to wait for our 3 initialization bytes (we want to 52 | // reinitialize). 53 | ports::outb(PIC1_COMMAND_PORT, 0x11); 54 | ports::outb(PIC2_COMMAND_PORT, 0x11); 55 | 56 | // Set master PIC offset to 0x20 (= IRQ0 = 32). 57 | ports::outb(PIC1_DATA_PORT, 0x20); 58 | // Set slave PIC offset to 0x28 (= IRQ8 = 40). 59 | ports::outb(PIC2_DATA_PORT, 0x28); 60 | 61 | // Set the wiring to 'attached to corresponding interrupt pin'. 62 | ports::outb(PIC1_DATA_PORT, 0x04); 63 | ports::outb(PIC2_DATA_PORT, 0x02); 64 | 65 | // We want to use 8086/8088 mode (bit 0). 66 | ports::outb(PIC1_DATA_PORT, 0x01); 67 | ports::outb(PIC2_DATA_PORT, 0x01); 68 | 69 | // Restore masking (if a bit is not set_PORT, the interrupts is on). 70 | ports::outb(PIC1_DATA_PORT, 0x00); 71 | ports::outb(PIC2_DATA_PORT, 0x00); 72 | } 73 | 74 | IrqHandlerFn irqHandlerFns[16]; // Implicitly zero-initialized. 75 | 76 | void setIrqHandler(u32 irqNum, IrqHandlerFn handlerFn) { 77 | assert(irqNum < 16); 78 | irqHandlerFns[irqNum] = handlerFn; 79 | } 80 | 81 | extern "C" void interruptHandler(Registers* regs) { 82 | if (regs->interruptNum >= 32 && regs->interruptNum < 48) { 83 | const u32 irqNum = regs->interruptNum - 32; 84 | 85 | if (irqHandlerFns[irqNum]) { 86 | irqHandlerFns[irqNum](regs); 87 | } else { 88 | display::println("Got unhandled irq ", irqNum); 89 | } 90 | 91 | // We need to send an EOI (end-of-interrupt command) to the interrupt 92 | // controller when we are done. Only send EOI to slave controller if it's 93 | // involved (irqs 8 and up). 94 | if(regs->interruptNum >= 8) { 95 | ports::outb(PIC2_COMMAND_PORT, PIC_EOI); 96 | } 97 | ports::outb(PIC1_COMMAND_PORT, PIC_EOI); 98 | } else { 99 | display::println("Got isr interrupt: ", regs->interruptNum); 100 | 101 | // Disable interrupts and halt. 102 | asm volatile("cli; hlt"); 103 | } 104 | } 105 | 106 | } // namespace interrupts 107 | -------------------------------------------------------------------------------- /src/display.cpp: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | #include "ports.h" 3 | #include "assert.h" 4 | 5 | namespace display { 6 | 7 | Cell* videoram = reinterpret_cast(0xb8000); 8 | int cursorX = 0; 9 | int cursorY = 0; 10 | u8 color; 11 | 12 | // Indexes for the index port. 13 | const int CURSOR_LOW_PORT = 0x0E; 14 | const int CURSOR_HIGH_PORT = 0x0F; 15 | 16 | // Display-related I/O ports. They are taken from the BIOS Data Area during 17 | // init(); 18 | u16 indexPort; 19 | u16 dataPort; 20 | 21 | void init() { 22 | // Find the base IO port for video from the BIOS Data Area. See 23 | // http://wiki.osdev.org/Memory_Map_%28x86%29#BIOS_Data_Area_.28BDA.29 24 | u16 baseIoPort = *reinterpret_cast(0x0463); 25 | indexPort = baseIoPort; 26 | dataPort = baseIoPort + 1; 27 | 28 | setColor(Color::LIGHT_GREY, Color::BLACK); 29 | } 30 | 31 | void setColor(Color fg, Color bg) { 32 | color = (u8(bg) << 4) | (u8(fg) & 0x0F); 33 | } 34 | 35 | void clearScreen() { 36 | for (int i = 0; i < CONSOLE_HEIGHT * CONSOLE_WIDTH; i++) 37 | videoram[i] = Cell(color, ' '); 38 | 39 | cursorX = 0; 40 | cursorY = 0; 41 | updateCursor(); 42 | } 43 | 44 | void scroll() { 45 | // Copy each line onto the one above it. 46 | for (int y = 0; y < CONSOLE_HEIGHT - 1; y++) 47 | for (int x = 0; x < CONSOLE_WIDTH; x++) 48 | cellAt(x, y) = cellAt(x, y + 1); 49 | 50 | // Blank out the bottom line. 51 | for (int x = 0; x < CONSOLE_WIDTH; x++) 52 | cellAt(x, CONSOLE_HEIGHT - 1) = Cell(color, ' '); 53 | } 54 | 55 | // Update the position of the blinking cursor on the screen. 56 | void updateCursor() { 57 | const int position = cursorY * CONSOLE_WIDTH + cursorX; 58 | 59 | ports::outb(indexPort, CURSOR_LOW_PORT); 60 | ports::outb(dataPort, position >> 8); 61 | 62 | ports::outb(indexPort, CURSOR_HIGH_PORT); 63 | ports::outb(dataPort, position); 64 | } 65 | 66 | Cell& cellAt(int x, int y) { 67 | return videoram[y * CONSOLE_WIDTH + x]; 68 | } 69 | 70 | void printAt(char c, int x, int y) { 71 | cellAt(x, y) = Cell(color, c); 72 | } 73 | 74 | void printChar(char c) { 75 | switch(c) { 76 | case '\b': 77 | if (cursorX != 0) { 78 | cursorX--; 79 | } 80 | printAt(' ', cursorX, cursorY); 81 | break; 82 | 83 | case '\t': 84 | // Align cursorX to the next multiple of 8 85 | cursorX = cursorX - (cursorX % 8) + 8; 86 | break; 87 | 88 | case '\r': 89 | cursorX = 0; 90 | break; 91 | 92 | case '\n': 93 | cursorX = 0; 94 | cursorY++; 95 | break; 96 | 97 | default: 98 | printAt(c, cursorX, cursorY); 99 | cursorX++; 100 | } 101 | 102 | if (cursorX == CONSOLE_WIDTH) { 103 | cursorX = 0; 104 | cursorY++; 105 | } 106 | 107 | if (cursorY == CONSOLE_HEIGHT) { 108 | cursorY--; 109 | scroll(); 110 | } 111 | 112 | updateCursor(); 113 | } 114 | 115 | void printString(const char* str) { 116 | while (*str != '\0') { 117 | printChar(*str); 118 | str++; 119 | } 120 | } 121 | 122 | void printInt(u32 n, int radix) { 123 | assert(radix >= 2 && radix <= 36); 124 | 125 | // Support up to base 36. 126 | const char numerals[36] = {'0', '1', '2', '3', '4', '5', '6', '7', 127 | '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 128 | 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 129 | 'Y', 'Z'}; 130 | 131 | // 32 chars max in the smallest base (binary) for 32-bit integers. 132 | char buffer[32]; 133 | 134 | if (n == 0) { 135 | printChar('0'); 136 | return; 137 | } 138 | 139 | // Keep track of how long the number is. 140 | int i = 0; 141 | 142 | // Fill the buffer with digits from least significant to most 143 | // significant (reverse digit order). 144 | while (n != 0) { 145 | buffer[i] = numerals[n % radix]; 146 | i++; 147 | n /= radix; 148 | } 149 | 150 | // Print the buffer in reverse order, since the digits are reversed. 151 | while (i != 0) { 152 | printChar(buffer[i - 1]); 153 | i--; 154 | } 155 | } 156 | 157 | } // namespace display 158 | -------------------------------------------------------------------------------- /src/memory.cpp: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "display.h" 3 | #include "memory.h" 4 | #include "multiboot.h" 5 | #include "util.h" 6 | 7 | namespace memory { 8 | 9 | // Defined by the linker (see linker.ld). The types and values of these 10 | // constants are arbitrary, but their addresses are important. 11 | extern "C" const int linker_kernelStart; 12 | extern "C" const int linker_kernelEnd; 13 | 14 | // The addresses of the start and end of the kernel in memory. 15 | const u32 KERNEL_START = reinterpret_cast(&linker_kernelStart); 16 | const u32 KERNEL_END = reinterpret_cast(&linker_kernelEnd); 17 | 18 | struct FreeInfo; 19 | 20 | // In every managed memory region, free or allocated, this structure is stored 21 | // at both the start and end. This enables finding the next or previous region 22 | // quickly. 23 | // 24 | // We will refer to the info at the start and end as the 'header' and 'footer' 25 | // respectively. 26 | struct Info { 27 | // TODO: Force `size` to be 2-byte aligned so we can use the least-significant 28 | // bit to store the isAllocated boolean and save space. 29 | u32 size; 30 | bool isAllocated; 31 | }; 32 | 33 | struct Footer; 34 | 35 | // Region info at the start of the region. 36 | struct Header : public Info { 37 | // If this region is free, return the associated FreeInfo which is 38 | // stored directly after this header. 39 | FreeInfo* freeInfo() const { 40 | assert(!isAllocated); 41 | // Calculate the address after the end of this structure. 42 | u32 addr = reinterpret_cast(this) + sizeof(*this); 43 | return reinterpret_cast(addr); 44 | } 45 | 46 | Footer* footer() const { 47 | u32 addr = reinterpret_cast(this) + sizeof(*this) + size; 48 | return reinterpret_cast(addr); 49 | } 50 | }; 51 | 52 | // Region info at the end of the region. 53 | struct Footer : public Info { 54 | Header* header() const { 55 | u32 addr = reinterpret_cast(this) - size - sizeof(Header); 56 | return reinterpret_cast(addr); 57 | } 58 | 59 | FreeInfo* freeInfo() const { 60 | return header()->freeInfo(); 61 | } 62 | }; 63 | 64 | // In free regions, this structure is stored just after the header. It links to 65 | // the nearest free memory regions before and after this one (specifically, to 66 | // their headers). 67 | struct FreeInfo { 68 | Header* prev; 69 | Header* next; 70 | }; 71 | 72 | Header* freeList = nullptr; 73 | 74 | Header* placeHeader(u32 addr, u32 size, bool isAllocated) { 75 | Header* h = reinterpret_cast(addr); 76 | h->size = size; 77 | h->isAllocated = isAllocated; 78 | return h; 79 | } 80 | 81 | Footer* placeFooter(u32 addr, u32 size, bool isAllocated) { 82 | Footer* f = reinterpret_cast(addr); 83 | f->size = size; 84 | f->isAllocated = isAllocated; 85 | return f; 86 | } 87 | 88 | // Make a contiguous region of usable memory available to the memory manager. 89 | // `startAddr` is inclusive and `endAddr` is exclusive. 90 | void initContiguousRegion(u32 startAddr, u32 endAddr) { 91 | // TODO: Move the lastAdded variable into the memory::init() function. 92 | static Header* lastAdded = nullptr; 93 | 94 | display::println("> initContiguousRegion({x}, {x})", startAddr, endAddr - 1); 95 | 96 | // Don't store anything at address zero or nullptr checks will go wrong. 97 | if (startAddr == 0) { 98 | startAddr += sizeof(Header); 99 | } 100 | 101 | // When coalescing adjacent free memory regions into a single large region, we 102 | // always examine the regions before and after the newly freed region, which 103 | // won't work when the first or last region of available memory is freed. 104 | // 105 | // As a work around, we store dummy Info structures at the start and end 106 | // of each contiguous usable memory region so coalescing knows where to stop. 107 | // 108 | // We must adjust the start and end points for the leftover region afterwards. 109 | placeHeader(startAddr, 0, true); 110 | startAddr += sizeof(Header); 111 | endAddr -= sizeof(Footer); 112 | placeFooter(endAddr, 0, true); 113 | 114 | // Set up the header and footer. 115 | u32 size = endAddr - startAddr - sizeof(Header) - sizeof(Footer); 116 | Header* header = placeHeader(startAddr, size, false); 117 | header->footer()->size = size; 118 | header->footer()->isAllocated = false; 119 | 120 | // Set up the free-region-specific info after the header and hook up the free 121 | // list links. 122 | header->freeInfo()->prev = lastAdded; 123 | header->freeInfo()->next = nullptr; 124 | 125 | if (lastAdded) { 126 | lastAdded->freeInfo()->next = header; 127 | } else { 128 | freeList = header; 129 | } 130 | 131 | lastAdded = header; 132 | } 133 | 134 | void init(u32 mmapAddr, u32 mmapLen) { 135 | u32 mmapCurr = mmapAddr; 136 | u32 mmapEnd = mmapAddr + mmapLen; 137 | 138 | display::println(""); 139 | while (mmapCurr < mmapEnd) { 140 | auto* mmapEntry = reinterpret_cast(mmapCurr); 141 | if (mmapEntry->type == 1) { 142 | u32 regionStart = mmapEntry->addr; 143 | u32 regionEnd = regionStart + mmapEntry->len; 144 | 145 | // Initialize the part of the region before the kernel, if it exists. 146 | if (regionStart < KERNEL_START) { 147 | initContiguousRegion(regionStart, min(KERNEL_START, regionEnd)); 148 | } 149 | 150 | // Initialize the part of the region after the kernel, if it exists. 151 | if (regionEnd > KERNEL_END) { 152 | initContiguousRegion(max(regionStart, KERNEL_END), regionEnd); 153 | } 154 | } 155 | 156 | display::print(mmapEntry->type == 1 ? "unused" : "used "); 157 | display::println(" 0x{x}\t- 0x{x}", mmapEntry->addr, 158 | mmapEntry->addr + mmapEntry->len - 1); 159 | 160 | mmapCurr += mmapEntry->size + sizeof(mmapEntry->size); 161 | } 162 | 163 | display::println("kernel start: 0x{x}", KERNEL_START); 164 | display::println("kernel end: 0x{x}", KERNEL_END); 165 | 166 | display::println(""); 167 | for (Header* h = freeList; h; h = h->freeInfo()->next) { 168 | assert(!h->isAllocated); 169 | assert(!h->footer()->isAllocated); 170 | assert(h->size == h->footer()->size); 171 | display::println("Header: 0x{x}", reinterpret_cast(h)); 172 | display::println("Footer: 0x{x}", reinterpret_cast(h->footer())); 173 | display::println("Size: {}", h->size); 174 | display::println(""); 175 | } 176 | } 177 | 178 | } // namespace memory 179 | -------------------------------------------------------------------------------- /src/keyboard.cpp: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "display.h" 3 | #include "keyboard.h" 4 | #include "ports.h" 5 | 6 | // TODO: Name the magic numbers in this file. 7 | 8 | namespace keyboard { 9 | 10 | // Mapping from scancodes to keys (the array is indexed by scancodes). 11 | const Key UNESCAPED_KEYS[] = { 12 | Key::UNKNOWN, // 0x00 13 | Key::ESCAPE, // 0x01 14 | Key::_1, // 0x02 15 | Key::_2, // 0x03 16 | Key::_3, // 0x04 17 | Key::_4, // 0x05 18 | Key::_5, // 0x06 19 | Key::_6, // 0x07 20 | Key::_7, // 0x08 21 | Key::_8, // 0x09 22 | Key::_9, // 0x0A 23 | Key::_0, // 0x0B 24 | Key::DASH, // 0x0C 25 | Key::EQUAL, // 0x0D 26 | Key::BACKSPACE, // 0x0E 27 | Key::TAB, // 0x0F 28 | Key::Q, // 0x10 29 | Key::W, // 0x11 30 | Key::E, // 0x12 31 | Key::R, // 0x13 32 | Key::T, // 0x14 33 | Key::Y, // 0x15 34 | Key::U, // 0x16 35 | Key::I, // 0x17 36 | Key::O, // 0x18 37 | Key::P, // 0x19 38 | Key::LEFT_SQUARE_BRACKET, // 0x1A 39 | Key::RIGHT_SQUARE_BRACKET, // 0x1B 40 | Key::ENTER, // 0x1C 41 | Key::LEFT_CONTROL, // 0x1D 42 | Key::A, // 0x1E 43 | Key::S, // 0x1F 44 | Key::D, // 0x20 45 | Key::F, // 0x21 46 | Key::G, // 0x22 47 | Key::H, // 0x23 48 | Key::J, // 0x24 49 | Key::K, // 0x25 50 | Key::L, // 0x26 51 | Key::SEMICOLON, // 0x27 52 | Key::SINGLE_QUOTE, // 0x28 53 | Key::BACKTICK, // 0x29 54 | Key::LEFT_SHIFT, // 0x2A 55 | Key::BACKSLASH, // 0x2B 56 | Key::Z, // 0x2C 57 | Key::X, // 0x2D 58 | Key::C, // 0x2E 59 | Key::V, // 0x2F 60 | Key::B, // 0x30 61 | Key::N, // 0x31 62 | Key::M, // 0x32 63 | Key::COMMA, // 0x33 64 | Key::PERIOD, // 0x34 65 | Key::SLASH, // 0x35 66 | Key::RIGHT_SHIFT, // 0x36 67 | Key::KEYPAD_MULTIPLY, // 0x37 68 | Key::LEFT_ALT, // 0x38 69 | Key::SPACE, // 0x39 70 | Key::CAPS_LOCK, // 0x3A 71 | Key::F1, // 0x3B 72 | Key::F2, // 0x3C 73 | Key::F3, // 0x3D 74 | Key::F4, // 0x3E 75 | Key::F5, // 0x3F 76 | Key::F6, // 0x40 77 | Key::F7, // 0x41 78 | Key::F8, // 0x42 79 | Key::F9, // 0x43 80 | Key::F10, // 0x44 81 | Key::NUM_LOCK, // 0x45 82 | Key::SCROLL_LOCK, // 0x46 83 | Key::KEYPAD_7, // 0x47 84 | Key::KEYPAD_8, // 0x48 85 | Key::KEYPAD_9, // 0x49 86 | Key::KEYPAD_SUBTRACT, // 0x4A 87 | Key::KEYPAD_4, // 0x4B 88 | Key::KEYPAD_5, // 0x4C 89 | Key::KEYPAD_6, // 0x4D 90 | Key::KEYPAD_ADD, // 0x4E 91 | Key::KEYPAD_1, // 0x4F 92 | Key::KEYPAD_2, // 0x50 93 | Key::KEYPAD_3, // 0x51 94 | Key::KEYPAD_0, // 0x52 95 | Key::KEYPAD_DECIMAL, // 0x53 96 | Key::UNKNOWN, // 0x54 97 | Key::UNKNOWN, // 0x55 98 | Key::UNKNOWN, // 0x56 99 | Key::F11, // 0x57 100 | Key::F12, // 0x58 101 | }; 102 | 103 | // Mapping from scancodes to keys (the array is indexed by scancodes) for 104 | // scancodes which come after the escape scancode. 105 | const Key ESCAPED_KEYS[] = { 106 | Key::UNKNOWN, // 0x00 107 | Key::UNKNOWN, // 0x01 108 | Key::UNKNOWN, // 0x02 109 | Key::UNKNOWN, // 0x03 110 | Key::UNKNOWN, // 0x04 111 | Key::UNKNOWN, // 0x05 112 | Key::UNKNOWN, // 0x06 113 | Key::UNKNOWN, // 0x07 114 | Key::UNKNOWN, // 0x08 115 | Key::UNKNOWN, // 0x09 116 | Key::UNKNOWN, // 0x0A 117 | Key::UNKNOWN, // 0x0B 118 | Key::UNKNOWN, // 0x0C 119 | Key::UNKNOWN, // 0x0D 120 | Key::UNKNOWN, // 0x0E 121 | Key::UNKNOWN, // 0x0F 122 | Key::UNKNOWN, // 0x10 123 | Key::UNKNOWN, // 0x11 124 | Key::UNKNOWN, // 0x12 125 | Key::UNKNOWN, // 0x13 126 | Key::UNKNOWN, // 0x14 127 | Key::UNKNOWN, // 0x15 128 | Key::UNKNOWN, // 0x16 129 | Key::UNKNOWN, // 0x17 130 | Key::UNKNOWN, // 0x18 131 | Key::UNKNOWN, // 0x19 132 | Key::UNKNOWN, // 0x1A 133 | Key::UNKNOWN, // 0x1B 134 | Key::KEYPAD_ENTER, // 0x1C 135 | Key::RIGHT_CONTROL, // 0x1D 136 | Key::UNKNOWN, // 0x1E 137 | Key::UNKNOWN, // 0x1F 138 | Key::UNKNOWN, // 0x20 139 | Key::UNKNOWN, // 0x21 140 | Key::UNKNOWN, // 0x22 141 | Key::UNKNOWN, // 0x23 142 | Key::UNKNOWN, // 0x24 143 | Key::UNKNOWN, // 0x25 144 | Key::UNKNOWN, // 0x26 145 | Key::UNKNOWN, // 0x27 146 | Key::UNKNOWN, // 0x28 147 | Key::UNKNOWN, // 0x29 148 | Key::UNKNOWN, // 0x2A 149 | Key::UNKNOWN, // 0x2B 150 | Key::UNKNOWN, // 0x2C 151 | Key::UNKNOWN, // 0x2D 152 | Key::UNKNOWN, // 0x2E 153 | Key::UNKNOWN, // 0x2F 154 | Key::UNKNOWN, // 0x30 155 | Key::UNKNOWN, // 0x31 156 | Key::UNKNOWN, // 0x32 157 | Key::UNKNOWN, // 0x33 158 | Key::UNKNOWN, // 0x34 159 | Key::KEYPAD_DIVIDE, // 0x35 160 | Key::UNKNOWN, // 0x36 161 | Key::UNKNOWN, // 0x37 162 | Key::RIGHT_ALT, // 0x38 163 | Key::UNKNOWN, // 0x39 164 | Key::UNKNOWN, // 0x3A 165 | Key::UNKNOWN, // 0x3B 166 | Key::UNKNOWN, // 0x3C 167 | Key::UNKNOWN, // 0x3D 168 | Key::UNKNOWN, // 0x3E 169 | Key::UNKNOWN, // 0x3F 170 | Key::UNKNOWN, // 0x40 171 | Key::UNKNOWN, // 0x41 172 | Key::UNKNOWN, // 0x42 173 | Key::UNKNOWN, // 0x43 174 | Key::UNKNOWN, // 0x44 175 | Key::UNKNOWN, // 0x45 176 | Key::UNKNOWN, // 0x46 177 | Key::HOME, // 0x47 178 | Key::UP, // 0x48 179 | Key::PAGE_UP, // 0x49 180 | Key::UNKNOWN, // 0x4A 181 | Key::LEFT, // 0x4B 182 | Key::UNKNOWN, // 0x4C 183 | Key::RIGHT, // 0x4D 184 | Key::UNKNOWN, // 0x4E 185 | Key::END, // 0x4F 186 | Key::DOWN, // 0x50 187 | Key::PAGE_DOWN, // 0x51 188 | Key::INSERT, // 0x52 189 | Key::DELETE, // 0x53 190 | Key::UNKNOWN, // 0x54 191 | Key::UNKNOWN, // 0x55 192 | Key::UNKNOWN, // 0x56 193 | Key::UNKNOWN, // 0x57 194 | Key::UNKNOWN, // 0x58 195 | Key::UNKNOWN, // 0x59 196 | Key::UNKNOWN, // 0x5A 197 | Key::LEFT_SUPER, // 0x5B 198 | Key::RIGHT_SUPER, // 0x5C 199 | Key::MENU, // 0x5D 200 | }; 201 | 202 | // To simplify the implementation, this ring buffer always keeps one slot 203 | // unused, so only `capacity - 1` items can be stored. 204 | template 205 | class RingQueue { 206 | public: 207 | void enqueue(const T& elem) { 208 | if (isFull()) { 209 | return; 210 | } 211 | data_[end_] = elem; 212 | end_ = (end_ + 1) % capacity; 213 | } 214 | 215 | T dequeue() { 216 | assert(!isEmpty()); 217 | T elem = data_[start_]; 218 | start_ = (start_ + 1) % capacity; 219 | return elem; 220 | } 221 | 222 | bool isFull() const { 223 | return (end_ + 1) % capacity == start_; 224 | } 225 | 226 | bool isEmpty() const { 227 | return start_ == end_; 228 | } 229 | private: 230 | u32 start_ = 0; 231 | u32 end_ = 0; 232 | T data_[capacity]; 233 | }; 234 | 235 | RingQueue scancodeQueue; 236 | 237 | void init() { 238 | interrupts::setIrqHandler(1, [](interrupts::Registers*) { 239 | scancodeQueue.enqueue(ports::inb(0x60)); 240 | }); 241 | flushBuffer(); 242 | } 243 | 244 | u8 readScancode() { 245 | // TODO: Find a way to wait that isn't busy-waiting. 246 | while (scancodeQueue.isEmpty()) {} 247 | return scancodeQueue.dequeue(); 248 | } 249 | 250 | // TODO: Handle the special case keys Print Screen and Pause. 251 | KeyEvent readEvent() { 252 | const int ESCAPE_CODE = 0xE0; 253 | static bool nextScancodeIsEscaped = false; 254 | 255 | // Persistant storage for the state of the various modifier and toggle keys. 256 | static bool leftAlt = false; 257 | static bool rightAlt = false; 258 | static bool leftControl = false; 259 | static bool rightControl = false; 260 | static bool leftShift = false; 261 | static bool rightShift = false; 262 | static bool leftSuper = false; 263 | static bool rightSuper = false; 264 | static bool capsLock = false; 265 | static bool numLock = false; 266 | static bool scrollLock = false; 267 | 268 | u8 scancode = readScancode(); 269 | 270 | if (scancode == ESCAPE_CODE) { 271 | nextScancodeIsEscaped = true; 272 | scancode = readScancode(); 273 | } 274 | 275 | KeyEvent event; 276 | 277 | // If the seventh bit is set, the scancode represents a key release, otherwise 278 | // it's a key press. 279 | bool isPress = !(scancode & (1 << 7)); 280 | event.action = isPress ? KeyEvent::DOWN : KeyEvent::UP; 281 | 282 | // Clear the seventh bit before indexing into the key code tables below. 283 | scancode &= ~(1 << 7); 284 | 285 | if (nextScancodeIsEscaped) { 286 | nextScancodeIsEscaped = false; 287 | event.key = ESCAPED_KEYS[scancode]; 288 | } else { 289 | event.key = UNESCAPED_KEYS[scancode]; 290 | } 291 | 292 | // Turn modifiers on or off based on whether this is a press or release. 293 | switch (event.key) { 294 | case Key::LEFT_ALT: leftAlt = isPress; break; 295 | case Key::RIGHT_ALT: rightAlt = isPress; break; 296 | case Key::LEFT_CONTROL: leftControl = isPress; break; 297 | case Key::RIGHT_CONTROL: rightControl = isPress; break; 298 | case Key::LEFT_SHIFT: leftShift = isPress; break; 299 | case Key::RIGHT_SHIFT: rightShift = isPress; break; 300 | case Key::LEFT_SUPER: leftSuper = isPress; break; 301 | case Key::RIGHT_SUPER: rightSuper = isPress; break; 302 | default: break; 303 | } 304 | 305 | // Switch the toggles if this is a key press, not a release. 306 | if (isPress) { 307 | switch (event.key) { 308 | case Key::CAPS_LOCK: capsLock = !capsLock; break; 309 | case Key::NUM_LOCK: numLock = !numLock; break; 310 | case Key::SCROLL_LOCK: scrollLock = !scrollLock; break; 311 | default: break; 312 | } 313 | } 314 | 315 | event.shift = leftShift || rightShift; 316 | event.control = leftControl || rightControl; 317 | event.alt = leftAlt || rightAlt; 318 | event.super = leftSuper || rightSuper; 319 | event.capsLock = capsLock; 320 | event.numLock = numLock; 321 | event.scrollLock = scrollLock; 322 | 323 | if (isPress) { 324 | if (event.shift) { 325 | event.character = upperCaseChar(event.key); 326 | } else { 327 | event.character = lowerCaseChar(event.key); 328 | } 329 | } else { 330 | event.character = '\0'; 331 | } 332 | 333 | return event; 334 | } 335 | 336 | char readChar() { 337 | KeyEvent event; 338 | do { 339 | event = readEvent(); 340 | } while (!event.character); 341 | return event.character; 342 | } 343 | 344 | // Flush the keyboard buffer. 345 | void flushBuffer() { 346 | while(ports::inb(0x64) & 1) { 347 | ports::inb(0x60); 348 | } 349 | } 350 | 351 | const char* keyName(Key key) { 352 | switch (key) { 353 | case Key::UNKNOWN: return ""; 354 | case Key::_1: return "1"; 355 | case Key::_2: return "2"; 356 | case Key::_3: return "3"; 357 | case Key::_4: return "4"; 358 | case Key::_5: return "5"; 359 | case Key::_6: return "6"; 360 | case Key::_7: return "7"; 361 | case Key::_8: return "8"; 362 | case Key::_9: return "9"; 363 | case Key::_0: return "0"; 364 | case Key::A: return "A"; 365 | case Key::B: return "B"; 366 | case Key::C: return "C"; 367 | case Key::D: return "D"; 368 | case Key::E: return "E"; 369 | case Key::F: return "F"; 370 | case Key::G: return "G"; 371 | case Key::H: return "H"; 372 | case Key::I: return "I"; 373 | case Key::J: return "J"; 374 | case Key::K: return "K"; 375 | case Key::L: return "L"; 376 | case Key::M: return "M"; 377 | case Key::N: return "N"; 378 | case Key::O: return "O"; 379 | case Key::P: return "P"; 380 | case Key::Q: return "Q"; 381 | case Key::R: return "R"; 382 | case Key::S: return "S"; 383 | case Key::T: return "T"; 384 | case Key::U: return "U"; 385 | case Key::V: return "V"; 386 | case Key::W: return "W"; 387 | case Key::X: return "X"; 388 | case Key::Y: return "Y"; 389 | case Key::Z: return "Z"; 390 | case Key::TAB: return "Tab"; 391 | case Key::SPACE: return "Space"; 392 | case Key::DASH: return "-"; 393 | case Key::EQUAL: return "="; 394 | case Key::BACKSPACE: return "Backspace"; 395 | case Key::ENTER: return "Enter"; 396 | case Key::SEMICOLON: return ";"; 397 | case Key::SINGLE_QUOTE: return "'"; 398 | case Key::BACKTICK: return "`"; 399 | case Key::BACKSLASH: return "\\"; 400 | case Key::COMMA: return ","; 401 | case Key::PERIOD: return "."; 402 | case Key::SLASH: return "/"; 403 | case Key::LEFT_SQUARE_BRACKET: return "["; 404 | case Key::RIGHT_SQUARE_BRACKET: return "]"; 405 | case Key::KEYPAD_MULTIPLY: return "Keypad *"; 406 | case Key::KEYPAD_DIVIDE: return "Keypad /"; 407 | case Key::KEYPAD_ADD: return "Keypad +"; 408 | case Key::KEYPAD_SUBTRACT: return "Keypad -"; 409 | case Key::KEYPAD_DECIMAL: return "Keypad ."; 410 | case Key::KEYPAD_ENTER: return "Keypad Enter"; 411 | case Key::KEYPAD_1: return "Keypad 1"; 412 | case Key::KEYPAD_2: return "Keypad 2"; 413 | case Key::KEYPAD_3: return "Keypad 3"; 414 | case Key::KEYPAD_4: return "Keypad 4"; 415 | case Key::KEYPAD_5: return "Keypad 5"; 416 | case Key::KEYPAD_6: return "Keypad 6"; 417 | case Key::KEYPAD_7: return "Keypad 7"; 418 | case Key::KEYPAD_8: return "Keypad 8"; 419 | case Key::KEYPAD_9: return "Keypad 9"; 420 | case Key::KEYPAD_0: return "Keypad 0"; 421 | case Key::F1: return "F1"; 422 | case Key::F2: return "F2"; 423 | case Key::F3: return "F3"; 424 | case Key::F4: return "F4"; 425 | case Key::F5: return "F5"; 426 | case Key::F6: return "F6"; 427 | case Key::F7: return "F7"; 428 | case Key::F8: return "F8"; 429 | case Key::F9: return "F9"; 430 | case Key::F10: return "F10"; 431 | case Key::F11: return "F11"; 432 | case Key::F12: return "F12"; 433 | case Key::CAPS_LOCK: return "Caps Lock"; 434 | case Key::NUM_LOCK: return "Num Lock"; 435 | case Key::SCROLL_LOCK: return "Scroll Lock"; 436 | case Key::ESCAPE: return "Escape"; 437 | case Key::PRINT_SCREEN: return "Print Screen"; 438 | case Key::PAUSE: return "Pause"; 439 | case Key::MENU: return "Menu"; 440 | case Key::LEFT_SHIFT: return "Left Shift"; 441 | case Key::RIGHT_SHIFT: return "Right Shift"; 442 | case Key::LEFT_ALT: return "Left Alt"; 443 | case Key::RIGHT_ALT: return "Right Alt"; 444 | case Key::LEFT_CONTROL: return "Left Control"; 445 | case Key::RIGHT_CONTROL: return "Right Control"; 446 | case Key::LEFT_SUPER: return "Left Super"; 447 | case Key::RIGHT_SUPER: return "Right Super"; 448 | case Key::HOME: return "Home"; 449 | case Key::END: return "End"; 450 | case Key::INSERT: return "Insert"; 451 | case Key::DELETE: return "Delete"; 452 | case Key::PAGE_UP: return "Page Up"; 453 | case Key::PAGE_DOWN: return "Page Down"; 454 | case Key::UP: return "Up"; 455 | case Key::DOWN: return "Down"; 456 | case Key::LEFT: return "Left"; 457 | case Key::RIGHT: return "Right"; 458 | default: return ""; 459 | } 460 | } 461 | 462 | char lowerCaseChar(Key key) { 463 | switch (key) { 464 | case Key::_1: return '1'; 465 | case Key::_2: return '2'; 466 | case Key::_3: return '3'; 467 | case Key::_4: return '4'; 468 | case Key::_5: return '5'; 469 | case Key::_6: return '6'; 470 | case Key::_7: return '7'; 471 | case Key::_8: return '8'; 472 | case Key::_9: return '9'; 473 | case Key::_0: return '0'; 474 | case Key::A: return 'a'; 475 | case Key::B: return 'b'; 476 | case Key::C: return 'c'; 477 | case Key::D: return 'd'; 478 | case Key::E: return 'e'; 479 | case Key::F: return 'f'; 480 | case Key::G: return 'g'; 481 | case Key::H: return 'h'; 482 | case Key::I: return 'i'; 483 | case Key::J: return 'j'; 484 | case Key::K: return 'k'; 485 | case Key::L: return 'l'; 486 | case Key::M: return 'm'; 487 | case Key::N: return 'n'; 488 | case Key::O: return 'o'; 489 | case Key::P: return 'p'; 490 | case Key::Q: return 'q'; 491 | case Key::R: return 'r'; 492 | case Key::S: return 's'; 493 | case Key::T: return 't'; 494 | case Key::U: return 'u'; 495 | case Key::V: return 'v'; 496 | case Key::W: return 'w'; 497 | case Key::X: return 'x'; 498 | case Key::Y: return 'y'; 499 | case Key::Z: return 'z'; 500 | case Key::TAB: return '\t'; 501 | case Key::SPACE: return ' '; 502 | case Key::DASH: return '-'; 503 | case Key::EQUAL: return '='; 504 | case Key::BACKSPACE: return '\b'; 505 | case Key::ENTER: return '\n'; 506 | case Key::SEMICOLON: return ';'; 507 | case Key::SINGLE_QUOTE: return '\''; 508 | case Key::BACKTICK: return '`'; 509 | case Key::BACKSLASH: return '\\'; 510 | case Key::COMMA: return ','; 511 | case Key::PERIOD: return '.'; 512 | case Key::SLASH: return '/'; 513 | case Key::LEFT_SQUARE_BRACKET: return '['; 514 | case Key::RIGHT_SQUARE_BRACKET: return ']'; 515 | case Key::KEYPAD_MULTIPLY: return '*'; 516 | case Key::KEYPAD_DIVIDE: return '/'; 517 | case Key::KEYPAD_ADD: return '+'; 518 | case Key::KEYPAD_SUBTRACT: return '-'; 519 | case Key::KEYPAD_DECIMAL: return '.'; 520 | case Key::KEYPAD_ENTER: return '\n'; 521 | case Key::KEYPAD_1: return '1'; 522 | case Key::KEYPAD_2: return '2'; 523 | case Key::KEYPAD_3: return '3'; 524 | case Key::KEYPAD_4: return '4'; 525 | case Key::KEYPAD_5: return '5'; 526 | case Key::KEYPAD_6: return '6'; 527 | case Key::KEYPAD_7: return '7'; 528 | case Key::KEYPAD_8: return '8'; 529 | case Key::KEYPAD_9: return '9'; 530 | case Key::KEYPAD_0: return '0'; 531 | default: return '\0'; 532 | } 533 | } 534 | 535 | char upperCaseChar(Key key) { 536 | switch (key) { 537 | case Key::_1: return '!'; 538 | case Key::_2: return '@'; 539 | case Key::_3: return '#'; 540 | case Key::_4: return '$'; 541 | case Key::_5: return '%'; 542 | case Key::_6: return '^'; 543 | case Key::_7: return '&'; 544 | case Key::_8: return '*'; 545 | case Key::_9: return '('; 546 | case Key::_0: return ')'; 547 | case Key::A: return 'A'; 548 | case Key::B: return 'B'; 549 | case Key::C: return 'C'; 550 | case Key::D: return 'D'; 551 | case Key::E: return 'E'; 552 | case Key::F: return 'F'; 553 | case Key::G: return 'G'; 554 | case Key::H: return 'H'; 555 | case Key::I: return 'I'; 556 | case Key::J: return 'J'; 557 | case Key::K: return 'K'; 558 | case Key::L: return 'L'; 559 | case Key::M: return 'M'; 560 | case Key::N: return 'N'; 561 | case Key::O: return 'O'; 562 | case Key::P: return 'P'; 563 | case Key::Q: return 'Q'; 564 | case Key::R: return 'R'; 565 | case Key::S: return 'S'; 566 | case Key::T: return 'T'; 567 | case Key::U: return 'U'; 568 | case Key::V: return 'V'; 569 | case Key::W: return 'W'; 570 | case Key::X: return 'X'; 571 | case Key::Y: return 'Y'; 572 | case Key::Z: return 'Z'; 573 | case Key::TAB: return '\t'; 574 | case Key::SPACE: return ' '; 575 | case Key::DASH: return '_'; 576 | case Key::EQUAL: return '+'; 577 | case Key::BACKSPACE: return '\b'; 578 | case Key::ENTER: return '\n'; 579 | case Key::SEMICOLON: return ':'; 580 | case Key::SINGLE_QUOTE: return '"'; 581 | case Key::BACKTICK: return '~'; 582 | case Key::BACKSLASH: return '|'; 583 | case Key::COMMA: return '<'; 584 | case Key::PERIOD: return '>'; 585 | case Key::SLASH: return '?'; 586 | case Key::LEFT_SQUARE_BRACKET: return '{'; 587 | case Key::RIGHT_SQUARE_BRACKET: return '}'; 588 | case Key::KEYPAD_MULTIPLY: return '*'; 589 | case Key::KEYPAD_DIVIDE: return '/'; 590 | case Key::KEYPAD_ADD: return '+'; 591 | case Key::KEYPAD_SUBTRACT: return '-'; 592 | case Key::KEYPAD_DECIMAL: return '.'; 593 | case Key::KEYPAD_ENTER: return '\n'; 594 | case Key::KEYPAD_1: return '1'; 595 | case Key::KEYPAD_2: return '2'; 596 | case Key::KEYPAD_3: return '3'; 597 | case Key::KEYPAD_4: return '4'; 598 | case Key::KEYPAD_5: return '5'; 599 | case Key::KEYPAD_6: return '6'; 600 | case Key::KEYPAD_7: return '7'; 601 | case Key::KEYPAD_8: return '8'; 602 | case Key::KEYPAD_9: return '9'; 603 | case Key::KEYPAD_0: return '0'; 604 | default: return '\0'; 605 | } 606 | } 607 | 608 | } // namespace keyboard 609 | --------------------------------------------------------------------------------