├── .bochsrc ├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── allocfree.py ├── build ├── buildfuncs.sh ├── common.mk ├── makebuffer.mk ├── makejobs.mk └── mkgrubcfg.sh ├── cuser ├── acpi_debugger.c ├── acpica │ ├── acenv_header.h │ ├── acpica.c │ ├── acpica.h │ ├── interrupts.c │ ├── malloc.c │ ├── osl.c │ ├── pci.c │ └── printf.c ├── apic.c ├── bochsvga.c ├── common.h ├── e1000.c ├── fbtest.c ├── helloworld.c ├── include │ ├── __decls.h │ ├── assert.h │ ├── ctype.h │ ├── msg_con.h │ ├── msg_ethernet.h │ ├── msg_fb.h │ ├── msg_irq.h │ ├── msg_syscalls.h │ ├── msg_timer.h │ ├── sb1.h │ ├── stdint.h │ ├── stdio.h │ ├── stdlib.h │ └── string.h ├── ioapic.c ├── libc │ ├── acpi_strtoul.c │ ├── ctype.c │ ├── printf.asm │ ├── stdio.c │ ├── stdio_raw.c │ ├── stdlib.c │ └── string.c ├── linker.ld ├── lwip │ ├── arch │ │ ├── cc.h │ │ ├── perf.h │ │ └── sys_arch.h │ ├── http.c │ ├── http.h │ ├── lwipopts.h │ └── main.c ├── msg_acpi.h ├── test_maps.c ├── timer_test.c └── zeropage.c ├── grub ├── .gitignore └── build.sh ├── include ├── common.inc ├── macros.inc ├── mboot.inc ├── module.inc ├── msr.inc ├── pic.inc ├── printf.inc ├── segments.inc └── string.inc ├── kasm ├── aspace.inc ├── kstart.asm ├── messages.inc ├── messages_con.inc ├── messages_io.inc ├── messages_irq.inc ├── pages.inc ├── probes.inc ├── proc.inc ├── sections.inc ├── start32.asm └── start32.inc ├── kcpp ├── Makefile ├── TODO ├── aspace.h ├── cpu.h ├── dict.h ├── dlist.h ├── handle.h ├── linker.ld ├── main.cc ├── mboot.h ├── mem.h ├── mkgrubcfg.sh ├── proc.h ├── refcnt.h ├── run.sh ├── runtime.s ├── start32.o ├── string.c ├── syscall.asm ├── syscall.h ├── test.asm └── xprintf.cpp ├── kern ├── console.asm ├── irq.asm ├── keymap.inc ├── pic.asm ├── portio.inc └── putchar.inc ├── notes ├── bochs.txt └── slirp.conf ├── run_qemu.sh ├── test ├── test_common.h ├── testlib.py └── tests.py ├── toolchain ├── .gitignore ├── build.sh └── clean.sh ├── user ├── loop.asm ├── newproc.asm ├── putchar.inc ├── shell.asm ├── test_puts.asm └── test_xmm.asm └── utils ├── cpuid.cpp └── rflags.c /.bochsrc: -------------------------------------------------------------------------------- 1 | # configuration file generated by Bochs 2 | plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, parallel=1, serial=1, iodebug=1, e1000=1 3 | config_interface: textconfig 4 | memory: host=32, guest=32 5 | boot: cdrom 6 | floppy_bootsig_check: disabled=0 7 | # no floppya 8 | # no floppyb 9 | ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 10 | ata0-master: type=none 11 | ata0-slave: type=none 12 | ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15 13 | ata1-master: type=cdrom, path="out/grub.iso", status=inserted, model="Generic 1234", biosdetect=auto 14 | ata1-slave: type=none 15 | ata2: enabled=0 16 | ata3: enabled=0 17 | pci: enabled=1, chipset=i440fx, slot1=pcivga 18 | vga: extension=vbe, update_freq=5 19 | cpu: count=1, ips=400000000, model=bx_generic, reset_on_triple_fault=0, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 20 | cpuid: level=6, stepping=3, model=3, family=6, vendor_string="GenuineIntel", brand_string=" Intel(R) Pentium(R) 4 CPU " 21 | cpuid: mmx=1, apic=xapic, sse4a=0, misaligned_sse=0, sep=1, movbe=0, adx=0 22 | cpuid: aes=0, xsave=1, xsaveopt=1, x86_64=1, 1g_pages=1, pcid=0, fsgsbase=0, smep=0 23 | cpuid: smap=0, mwait=1 24 | cpuid: simd=avx 25 | print_timestamps: enabled=0 26 | debugger_log: - 27 | magic_break: enabled=0 28 | port_e9_hack: enabled=1 29 | private_colormap: enabled=0 30 | clock: sync=both, time0=local, rtc_sync=1 31 | # no cmosimage 32 | # no loader 33 | log: - 34 | logprefix: %t%e%d 35 | debug: action=ignore, E1000=report 36 | info: action=report 37 | error: action=report 38 | panic: action=ask 39 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 40 | mouse: type=ps2, enabled=0, toggle=ctrl+mbutton 41 | parport1: enabled=1, file=none 42 | parport2: enabled=0 43 | com1: enabled=1, mode=null, dev=none 44 | com2: enabled=0 45 | com3: enabled=0 46 | com4: enabled=0 47 | # Uncomment to use tun/tap networking (and comment out the next line) 48 | # e1000: enabled=1, mac=fe:fd:de:ad:be:ef, ethmod=tuntap, ethdev="/dev/net/tun:tap0", script=none, bootrom=none 49 | e1000: enabled=1, mac=fe:fd:de:ad:be:ef, ethmod=slirp, script="notes/slirp.conf" 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # For this to work, git config diff.readelf.textconv "readelf -a" 2 | *.o diff=readelf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | /out/ 3 | /kcpp/out/ 4 | /utils/rflags 5 | /utils/cpuid 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "yasm"] 2 | path = yasm 3 | url = git://github.com/olsner/yasm.git 4 | ignore = untracked 5 | [submodule "acpica"] 6 | path = acpica 7 | url = git://github.com/olsner/acpica.git 8 | [submodule "lwip"] 9 | path = lwip 10 | url = git://github.com/olsner/lwip.git 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Simon Brenner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | "os" (proper name pending) 2 | ========================== 3 | 4 | An x86-64 µkernel in Assembly. User-space parts in Assembly and C. 5 | 6 | ## License ## 7 | 8 | This software is licensed under the MIT license. See the LICENSE file. 9 | 10 | ## Building the cross-toolchain ## 11 | 12 | Scripts for building a cross compiler can be found in `toolchain/`. Running 13 | `./build.sh` in that directory will download the tarballs (if necessary), 14 | verify the signatures, unpack, configure and build. 15 | 16 | ## Cross-compiling grub ## 17 | 18 | On Linux systems, using the system grub is usually fine, but to support more 19 | esoteric operating systems (e.g. macOS), grub needs to be cross-compiled with 20 | an ELF toolchain. Use the `./build.sh` script in `grub/` to build one after 21 | building the toolchain. 22 | 23 | `grub-mkrescue` also requires `xorriso`. 24 | 25 | ## Building ## 26 | 27 | After building the cross-compiler and cross-grub, just run `make` to build the 28 | kernel(s) and user-space components. ccache is used by default, but can be 29 | disabled by setting `CCACHE=` on the Make command line. (Just install ccache 30 | though. Srsly.) 31 | 32 | ## Network setup ## 33 | 34 | ### User-mode networking ### 35 | 36 | The simplest setup and the default in run_qemu.sh. QEMU presents a virtual 37 | network with an internal DHCP server and something similar to NAT. Port 5555 on 38 | localhost is redirected to port 80 on the virtual machine, which is where the 39 | lwIP web server is listening. 40 | 41 | ### Setting up tun/tap networking ### 42 | 43 | Useful when working on low-level network stuff, since it allows easy 44 | wiresharking of all traffic over the simulated network, and makes the VM 45 | available to all the normal network debugging tools (arping, ping, etc). 46 | 47 | To create a tun/tap device owned by yourself, 48 | 49 | * Install uml-utilities (for tunctl), 50 | * run `sudo tunctl -u $(whoami)`, this will print the name of the created 51 | device, which should be tap0 (which is hardcoded in various places) 52 | * then `sudo ifconfig tap0 up 192.168.100.1` to set the IP 53 | * modify the lwIP program for a static IP 54 | * modify run_qemu.sh or bochsrc to use the tun/tap networking 55 | 56 | The lwIP program needs to be modified to set up a static IP at 192.168.100.3, 57 | but after that it should present the dumb web server on that IP. 58 | 59 | ## Running in QEMU ## 60 | 61 | To run in qemu, there's a wrapper script in ./run_qemu.sh that automatically 62 | runs make and starts qemu. 63 | 64 | ## Running in Bochs ## 65 | 66 | See notes/bochs.txt for build instructions for bochs 2.6. 67 | 68 | Build everything as above, then start bochs using `bochs` from the repo root. A 69 | .bochsrc with most of the necessary settings is provided. 70 | 71 | ## Known issues ## 72 | 73 | Only unknown issues left. Yay! 74 | -------------------------------------------------------------------------------- /allocfree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Shell snippet to process logs for parsing by this script: 4 | # 1. remove the initial memory test printouts 5 | # 2. remove escape sequences (and partial ones?) 6 | # 3. filter out the relevant lines 7 | # cat log2 | tail -n +7574 | sed 's/\x1b\[[^m]*m//g' | sed 's/\x1b.*$//g' | grep '\(allocate\|free\)_frame' > allocfree2.txt 8 | 9 | import sys 10 | 11 | last = {} 12 | counts = {} 13 | 14 | def int_(s): 15 | if type(s) is int: return s 16 | if '=' in s: 17 | s = s.split('=', 1)[1] 18 | return int(s, 16) 19 | 20 | for line in sys.stdin: 21 | try: 22 | if '_frame' not in line: 23 | continue 24 | line_ = line.split('_frame', 1)[1] 25 | fs = line_.split() 26 | if "free_frame" in line: 27 | addr = fs[0] 28 | rip = 0 29 | act = 0 30 | else: 31 | addr = fs[1] 32 | rip = fs[0] 33 | act = 1 34 | addr = int_(addr) 35 | rip = int_(rip) 36 | last[addr] = (rip,act) 37 | #print "%d %#x" % (act, addr) 38 | counts[(addr,act)] = counts.setdefault((addr,act), 0) + 1 39 | except Exception, e: 40 | print >>sys.stderr, e, "in wonky line "+repr(line)+" "+repr(line_)+" "+repr(fs) 41 | raise 42 | 43 | for addr,(rip,act) in last.iteritems(): 44 | print "%d %s addr=%#x rip=%#x" % (act, 'alloc' if act == 1 else 'free', addr, rip) 45 | 46 | #for count,addr in sorted([(count,addr) for addr,count in counts.iteritems()]): 47 | # print "%8d %r" % (count, addr) 48 | -------------------------------------------------------------------------------- /build/buildfuncs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET=x86_64-elf 4 | SYSTEM=`uname -s` 5 | if [ -z "$NPROC" ]; then 6 | if [ "$SYSTEM" = Darwin ]; then 7 | NPROC=`sysctl -n hw.activecpu` 8 | else 9 | NPROC=`nproc` 10 | fi 11 | fi 12 | 13 | die() { 14 | echo >&2 "$@" 15 | exit 1 16 | } 17 | 18 | GET() { 19 | wget -nc -c "$1/$2" 20 | sha256sum -c <<<"$3 $2" 21 | } 22 | 23 | unpack() { 24 | if [ ! -d "$1" ]; then 25 | echo "Unpacking $1..." 26 | if [ ! -f "${1}.tar"* ]; then 27 | return 1 28 | fi 29 | if ! tar -xf "${1}.tar"*; then 30 | rm -fr "$1" 31 | return 1 32 | fi 33 | fi 34 | if [ -n "$2" ]; then 35 | ln -sfT "$1" "$2" 36 | fi 37 | } 38 | 39 | log() { 40 | echo "$@" 41 | test -n "$logging" && eval 'echo "$@" '"$logging" 42 | } 43 | 44 | r() { 45 | log "RUNNING: $@ [in `pwd`]" 46 | local res=0 47 | eval '"$@" '"$logging" || res=$? 48 | if [ $res -ne 0 ]; then 49 | log "FAILED: $@" 50 | if [ -n "$logfile" ]; then 51 | log "[ logs in $logfile ]" 52 | fi 53 | fi 54 | return $res 55 | } 56 | 57 | setlog() { 58 | logfile="$LOGDIR/${1}.log" 59 | :> "$logfile" 60 | logging=">>$logfile 2>&1" 61 | } 62 | clearlog() { 63 | logging= 64 | logfile= 65 | } 66 | 67 | CONFIGURE() { 68 | local dir="$1" 69 | shift 70 | echo "$@" > config_args.tmp 71 | if [ -f Makefile ] && ! cmp -s config_args config_args.tmp; then 72 | echo "Configure arguments changed! Rerunning configure." 73 | rm -f Makefile 74 | fi 75 | mv config_args.tmp config_args 76 | if [ ! -f Makefile ]; then 77 | r "../$dir/configure" "$@" 78 | fi 79 | } 80 | MAKE() { 81 | r make -j$NPROC "$@" 82 | } 83 | -------------------------------------------------------------------------------- /build/common.mk: -------------------------------------------------------------------------------- 1 | SYSTEM := $(shell uname -s) 2 | 3 | ifeq ($(SYSTEM), Darwin) 4 | FILE_SIZE = stat -f%z 5 | else 6 | FILE_SIZE = stat -c%s 7 | endif 8 | 9 | # Default is not verbose, i.e. VERBOSE is empty. 10 | ifeq ($(VERBOSE),YES) 11 | CP=cp -v 12 | else 13 | CP=cp 14 | endif 15 | 16 | ifneq ($(VERBOSE),YES) 17 | HUSH_AS = @echo -e ' [AS]\t'$@; 18 | HUSH_ASM = @echo -e ' [ASM]\t'$@; 19 | # Not useful to know about really - implied by CC/CXX so might as well be implied by AS/ASM too 20 | #HUSH_ASM_DEP=@echo -e ' [DEP]\t'$@; 21 | HUSH_ASM_DEP= @ 22 | HUSH_CC = @echo -e ' [CC]\t'$@; 23 | HUSH_CXX = @echo -e ' [CXX]\t'$@; 24 | HUSH_LD = @echo -e ' [LD]\t'$@; 25 | HUSH_OBJCOPY= @echo -e ' [OBJCOPY]\t'$@; 26 | 27 | SIZE_ASM=@echo -e ' [ASM]\t'$@: `$(FILE_SIZE) $@` bytes 28 | SIZE_LD= @echo -e ' [LD]\t'$@: `$(FILE_SIZE) $@` bytes 29 | SIZE_OBJCOPY= @echo -e ' [OBJCOPY]\t'$@: `$(FILE_SIZE) $@` bytes 30 | endif 31 | 32 | ifeq ($(LTO), YES) 33 | HUSH_LD_LTO = +$(HUSH_LD) 34 | else 35 | HUSH_LD_LTO = $(HUSH_LD) 36 | endif 37 | -------------------------------------------------------------------------------- /build/makebuffer.mk: -------------------------------------------------------------------------------- 1 | # TODO Check for a GNU Make recent enough to have this option 2 | MAKEFLAGS += --output-sync 3 | -------------------------------------------------------------------------------- /build/makejobs.mk: -------------------------------------------------------------------------------- 1 | # Prevent setting -jN for submakes - we should let the jobserver handle it. 2 | ifneq ($(DID_SET_MAKEJOBS), YES) 3 | # Set JOBS to NO to disable this code (e.g. when making recursively), set JOBS 4 | # to another value to pass it to -j$(JOBS), or leave unset to default to the 5 | # number of processors. 6 | ifneq (NO,$(JOBS)) 7 | ifeq ($(SYSTEM), Darwin) 8 | NPROC := $(shell sysctl -n hw.activecpu) 9 | else 10 | NPROC := $(shell nproc) 11 | endif 12 | JOBS ?= $(NPROC) 13 | MAKEFLAGS += -j$(JOBS) 14 | export DID_SET_MAKEJOBS=YES 15 | export JOBS:=$(JOBS) 16 | endif 17 | endif 18 | -------------------------------------------------------------------------------- /build/mkgrubcfg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkgrubcfg() { 4 | local kernel="$1" 5 | local menuname="$2" 6 | shift 7 | shift 8 | echo "submenu \"$menuname\" {" 9 | mkgrubcfg1 "$kernel" "$@" 10 | echo "}" 11 | } 12 | 13 | mkgrubcfg1() { 14 | local kernel="$1" 15 | shift 16 | 17 | cat < 2 | #include 3 | 4 | #define ACPI_MACHINE_WIDTH __INTPTR_WIDTH__ 5 | #define ACPI_SINGLE_THREADED 6 | #define ACPI_USE_LOCAL_CACHE 7 | #define ACPI_USE_NATIVE_DIVIDE 8 | #define ACPI_USE_SYSTEM_CLIBRARY 9 | #define ACPI_USE_STANDARD_HEADERS 10 | 11 | #define DEBUGGER_THREADING DEBUGGER_SINGLE_THREADED 12 | 13 | // Doesn't seem to be possible to build without this enabled? Are you supposed 14 | // to remove the debugger source files when disabling it? 15 | #define ACPI_FULL_DEBUG 16 | #ifdef ACPI_FULL_DEBUG 17 | #define ACPI_DEBUG_OUTPUT 18 | #define ACPI_DEBUGGER 1 19 | #define ACPI_DISASSEMBLER 1 20 | #endif 21 | 22 | #undef ACPI_GET_FUNCTION_NAME 23 | #ifdef ACPI_FULL_DEBUG 24 | #define ACPI_DBG_TRACK_ALLOCATIONS 25 | #define ACPI_GET_FUNCTION_NAME __FUNCTION__ 26 | #else 27 | #define ACPI_GET_FUNCTION_NAME "" 28 | #endif 29 | 30 | static const uint64_t ACPI_PHYS_BASE = 0x100000000; 31 | 32 | // Prevent ACPI from redeclaring printf 33 | // (without -ffreestanding, that will trigger -Wbuiltin-declaration-mismatch) 34 | #define ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsPrintf 35 | #define ACPI_USE_ALTERNATE_PROTOTYPE_AcpiOsVprintf 36 | #define AcpiOsPrintf printf 37 | #define AcpiOsVprintf vprintf 38 | 39 | struct acpi_table_facs; 40 | uint32_t AcpiOsReleaseGlobalLock(struct acpi_table_facs* facs); 41 | uint32_t AcpiOsAcquireGlobalLock(struct acpi_table_facs* facs); 42 | 43 | #define ACPI_ACQUIRE_GLOBAL_LOCK(GLptr, Acquired) Acquired = AcpiOsAcquireGlobalLock(GLptr) 44 | #define ACPI_RELEASE_GLOBAL_LOCK(GLptr, Pending) Pending = AcpiOsReleaseGlobalLock(GLptr) 45 | 46 | void AcpiOsFlushCache(void); 47 | #define ACPI_FLUSH_CPU_CACHE AcpiOsFlushCache 48 | 49 | #define COMPILER_DEPENDENT_UINT64 uint64_t 50 | #define COMPILER_DEPENDENT_UINT32 uint32_t 51 | 52 | // ACPICA by default defines this to void*, so define this to the proper type. 53 | // Note that using uintptr_t instead of void* actually hides a lot of constness 54 | // issues in ACPICA code. So arguably it'd be better to leave this defined to 55 | // the wrong type... 56 | #define ACPI_UINTPTR_T uintptr_t 57 | -------------------------------------------------------------------------------- /cuser/acpica/acpica.h: -------------------------------------------------------------------------------- 1 | #include "acpi.h" 2 | #include "accommon.h" 3 | #include "acdebug.h" 4 | 5 | #define log(scope, fmt, ...) \ 6 | do { if (log_## scope) { printf("acpica: " fmt, ## __VA_ARGS__); } } while (0) 7 | 8 | #define CHECK_STATUS(fmt, ...) do { if (ACPI_FAILURE(status)) { \ 9 | printf("ACPI failed (%d): " fmt "\n", status, ## __VA_ARGS__); \ 10 | goto failed; \ 11 | } } while(0) 12 | 13 | #define _COMPONENT ACPI_DRIVER 14 | ACPI_MODULE_NAME("acpica") 15 | 16 | int AcpiOsCheckInterrupt(uintptr_t rcpt, uintptr_t arg); 17 | void RegIRQ(uintptr_t rcpt, uintptr_t int_spec); 18 | void AckIRQ(uintptr_t rcpt); 19 | 20 | ACPI_STATUS PrintAPICTable(void); 21 | ACPI_STATUS FindIOAPICs(int *pic_mode); 22 | void AddPIC(void); 23 | ACPI_STATUS RouteIRQ(ACPI_PCI_ID* device, int pin, int* irq); 24 | 25 | ACPI_STATUS EnumeratePCI(void); 26 | ACPI_STATUS PrintAcpiDevice(ACPI_HANDLE Device); 27 | 28 | UINT32 PciReadWord(UINT32 Addr); 29 | UINT32 AddrFromPciId(ACPI_PCI_ID* PciId, UINT32 Register); 30 | 31 | ACPI_STATUS FindPCIDevByVendor(UINT16 vendor, UINT16 device, ACPI_PCI_ID* id); 32 | 33 | static void FreeBuffer(ACPI_BUFFER* buffer) { 34 | AcpiOsFree(buffer->Pointer); 35 | } 36 | static void ResetBuffer(ACPI_BUFFER* buffer) { 37 | FreeBuffer(buffer); 38 | buffer->Pointer = 0; 39 | buffer->Length = ACPI_ALLOCATE_BUFFER; 40 | } 41 | 42 | #include "msg_acpi.h" 43 | -------------------------------------------------------------------------------- /cuser/acpica/malloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "acpica.h" 6 | 7 | static char heap[1048576]; 8 | static char* heap_end = heap; 9 | 10 | static const bool log_malloc = false; 11 | 12 | // The crummiest malloc in the west 13 | 14 | void* malloc(size_t size) { 15 | log(malloc, "malloc %x\n", size); 16 | size = (size + 2 * sizeof(size) - 1) & ~(sizeof(size) - 1); 17 | char* new_heap_end = heap_end + size; 18 | if (new_heap_end >= heap + sizeof(heap)) { 19 | printf("OUT OF MEMORY! size=%x\n", size); 20 | return NULL; 21 | } else { 22 | void* res = heap_end; 23 | ((size_t*)new_heap_end)[-1] = size; 24 | heap_end = new_heap_end; 25 | log(malloc, "malloc %x => %x\n", size, res); 26 | return res; 27 | } 28 | } 29 | 30 | void free(void* ptr) { 31 | if (!ptr) { 32 | return; 33 | } 34 | if ((char*)ptr < heap || (char*)ptr >= heap_end) { 35 | printf("Invalid pointer freed!\n"); 36 | return; 37 | } 38 | char* start_of_tail = heap_end - ((size_t*)heap_end)[-1]; 39 | if (ptr == start_of_tail) { 40 | heap_end = start_of_tail; 41 | } else { 42 | log(malloc, "free leaves hole at %x (< %x < %x)\n", ptr, start_of_tail, heap_end); 43 | // What? 44 | } 45 | } 46 | 47 | char *strdup(const char *src) { 48 | size_t n = strlen(src) + 1; 49 | return memcpy(malloc(n), src, n); 50 | } 51 | -------------------------------------------------------------------------------- /cuser/acpica/pci.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "acpi.h" 7 | #include "acpica.h" 8 | 9 | static const bool log_enum_pci = true; 10 | static const bool log_find_pci = false; 11 | 12 | static UINT32 getPCIConfig(u8 bus, u8 dev, u8 func, u8 offset, UINT32 width) 13 | { 14 | UINT64 ret; 15 | ACPI_PCI_ID id = { 0, bus, dev, func }; 16 | AcpiOsReadPciConfiguration(&id, offset, &ret, width); 17 | return ret; 18 | } 19 | 20 | typedef struct PCIEnum 21 | { 22 | // Return AE_CTRL_TERMINATE to stop iterating 23 | ACPI_STATUS (*cb)(struct PCIEnum* context); 24 | union { 25 | struct { u16 vendor, device; }; 26 | } in; 27 | struct { 28 | ACPI_PCI_ID pci_id; 29 | u16 vendor; 30 | u16 device; 31 | } cur; 32 | } PCIEnum; 33 | 34 | #define getVendorID(b,d,f) getPCIConfig(b,d,f, 0, 16) 35 | #define getDeviceID(b,d,f) getPCIConfig(b,d,f, 2, 16) 36 | #define getHeaderType(b,d,f) getPCIConfig(b,d,f, 14, 8) 37 | #define getSubClass(b,d,f) getPCIConfig(b,d,f, 10, 8) 38 | #define getBaseClass(b,d,f) getPCIConfig(b,d,f, 11, 8) 39 | #define getSecondaryBus(b,d,f) getPCIConfig(b,d,f, 0x19, 8) 40 | 41 | #define ACPI_RETURN_IF(e) \ 42 | do { \ 43 | ACPI_STATUS status__ = (e); \ 44 | /* Disable error logging for control codes. */ \ 45 | if ((status__ & AE_CODE_MASK) == AE_CODE_CONTROL) return status__; \ 46 | else if (ACPI_FAILURE(status__)) return_ACPI_STATUS(status__); \ 47 | } while(0) 48 | 49 | static ACPI_STATUS EnumPCIDevice(u8 bus, u8 dev, PCIEnum* cb); 50 | 51 | static ACPI_STATUS EnumPCIBus(u8 bus, PCIEnum* cb) { 52 | u8 dev = 0; 53 | //log(enum_pci, "acpica: Enumerating bus %02x...\n", bus); 54 | for (; dev < 32; dev++) { 55 | ACPI_RETURN_IF(EnumPCIDevice(bus, dev, cb)); 56 | } 57 | return AE_OK; 58 | } 59 | 60 | static ACPI_STATUS EnumPCIFunction(u8 bus, u8 dev, u8 func, PCIEnum* cb) { 61 | u16 vendor = getVendorID(bus, dev, func); 62 | if (vendor == 0xffff) { 63 | return AE_OK; 64 | } 65 | u8 headerType = getHeaderType(bus, dev, func); 66 | u8 baseClass = getBaseClass(bus, dev, func); 67 | u8 subClass = getSubClass(bus, dev, func); 68 | u16 device = getDeviceID(bus, dev, func); 69 | ACPI_STATUS status = AE_OK; 70 | if (cb) 71 | { 72 | ACPI_PCI_ID id = { 0, bus, dev, func }; 73 | cb->cur.pci_id = id; 74 | cb->cur.vendor = vendor; 75 | cb->cur.device = device; 76 | status = cb->cb(cb); 77 | } 78 | if (!cb || status == AE_CTRL_TERMINATE) 79 | { 80 | log(enum_pci, "%02x:%02x.%x: Found device %#04x:%#04x class %#x:%#x\n", 81 | bus, dev, func, vendor, device, baseClass, subClass); 82 | } 83 | ACPI_RETURN_IF(status); 84 | if (baseClass == 6 && subClass == 4) 85 | { 86 | if ((headerType & 0x7f) != 1) { 87 | log(enum_pci, "%02x:%02x.%x: Wrong header type %#x for PCI-to-PCI bridge\n", 88 | bus, dev, func, headerType); 89 | /* Just ignore this device "successfully". */ 90 | return AE_OK; 91 | } 92 | return EnumPCIBus(getSecondaryBus(bus, dev, func), cb); 93 | } 94 | return AE_OK; 95 | } 96 | 97 | static ACPI_STATUS EnumPCIDevice(u8 bus, u8 dev, PCIEnum* cb) { 98 | u8 func = 0; 99 | u16 vendor = getVendorID(bus, dev, func); 100 | if (vendor == 0xffff) { 101 | return AE_OK; 102 | } 103 | const u8 maxFunc = getHeaderType(bus, dev, func) & 0x80 ? 8 : 1; 104 | for (func = 0; func < maxFunc; func++) { 105 | ACPI_RETURN_IF(EnumPCIFunction(bus, dev, func, cb)); 106 | } 107 | return AE_OK; 108 | } 109 | 110 | ACPI_STATUS EnumeratePCI(void) { 111 | return EnumPCIBus(0, NULL); 112 | } 113 | 114 | static ACPI_STATUS FindPCIDevCB(PCIEnum* context) { 115 | if (context->cur.vendor == context->in.vendor && 116 | context->cur.device == context->in.device) { 117 | return AE_CTRL_TERMINATE; 118 | } 119 | return AE_OK; 120 | } 121 | 122 | ACPI_STATUS FindPCIDevByVendor(u16 vendor, u16 device, ACPI_PCI_ID* id) { 123 | PCIEnum cb; 124 | memset(&cb, 0, sizeof(cb)); 125 | cb.cb = FindPCIDevCB; 126 | cb.in.vendor = vendor; 127 | cb.in.device = device; 128 | log(find_pci, "Looking for %#04x:%#04x devices...\n", vendor, device); 129 | ACPI_STATUS status = EnumPCIBus(0, &cb); 130 | if (status == AE_OK) 131 | { 132 | return AE_NOT_FOUND; 133 | } 134 | else if (status == AE_CTRL_TERMINATE) 135 | { 136 | *id = cb.cur.pci_id; 137 | return AE_OK; 138 | } 139 | else 140 | { 141 | return status; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /cuser/acpica/printf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define fputc_unlocked(x, file) putchar(x) 10 | #define fwrite_unlocked(buf, n, s, file) putchars(buf, (n) * (s)) 11 | #define flockfile(file) (void)0 12 | 13 | static void putchars(const char* buf, size_t n) { 14 | while (n--) { 15 | putchar(*buf++); 16 | } 17 | } 18 | 19 | static void format_num(int width, bool leading_zero, int base, bool show_base, uintmax_t num) 20 | { 21 | if (show_base) 22 | { 23 | assert(base == 16); 24 | fputc_unlocked('0', file); 25 | fputc_unlocked('x', file); 26 | } 27 | char buf[32]; 28 | memset(buf, 0, sizeof(buf)); 29 | size_t len = 0; 30 | do 31 | { 32 | int i = num % base; 33 | buf[len++] = "0123456789abcdef"[i >= 0 ? i : -i]; 34 | num /= base; 35 | } 36 | while (num); 37 | if (width) 38 | { 39 | int c = leading_zero ? '0' : ' '; 40 | while (len < (size_t)width--) 41 | { 42 | fputc_unlocked(c, file); 43 | } 44 | } 45 | while (len--) 46 | { 47 | fputc_unlocked(buf[len], file); 48 | } 49 | } 50 | 51 | static void format_snum(int width, bool leading_zero, int64_t num) 52 | { 53 | if (num < 0) 54 | { 55 | num = -num; 56 | fputc_unlocked('-', file); 57 | } 58 | format_num(width, leading_zero, 10, false, num); 59 | } 60 | 61 | static const char* read_width(const char* fmt, int* width) 62 | { 63 | //errno = 0; 64 | char* endptr = NULL; 65 | *width = strtol(fmt, &endptr, 10); 66 | //assert(!errno); 67 | return endptr; 68 | } 69 | 70 | static void format_str(const char* s, bool left, size_t width, size_t maxwidth) 71 | { 72 | size_t len = strlen(s); 73 | if (maxwidth && len > maxwidth) len = maxwidth; 74 | if (left) { 75 | putchars(s, len); 76 | while (width && len++ < width) putchar(' '); 77 | } else { 78 | while (width-- > len) putchar(' '); 79 | putchars(s, len); 80 | } 81 | } 82 | 83 | void vprintf(const char* fmt, va_list ap) 84 | { 85 | flockfile(file); 86 | while (*fmt) 87 | { 88 | const char* nextformat = strchr(fmt, '%'); 89 | if (!nextformat) 90 | { 91 | fwrite_unlocked(fmt, 1, strlen(fmt), file); 92 | break; 93 | } 94 | else 95 | { 96 | fwrite_unlocked(fmt, 1, nextformat - fmt, file); 97 | fmt = nextformat + 1; 98 | } 99 | bool is_long = false; 100 | bool is_size = false; 101 | bool leading_zero = false; 102 | bool sign = true; 103 | bool show_base = false; 104 | bool left_align = false; 105 | int width = 0; 106 | bool after_point = false; 107 | int precision = 0; 108 | int base = 10; 109 | for (;;) 110 | { 111 | switch (*fmt++) 112 | { 113 | case '%': 114 | fputc_unlocked('%', file); 115 | break; 116 | case 'c': 117 | fputc_unlocked(va_arg(ap, int), file); 118 | break; 119 | case 's': 120 | { 121 | const char* arg = va_arg(ap, const char*); 122 | if (arg) 123 | format_str(arg, left_align, width, precision); 124 | else 125 | fwrite_unlocked("(null)", 1, sizeof("(null)")-1, file); 126 | break; 127 | } 128 | case 'x': 129 | case 'X': 130 | base = 16; 131 | __attribute__((fallthrough)); 132 | case 'u': 133 | sign = false; 134 | __attribute__((fallthrough)); 135 | case 'd': 136 | #define format_signed(type) format_snum(width, leading_zero, va_arg(ap, type)) 137 | #define format_unsigned(type) format_num(width, leading_zero, base, show_base, va_arg(ap, type)) 138 | #define format_num_type(type) sign ? format_signed(type) : format_unsigned(unsigned type) 139 | if (is_long) 140 | format_num_type(long); 141 | else if (is_size) 142 | format_unsigned(size_t); 143 | else 144 | format_num_type(int); 145 | break; 146 | case 'p': 147 | format_num(0, false, 16, true, (uintptr_t)va_arg(ap, void*)); 148 | break; 149 | case 'l': 150 | is_long = true; 151 | continue; 152 | case 'z': 153 | is_size = true; 154 | continue; 155 | case '#': 156 | show_base = true; 157 | continue; 158 | case '.': 159 | after_point = true; 160 | continue; 161 | case '0': 162 | leading_zero = true; 163 | fmt = read_width(fmt, &width); 164 | continue; 165 | case '-': 166 | left_align = true; 167 | continue; 168 | case '+': 169 | case ' ': 170 | // TODO Implement this correctly 171 | // +: show plus for positive number 172 | // space: show space for positive number (e.g. preserve width fro - but don't show the +) 173 | continue; 174 | default: 175 | if (isdigit(fmt[-1])) 176 | { 177 | fmt = read_width(fmt - 1, after_point ? &precision : &width); 178 | continue; 179 | } 180 | fputc_unlocked('%', file); 181 | fputc_unlocked(fmt[-1], file); 182 | return; 183 | } 184 | break; 185 | } 186 | } 187 | //funlockfile(file); 188 | //fflush(file); 189 | //fflush(stderr); // HACK 190 | } 191 | 192 | void printf(const char* fmt, ...) 193 | { 194 | va_list ap; 195 | va_start(ap, fmt); 196 | vprintf(fmt, ap); 197 | va_end(ap); 198 | } 199 | 200 | -------------------------------------------------------------------------------- /cuser/apic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "common.h" 5 | #include "msg_irq.h" 6 | #include "msg_timer.h" 7 | 8 | #if 0 9 | #define logf(fmt, ...) printf("apic: " fmt, ## __VA_ARGS__) 10 | #else 11 | #define logf(...) (void)0 12 | #endif 13 | 14 | static const uintptr_t irq_driver = 1; 15 | static const uintptr_t fresh_handle = 100; 16 | static const uintptr_t apic_pbase = 0xfee00000; 17 | // Very approximate ticks per second for qemu 18 | #define APIC_TICKS 6000000 19 | static const u32 apic_ticks = APIC_TICKS; 20 | 21 | static const u8 apic_timer_irq = 48; 22 | 23 | #define REG(name, offset) \ 24 | static const size_t name = (offset) / sizeof(u32) 25 | 26 | //REG(TPR, 0x80); 27 | REG(EOI, 0xb0); 28 | REG(LDR, 0xd0); 29 | REG(DFR, 0xe0); 30 | REG(SPURIOUS, 0xf0); 31 | enum SpuriousReg { 32 | APIC_SOFTWARE_ENABLE = 0x100, 33 | }; 34 | REG(TIMER_LVT, 0x320); 35 | enum { 36 | // Bit 17: TMM 37 | TIMER_TMM_PERIODIC = 0x20000, 38 | TIMER_TMM_ONESHOT = 0, 39 | }; 40 | REG(PERFC_LVT, 0x340); 41 | REG(LINT0_LVT, 0x350); 42 | REG(LINT1_LVT, 0x360); 43 | REG(ERROR_LVT, 0x370); 44 | enum { 45 | // TODO reverse the values used for LVT registers and define some nice 46 | // symbolic constants... 47 | LVT_TMM = 0x20000, // TiMerMode 48 | LVT_MASK = 0x10000, 49 | 50 | LVT_TGM = 0x8000, 51 | LVT_TRIGGER_MODE = LVT_TGM, 52 | LVT_TGM_LEVEL = LVT_TGM, 53 | LVT_TGM_EDGE = 0, 54 | 55 | LVT_MT_MASK = 0x700, 56 | LVT_MT_FIXED = 0x000, 57 | LVT_MT_EXT = 0x700, 58 | LVT_MT_NMI = 0x400, 59 | //0x08700 (LINT0) 60 | //0x00400 (LINT1) 61 | }; 62 | REG(APICTIC, 0x380); // Timer Initial Count? 63 | REG(APICTCC, 0x390); // Timer Current Count? 64 | REG(TIMER_DIV, 0x3e0); 65 | enum TimerDiv { 66 | TIMER_DIV_128 = 10, 67 | }; 68 | 69 | typedef struct timer timer; 70 | struct timer { 71 | u64 timeout; 72 | timer* down; 73 | timer* right; 74 | u8 pulse; 75 | }; 76 | 77 | static timer* ph_merge(timer* l, timer* r) { 78 | if (!l) { 79 | assert(!r->right); 80 | return r; 81 | } 82 | if (!r) { 83 | assert(!l->right); 84 | return l; 85 | } 86 | assert(!l->right && !r->right); 87 | if (r->timeout < l->timeout) { 88 | timer* t = r; 89 | r = l; 90 | l = t; 91 | } 92 | // l <= r! 93 | 94 | // Insert r first in l's down-list. 95 | r->right = l->down; 96 | l->down = r; 97 | return l; 98 | } 99 | static timer* ph_mergepairs(timer* l) { 100 | if (!l || !l->right) return l; 101 | 102 | timer* r = l->right; 103 | timer* hs = r->right; 104 | l->right = r->right = NULL; 105 | assert(hs != l && hs != r && r != l); 106 | // FIXME recursion... 107 | // We can use l->right after merge returns, since merge() always returns 108 | // something with a right-value of NULL. l will never be touched until 109 | // we're about to merge it with the result of mergePairs 110 | l = ph_merge(l,r); 111 | hs = ph_mergepairs(hs); 112 | return ph_merge(l,hs); 113 | } 114 | static timer* timer_pop(timer** head) { 115 | timer* res = *head; 116 | if (!res) return NULL; 117 | 118 | assert(!res->right); 119 | timer* l = res->down; 120 | res->down = NULL; 121 | *head = ph_mergepairs(l); 122 | return res; 123 | } 124 | static timer* timer_add(timer** head, timer* x) { 125 | *head = ph_merge(*head, x); 126 | assert(!(*head)->right); 127 | return x; 128 | } 129 | 130 | static volatile u32 apic[1024] PLACEHOLDER_SECTION ALIGN(4096); 131 | 132 | static timer* timers_head; 133 | // one MILLION timers 134 | #define MAX_TIMERS (1048576) 135 | static timer timer_heap[MAX_TIMERS]; 136 | static u32 timer_heap_limit = 0; 137 | static timer* free_timers; 138 | static u32 prevTIC = 0; 139 | static struct { 140 | u64 tick_counter; 141 | u64 ms_counter; 142 | u64 ns_counter; 143 | // pad to a full page to avoid exposing anything we don't have to 144 | u64 padding[509]; 145 | } static_data ALIGN(4096); 146 | 147 | static u64 ticks_to_millis(u64 ticks) { 148 | return ticks / (apic_ticks / 1000); 149 | } 150 | static u64 ticks_to_nanos(u64 ticks) { 151 | return 500 * ticks / (apic_ticks / 2000000); 152 | } 153 | 154 | static u64 get_tick_counter(void) { 155 | // Since this is a count*down* timer, the elapsed count is the initial count 156 | // minus the current count. 157 | u32 tcc = apic[APICTCC]; 158 | logf("from tic %u to tcc %u: %u elapsed\n", prevTIC, tcc, prevTIC - tcc); 159 | static_data.tick_counter += prevTIC - tcc; 160 | static_data.ms_counter = ticks_to_millis(static_data.tick_counter); 161 | prevTIC = tcc; 162 | return static_data.tick_counter; 163 | } 164 | #define MIN(x,y) ((x) > (y) ? (y) : (x)) 165 | #define MAX_U32(x) MIN(x, (u32)-1) 166 | static void setTIC(u64 ticks) { 167 | ticks = MAX_U32(ticks); 168 | // When we reset TIC we lose track of how much time passed between TIC..TCC 169 | get_tick_counter(); 170 | prevTIC = ticks; 171 | apic[APICTIC] = ticks; 172 | logf("setTIC %u\n", ticks); 173 | } 174 | 175 | static timer* timer_alloc(void) { 176 | if (free_timers) { 177 | return timer_pop(&free_timers); 178 | } else { 179 | return &timer_heap[timer_heap_limit++]; 180 | } 181 | } 182 | static timer* timer_new(u64 timeout, u8 pulse) { 183 | timer* t = timer_alloc(); 184 | t->timeout = timeout; 185 | t->pulse = pulse; 186 | return t; 187 | } 188 | static void timer_free(timer* t) { 189 | t->timeout = (uintptr_t)t; 190 | timer_add(&free_timers, t); 191 | } 192 | static timer* reg_timer(u64 ns, u8 pulse) { 193 | u64 ticks = (ns / 1000) * apic_ticks / 1000000; 194 | logf("%lu ns -> %lu ticks\n", ns, ticks); 195 | u64 tick_counter = get_tick_counter(); 196 | u64 tick_timeout = tick_counter + ticks; 197 | timer* t = timer_add(&timers_head, timer_new(tick_timeout, pulse)); 198 | logf("registered %p\n", t); 199 | return t; 200 | } 201 | 202 | static void __more_stack(size_t more) { 203 | static const uintptr_t TOP = 0x100000; 204 | static const size_t START = 0x1000; 205 | static const size_t MAX = TOP - START; 206 | static size_t extra; 207 | assert(!(more % 0x1000)); 208 | logf("Extending stack... had %ld want %ld more\n", extra, more); 209 | extra += more; 210 | assert(extra <= MAX); 211 | char* bottom = (char*)TOP - START - extra; 212 | logf("Extending stack... adding %p..%p\n", bottom, bottom + more); 213 | map_anon(PROT_READ | PROT_WRITE, bottom, more); 214 | logf("Extending stack... bottom now %p\n", bottom); 215 | } 216 | 217 | // Fun stuff: APIC is CPU local, user programs generally don't know which CPU 218 | // they're running on. (Though we're not multiprocessing yet anyway.) 219 | void start() { 220 | __default_section_init(); 221 | // FIXME It's really a bug that we end up using loads of stack here. Maybe 222 | // ph_mergepairs has to be made norecursive, or the problem is that a bug 223 | // leads it into an infinite loop? 224 | __more_stack(0xff000); 225 | logf("starting...\n"); 226 | 227 | // Perhaps we should use ACPI information to tell us if/that there's an 228 | // APIC and where we can find it. 229 | map(0, MAP_PHYS | PROT_READ | PROT_WRITE | PROT_NO_CACHE, 230 | apic, apic_pbase, sizeof(apic)); 231 | 232 | apic[TIMER_DIV] = TIMER_DIV_128; 233 | apic[APICTIC] = 0; 234 | apic[TIMER_LVT] = TIMER_TMM_ONESHOT | apic_timer_irq; 235 | apic[PERFC_LVT] = LVT_MASK; 236 | // These are bogus - we should consult ACPI to ask what the trigger mode 237 | // etc of these two are. In principle they could be anything. 238 | apic[LINT0_LVT] = LVT_TGM_EDGE | LVT_MT_EXT; 239 | apic[LINT1_LVT] = LVT_MT_NMI; 240 | apic[ERROR_LVT] = LVT_MASK; 241 | 242 | // Register irq 48 with irq driver 243 | // Note we don't use the PIC driver here - APIC interrupts have their own 244 | // EOI etc. 245 | ipc_arg_t arg = apic_timer_irq; 246 | sendrcv1(MSG_REG_IRQ, irq_driver, &arg); 247 | 248 | logf("irq registered, enabling APIC and EOI:ing\n"); 249 | 250 | // Set the mask to listen on. Should depend on CPU number. 251 | apic[DFR] = 1 << 24; 252 | // 0xf = flat model rather than cluster model 253 | // rest of the bits should be all 1s 254 | apic[LDR] = 0xffffffff; 255 | 256 | // enable and set spurious interrupt vector to 0xff 257 | apic[SPURIOUS] |= APIC_SOFTWARE_ENABLE | 0xff; 258 | 259 | setTIC(-1); 260 | 261 | bool need_eoi = true; 262 | for (;;) { 263 | { 264 | u64 tick_counter = get_tick_counter(); 265 | //send1(MSG_IRQ_ACK, irq_driver, arg1); 266 | logf("T: %lu %lums\n", tick_counter, static_data.ms_counter); 267 | if (!timers_head) { 268 | logf("idle.\n"); 269 | } 270 | while (timers_head && timers_head->timeout <= tick_counter) { 271 | logf("triggered %p.\n", timers_head); 272 | timer* t = timer_pop(&timers_head); 273 | pulse((uintptr_t)t, 1 << t->pulse); 274 | hmod_delete((uintptr_t)t); 275 | timer_free(t); 276 | } 277 | if (timers_head) { 278 | assert(timers_head->timeout > tick_counter); 279 | u64 t_next = timers_head->timeout - tick_counter; 280 | logf("time to next timeout %ld ticks\n", t_next); 281 | setTIC(t_next); 282 | } 283 | if (need_eoi) { 284 | apic[EOI] = 0; 285 | logf("EOI\n"); 286 | need_eoi = false; 287 | } 288 | } 289 | 290 | ipc_dest_t rcpt = fresh_handle; 291 | ipc_arg_t arg1, arg2; 292 | logf("receiving\n"); 293 | const ipc_msg_t msg = recv2(&rcpt, &arg1, &arg2); 294 | 295 | logf("received %x from %p: %lx %lx\n", msg&0xff, rcpt, arg1, arg2); 296 | 297 | // Note that since we use the raw IRQ driver, we don't need to ACK the 298 | // IRQ's we get - it's not listening for that anyway. 299 | if (rcpt == irq_driver) { 300 | logf("irq\n"); 301 | // Keep the counter counting please. (Switch back to periodic mode?) 302 | setTIC((u32)-1); 303 | need_eoi = true; 304 | continue; 305 | } 306 | 307 | logf("received %x from %p: %lx %lx\n", msg&0xff, rcpt, arg1, arg2); 308 | switch (msg & 0xff) { 309 | case MSG_REG_TIMER: 310 | // FIXME Check if the rcpt is already registered as a timer. 311 | hmod_rename(rcpt, (uintptr_t)reg_timer(arg1, arg2)); 312 | break; 313 | case MSG_TIMER_GETTIME: 314 | if (msg_get_kind(msg) == MSG_KIND_CALL) { 315 | u64 ticks = get_tick_counter(); 316 | send2(msg & 0xff, rcpt, static_data.ms_counter, ticks); 317 | } else { 318 | logf("gettime must be a sendrcv call\n"); 319 | } 320 | if (rcpt == fresh_handle) { 321 | hmod_delete(rcpt); 322 | } 323 | break; 324 | case MSG_PFAULT: 325 | *(volatile u64*)&static_data; 326 | grant(rcpt, &static_data, PROT_READ); 327 | break; 328 | default: 329 | logf("unknown request %x\n", msg); 330 | break; 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /cuser/bochsvga.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "msg_acpi.h" 5 | #include "msg_fb.h" 6 | 7 | #define log printf 8 | #if 0 9 | #define debug log 10 | #else 11 | #define debug(...) (void)0 12 | #endif 13 | 14 | #define VBE_DISPI_MAX_XRES 2560 15 | #define VBE_DISPI_MAX_YRES 1600 16 | #define VBE_DISPI_MAX_BPP 32 17 | 18 | #define VBE_DISPI_IOPORT_INDEX 0x01CE 19 | #define VBE_DISPI_IOPORT_DATA 0x01CF 20 | 21 | enum Index { 22 | INDEX_ID = 0x0, 23 | INDEX_XRES = 0x1, 24 | INDEX_YRES = 0x2, 25 | INDEX_BPP = 0x3, 26 | INDEX_ENABLE = 0x4, 27 | INDEX_BANK = 0x5, 28 | INDEX_VIRT_WIDTH = 0x6, 29 | INDEX_VIRT_HEIGHT = 0x7, 30 | INDEX_X_OFFSET = 0x8, 31 | INDEX_Y_OFFSET = 0x9, 32 | INDEX_VIDEO_MEMORY_64K = 0xa, 33 | }; 34 | 35 | #define VBE_DISPI_ID0 0xB0C0 36 | #define VBE_DISPI_ID1 0xB0C1 37 | #define VBE_DISPI_ID2 0xB0C2 38 | #define VBE_DISPI_ID3 0xB0C3 39 | #define VBE_DISPI_ID4 0xB0C4 40 | #define VBE_DISPI_ID5 0xB0C5 41 | 42 | #define VBE_DISPI_BPP_4 0x04 43 | #define VBE_DISPI_BPP_8 0x08 44 | #define VBE_DISPI_BPP_15 0x0F 45 | #define VBE_DISPI_BPP_16 0x10 46 | #define VBE_DISPI_BPP_24 0x18 47 | #define VBE_DISPI_BPP_32 0x20 48 | 49 | #define VBE_DISPI_DISABLED 0x00 50 | #define VBE_DISPI_ENABLED 0x01 51 | #define VBE_DISPI_GETCAPS 0x02 52 | #define VBE_DISPI_8BIT_DAC 0x20 53 | #define VBE_DISPI_LFB_ENABLED 0x40 54 | #define VBE_DISPI_NOCLEARMEM 0x80 55 | 56 | static const uintptr_t acpi_handle = 6; 57 | static const uintptr_t the_client = 0xff; 58 | static const uintptr_t fresh = 0x100; 59 | 60 | #define LFB_SIZE (16 * 1048576) 61 | 62 | static u8 mmiospace[LFB_SIZE] PLACEHOLDER_SECTION; 63 | 64 | static void outb(u16 port, u8 data) { 65 | portio(port, 0x11, data); 66 | } 67 | 68 | static u16 read_reg(enum Index i) { 69 | portio(VBE_DISPI_IOPORT_INDEX, 0x12, (u16)i); 70 | return portio(VBE_DISPI_IOPORT_DATA, 0x2, 0); 71 | } 72 | 73 | static void write_reg(enum Index i, u16 data) { 74 | portio(VBE_DISPI_IOPORT_INDEX, 0x12, (u16)i); 75 | portio(VBE_DISPI_IOPORT_DATA, 0x12, data); 76 | } 77 | 78 | static u32 readpci32(u32 addr, u8 reg) 79 | { 80 | ipc_arg_t arg = addr << 8 | (reg & 0xfc); 81 | sendrcv1(MSG_ACPI_READ_PCI, acpi_handle, &arg); 82 | return arg; 83 | } 84 | 85 | void start() { 86 | __default_section_init(); 87 | 88 | ipc_arg_t arg = 0x12341111; // Silly PCI ID of Bochs VGA 1234:1111 89 | log("bochsvga: looking for PCI device...\n"); 90 | sendrcv1(MSG_ACPI_FIND_PCI, acpi_handle, &arg); 91 | if (arg == ACPI_PCI_NOT_FOUND) 92 | { 93 | log("bochsvga: No devices found\n"); 94 | abort(); 95 | } 96 | log("bochsvga: found %x\n", arg); 97 | // bus << 8 | dev << 3 | func 98 | const uintptr_t pci_id = arg; 99 | ipc_arg_t arg2 = 0; // No interrupts to claim. 100 | sendrcv2(MSG_ACPI_CLAIM_PCI, acpi_handle, &arg, &arg2); 101 | if (!arg) 102 | { 103 | log("bochsvga: failed :(\n"); 104 | abort(); 105 | } 106 | 107 | u32 bar0 = readpci32(pci_id, PCI_BAR_0); 108 | debug("BAR 0: %s %s %s: %x\n", 109 | bar0 & 1 ? "io" : "mem", 110 | (bar0 >> 1) & 3 ? "64-bit" : "32-bit", 111 | (bar0 >> 3) & 1 ? "prefetchable" : "non-prefetchable", 112 | bar0 & ~0xf); 113 | u64 mmiobase = bar0 & ~0xf; 114 | if (((bar0 >> 1) & 3) == 2) // 64-bit 115 | { 116 | u64 bar1 = readpci32(pci_id, PCI_BAR_1); 117 | debug("BAR0 was 64-bit, adding %lx:%lx.\n", bar1, mmiobase); 118 | mmiobase |= bar1 << 32; 119 | } 120 | debug("Mapping mmiospace %p to BAR %p\n", (void*)mmiospace, mmiobase); 121 | // TODO Map unprefetchable! 122 | map(0, MAP_PHYS | PROT_READ | PROT_WRITE, 123 | (void*)mmiospace, mmiobase, sizeof(mmiospace)); 124 | memset(mmiospace, 0, sizeof(mmiospace)); 125 | 126 | u16 bochs_id = read_reg(INDEX_ID); 127 | debug("bochsvga: Found bochs version %x\n", bochs_id); 128 | assert(bochs_id >= VBE_DISPI_ID0 && bochs_id <= VBE_DISPI_ID5); 129 | 130 | for(;;) { 131 | ipc_dest_t rcpt = fresh; 132 | arg = 0; 133 | arg2 = 0; 134 | ipc_msg_t msg = recv2(&rcpt, &arg, &arg2); 135 | debug("bochsvga: received %x from %x: %x %x\n", msg, rcpt, arg, arg2); 136 | 137 | switch (msg & 0xff) { 138 | case MSG_SET_VIDMODE: 139 | { 140 | u32 w = arg >> 32; 141 | u32 h = arg; 142 | u32 bpp = arg2; 143 | log("bochsvga: %x setting video mode to %ux%u %ubpp\n", rcpt, w, h, bpp); 144 | assert(w <= VBE_DISPI_MAX_XRES && h <= VBE_DISPI_MAX_YRES); 145 | assert(bpp <= VBE_DISPI_MAX_BPP); 146 | write_reg(INDEX_ENABLE, VBE_DISPI_DISABLED); 147 | write_reg(INDEX_XRES, w); 148 | write_reg(INDEX_YRES, h); 149 | write_reg(INDEX_BPP, bpp); 150 | write_reg(INDEX_ENABLE, VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED 151 | | VBE_DISPI_8BIT_DAC); 152 | debug("bochsvga: mode updated!\n"); 153 | send2(MSG_SET_VIDMODE, rcpt, arg, arg2); 154 | hmod_rename(rcpt, the_client); 155 | break; 156 | } 157 | case MSG_SET_PALETTE: 158 | { 159 | u8 c = arg >> 24; 160 | u8 r = arg >> 16; 161 | u8 g = arg >> 8; 162 | u8 b = arg; 163 | outb(0x3c8, c); 164 | outb(0x3c9, r); 165 | outb(0x3c9, g); 166 | outb(0x3c9, b); 167 | debug("bochsvga: palette %x := (%x,%x,%x)\n", c, r, g, b); 168 | break; 169 | } 170 | case MSG_PFAULT: 171 | { 172 | assert(arg < LFB_SIZE && rcpt == the_client); 173 | if (arg < LFB_SIZE) { 174 | void *addr = (char*)&mmiospace + arg; 175 | int prot = arg2 & (PROT_READ | PROT_WRITE); 176 | debug("bochsvga: granting %p (%x) to client %x\n", addr, prot, rcpt); 177 | grant(rcpt, addr, prot); 178 | } 179 | break; 180 | } 181 | default: 182 | abort(); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /cuser/common.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMMON_H 2 | #define __COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | __BEGIN_DECLS 10 | 11 | typedef uint64_t u64; 12 | typedef uint32_t u32; 13 | typedef uint16_t u16; 14 | typedef uint8_t u8; 15 | typedef unsigned int uint; 16 | 17 | // FIXME This causes 'start' to follow various silly calling conventions - such 18 | // as saving callee-save registers. Find some way to get rid of that... 19 | // Or wait for http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38534 to be fixed. 20 | void start(void) __attribute__((noreturn,section(".start"))); 21 | 22 | // Symbols exposed by the linker script 23 | extern char __bss_start[1]; 24 | extern char __bss_end[1]; 25 | extern char __data_lma[1]; 26 | extern char __data_lma_end[1]; 27 | extern char __data_size[1]; 28 | extern char __data_vma[1]; 29 | 30 | // Attribute for putting variable in a special placeholder section. Useful for 31 | // reserving virtual memory space or dummy memory space for handles. 32 | #define S__(x) #x 33 | #define S_(x) S__(x) 34 | #define PLACEHOLDER_SECTION __attribute__((section(".placeholder." __FILE__ "." S_(__LINE__)))) 35 | #define ALIGN(n) __attribute__((aligned(n))) 36 | 37 | static void prefault_range(void* start, size_t size, int prot) { 38 | uint8_t* p = (uint8_t*)start; 39 | uint8_t* end = p + size; 40 | while (p < end) { 41 | prefault(p, prot); 42 | p += 4096; 43 | } 44 | } 45 | 46 | static void __default_section_init(void) { 47 | map_anon(PROT_READ | PROT_WRITE, __bss_start, __bss_end - __bss_start); 48 | memcpy(__data_vma, __data_lma, (uintptr_t)&__data_size); 49 | } 50 | 51 | static void hexdump(char* data, size_t length) { 52 | size_t pos = 0; 53 | while (pos < length) { 54 | printf("\n%04x: ", pos); 55 | for (int i = 0; i < 16 && pos < length; i++) { 56 | printf("%02x ", (u8)data[pos++]); 57 | } 58 | } 59 | printf("\n"); 60 | } 61 | 62 | // For header type 0 (!) 63 | // Is this part of the ACPI message API? Or maybe independent PCI utils. 64 | enum pci_regs 65 | { 66 | PCI_VENDOR_ID = 0x00, 67 | PCI_DEVICE_ID = 0x02, 68 | PCI_COMMAND = 0x04, 69 | PCI_STATUS = 0x06, 70 | PCI_REV_ID = 0x08, 71 | PCI_PROG_IF = 0x09, 72 | PCI_SUBCLASS = 0x0a, 73 | PCI_CLASS = 0x0b, 74 | PCI_CL_SIZE = 0x0c, 75 | PCI_LATENCY_TIMER = 0x0d, 76 | PCI_HEADER_TYPE = 0x0e, 77 | PCI_BIST = 0x0f, 78 | PCI_BAR_0 = 0x10, 79 | PCI_BAR_1 = 0x14, 80 | // .. BAR5 81 | // Cardbus CIS Pointer 82 | PCI_SS_VENDOR_ID = 0x2c, 83 | PCI_SUBSYSTEM_ID = 0x2e, 84 | }; 85 | enum pci_command_bits 86 | { 87 | PCI_COMMAND_IOSPACE = 1, 88 | PCI_COMMAND_MEMSPACE = 2, 89 | PCI_COMMAND_MASTER = 4, 90 | }; 91 | 92 | /** 93 | * A simple (compiler) barrier. Memory writes to volatile variables before the 94 | * barrier must not be moved to after the barrier, regardless of optimizations. 95 | */ 96 | static void __barrier(void) { 97 | __asm__ __volatile__ ("":::"memory"); 98 | } 99 | 100 | __END_DECLS 101 | 102 | #endif // __COMMON_H 103 | -------------------------------------------------------------------------------- /cuser/fbtest.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #include "msg_fb.h" 4 | #include "msg_timer.h" 5 | 6 | #define log printf 7 | #if 0 8 | #define debug log 9 | #else 10 | #define debug(...) (void)0 11 | #endif 12 | 13 | #define ALIGN(n) __attribute__((aligned(n))) 14 | 15 | #define FRAME_DELAY 20000000/*ns*/ 16 | #define W 640UL 17 | #define H 480UL 18 | #define BPP 8 19 | 20 | static const uintptr_t fbhandle = 6; 21 | static const uintptr_t apic_handle = 4; 22 | 23 | static __attribute__((const)) u16 usqrt(u32 x) { 24 | u32 res = x; 25 | while (res * res < x) res++; 26 | return res; 27 | } 28 | 29 | static u32 diff(u32 x1, u32 x2) { 30 | return x1 < x2 ? x2 - x1 : x1 - x2; 31 | } 32 | 33 | static u16 alpha(u32 dx, u32 dy) { 34 | u32 res = (dx << 16) / (dy ? dy : 1); 35 | while (res >= 0x100) res >>= 1; 36 | return res; 37 | } 38 | 39 | static void set_palette(u8 pal) { 40 | debug("fbtest: set palette %u\n", pal); 41 | for (uint i = 0; i < 256; i++) { 42 | u8 r = i + pal, g = i + pal + 85, b = i + pal - 85; 43 | uintptr_t arg = (i << 24) | (r << 16) | (g << 8) | b; 44 | send1(MSG_SET_PALETTE, fbhandle, arg); 45 | } 46 | } 47 | 48 | static u8 frame_buffer[W*H*BPP/8] PLACEHOLDER_SECTION ALIGN(4096); 49 | 50 | void start() { 51 | __default_section_init(); 52 | log("fbtest: starting...\n"); 53 | { 54 | ipc_arg_t arg1 = ((u64)W) << 32 | H; 55 | ipc_arg_t arg2 = BPP; 56 | sendrcv2(MSG_SET_VIDMODE, fbhandle, &arg1, &arg2); 57 | } 58 | 59 | map(fbhandle, PROT_READ | PROT_WRITE, &frame_buffer, 0, sizeof(frame_buffer)); 60 | prefault_range(frame_buffer, sizeof(frame_buffer), PROT_READ | PROT_WRITE); 61 | memset(frame_buffer, 0, sizeof(frame_buffer)); 62 | log("fbtest: faulted and cleared frame buffer\n"); 63 | 64 | send2(MSG_REG_TIMER, apic_handle, FRAME_DELAY, 0); 65 | log("fbtest: timer started\n"); 66 | 67 | u8 *dst = frame_buffer; 68 | for (uint y = 0; y < H; y++) { 69 | for (uint x = 0; x < W; x++) { 70 | u32 dx = diff(x, W/2); 71 | u32 dy = diff(y, H/2); 72 | 73 | #if BPP > 8 74 | // B, G, R 75 | *dst++ = alpha(dx, dy); 76 | *dst++ = usqrt(dx*dx + dy*dy); 77 | *dst++ = r; 78 | *dst++ = r; 79 | #else 80 | // palette index 81 | *dst++ = usqrt(dx*dx + dy*dy); 82 | #endif 83 | } 84 | } 85 | 86 | debug("fbtest: pattern drawn\n"); 87 | 88 | u8 palette = 0; 89 | set_palette(palette); 90 | 91 | for(;;) { 92 | ipc_dest_t rcpt = 0; 93 | ipc_arg_t arg, arg2; 94 | ipc_msg_t msg = recv2(&rcpt, &arg, &arg2); 95 | if ((msg & 0xff) == MSG_PULSE) { 96 | // && rcpt == apic_handle 97 | set_palette(++palette); 98 | send2(MSG_REG_TIMER, apic_handle, FRAME_DELAY, 0); 99 | continue; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /cuser/helloworld.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | void start(void) { 4 | for(;;) 5 | puts("hello world"); 6 | } 7 | -------------------------------------------------------------------------------- /cuser/include/__decls.h: -------------------------------------------------------------------------------- 1 | #ifndef __BEGIN_DECLS 2 | # ifdef __cplusplus 3 | # define __BEGIN_DECLS extern "C" { 4 | # define __END_DECLS } 5 | # else 6 | # define __BEGIN_DECLS /* empty */ 7 | # define __END_DECLS /* empty */ 8 | # endif 9 | #endif 10 | -------------------------------------------------------------------------------- /cuser/include/assert.h: -------------------------------------------------------------------------------- 1 | /* Note: the include guard is not covering the whole header - the assert macro 2 | * shall be redefined according to the current state of NDEBUG each time 3 | * assert.h is included. */ 4 | #ifndef __ASSERT_H 5 | #define __ASSERT_H 6 | 7 | #include <__decls.h> 8 | #include 9 | #include 10 | 11 | __BEGIN_DECLS 12 | 13 | static void assert_failed(const char* file, int line, const char* msg) __attribute__((noreturn)); 14 | static void assert_failed(const char* file, int line, const char* msg) { 15 | printf("%s:%d: ASSERT FAILED (%s)\n", file, line, msg); 16 | abort(); 17 | } 18 | 19 | __END_DECLS 20 | 21 | #endif /* __ASSERT_H */ 22 | 23 | // TODO: If not NDEBUG 24 | 25 | #undef assert 26 | #define assert(X) \ 27 | do { if (!(X)) assert_failed(__FILE__, __LINE__, #X); } while (0) 28 | 29 | -------------------------------------------------------------------------------- /cuser/include/ctype.h: -------------------------------------------------------------------------------- 1 | int isalpha(int c) __attribute__((pure)); 2 | int islower(int c) __attribute__((pure)); 3 | int isprint(int c) __attribute__((pure)); 4 | int isspace(int c) __attribute__((pure)); 5 | int isupper(int c) __attribute__((pure)); 6 | int tolower(int c) __attribute__((pure)); 7 | int toupper(int c) __attribute__((pure)); 8 | 9 | int isdigit(int c) __attribute__((const)); 10 | int isxdigit(int c) __attribute__((const)); 11 | -------------------------------------------------------------------------------- /cuser/include/msg_con.h: -------------------------------------------------------------------------------- 1 | enum msg_con { 2 | MSG_CON_WRITE = MSG_USER, 3 | MSG_CON_READ, 4 | }; 5 | 6 | -------------------------------------------------------------------------------- /cuser/include/msg_ethernet.h: -------------------------------------------------------------------------------- 1 | #ifndef __MSG_ETHERNET_H 2 | #define __MSG_ETHERNET_H 3 | 4 | enum msg_ethernet { 5 | /** 6 | * Register an ethernet protocol. Use with a fresh handle, memory map pages 7 | * to read incoming packets and store outgoing packets. 8 | * The special protocol number 0 can be used to match all protocols. 9 | * 10 | * The number of buffers to use is decided by the protocol through sending 11 | * receive messages for each buffer it wants to use for reception. All 12 | * buffers start out owned by the protocol until given to ethernet for 13 | * sending or receiving. 14 | * 15 | * arg1: protocol number 16 | * Returns: 17 | * arg1: MAC address of card 18 | */ 19 | MSG_ETHERNET_REG_PROTO = MSG_USER, 20 | /** 21 | * protocol -> ethernet: allocate a buffer to reception, handing over 22 | * ownership to the ethernet driver. Can only be sent without sendrcv. The 23 | * reply comes by pulse afterwards. 24 | * 25 | * The buffer contains the whole ethernet frame including headers, as it 26 | * came from the network. It may contain any type of frame, with or without 27 | * a VLAN header. 28 | * 29 | * arg1: page number of the buffer to receive into 30 | */ 31 | MSG_ETHERNET_RECV, 32 | /** 33 | * protocol -> ethernet: Send a packet. The given page number is owned by 34 | * the ethernet driver until delivered over the wire and the 35 | * pulse reply is sent. 36 | * 37 | * arg1: page number of buffer that contains data to send. 38 | * arg2: datagram length 39 | * Returns: 40 | * arg1: page number of delivered packet - the page is no longer owned by 41 | * the driver. 42 | */ 43 | MSG_ETHERNET_SEND, 44 | }; 45 | enum 46 | { 47 | ETHERTYPE_ANY = 0, 48 | }; 49 | 50 | #endif /* __MSG_ETHERNET_H */ 51 | -------------------------------------------------------------------------------- /cuser/include/msg_fb.h: -------------------------------------------------------------------------------- 1 | #ifndef __MSG_FB_H 2 | #define __MSG_FB_H 3 | 4 | enum msg_fb 5 | { 6 | /** 7 | * Set video mode (size and bpp). 8 | * 9 | * arg1: width << 32 | height 10 | * arg2: bits per pixel 11 | * 12 | * The user should map the handle, as much memory as needed for the given 13 | * resolution. (Or more, to support use of MSG_PRESENT.) 14 | */ 15 | MSG_SET_VIDMODE = MSG_USER, 16 | /** 17 | * For 4- or 8-bit modes, update a palette entry. 18 | * 19 | * arg1: index << 24 | r << 16 | g << 8 | b 20 | */ 21 | MSG_SET_PALETTE, 22 | /** 23 | * Placeholder for future (window manager driven) api - present a frame. 24 | * 25 | * arg1: origin of frame to present, relative mapped memory area. 26 | * (an id? some way for the client to know when this has been presented and 27 | * the previous frame will not be used by the window manager again) 28 | */ 29 | MSG_PRESENT, 30 | }; 31 | 32 | #endif /* __MSG_FB_H */ 33 | -------------------------------------------------------------------------------- /cuser/include/msg_irq.h: -------------------------------------------------------------------------------- 1 | #ifndef __MSG_IRQ_H 2 | #define __MSG_IRQ_H 3 | 4 | #include "msg_syscalls.h" 5 | 6 | enum msg_irq { 7 | /** 8 | * Takes one argument, the IRQ number (GSI in the case of I/O APICs). 9 | * (Or does it take a number local to the interrupt controller?) 10 | */ 11 | MSG_REG_IRQ = MSG_USER, 12 | /** 13 | * Acknowledge receipt of the interrupt. 14 | * 15 | * arg1: IRQ number to acknowledge 16 | */ 17 | MSG_IRQ_ACK, 18 | }; 19 | 20 | #endif /* __MSG_IRQ_H */ 21 | -------------------------------------------------------------------------------- /cuser/include/msg_syscalls.h: -------------------------------------------------------------------------------- 1 | #ifndef __MSG_SYSCALLS_H 2 | #define __MSG_SYSCALLS_H 3 | 4 | enum syscalls_builtins { 5 | MSG_NONE = 0, 6 | SYSCALL_RECV = MSG_NONE, 7 | MSG_MAP, 8 | MSG_PFAULT, 9 | MSG_UNMAP, 10 | MSG_HMOD, 11 | SYSCALL_WRITE = 6, 12 | // arg0 (dst) = port 13 | // arg1 = flags (i/o) and data size: 14 | // 0x10 = write (or with data size) 15 | // 0x01 = byte 16 | // 0x02 = word 17 | // 0x04 = dword 18 | // arg2 (if applicable) = data for output 19 | SYSCALL_IO = 7, // Backdoor! 20 | MSG_GRANT = 8, 21 | MSG_PULSE = 9, 22 | SYSCALL_YIELD = 10, 23 | MSG_USER = 16, 24 | }; 25 | 26 | #endif /* __MSG_SYSCALLS_H */ 27 | -------------------------------------------------------------------------------- /cuser/include/msg_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __MSG_TIMER_H 2 | #define __MSG_TIMER_H 3 | 4 | enum msg_timer 5 | { 6 | /** 7 | * Register a timer to trigger in approximately N nanoseconds. 8 | * 9 | * arg1: timeout. 10 | */ 11 | MSG_REG_TIMER = MSG_USER, 12 | /** 13 | * A registered timer has triggered. The timer will not be triggered again 14 | * unless you re-register a new timeout. 15 | */ 16 | MSG_TIMER_T, 17 | /** 18 | * Two-argument sendrcv. 19 | * 20 | * returns: 21 | * arg1: milliseconds 22 | * arg2: ticks 23 | */ 24 | MSG_TIMER_GETTIME, 25 | }; 26 | 27 | #endif /* __MSG_TIMER_H */ 28 | -------------------------------------------------------------------------------- /cuser/include/stdint.h: -------------------------------------------------------------------------------- 1 | /* In hosted environment, GCC's stdint.h includes libc's stdint.h, which is 2 | * this. To avoid reimplementing stuff, just include the gcc header :) */ 3 | #include 4 | -------------------------------------------------------------------------------- /cuser/include/stdio.h: -------------------------------------------------------------------------------- 1 | #ifndef __STDIO_H 2 | #define __STDIO_H 3 | 4 | #include <__decls.h> 5 | #include 6 | 7 | // Without -ffreestanding, our printf/vprintf generate warnings due to 8 | // returning void instead of int. Just silence the warning :) 9 | #pragma GCC diagnostic push 10 | #pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch" 11 | 12 | __BEGIN_DECLS 13 | 14 | char getchar(void); 15 | void putchar(char c); 16 | void puts(const char* str); 17 | 18 | void printf(const char* fmt, ...); 19 | void vprintf(const char* fmt, va_list ap); 20 | 21 | __END_DECLS 22 | 23 | #pragma GCC diagnostic pop 24 | 25 | #endif /* __STDIO_H */ 26 | -------------------------------------------------------------------------------- /cuser/include/stdlib.h: -------------------------------------------------------------------------------- 1 | #ifndef __STDLIB_H 2 | #define __STDLIB_H 3 | 4 | #include <__decls.h> 5 | #include 6 | 7 | __BEGIN_DECLS 8 | 9 | /* malloc/free might not be implemented, depending on what you link against. */ 10 | void* malloc(size_t size); 11 | void free(void* p); 12 | 13 | void abort(void) __attribute__((noreturn)); 14 | 15 | unsigned long int strtoul(const char *nptr, char **endptr, int base); 16 | long int strtol(const char* p, char** end, int base); 17 | int atoi(const char *s); 18 | 19 | 20 | __END_DECLS 21 | 22 | #endif /* __STDLIB_H */ 23 | -------------------------------------------------------------------------------- /cuser/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <__decls.h> 4 | #include 5 | 6 | __BEGIN_DECLS 7 | 8 | void* memcpy(void* dest, const void* src, size_t n); 9 | void* memmove(void* dest, const void* src, size_t n); 10 | void* memset(void* dest, int c, size_t n); 11 | int memcmp(const void* a_, const void* b_, size_t n); 12 | 13 | char *strcpy(char *dest, const char *src); 14 | char *strncpy(char *dest, const char *src, size_t n); 15 | 16 | int strcmp(const char* a, const char* b); 17 | int strncmp(const char* a, const char* b, size_t n); 18 | size_t strlen(const char* s); 19 | char *strcat(char* dest, const char* src); 20 | char *strncat(char* dest, const char* src, size_t n); 21 | char* strchr(const char* s, int c); 22 | char* strstr(const char* haystack, const char *needle); 23 | 24 | char *strdup(const char *src); 25 | 26 | __END_DECLS 27 | -------------------------------------------------------------------------------- /cuser/ioapic.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "msg_acpi.h" 5 | 6 | // FIXME Since ACPICA uses I/O APIC and console uses ACPICA, we must use raw 7 | // stdio here if we want to be able to output anything. 8 | #define LOG_ENABLED 0 9 | #define log(fmt, ...) do { if (LOG_ENABLED) printf("ioapic: " fmt, ## __VA_ARGS__); } while (0) 10 | 11 | static const uintptr_t apic_pbase = 0xfee00000; 12 | static volatile u32 lapic[1024] PLACEHOLDER_SECTION ALIGN(4096); 13 | #define REG(name, offset) \ 14 | static const size_t name = (offset) / sizeof(u32) 15 | REG(EOI, 0xb0); 16 | 17 | static const uintptr_t fresh = 0x100; 18 | static const uintptr_t rawIRQ = 0x1; 19 | #define MAX_GSI 64 20 | // 48 is the 32 CPU exceptions plus 16 PIC interrupts still there to deal with 21 | // spurious interrupts. 22 | #define GSI_IRQ_BASE 49 23 | 24 | typedef volatile u32 apic_page[4096 / 4]; 25 | static apic_page apic_pages[256] PLACEHOLDER_SECTION ALIGN(4096); 26 | 27 | static void map_mmio(volatile void *p, uintptr_t physAddr, size_t size) 28 | { 29 | map(0, MAP_PHYS | PROT_READ | PROT_WRITE | PROT_NO_CACHE, 30 | p, physAddr, size); 31 | } 32 | 33 | struct apic 34 | { 35 | u8 id; 36 | u8 gsibase; 37 | volatile u32* mmio; 38 | }; 39 | static struct apic apics[256]; 40 | enum { 41 | REGSEL = 0, 42 | REGWIN = 0x10 / sizeof(u32), 43 | }; 44 | enum Reg { 45 | IOAPICID = 0, 46 | IOAPICVER = 1, 47 | IOAPICARB = 2, 48 | IOAPICRED = 0x10, 49 | }; 50 | 51 | // Map each gsi to the apic id that handles it. 52 | // 0 will be used for unknown GSIs, but this is also a valid APIC ID. Check the 53 | // mmio pointer for an apic to see if it is initialized. 54 | u8 apic_id_for_gsi[256]; 55 | 56 | // Handles for registered GSI clients. Set to 1 when registered. 57 | u8 downstream_gsi[256]; 58 | // Handles for raw IRQs upstream 59 | u8 upstream_irq[256] PLACEHOLDER_SECTION; 60 | 61 | // General operation: 62 | // * one or more MSG_ACPI_ADD_IOAPIC messages to register the I/O APIC's present 63 | // on the system, they are assigned ranges of interrupts starting from 48 64 | // 65 | // An interrupt vector allocation API for those will help with e.g. the APIC 66 | // timer interrupt, currently hardcoded to interrupt 48. 67 | // * users (ACPICA) register for interrupts with MSG_REG_IRQ 68 | // The argument is GSI, to be interpreted against the gsi base of each APIC. 69 | 70 | static u32 read(struct apic* a, u32 reg) 71 | { 72 | a->mmio[REGSEL] = reg; 73 | return a->mmio[REGWIN]; 74 | } 75 | 76 | static void write(struct apic* a, u32 reg, u32 value) 77 | { 78 | a->mmio[REGSEL] = reg; 79 | a->mmio[REGWIN] = value; 80 | } 81 | 82 | static void write_redirect(struct apic* a, u8 pin, u64 data) 83 | { 84 | write(a, IOAPICRED + 2 * pin, data); 85 | write(a, IOAPICRED + 2 * pin + 1, data >> 32); 86 | } 87 | 88 | static u64 read_redirect(struct apic* a, u8 pin) 89 | { 90 | u64 lo = read(a, IOAPICRED + 2 * pin); 91 | u64 hi = read(a, IOAPICRED + 2 * pin + 1); 92 | return (hi << 32) | lo; 93 | } 94 | 95 | static void register_rawirq(ipc_arg_t irq, ipc_dest_t handle) 96 | { 97 | hmod_copy(rawIRQ, handle); 98 | sendrcv1(MSG_REG_IRQ, handle, &irq); 99 | } 100 | 101 | static void add_ioapic(uintptr_t h, u8 id, uintptr_t physAddr, u64 gsibase) 102 | { 103 | log("Adding I/O APIC at %p (id %d, gsi base %d)\n", 104 | physAddr, id, gsibase); 105 | if (gsibase < MAX_GSI) 106 | { 107 | struct apic *apic = &apics[id]; 108 | apic->id = id; 109 | apic->gsibase = gsibase; 110 | apic->mmio = apic_pages[id]; 111 | map_mmio(apic->mmio, physAddr, 4096); 112 | 113 | u32 apicid = read(apic, IOAPICID); 114 | u32 ver = read(apic, IOAPICVER); 115 | log("IOAPICID=%#08x IOAPICVER=%#08x\n", apicid, ver); 116 | u8 max_redir = (ver >> 16) & 0xff; 117 | log("Found APIC version %#x with %d interrupts\n", ver & 0xff, max_redir + 1); 118 | 119 | send1(MSG_ACPI_ADD_IOAPIC, h, max_redir + 1); 120 | hmod_rename(h, (uintptr_t)apic); 121 | 122 | for (u64 i = 0; i <= max_redir; i++) { 123 | u8 gsi = gsibase + i; 124 | u8 irq = GSI_IRQ_BASE + gsi; 125 | //log("Registering IRQ %d for GSI %d\n", irq, gsi); 126 | register_rawirq(irq, (uintptr_t)&upstream_irq[irq]); 127 | apic_id_for_gsi[gsi] = id; 128 | } 129 | } 130 | else 131 | { 132 | send1(MSG_ACPI_ADD_IOAPIC, h, 0); 133 | hmod_delete(h); 134 | } 135 | } 136 | 137 | enum { 138 | RED_MASKED = 1 << 16, 139 | // Trigger mode: 1 = level, 0 = edge 140 | RED_TRIGGER_MODE = 1 << 15, 141 | RED_TRIGGER_MODE_LEVEL = RED_TRIGGER_MODE, 142 | RED_TRIGGER_MODE_EDGE = 0, 143 | // Read-only, for level-triggered interrupts. 144 | // Set to 1 when local APIC(s) accept the level-triggered interrupt. 145 | // Reset to 0 when an EOI with matching vector is received. 146 | RED_REMOTE_IRR = 1 << 14, 147 | RED_INTPOL_LOW = 1 << 13, 148 | RED_INTPOL_HIGH = 0 << 13, 149 | // Read-only. 0 = idle, 1 = send pending 150 | RED_DELIVERY_STATUS = 1 << 12, 151 | RED_DESTMOD = 1 << 11, 152 | RED_DESTMOD_LOGICAL = RED_DESTMOD, 153 | RED_DELMOD_MASK = 7 << 8, 154 | // 0 = Fixed, deliver to every destination CPU 155 | // 1 = Lowest Priority, use the TPR register to select CPU 156 | RED_DELMOD_LOWPRIO = 1 << 8, 157 | // 2 = SMI 158 | // 3 = Reserved 159 | // 4 = NMI 160 | // 5 = INIT 161 | // 6 = Reserved 162 | // 7 = ExtINT 163 | }; 164 | 165 | static void reg_gsi(uintptr_t h, uintptr_t gsi, uintptr_t flags) 166 | { 167 | assert(gsi < MAX_GSI); 168 | struct apic* apic = &apics[apic_id_for_gsi[gsi]]; 169 | assert(apic->mmio); 170 | 171 | u8 pin = gsi - apic->gsibase; 172 | u64 prev = read_redirect(apic, pin); 173 | // Fill out the redirection entry in the apic thing 174 | // Flags should specify some of the things we need to know here. Delivery 175 | // mode probably needs to be set up for some ACPI things (SMI, NMI). 176 | u64 x = 177 | // Destination: all CPUs 178 | ((u64)0xff << 56) 179 | // Flags etc: 180 | | (flags & 1 ? RED_TRIGGER_MODE_EDGE : RED_TRIGGER_MODE_LEVEL) 181 | | (flags & 2 ? RED_INTPOL_LOW : RED_INTPOL_HIGH) 182 | | RED_DESTMOD_LOGICAL 183 | | RED_DELMOD_LOWPRIO 184 | // Vector: 185 | | (GSI_IRQ_BASE + gsi); 186 | write_redirect(apic, pin, x); 187 | log("Changed redirect from %#lx to %#lx\n", prev, read_redirect(apic, pin)); 188 | 189 | send1(MSG_REG_IRQ, h, gsi); 190 | downstream_gsi[gsi] = 1; 191 | hmod_rename(h, (uintptr_t)&downstream_gsi[gsi]); 192 | } 193 | 194 | static void handle_irq(uintptr_t irq) { 195 | uintptr_t gsi = irq - GSI_IRQ_BASE; 196 | if (gsi >= MAX_GSI) return; 197 | 198 | struct apic* apic = &apics[apic_id_for_gsi[gsi]]; 199 | log("Received IRQ %d (GSI %d)\n", (int)irq, gsi); 200 | if (apic->mmio) { 201 | u8 pin = gsi - apic->gsibase; 202 | // Mask the interrupt (until we get an IRQ_ACK back) 203 | u64 red_entry = read_redirect(apic, pin); 204 | write_redirect(apic, pin, red_entry | RED_MASKED); 205 | lapic[EOI] = 0; 206 | } 207 | if (downstream_gsi[gsi]) { 208 | pulse((uintptr_t)&downstream_gsi[gsi], 1); 209 | } 210 | } 211 | 212 | static void ack_irq(uintptr_t gsi) { 213 | if (gsi >= MAX_GSI) return; 214 | 215 | struct apic* apic = &apics[apic_id_for_gsi[gsi]]; 216 | log("Ack'ed GSI %d\n", gsi); 217 | if (apic->mmio) { 218 | u8 pin = gsi - apic->gsibase; 219 | u64 red_entry = read_redirect(apic, pin); 220 | write_redirect(apic, pin, red_entry & ~RED_MASKED); 221 | log("Changed redirect from %#lx to %#lx\n", red_entry, read_redirect(apic, pin)); 222 | } 223 | } 224 | 225 | void start() { 226 | __default_section_init(); 227 | 228 | map(0, MAP_PHYS | PROT_READ | PROT_WRITE | PROT_NO_CACHE, 229 | lapic, apic_pbase, sizeof(lapic)); 230 | 231 | for (;;) { 232 | ipc_arg_t arg1, arg2, arg3; 233 | ipc_dest_t rcpt = fresh; 234 | ipc_msg_t msg = recv3(&rcpt, &arg1, &arg2, &arg3); 235 | switch (msg & 0xff) 236 | { 237 | case MSG_PULSE: 238 | handle_irq(rcpt - (uintptr_t)upstream_irq); 239 | break; 240 | case MSG_IRQ_ACK: 241 | ack_irq(rcpt - (uintptr_t)downstream_gsi); 242 | break; 243 | case MSG_ACPI_ADD_IOAPIC: 244 | add_ioapic(rcpt, arg1, arg2, arg3); 245 | break; 246 | case MSG_REG_IRQ: 247 | reg_gsi(rcpt, arg1 & 0xff, arg1 >> 8); 248 | break; 249 | default: 250 | log("Unknown message %#lx from %#lx\n", msg & 0xff, rcpt); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /cuser/libc/acpi_strtoul.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * 3 | * 1. Copyright Notice 4 | * 5 | * Some or all of this work - Copyright (c) 1999 - 2017, Intel Corp. 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions, and the following disclaimer, 13 | * without modification. 14 | * 2. Redistributions in binary form must reproduce at minimum a disclaimer 15 | * substantially similar to the "NO WARRANTY" disclaimer below 16 | * ("Disclaimer") and any redistribution must be conditioned upon 17 | * including a substantially similar Disclaimer requirement for further 18 | * binary redistribution. 19 | * 3. Neither the names of the above-listed copyright holders nor the names 20 | * of any contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | 45 | enum signum 46 | { 47 | ACPI_SIGN_POSITIVE, 48 | ACPI_SIGN_NEGATIVE, 49 | }; 50 | 51 | unsigned long strtoul(const char *String, char **Terminator, int Base) 52 | { 53 | const char *StringStart; 54 | unsigned long converted = 0; 55 | enum signum sign; 56 | unsigned long ReturnValue = 0; 57 | bool error = false; 58 | 59 | /* 60 | * Save the value of the pointer to the buffer's first 61 | * character, save the current errno value, and then 62 | * skip over any white space in the buffer: 63 | */ 64 | StringStart = String; 65 | while (isspace (*String)) 66 | { 67 | ++String; 68 | } 69 | 70 | /* 71 | * The buffer may contain an optional plus or minus sign. 72 | * If it does, then skip over it but remember what is was: 73 | */ 74 | if (*String == '-') 75 | { 76 | sign = ACPI_SIGN_NEGATIVE; 77 | ++String; 78 | } 79 | else if (*String == '+') 80 | { 81 | ++String; 82 | sign = ACPI_SIGN_POSITIVE; 83 | } 84 | else 85 | { 86 | sign = ACPI_SIGN_POSITIVE; 87 | } 88 | 89 | /* 90 | * If the input parameter Base is zero, then we need to 91 | * determine if it is octal, decimal, or hexadecimal: 92 | */ 93 | if (Base == 0) 94 | { 95 | if (*String == '0') 96 | { 97 | if (tolower (*(++String)) == 'x') 98 | { 99 | Base = 16; 100 | ++String; 101 | } 102 | else 103 | { 104 | Base = 8; 105 | } 106 | } 107 | else 108 | { 109 | Base = 10; 110 | } 111 | } 112 | else if (Base < 2 || Base > 36) 113 | { 114 | /* 115 | * The specified Base parameter is not in the domain of 116 | * this function: 117 | */ 118 | goto done; 119 | } 120 | 121 | /* 122 | * For octal and hexadecimal bases, skip over the leading 123 | * 0 or 0x, if they are present. 124 | */ 125 | if (Base == 8 && *String == '0') 126 | { 127 | String++; 128 | } 129 | 130 | if (Base == 16 && 131 | *String == '0' && 132 | tolower (*(++String)) == 'x') 133 | { 134 | String++; 135 | } 136 | 137 | /* 138 | * Main loop: convert the string to an unsigned long: 139 | */ 140 | while (*String) 141 | { 142 | int index; 143 | if (isdigit (*String)) 144 | { 145 | index = ((uint8_t) *String - '0'); 146 | } 147 | else 148 | { 149 | index = toupper(*String); 150 | if (isupper(*String)) 151 | { 152 | index = index - 'A' + 10; 153 | } 154 | else 155 | { 156 | goto done; 157 | } 158 | } 159 | 160 | if (index >= Base) 161 | { 162 | goto done; 163 | } 164 | 165 | /* 166 | * Check to see if value is out of range: 167 | */ 168 | 169 | if (ReturnValue > ((ULONG_MAX - index) / (unsigned long) Base)) 170 | { 171 | error = true; 172 | ReturnValue = 0; /* reset */ 173 | } 174 | else 175 | { 176 | ReturnValue *= Base; 177 | ReturnValue += index; 178 | converted = 1; 179 | } 180 | 181 | ++String; 182 | } 183 | 184 | done: 185 | /* 186 | * If appropriate, update the caller's pointer to the next 187 | * unconverted character in the buffer. 188 | */ 189 | if (Terminator) 190 | { 191 | if (converted == 0 && ReturnValue == 0 && String != NULL) 192 | { 193 | *Terminator = (char *) StringStart; 194 | } 195 | else 196 | { 197 | *Terminator = (char *) String; 198 | } 199 | } 200 | 201 | if (error) 202 | { 203 | ReturnValue = ULONG_MAX; 204 | } 205 | 206 | /* 207 | * If a minus sign was present, then "the conversion is negated": 208 | */ 209 | if (sign == ACPI_SIGN_NEGATIVE) 210 | { 211 | ReturnValue = (ULONG_MAX - ReturnValue) + 1; 212 | } 213 | 214 | return (ReturnValue); 215 | } 216 | 217 | -------------------------------------------------------------------------------- /cuser/libc/ctype.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int isdigit(int c) { 5 | return c >= '0' && c <= '9'; 6 | } 7 | 8 | int isxdigit(int c) { 9 | return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); 10 | } 11 | 12 | int isspace(int c) { 13 | return !!strchr(" \t\r\n\v\f", c); 14 | } 15 | 16 | int isprint(int c) { 17 | return (unsigned)c-0x20 < 0x5f; 18 | } 19 | 20 | int tolower(int c) { 21 | return isupper(c) ? c ^ 0x20 : c; 22 | } 23 | 24 | int toupper(int c) { 25 | return islower(c) ? c ^ 0x20 : c; 26 | } 27 | 28 | int islower(int c) { 29 | return c >= 'a' && c <= 'z'; 30 | } 31 | 32 | int isupper(int c) { 33 | return c >= 'A' && c <= 'Z'; 34 | } 35 | 36 | int isalpha(int c) { 37 | return islower(c) || isupper(c); 38 | } 39 | -------------------------------------------------------------------------------- /cuser/libc/printf.asm: -------------------------------------------------------------------------------- 1 | bits 64 2 | section .text 3 | global printf 4 | 5 | %include "macros.inc" 6 | %include "messages.inc" 7 | 8 | %include "../user/putchar.inc" 9 | %include "printf.inc" 10 | 11 | ; Inputs: 12 | ; rdi = format 13 | ; rsi = pointer to va_list structure 14 | ; 15 | ; va_list: 16 | ; 0. gp_offset 17 | ; 4. fp_offset 18 | ; 8. overflow_arg_area 19 | ; 16. reg_save_area 20 | ; 21 | ; When in printf, we want the stack to contain the relevant values from 22 | ; overflow_arg_area, and want all the GP registers to be loaded with values 23 | ; from reg_save_area(?). Do we know how much data is saved in either place? 24 | vprintf: 25 | jmp puts 26 | -------------------------------------------------------------------------------- /cuser/libc/stdio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static const ipc_dest_t CONSOLE_HANDLE = 3; /* Hardcode galore */ 6 | 7 | void putchar(char c) { 8 | send1(MSG_CON_WRITE, CONSOLE_HANDLE, c); 9 | } 10 | 11 | char getchar(void) { 12 | ipc_arg_t c = 0; 13 | sendrcv1(MSG_CON_READ, CONSOLE_HANDLE, &c); 14 | return c; 15 | } 16 | 17 | void puts(const char* str) { 18 | while (*str) putchar(*str++); 19 | putchar('\n'); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /cuser/libc/stdio_raw.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void putchar(char c) { 5 | syscall1(SYSCALL_WRITE, c); 6 | } 7 | void puts(const char* str) { 8 | while (*str) putchar(*str++); 9 | putchar('\n'); 10 | } 11 | -------------------------------------------------------------------------------- /cuser/libc/stdlib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void abort(void) { 5 | for (;;) recv0(-1); 6 | } 7 | 8 | long int strtol(const char* p, char** end, int base) { 9 | return strtoul(p, end, base); 10 | } 11 | 12 | int atoi(const char *nptr) { 13 | return strtol(nptr, NULL, 10); 14 | } 15 | -------------------------------------------------------------------------------- /cuser/libc/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void* memcpy(void* dest, const void* src, size_t n) { 5 | asm("rep movsb": "+D"(dest), "+S"(src), "+c"(n), "=m"(dest) : : "memory"); 6 | return dest; 7 | } 8 | 9 | void* memmove(void* dest, const void* src, size_t n) { 10 | // Non-overlapping or overlapping such that a forwards copy works. 11 | if (dest < src || (char*)dest > (char*)src + n) { 12 | return memcpy(dest, src, n); 13 | } else if (dest > src) { 14 | while (n--) { 15 | ((char *)dest)[n] = ((const char*)src)[n]; 16 | } 17 | } 18 | return dest; 19 | } 20 | 21 | void* memset(void* dest, int c, size_t n) { 22 | asm("rep stosb": "+D"(dest), "+c"(n), "=m"(dest) : "a"(c) : "memory"); 23 | return dest; 24 | } 25 | 26 | int memcmp(const void* a_, const void* b_, size_t n) { 27 | const char* a = a_, *b = b_; 28 | while (n--) { 29 | int diff = *a++ - *b++; 30 | if (diff) return diff; 31 | } 32 | return 0; 33 | } 34 | 35 | int strcmp(const char* a, const char* b) { 36 | while (*a && *b && *a == *b) { 37 | a++, b++; 38 | } 39 | return *a - *b; 40 | } 41 | 42 | int strncmp(const char* a, const char* b, size_t n) { 43 | if (!n) return 0; 44 | 45 | while (n-- && *a && *b && *a == *b) { 46 | a++, b++; 47 | } 48 | return n + 1 ? *a - *b : 0; 49 | } 50 | 51 | size_t strlen(const char* s) { 52 | size_t res = 0; 53 | while (*s++) res++; 54 | return res; 55 | } 56 | 57 | char* strchr(const char* s, int c) { 58 | while (*s && *s != c) s++; 59 | return *s ? (char*)s : NULL; 60 | } 61 | 62 | char* strstr(const char* haystack, const char *needle) { 63 | const size_t nlen = strlen(needle); 64 | const size_t hlen = strlen(haystack); 65 | for (size_t i = 0; i < hlen; i++) 66 | { 67 | if (memcmp(haystack + i, needle, nlen) == 0) { 68 | return (char*)haystack + i; 69 | } 70 | } 71 | return NULL; 72 | } 73 | 74 | char* strcpy(char* d, const char *s) { 75 | return memcpy(d, s, strlen(s) + 1); 76 | } 77 | 78 | char* strcat(char* d, const char *s) { 79 | strcpy(d + strlen(d), s); 80 | return d; 81 | } 82 | 83 | char* strncpy(char* d, const char *s, size_t n) { 84 | size_t copy = strlen(s) + 1; 85 | size_t fill = 0; 86 | if (copy > n) 87 | { 88 | copy = n; 89 | } 90 | else 91 | { 92 | fill = n - copy; 93 | } 94 | memcpy(d, s, copy); 95 | memset(d + copy, 0, fill); 96 | return d; 97 | } 98 | -------------------------------------------------------------------------------- /cuser/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(start) 2 | 3 | SECTIONS 4 | { 5 | . = 1M; 6 | 7 | .text : { 8 | /* GCC expects the stack to be 16-byte aligned. It is 1MB-aligned 9 | * already, *but* the alignment that gcc is talking about is *before* 10 | * a return address is pushed by a 'call'. So, misalign the stack by 11 | * pushing a qword in place of the return address. */ 12 | /* 0x6a 0x00 = push byte 0 */ 13 | SHORT(0x6a); 14 | *(.start); 15 | *(.text*); 16 | *(.rodata*); 17 | } 18 | /* 1MB of .text and initialized .data should be enough for anyone. */ 19 | . = 2M; 20 | PROVIDE(__bss_start = .); 21 | .data : AT( ADDR(.text) + SIZEOF(.text) ) { 22 | PROVIDE(__data_lma = LOADADDR(.data)); 23 | PROVIDE(__data_vma = .); 24 | . += SIZEOF(.data); 25 | *(.data*); 26 | PROVIDE(__data_lma_end = LOADADDR(.data) + SIZEOF(.data)); 27 | } 28 | /* Note that none of these get loaded properly unless the program does it 29 | * by itself. 30 | * 1. Map __bss_start .. __bss_end as read-writeable zero-init memory 31 | * 2. Copy __data_size bytes from __data_lma to __data_vma. 32 | * __data_vma is inside the bss range just allocated. */ 33 | .bss : { 34 | *(.bss*); 35 | } 36 | PROVIDE(__bss_end = ALIGN(4096)); 37 | PROVIDE(__data_size = SIZEOF(.data)); 38 | .placeholder (NOLOAD) : { 39 | *(SORT_BY_ALIGNMENT(.placeholder*)); 40 | } 41 | /DISCARD/ : { 42 | *(.eh_frame); 43 | *(.comment); 44 | *(.note*); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cuser/lwip/arch/cc.h: -------------------------------------------------------------------------------- 1 | // compiler/platform-specific definitions for lwip 2 | 3 | #ifndef __ARCH_CC_H__ 4 | #define __ARCH_CC_H__ 5 | 6 | #include // for assert_failed, non-standard function but we assume it's in assert.h 7 | #include 8 | #include 9 | 10 | #define BYTE_ORDER LITTLE_ENDIAN 11 | 12 | #define LWIP_ERR_T int 13 | #define LWIP_NO_INTTYPES_H 1 14 | 15 | #define X8_F "02x" 16 | 17 | #define U16_F "u" 18 | #define S16_F "d" 19 | #define X16_F "x" 20 | 21 | #define U32_F "u" 22 | #define S32_F "d" 23 | #define X32_F "x" 24 | 25 | #define SZT_F "zu" 26 | 27 | /* Use our assert_failed instead of LWIP's print/flush/abort. */ 28 | #define LWIP_PLATFORM_ASSERT(x) assert_failed(__FILE__, __LINE__, x) 29 | 30 | // Saves about a kilobyte over the default out-of-line implementations 31 | #define lwip_htons __builtin_bswap16 32 | #define lwip_htonl __builtin_bswap32 33 | 34 | #endif /* __ARCH_CC_H__ */ 35 | -------------------------------------------------------------------------------- /cuser/lwip/arch/perf.h: -------------------------------------------------------------------------------- 1 | #define PERF_START 2 | #define PERF_STOP(x) 3 | -------------------------------------------------------------------------------- /cuser/lwip/arch/sys_arch.h: -------------------------------------------------------------------------------- 1 | // What here? 2 | -------------------------------------------------------------------------------- /cuser/lwip/http.c: -------------------------------------------------------------------------------- 1 | #include "lwip/ip.h" 2 | #include "lwip/tcp.h" 3 | #include "http.h" 4 | 5 | #define log_enabled 0 6 | #define log(...) do { if (log_enabled) printf(__VA_ARGS__); } while (0) 7 | 8 | typedef struct tcp_pcb tcp_pcb; 9 | 10 | static tcp_pcb* server_pcb; 11 | 12 | const char response[] = 13 | "HTTP/1.0 200 OK\r\n" 14 | "Content-Type: text/html\r\n" 15 | "\r\n" 16 | "

Hello World!

\r\n" 17 | ; 18 | 19 | static err_t http_request(tcp_pcb* pcb, struct pbuf* p) { 20 | log("http[%d]: sending response.\n", pcb->remote_port); 21 | tcp_write(pcb, response, sizeof(response) - 1, 0); 22 | tcp_close(pcb); 23 | // TODO Forward error codes from tcp write/close 24 | return ERR_OK; 25 | } 26 | 27 | static err_t http_recv_cb(void* arg_, tcp_pcb* pcb, struct pbuf* p, err_t err) { 28 | if (err != ERR_OK) { 29 | log("http: recv_cb err=%d\n", err); 30 | tcp_recv(pcb, NULL); 31 | tcp_output(pcb); 32 | tcp_close(pcb); 33 | return err; 34 | } 35 | struct pbuf* arg = arg_; 36 | if (p) { 37 | size_t len = p->len; 38 | size_t total = (arg ? arg->tot_len : 0) + len; 39 | log("http[%d]: received %ld bytes (%ld total)\n", pcb->remote_port, len, total); 40 | if (arg) { 41 | pbuf_cat(arg, p); 42 | } else { 43 | tcp_arg(pcb, arg = p); 44 | p->tot_len = len; 45 | } 46 | tcp_recved(pcb, len); 47 | if (arg->tot_len) { 48 | http_request(pcb, arg); 49 | tcp_arg(pcb, NULL); 50 | pbuf_free(arg); 51 | } 52 | } else if (arg) { 53 | log("http: received %ld bytes without sending response\n", arg->tot_len); 54 | tcp_arg(pcb, NULL); 55 | pbuf_free(arg); 56 | tcp_close(pcb); 57 | } 58 | return ERR_OK; 59 | } 60 | 61 | static err_t http_accept_cb(void *arg, tcp_pcb *newpcb, err_t err) { 62 | log("http[%d]: accepted connection from %x, err=%d\n", newpcb->remote_port, newpcb->remote_ip.addr, err); 63 | if (err != ERR_OK) { 64 | return err; 65 | } 66 | 67 | tcp_recv(newpcb, http_recv_cb); 68 | // TODO tcp_err(newpcb, http_err_cb); 69 | tcp_accepted(server_pcb); 70 | return err; 71 | } 72 | 73 | err_t http_start(void) { 74 | server_pcb = tcp_new(); 75 | err_t res = tcp_bind(server_pcb, IP_ADDR_ANY, 80); 76 | if (res != ERR_OK) { 77 | return res; 78 | } 79 | server_pcb = tcp_listen_with_backlog(server_pcb, 5); 80 | tcp_accept(server_pcb, http_accept_cb); 81 | 82 | return res; 83 | } 84 | -------------------------------------------------------------------------------- /cuser/lwip/http.h: -------------------------------------------------------------------------------- 1 | err_t http_start(void); 2 | -------------------------------------------------------------------------------- /cuser/lwip/lwipopts.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define NO_SYS 1 4 | #define SYS_LIGHTWEIGHT_PROT 0 5 | 6 | // options, options, options! 7 | #define LWIP_DHCP 1 8 | #define LWIP_AUTOIP 0 9 | #define LWIP_DNS 1 10 | #define LWIP_NETIF_LOOPBACK_MULTITHREADING 0 11 | #define LWIP_HAVE_LOOPIF 1 12 | //#define LWIP_IPV6 1 13 | #define LWIP_PROVIDE_ERRNO 14 | #define LWIP_SOCKET 0 15 | #define LWIP_NETCONN 0 16 | #define LWIP_TCP 1 17 | #define TCP_LISTEN_BACKLOG 1 18 | 19 | uint32_t lwip_random(void); 20 | #define LWIP_RAND lwip_random 21 | 22 | #ifdef NDEBUG 23 | // Kind of good to have asserts, but they're like 20% of the IP stack? 24 | #define LWIP_NOASSERT 25 | #endif 26 | #ifndef NDEBUG 27 | #define LWIP_DEBUG 28 | #endif 29 | 30 | #ifdef NDEBUG 31 | #define LWIP_DBG_NDEBUG 0 32 | #else 33 | #define LWIP_DBG_NDEBUG LWIP_DBG_ON 34 | #endif 35 | #define LWIP_DBG_TYPES_ON (LWIP_DBG_ON | LWIP_DBG_STATE) 36 | 37 | #define ETHARP_DEBUG LWIP_DBG_NDEBUG 38 | #define AUTOIP_DEBUG LWIP_DBG_NDEBUG 39 | #define DHCP_DEBUG LWIP_DBG_NDEBUG 40 | #define IP_DEBUG LWIP_DBG_NDEBUG 41 | #define INET_DEBUG LWIP_DBG_NDEBUG 42 | #define ICMP_DEBUG LWIP_DBG_NDEBUG 43 | #define TCP_DEBUG LWIP_DBG_NDEBUG 44 | #define TCP_INPUT_DEBUG LWIP_DBG_NDEBUG 45 | #define TCP_OUTPUT_DEBUG LWIP_DBG_NDEBUG 46 | 47 | #define TIMERS_DEBUG LWIP_DBG_NDEBUG 48 | #define LWIP_DEBUG_TIMERNAMES (TIMERS_DEBUG & LWIP_DBG_ON) 49 | 50 | // Disable to get rid of a timer. 51 | #define IP_REASSEMBLY 0 52 | // Also disabled to get rid of timers 53 | #define LWIP_ACD 0 54 | #define LWIP_DHCP_DOES_ACD_CHECK 0 55 | -------------------------------------------------------------------------------- /cuser/lwip/main.c: -------------------------------------------------------------------------------- 1 | #include "lwip/autoip.h" 2 | #include "lwip/dhcp.h" 3 | #include "lwip/init.h" 4 | #include "lwip/ip.h" 5 | #include "lwip/sys.h" 6 | #include "lwip/timeouts.h" 7 | #include "netif/etharp.h" 8 | 9 | #include 10 | #include 11 | 12 | #include "http.h" 13 | 14 | #include "common.h" 15 | #include "msg_ethernet.h" 16 | #include "msg_timer.h" 17 | 18 | #define log printf 19 | #if 0 20 | #define debug log 21 | #else 22 | #define debug(...) (void)0 23 | #endif 24 | 25 | #define HEXDUMP 0 26 | 27 | static const uintptr_t eth_handle = 7; 28 | static const uintptr_t apic_handle = 4; 29 | static const uintptr_t proto_handle = 0x100; 30 | static const uintptr_t timer_handle = 0x101; 31 | static const uintptr_t fresh_handle = 0x200; 32 | 33 | // static const u16 ETHERTYPE_ARP = 0x0806; 34 | 35 | #define NBUFS 32 36 | #define BUFFER_SIZE 4096 37 | static char receive_buffers[NBUFS][BUFFER_SIZE] PLACEHOLDER_SECTION ALIGN(BUFFER_SIZE); 38 | static char send_buffers[NBUFS][BUFFER_SIZE] PLACEHOLDER_SECTION ALIGN(BUFFER_SIZE); 39 | static bool send_busy[NBUFS]; 40 | 41 | extern void netif_rcvd(const char*, size_t); 42 | 43 | struct netif netif; 44 | static ip_addr_t ipaddr, netmask, gw; 45 | static u64 hwaddr; 46 | 47 | // Should be a struct somewhere we can share it with the apic implementation. 48 | static volatile const struct { 49 | u64 tick_counter; 50 | u64 ms_counter; 51 | } timer_data PLACEHOLDER_SECTION ALIGN(BUFFER_SIZE); 52 | 53 | #if 1 54 | u32 sys_now() { 55 | u64 ms = 0, ticks = 0; 56 | sendrcv2(MSG_TIMER_GETTIME, apic_handle, &ms, &ticks); 57 | debug("sys_now: %lu ms (%lu ticks)\n", ms, ticks); 58 | return ms; 59 | } 60 | #else 61 | u32 sys_now() { 62 | u64 res = timer_data.ms_counter; 63 | debug("sys_now: %lu ms (%lu ticks)\n", res, timer_data.tick_counter); 64 | return res; 65 | } 66 | #endif 67 | 68 | // TODO Actually implement a random number generator and an API 69 | u32 lwip_random() { 70 | return (u32)timer_data.tick_counter; 71 | } 72 | 73 | static void check_timers(void) { 74 | sys_check_timeouts(); 75 | u64 timeout_ms = sys_timeouts_sleeptime(); 76 | if (timeout_ms != (u32)-1) { 77 | debug("lwip: timeout %lums\n", timeout_ms); 78 | hmod(apic_handle, apic_handle, timer_handle); 79 | send2(MSG_REG_TIMER, timer_handle, timeout_ms * 1000000, 0); 80 | } 81 | } 82 | 83 | static void rcvd(uintptr_t buffer_index, uintptr_t packet_length) { 84 | debug("lwip: received %u bytes\n", packet_length); 85 | #if HEXDUMP 86 | hexdump(receive_buffers[buffer_index], packet_length); 87 | #endif 88 | struct pbuf* p = pbuf_alloc(PBUF_LINK, packet_length, PBUF_POOL); 89 | const char* src = receive_buffers[buffer_index]; 90 | pbuf_take(p, src, packet_length); 91 | netif.input(p, &netif); 92 | // Prepare to receive the next message 93 | send1(MSG_ETHERNET_RECV, proto_handle, buffer_index); 94 | } 95 | 96 | static void buffer_done(uintptr_t i) { 97 | if (i < NBUFS) { 98 | debug("lwip: finish recv on %d\n", i); 99 | rcvd(i, *(u16*)(receive_buffers[i] + 4094)); 100 | } else { 101 | debug("lwip: finish send on %d\n", i); 102 | send_busy[i - NBUFS] = false; 103 | } 104 | } 105 | 106 | static err_t if_output(struct netif* netif, struct pbuf* p) { 107 | for (size_t i = 0; i < NBUFS; i++) { 108 | if (send_busy[i]) { 109 | continue; 110 | } 111 | size_t len = pbuf_copy_partial(p, send_buffers[i], BUFFER_SIZE, 0); 112 | debug("if_output: %ld bytes\n", len); 113 | #if HEXDUMP 114 | hexdump(send_buffers[i], len); 115 | #endif 116 | send_busy[i] = true; 117 | send2(MSG_ETHERNET_SEND, proto_handle, NBUFS + i, len); 118 | return 0; 119 | } 120 | 121 | log("if_output: busy...\n"); 122 | return ERR_WOULDBLOCK; 123 | } 124 | 125 | static err_t if_init(struct netif* netif) { 126 | netif->hwaddr_len = 6; 127 | netif->name[0] = 'e'; netif->name[1] = 'n'; 128 | netif->output = etharp_output; 129 | netif->linkoutput = if_output; 130 | netif->mtu = 1500; 131 | memcpy(netif->hwaddr, &hwaddr, 6); 132 | netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_ETHERNET; 133 | return 0; 134 | } 135 | 136 | void start() { 137 | __default_section_init(); 138 | 139 | map(apic_handle, PROT_READ, &timer_data, 0, 4096); 140 | prefault(&timer_data, PROT_READ); 141 | debug("lwip: initialized timer\n"); 142 | 143 | hmod(eth_handle, eth_handle, proto_handle); 144 | ipc_arg_t arg1 = ETHERTYPE_ANY; 145 | sendrcv1(MSG_ETHERNET_REG_PROTO, proto_handle, &arg1); 146 | hwaddr = arg1; 147 | debug("lwip: registered protocol on %012lx, mapping+faulting buffer...\n", hwaddr); 148 | 149 | map(proto_handle, PROT_READ, receive_buffers, 0, sizeof(receive_buffers)); 150 | map(proto_handle, PROT_READ | PROT_WRITE, send_buffers, sizeof(receive_buffers), sizeof(send_buffers)); 151 | for (int i = 0; i < NBUFS; i++) { 152 | prefault(receive_buffers[i], PROT_READ); 153 | send1(MSG_ETHERNET_RECV, proto_handle, i); 154 | prefault(send_buffers[i], PROT_READ | PROT_WRITE); 155 | } 156 | debug("lwip: registered protocol\n"); 157 | puts("lwip: starting lwIP " LWIP_VERSION_STRING "..."); 158 | 159 | lwip_init(); 160 | IP4_ADDR(&ipaddr, 192,168,100,3); 161 | IP4_ADDR(&netmask, 255,255,255,0); 162 | IP4_ADDR(&gw, 192,168,100,1); 163 | netif_add(&netif, &ipaddr, &netmask, &gw, NULL, if_init, ethernet_input); 164 | // Set hardware address of netif. The ethernet driver doesn't send it 165 | // though, it doesn't even know its own MAC yet. 166 | netif_set_default(&netif); 167 | netif_set_up(&netif); 168 | // If IPv6: set linklocal address for interface 169 | #if LWIP_AUTOIP 170 | autoip_start(&netif); 171 | #elif LWIP_DHCP 172 | dhcp_start(&netif); 173 | #endif 174 | 175 | http_start(); 176 | 177 | check_timers(); 178 | for (;;) { 179 | ipc_dest_t rcpt = fresh_handle; 180 | ipc_arg_t arg2 = 0; 181 | ipc_msg_t msg = recv2(&rcpt, &arg1, &arg2); 182 | //debug("lwip: received %lx from %lx: %lx %lx\n", msg, rcpt, arg1, arg2); 183 | if (rcpt == proto_handle) { 184 | switch (msg & 0xff) { 185 | case MSG_PULSE: 186 | for (int i = 0; i < 2 * NBUFS; i++) { 187 | if (arg1 & (UINT64_C(1) << i)) { 188 | buffer_done(i); 189 | } 190 | } 191 | break; 192 | default: 193 | debug("lwip: received %lx from %lx: %lx %lx\n", msg, rcpt, arg1, arg2); 194 | assert(false); 195 | break; 196 | } 197 | } else if (rcpt == timer_handle) { 198 | //debug("lwip: timer\n"); 199 | } else { 200 | debug("lwip: received %lx from %lx: %lx %lx\n", msg, rcpt, arg1, arg2); 201 | } 202 | if (rcpt == fresh_handle) { 203 | hmod_delete(rcpt); 204 | } 205 | check_timers(); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /cuser/msg_acpi.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | enum msg_acpi { 4 | /* Wrappers around PCI IRQ routing (to PIC or I/O APIC) */ 5 | MSG_ACPI_REG_IRQ = MSG_REG_IRQ, 6 | MSG_ACPI_IRQ_ACK = MSG_IRQ_ACK, 7 | /* Find (unclaimed) PCI device. 8 | * 9 | * arg1: pci vendor/device 10 | * arg2: index (0..) 11 | * Returns: 12 | * arg1: pci bus/device/function, or -1 if not found 13 | * 14 | * Iterate index upwards to find multiple matching PCI devices until -1 is 15 | * returned. 16 | */ 17 | MSG_ACPI_FIND_PCI, 18 | /* Claim a PCI device for the caller. 19 | * 20 | * arg1: pci bus/device/function 21 | * arg2: flags (etc) 22 | * low 4 bits: mask of pins to route IRQs for 23 | * bit 5: set to enable bus master if possible 24 | */ 25 | MSG_ACPI_CLAIM_PCI, 26 | /** 27 | * Read a 32-bit word from PCI config space. 28 | * 29 | * arg1: upper 24 bits: pci bus/device/function 30 | * lower 8 bits: config-space address, must be 32-bit aligned. 31 | * returns 32-bit value in arg1 32 | */ 33 | MSG_ACPI_READ_PCI, 34 | 35 | /** 36 | * Wait for ACPI init and initialize debugger. 37 | */ 38 | MSG_ACPI_DEBUGGER_INIT, 39 | /** 40 | * Add a character to the debugger command buffer. 41 | * 42 | * arg1: number of characters to add. Currently only 1 is supported, but 43 | * more characters could be encoded in fun ways. 44 | * arg2: character to add 45 | */ 46 | MSG_ACPI_DEBUGGER_BUFFER, 47 | /** 48 | * Interpret the command currently in the command buffer, and clear the 49 | * buffer. 50 | */ 51 | MSG_ACPI_DEBUGGER_CMD, 52 | /** 53 | * Clear the command buffer without running the command in it. 54 | */ 55 | MSG_ACPI_DEBUGGER_CLR_BUFFER, 56 | 57 | /** 58 | * Register an I/O APIC. Sent from ACPI driver to I/O APIC driver when 59 | * trying to use APIC mode. 60 | * 61 | * arg1: I/O APIC ID 0-255 62 | * arg2: Base physical address of I/O APIC 63 | * arg3: Global System Interrupt base of the I/O APIC. 64 | * Return: 65 | * arg1: Number of interrupt sources for this I/O APIC. 66 | */ 67 | MSG_ACPI_ADD_IOAPIC, 68 | }; 69 | // Return from MSG_ACPI_FIND_PCI when no device is found. 70 | static const uintptr_t ACPI_PCI_NOT_FOUND = -1; 71 | static const uintptr_t ACPI_PCI_CLAIM_MASTER = 16; 72 | 73 | -------------------------------------------------------------------------------- /cuser/test_maps.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | /* Test some stuff - map some physical memory and inspect it. */ 4 | 5 | static const uintptr_t zeropage = 4; 6 | 7 | static void inspect(const char* start, const char* end) { 8 | printf("Inspecting %x..%x\n", start, end); 9 | const u64* p = (const u64*)start; 10 | size_t n = 0; 11 | while (p < (const u64*)end) { 12 | uint64_t b = *p; 13 | if (b) { 14 | printf("%x: %p\n", p, b); 15 | n++; 16 | } 17 | p++; 18 | } 19 | printf("%x non-zero bytes in %x..%x\n", n, start, end); 20 | } 21 | 22 | void start() { 23 | char* pointer = (char*)0x200000; 24 | uintptr_t addr = 0x1234000; 25 | 26 | map(zeropage, PROT_READ, (void*)pointer, addr, 4096); 27 | prefault(pointer, PROT_READ); 28 | 29 | /*while (addr < 32*1024*1024)*/ { 30 | inspect(pointer, pointer + 4096); 31 | } 32 | for(;;); 33 | } 34 | -------------------------------------------------------------------------------- /cuser/timer_test.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "msg_timer.h" 3 | 4 | static const ipc_dest_t apic_handle = 4; 5 | static const ipc_arg_t start_delay = 1000; 6 | 7 | static const u8 PULSE_MIN = 0; 8 | static const u8 PULSE_MAX = 63; 9 | 10 | void start() { 11 | printf("timertest: starting.\n"); 12 | ipc_arg_t delay = start_delay; 13 | u8 pulse = PULSE_MIN; 14 | 15 | send2(MSG_REG_TIMER, apic_handle, delay, pulse); 16 | for (;;) { 17 | ipc_dest_t rcpt = 0; 18 | ipc_arg_t arg1; 19 | ipc_msg_t msg = recv1(&rcpt, &arg1); 20 | if ((msg & 0xff) == MSG_PULSE) { 21 | printf("timertest: T %x %x\n", rcpt, arg1); 22 | send2(MSG_REG_TIMER, apic_handle, delay *= 2, pulse = (pulse + 1) % (PULSE_MAX + 1)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cuser/zeropage.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | /* Silly little service that'll give you read-only (and execute) access to any 4 | * number of pages filled with zeroes. */ 5 | 6 | static const int ALLOWED_FLAGS = PROT_READ | PROT_EXECUTE; 7 | static const ipc_dest_t fresh = 256; 8 | 9 | static const char zeropage[4096] __attribute__((aligned(4096))); 10 | 11 | void start() { 12 | map(0, MAP_ANON | PROT_READ, (void*)&zeropage, 0, sizeof(zeropage)); 13 | /* Page is zeroed when we get it from pfalloc. PFALLOC gives one unique 14 | * page frame for each page faulted in from it. Until it runs out of 15 | * memory... */ 16 | /* Likewise from the kernel anonymous memory allocator, as long as we use 17 | * the backdoor... */ 18 | 19 | printf("zeropage: page at %p\n", (void*)&zeropage); 20 | if (*(const volatile char*)zeropage) { 21 | puts("zeropage: nonzero value in zero page!?"); 22 | } else { 23 | puts("zeropage: zero page is indeed zero"); 24 | } 25 | 26 | // Try to suicide 27 | // Sweet! This does crash! 28 | //((volatile char*)zeropage)[0]++; 29 | 30 | for (;;) { 31 | ipc_arg_t arg1, arg2; 32 | ipc_dest_t rcpt = fresh; 33 | ipc_msg_t msg = recv2(&rcpt, &arg1, &arg2); 34 | // PFAULT: source handle, offset, requested flags 35 | switch (msg & 0xff) { 36 | case MSG_PFAULT: { 37 | printf("zeropage: %x pfault %x %x\n", rcpt, arg1, arg2); 38 | grant(rcpt, zeropage, arg2 & ALLOWED_FLAGS); 39 | break; 40 | } 41 | default: 42 | printf("zeropage: unknown message %x from %x: %x %x\n", msg, rcpt, arg1, arg2); 43 | break; 44 | } 45 | if (rcpt == fresh) { 46 | hmod(rcpt, 0, 0); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /grub/.gitignore: -------------------------------------------------------------------------------- 1 | /logs/ 2 | /prefix-*/ 3 | /src/ 4 | -------------------------------------------------------------------------------- /grub/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Requires that you've already built the toolchain in toolchain/cross-X/bin 3 | 4 | . ../build/buildfuncs.sh 5 | 6 | CROSSVER=8.3.0 7 | GRUBVER=grub-2.02 8 | PREFIX=`pwd`/prefix-${GRUBVER}-${CROSSVER} 9 | LOGDIR=`pwd`/logs 10 | PATH="$PATH:$PREFIX/bin" 11 | toolchainBin=`pwd`/../toolchain/cross-$CROSSVER/bin 12 | [ -d "$toolchainBin" -a -x "$toolchainBin/$TARGET-gcc" ] || 13 | die "$toolchainBin/$TARGET-gcc not found or not executable - build cross toolchain first?" 14 | export PATH="$PATH:$toolchainBin" 15 | 16 | set -e 17 | 18 | mkdir -p src "$PREFIX" "$LOGDIR" 19 | 20 | cd src 21 | 22 | GET ftp://ftp.nluug.nl/mirror/gnu/grub/ "${GRUBVER}.tar.xz" 810b3798d316394f94096ec2797909dbf23c858e48f7b3830826b8daa06b7b0f 23 | unpack "${GRUBVER}" 24 | 25 | mkdir -p build-$GRUBVER-$CROSSVER 26 | cd build-$GRUBVER-$CROSSVER 27 | setlog grub_configure 28 | # efiemu is disabled because it has -Werror flags not fixed by --disable-werror 29 | CONFIGURE "$GRUBVER" -C --disable-werror --disable-efiemu --target=$TARGET --prefix="$PREFIX" TARGET_CC="ccache $TARGET-gcc" HOST_CC="ccache gcc" 30 | setlog grub_build 31 | MAKE 32 | MAKE install 33 | clearlog 34 | cd .. 35 | 36 | if ! command -v xorriso >/dev/null; then 37 | echo >&2 "Warning: xorriso is not installed - will be required for grub-mkrescue" 38 | fi 39 | -------------------------------------------------------------------------------- /include/common.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | %ifndef COMMON_INC_INCLUDED 4 | %define COMMON_INC_INCLUDED 5 | 6 | %include "macros.inc" 7 | %include "msr.inc" 8 | %include "segments.inc" 9 | %include "string.inc" 10 | %include "mboot.inc" 11 | %include "pages.inc" 12 | %include "messages.inc" 13 | %include "pic.inc" 14 | %include "probes.inc" 15 | 16 | %endif 17 | -------------------------------------------------------------------------------- /include/macros.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | ; callee-save: rbp, rbx, r12-r15 4 | ; caller-save: rax, rcx, rdx, rsi, rdi, r8-r11 5 | %macro clear_clobbered_syscall 0 6 | ; rax, rcx, r11 are also in this list, but are used for return, rip and rflags respectively. 7 | zero edx ; If we start returning more than one 64-bit value 8 | zero esi 9 | zero edi 10 | zero r8 11 | zero r9 12 | zero r10 13 | %endmacro 14 | 15 | %macro pushsection 1 16 | [section %1] 17 | %endmacro 18 | %macro popsection 0 19 | __SECT__ 20 | %endmacro 21 | 22 | %macro zero 1 23 | xor %1, %1 24 | %endmacro 25 | 26 | %macro restruc 1-2 1 27 | resb (%1 %+ _size) * %2 28 | %endmacro 29 | 30 | %macro SPIN_LOCK 1 31 | %%spin: 32 | lock bts dword %1, 1 33 | jc %%spin 34 | %endmacro 35 | 36 | %macro SPIN_UNLOCK 1 37 | mov byte %1, 0 38 | %endmacro 39 | 40 | %macro reslock 0-1 1 41 | resb %1 42 | %endmacro 43 | -------------------------------------------------------------------------------- /include/mboot.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | MBOOT_MAGIC equ 0x1BADB002 4 | 5 | ; flags 0..15: mandatory flags, we won't boot unless the loader supports them 6 | %define MBOOT_FLAG_PAGE_ALIGNED (1 << 0) 7 | %define MBOOT_FLAG_NEED_MEMMAP (1 << 1) 8 | %define MBOOT_FLAG_NEED_VIDMODE (1 << 2) 9 | ; flags 16..: optional flags, the loader is allowed to ignore these 10 | %define MBOOT_FLAG_LOADINFO (1 << 16) 11 | 12 | %macro assert 1 13 | %if (%1) == 0 14 | %error assert %1 failed 15 | %endif 16 | %endmacro 17 | 18 | %macro mboot 1 19 | %push mboot_header 20 | %assign mboot_flags %1 21 | align 4 22 | %%header_addr: 23 | dd MBOOT_MAGIC 24 | dd %1 25 | dd -(%1) - MBOOT_MAGIC 26 | %assign header_pos 12 27 | %endmacro 28 | 29 | %macro mboot_dd 1-* 30 | %assign header_pos (header_pos + (%0) * 4) 31 | %rep %0 32 | dd %1 33 | %rotate 1 34 | %endrep 35 | %endmacro 36 | 37 | ; Provide load address information. 38 | ; Takes five arguments: 39 | ; - header address 40 | ; - load address 41 | ; - load_end_address 42 | ; - bss_end_address 43 | ; - entry_address 44 | ; TODO We should be able to figure out the appropriate address of the header 45 | ; ourselves! 46 | %macro mboot_load 5 47 | assert (mboot_flags & MBOOT_FLAG_LOADINFO) 48 | assert (header_pos == 12) 49 | mboot_dd %1, %2, %3, %4, %5 50 | %endmacro 51 | 52 | %macro mboot_vidmode_text 0 53 | dd 1 54 | times 3 dd 0 55 | %endmacro 56 | 57 | %macro mboot_vidmode_any 0 58 | times 4 dd 0 59 | %endmacro 60 | 61 | %macro endmboot 0 62 | %if mboot_flags & (MBOOT_FLAG_LOADINFO) 63 | assert header_pos >= 32 64 | %endif 65 | %pop 66 | %endmacro 67 | 68 | %define MBI_FLAG_CMDLINE (1 << 2) 69 | %define MBI_FLAG_MODULES (1 << 3) 70 | %define MBI_FLAG_MMAP (1 << 6) 71 | 72 | ; Data of .vbe member of mboot info 73 | struc mboot_vbe 74 | .control_info resd 1 75 | .mode_info resd 1 76 | .mode resw 1 77 | .iface_seg resw 1 78 | .iface_off resw 1 79 | .iface_len resw 1 80 | endstruc 81 | 82 | struc mboot_fb 83 | .addr resq 1 84 | .pitch resd 1 85 | .width resd 1 86 | .height resd 1 87 | .bpp resb 1 88 | .type resb 1 89 | .colors resb 6 90 | endstruc 91 | 92 | MBOOT_FB_INDEXED equ 0 93 | MBOOT_FB_RGB equ 1 94 | MBOOT_FB_TEXT equ 2 95 | 96 | struc mboot_fb_palette 97 | .addr resd 1 98 | .count resw 1 99 | endstruc 100 | 101 | struc mboot_fb_pixelfmt 102 | .red_shift resb 1 103 | .red_mask resb 1 104 | .green_shift resb 1 105 | .green_mask resb 1 106 | .blue_shift resb 1 107 | .blue_mask resb 1 108 | endstruc 109 | 110 | struc mbootinfo 111 | .flags resd 1 112 | ; if flags[0]: 113 | .mem_lower resd 1 114 | .mem_upper resd 1 115 | ; if flags[1]: 116 | .boot_devices resd 1 117 | 118 | ; if (flags & MBI_FLAG_CMDLINE) 119 | .cmdline resd 1 120 | 121 | ; if (flags & MBI_FLAG_MODULES) 122 | .mods_count resd 1 123 | .mods_addr resd 1 124 | 125 | .syms resd 4 126 | 127 | ; if (flags & MBI_FLAG_MMAP) 128 | .mmap_length resd 1 129 | .mmap_addr resd 1 130 | 131 | .drives_length resd 1 132 | .drives_addr resd 1 133 | 134 | .config_table resd 1 135 | 136 | .boot_loader resd 1 137 | .apm_table resd 1 138 | 139 | .vbe restruc mboot_vbe 140 | .fb restruc mboot_fb 141 | endstruc 142 | 143 | struc mboot_mod 144 | .start resd 1 145 | .end resd 1 146 | .string resd 1 147 | resd 1 ; reserved 148 | endstruc 149 | -------------------------------------------------------------------------------- /include/module.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | %define in_module 1 4 | 5 | ; Takes one argument: entry point label 6 | %macro defmod 1 7 | jmp %1 8 | %endmacro 9 | 10 | %include "macros.inc" 11 | %include "messages.inc" 12 | %include "string.inc" 13 | 14 | bits 64 15 | default rel 16 | 17 | section .text vstart=0x100000 18 | section .rodata vfollows=.text follows=.text 19 | section module_end nobits vfollows=.rodata align=1 20 | section .bss nobits align=8 vfollows=.rodata align=4096 21 | 22 | section module_end 23 | end_of_module: 24 | section .text 25 | module: 26 | -------------------------------------------------------------------------------- /include/msr.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | MSR_APIC_BASE equ 0x1b 4 | MSR_EFER equ 0xc000_0080 5 | MSR_STAR equ 0xc000_0081 6 | MSR_LSTAR equ 0xc000_0082 7 | MSR_CSTAR equ 0xc000_0083 8 | MSR_SF_MASK equ 0xc000_0084 9 | MSR_GSBASE equ 0xc000_0101 10 | 11 | CR0_TS_BIT equ 3 12 | CR0_PE equ 0x00000001 13 | CR0_MP equ 0x00000002 14 | CR0_EM equ 0x00000004 15 | CR0_PG equ 0x80000000 16 | 17 | CR4_PAE equ 0x020 18 | CR4_MCE equ 0x040 19 | CR4_PGE equ 0x080 20 | CR4_PCE equ 0x100 21 | CR4_OSFXSR equ 0x200 22 | CR4_OSXMMEXCPT equ 0x400 23 | 24 | -------------------------------------------------------------------------------- /include/pic.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | PIC1_CMD equ 0x20 4 | PIC1_DATA equ 0x21 5 | PIC2_CMD equ 0xa0 6 | PIC2_DATA equ 0xa1 7 | 8 | ICW1_ICW4 equ 0x01 9 | ICW1_INIT equ 0x10 10 | 11 | ; 4 = bit 2 set = there's a slave on 2 12 | ICW3_MASTER equ 4 13 | ; 2 = slave has slave ID 2 14 | ICW3_SLAVE equ 2 15 | 16 | ICW4_8086 equ 0x01 17 | 18 | ; Send this on the command port to signal EOI 19 | PIC_EOI equ 0x20 20 | ; Send this + IRQ to do a specific EOI for some interrupt 21 | PIC_SEOI equ 0x60 22 | -------------------------------------------------------------------------------- /include/printf.inc: -------------------------------------------------------------------------------- 1 | ; vim:set filetype=nasm: 2 | 3 | bits 64 4 | section .text 5 | 6 | printf: 7 | ; al: number of vector arguments (won't be used...) 8 | ; rdi: format string 9 | ; rsi,rdx,rcx,r8,r9: consecutive arguments 10 | ; following parameters are on the stack pushed from right, so that the 11 | ; stack on entry is , arg 5, arg 6, etc 12 | 13 | ; reorder the stack a bit so that we have all our parameters in a row 14 | mov [rsp-32],rsi 15 | mov rsi,[rsp] 16 | mov [rsp-40],rsi ; rsp-40 is now the return address! 17 | mov [rsp-24],rdx 18 | mov [rsp-16],rcx 19 | mov [rsp-8],r8 20 | mov [rsp],r9 21 | sub rsp,40 22 | ; rdi: pointer to parameters 23 | ; rsi: pointer to format string 24 | mov rsi,rdi 25 | lea rdi,[rsp+8] 26 | 27 | push r12 28 | push r13 29 | push r14 30 | push r15 31 | push rbx 32 | 33 | .nextchar: 34 | lodsb 35 | test al,al 36 | jz .done 37 | cmp al,'%' 38 | je .handle_format 39 | 40 | .write_al: 41 | mov r12,rdi 42 | mov r13,rsi 43 | movzx edi,al 44 | call putchar 45 | mov rsi,r13 46 | mov rdi,r12 47 | jmp .nextchar 48 | 49 | .done: 50 | pop rbx 51 | pop r15 52 | pop r14 53 | pop r13 54 | pop r12 55 | add rsp,48 56 | jmp [rsp-48] 57 | 58 | .handle_format: 59 | lodsb 60 | cmp al,'c' 61 | je .fmt_c 62 | cmp al,'s' 63 | je .fmt_s 64 | cmp al,'p' 65 | je .fmt_p 66 | cmp al,'x' 67 | je .fmt_x 68 | ;cmp al,'%' 69 | jmp .write_al 70 | 71 | .fmt_c: 72 | mov rax,[rdi] 73 | add rdi,8 74 | jmp .write_al 75 | 76 | .fmt_s: 77 | ; syscall will clobber rsi and rdi but not r12 and r13 78 | lea r13,[rdi+8] 79 | mov r12,rsi 80 | 81 | lea rsi, [rel null_str] 82 | mov rdi, [rdi] 83 | test rdi, rdi 84 | cmovz rdi, rsi 85 | call puts 86 | 87 | mov rsi,r12 88 | mov rdi,r13 89 | jmp .nextchar 90 | 91 | .fmt_p: 92 | cmp qword [rdi], 0 93 | ; Rely on the special-case for null strings to print (null) 94 | jz .fmt_s 95 | .fmt_x: 96 | lea r13,[rdi+8] 97 | mov r12,rsi 98 | mov rbx, [rdi] 99 | 100 | cmp al, 'x' 101 | setz r15b 102 | 103 | mov edi, '0' 104 | call putchar 105 | mov edi, 'x' 106 | call putchar 107 | 108 | mov cl, 64 109 | 110 | ; cl = highest bit of first hex-digit to print (>= 4) 111 | ; rbx = number to print 112 | .print_digits: 113 | .loop: 114 | sub cl, 4 115 | mov rax, rbx 116 | shr rax, cl 117 | and eax, 0xf 118 | jnz .print 119 | ; digit is 0 - is this the last digit (cl = 0)? Then always print it. 120 | test cl, cl 121 | jz .print 122 | ; non-last zero digit - r15b is 1 if printing 'x' (not a pointer), 123 | ; meaning we should skip until the first non-zero digit. 124 | test r15b, r15b 125 | jnz .loop 126 | .print: 127 | ; al = digit to print 128 | cmp al, 10 129 | jb .low 130 | add al, 'a' - 10 - '0' 131 | .low: 132 | add al, '0' 133 | mov edi, eax 134 | ; we've printed, so make sure we print all following digits now 135 | mov r15b, 0 136 | push rcx 137 | call putchar 138 | pop rcx 139 | .next_digit: 140 | test cl, cl 141 | jnz .loop 142 | 143 | mov rsi,r12 144 | mov rdi,r13 145 | jmp .nextchar 146 | 147 | puts: 148 | ; callee-save: rbp, rbx, r12-r15 149 | mov rsi,rdi 150 | 151 | .loop: 152 | lodsb 153 | mov edi,eax 154 | test dil,dil 155 | jz .ret 156 | 157 | push rsi 158 | call putchar 159 | pop rsi 160 | jmp .loop 161 | 162 | .ret: 163 | ret 164 | 165 | pushsection .rodata 166 | null_str: 167 | db '(null)', 0 168 | popsection 169 | -------------------------------------------------------------------------------- /include/segments.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | %macro define_segment 3 ; limit, address, flags and access 4 | dw %1 & 0xffff ;seg_limit 5 | dw %2 & 0xffff ;addr_00_15 6 | db ((%2) >> 16) & 0xff ;addr_16_23 7 | db (%3) >> 4 ;access 8 | db ((%3) << 4) & 0xf0 | ((%1) >> 16) & 0x0f ;flags/limit 9 | db ((%2) >> 24) & 0xff ;addr_24_31 10 | %endmacro 11 | 12 | %macro define_tss64 2 ; limit, address 13 | define_segment %1, (%2) & 0xffffffff, SEG_PRESENT | SEG_SYSTEM | SEG_TYPE_TSS 14 | dd (%2) >> 32 15 | dd 0 16 | %endmacro 17 | 18 | ; Bit Field 19 | ; 7 Present = 1 20 | ; 6..5 Ring == 0 21 | ; 4 Descriptor type == 1 (user) 22 | ; 3..0 Type = cs: 1010, ds: 0010 23 | 24 | SEG_PRESENT equ 1000_0000_0000b 25 | SEG_USER equ 0001_0000_0000b 26 | SEG_SYSTEM equ 0000_0000_0000b 27 | SEG_DPL3 equ 0110_0000_0000b 28 | 29 | SEG_TYPE_CODE equ 1000_0000b 30 | SEG_TYPE_DATA equ 0 31 | SEG_TYPE_TSS equ 1001_0000b 32 | 33 | CODE_SEG_RX equ 0010_0000b 34 | DATA_SEG_RW equ 0010_0000b 35 | 36 | RX_ACCESS equ SEG_PRESENT | SEG_USER | SEG_TYPE_CODE | CODE_SEG_RX 37 | RW_ACCESS equ SEG_PRESENT | SEG_USER | SEG_TYPE_DATA | DATA_SEG_RW 38 | 39 | ; Bits in the flags/limit byte - scaled down by one nibble (the low is the high 40 | ; bits of the limit) 41 | ; Bit Field 42 | ; 7 Granularity 43 | ; 6 Default Operand Size (D/B) 44 | GRANULARITY equ 1000b 45 | SEG_32BIT equ 0100b 46 | SEG_64BIT equ 0010b 47 | 48 | code_seg equ 8 49 | data_seg equ 16 50 | code64_seg equ 24 51 | data64_seg equ 32 52 | tss64_seg equ 40 53 | user_code_seg equ 56 54 | ; code32_user equ 56 55 | ; data32_user equ 64 56 | user_cs equ user_code_seg+16 | 11b 57 | user_ds equ user_cs+8 58 | 59 | %macro define_gate64 3 ; code-seg, offset, flags 60 | dw (%2) & 0xffff 61 | dw %1 62 | db 0 63 | db %3 64 | dw ((%2) >> 16) & 0xffff 65 | dd (%2) >> 32 66 | dd 0 67 | %endmacro 68 | 69 | GATE_PRESENT equ 1000_0000b 70 | GATE_TYPE_INTERRUPT equ 0000_1110b 71 | ; Among other(?) things, a task gate leaves EFLAGS.IF unchanged when invoking the gate 72 | GATE_TYPE_TASK equ 0000_1111b 73 | 74 | -------------------------------------------------------------------------------- /include/string.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | %define strsection .rodata 3 | 4 | %macro STR 1+ 5 | [section strsection] 6 | %xdefine _STR %%str 7 | _STR: db %1, 0 8 | __SECT__ 9 | %endmacro 10 | 11 | %macro lodstr 2+ 12 | STR %2 13 | %ifidn %1, rdi 14 | %ifdef in_module 15 | %error lea rdi is wasteful in user modules 16 | %endif 17 | %endif 18 | lea %1, [_STR] 19 | %endmacro 20 | -------------------------------------------------------------------------------- /kasm/aspace.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | ; Ordinary and boring flags 4 | MAPFLAG_X equ 1 5 | MAPFLAG_W equ 2 6 | MAPFLAG_R equ 4 7 | MAPFLAG_RWX equ 7 ; All/any of the R/W/X flags 8 | 9 | ; Anonymous page: allocate frame on first use 10 | MAPFLAG_ANON equ 8 11 | ; Backdoor flag for physical memory mapping. handle is 0, .offset is (paddr - 12 | ; vaddr). 13 | MAPFLAG_PHYS equ 16 14 | ; Mix: automatically allocated page that is "locked" (really it only differs in 15 | ; that it's allocated at map time and the physical address is returned to the 16 | ; user). 17 | MAPFLAG_DMA equ (MAPFLAG_PHYS | MAPFLAG_ANON) 18 | MAPFLAG_PCD equ 32 19 | ; PWT (page write-through) too? 20 | 21 | ; mapcard: the handle, offset and flags for the range of virtual addresses until 22 | ; the next card. 23 | ; 5 words: 24 | ; - 3 for dict_node w/ vaddr 25 | ; - 1 handle 26 | ; - 1 offset+flags 27 | ; (since offsets must be page aligned we have 12 left-over bits) 28 | ; This structure is completely unrelated to the physical pages backing virtual 29 | ; memory - it represents each process' wishful thinking about how their memory 30 | ; should look. backings and sharings control physical memory. 31 | struc mapcard 32 | .as_node restruc dict_node 33 | .vaddr equ .as_node 34 | .handle resq 1 35 | ; .vaddr + .offset = handle-offset to be sent to backer on fault 36 | ; For a direct physical mapping, paddr = .vaddr + .offset 37 | ; .offset = handle-offset - vaddr 38 | ; .offset = paddr - .vaddr 39 | .offset resq 1 40 | .flags equ .offset ; low byte (12 bits?) of offset is flags 41 | endstruc 42 | 43 | ; backing: mapping *one page* to the place that page came from. 44 | ; Indexed by vaddr for the process that maps it. The vaddr includes flags, so 45 | ; look up by vaddr|0xfff. 46 | ; This is likely to exist once per physical page per process. Should be 47 | ; minimized. 48 | ; 6 words: 49 | ; - 3 words for dict_node w/ vaddr 50 | ; - 1 word for parent 51 | ; - 2 words for child-list links 52 | ; 53 | ; Could be reduced to 4 words: flags, parent, child-list links, if moving to 54 | ; an external dictionary. 32 bytes per page gives 128 entries in the last-level 55 | ; table. Flags could indicate how many levels that are required, and e.g. a 56 | ; very small process could have only one level, and map 128 pages at 0..512kB. 57 | struc backing 58 | .as_node restruc dict_node 59 | .vaddr equ .as_node 60 | ; Flags stored in low bits of vaddr! 61 | .flags equ .vaddr 62 | ; Pointer to parent sharing. Needed to unlink self when unmapping. 63 | ; Could have room for flags (e.g. to let it be a paddr when we don't 64 | ; need the parent - we might have a direct physical address mapping) 65 | .parent resq 1 66 | ; Space to participate in parent's list of remappings. 67 | .child_node restruc dlist_node 68 | endstruc 69 | 70 | ; sharing: mapping one page to every place it's been shared to 71 | ; 7 words! 72 | struc sharing 73 | .as_node restruc dict_node 74 | .vaddr equ .as_node 75 | .paddr resq 1 76 | .aspace resq 1 77 | .children restruc dlist 78 | endstruc 79 | 80 | struc aspace 81 | ; Upon setup, pml4 is set to a freshly allocated frame that is empty 82 | ; except for the mapping to the kernel memory area (which, as long as 83 | ; it's less than 4TB is only a single entry in the PML4). 84 | ; (This is the virtual address in the higher half. proc.cr3 is a 85 | ; physical address.) 86 | .pml4 resq 1 87 | ; TODO Lock structure for multiprocessing 88 | .count resq 1 89 | ; Do we need a list of processes that share an address space? 90 | ; (That would remove the need for .count, I think.) 91 | ;.procs resq 1 92 | .handles restruc dict 93 | .pending restruc dict 94 | 95 | .mapcards restruc dict 96 | .backings restruc dict 97 | .sharings restruc dict 98 | endstruc 99 | 100 | -------------------------------------------------------------------------------- /kasm/messages_con.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | MSG_CON_WRITE equ MSG_USER 4 | ; Should be sendrcv'd, returns one byte of data (when something arrives) 5 | MSG_CON_READ equ MSG_USER + 1 6 | -------------------------------------------------------------------------------- /kasm/messages_io.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | ; I/O Port 'in' 4 | ; 5 | ; Parameters: 6 | ; * io port number 7 | ; * data size, either 1, 2 or 4 8 | ; (Is 64-bit port i/o possible?) 9 | ; 10 | ; Returns the received data. Use as a call. 11 | MSG_IOPORT_IN equ MSG_USER + 0 12 | ; I/O port 'out' 13 | ; 14 | ; * io port number 15 | ; * data size: 1, 2, or 4 16 | ; * data (1-4 bytes actually used) 17 | MSG_IOPORT_OUT equ MSG_USER + 1 18 | 19 | -------------------------------------------------------------------------------- /kasm/messages_irq.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | ; Register for IRQ 4 | ; 5 | ; Should be sent as a sendrcv from a fresh handle - this handle will become 6 | ; associated with the given IRQ. The IRQ will send MSG_IRQ_T when triggered and 7 | ; expects to receive back a MSG_IRQ_ACK when the interrupt is handled. 8 | ; 9 | ; IRQ messages will be sent asynchronously - if the receivers are not in a 10 | ; message receive phase at that time the IRQ will be dropped. 11 | ; 12 | ; Takes one parameter, the IRQ number to listen for. 13 | MSG_REG_IRQ equ MSG_USER + 0 14 | 15 | ; The IRQ has been handled, feel free to signal us again when something happens. 16 | MSG_IRQ_ACK equ MSG_USER + 1 17 | -------------------------------------------------------------------------------- /kasm/pages.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | %macro respage 0-1 1 4 | resb (4096*%1) 5 | %endmacro 6 | 7 | ; Includes the .bss section (really, the pages. stuff should actually be in the 8 | ; .bss section I think). Though it is required by the 32-bit bootstrap as well, 9 | ; so it needs well-defined addresses. 10 | %ifndef kernel_pages 11 | %define kernel_pages 4 12 | %endif 13 | 14 | struc pages, 0x100000 15 | .kernel respage kernel_pages 16 | 17 | .page_tables: 18 | .pml4 respage 19 | .low_pdp respage 20 | .low_pd respage 21 | .kernel_pdp respage 22 | %if use_1gb_pages == 0 23 | .kernel_pd respage 24 | %endif 25 | .page_tables_end: 26 | 27 | ; Annoying: BSP needs a stack quite early in the bootstrap, so it needs to be 28 | ; static, but APs will always use a heap-allocated stack. It would be nice to 29 | ; unify it and always heap-allocate the stack. 30 | ; (Can't *call* allocate_frame without a stack...) 31 | .kernel_stack respage 32 | .kernel_stack_end: 33 | 34 | .stats respage 35 | endstruc 36 | 37 | kernel_stack_end equ pages.kernel_stack_end 38 | kernel_reserved_end equ pages + pages_size 39 | free_mem_start equ kernel_reserved_end 40 | 41 | kernel_base equ -(1 << 30) 42 | -------------------------------------------------------------------------------- /kasm/probes.inc: -------------------------------------------------------------------------------- 1 | %define probesection .stats 2 | %macro DEFPROBE 1 3 | [section probesection] 4 | %xdefine _PROBE .probe_ %+ %1 5 | _PROBE: resd 1 6 | __SECT__ 7 | %endmacro 8 | 9 | %macro PROBE 1 10 | %if probes 11 | DEFPROBE %1 12 | inc dword [_PROBE] 13 | %else 14 | ; Nothing 15 | %endif 16 | %endmacro 17 | -------------------------------------------------------------------------------- /kasm/proc.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | %assign i 0 4 | 5 | %macro reglabels 1-* 6 | %rep %0 7 | .r %+ %1 equ .regs+(i * 8) 8 | %assign i i+1 9 | %rotate 1 10 | %endrep 11 | %endmacro 12 | 13 | struc proc, -0x80 14 | .regs resq 16 ; a,c,d,b,sp,bp,si,di,r8-15 15 | 16 | ; Aliases for offsets into regs 17 | reglabels ax,cx,dx,bx,sp,bp,si,di 18 | %rep 8 19 | reglabels i 20 | %endrep 21 | 22 | .rip resq 1 23 | .endregs equ .rip 24 | .rflags resq 1 25 | .flags resq 1 ; See PROC_* 26 | ; Pointer to the process we're waiting for (if any). See flags below. 27 | .waiting_for resq 1 28 | ; Pointer to the first process waiting on this process. 29 | .waiters restruc dlist 30 | ; If/when in a list, points to next process in list 31 | .node restruc dlist_node 32 | ; Physical address of PML4 to put in CR3 33 | .cr3 resq 1 34 | .aspace resq 1 35 | .count resq 1 36 | ; When PROC_PFAULT is set, the virtual address that faulted 37 | ; Note that we lose a lot of data about the mapping that we looked up 38 | ; in PFAULT, and have to look up again in GRANT. This is intentional, 39 | ; since we have to verify and match the GRANT to the correct page, we 40 | ; simply don't save anything that might be wrong. 41 | ; The lower bits are access flags for the fault/request. 42 | .fault_addr resq 1 43 | 44 | align 16 45 | .fxsave resb 512 46 | endstruc 47 | 48 | %macro defbit 2 49 | %1 %+ _BIT equ %2 50 | %1 equ (1 << (%1 %+ _BIT)) 51 | %endmacro 52 | 53 | ; The process is currently queued on the run queue. 54 | defbit PROC_ON_RUNQUEUE, 0 55 | ; Can return to user-mode with sysret, only some registers will be restored: 56 | ; rsp, rip: restored to previous values 57 | ; rcx, r11: rip and rflags, respectively 58 | ; rax: syscall return value 59 | ; Remaining registers will be 0 (?) 60 | ; TODO where do we set this? do we use it? 61 | defbit PROC_FASTRET, 1 62 | ; IN_RECV: Similar to FASTRET, when waiting for a message-send rendezvous 63 | ; When set together with IN_SEND, it's a sendrcv and the SEND needs to finish 64 | ; first. 65 | ; At any time when IN_RECV is set, the proc's saved rdi contains a pointer to 66 | ; the handle being received from. 67 | ; When a process starts a receive, until it becomes properly blocked on some 68 | ; process or finishes the receive immediately, it will be both RUNNING and 69 | ; IN_RECV. 70 | defbit PROC_IN_RECV, 2 71 | ; Process is trying to do a synchronous send or sendrcv, blocking on the 72 | ; waiting_for process to reach a PROC_IN_RECV state. Both IN_SEND and IN_RECV 73 | ; can be set at the same time. 74 | ; At any time when IN_SEND is set, the proc's saved rdi contains a pointer to 75 | ; the handle being sent to. 76 | ; When a process starts a send, until it becomes properly blocked on some 77 | ; process or finishes the operation, it will be both RUNNING and IN_SEND. 78 | defbit PROC_IN_SEND, 3 79 | ; Is the currently running process 80 | defbit PROC_RUNNING, 4 81 | ; Process has had a page fault that requires a response from a backer, or has 82 | ; requested a page paged in. 83 | ; proc.fault_addr is the address that faulted/was requested. 84 | defbit PROC_PFAULT, 5 85 | -------------------------------------------------------------------------------- /kasm/sections.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | section .rodata 4 | rodata_vstart_dummy: 5 | section .bss 6 | bss_vstart_dummy: 7 | section .text 8 | text_vstart_dummy: 9 | 10 | ; get the physical address of a symbol in the .text section 11 | %define text_paddr(sym) (section..text.vstart + sym - text_vstart_dummy) 12 | %define bss_paddr(sym) (section..bss.vstart + sym - bss_vstart_dummy) 13 | %define rodata_paddr(sym) (section..rodata.vstart + sym - rodata_vstart_dummy) 14 | ; get the virtual (kernel) address for a symbol in the .text section 15 | %define text_vpaddr(sym) phys_vaddr(text_paddr(sym)) 16 | ; get the virtual (kernel) address for a .bss-section symbol 17 | %define bss_vpaddr(sym) phys_vaddr(bss_paddr(sym)) 18 | %define rodata_vpaddr(sym) phys_vaddr(rodata_paddr(sym)) 19 | ; translate a physical address to a virtual address in the 'core' 20 | ; Note: in most cases, RIP-relative addressing is enough (since we tell the 21 | ; assembler we're based at 0x8000 while we're actually at kernel_base+0x8000), 22 | ; but this can be used for constant data or wherever we need the full address. 23 | %define phys_vaddr(phys) kernel_base + phys 24 | -------------------------------------------------------------------------------- /kasm/start32.asm: -------------------------------------------------------------------------------- 1 | global gdtr 2 | extern start64 3 | extern section.data.end 4 | global start32_mboot 5 | global start32.trampoline 6 | 7 | global mbi_pointer 8 | global memory_start 9 | global kernel_pdp 10 | 11 | global gdtr 12 | 13 | section..text.vstart equ pages.kernel 14 | kernel_pdp equ pages.kernel_pdp 15 | %define kernel_pages 16 16 | 17 | %define mboot_use_cmdline 1 18 | %define use_1gb_pages 0 19 | 20 | %include "sections.inc" 21 | %include "start32.inc" 22 | -------------------------------------------------------------------------------- /kasm/start32.inc: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | %include "common.inc" 4 | 5 | align 4 6 | mboot_header: 7 | mboot MBOOT_FLAG_LOADINFO | MBOOT_FLAG_NEED_MEMMAP 8 | ; | MBOOT_FLAG_NEED_VIDMODE 9 | mboot_load \ 10 | text_paddr(mboot_header), \ 11 | section..text.vstart, \ 12 | section.data.end, \ 13 | kernel_reserved_end, \ 14 | text_paddr(start32_mboot) 15 | ;mboot_vidmode_text 16 | endmboot 17 | 18 | bits 32 19 | start32_mboot: 20 | ; The multiboot loader will have set cs and ds to sensible segments, 21 | ; but we have no guarantees that the IDT and GDT tables still exist 22 | ; or what they contain. 23 | ; We don't need IDT until we enable interrupts (after long mode) 24 | ; Trick: use o16 to read a 24-bit offset instead of a 32-bit one 25 | ; The high byte is 0xc0 because the 32-bit offset at idtr is in fact 26 | ; just the lower 32-bit part of a 64-bit offset 0xffffffffc000XXXX. 27 | o16 lgdt [gdtr] 28 | 29 | cmp eax, 0x2BADB002 30 | ; TODO Put some error message on the screen at least 31 | jne $ 32 | 33 | test byte [ebx + mbootinfo.flags], MBI_FLAG_MMAP 34 | jz $ ; Infinite loop fail! :) 35 | 36 | find_start_of_memory: 37 | ; Start of memory. Starts at 1MB, gets poked for any multiboot data we 38 | ; see stored above 1MB. Note: assumes everything is stored *starting* 39 | ; at 1MB, will break down if grub e.g. loads the modules at the *end* 40 | ; of ram instead... 41 | mov ebp, free_mem_start 42 | 43 | ; Some notes: 44 | ; kvm seems to put the modules and multiboot info directly after the 45 | ; end of the bss data. Our pages struct (outside BSS) normally 46 | ; prevents that from working. 47 | ; 48 | ; grub seems to load modules as early as possible after 0x100000. 49 | ; If the kernel is in low memory, the multiboot info is in low memory 50 | ; but the modules are still at 1MB. 51 | ; If the kernel is loaded above 1MB, the multiboot info may still be 52 | ; below 1MB. 53 | 54 | find_mod_ends: 55 | mov esi, [ebx + mbootinfo.mods_addr] 56 | mov ecx, [ebx + mbootinfo.mods_count] 57 | .mods_loop: 58 | jecxz .mods_loop_out 59 | 60 | mov eax, [esi + mboot_mod.end] 61 | cmp eax, ebp 62 | jb .below 63 | mov ebp, eax 64 | .below: 65 | add esi, mboot_mod_size 66 | loop .mods_loop 67 | .mods_loop_out: 68 | 69 | ; Align start-of-memory to page boundary (just to keep out of the 70 | ; modules) 71 | add ebp, 0xfff 72 | and bp, 0xf000 73 | 74 | copy_multiboot_info: 75 | mov [mbi_pointer], ebp 76 | ;mov [orig_mbi_pointer], ebx 77 | 78 | mov esi, ebx 79 | mov edi, ebp 80 | mov ecx, mbootinfo_size / 4 81 | rep movsd 82 | 83 | ; TODO Should check for MBI_FLAG_MMAP 84 | ; Copy the memory mappings 85 | mov esi, [ebx + mbootinfo.mmap_addr] 86 | mov [ebp + mbootinfo.mmap_addr], edi 87 | mov ecx, [ebx + mbootinfo.mmap_length] 88 | rep movsb 89 | 90 | %ifdef mboot_use_cmdline 91 | test byte [ebx + mbootinfo.flags], MBI_FLAG_CMDLINE 92 | jz .no_cmdline 93 | 94 | mov esi, [ebx + mbootinfo.cmdline] 95 | mov [ebp + mbootinfo.cmdline], edi 96 | .strcpy_loop: 97 | lodsb 98 | stosb 99 | test al,al 100 | jnz .strcpy_loop 101 | 102 | .no_cmdline: 103 | %endif 104 | copy_modules: 105 | ; If we have modules, copy them too 106 | test byte [ebx + mbootinfo.flags], MBI_FLAG_MODULES 107 | jz .no_modules 108 | 109 | mov esi, [ebx + mbootinfo.mods_addr] 110 | mov [ebp + mbootinfo.mods_addr], edi 111 | mov ecx, [ebx + mbootinfo.mods_count] 112 | jecxz .no_modules 113 | lea ecx, [ecx * (mboot_mod_size / 4)] 114 | rep movsd 115 | 116 | mov esp, [ebx + mbootinfo.mods_addr] 117 | mov edx, [ebp + mbootinfo.mods_addr] 118 | mov ecx, [ebx + mbootinfo.mods_count] 119 | .loop: 120 | mov esi, [esp + mboot_mod.string] 121 | mov [edx + mboot_mod.string], edi 122 | .cp_str: 123 | lodsb 124 | test al,al 125 | stosb 126 | jnz .cp_str 127 | add esp, mboot_mod_size 128 | add edx, mboot_mod_size 129 | loop .loop 130 | .no_modules: 131 | 132 | ; Memory start is just after the copied data 133 | add edi, 0xfff 134 | and di, 0xf000 135 | mov [memory_start], edi 136 | 137 | start32: 138 | 139 | ; Remap PICs to 0x20..0x2f. We do this despite the fact we're going to 140 | ; disable them just afterwards, since spurious interrupts may still 141 | ; happen (and in any case, we want to be able to tell exceptions from 142 | ; IRQs). 143 | mov al, ICW1_INIT | ICW1_ICW4 144 | out PIC1_CMD, al 145 | out PIC2_CMD, al 146 | mov al, 0x20 147 | out PIC1_DATA, al 148 | mov al, 0x28 149 | out PIC2_DATA, al 150 | mov al, ICW3_MASTER 151 | out PIC1_DATA, al 152 | mov al, ICW3_SLAVE 153 | out PIC2_DATA, al 154 | 155 | mov al, ICW4_8086 156 | out PIC1_DATA, al 157 | out PIC2_DATA, al 158 | 159 | ; Disable PICs. Multiboot doesn't guarantee anything about their state. 160 | mov al,0xff 161 | out PIC1_DATA, al 162 | out PIC2_DATA, al 163 | 164 | mov ebx, 0xb8000 165 | mov edi, ebx 166 | 167 | xor eax,eax 168 | mov ecx,2*80*25/4 ; 125 << 3 169 | rep stosd 170 | 171 | mov word [ebx],0x0f00+'P' 172 | 173 | ; Static page allocations: 174 | ; The first 2MB are identity mapped, for now we assume that's enough to 175 | ; get started and up into the higher half. The low 2MB mapping is also 176 | ; used for the AP trampoline. 177 | ; It helps that the smallest granularity is 2MB here, we don't have to 178 | ; allocate any PT (last-level, 4kB mapping) tables. 179 | ; We need 5 page tables: 180 | ; pages.pml4: PML4 put in CR3 181 | ; - 0: low_pdp 182 | ; - 0: low_pd 183 | ; - 0: 2MB page at 0 184 | ; - 511: kernel_pdp 185 | ; - 511: (1GB pages): one 1GB page at 0 186 | ; - 511: kernel_pd 187 | ; - 0..511: 2MB page at matching physical addresses 188 | 189 | PT_PRESENT equ 1 190 | PT_WRITE equ 2 191 | PT_USER equ 4 192 | PT_PS equ 0x80 193 | PT_GLOBAL equ 0x100 194 | 195 | ; "Default" flags for our mappings: kernel-only, writable, present 196 | ; Could add global too, but I think it's not a universal CPU feature. Using 197 | ; global page mappings requires that we do a more explicit TLB flush when we 198 | ; remove the low-memory mappings too. (Or that we use it only for the upper 199 | ; memory mappings.) 200 | PT_FLAGS equ PT_WRITE | PT_PRESENT 201 | 202 | ; Write PML4 (one entry, pointing to one PDP) 203 | mov edi, pages.pml4 204 | mov eax, pages.low_pdp | PT_FLAGS 205 | stosd 206 | 207 | zero eax 208 | mov ecx, 0x03ff 209 | rep stosd 210 | 211 | KERNEL_PML4E equ pages.kernel_pdp | PT_FLAGS 212 | ; Upper memory mappings at 511 213 | mov dword [edi-8], KERNEL_PML4E 214 | 215 | ; edi = low_pdp. 216 | ; Write PDP (one entry, pointing to one PD) 217 | mov eax, pages.low_pd | PT_FLAGS 218 | stosd 219 | 220 | xor eax,eax 221 | mov ecx, 0x03ff 222 | rep stosd 223 | 224 | ; Write PD (one entry, pointing to a 2MB page at addr 0) 225 | ; edi = low_pd 226 | mov eax, 0 | PT_PS | PT_FLAGS 227 | stosd 228 | xor eax,eax 229 | mov ecx, 0x03ff 230 | rep stosd 231 | 232 | ; Page mapping for kernel space (top 4TB part) 233 | ; edi = kernel_pdp 234 | xor eax, eax 235 | mov ecx, 0x0400 236 | rep stosd 237 | %if use_1gb_pages 238 | ; Write a single 1GB page mapping at physical address 0 239 | mov word [edi - 8], 0 | PT_PS | PT_FLAGS ; | PT_GLOBAL 240 | %else 241 | ; Pointer to PD for upper 1GB of memory 242 | mov dword [edi - 8], pages.kernel_pd | PT_FLAGS 243 | ; edi = kernel_pd 244 | .fill_pd: 245 | mov eax, 0 | PT_PS | PT_FLAGS 246 | ; Fill in with 512 x 2MB blocks 247 | mov ecx, 512 248 | .loop: 249 | stosd 250 | add edi, 4 251 | add eax, 1 << 21 252 | loop .loop 253 | %endif 254 | 255 | ; Start mode-switching 256 | mov eax, CR4_PAE | CR4_MCE | CR4_PGE | CR4_PCE | CR4_OSFXSR | CR4_OSXMMEXCPT 257 | mov cr4, eax 258 | 259 | mov edx, pages.pml4 ; address of PML4 260 | mov cr3, edx 261 | 262 | EFER_SCE equ 1 263 | EFER_LME equ 0x100 264 | ; LMA 0x400 265 | EFER_NXE equ 0x800 266 | 267 | mov ecx, MSR_EFER 268 | mov eax, EFER_NXE | EFER_LME | EFER_SCE 269 | cdq ; clear edx 270 | wrmsr 271 | 272 | mov eax,cr0 273 | or eax, CR0_PG | CR0_MP ; Enable paging, monitor-coprocessor 274 | and al, ~CR0_EM ; Disable Emulate Coprocessor 275 | mov cr0,eax 276 | 277 | jmp code64_seg:.trampoline 278 | 279 | bits 64 280 | .trampoline: 281 | mov rsp, phys_vaddr(kernel_stack_end) 282 | ; Start by jumping into the kernel memory area at -1GB. Since that's a 283 | ; 64-bit address, we must do it in long mode... 284 | jmp start64 285 | 286 | section .bss 287 | mbi_pointer resd 1 288 | memory_start resd 1 289 | ;orig_mbi_pointer resd 1 290 | 291 | section .text 292 | align 8 293 | gdt_start: 294 | define_segment 0,0,0 295 | ; KERNEL segments 296 | ; 32-bit code/data. Used for running ancient code in compatibility mode. (i.e. nothing) 297 | define_segment 0xfffff,0,RX_ACCESS | GRANULARITY | SEG_32BIT 298 | define_segment 0xfffff,0,RW_ACCESS | GRANULARITY | SEG_32BIT 299 | ; 64-bit code/data. Used. 300 | define_segment 0,0,RX_ACCESS | SEG_64BIT 301 | define_segment 0,0,RW_ACCESS 302 | ; 64-bit TSS 303 | define_tss64 0x68, text_vpaddr(tss) 304 | ; USER segments 305 | ; 32-bit code/data. Used for running ancient code in compatibility mode. (i.e. nothing) 306 | define_segment 0xfffff,0,RX_ACCESS | GRANULARITY | SEG_32BIT | SEG_DPL3 307 | define_segment 0xfffff,0,RW_ACCESS | GRANULARITY | SEG_32BIT | SEG_DPL3 308 | ; 64-bit code/data. Used. 309 | define_segment 0,0,RX_ACCESS | SEG_64BIT | SEG_DPL3 310 | define_segment 0,0,RW_ACCESS | SEG_DPL3 311 | gdt_end: 312 | 313 | section .text 314 | tss: 315 | dd 0 ; Reserved 316 | ; Interrupt stack when interrupting non-kernel code and moving to CPL 0 317 | dq phys_vaddr(kernel_stack_end) 318 | dq 0 319 | dq 0 320 | times 0x66-28 db 0 321 | ; IOPB starts just after the TSS 322 | dw 0x68 323 | 324 | section .rodata 325 | align 4 326 | gdtr: 327 | dw gdt_end-gdt_start-1 ; Limit 328 | dq text_vpaddr(gdt_start) ; Offset 329 | 330 | -------------------------------------------------------------------------------- /kcpp/Makefile: -------------------------------------------------------------------------------- 1 | # NB! This make file depends on cross-toolchain setup in toplevel makefile, so 2 | # it's not actually independently buildable anymore. 3 | 4 | include ../build/common.mk 5 | include ../build/makejobs.mk 6 | 7 | .PHONY: all clean 8 | 9 | OUT ?= out 10 | GRUBDIR ?= ../out/grub 11 | 12 | YASM ?= ../yasm/yasm 13 | 14 | CFLAGS = -g -Os -ffunction-sections -fdata-sections 15 | CFLAGS += -fvisibility-inlines-hidden -fvisibility=hidden 16 | CFLAGS += -mcmodel=kernel -mno-red-zone -mno-sse -mno-mmx 17 | CFLAGS += -ffreestanding $(COPTFLAGS) 18 | CFLAGS += -fno-threadsafe-statics 19 | CFLAGS += -Wall -Wextra -Werror -Wno-error=unused-parameter 20 | COPTFLAGS = -fno-unroll-loops -funit-at-a-time 21 | CXXFLAGS = $(CFLAGS) -std=gnu++17 22 | LDFLAGS = -Wl,--check-sections -Wl,--gc-sections 23 | LDFLAGS += -Wl,--oformat=elf64-x86-64 24 | LDFLAGS += -Wl,-Map,$(@:.elf=.map) 25 | LDFLAGS += -ffreestanding -nostdlib 26 | 27 | HOST_CXXFLAGS = -g -Os -std=gnu++17 28 | 29 | ifeq ($(LTO), YES) 30 | HUSH_LD_LTO = +$(HUSH_LD) 31 | LDFLAGS += -flto=$(JOBS) -fuse-linker-plugin 32 | CFLAGS += -flto 33 | endif 34 | 35 | all: $(GRUBDIR)/kcpp 36 | 37 | clean: 38 | rm -fr out 39 | 40 | KERNEL_OBJS = $(addprefix $(OUT)/, runtime.o syscall.o main.o) 41 | 42 | KERNEL_OBJS += start32.o 43 | 44 | KERNEL_DEPS = $(KERNEL_OBJS:.o=.d) 45 | -include $(KERNEL_DEPS) 46 | 47 | $(OUT)/kernel.elf: linker.ld $(KERNEL_OBJS) 48 | $(HUSH_LD_LTO) $(LD) $(LDFLAGS) -o $@ -T $^ 49 | @echo $@: `awk '/fill/ { sum += $$3 } END { print sum } ' <$(@:.elf=.map)` bytes wasted on alignment 50 | $(OUT)/kernel: $(OUT)/kernel.elf 51 | $(HUSH_OBJCOPY) $(OBJCOPY) -O binary $< $@ 52 | @echo $@: `$(FILE_SIZE) $@` bytes 53 | 54 | -include $(OUT)/syscall.d 55 | 56 | $(OUT)/%.o: %.s 57 | @mkdir -p $(@D) 58 | $(HUSH_AS) $(AS) -g -o $@ $< 59 | 60 | $(OUT)/%.o: %.cc 61 | @mkdir -p $(@D) 62 | $(HUSH_CXX) $(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $< 63 | 64 | $(OUT)/%.o: %.asm 65 | @mkdir -p $(@D) 66 | $(HUSH_ASM_DEP) $(YASM) -i . -e -M $< -o $@ > $(@:.o=.d) 67 | $(HUSH_ASM) $(YASM) -i . -f elf64 -g dwarf2 $< -o $@ -L nasm -l $(OUT)/$*.lst 68 | 69 | $(GRUBDIR)/kcpp: $(OUT)/kernel 70 | @$(CP) $< $@ 71 | 72 | $(OUT)/xprintf_test: xprintf.cpp 73 | $(HUSH_CXX) $(HOST_CXX) $(HOST_CXXFLAGS) -o $@ $< -DXPRINTF_TEST 74 | 75 | test-xprintf: $(OUT)/xprintf_test 76 | @$< 77 | 78 | all: test-xprintf 79 | -------------------------------------------------------------------------------- /kcpp/TODO: -------------------------------------------------------------------------------- 1 | * string.c and runtime.s are a bit redundant. 2 | Might want to actually use string.c instead of the assembly. 3 | 4 | * Run static constructors in the start of start64 5 | It's confusing when something like "static int X = 3;" fails because "3" was 6 | too complicated to make a constant. OTOH, those things should rather be 7 | constexpr and .rodata (or compile time) anyway, so maybe it's a "feature" that 8 | this doesn't work? :D 9 | (And some way to explicitly detect when there is initialization code?) 10 | -------------------------------------------------------------------------------- /kcpp/cpu.h: -------------------------------------------------------------------------------- 1 | namespace cpu { 2 | 3 | namespace { 4 | extern "C" void fastret(Process *p, u64 rax) NORETURN; 5 | extern "C" void slowret(Process *p) NORETURN; 6 | extern "C" void syscall_entry_stub(); 7 | extern "C" void syscall_entry_compat(); 8 | } 9 | 10 | struct Cpu; 11 | void idle(Cpu *) NORETURN; 12 | 13 | Cpu &getcpu() { 14 | return *(Cpu *)x86::get_cpu_specific(); 15 | } 16 | 17 | void setup_msrs(u64 gs) { 18 | using x86::seg; 19 | using x86::rflags; 20 | using x86::efer; 21 | using namespace x86::msr; 22 | 23 | wrmsr(STAR, (seg::user_code32_base << 16) | seg::code); 24 | wrmsr(LSTAR, (uintptr_t)syscall_entry_stub); 25 | wrmsr(CSTAR, (uintptr_t)syscall_entry_compat); 26 | // FIXME: We want to clear a lot more flags - Direction for instance. 27 | // FreeBSD sets PSL_NT|PSL_T|PSL_I|PSL_C|PSL_D 28 | wrmsr(FMASK, rflags::IF | rflags::VM); 29 | wrmsr(EFER, rdmsr(EFER) | efer::SCE | efer::NXE); 30 | wrmsr(GSBASE, gs); 31 | } 32 | 33 | using x86::SavedRegs; 34 | 35 | struct Cpu { 36 | // NB: Initial fields shared with assembly code. 37 | Cpu *self; 38 | u8 *stack; 39 | Process *process; 40 | SavedRegs *kernel_reg_save_pointer; 41 | // END OF ASSEMBLY-SHARED FIELDS 42 | 43 | // mem::PerCpu memory 44 | DList runqueue; 45 | Process *irq_process; 46 | u64 irq_delayed[4]; 47 | 48 | SavedRegs kernel_reg_save; 49 | 50 | // Assume everything else is 0-initialized 51 | // FIXME There's already a stack allocated by the boot loader, a bit 52 | // wasteful to allocate a new one. Non-first CPUs might need this code 53 | // though? 54 | Cpu(): 55 | self(this), 56 | stack(new u8[4096]), 57 | kernel_reg_save_pointer(&kernel_reg_save) { 58 | } 59 | Cpu(Cpu&) = delete; 60 | Cpu& operator=(Cpu&) = delete; 61 | 62 | void start() { 63 | setup_msrs((u64)this); 64 | } 65 | 66 | NORETURN void run() { 67 | if (Process *p = runqueue.pop()) { 68 | log(switch, "run: popped %s\n", p->name()); 69 | assert(p->is_queued()); 70 | p->unset(proc::Queued); 71 | switch_to(p); 72 | } else { 73 | idle(this); 74 | } 75 | } 76 | 77 | void queue(Process *p) { 78 | log(runqueue, "queue %s. queued=%d flags=%lu\n", p->name(), p->is_queued(), p->flags); 79 | assert(p->is_runnable()); 80 | if (!p->is_queued()) { 81 | p->set(proc::Queued); 82 | runqueue.append(p); 83 | } 84 | } 85 | 86 | void leave(Process *p) { 87 | assert(p == process); 88 | log(runqueue, "leaving %s\n", p->name()); 89 | p->unset(proc::Running); 90 | process = NULL; 91 | } 92 | 93 | NORETURN void switch_to(Process *p) { 94 | log(switch, "switch_to %s rip=%#lx fastret=%d queued=%d\n", 95 | p->name(), p->rip, p->is(proc::FastRet), p->is(proc::Queued)); 96 | assert(this == &getcpu()); 97 | assert(!process); 98 | assert(!p->is(proc::Running)); 99 | assert(p->is_runnable()); 100 | p->set(proc::Running); 101 | if (process != p) { 102 | process = p; 103 | x86::set_cr3(p->cr3); 104 | } 105 | if (p->is(proc::FastRet)) { 106 | p->unset(proc::FastRet); 107 | fastret(p, p->regs.rax); 108 | } else { 109 | slowret(p); 110 | } 111 | } 112 | 113 | // TODO Using fastret here should be guaranteed possible, so we can avoid 114 | // going through memory for rax. Note that we don't always return to the 115 | // same process that called (e.g. in IPC cases when the old is blocked and 116 | // the new is immediately made runnable), so the context switch stuff in 117 | // switch_to is mostly still necessary. 118 | NORETURN void syscall_return(Process *p, u64 rax) { 119 | log(switch, "syscall_return %s rax=%lx\n", p->name(), p->regs.rax); 120 | p->regs.rax = rax; 121 | switch_to(p); 122 | } 123 | 124 | void dump_stack(u64 *start, u64 *end) { 125 | while (start < end) { 126 | printf("%p: %16lx\n", start, *start); 127 | start++; 128 | } 129 | } 130 | 131 | void dump_regs() { 132 | kernel_reg_save.dump(); 133 | u64* sp = (u64*)kernel_reg_save.regs.rsp; 134 | dump_stack(sp, (u64*)(((uintptr_t)sp + 0xfff) & ~0xfff)); 135 | } 136 | }; 137 | 138 | void idle(Cpu *cpu) { 139 | log(idle, "idle\n"); 140 | cpu->process = NULL; 141 | asm volatile("sti; hlt" ::: "memory"); 142 | // We should have entered an interrupt handler which would not "return" 143 | // here but rather just re-idle. 144 | abort("idle returned"); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /kcpp/dict.h: -------------------------------------------------------------------------------- 1 | template struct DictNode; 2 | template 3 | DictNode *node_from_item(T *c) { return &c->node; } 4 | 5 | template struct DictNode 6 | { 7 | K key; 8 | //DictNode* left; 9 | DictNode* right; 10 | 11 | DictNode(K key): 12 | key(key), 13 | //left(nullptr), 14 | right(nullptr) {} 15 | 16 | static DictNode *node(V *item) { 17 | return node_from_item(item); 18 | } 19 | static intptr_t node_offset() { 20 | return (intptr_t)node((V*)0); 21 | } 22 | V* item() { 23 | return (V*)((u8*)this - node_offset()); 24 | } 25 | }; 26 | template struct Dict 27 | { 28 | typedef DictNode Node; 29 | Node* root; 30 | 31 | Dict(): root(nullptr) {} 32 | 33 | // Return the greatest item with key <= key 34 | V* find_le(K key) const { 35 | Node *max = NULL; 36 | Node *node = root; 37 | while (node) { 38 | log(dict_find, 39 | "find(%#lx): node %p (%#lx) right %p max %p (%#lx)\n", 40 | key, node, node->key, node->right, max, max ? max->key : 0); 41 | if (node->key <= key && (!max || node->key > max->key)) { 42 | max = node; 43 | } 44 | node = node->right; 45 | } 46 | return max ? max->item() : NULL; 47 | } 48 | 49 | V* find_exact(K key) const { 50 | Node *node = root; 51 | while (node) { 52 | if (node->key == key) { 53 | return node->item(); 54 | } 55 | node = node->right; 56 | } 57 | return NULL; 58 | } 59 | 60 | bool contains(V* item) const { 61 | Node *node = root; 62 | while (node) { 63 | if (node == node_from_item(item)) { 64 | return true; 65 | } 66 | node = node->right; 67 | } 68 | return false; 69 | } 70 | 71 | V *insert(V* item) { 72 | Node *node = node_from_item(item); 73 | log(dict_insert, "insert(%p): key %#lx root %p (%#lx)\n", 74 | item, node->key, root, root ? root->key : 0); 75 | node->right = root; 76 | root = node; 77 | return item; 78 | } 79 | 80 | void rekey(V* item, K key) { 81 | // This dictionary is still very stupid, so no need to update a sort 82 | // tree or anything. 83 | node_from_item(item)->key = key; 84 | } 85 | 86 | WARN_UNUSED_RESULT V* remove(V* item) { 87 | if (Node *n = remove(node_from_item(item)->key)) { 88 | return n->item(); 89 | } else { 90 | return NULL; 91 | } 92 | } 93 | WARN_UNUSED_RESULT V *remove(K key) { 94 | Node **p = &root; 95 | while (Node *node = *p) { 96 | if (node->key == key) { 97 | *p = node->right; 98 | return node->item(); 99 | } 100 | p = &node->right; 101 | } 102 | return NULL; 103 | } 104 | WARN_UNUSED_RESULT V *pop() { 105 | Node **p = &root; 106 | if (Node *node = *p) { 107 | *p = node->right; 108 | return node->item(); 109 | } 110 | return NULL; 111 | } 112 | 113 | // Find and remove one item with start < key < end and return it to the 114 | // caller (which takes over ownership). 115 | WARN_UNUSED_RESULT V *remove_range_exclusive(K start, K end) { 116 | Node **p = &root; 117 | while (Node *node = *p) { 118 | if (start < node->key && node->key < end) { 119 | *p = node->right; 120 | return node->item(); 121 | } 122 | p = &node->right; 123 | } 124 | return NULL; 125 | } 126 | }; 127 | 128 | #define DICT_NODE(Class, member) \ 129 | DictNode *node_from_item(Class *c) { return &c->member; } 130 | 131 | -------------------------------------------------------------------------------- /kcpp/dlist.h: -------------------------------------------------------------------------------- 1 | template struct DListNode; 2 | template 3 | DListNode *dlistnode_from_item(T *c) { return &c->node; } 4 | 5 | template struct DListNode 6 | { 7 | // TODO Use node so that all lists can share iterator code. (maybe) 8 | T* prev; 9 | T* next; 10 | 11 | DListNode(): prev(nullptr), next(nullptr) {} 12 | 13 | static DListNode *node(T *item) { 14 | return dlistnode_from_item(item); 15 | } 16 | static intptr_t node_offset() { 17 | return (intptr_t)node((T*)0); 18 | } 19 | T* item() { 20 | return (T*)((u8*)this - node_offset()); 21 | } 22 | }; 23 | template class DListIterator 24 | { 25 | typedef DListNode Node; 26 | Node *node; 27 | 28 | public: 29 | DListIterator(Node *node): node(node) { 30 | } 31 | DListIterator(T *item): node(Node::node(item)) { 32 | } 33 | 34 | static DListIterator end() { 35 | return DListIterator((Node*)nullptr); 36 | } 37 | 38 | DListIterator operator ++(int) { 39 | DListIterator res = *this; 40 | ++*this; 41 | return res; 42 | } 43 | DListIterator& operator ++() { 44 | assert(node); 45 | node = node->next ? Node::node(node->next) : nullptr; 46 | return *this; 47 | } 48 | bool operator==(const DListIterator &other) const { 49 | return node == other.node; 50 | } 51 | bool operator!=(const DListIterator &other) const { 52 | return node != other.node; 53 | } 54 | T *operator *() const { 55 | return node->item(); 56 | } 57 | }; 58 | template struct DList 59 | { 60 | typedef DListNode Node; 61 | T* head; 62 | T* tail; 63 | 64 | DList(): head(nullptr), tail(nullptr) {} 65 | 66 | // using Node::node? 67 | static Node *node(T *item) { 68 | return Node::node(item); 69 | } 70 | static Node *node(Node *n) { 71 | return n; 72 | } 73 | 74 | DListIterator begin() { 75 | return head ? DListIterator(head) : end(); 76 | } 77 | DListIterator end() { 78 | return DListIterator::end(); 79 | } 80 | 81 | T *pop() { 82 | if (head) { 83 | return remove(head); 84 | } else { 85 | return nullptr; 86 | } 87 | } 88 | 89 | T *append(T* item) { 90 | assert(!node(item)->prev && !node(item)->next); 91 | if (tail) { 92 | assert(head); 93 | node(item)->prev = tail; 94 | node(tail)->next = item; 95 | tail = item; 96 | } else { 97 | tail = head = item; 98 | } 99 | return item; 100 | } 101 | 102 | T *remove(T* item) { 103 | auto prev = node(item)->prev; 104 | auto next = node(item)->next; 105 | node(item)->prev = nullptr; 106 | if (prev) { 107 | node(prev)->next = next; 108 | } 109 | node(item)->next = nullptr; 110 | if (next) { 111 | node(next)->prev = prev; 112 | } 113 | 114 | if (head == item) { 115 | head = next; 116 | } 117 | if (tail == item) { 118 | tail = prev; 119 | } 120 | return item; 121 | } 122 | 123 | bool contains(T* item) const { 124 | T *p = head; 125 | while (p) { 126 | if (p == item) { 127 | return true; 128 | } 129 | p = node(p)->next; 130 | } 131 | return false; 132 | } 133 | 134 | }; 135 | 136 | #define DLIST_NODE(Class, member) \ 137 | UNUSED DListNode *dlistnode_from_item(Class *c) \ 138 | { return &c->member; } 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /kcpp/handle.h: -------------------------------------------------------------------------------- 1 | namespace { 2 | 3 | enum HandleType { 4 | USER_HANDLE = 0, 5 | ASPACE_HANDLE = 1, 6 | THREAD_HANDLE = 2, 7 | }; 8 | 9 | struct Handle 10 | { 11 | typedef uintptr_t Key; 12 | DictNode node; 13 | AddressSpace *otherspace; 14 | Handle *other; 15 | u64 events; 16 | u8 type; 17 | 18 | // Assume 0-init! 19 | Handle(uintptr_t key, AddressSpace *otherspace): 20 | node(key), otherspace(otherspace) {} 21 | 22 | uintptr_t key() const { return node.key; } 23 | 24 | void dissociate() { 25 | if (other) { 26 | other->other = NULL; 27 | other = NULL; 28 | } 29 | } 30 | 31 | void associate(AddressSpace *p, Handle *g) { 32 | g->otherspace = p; 33 | g->other = this; 34 | other = g; 35 | } 36 | 37 | static void associate(AddressSpace *p, AddressSpace *q, Handle *h, Handle *g) { 38 | h->otherspace = q; 39 | h->other = g; 40 | 41 | g->otherspace = p; 42 | g->other = h; 43 | } 44 | }; 45 | 46 | struct PendingPulse 47 | { 48 | typedef uintptr_t Key; 49 | DictNode node; 50 | 51 | PendingPulse(Handle *handle): node(handle->key()) {} 52 | 53 | uintptr_t key() const { return node.key; } 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /kcpp/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(start32_mboot) 2 | OUTPUT_FORMAT(binary) 3 | 4 | high_org = 0xffffffffc0000000; 5 | 6 | MEMORY { 7 | ram : org = 0x100000, l = 64K 8 | high : org = 0xffffffffc0100000, l = 64K 9 | } 10 | 11 | SECTIONS { 12 | . = 0x100000; 13 | 14 | .text32 : { 15 | start32.o(.text*); 16 | } >ram AT>ram 17 | 18 | .text64 (. + high_org) : { 19 | *(.text*); 20 | } >high AT>ram 21 | 22 | .data32 (. - high_org) : { 23 | start32.o(.data*); 24 | start32.o(.rodata*); 25 | } >ram AT>ram 26 | 27 | .data64 (. + high_org) : { 28 | *(.rodata*); 29 | *(.data*); 30 | __CTOR_LIST__ = .; 31 | KEEP(*(.ctors)); 32 | __CTOR_END__ = .; 33 | PROVIDE(section.data.end = . - 0xffffffffc0000000); 34 | } >high AT>ram 35 | 36 | .bss32 (. - high_org) : { 37 | start32.o(.bss*); 38 | } >ram AT>ram 39 | 40 | .bss64 (. + high_org) : { 41 | *(.bss*); 42 | } >high AT>ram 43 | 44 | /DISCARD/ : { 45 | *(.comment) 46 | *(.eh_frame) 47 | *(.rel.eh_frame) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /kcpp/mboot.h: -------------------------------------------------------------------------------- 1 | namespace mboot { 2 | 3 | struct VBE { 4 | u32 control_info; 5 | u32 mode_info; 6 | u16 mode; 7 | u16 iface_seg; 8 | u16 iface_off; 9 | u16 iface_len; 10 | }; 11 | 12 | struct FB { 13 | u64 addr; 14 | u32 pitch; 15 | u32 width; 16 | u32 height; 17 | u8 bpp; 18 | u8 fbtype; 19 | u8 colors[6]; 20 | }; 21 | 22 | enum FBType { 23 | Indexed = 0, 24 | RGB = 1, 25 | Text = 2, 26 | }; 27 | 28 | struct FBPalette { 29 | u32 addr; 30 | u16 count; 31 | }; 32 | 33 | struct FBPixelFormat { 34 | u8 red_shift; 35 | u8 red_mask; 36 | u8 green_shift; 37 | u8 green_mask; 38 | u8 blue_shift; 39 | u8 blue_mask; 40 | }; 41 | 42 | enum InfoFlags { 43 | MemorySize = 1, 44 | BootDevice = 2, 45 | CommandLine = 4, 46 | Modules = 8, 47 | // There are two formats for the symbol bit of the info struct, not sure 48 | // what they are though. 49 | Symbols = 16 | 32, 50 | Symbols1 = 16, 51 | Symbols2 = 32, 52 | MemoryMap = 64, 53 | Drives = 128, 54 | ConfigTable = 256, 55 | LoaderName = 512, 56 | APMTable = 1024, 57 | VBEInfo = 2048 58 | }; 59 | 60 | struct Info { 61 | u32 flags; 62 | // if has(MemorySize) 63 | u32 mem_lower; 64 | u32 mem_upper; 65 | // if has(BootDevice) 66 | u32 boot_devices; 67 | 68 | // if has(CommandLine) 69 | u32 cmdline; 70 | 71 | // if has(Modules) 72 | u32 mods_count; 73 | u32 mods_addr; 74 | 75 | // 76 | u32 syms[4]; 77 | 78 | // if has(MemoryMap) 79 | u32 mmap_length; 80 | u32 mmap_addr; 81 | 82 | // 83 | u32 drives_length; 84 | u32 drives_addr; 85 | 86 | u32 config_table; 87 | 88 | u32 boot_loader; 89 | u32 apm_table; 90 | 91 | VBE vbe; 92 | FB fb; 93 | 94 | bool has(InfoFlags flag) const { return !!(flags & flag); } 95 | }; 96 | 97 | struct Module { 98 | u32 start; 99 | u32 end; 100 | u32 string; 101 | u32 reserved; 102 | }; 103 | 104 | struct MemoryMapItem { 105 | // Size of item (bytes), *not* including the item_size field 106 | u32 item_size; 107 | u64 start; 108 | u64 length; 109 | // See values from MemoryTypes 110 | u32 item_type; 111 | } __attribute__((packed)); 112 | 113 | enum MemoryTypes { 114 | MemoryTypeMemory = 1, 115 | MemoryTypeReserved = 2, 116 | MemoryTypeACPIRCL = 3, 117 | MemoryTypeACPISomething = 4, 118 | }; 119 | 120 | } 121 | -------------------------------------------------------------------------------- /kcpp/mem.h: -------------------------------------------------------------------------------- 1 | namespace mem { 2 | 3 | struct free_page { 4 | free_page *next; 5 | }; 6 | static free_page* freelist_head; 7 | static u32 used_pages, total_pages; 8 | 9 | template 10 | T *add_byte_offset(T *p, intptr_t offset) { 11 | return (T*)((char*)p + offset); 12 | } 13 | 14 | // TODO Implement per-cpu cache of page(s). 15 | void free(void *page) { 16 | if (!page) return; 17 | 18 | free_page *free = (free_page *)page; 19 | free->next = freelist_head; 20 | freelist_head = free; 21 | used_pages--; 22 | } 23 | 24 | void *malloc(size_t sz) { 25 | assert(sz <= 4096); 26 | free_page *res = freelist_head; 27 | assert(res); 28 | freelist_head = res->next; 29 | memset(res, 0, 4096); 30 | used_pages++; 31 | return res; 32 | } 33 | 34 | uintptr_t allocate_frame() { 35 | return ToPhysAddr(malloc(4096)); 36 | } 37 | 38 | void init(const mboot::Info& info, u32 memory_start, u64 memory_end) { 39 | assert(info.has(mboot::MemoryMap)); 40 | auto mmap = PhysAddr(info.mmap_addr); 41 | auto mmap_end = add_byte_offset(mmap, info.mmap_length); 42 | size_t n = 0; 43 | while (mmap < mmap_end) { 44 | printf("%p: start=%#lx length=%#lx\n", mmap, 45 | mmap->start, mmap->length); 46 | 47 | if (mmap->item_type == mboot::MemoryTypeMemory) { 48 | uintptr_t p = mmap->start; 49 | const uintptr_t e = p + mmap->length; 50 | while (p < e) { 51 | if (memory_start <= p && p < memory_end) { 52 | used_pages++; 53 | free(PhysAddr(p)); 54 | n++; 55 | } 56 | p += 4096; 57 | } 58 | } 59 | 60 | mmap = add_byte_offset(mmap, 4 + mmap->item_size); 61 | } 62 | 63 | // We added one for each page, then "freed" it, so this should be 0 64 | assert(!used_pages); 65 | total_pages = n; 66 | printf("Found %zu pages for %zuMiB of memory\n", n, (n * 4 + 1023) / 1024); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /kcpp/mkgrubcfg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat < node; 58 | 59 | RefCnt aspace; 60 | AddressSpace *waiting_for; 61 | uintptr_t fault_addr; 62 | // TODO FXSave 63 | 64 | Process(AddressSpace *aspace): 65 | aspace(aspace) 66 | { 67 | flags = 1 << FastRet; 68 | cr3 = aspace->cr3(); 69 | rflags = x86::rflags::IF; 70 | } 71 | 72 | void assoc_handles(uintptr_t j, Process *other, uintptr_t i) { 73 | AddressSpace *otherspace = other->aspace.get(); 74 | auto x = aspace->new_handle(j, otherspace); 75 | auto y = otherspace->new_handle(i, aspace.get()); 76 | x->other = y; 77 | y->other = x; 78 | } 79 | 80 | Handle *new_handle(uintptr_t key, AddressSpace *otherspace) { 81 | return aspace->new_handle(key, otherspace); 82 | } 83 | Handle *find_handle(uintptr_t key) const { 84 | return aspace->find_handle(key); 85 | } 86 | void rename_handle(Handle *handle, uintptr_t new_key) { 87 | aspace->rename_handle(handle, new_key); 88 | } 89 | void delete_handle(Handle *handle) { 90 | aspace->delete_handle(handle); 91 | } 92 | 93 | void wait_for(AddressSpace *otherspace) { 94 | otherspace->add_waiter(this); 95 | } 96 | void add_waiter(Process *other) { 97 | other->wait_for(aspace.get()); 98 | } 99 | void remove_waiter(Process *other) { 100 | aspace->remove_waiter(other); 101 | } 102 | 103 | bool is(ProcFlags flag) const { 104 | return flags & (1 << flag); 105 | } 106 | bool is_queued() const { return is(Queued); } 107 | 108 | void set(ProcFlags flag) { 109 | flags |= (1 << flag); 110 | } 111 | void unset(ProcFlags flag) { 112 | flags &= ~(1 << flag); 113 | } 114 | 115 | u64 ipc_state() const { 116 | return flags & (mask(InSend) | mask(InRecv) | mask(PFault)); 117 | } 118 | bool is_runnable() const { 119 | return ipc_state() == 0; 120 | } 121 | bool is_blocked() const { return !is_runnable(); } 122 | 123 | const char *name() const { return aspace->name(); } 124 | 125 | void dump_regs() const { saved_regs.dump(); } 126 | }; 127 | } 128 | 129 | namespace aspace { 130 | 131 | Process *AddressSpace::pop_sender(Handle *target) { 132 | for (auto p: waiters) { 133 | if (p->is(proc::InSend) 134 | && (!target 135 | || (target->otherspace == p->aspace.get() 136 | && target->other->key() == p->regs.rdi))) { 137 | remove_waiter(p); 138 | return p; 139 | } 140 | } 141 | 142 | return nullptr; 143 | } 144 | 145 | Process *AddressSpace::pop_recipient(Handle *source) { 146 | return pop_recipient(source, proc::mask(proc::InRecv)); 147 | } 148 | Process *AddressSpace::pop_pfault_recipient(Handle *source) { 149 | return pop_recipient(source, proc::mask(proc::InRecv) | proc::mask(proc::PFault)); 150 | } 151 | Process *AddressSpace::pop_recipient(Handle *source, uintptr_t ipc_state) { 152 | // Iterate waiters, find a "remote" process that can receive something from 153 | // source. 154 | Handle *other = source->other; 155 | for (auto p: waiters) { 156 | if (p->ipc_state() == ipc_state) { 157 | auto rcpt = p->find_handle(p->regs.rdi); 158 | log(waiters, 159 | "%s get_recipient(%#lx): found %s receiving from %p (looking for %p)\n", 160 | name(), source->key(), p->name(), rcpt, other); 161 | // If there was no handle, we don't expect to find that process 162 | // here - pop_open_recipient would be used for that. 163 | assert(rcpt); 164 | if (!rcpt || rcpt == other) { 165 | remove_waiter(p); 166 | return p; 167 | } 168 | } else { 169 | log(waiters, "%s get_recipient(%#lx): found %s in state %lu (wanted %lu)\n", 170 | name(), source->key(), p->name(), p->ipc_state(), ipc_state); 171 | } 172 | } 173 | log(waiters, "%s get_recipient(%#lx): no match\n", name(), source->key()); 174 | return nullptr; 175 | } 176 | 177 | Process *AddressSpace::pop_open_recipient() { 178 | for (auto p: blocked) { 179 | if (p->ipc_state() == proc::mask(proc::InRecv)) { 180 | log(waiters, "%s get_recipient(): found %s\n", name(), p->name()); 181 | // If it's receiving to a specific handle, we shouldn't find it 182 | // here but in the other address space's waiter list. 183 | assert(!p->find_handle(p->regs.rdi)); 184 | remove_blocked(p); 185 | return p; 186 | } 187 | } 188 | log(waiters, "%s get_recipient(): no open recipients\n", name()); 189 | return nullptr; 190 | } 191 | 192 | void AddressSpace::add_waiter(Process *p) 193 | { 194 | log(waiters, "%s adds waiter %s\n", name(), p->name()); 195 | assert(!p->waiting_for); 196 | assert(!p->is_queued()); 197 | waiters.append(p); 198 | p->waiting_for = this; 199 | } 200 | void AddressSpace::remove_waiter(Process *p) 201 | { 202 | log(waiters, "%s removes waiter %s\n", name(), p->name()); 203 | assert(p->waiting_for == this || p->waiting_for == nullptr); 204 | waiters.remove(p); 205 | p->waiting_for = nullptr; 206 | } 207 | void AddressSpace::add_blocked(Process *p) 208 | { 209 | log(waiters, "%s is now blocked on %s\n", p->name(), name()); 210 | assert(!p->waiting_for); 211 | assert(!p->is_queued()); 212 | blocked.append(p); 213 | p->waiting_for = this; 214 | } 215 | void AddressSpace::remove_blocked(Process *p) 216 | { 217 | log(waiters, "%s unblocked\n", p->name()); 218 | assert(p->waiting_for == this); 219 | blocked.remove(p); 220 | p->waiting_for = nullptr; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /kcpp/refcnt.h: -------------------------------------------------------------------------------- 1 | template 2 | class RefCnt 3 | { 4 | T *ptr_; 5 | public: 6 | explicit RefCnt(T* ptr): ptr_(ptr) { 7 | if (ptr) ptr->addref(); 8 | } 9 | ~RefCnt() { 10 | if (ptr_) ptr_->release(); 11 | } 12 | 13 | RefCnt(): ptr_(nullptr) {} 14 | explicit RefCnt(nullptr_t): ptr_(nullptr) {} 15 | RefCnt(RefCnt&& other) { 16 | ptr_ = other.ptr_; 17 | other.ptr_ = nullptr; 18 | } 19 | void operator=(RefCnt&& other) { 20 | ptr_ = other.ptr_; 21 | other.ptr_ = nullptr; 22 | } 23 | void operator=(const RefCnt& other) { 24 | ptr_ = other.ptr_; 25 | if (ptr_) ptr_->addref(); 26 | } 27 | 28 | void reset(nullptr_t p = nullptr) { 29 | if (ptr_) ptr_->release(); 30 | ptr_ = nullptr; 31 | } 32 | void reset_addref(T* ptr) { 33 | if (ptr_) ptr_->release(); 34 | ptr_ = ptr; 35 | if (ptr_) ptr_->addref(); 36 | } 37 | void reset_take(T* ptr) { 38 | if (ptr_) ptr_->release(); 39 | ptr_ = ptr; 40 | } 41 | 42 | T* operator->() { 43 | return ptr_; 44 | } 45 | const T* operator ->() const { 46 | return ptr_; 47 | } 48 | T* get() { 49 | return ptr_; 50 | } 51 | }; 52 | 53 | template 54 | class RefCounted 55 | { 56 | uint32_t count; 57 | 58 | void deleteme() { 59 | delete static_cast(this); 60 | } 61 | public: 62 | RefCounted(): count(0) {} 63 | RefCounted(const RefCounted&) = delete; 64 | RefCounted& operator =(const RefCounted&) = delete; 65 | 66 | void addref() { 67 | assert(count < UINT32_MAX); 68 | count++; 69 | } 70 | void release() { 71 | assert(count); 72 | if (!--count) 73 | deleteme(); 74 | } 75 | 76 | uint32_t get_refcount() const { 77 | return count; 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /kcpp/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | make -j4 6 | 7 | testmod=out/grub/test.mod 8 | exec qemu-system-x86_64 -m 32M -kernel out/kernel -initrd "$testmod asdf,$testmod jkl,$testmod 123" "$@" 9 | -------------------------------------------------------------------------------- /kcpp/runtime.s: -------------------------------------------------------------------------------- 1 | .text 2 | .section .text.memset,"ax",@progbits 3 | .globl memset 4 | .type memset,@function 5 | memset: 6 | # rdi = target, rsi = data, rdx = count 7 | movl %esi, %eax 8 | movq %rdx, %rcx # save one byte: assume count < 4GB 9 | rep stosb 10 | retq 11 | 1: 12 | .size memset, 1b - memset 13 | 14 | .section .text.memcpy,"ax",@progbits 15 | .globl memcpy 16 | .type memcpy,@function 17 | memcpy: 18 | # rdi = target, rsi = src, rdx = count 19 | movq %rdx, %rcx 20 | rep movsb 21 | retq 22 | 1: 23 | .size memcpy, 1b - memcpy 24 | -------------------------------------------------------------------------------- /kcpp/start32.o: -------------------------------------------------------------------------------- 1 | ../out/start32.o -------------------------------------------------------------------------------- /kcpp/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // FIXME These are all implemented in cuser/string.c too. Should unify that 4 | // (there might be a klibc and a user libc, but the shared bits should be 5 | // shared at least...) 6 | 7 | #ifndef STRING_INL_LINKAGE 8 | #define STRING_INL_LINKAGE UNUSED 9 | #endif 10 | 11 | STRING_INL_LINKAGE void memcpy(void* dest, const void* src, size_t n) { 12 | asm("rep movsb": "+D"(dest), "+S"(src), "+c"(n), "=m"(dest) : : "memory"); 13 | } 14 | 15 | STRING_INL_LINKAGE void memmove(void* dest, const void* src, size_t n) { 16 | if (dest < src) { 17 | memcpy(dest, src, n); 18 | } else { 19 | dest = (char *)dest + n; 20 | src = (char *)src + n; 21 | asm("std; rep movsb; cld" 22 | : "+D"(dest), "+S"(src), "+c"(n), "=m"(dest) 23 | : 24 | : "memory"); 25 | } 26 | } 27 | 28 | STRING_INL_LINKAGE void memset(void* dest, int c, size_t n) { 29 | asm("rep stosb": "+D"(dest), "+c"(n), "=m"(dest) : "a"(c) : "memory"); 30 | } 31 | 32 | STRING_INL_LINKAGE int memcmp(const void* a_, const void* b_, size_t n) { 33 | const char* a = (const char *)a_, *b = (const char *)b_; 34 | while (n--) { 35 | int diff = *a++ - *b++; 36 | if (diff) return diff; 37 | } 38 | return 0; 39 | } 40 | 41 | STRING_INL_LINKAGE int strcmp(const char* a, const char* b) { 42 | while (*a && *b && *a == *b) { 43 | a++, b++; 44 | } 45 | return *a - *b; 46 | } 47 | 48 | STRING_INL_LINKAGE size_t strlen(const char* s) { 49 | size_t res = 0; 50 | while (*s++) res++; 51 | return res; 52 | } 53 | 54 | STRING_INL_LINKAGE void strcat(char* dest, const char* src) { 55 | memcpy(dest + strlen(dest), src, strlen(src)); 56 | } 57 | 58 | STRING_INL_LINKAGE char* strchr_(const char* str, char c) { 59 | while (*str && *str != c) str++; 60 | return *str ? (char*)str : NULL; 61 | } 62 | 63 | #ifndef __cplusplus 64 | #define strchr strchr_ 65 | #else 66 | STRING_INL_LINKAGE const char* strchr(const char* str, char c) { 67 | return strchr_(str, c); 68 | } 69 | STRING_INL_LINKAGE char* strchr(char* str, char c) { 70 | return strchr_(str, c); 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /kcpp/syscall.asm: -------------------------------------------------------------------------------- 1 | ; vim:filetype=nasm: 2 | 3 | bits 64 4 | 5 | ; callee-save: rbp, rbx, r12-r15 6 | ; caller-save: rax, rcx, rdx, rsi, rdi, r8-r11 7 | %macro clear_clobbered_syscall 0 8 | ; rax, rcx, r11 are also in this list, but are used for return, rip and rflags respectively. 9 | %endmacro 10 | %macro clear_clobbered 0 11 | clear_clobbered_syscall 12 | zero ecx 13 | zero r11 14 | %endmacro 15 | 16 | %macro zero 1 17 | xor %1, %1 18 | %endmacro 19 | 20 | %macro restruc 1-2 1 21 | resb (%1 %+ _size) * %2 22 | %endmacro 23 | 24 | %assign i 0 25 | 26 | %macro reglabels 1-* 27 | %rep %0 28 | .r %+ %1 equ .regs+(i * 8) 29 | %assign i i+1 30 | %rotate 1 31 | %endrep 32 | %endmacro 33 | 34 | struc iframe 35 | .rip resq 1 36 | .cs resq 1 37 | .rflags resq 1 38 | .rsp resq 1 39 | .ss resq 1 40 | endstruc 41 | 42 | struc gseg 43 | .self resq 1 44 | .rsp resq 1 45 | .proc resq 1 46 | ; Pointer to register save area. Most likely inside of gseg, but 47 | ; referenced by pointer to avoid depending on its location from the 48 | ; assembly stubs. 49 | .kernel_reg_save resq 1 50 | endstruc 51 | 52 | struc proc, -0x80 53 | .regs resq 16 ; a,c,d,b,sp,bp,si,di,r8-15 54 | 55 | ; Aliases for offsets into regs 56 | reglabels ax,cx,dx,bx,sp,bp,si,di 57 | %rep 8 58 | reglabels i 59 | %endrep 60 | 61 | .rip resq 1 62 | .rflags resq 1 63 | 64 | .cr3 resq 1 65 | 66 | endstruc 67 | 68 | %macro load_regs 1-* 69 | %define %%reg %1 70 | %rotate 1 71 | %rep (%0 - 1) 72 | %ifidni %1,%%reg 73 | %error %%reg is in use by this macro 74 | %else 75 | mov %1,[%%reg+proc. %+ %1] 76 | %endif 77 | %rotate 1 78 | %endrep 79 | %endmacro 80 | 81 | %macro save_regs 1-* 82 | %define %%reg %1 83 | %rotate 1 84 | %rep (%0 - 1) 85 | %ifidni %1,%%reg 86 | %error %%reg is in use by this macro 87 | %else 88 | mov [%%reg+proc. %+ %1], %1 89 | %endif 90 | %rotate 1 91 | %endrep 92 | %endmacro 93 | 94 | %macro gfunc 1 95 | %%end: global %1:function (%%end - %1) 96 | %endmacro 97 | 98 | %macro proc 1-2 1 99 | %ifnidn %2,NOSECTION 100 | section .text.%1, exec 101 | %endif 102 | %push proc 103 | %1: 104 | %define %$LAST_PROC %1 105 | %endmacro 106 | 107 | %macro endproc 0 108 | %ifnctx proc 109 | %error unmatched endproc 110 | %endif 111 | gfunc %$LAST_PROC 112 | ; %%end: 113 | ; global %$LAST_PROC:function (%%end - %$LAST_PROC) 114 | %pop 115 | %endmacro 116 | 117 | proc fastret 118 | zero edx 119 | zero r8 120 | zero r9 121 | zero r10 122 | .no_clear: 123 | sub rdi, proc 124 | mov rbx, cr3 125 | cmp rbx, [rdi + proc.cr3] 126 | jne .wrong_cr3 127 | load_regs rdi, rbp,rbx,r12,r13,r14,r15 128 | .fast_fastret: 129 | mov rsp, [rdi + proc.rsp] 130 | mov rcx, [rdi + proc.rip] 131 | mov r11, [rdi + proc.rflags] 132 | mov rax, rsi 133 | swapgs 134 | o64 sysret 135 | .wrong_cr3: 136 | ud2 137 | endproc 138 | 139 | ; section .text.syscall_entry_stub, exec 140 | ; syscall_entry_stub: 141 | proc syscall_entry_stub 142 | swapgs 143 | xchg [gs:8], rsp 144 | push rax 145 | zero eax 146 | mov rax, [gs:rax + gseg.proc] 147 | sub rax, proc 148 | ; * Save registers that aren't caller-save 149 | ; if we have syscall *return* instead, we could get rid of these, but 150 | ; that would require that it return *here* and not try to switch 151 | ; tasks by itself. It wouldn't be all wrong to do that though. 152 | save_regs rax, rbp,rbx,r12,r13,r14,r15 153 | ; * Save rip and rflags 154 | mov [rax + proc.rflags], r11 155 | mov [rax + proc.rip], rcx 156 | zero rcx 157 | mov rbp, [gs:rcx + gseg.self] 158 | mov rcx, [rbp + gseg.rsp] 159 | mov [rax + proc.rsp], rcx 160 | lea rcx, [rsp + 8] 161 | mov [rbp + gseg.rsp], rcx 162 | ; * Fix up for syscall vs normal calling convention. 163 | ; r10 (caller-save) is used instead of rcx for argument 4 164 | mov rcx, r10 165 | 166 | ; The syscall function's prototype is: 167 | ; fn(rdi,rsi,rdx,r10,r8,r9, rax) 168 | 169 | ; Need to use call since we have one stack-allocated register (rax) 170 | ; But note that we do *not* expect syscall to return here - in that 171 | ; case fall through to the ud2 below. 172 | extern syscall 173 | call syscall 174 | 175 | proc syscall_entry_compat, NOSECTION 176 | ; Fail 177 | ud2 178 | endproc 179 | 180 | ; .end 181 | ; global syscall_entry_compat:function (syscall_entry_compat.end - syscall_entry_compat) 182 | ;global syscall_entry_stub:function (syscall_entry_compat.end - syscall_entry_stub) 183 | endproc 184 | 185 | 186 | %macro stub 1 187 | %if %1 >= 128 188 | push byte %1 - 256 189 | %else 190 | push byte %1 191 | %endif 192 | jmp asm_int_entry 193 | %endmacro 194 | 195 | %macro cond 2 196 | j%-1 %%skip 197 | %2 198 | %%skip: 199 | %endmacro 200 | 201 | ; Stack when we get here (from low to high address) 202 | ; vector 203 | ; (error) 204 | ; rip 205 | ; cs 206 | ; rflags 207 | ; rsp 208 | ; ss 209 | ; some tasks: 210 | ; get gseg 211 | ; save rip, rflags, rsp to process 212 | ; save all caller-save regs to process 213 | proc asm_int_entry 214 | push rsi 215 | lea rsi, [rsp + 8] 216 | push rdi 217 | push rax 218 | 219 | ; Set flags to a known state. Must be done before lodsq in case someone 220 | ; set the direction flag. 221 | push byte 0 222 | popfq 223 | 224 | ; rsp is always 16-byte aligned before pushing the stack frame, the 225 | ; basic interrupt stack frame is 5 qwords (making it misaligned), we 226 | ; always add a vector (aligning it again), and sometimes add another 227 | ; error making it misaligned. 228 | ; rsi is the original stack pointer on entry. 229 | ; I.e. test esi,8 => nz if we have an error. 230 | lodsq 231 | mov edi, eax 232 | ; We removed the vector above, so the parity we're checking is flipped. 233 | test esi, 8 234 | cond z, lodsq 235 | 236 | ; edi = vector 237 | ; rax = error (if applicable, otherwise garbage) 238 | ; rsi = frame 239 | 240 | ; If we came from privilege level 0, this is some sort of kernel 241 | ; fault. 242 | test byte [rsi + iframe.cs], 3 243 | jz .kernel_fault 244 | 245 | swapgs 246 | zero eax 247 | mov rax, [gs:rax + gseg.proc] 248 | 249 | test rax, rax 250 | jz .idle 251 | 252 | .save_regs: 253 | ; stack: 254 | ; saved_rax, saved_rdi, saved_rsi, vector, [error], rip, cs, rflags, rsp, ss 255 | 256 | ; rax points to the actual start of proc (C++ pointer), not at the 0x80 257 | ; struct offset. Special case for proc.rax here, since it's at offset 258 | ; 0, this is shorter than the corresponding offseted [rax-0x80]. It 259 | ; matters more for regs 8 and up. 260 | pop qword [rax - proc + proc.rax] ; The rax saved on entry 261 | sub rax, proc 262 | pop qword [rax + proc.rdi] 263 | pop qword [rax + proc.rsi] 264 | pop rdi ; vector 265 | ; rsp now points to error or frame, rsi should be error or zero 266 | zero esi 267 | ; size of stack frame is misaligned, meaning if it's aligned we have 268 | ; an error code. 269 | test esp, 8 270 | jnz .no_err 271 | pop rsi 272 | .no_err: 273 | 274 | pop qword [rax + proc.rip] 275 | add rsp, 8 ; cs 276 | pop qword [rax + proc.rflags] 277 | pop qword [rax + proc.rsp] 278 | add rsp, 8 ; ss 279 | 280 | ; already saved: ax, di, si 281 | ; caller-save: rax, rcx, rdx, rsi, rdi, r8-r11 282 | save_regs rax, rdx,rcx,r8,r9,r10,r11 283 | ; callee-saved regs (the rest): 284 | ; if we had int_entry *return* instead of tail-calling it, we could 285 | ; perhaps avoid saving callee-save registers to the process and doing 286 | ; an iretq from here. 287 | save_regs rax, rbp,rbx,r12,r13,r14,r15 288 | 289 | .int_entry: 290 | zero edx 291 | mov rdx, [gs:rdx + gseg.self] 292 | ; Now rdi = vector, rsi = error (or 0), rdx = gseg 293 | extern int_entry 294 | jmp int_entry 295 | 296 | .idle: 297 | ; Ignore saved_{rax,rdi,rsi}, just get the vector 298 | add rsp, 24 299 | pop rdi 300 | zero esi 301 | ; See above for details on the stack misalignment check to detect the 302 | ; error code 303 | test esp, 8 304 | jz .int_entry 305 | pop rsi 306 | jmp .int_entry 307 | 308 | .kernel_fault: 309 | ; Since this was a kernel fault of some kind, tnstead of saving regs in 310 | ; proc we should save it in a temporary register storage area for 311 | ; faults (somewhere in gseg). Our stack layout is compatible with the 312 | ; state at .save_regs, so we just need to load rax and adjust it for 313 | ; the proc struct offset. 314 | zero eax 315 | mov rax, [gs:rax + gseg.kernel_reg_save] 316 | test rax, rax 317 | ; If we have a kernel-reg-save-area, store everything there 318 | jnz .save_regs 319 | 320 | ; Otherwise just "panic" (should try to print something first though) 321 | .panic: 322 | extern abort 323 | jmp abort 324 | 325 | ; slowret: all registers are currently unknown, load *everything* from process 326 | ; (in rdi), then iretq 327 | proc slowret, NOSECTION 328 | sub rdi, proc ; offset because it comes from C++ code 329 | ; All the callee-saves 330 | load_regs rdi, rbp,rbx,r12,r13,r14,r15 331 | .from_int: 332 | ; all caller-saves except rdi 333 | load_regs rdi, rax,rcx,rsi,rdx,r8,r9,r10,r11 334 | ; Push stuff for iretq 335 | user_code_seg equ 56 336 | user_cs equ user_code_seg+16 | 11b 337 | user_ds equ user_cs+8 338 | push user_ds 339 | push qword [rdi + proc.rsp] 340 | push qword [rdi + proc.rflags] 341 | push user_cs 342 | push qword [rdi + proc.rip] 343 | mov rdi, [rdi + proc.rdi] 344 | swapgs 345 | iretq 346 | endproc 347 | 348 | endproc 349 | 350 | handler_NM_stub stub 7 351 | gfunc handler_NM_stub 352 | handler_PF_stub stub 14 353 | gfunc handler_PF_stub 354 | handler_DF_stub stub 8 355 | gfunc handler_DF_stub 356 | -------------------------------------------------------------------------------- /kcpp/test.asm: -------------------------------------------------------------------------------- 1 | bits 64 2 | .loop: 3 | mov eax, 0xdeadbeef 4 | mov edi, 1 5 | mov esi, 2 6 | mov edx, 3 7 | mov r8d, 4 8 | mov r9d, 5 9 | mov r10d, 6 10 | syscall 11 | jmp .loop 12 | -------------------------------------------------------------------------------- /kcpp/xprintf.cpp: -------------------------------------------------------------------------------- 1 | // Headers that might be available without stdlib itself (e.g. that are 2 | // compiler/target specific rather than part of any library). 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef XPRINTF_NOSTDLIB 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #endif 14 | 15 | #ifdef STATIC_XPRINTF 16 | #define xprintf(...) xfprintf(stdout, __VA_ARGS__) 17 | #define XPRINTF_LINKAGE static 18 | #endif 19 | 20 | #ifndef XPRINTF_LINKAGE 21 | #define XPRINTF_LINKAGE 22 | #endif 23 | 24 | #ifndef xprintf 25 | XPRINTF_LINKAGE void xprintf(const char* fmt, ...) 26 | __attribute__((format(printf, 1, 2))); 27 | #endif 28 | XPRINTF_LINKAGE void xfprintf(FILE* fp, const char* fmt, ...) 29 | __attribute__((format(printf, 2, 3))); 30 | XPRINTF_LINKAGE void xvfprintf(FILE* file, const char* fmt, va_list ap); 31 | 32 | static void format_num(FILE* file, int width, bool leading_zero, bool sign, int base, bool show_base, uintptr_t num) 33 | { 34 | if (sign && (intptr_t)num < 0) 35 | { 36 | num = -num; 37 | fputc_unlocked('-', file); 38 | } 39 | // For o and x, include the 0 or 0x, for other formats it's undefined. 40 | // This appends a prefix even if the value is 0. 41 | if (show_base && (base == 8 || base == 16)) 42 | { 43 | fputc_unlocked('0', file); 44 | if (base == 16) 45 | fputc_unlocked('x', file); 46 | } 47 | char buf[32]; 48 | memset(buf, 0, sizeof(buf)); 49 | size_t len = 0; 50 | do 51 | { 52 | buf[len++] = "0123456789abcdef"[num % base]; 53 | num /= base; 54 | } 55 | while (num); 56 | if (width) 57 | { 58 | int c = leading_zero ? '0' : ' '; 59 | while (len < (size_t)width--) 60 | { 61 | fputc_unlocked(c, file); 62 | } 63 | } 64 | while (len--) 65 | { 66 | fputc_unlocked(buf[len], file); 67 | } 68 | } 69 | 70 | static const char* read_width(const char* fmt, int* width) 71 | { 72 | char* endptr = NULL; 73 | *width = strtol(fmt, &endptr, 10); 74 | return endptr; 75 | } 76 | 77 | void xvfprintf(FILE* file, const char* fmt, va_list ap) 78 | { 79 | flockfile(file); 80 | while (*fmt) 81 | { 82 | const char* nextformat = strchr(fmt, '%'); 83 | if (!nextformat) 84 | { 85 | fwrite_unlocked(fmt, 1, strlen(fmt), file); 86 | break; 87 | } 88 | else 89 | { 90 | fwrite_unlocked(fmt, 1, nextformat - fmt, file); 91 | fmt = nextformat + 1; 92 | } 93 | bool is_long = false; 94 | bool is_size = false; 95 | bool leading_zero = false; 96 | bool sign = true; 97 | bool show_base = false; 98 | int width = 0; 99 | //int before_point = 0; 100 | int base = 10; 101 | for (;;) 102 | { 103 | #define ARG(t) va_arg(ap, t) 104 | switch (*fmt++) 105 | { 106 | case '%': 107 | fputc_unlocked('%', file); 108 | break; 109 | case 's': 110 | { 111 | const char* arg = ARG(const char*); 112 | if (arg) 113 | fwrite_unlocked(arg, 1, strlen(arg), file); 114 | else 115 | fwrite_unlocked("(null)", 1, sizeof("(null)")-1, file); 116 | break; 117 | } 118 | // 'o' is also unsigned, somewhat surprisingly 119 | case 'o': 120 | base = 8; 121 | if (false) 122 | case 'x': 123 | base = 16; 124 | #if __GNUC__ >= 7 125 | [[fallthrough]]; 126 | #endif 127 | case 'u': 128 | sign = false; 129 | #if __GNUC__ >= 7 130 | [[fallthrough]]; 131 | #endif 132 | case 'd': 133 | #if 0 134 | case 'i': 135 | #endif 136 | format_num(file, width, leading_zero, sign, base, show_base, 137 | is_long ? 138 | (sign ? ARG(long) : ARG(unsigned long)) 139 | : is_size ? 140 | (sign ? ARG(ssize_t) : ARG(size_t)) 141 | : 142 | // Careful here: the int must be sign-extended to the 143 | // same width type that format_num takes (at least). 144 | // x?int:unsigned :: unsigned, which may be narrower 145 | // than that, causing only partial sign extension. 146 | (sign ? (intptr_t)ARG(int) : ARG(unsigned))); 147 | break; 148 | case 'p': 149 | format_num(file, 0, false, false, 16, true, (uintptr_t)va_arg(ap, void*)); 150 | break; 151 | case 'l': 152 | is_long = true; 153 | continue; 154 | case 'z': 155 | is_size = true; 156 | continue; 157 | case '#': 158 | show_base = true; 159 | continue; 160 | case '.': 161 | //before_point = width; 162 | width = 0; 163 | continue; 164 | case '0': 165 | leading_zero = true; 166 | fmt = read_width(fmt, &width); 167 | continue; 168 | default: 169 | if (isdigit(fmt[-1])) 170 | { 171 | fmt = read_width(fmt - 1, &width); 172 | continue; 173 | } 174 | return; /* -1 */ 175 | } 176 | break; 177 | } 178 | } 179 | funlockfile(file); 180 | fflush(file); 181 | fflush(stderr); // HACK 182 | /* Should return the number of characters output, or -1 on error. */ 183 | } 184 | 185 | void xfprintf(FILE* fp, const char* fmt, ...) 186 | { 187 | va_list ap; 188 | va_start(ap, fmt); 189 | xvfprintf(fp, fmt, ap); 190 | va_end(ap); 191 | } 192 | 193 | #ifndef xprintf 194 | XPRINTF_LINKAGE void xprintf(const char* fmt, ...) 195 | { 196 | va_list ap; 197 | va_start(ap, fmt); 198 | xvfprintf(stdout, fmt, ap); 199 | va_end(ap); 200 | } 201 | #endif 202 | 203 | #ifdef XPRINTF_TEST 204 | #define test(result, fmt, ...) \ 205 | do { \ 206 | FILE* fp = open_memstream(&memstream_buffer, &memstream_size); \ 207 | xfprintf(fp, fmt, ## __VA_ARGS__); \ 208 | fflush(fp); \ 209 | fclose(fp); \ 210 | char tmp[256] = {0}; \ 211 | snprintf(tmp, sizeof(tmp), fmt, ## __VA_ARGS__); \ 212 | if (strcmp(memstream_buffer, result) != 0 \ 213 | || strcmp(tmp, result) != 0) { \ 214 | fprintf(stderr, "%s:\n\tlibc \"" fmt "\"\n\tactual \"%s\"\n\texpected \"%s\"\n", \ 215 | fmt, ## __VA_ARGS__, memstream_buffer, result); \ 216 | fail++; \ 217 | } else { \ 218 | pass++; \ 219 | } \ 220 | } while (0) 221 | 222 | int main() 223 | { 224 | char* memstream_buffer = NULL; 225 | size_t memstream_size = 0; 226 | int fail = 0, pass = 0; 227 | test("-2147483648", "%d", INT_MIN); 228 | test("0x80000000", "%#x", unsigned(INT_MIN)); 229 | test("80000000", "%x", unsigned(INT_MIN)); 230 | test("20000000000", "%o", unsigned(INT_MIN)); 231 | if (sizeof(long) == 8) { 232 | test("-9223372036854775808", "%ld", LONG_MIN); 233 | test("9223372036854775808", "%lu", 1 + (unsigned long)LONG_MAX); 234 | } else { 235 | test("-2147483648", "%ld", LONG_MIN); 236 | test("2147483648", "%lu", 1 + (unsigned long)LONG_MAX); 237 | } 238 | test("-2147483648", "%zd", ssize_t(INT32_MIN)); 239 | test("2147483648", "%zu", size_t(1) << 31); 240 | 241 | test(" 8", "%5d", 8); 242 | test("00008", "%05d", 8); 243 | // Unimplemented precision/sign/etc bits: 244 | // test(" 8", "%-5d", 8); 245 | // test(" 08", "%5.2d", 8); 246 | // test(" 123", "%5.2d", 123); 247 | // 248 | // test(" 8", "% d", 8); 249 | // test("+8", "%+d", 8); 250 | 251 | if (fail) { 252 | fprintf(stderr, "xprintf: FAIL: %d test cases failed\n", fail); 253 | } else { 254 | fprintf(stdout, "xprintf: OK: %d test cases passed\n", pass); 255 | } 256 | return fail; 257 | } 258 | #endif 259 | -------------------------------------------------------------------------------- /kern/console.asm: -------------------------------------------------------------------------------- 1 | %include "module.inc" 2 | 3 | %define log 0 4 | 5 | ; set to 1 to disable AT/XT translation and activate scan code set 2. 6 | ; the XT codes are easier to deal with though :) 7 | %define use_set2 0 8 | 9 | ; TODO: Extract the keyboard driver stuff from this into a separate driver. 10 | ; - Allows ACPICA to use the console again. The problem is that our keyboard 11 | ; driver needs acpica to route interrupts, so we have deadlocks. 12 | ; - Allows e.g. USB and future keyboard drivers 13 | ; - More similar to what a terminal emulator interface might look like. 14 | ; 15 | ; Interface summary: 16 | ; * MSG_CON_WRITE: write to screen 17 | ; (use send - console does not respond) 18 | ; byte to write in esi 19 | ; * MSG_CON_READ: input a character 20 | ; (use sendrcv - the console will block until you've received the input) 21 | ; Accepts fresh handles. 22 | 23 | ; (Internals) 24 | ; * MSG_IRQ_T: keyboard interrupt 25 | ; responds as soon as the new key is in the buffer 26 | ; (after 15 characters, start dropping stuff) 27 | 28 | the_reader equ 1 29 | pic_driver equ 6 30 | 31 | IRQ_KEYBOARD equ 1 32 | KEY_DATA equ 0x60 33 | KEY_CMD equ 0x64 34 | 35 | boot: 36 | %if log 37 | lodstr edi, 'console: booting...', 10 38 | call printf 39 | %endif 40 | 41 | ; bits: 42 | ; 0 = first port IRQ (1 = enabled) 43 | ; 1 = second port irq (disabled) 44 | ; 2 = passed POST (not sure if need to write, but let's say we did pass 45 | ; POST) 46 | ; 3 = should be zero 47 | ; 4 = first ps/2 port clock (1 = disabled) 48 | ; 5 = second ps/2 port clock (1 = disabled) 49 | ; 6 = first ps/2 port translation (1 = enabled) 50 | ; we want this disabled, to get the raw scan codes from keyboard 51 | CONFIG_BYTE equ 1 | 4 52 | 53 | %if use_set2 54 | mov di, KEY_CMD 55 | mov si, 0x60 ; write config byte 56 | call outb 57 | call wait_ready_for_write 58 | mov di, KEY_DATA 59 | mov si, CONFIG_BYTE 60 | call outb 61 | call wait_ready_for_write 62 | mov di, KEY_DATA 63 | mov si, 0xff ; reset 64 | call outb 65 | call wait_ready_for_write 66 | mov di, KEY_DATA 67 | mov si, 0xf0 ; set scan code set 68 | call outb 69 | call wait_ready_for_write 70 | mov di, KEY_DATA 71 | mov si, 2 ; scan code set 2 72 | call outb 73 | %endif 74 | ; Clear the 8042 input buffer twice for good measure? 75 | call clear_buffer 76 | call clear_buffer 77 | 78 | ; TODO Reinitialize the keyboard and 8042 here. We should not assume it 79 | ; has a sane state after the boot loader. 80 | ; Also, the key-up code of the last input to grub often comes in just 81 | ; after boot up. We should ignore that (would come as a bonus after 82 | ; resetting the keyboard.) 83 | 84 | mov edi, pic_driver 85 | push rdi 86 | push byte 0 87 | mov ebp, esp 88 | ; [rbp]: last character read, or 0 89 | ; [rbp+1]: non-zero if there's a process waiting for a key press, or a 90 | ; process in the middle of writing a line. 91 | ; [rbp+4]: shift state 92 | 93 | mov esi, IRQ_KEYBOARD 94 | mov eax, msg_call(MSG_REG_IRQ) 95 | syscall 96 | 97 | %if log 98 | lodstr edi, 'console: boot complete', 10 99 | call printf 100 | %endif 101 | 102 | mov eax, MSG_HMOD 103 | mov edi, the_reader 104 | ; delete 105 | zero esi 106 | zero edx 107 | syscall 108 | 109 | rcv_loop: 110 | zero eax 111 | zero edi 112 | cmp byte [rsp + 1], 0 113 | jnz .waiting 114 | mov edi,the_reader ; recipient == any, or "the" reader 115 | .waiting: 116 | syscall 117 | ; rdi = source 118 | ; rax = message type (MSG_CON_*) 119 | 120 | cmp rdi, [rsp + 8] ; The sender is the PIC driver 121 | je irq_message 122 | 123 | cmp al, MSG_CON_WRITE 124 | jz msg_write 125 | 126 | cmp al, MSG_CON_READ 127 | jz msg_read 128 | 129 | ; unknown 130 | jmp rcv_loop 131 | 132 | msg_write: 133 | push rsi 134 | cmp esi, 10 135 | je .newline 136 | 137 | cmp rdi, the_reader 138 | je .write 139 | 140 | ; rename the writer to the_reader 141 | mov eax, MSG_HMOD 142 | mov esi, the_reader 143 | zero edx 144 | syscall 145 | 146 | .write: 147 | ; Let's cheat for now 148 | ; Later: have the frame buffer mapped in this process instead. 149 | mov eax, MSG_SYSCALL_WRITE 150 | pop rdi ; the character pushed above 151 | syscall 152 | 153 | jmp rcv_loop 154 | 155 | .newline: 156 | ; new line, clear the reader 157 | call clear_reader 158 | jmp .write 159 | 160 | msg_read: 161 | push rdi 162 | %if log 163 | mov rsi, rdi 164 | lodstr edi, 'msg_read from %x', 10 165 | call printf 166 | %endif 167 | 168 | pop rdi 169 | mov eax, MSG_HMOD 170 | mov esi, the_reader 171 | zero edx 172 | syscall 173 | 174 | mov byte [rbp + 1], 1 175 | %if log 176 | lodstr edi, 'Have reader, key=%x waiting=%x', 10 177 | movzx esi, byte [rbp] 178 | movzx edx, byte [rbp + 1] 179 | call printf 180 | %endif 181 | 182 | ; no character queued yet, wait for input 183 | cmp byte [rbp], 0 184 | je rcv_loop 185 | call have_key 186 | jmp rcv_loop 187 | 188 | have_key: 189 | %if log 190 | lodstr edi, 'Have key %x (waiting=%x), sending to reader', 10 191 | movzx esi, byte [rbp] 192 | movzx edx, byte [rbp + 1] 193 | call printf 194 | %endif 195 | 196 | mov edi, the_reader 197 | mov eax, msg_send(MSG_CON_READ) 198 | movzx esi, byte [rbp] 199 | syscall 200 | 201 | ; delete the reader now that it's done 202 | call clear_reader 203 | 204 | zero eax 205 | mov [rbp], eax 206 | 207 | ret 208 | 209 | clear_reader: 210 | mov eax, MSG_HMOD 211 | mov edi, the_reader 212 | zero esi 213 | zero edx 214 | syscall 215 | ret 216 | 217 | irq_message: 218 | push rdi 219 | ; Assume message == MSG_IRQ_T 220 | 221 | mov edi, KEY_DATA 222 | call inb 223 | 224 | mov [rbp], al 225 | %if log 226 | mov esi, eax 227 | movzx edx, byte [rbp + 1] 228 | lodstr edi, 'Key scancode received: %x (waiting=%x)', 10 229 | call printf 230 | %endif 231 | 232 | call map_key 233 | 234 | %if log 235 | movzx esi, byte [rbp] 236 | mov edx, esi 237 | movzx ecx, byte [rbp + 1] 238 | lodstr edi, 'ASCII received: %c (%x) (waiting=%x)', 10 239 | call printf 240 | %endif 241 | 242 | ; mapping ate the key, or it didn't have a mapping or shouldn't output 243 | ; any characters. 244 | cmp byte [rbp], 0 245 | jz .ack_and_ret 246 | cmp byte [rbp + 1], 0 247 | jz .ack_and_ret 248 | call have_key 249 | .ack_and_ret: 250 | 251 | pop rdi 252 | mov eax, msg_send(MSG_IRQ_ACK) 253 | syscall 254 | 255 | jmp rcv_loop 256 | 257 | %if use_set2 258 | wait_ready_for_write: 259 | mov di, KEY_CMD 260 | call inb 261 | test al, 2 262 | jnz wait_ready_for_write 263 | ret 264 | %endif 265 | 266 | clear_buffer: 267 | mov di, KEY_DATA 268 | call inb 269 | mov di, KEY_CMD 270 | call inb 271 | test al, 1 272 | jnz clear_buffer 273 | .ret ret 274 | 275 | IS_SHIFTED equ 1 276 | 277 | ; one key event: optional e0 (or e1/e2), followed by one make/break code 278 | ; for now, we ignore all e0 codes and only look at the "normal" code that follows 279 | 280 | ; [rbp] = key 281 | ; [rbp + 4] = shift state 282 | map_key: 283 | movzx eax, byte [rbp] 284 | cmp al, 0xe0 285 | je .ignore_this 286 | 287 | and al, 0x7f 288 | cmp al, keymap.numkeys 289 | ja .ignore_this 290 | 291 | .can_map_key: 292 | test byte [rbp + 4], IS_SHIFTED 293 | jz .not_shifted 294 | add eax, keymap.shifted - keymap 295 | .not_shifted: 296 | mov al, [keymap + rax] 297 | cmp al, SPEC_SHIFT 298 | je .shift 299 | 300 | .key_mapped: 301 | ; press events don't generate any characters 302 | test byte [rbp], 0x80 303 | jz .ret 304 | .ignore_this: 305 | xor eax,eax 306 | .ret 307 | ; al is ascii key code of a released normal key (or 0 for an ignored 308 | ; one). 309 | mov byte [rbp], al 310 | ret 311 | 312 | .shift: 313 | and byte [rbp + 4], ~IS_SHIFTED 314 | test byte [rbp], 0x80 315 | jnz .ignore_this 316 | or byte [rbp + 4], IS_SHIFTED 317 | jmp .ignore_this 318 | 319 | %include "portio.inc" 320 | %if log 321 | %include "printf.inc" 322 | %include "putchar.inc" 323 | %endif 324 | %include "keymap.inc" 325 | -------------------------------------------------------------------------------- /kern/irq.asm: -------------------------------------------------------------------------------- 1 | %include "module.inc" 2 | 3 | %define log 0 4 | 5 | ; This is the out-of-kernel part of the rawIRQ handling. This is started by the 6 | ; kernel as the first process, and handed to the second process in rdi. (At 7 | ; this point there are no handles in that process, so the handle key can be 8 | ; anything.) 9 | 10 | ; Interface: 11 | ; MSG_REG_IRQ: Register the sender for an IRQ (irq number in rsi) 12 | ; MSG_IRQ_ACK: Acknowledge an IRQ as received 13 | ; (currently ignored because the "raw" irq handler doesn't do EOI handling etc.) 14 | ; 15 | ; Sends: 16 | ; MSG_IRQ_T: to a handle registered for an IRQ 17 | 18 | ; Assume we only have interrupts 0..255 (actually 32..255 since the first 32 19 | ; are reserved for cpu exceptions) 20 | fresh_handle equ 256 21 | ; Further limit the interrupts we actually support 22 | %define MAX_IRQ 0x100 23 | %define IRQ_START 0x20 24 | %define NUM_IRQS (MAX_IRQ - IRQ_START) 25 | 26 | boot: 27 | ; no parameters here 28 | 29 | ; Allocate space for bits for which interrupts have listeners 30 | zero eax 31 | %define NUM_IRQ_WORDS (NUM_IRQS + 63) / 64 32 | ; The stack instructions are ridiculously cheap! Storing the count and doing 33 | ; the loop takes 6 bytes, push rax takes 1 byte... 34 | %if NUM_IRQ_WORDS > 6 35 | lea ecx, [rax + NUM_IRQ_WORDS] 36 | .pushloop: 37 | push rax 38 | loop .pushloop 39 | %else 40 | times NUM_IRQ_WORDS push rax 41 | %endif 42 | 43 | %if log 44 | lodstr edi, 'rawIRQ: boot complete.', 10 45 | call puts 46 | %endif 47 | 48 | rcv_loop: 49 | zero eax 50 | mov edi, fresh_handle 51 | syscall 52 | 53 | cmp al, MSG_REG_IRQ 54 | je reg_irq 55 | 56 | cmp al, MSG_PULSE 57 | jne rcv_loop 58 | 59 | irq: 60 | ; received interrupt 61 | ; rdi = null (magic message from kernel) 62 | ; rsi = interrupt mask 63 | 64 | %if log 65 | test [rsp + 4], rsi 66 | jnz .registered 67 | lodstr edi, "rawIRQ: %x triggered but I'm not listening", 10 68 | call printf 69 | jmp rcv_loop 70 | %endif 71 | 72 | .registered: 73 | and esi, [rsp + 4] 74 | jz rcv_loop 75 | 76 | ; Some interrupts were interesting 77 | zero ebx 78 | mov rbp, rsi 79 | .loop: 80 | test rbp, rbp 81 | jz rcv_loop 82 | btr rbp, rbx 83 | jnc .cont 84 | 85 | %if log 86 | lodstr edi, 'rawIRQ: %x triggered', 10 87 | lea esi, [ebx + IRQ_START] 88 | call printf 89 | %endif 90 | lea edi, [ebx + IRQ_START] 91 | ; We only use bit 0, but we could also let the caller choose. 92 | zero esi 93 | inc esi 94 | mov eax, MSG_PULSE 95 | syscall 96 | 97 | .cont: 98 | inc ebx 99 | and ebx, byte 63 100 | jnz .loop 101 | jmp rcv_loop 102 | 103 | ; rdi is the fresh handle that wants to register 104 | ; rsi is the interrupt number 105 | ; TODO: support multiple listeners for one interrupt, make a list of them, 106 | ; allocate handles dynamically. 107 | reg_irq: 108 | ; Set the flag that says we have a listener for that interrupt. 109 | ; (This is to make sure we know whether to try to forward an incoming 110 | ; interrupt or not.) 111 | bts dword [rsp], esi 112 | 113 | ; Remap the fresh handle to the interrupt number 114 | push rsi 115 | zero edx 116 | mov eax, MSG_HMOD 117 | syscall 118 | 119 | %if log 120 | lodstr edi, 'rawIRQ: %x registered', 10 121 | mov rsi, [rsp] 122 | call printf 123 | %endif 124 | 125 | pop rdi 126 | mov rsi, rdi 127 | mov eax, msg_send(MSG_REG_IRQ) 128 | syscall 129 | 130 | jmp rcv_loop 131 | 132 | %if log 133 | %include "printf.inc" 134 | %include "putchar.inc" 135 | %endif 136 | -------------------------------------------------------------------------------- /kern/keymap.inc: -------------------------------------------------------------------------------- 1 | ; US layout, scan code set 2/1 -> ASCII table 2 | ; Preprocessing: e0, e1, e2 are either ignored or add some constant to the key 3 | ; code value 4 | ; positive values are ascii key codes, negative values are some kind of special 5 | ; key. zeroes are ignored. 6 | 7 | SPEC_SHIFT equ -1 8 | SPEC_CTRL equ 0 ; ignored for now 9 | 10 | keymap: 11 | %if use_set2 12 | %error Meh 13 | ; 01..0c: F keys 14 | ; 0d: tab 15 | ; 0e: `~ 16 | ; 11: left Alt 17 | ; 12: left Shift 18 | ; 14: left control 19 | ; 15: q 20 | %else 21 | ; 0: dummy value 22 | db 0 23 | ; 1..e: first row 24 | ; 25 | ; 1: esc 26 | ; 2..b: 1..9, 0 27 | ; c: -_ 28 | ; d: =+ 29 | ; e: backspace = 8 (BS) - but could also be 0x74 (DEL) 30 | db 27,'1234567890-=',8 31 | ; f..1c: second row, tab, qweryuiop[], ending with enter 32 | db 9,'qwertyuiop[]',10 33 | ; 1d..28: left control, asdfghkl;', 34 | db SPEC_CTRL,"asdfghjkl;'" 35 | ; 29: ` 36 | db '`' 37 | ; 2a..36: left shift, \zxcvbnm,./, right shift 38 | db SPEC_SHIFT,'\zxcvbnm,./', SPEC_SHIFT 39 | ; 37, 38, 39: keypad *, left alt, space 40 | db '*', 0, ' ' 41 | ; 3a: CapsLock 42 | ; 2b..44: F1..F10 43 | ; 46,46: NumLock, ScrollLock 44 | ; 47..53: keypad: 789-456+1230. 45 | ; 57, 58: F11, F12 46 | %endif 47 | 48 | .end: 49 | .numkeys equ .end - keymap 50 | .shifted_: 51 | .shifted equ .shifted_ - 1 52 | ; 27,'1234567890-=',8 53 | db 27,'!@#$%^&*()_+',8 54 | ; 9,'qwertyuiop[]',10 55 | db 9,'QWERTYUIOP{}',10 56 | ; SPEC_CTRL,"asdfghjkl;'" 57 | db SPEC_CTRL,'ASDFGHJKL:"' 58 | ; '`' 59 | db '~' 60 | ; SPEC_SHIFT,'\zxcvbnm,./', SPEC_SHIFT 61 | db SPEC_SHIFT,'|ZXCVBNM<>?', SPEC_SHIFT 62 | db '*', 0, ' ' 63 | -------------------------------------------------------------------------------- /kern/pic.asm: -------------------------------------------------------------------------------- 1 | ; PIC driver, wraps IRQ driver for PIC-originated interrupts 2 | %include "module.inc" 3 | %include "pic.inc" 4 | 5 | %define log 0 6 | 7 | ; Base vector for PIC interrupts. Assume slave is mapped at base + 8. 8 | ; Clients are mapped at PIC_IRQ_BASE..PIC_IRQ_BASE+15 9 | ; This is also the same as the raw IRQ numbers we listen to. 10 | ; Note: clients register for IRQs 0..15 :) 11 | PIC_IRQ_BASE equ 0x20 12 | ; Base where we map incoming IRQs. (handles to rawIRQ process) 13 | IN_IRQ_BASE equ 0x30 14 | 15 | IRQ_DRIVER equ 1 16 | fresh_handle equ 0x100 17 | 18 | boot: 19 | ; Input: IRQ driver in rdi 20 | mov edi, IRQ_DRIVER 21 | push rdi 22 | ; Check that rdi doesn't overlap our internal stuff: 23 | ; 0x20..0x2f: keep track of our clients 24 | ; 0x30..0x3f: our receivers for interrupts 25 | ; 1: temporary handle for something that is registering itself 26 | 27 | %if log 28 | lodstr edi, 'PIC booting...', 10 29 | call printf 30 | %endif 31 | 32 | ; Reinitialize PIC? 33 | ; * Mask all interrupts (we don't want them until someone registers) 34 | ; * Map either to a constant range of real interrupts, or have some 35 | ; way to "allocate" through the IRQ driver? 36 | 37 | ; For now - assume PICs are mapped to 0x20..0x2f and all interrupts are 38 | ; masked (this is what start32.inc does). 39 | 40 | mov ebx, 16 41 | .reg_loop: 42 | ; Duplicate IRQ handler -> 0x30..0x3f (incoming IRQ) 43 | mov rdi, [rsp] 44 | mov rsi, rdi 45 | lea edx, [rbx + IN_IRQ_BASE - 1] 46 | mov eax, MSG_HMOD 47 | syscall 48 | ; Then register it for IRQ 0x20..0x2f 49 | lea edi, [rbx + IN_IRQ_BASE - 1] 50 | lea esi, [rbx + PIC_IRQ_BASE - 1] 51 | mov eax, msg_call(MSG_REG_IRQ) 52 | syscall 53 | dec ebx 54 | jnz .reg_loop 55 | 56 | mov eax, MSG_HMOD 57 | mov edi, fresh_handle 58 | ; delete 59 | zero esi 60 | zero edx 61 | syscall 62 | 63 | mov edi, PIC1_CMD 64 | mov esi, PIC_EOI 65 | call outb 66 | mov edi, PIC2_CMD 67 | mov esi, PIC_EOI 68 | call outb 69 | 70 | %if log 71 | mov rsi, [rsp] 72 | lodstr edi, 'PIC boot complete. rawIRQ is %x', 10 73 | call printf 74 | %endif 75 | 76 | rcv_loop: 77 | zero eax 78 | mov edi, fresh_handle 79 | syscall 80 | 81 | push rax 82 | push rdi 83 | push rsi 84 | 85 | %if log 86 | mov rcx, rsi 87 | mov rdx, rdi 88 | mov rsi, rax 89 | lodstr edi, 'PIC received %x from %x: %x', 10 90 | call printf 91 | %endif 92 | 93 | pop rsi 94 | pop rdi 95 | pop rax 96 | 97 | cmp edi, IN_IRQ_BASE + 16 98 | jae .not_irq 99 | cmp edi, IN_IRQ_BASE 100 | jae irq 101 | 102 | .not_irq: 103 | cmp ax, MSG_KIND_CALL | MSG_REG_IRQ 104 | je reg_irq 105 | 106 | cmp al, MSG_IRQ_ACK 107 | je ack_irq 108 | 109 | %if log 110 | lodstr edi, 'Message %x not handled', 10 111 | mov rsi, rax 112 | call printf 113 | %endif 114 | 115 | jmp rcv_loop 116 | 117 | ; rsi = 0..15 118 | ; rdi = (probably) fresh handle 119 | ; 120 | ; Remap rdi to rdi + PIC_IRQ_BASE (where we have our clients), then unmask 121 | ; the corresponding IRQ. 122 | reg_irq: 123 | push rsi 124 | %if log 125 | push rdi 126 | mov rdx, rdi 127 | lodstr edi, 'PIC registering IRQ %x to %x', 10 128 | call printf 129 | 130 | pop rdi 131 | mov rsi, [rsp] 132 | %endif 133 | 134 | ; Rename incoming handle to the IRQ they registered + PIC_IRQ_BASE 135 | add esi, PIC_IRQ_BASE 136 | mov eax, MSG_HMOD 137 | zero edx 138 | syscall 139 | 140 | mov edi, [rsp] 141 | call unmask 142 | 143 | %if log 144 | mov rsi, [rsp] 145 | lodstr edi, 'PIC: IRQ %x registered', 10 146 | call printf 147 | %endif 148 | 149 | ; Send response to tell caller they're registered 150 | %if log 151 | mov rdi, [rsp] 152 | %else 153 | pop rdi 154 | %endif 155 | add edi, PIC_IRQ_BASE 156 | mov eax, msg_send(MSG_REG_IRQ) 157 | syscall 158 | 159 | %if log 160 | lodstr edi, 'PIC: registration acknowledged', 10 161 | pop rsi 162 | call printf 163 | %endif 164 | 165 | jmp rcv_loop 166 | 167 | irq: 168 | %if log 169 | push rdi 170 | lea esi, [rdi - IN_IRQ_BASE] 171 | lodstr edi, 'PIC: IRQ %x triggered', 10 172 | call printf 173 | pop rdi 174 | %endif 175 | 176 | ; rdi = IN_IRQ_BASE + num 177 | sub edi, IN_IRQ_BASE 178 | push rdi 179 | 180 | cmp edi, 0x8 181 | jb .only_master 182 | 183 | ; Slave IRQ. Mask the slave irq and EOI the slave PIC. 184 | lea esi, [edi - 8] 185 | mov edi, PIC2_DATA 186 | call pic_mask 187 | 188 | mov edi, PIC2_CMD 189 | mov esi, PIC_EOI 190 | call outb 191 | 192 | jmp .skip_mask 193 | 194 | .only_master: 195 | ; TODO Check for spurious IRQs for 7 (and 15) 196 | ; Mask interrupt (ignore it until the driver has responded back to us) 197 | mov esi, edi 198 | mov edi, PIC1_DATA 199 | call pic_mask 200 | 201 | .skip_mask: 202 | ; Since we use the mask to control exactly which IRQs get delivered, we 203 | ; can use non-specific EOI (and we do it right away so that another IRQ 204 | ; can get delivered ASAP). 205 | mov edi, PIC1_CMD 206 | mov esi, PIC_EOI 207 | call outb 208 | 209 | ; Unmask later when we get a response from the handler. 210 | 211 | pop rsi 212 | lea edi, [rsi + PIC_IRQ_BASE] 213 | zero esi 214 | inc esi 215 | mov eax, MSG_PULSE 216 | syscall 217 | 218 | jmp rcv_loop 219 | 220 | ack_irq: 221 | %if log 222 | push rdi 223 | mov esi, edi 224 | lodstr edi, 'PIC: IRQ %x acknowledged', 10 225 | call printf 226 | pop rdi 227 | %endif 228 | 229 | ; The EOI was already sent. Now we just need to unmask the interrupt 230 | ; to allow it to be delivered again. 231 | 232 | ; TODO Needs some validation :) 233 | sub edi, PIC_IRQ_BASE 234 | call unmask 235 | jmp rcv_loop 236 | 237 | unmask: 238 | %if log 239 | push rdi 240 | mov esi, edi 241 | lodstr edi, 'PIC: unmasking %x', 10 242 | call printf 243 | pop rdi 244 | %endif 245 | 246 | cmp edi, 8 247 | jae unmask_slave 248 | 249 | .not_slave: 250 | mov esi, edi 251 | mov edi, PIC1_DATA 252 | call pic_unmask 253 | 254 | ret 255 | 256 | unmask_slave: 257 | lea esi, [rdi - 8] 258 | mov edi, PIC2_DATA 259 | call pic_unmask 260 | mov edi, 2 261 | jmp unmask.not_slave 262 | 263 | ; edi = PIC port 264 | ; esi = bit to unmask 265 | pic_unmask: 266 | push rdi 267 | push rsi 268 | call inb 269 | pop rsi 270 | btr eax, esi 271 | pop rdi 272 | mov esi, eax 273 | jmp outb 274 | 275 | ; edi = PIC port 276 | ; esi = bit to mask 277 | pic_mask: 278 | push rdi 279 | push rsi 280 | call inb 281 | pop rsi 282 | bts eax, esi 283 | pop rdi 284 | mov esi, eax 285 | jmp outb 286 | 287 | %include "portio.inc" 288 | %if log 289 | %include "printf.inc" 290 | %include "putchar.inc" 291 | %endif 292 | -------------------------------------------------------------------------------- /kern/portio.inc: -------------------------------------------------------------------------------- 1 | 2 | ; di = port 3 | ; => ax = input 4 | inb: 5 | mov eax, MSG_SYSCALL_IO 6 | mov esi, 0x01 7 | syscall 8 | ret 9 | 10 | ; di = port 11 | ; sil = byte to output 12 | outb: 13 | mov edx, esi 14 | mov esi, 0x11 15 | mov eax, MSG_SYSCALL_IO 16 | syscall 17 | ret 18 | 19 | -------------------------------------------------------------------------------- /kern/putchar.inc: -------------------------------------------------------------------------------- 1 | putchar: 2 | mov eax, MSG_SYSCALL_WRITE 3 | syscall 4 | ret 5 | -------------------------------------------------------------------------------- /notes/bochs.txt: -------------------------------------------------------------------------------- 1 | Configuration required for Bochs (2.6.2): 2 | 3 | Required: 4 | --enable-x86-64 --enable-pci --enable-clgd54xx --enable-e1000 --enable-avx 5 | 6 | Recommended: 7 | * debugger 8 | * "x86 debugger" (not sure what the difference is) 9 | * readline! 10 | 11 | --enable-debugger --enable-x86-debugger 12 | --enable-disasm --enable-idle-hack 13 | --enable-readline 14 | 15 | Optimization flags: 16 | --enable-all-optimizations 17 | 18 | Whole shebang: 19 | ./configure -C --prefix=/opt/bochs --enable-debugger --enable-x86-debugger --enable-x86-64 --enable-all-optimizations --enable-pci --enable-readline --enable-clgd54xx --enable-disasm --enable-idle-hack --enable-ne2000 --enable-e1000 --enable-avx 20 | 21 | 22 | Mac OS using homebrew: 23 | 24 | The e1000 module is not enabled by default, so do a brew edit bochs to add 25 | --enable-e1000 option to configure, and insert the following patch to disable 26 | the "socket" netmod (which has some portability issues): 27 | 28 | 29 | diff --git a/configure b/configure 30 | index 3979ae3..2257c45 100755 31 | --- a/configure 32 | +++ b/configure 33 | @@ -23637,7 +23637,7 @@ fi 34 | 35 | NETLOW_OBJS="$NETLOW_OBJS eth_socket.o" 36 | ethernet_modules="$ethernet_modules socket" 37 | - $as_echo "#define BX_NETMOD_SOCKET 1" >>confdefs.h 38 | + $as_echo "#define BX_NETMOD_SOCKET 0" >>confdefs.h 39 | 40 | fi 41 | if test "$MSVC_TARGET" = 0; then 42 | diff --git a/misc/bxhub.cc b/misc/bxhub.cc 43 | index 71438b0..ece9e77 100644 44 | --- a/misc/bxhub.cc 45 | +++ b/misc/bxhub.cc 46 | @@ -53,6 +53,9 @@ typedef int SOCKET; 47 | #define MSG_NOSIGNAL 0 48 | #define MSG_DONTWAIT 0 49 | #endif 50 | +#ifdef __APPLE__ 51 | +#define MSG_NOSIGNAL 0 52 | +#endif 53 | 54 | #include "misc/bxcompat.h" 55 | #include "osdep.h" 56 | 57 | -------------------------------------------------------------------------------- /notes/slirp.conf: -------------------------------------------------------------------------------- 1 | # slirp config 2 | # WTF. The above line is necessary for slirp to accept the config... 3 | # 4 | # To set up port forwarding in Bochs, this file is pointed out as the slirp 5 | # netmod's "script" in bochsrc. 6 | hostfwd = tcp::5555-:80 7 | -------------------------------------------------------------------------------- /run_qemu.sh: -------------------------------------------------------------------------------- 1 | ISO=${ISO-out/grub.iso} 2 | make || exit $? 3 | VGA="-vga std" 4 | NETDEV="user,id=vmnet0,hostfwd=tcp::5555-:80" 5 | #NETDEV=${NETDEV-tap,id=vmnet0,script=no,ifname=tap0,downscript=no} 6 | # New syntax required if using -machine q35 7 | #CD="-drive file=$ISO,if=none,media=cdrom,id=cd1 -device ide-cd,drive=cd1" 8 | ${QEMU-qemu-system-x86_64} -cpu SandyBridge -smp 2 -m 32M $VGA "$@" -cdrom $ISO -netdev $NETDEV -device e1000,netdev=vmnet0 9 | -------------------------------------------------------------------------------- /test/test_common.h: -------------------------------------------------------------------------------- 1 | #include "../cuser/common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define SYS_WRITE SYSCALL_WRITE 8 | 9 | enum msg_test { 10 | MSG_STEP = MSG_USER, 11 | MSG_RESULT, 12 | }; 13 | 14 | __attribute__((noreturn)) static void pass(void) { 15 | puts("PASS"); 16 | abort(); 17 | } 18 | 19 | __attribute__((noreturn)) static void fail(void) { 20 | puts("FAIL"); 21 | abort(); 22 | } 23 | 24 | static void assert_eq(const char *file, int line, const char* exp_s, uintptr_t exp, const char *actual_s, uintptr_t actual) { 25 | if (exp != actual) { 26 | printf("%s:%d: Expected %s == %s (%ld) but got %ld\n", file, line, actual_s, exp_s, exp, actual); 27 | fail(); 28 | } 29 | } 30 | 31 | #define ASSERT_EQ(exp, actual) assert_eq(__FILE__, __LINE__, #exp, exp, #actual, actual) 32 | 33 | static void wait_for_master(ipc_dest_t rcpt, ipc_arg_t step) { 34 | ipc_arg_t arg0; 35 | ipc_msg_t msg = recv1(&rcpt, &arg0); 36 | ASSERT_EQ(msg, MSG_STEP); 37 | ASSERT_EQ(arg0, step); 38 | } 39 | -------------------------------------------------------------------------------- /test/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | sys.path.append(os.path.dirname(__file__)) 6 | 7 | from testlib import * 8 | 9 | @with_procs(2) 10 | def test_pulse1(M, A, B): 11 | result = A.recv(B, 1) 12 | B.pulse(A, 1) # .expect(0) # TODO pulse doesn't actually return anything 13 | result.expect(PULSE, B, 1) 14 | 15 | if __name__=="__main__": 16 | main(globals()) 17 | 18 | -------------------------------------------------------------------------------- /toolchain/.gitignore: -------------------------------------------------------------------------------- 1 | /logs/ 2 | /cross*/ 3 | /src/ 4 | -------------------------------------------------------------------------------- /toolchain/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . ../build/buildfuncs.sh 6 | 7 | BINUTILSVER=binutils-2.32 8 | GCCVERNUM=8.3.0 9 | GCCVER=gcc-${GCCVERNUM} 10 | PREFIX=`pwd`/cross-$GCCVERNUM 11 | LOGDIR=`pwd`/logs 12 | PATH="$PATH:$PREFIX/bin" 13 | 14 | mkdir -p src "$PREFIX" "$LOGDIR" 15 | cd src 16 | 17 | GET ftp://ftp.nluug.nl/mirror/gnu/binutils "${BINUTILSVER}.tar.xz" 0ab6c55dd86a92ed561972ba15b9b70a8b9f75557f896446c82e8b36e473ee04 18 | GET ftp://ftp.nluug.nl/mirror/languages/gcc/releases/$GCCVER "${GCCVER}.tar.xz" 64baadfe6cc0f4947a84cb12d7f0dfaf45bb58b7e92461639596c21e02d97d2c 19 | 20 | unpack "$BINUTILSVER" 21 | unpack "$GCCVER" 22 | #unpack "$MPFRVER" "$GCCVER/mpfr" 23 | #unpack "$GMPVER" "$GCCVER/gmp" 24 | #unpack "$MPCVER" "$GCCVER/mpc" 25 | 26 | mkdir -p build-$BINUTILSVER 27 | cd build-$BINUTILSVER 28 | setlog binutils_configure 29 | CONFIGURE "$BINUTILSVER" --target=$TARGET --prefix="$PREFIX" --with-sysroot --disable-nls --disable-werror 30 | setlog binutils_build 31 | MAKE 32 | MAKE install 33 | clearlog 34 | cd .. 35 | 36 | # Check that binutils installed successfully and is in path 37 | which -- $TARGET-as >/dev/null || echo $TARGET-as is not in the PATH 38 | 39 | mkdir -p build-$GCCVER 40 | cd build-$GCCVER 41 | setlog gcc_configure 42 | CONFIGURE "$GCCVER" --target=$TARGET --prefix="$PREFIX" --disable-nls --enable-languages=c,c++ --without-headers --disable-libstdcxx 43 | setlog gcc_build 44 | MAKE all-gcc all-target-libgcc 45 | MAKE install-strip-gcc install-strip-target-libgcc 46 | clearlog 47 | cd .. 48 | -------------------------------------------------------------------------------- /toolchain/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -fr src/build-binutils* src/build-gcc* 4 | -------------------------------------------------------------------------------- /user/loop.asm: -------------------------------------------------------------------------------- 1 | %include "module.inc" 2 | 3 | jmp $ 4 | -------------------------------------------------------------------------------- /user/newproc.asm: -------------------------------------------------------------------------------- 1 | %include "module.inc" 2 | 3 | CHILD_HANDLE equ 1 4 | 5 | mov edi, CHILD_HANDLE 6 | mov ebx, edi ; save it away for later 7 | mov esi, user_entry_new 8 | mov edx, NEWPROC_PROC 9 | mov r8d, end_of_module 10 | mov eax, MSG_NEWPROC 11 | syscall 12 | 13 | lodstr edi, 'old: calling new proc...', 10 14 | call printf 15 | 16 | mov esi, 1 17 | mov edx, 2 18 | mov r8, 3 19 | mov r9, 4 20 | mov r10, 5 21 | mov eax, msg_call(MSG_USER) 22 | mov edi, ebx 23 | syscall 24 | 25 | lodstr edi, 'old: call returned %x from %x: %x %x %x %x %x', 10 26 | call log_message 27 | 28 | lodstr edi, 'old calling %p...', 10 29 | mov rsi, rbx 30 | call printf 31 | 32 | mov esi, 1 33 | mov edx, 2 34 | mov r8, 3 35 | mov r9, 4 36 | mov r10, 5 37 | 38 | .call_new: 39 | mov eax, msg_call(MSG_USER) 40 | mov rdi, rbx 41 | syscall 42 | 43 | test si, si 44 | jnz .call_new 45 | lodstr edi, 'old received %p %x %x %x %x', 10 46 | call log_message 47 | 48 | jmp .call_new 49 | 50 | user_entry_new: 51 | ; Creator pid (not parent, parents don't really exist here...) 52 | push rdi 53 | 54 | ; TODO Define the initial program state for a process. 55 | ; - parent pid 56 | ; - function parameters in registers? 57 | ; - address space 58 | 59 | .loop: 60 | mov rsi, [rsp] 61 | ;lodstr edi, 'new receiving from %p...', 10 62 | ; call printf 63 | 64 | mov rdi, [rsp] 65 | zero eax 66 | syscall 67 | 68 | ; (ax di si dx r8 r9 10) -> 69 | ; di (si dx cx r8 r9 st st) 70 | 71 | ;lodstr edi, 'new received %x from %x: %x %x %x %x %x', 10 72 | ; call log_message 73 | 74 | mov rdi, [rsp] 75 | inc rsi 76 | mov eax, msg_send(MSG_USER + 1) 77 | syscall 78 | 79 | jmp .loop 80 | 81 | ; rdi = log message 82 | ; [rsp+8] = old rdi 83 | ; other registers: as they were when received 84 | ; returns: all message registers except rdi restored 85 | log_message: 86 | push r8 87 | push rdx 88 | push rsi 89 | push rax 90 | push r10 91 | push r9 92 | mov r9, r8 93 | mov r8, rdx 94 | mov rcx, rsi 95 | mov rdx, [rsp + 7*8] 96 | mov rsi, rax 97 | call printf 98 | pop r9 99 | pop r10 100 | pop rax 101 | pop rsi 102 | pop rdx 103 | pop r8 104 | ret 105 | 106 | %include "printf.inc" 107 | %include "putchar.inc" 108 | -------------------------------------------------------------------------------- /user/putchar.inc: -------------------------------------------------------------------------------- 1 | ; edi: character to put 2 | putchar: 3 | %if 0 4 | mov esi, edi 5 | mov rdi, HANDLE_CONSOLE 6 | mov eax, msg_send(MSG_CON_WRITE) 7 | %else 8 | mov eax, MSG_SYSCALL_WRITE 9 | %endif 10 | syscall 11 | clear_clobbered 12 | ret 13 | -------------------------------------------------------------------------------- /user/shell.asm: -------------------------------------------------------------------------------- 1 | %include "module.inc" 2 | 3 | con_handle equ 3 4 | 5 | boot: 6 | ; We have 0x1000 (4k) of stack. Let's allocate 1024 or so for reading 7 | ; a line. 8 | sub esp, 0x400 9 | push rsp 10 | 11 | lodstr edi, "Shell v0.1", 10 12 | call printf 13 | 14 | prompt: 15 | mov edi, '$' 16 | call putchar 17 | mov edi, ' ' 18 | call putchar 19 | 20 | cmd_loop: 21 | call getchar 22 | pop rdi 23 | push rdi 24 | 25 | cmp al, 0xa 26 | je newline 27 | 28 | %if 1 29 | mov edi, eax 30 | call putchar 31 | %else 32 | lodstr edi, 'Char %x', 10 33 | mov edi, eax 34 | call printf 35 | %endif 36 | 37 | jmp cmd_loop 38 | 39 | newline: 40 | mov edi, 10 41 | call putchar 42 | 43 | jmp prompt 44 | 45 | putchar: 46 | mov esi, edi 47 | mov edi, con_handle 48 | mov eax, msg_send(MSG_CON_WRITE) 49 | syscall 50 | ret 51 | 52 | getchar: 53 | mov edi, con_handle 54 | mov eax, msg_call(MSG_CON_READ) 55 | syscall 56 | mov eax, esi 57 | ret 58 | 59 | %include "printf.inc" 60 | -------------------------------------------------------------------------------- /user/test_puts.asm: -------------------------------------------------------------------------------- 1 | %include "module.inc" 2 | 3 | movq xmm1, rbx 4 | movq xmm0, xmm1 5 | .start: 6 | mov ebx, 7 7 | .loop 8 | lea edi,['a'+rbx] 9 | call putchar 10 | 11 | paddq xmm0, xmm1 12 | dec ebx 13 | jnz .loop 14 | 15 | .end: 16 | lodstr edi, 'Hello World from puts', 10 17 | call puts 18 | 19 | lodstr edi, 'printf %% "%s" %c',10,0 20 | lodstr esi, 'Hello World',0 21 | mov edx,'C' 22 | call printf 23 | 24 | mov eax, MSG_SYSCALL_YIELD 25 | syscall 26 | 27 | jmp .start 28 | 29 | %include "printf.inc" 30 | %include "putchar.inc" 31 | -------------------------------------------------------------------------------- /user/test_xmm.asm: -------------------------------------------------------------------------------- 1 | %include "module.inc" 2 | 3 | mov ebx, 2 4 | movq xmm1, rbx 5 | movq xmm0, xmm1 6 | .loop: 7 | mov edi,'2' 8 | call putchar 9 | 10 | paddq xmm0,xmm1 11 | 12 | mov eax, MSG_SYSCALL_YIELD 13 | syscall 14 | 15 | jmp .loop 16 | 17 | %include "putchar.inc" 18 | -------------------------------------------------------------------------------- /utils/cpuid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef int32_t i32; 7 | typedef uint32_t u32; 8 | 9 | u32 parse_hex(const char* arg) 10 | { 11 | char temp[9]; 12 | char* p = temp, *pend = temp+sizeof(temp); 13 | memset(p, 0, pend-p); 14 | if (arg[0] == '0' && (arg[1] == 'x' || arg[1] == 'X')) 15 | arg+=2; 16 | while (*arg && *arg != ':') 17 | { 18 | if (*arg != '_') 19 | *p++ = *arg; 20 | arg++; 21 | } 22 | 23 | return strtoul(temp, NULL, 16); 24 | } 25 | 26 | static const char* regs[] = { "eax","ebx","ecx","edx",NULL }; 27 | void parse_regbit(const char* bitarg, int* reg, int* bit) 28 | { 29 | const char* colon = strchr(bitarg, ':'); 30 | if (colon && colon-bitarg != 3) 31 | { 32 | fprintf(stderr, "Invalid register/bit argument: expected 3 chars\n"); 33 | abort(); 34 | } 35 | const char** regsp = regs; 36 | while (const char* regname = *regsp++) 37 | { 38 | if (!strncasecmp(regname, bitarg, 3)) 39 | *reg = regsp-regs-1; 40 | } 41 | if (colon) 42 | { 43 | *bit = atoi(colon+1); 44 | } 45 | } 46 | 47 | void cpuid(u32 cpuid_num, u32 cpuid_num2, u32* data) 48 | { 49 | __asm__ __volatile__( 50 | "cpuid" 51 | : "=a" (data[0]) 52 | , "=b" (data[1]) 53 | , "=c" (data[2]) 54 | , "=d" (data[3]) 55 | : "a" (cpuid_num) 56 | , "c" (cpuid_num2) 57 | : ); 58 | } 59 | 60 | void print_info(u32 num, u32 num2, u32* data, int reg, int bit) 61 | { 62 | printf("CPUID %08x:%08x:\n", num, num2); 63 | for (int i=0;i<4;i++) 64 | printf("%s: %08x\n", regs[i], data[i]); 65 | printf("\n"); 66 | if (reg >= 0 && bit >= 0) 67 | printf("Reg %s bit %d: %s\n", regs[reg], bit, (data[reg] & (1 << bit)) ? "set" : "clear"); 68 | } 69 | 70 | int main(int argc, const char *const argv[]) 71 | { 72 | const char* idarg = NULL; // 8-digit hexadecimal number, _'s ignored 73 | const char* bitarg = NULL; // reg[:bit], if not given print everything. 74 | 75 | if (argc >= 2) 76 | idarg = argv[1]; 77 | if (argc >= 3) 78 | bitarg = argv[2]; 79 | 80 | u32 cpuid_num = 0, cpuid_num2 = 0; 81 | if (idarg) 82 | { 83 | cpuid_num = parse_hex(idarg); 84 | if (const char *col = strchr(idarg, ':')) 85 | { 86 | cpuid_num2 = parse_hex(col + 1); 87 | } 88 | } 89 | int bitnum = -1; 90 | int reg = -1; 91 | if (bitarg) parse_regbit(bitarg, ®, &bitnum); 92 | 93 | printf("cpuid'ing %08x %d bit %d\n", cpuid_num, reg, bitnum); 94 | 95 | u32 data[4]; 96 | cpuid(cpuid_num, cpuid_num2, data); 97 | print_info(cpuid_num, cpuid_num2, data, reg, bitnum); 98 | } 99 | -------------------------------------------------------------------------------- /utils/rflags.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | uint64_t rflags; 7 | __asm__ __volatile__ ("pushf; popq %0" : "=g" (rflags) ::"memory"); 8 | printf("rFLAGS: %016llx\n", (unsigned long long)rflags); 9 | #define BIT(name, number) \ 10 | printf("#%d\t%s:\t%d\n", number, name, rflags & (1 << number) ? 1 : 0) 11 | 12 | const char* bits[] = { 13 | "CF", 14 | "Reserved (1)", 15 | "PF", 16 | "Reserved (0)", 17 | "AF", 18 | "Reserved (0)", 19 | "ZF (Zero)", 20 | "SF (Sign)", 21 | "TF (Trap)", 22 | "IF (Interrupt)", 23 | "DF (Direction)", 24 | "OF (Overflow)", 25 | "IOPL LSB", 26 | "IOPL MSB", 27 | "NT", 28 | "Reserved (0)", 29 | "RF (Resume)", 30 | "VM", 31 | "AC (Alignment check)", 32 | "VIF", 33 | "VIP", 34 | "ID" 35 | }; 36 | int i; 37 | for (i=0;i