diff --git a/Makefile b/Makefile index a1695f45..fe420020 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ TARGETL ?= $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F KERNEL_NAME = kernel.elf SYSDLL_NAME = libboron.so +ifeq ($(TARGET),AMD64) DRIVERS_LIST = \ halx86 \ framebuf \ @@ -26,6 +27,14 @@ DRIVERS_LIST = \ stornvme \ ext2fs \ test +else ifeq ($(TARGET),I386) +DRIVERS_LIST = \ + hali386 \ + framebuf \ + i8042prt \ + ext2fs \ + test +endif # The build directory BUILD_DIR = build/$(TARGETL) @@ -75,23 +84,13 @@ clean: image: limine $(IMAGE_TARGET) -$(IMAGE_TARGET): kernel drivers apps limine_config - @echo "Building iso..." - @rm -rf $(ISO_DIR) - @mkdir -p $(ISO_DIR) - @cp $(KERNEL_ELF) $(ISO_DIR)/$(KERNEL_NAME) - @cp -r $(BUILD_DIR)/*.exe $(BUILD_DIR)/*.sys $(BUILD_DIR)/*.so limine.cfg limine/limine-bios.sys limine/limine-bios-cd.bin $(ISO_DIR) - @xorriso -as mkisofs -b limine-bios-cd.bin -no-emul-boot -boot-load-size 4 -boot-info-table --protective-msdos-label $(ISO_DIR) -o $@ 2>/dev/null - @limine/limine-deploy $@ 2>/dev/null - @rm -rf $(ISO_DIR) - -run: image - @echo "Running..." - @./run-unix.sh - -runw: image - @echo "Invoking WSL to run the OS..." - @./run.sh +ifeq ($(TARGET),AMD64) +include tools/build_iso_limine.mk +include tools/run_rule_amd64.mk +else ifeq ($(TARGET),I386) +include tools/build_iso_limine.mk +include tools/run_rule_i386.mk +endif kernel: $(KERNEL_ELF) @@ -119,7 +118,4 @@ $(BUILD_DIR)/%.sys: FORCE @$(MAKE) -C $(patsubst $(BUILD_DIR)/%.sys,$(DRIVERS_DIR)/%,$@) @cp $(patsubst $(BUILD_DIR)/%.sys,$(DRIVERS_DIR)/%/build/out.$(TARGETL).sys,$@) $@ -limine_config: limine.cfg - @echo "[MK]\tlimine.cfg was updated" - FORCE: ; diff --git a/boron/Makefile b/boron/Makefile index 37071b40..9e8a65fe 100644 --- a/boron/Makefile +++ b/boron/Makefile @@ -23,20 +23,21 @@ VER_MINOR = 0 # Currently the only application is clearing the input buffers with zero once read. SECURE ?= no +# Default Target +TARGET ?= AMD64 + +# This sucks. +TARGETL=$(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(TARGET))))))))))))))))))))))))))) + BUILD_DIR = build SRC_DIR = source INC_DIR = include -DDKI_DIR = ../common/include +DDK_DIR = ../common/include SCRIPTS_DIR = scripts -LINKER_FILE = linker.ld +LINKER_FILE = linker.$(TARGETL).ld ISO_DIR=$(BUILD_DIR)/iso_root IMAGE_TARGET=$(BUILD_DIR)/image.iso -TARGET ?= AMD64 - -# This sucks. -TARGETL=$(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(TARGET))))))))))))))))))))))))))) - # This is the name that our final kernel executable will have. # Change as needed. override KERNEL := $(BUILD_DIR)/kernel.$(TARGETL).elf @@ -74,60 +75,42 @@ else DEFINES += -O3 endif -# It is highly recommended to use a custom built cross toolchain to build a kernel. -# We are only using "cc" as a placeholder here. It may work by using -# the host system's toolchain, but this is not guaranteed. -$(eval $(call DEFAULT_VAR,CC,cc)) -$(eval $(call DEFAULT_VAR,CXX,c++)) +IS_KERNEL = yes +include ../tools/toolchain.mk -# Same thing for "ld" (the linker). -$(eval $(call DEFAULT_VAR,LD,ld)) +ifeq ($(SMP), yes) + ARCH_CFLAGS += -DCONFIG_SMP +endif # User controllable CFLAGS. -CFLAGS ?= -pipe -Wall -Wextra -DTARGET_$(TARGET) -DKERNEL -DIS_KERNEL_MODE $(DEFINES) +CFLAGS ?= -pipe -Wall -Wextra -DTARGET_$(TARGET) -DBORON_TARGET=\"$(TARGETL)\" -DKERNEL -DIS_KERNEL_MODE $(DEFINES) # User controllable CXXFLAGS. -CXXFLAGS ?= -pipe -Wall -Wextra -DTARGET_$(TARGET) -DKERNEL -DIS_KERNEL_MODE $(DEFINES) +CXXFLAGS ?= -pipe -Wall -Wextra -DTARGET_$(TARGET) -DBORON_TARGET=\"$(TARGETL)\" -DKERNEL -DIS_KERNEL_MODE $(DEFINES) # User controllable preprocessor flags. -CPPFLAGS ?= -I $(INC_DIR) -I $(DDKI_DIR) +CPPFLAGS ?= -I $(INC_DIR) -I $(DDK_DIR) # User controllable nasm flags. -NASMFLAGS ?= -F dwarf -I$(SRC_DIR) -I$(INC_DIR) -I$(DDKI_DIR) +NASMFLAGS ?= -F dwarf -I$(SRC_DIR) -I$(INC_DIR) -I$(DDK_DIR) # User controllable linker flags. We set none by default. LDFLAGS ?= # Internal C flags that should not be changed by the user. -# -# NOTE 7.7.2024 -- No-reorder-functions was added because a certain functions -# was generating an "unlikely" section, which was placed at different addresses -# in kernel.elf and kernel2.elf, screwing up the symbol table... That's pretty -# bad. -# -# TODO: fix above ^^^ CFLAGS += \ -fno-omit-frame-pointer \ - -std=c11 \ + -std=c2x \ -ffreestanding \ -fno-stack-protector \ -fno-stack-check \ -fno-lto \ -fno-pie \ -fno-pic \ - -fno-reorder-functions \ - -m64 \ - -march=x86-64 \ - -mabi=sysv \ - -mno-80387 \ - -mno-mmx \ - -mno-sse \ - -mno-sse2 \ - -mno-red-zone \ - -mcmodel=kernel \ -MMD \ -MP \ - -I. + -I. \ + $(ARCH_CFLAGS) # Internal C++ flags that should not be changed by the user. CXXFLAGS += \ @@ -140,25 +123,17 @@ CXXFLAGS += \ -fno-pie \ -fno-pic \ -fno-reorder-functions \ - -m64 \ - -march=x86-64 \ - -mabi=sysv \ - -mno-80387 \ - -mno-mmx \ - -mno-sse \ - -mno-sse2 \ - -mno-red-zone \ - -mcmodel=kernel \ -MMD \ -MP \ -fno-exceptions \ -fno-rtti \ - -I. + -I. \ + $(ARCH_CFLAGS) LDFLAGSBASE += \ -nostdlib \ - -m elf_x86_64 \ - -z max-page-size=0x1000 + -m $(LINK_ARCH) \ + $(ARCH_LDFLAGS) # Internal linker flags that should not be changed by the user. LDFLAGS += \ @@ -171,15 +146,15 @@ ifeq ($(shell $(LD) --help 2>&1 | grep 'no-pie' >/dev/null 2>&1; echo $$?),0) endif # Internal nasm flags that should not be changed by the user. -NASMFLAGS += \ - -f elf64 +NASMFLAGS += $(ARCH_ASFLAGS) # Use find to glob all *.c, *.S, and *.asm files in the directory and extract the object names. -override CFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.c') -override CXXFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.cpp') -override ASFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.S') -override NASMFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.asm') -override OBJ := $(patsubst %.o,%.$(TARGETL).o,$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(CFILES:.c=.o) $(CXXFILES:.cpp=.o) $(ASFILES:.S=.o) $(NASMFILES:.asm=.o))) +EXCLUDE_WRONG_ARCH = '(' '(' -path 'source/ke/$(TARGETL)/*' ')' -o '(' -path 'source/mm/$(TARGETL)/*' ')' -o '(' -not -path 'source/ke/*/*' -not -path 'source/mm/*/*' ')' ')' +override CFILES := $(shell find $(SRC_DIR) -not -path '*/.*' $(EXCLUDE_WRONG_ARCH) -type f -name '*.c') +override CXXFILES := $(shell find $(SRC_DIR) -not -path '*/.*' $(EXCLUDE_WRONG_ARCH) -type f -name '*.cpp') +override ASFILES := $(shell find $(SRC_DIR) -not -path '*/.*' $(EXCLUDE_WRONG_ARCH) -type f -name '*.S') +override NASMFILES := $(shell find $(SRC_DIR) -not -path '*/.*' $(EXCLUDE_WRONG_ARCH) -type f -name '*.asm') +override OBJ := $(patsubst %.o,%.$(TARGETL).o,$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(CFILES:.c=.o) $(CXXFILES:.cpp=.o) $(ASFILES:.S=.o) $(NASMFILES:.asm=.o))) override HEADER_DEPS := $(patsubst %.o,%.d,$(OBJ)) # It's kind of a mess, but basically, here's what everything does: @@ -202,26 +177,26 @@ CFLAGS += -D__BORON_MAJOR=$(VER_MAJOR) -D__BORON_MINOR=$(VER_MINOR) -D__BORON_BU # Link rules for the final kernel executable. $(KERNEL): $(SYMBOLS) @echo "[LD]\tBuilding $(KERNEL)" - @$(LD) $(OBJ) $(SYMBOLS) $(LDFLAGS) -o $@ + @$(BLD) $(OBJ) $(SYMBOLS) $(LDFLAGS) -o $@ $(SYMBOLS): $(KERNEL2) @echo "[NM]\tDumping and compiling symbols" - @nm -P $(KERNEL2) | $(SCRIPTS_DIR)/generate_symbols.py > $(BUILD_DIR)/_symtab.$(TARGETL).asm - @nasm $(NASMFLAGS) $(BUILD_DIR)/_symtab.$(TARGETL).asm -o $(BUILD_DIR)/_symtab.$(TARGETL).o + @nm -P $(KERNEL2) | $(SCRIPTS_DIR)/generate_symbols.py $(TARGETL) > $(BUILD_DIR)/_symtab.$(TARGETL).asm + @$(BASM) $(NASMFLAGS) $(BUILD_DIR)/_symtab.$(TARGETL).asm -o $(BUILD_DIR)/_symtab.$(TARGETL).o $(KERNEL2): $(KERNEL_PARTIAL) @echo "[LD]\tLinking kernel to extract symbols" - @$(LD) $(KERNEL_PARTIAL) -static $(LDFLAGS) -o $@ + @$(BLD) $(KERNEL_PARTIAL) -static $(LDFLAGS) -o $@ # Link rules for the amalgam object file. $(KERNEL_PARTIAL): $(OBJ) $(LINKER_FILE) @echo "[LD]\tPartially linking kernel" - @$(LD) -r $(OBJ) $(LDFLAGSBASE) -o $@ + @$(BLD) -m $(LINK_ARCH) -r $(OBJ) $(LDFLAGSBASE) -o $@ $(BUILD_DIR)/ke/version.$(TARGETL).o: $(filter-out $(BUILD_DIR)/ke/version.$(TARGETL).o, $(OBJ)) $(SRC_DIR)/ke/version.c @echo "[CC]\tCompiling $(SRC_DIR)/ke/version.c" @mkdir -p $(dir $@) - @$(CC) $(CPPFLAGS) $(CFLAGS) -c $(SRC_DIR)/ke/version.c -o $@ + @$(BCC) $(CPPFLAGS) $(CFLAGS) -c $(SRC_DIR)/ke/version.c -o $@ @echo "[MK]\tIncrementing build number" @echo $$(($(VER_BUILD) + 1)) > $(BUILD_NUMBER_FILE) @@ -232,25 +207,25 @@ $(BUILD_DIR)/ke/version.$(TARGETL).o: $(filter-out $(BUILD_DIR)/ke/version.$(TAR $(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.c @echo "[CC]\tCompiling $<" @mkdir -p $(dir $@) - @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + @$(BCC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ # Compilation rules for *.cpp files. $(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.cpp @echo "[CXX]\tCompiling $<" @mkdir -p $(dir $@) - @$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ + @$(BCXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ # Compilation rules for *.S files. $(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.S @echo "[AS]\tCompiling $<" @mkdir -p $(dir $@) - @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + @$(BCC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ # Compilation rules for *.asm (nasm) files. $(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.asm @echo "[AS]\tCompiling $<" @mkdir -p $(dir $@) - @nasm $(NASMFLAGS) $< -o $@ + @$(BASM) $(NASMFLAGS) $< -o $@ # Remove object files and the final executable. .PHONY: clean diff --git a/boron/address_space.txt b/boron/address_space.txt index ed9728fa..44e27610 100644 --- a/boron/address_space.txt +++ b/boron/address_space.txt @@ -26,8 +26,6 @@ BORON Operating System Address Space (AMD64) | higher half direct map | +------------------------------+ - 0xFFFF800000000000 -NOTE: file system caching will be implemented in the future - ** User Mode ** Currently this is nothing more than a plan. @@ -39,13 +37,13 @@ However, this is the plan for normal user processes. +-----------------------------+ - 0x0000800000000000 | process environment block | -+-----------------------------+ - 0x00007ffffffff000 ++-----------------------------+ - 0x00007FFFFFFFE000 | operating system DLLs | -+-----------------------------+ - 0x00007fff00000000 ++-----------------------------+ - 0x00007FFF00000000 | thread environment blocks | -+-----------------------------+ - 0x00007ffe00000000 ++-----------------------------+ - 0x00007FFE00000000 | thread stacks | -+-----------------------------+ - 0x00007e0000000000 ++-----------------------------+ - 0x00007E0000000000 | user mappings | +-----------------------------+ - 0x0000500000000000 | user heap | diff --git a/boron/address_space_i386.txt b/boron/address_space_i386.txt new file mode 100644 index 00000000..9bacf2ba --- /dev/null +++ b/boron/address_space_i386.txt @@ -0,0 +1,60 @@ +BORON Operating System Address Space (i386) + +** Kernel Mode ** + ++------------------------------+ - 0xFFFFFFFF +| virtually linear page tables | ++------------------------------+ - 0xFFC00000 +| unused | ++------------------------------+ - 0xF0000000 +| more dynamic pool space | ++------------------------------+ - 0xE0000000 +| unused | ++------------------------------+ - 0xD8000000 +| page frame data base | ++------------------------------+ - 0xD4000000 +| system module DLLs | ++------------------------------+ - 0xD2000000 +| unused | ++------------------------------+ - 0xD1800000 +| pool header slab magazines | ++------------------------------+ - 0xD1000000 +| fast mapping in 16MB windows | ++------------------------------+ - 0xD0000000 +| map of first 256 MB of phys | +| kernel code & data | ++------------------------------+ - 0xC0000000 +| dynamic pool space | ++------------------------------+ - 0x80000000 +| higher half direct map | ++------------------------------+ - 0x00000000 + +Notes: +- if we use the multiboot boot protocol, then "kernel code & data" really means "first 8MB of physical memory" + +** User Mode ** + +Currently this is nothing more than a plan. +Now, a user process has complete control over its own +address space. (They will even be able to unmap the +PEB/TEBs if they want as well!) + +However, this is the plan for normal user processes. + ++-----------------------------+ - 0x80000000 +| process environment block | ++-----------------------------+ - 0x7ffe0000 +| thread environment blocks | ++-----------------------------+ - 0x7fe00000 +| operating system DLLs | ++-----------------------------+ - 0x78000000 +| thread stacks | ++-----------------------------+ - 0x70000000 +| user DLLs | ++-----------------------------+ - 0x60000000 +| user heap + mappings | ++-----------------------------+ - 0x10000000 +| program executable | ++-----------------------------+ - 0x00001000 +| | ++-----------------------------+ - 0x00000000 diff --git a/boron/include/arch.h b/boron/include/arch.h index 0ee0675f..801efee0 100644 --- a/boron/include/arch.h +++ b/boron/include/arch.h @@ -4,8 +4,10 @@ #include // ==== Platform specific defintions ==== -#ifdef TARGET_AMD64 +#if defined TARGET_AMD64 #include +#elif defined TARGET_I386 +#include #endif // ==== Forward declarations. Depending on the platform, we'll include platform specific definitions. ==== @@ -13,7 +15,7 @@ typedef struct KREGISTERS_tag KREGISTERS, *PKREGISTERS; // List of registers. // Functions that do different things based on architecture, // but exist everywhere -#ifdef TARGET_AMD64 +#if defined TARGET_AMD64 || defined TARGET_I386 FORCE_INLINE void KeWaitForNextInterrupt(void) diff --git a/boron/include/arch/amd64.h b/boron/include/arch/amd64.h index 894ea386..967fad78 100644 --- a/boron/include/arch/amd64.h +++ b/boron/include/arch/amd64.h @@ -5,6 +5,8 @@ #error "Don't include this if you aren't building for amd64!" #endif +#include + // Model specific registers uint64_t KeGetMSR(uint32_t msr); void KeSetMSR(uint32_t msr, uint64_t value); @@ -52,7 +54,7 @@ typedef union } MMADDRESS_CONVERT; -#endif +#endif // KERNEL // Note! Most of these are going to be present everywhere we'll port to. @@ -76,11 +78,11 @@ MMADDRESS_CONVERT; #define MM_PTE_PAGESIZE (1ULL << 7) // in terms of PML3/PML2 entries, for 1GB/2MB pages respectively. Not Used by the kernel #define MM_PTE_GLOBAL (1ULL << 8) // doesn't invalidate the pages from the TLB when CR3 is changed #define MM_PTE_ISFROMPMM (1ULL << 9) // if the allocated memory is managed by the PFN database -#define MM_PTE_COW (1ULL << 10) // if this page is to be copied after a write -#define MM_PTE_TRANSITION (1ULL << 11) // if this page is in transition (3) +#define MM_PTE_COW (1ULL << 10) // if this page is to be copied after a write -- TODO: We are supposed to be phasing this one out. +#define MM_PTE_TRANSITION (1ULL << 11) // if this page is in transition (3) (UNUSED) #define MM_PTE_NOEXEC (1ULL << 63) // aka eXecute Disable #define MM_PTE_PKMASK (15ULL<< 59) // protection key mask. We will not use it. -#define MM_PTE_ISPOOLHDR (1ULL << 58) // if the PTE actually contains the address of a pool entry (subtracted MM_KERNEL_SPACE_BASE from it) +#define MM_PTE_ISPOOLHDR (1ULL << 58) // if the PTE actually contains the address of a pool entry (subtracted MM_KERNEL_SPACE_BASE from it) (NOTE: This is supposed to be MM_DPTE_ISPOOLHDR) #define MM_PTE_ADDRESSMASK (0x000FFFFFFFFFF000) // description of the other bits that aren't 1 in the mask: // 63 - execute disable @@ -172,8 +174,6 @@ struct KREGISTERS_tag uint64_t ss; }; -#include - // IDT #define C_IDT_MAX_ENTRIES (0x100) diff --git a/boron/include/arch/i386.h b/boron/include/arch/i386.h new file mode 100644 index 00000000..909fad1c --- /dev/null +++ b/boron/include/arch/i386.h @@ -0,0 +1,306 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + arch/i386.h + +Abstract: + This header file contains the constant definitions for + the i386 platform. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#pragma once + +#ifndef TARGET_I386 +#error "Don't include this if you aren't building for i386!" +#endif + +#include + +// Model specific registers +uint64_t KeGetMSR(uint32_t msr); +void KeSetMSR(uint32_t msr, uint64_t value); + +// Port I/O +uint8_t KePortReadByte(uint16_t portNo); +void KePortWriteByte(uint16_t portNo, uint8_t data); +uint16_t KePortReadWord(uint16_t portNo); +void KePortWriteWord(uint16_t portNo, uint16_t data); +uint32_t KePortReadDword(uint16_t portNo); +void KePortWriteDword(uint16_t portNo, uint32_t data); + +#ifdef KERNEL + +// start PML2 index will be 512. The PFN database's is 776 +#define MI_GLOBAL_AREA_START (512) +#define MI_GLOBAL_AREA_START_2ND (896) + +#define MI_RECURSIVE_PAGING_START (1023) + +#define MI_PML2_LOCATION ((uintptr_t)0xFFFFF000U) +#define MI_PML1_LOCATION ((uintptr_t)0xFFC00000U) +#define MI_PML1_LOC_END ((uint64_t)0x100000000U) +#define MI_PML_ADDRMASK ((uintptr_t)0xFFFFF000U) + +// MmGetHHDMOffsetAddr and other HHDM-related calls are implemented two-fold: +// +// - The first 256 MB of RAM are mapped in an offset identity mapping +// +// - The rest of the address space is accessible via a 16 MB window fast mapping. +#define MI_IDENTMAP_START ((uintptr_t) 0xC0000000) +#define MI_IDENTMAP_SIZE ((uintptr_t) 0x10000000) + +#define MI_FASTMAP_START ((uintptr_t) 0xD0000000) +#define MI_FASTMAP_MASK ((uintptr_t) 0xFF000000) +#define MI_FASTMAP_SIZE (16 * 1024 * 1024) + +typedef union +{ + struct + { + uintptr_t PageOffset : 12; + uintptr_t Level1Index : 10; + uintptr_t Level2Index : 10; + }; + + uintptr_t Long; +} +MMADDRESS_CONVERT; + +#endif // KERNEL + +#define MM_KERNEL_SPACE_BASE (0x80000000U) +#define MM_USER_SPACE_END (0x7FFFFFFFU) + +#define MM_PFNDB_BASE (0xD4000000U) + +#define MM_PTE_PRESENT (1 << 0) +#define MM_PTE_READWRITE (1 << 1) +#define MM_PTE_USERACCESS (1 << 2) +#define MM_PTE_WRITETHRU (1 << 3) +#define MM_PTE_CDISABLE (1 << 4) +#define MM_PTE_ACCESSED (1 << 5) +#define MM_PTE_DIRTY (1 << 6) +#define MM_PTE_PAT (1 << 7) +#define MM_PTE_PAGESIZE (1 << 7) // for 4MB pages. not used by the kernel +#define MM_PTE_GLOBAL (1 << 8) // doesn't invalidate the pages from the TLB when CR3 is changed +#define MM_PTE_ISFROMPMM (1 << 9) // if the allocated memory is managed by the PFN database +#define MM_PTE_COW (1 << 10) // if this page is to be copied after a write + +#define MM_PTE_NOEXEC (0) // no such thing on 32-bit (without PAE) +#define MM_PTE_PKMASK (0) // no such thing on 32-bit + +#define MM_PTE_ADDRESSMASK (0xFFFFF000U) + +// Disabled PTE (present bit is zero): +// bits 0..2 - Permission bits as usual +// bit 3 - Is decommitted (was previously committed but is no longer) +// bit 8 - Is demand paged +// bit 9 - 0 if from PMM (anonymous), 1 if mapped from a file +// bit 10 - Was swapped to pagefile (2) +// bit 30 - used by the unmap code, see (1) + +// NOTES: +// +// (1) - If MM_DPTE_WASPRESENT is set, it's treated as a regular PTE in terms of flags, except that MM_PTE_PRESENT is zero. +// It contains a valid PMM address which should be freed. +// +// (2) - If MM_DPTE_SWAPPED is set, bits 52...12 represent the offset into the pagefile, and bits 57...53 mean the pagefile index. +// +// (3) - If the PTE is in transition, then the physical page is part of either the standby or the modified page list. + +#define MM_DPTE_DECOMMITTED (1 << 3) +#define MM_DPTE_WASPRESENT (1 << 7) +#define MM_DPTE_COMMITTED (1 << 8) +#define MM_DPTE_BACKEDBYFILE (1 << 9) +#define MM_DPTE_SWAPPED (1 << 10) + +#define MM_PTE_ISPOOLHDR (1 << 11) // see src/mm/poolsup.c for an explanation + +// Page fault reasons +#define MM_FAULT_PROTECTION (1 << 0) // 0: Page wasn't marked present; 1: Page protection violation (e.g. writing to a readonly page) +#define MM_FAULT_WRITE (1 << 1) // 0: Caused by a read; 1: Caused by a write +#define MM_FAULT_USER (1 << 2) // 1: Fault was caused in user mode +#define MM_FAULT_INSNFETCH (1 << 0) // 1: Attempted to execute code from a page marked with the NOEXEC bit + +#define PAGE_SIZE (0x1000) + +typedef uint32_t MMPTE, *PMMPTE; + +// bits 0.11 - Offset within the page +// bits 12..21 - Index within the PML1 +// bits 22..31 - Index within the PML2 +#define PML1_IDX(addr) (((addr) >> 12) & 0x3FF) +#define PML2_IDX(addr) (((addr) >> 22) & 0x3FF) + +// ======== model specific registers ======== +#define MSR_FS_BASE (0xC0000100) +#define MSR_GS_BASE (0xC0000101) +#define MSR_GS_BASE_KERNEL (0xC0000102) +#define MSR_IA32_EFER (0xC0000080) +#define MSR_IA32_STAR (0xC0000081) +#define MSR_IA32_LSTAR (0xC0000082) +#define MSR_IA32_FMASK (0xC0000084) + +#define MSR_IA32_EFER_SCE (1 << 0) // SYSCALL/SYSRET enable +#define MSR_IA32_EFER_LME (1 << 8) // Long Mode enable - Limine sets this +#define MSR_IA32_EFER_LMA (1 << 10) // Long Mode active +#define MSR_IA32_EFER_NXE (1 << 11) // No Execute enable + +// Registers pushed by KiTrapCommon. Pushed in reverse order from how they're laid out. +struct KREGISTERS_tag +{ + uint32_t Cr2, Edi, Esi, Ebx; + uint32_t Ebp; + uint32_t Sfra; // stack frame return address + uint32_t OldIpl; + uint32_t Edx, Ecx, Eax; + + // Registers pushed by each trap handler + uint32_t IntNumber; + uint32_t ErrorCode; + + // Registers pushed by the CPU when handling the interrupt + uint32_t Eip; + uint32_t Cs; + uint32_t Eflags; + // NOTE: these are only valid and used if the privilege level is different! + // (e.g. if we interrupted user mode), so DO NOT rely on setting these + // when returning to kernel mode + uint32_t Esp; + uint32_t Ss; +}; + +// IDT +#define C_IDT_MAX_ENTRIES (0x100) + +#define INTV_DBL_FAULT (0x08) +#define INTV_PROT_FAULT (0x0D) +#define INTV_PAGE_FAULT (0x0E) +#define INTV_SYSTEMCALL (0x80) + +typedef struct KIDT_ENTRY_tag +{ + // bytes 0 and 1 + uint64_t OffsetLow : 16; + // bytes 2 and 3 + uint64_t SegmentSel : 16; + // byte 4 + uint64_t IST : 3; + uint64_t Reserved0 : 5; + // byte 5 + uint64_t GateType : 4; + uint64_t Reserved1 : 1; + uint64_t DPL : 2; + uint64_t Present : 1; + // bytes 6, 7 + uint64_t OffsetHigh : 16; +} +PACKED +KIDT_ENTRY, *PKIDT_ENTRY; + +typedef struct +{ + KIDT_ENTRY Entries[C_IDT_MAX_ENTRIES]; +} +KIDT, *PKIDT; + +// GDT + +// Note: Data and code segments are *swapped* for user mode because +// for whatever reason AMD or intel decided they just *had* to take +// the value of IA32_STAR 63:48 and add 16 to it. for "no reason". +// +// surely there was a reason... right? +#define SEG_NULL (0x00) +#define SEG_RING_0_CODE (0x08) +#define SEG_RING_0_DATA (0x10) +#define SEG_RING_3_DATA (0x18) +#define SEG_RING_3_CODE (0x20) +#define C_GDT_SEG_COUNT (5) + +// note: not packed, so this struct will get padding after each uint16_t +typedef struct +{ + uint16_t Link; + uint32_t Esp0; + uint16_t Ss0; + uint32_t Esp1; + uint16_t Ss1; + uint32_t Esp2; + uint16_t Ss2; + uint32_t Cr3; + uint32_t Eip; + uint32_t Eflags; + uint32_t Eax; + uint32_t Ecx; + uint32_t Edx; + uint32_t Ebx; + uint32_t Esp; + uint32_t Ebp; + uint32_t Esi; + uint32_t Edi; + uint16_t Es, Pad0; + uint16_t Cs, Pad1; + uint16_t Ss, Pad2; + uint16_t Ds, Pad3; + uint16_t Fs, Pad4; + uint16_t Gs, Pad5; + uint16_t Ldtr, Pad6; + uint16_t Pad7, Iopb; + uint32_t Ssp; +} +KTSS; + +typedef union KGDT_ENTRY_tag +{ + struct + { + uint64_t Limit1 : 16; + uint64_t Base1 : 24; + uint64_t Access : 8; + uint64_t Limit2 : 4; + uint64_t Flags : 4; + uint64_t Base2 : 16; + } + PACKED; + + uint64_t Entry; +} +PACKED +KGDT_ENTRY; + +// Global Descriptor Table +typedef struct +{ + uint64_t Segments[C_GDT_SEG_COUNT]; + KGDT_ENTRY TssEntry; +} +KGDT; + +#define KE_MAX_QUEUED_INTERRUPTS 32 + +typedef struct +{ + KGDT Gdt; + KTSS Tss; + + uint8_t InterruptQueue[KE_MAX_QUEUED_INTERRUPTS]; + uint8_t InterruptQueuePlace; +} +KARCH_DATA, *PKARCH_DATA; + +// Manual interrupt disabling functions. Should instead use +// KeDisableInterrupts and KeRestoreInterrupts, but these have +// their uses too. +#define DISABLE_INTERRUPTS() ASM("cli") +#define ENABLE_INTERRUPTS() ASM("sti") + +// MSI message data register +#define MSI_TRIGGERLEVEL (1 << 15) +#define MSI_LEVELASSERT (1 << 14) + +#include diff --git a/boron/include/arch/i386.inc b/boron/include/arch/i386.inc new file mode 100644 index 00000000..abc9ded5 --- /dev/null +++ b/boron/include/arch/i386.inc @@ -0,0 +1,16 @@ +; Boron - Include file for platform specific assembly stuff +%ifndef NS64_I386_INC +%define NS64_I386_INC +bits 32 + +%define SEG_NULL 0x00 +%define SEG_RING_0_CODE 0x08 +%define SEG_RING_0_DATA 0x10 +%define SEG_RING_3_DATA 0x18 +%define SEG_RING_3_CODE 0x20 + +; magic IPL to avoid popping registers we don't need to pop, since +; this interrupt frame was pushed by KeYieldCurrentThread in kernel mode +%define MAGIC_IPL 0x42424242 + +%endif ;NS64_I386_INC diff --git a/boron/include/arch/i386/ipl.h b/boron/include/arch/i386/ipl.h new file mode 100644 index 00000000..9ab6c96a --- /dev/null +++ b/boron/include/arch/i386/ipl.h @@ -0,0 +1,40 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + arch/i386/ipl.h + +Abstract: + This header file contains the constant IPL definitions + for the i386 platform. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#ifndef BORON_ARCH_IPL_I386_H +#define BORON_ARCH_IPL_I386_H + +// TODO: Currently copied from AMD64. +typedef enum KIPL_tag +{ + IPL_UNDEFINED = -1, + IPL_NORMAL = 0x0, // business as usual + IPL_APC = 0x3, // asynch procedure calls. Page faults only allowed up to this IPL + IPL_DPC = 0x4, // deferred procedure calls and the scheduler + IPL_DEVICES0 = 0x5, // tier 1 for devices (keyboard, mouse) + IPL_DEVICES1 = 0x6, // tier 2 for devices + IPL_DEVICES2 = 0x7, + IPL_DEVICES3 = 0x8, + IPL_DEVICES4 = 0x9, + IPL_DEVICES5 = 0xA, + IPL_DEVICES6 = 0xB, + IPL_DEVICES7 = 0xC, + IPL_DEVICES8 = 0xD, + IPL_CLOCK = 0xE, // for clock timers + IPL_NOINTS = 0xF, // total control of the CPU. Interrupts are disabled in this IPL and this IPL only. + IPL_COUNT, +} +KIPL, *PKIPL; + +#endif//BORON_ARCH_IPL_I386_H diff --git a/boron/include/arch/ipl.h b/boron/include/arch/ipl.h index 2ce09c99..cd7a7f87 100644 --- a/boron/include/arch/ipl.h +++ b/boron/include/arch/ipl.h @@ -15,10 +15,12 @@ Module name: #ifndef BORON_ARCH_IPL_H #define BORON_ARCH_IPL_H -#ifdef TARGET_AMD64 +#if defined TARGET_AMD64 #include +#elif defined TARGET_I386 +#include #else -#error Hello +#error Implement ipl.h for your architecture! #endif #endif//BORON_ARCH_IPL_H diff --git a/boron/include/ex/object.h b/boron/include/ex/object.h index ba504cfd..cae9109b 100644 --- a/boron/include/ex/object.h +++ b/boron/include/ex/object.h @@ -84,4 +84,6 @@ BSTATUS OSWaitForSingleObject( HANDLE Handle, bool Alertable, int TimeoutMS -); \ No newline at end of file +); + +BSTATUS OSDuplicateHandle(HANDLE SourceHandle, HANDLE DestinationProcessHandle, PHANDLE OutNewHandle, int OpenFlags); \ No newline at end of file diff --git a/boron/include/hal.h b/boron/include/hal.h index 1722e0ff..9588f27d 100644 --- a/boron/include/hal.h +++ b/boron/include/hal.h @@ -16,7 +16,7 @@ bool HalWasInitted(); // HAL API. See hal/init.h -void HalEndOfInterrupt(); +void HalEndOfInterrupt(int InterruptNumber); void HalRequestIpi(uint32_t LapicId, uint32_t Flags, int Vector); void HalInitSystemUP(); void HalInitSystemMP(); @@ -28,7 +28,13 @@ uint64_t HalGetIntTimerFrequency(); uint64_t HalGetTickCount(); uint64_t HalGetTickFrequency(); uint64_t HalGetIntTimerDeltaTicks(); +#ifdef TARGET_AMD64 void HalIoApicSetIrqRedirect(uint8_t Vector, uint8_t Irq, uint32_t LapicId, bool Status); +#endif +#ifdef TARGET_I386 +void HalPicRegisterInterrupt(uint8_t Vector, KIPL Ipl); +void HalPicDeregisterInterrupt(uint8_t Vector, KIPL Ipl); +#endif #ifdef IS_HAL void HalSetVftable(const HAL_VFTABLE* Table); diff --git a/boron/include/hal/init.h b/boron/include/hal/init.h index 32d25631..4454e2db 100644 --- a/boron/include/hal/init.h +++ b/boron/include/hal/init.h @@ -21,7 +21,7 @@ Module name: #define HAL_VFTABLE_LOADED (1 << 0) // Function pointer definitions -typedef void(*PFHAL_END_OF_INTERRUPT)(void); +typedef void(*PFHAL_END_OF_INTERRUPT)(int InterruptNumber); typedef void(*PFHAL_REQUEST_INTERRUPT_IN_TICKS)(uint64_t Ticks); typedef void(*PFHAL_REQUEST_IPI)(uint32_t LapicId, uint32_t Flags, int Vector); typedef void(*PFHAL_INIT_SYSTEM_UP)(void); @@ -36,11 +36,20 @@ typedef uint64_t(*PFHAL_GET_TICK_FREQUENCY)(void); typedef uint64_t(*PFHAL_GET_INT_TIMER_DELTA_TICKS)(void); #ifdef TARGET_AMD64 - typedef void(*PFHAL_IOAPIC_SET_IRQ_REDIRECT)(uint8_t Vector, uint8_t Irq, uint32_t LapicId, bool Status); +#endif + +#ifdef TARGET_I386 +typedef void(*PFHAL_PIC_REGISTER_INTERRUPT)(uint8_t Vector, KIPL Ipl); +typedef void(*PFHAL_PIC_DEREGISTER_INTERRUPT)(uint8_t Vector, KIPL Ipl); +#endif +#if defined TARGET_AMD64 || defined TARGET_I386 #include "pci.h" +#endif +#ifdef TARGET_I386 +#define PIC_INTERRUPT_BASE (0x20) #endif typedef struct @@ -63,6 +72,12 @@ typedef struct #ifdef TARGET_AMD64 PFHAL_IOAPIC_SET_IRQ_REDIRECT IoApicSetIrqRedirect; +#endif +#ifdef TARGET_I386 + PFHAL_PIC_REGISTER_INTERRUPT PicRegisterInterrupt; + PFHAL_PIC_DEREGISTER_INTERRUPT PicDeregisterInterrupt; +#endif +#if defined TARGET_AMD64 || defined TARGET_I386 PFHAL_PCI_ENUMERATE PciEnumerate; PFHAL_PCI_CONFIG_READ_DWORD PciConfigReadDword; PFHAL_PCI_CONFIG_READ_WORD PciConfigReadWord; diff --git a/boron/include/hal/pci.h b/boron/include/hal/pci.h index ddb97809..32ca0996 100644 --- a/boron/include/hal/pci.h +++ b/boron/include/hal/pci.h @@ -316,10 +316,14 @@ void HalPciMsixSetInterrupt(PPCI_DEVICE Device, int Index, uint8_t ProcessorId, { uintptr_t Address = HalPciReadBarAddress(&Device->Address, Device->MsixData.Bir); + MmBeginUsingHHDM(); + PPCI_MSIX_TABLE_ENTRY Table = MmGetHHDMOffsetAddr(Address + Device->MsixData.TableOffset); Table += Index; Table->Address = HalPciMsiCreateAddress(ProcessorId); Table->MessageData = HalPciMsiCreateMessage(Vector, EdgeTrigger, Deassert); Table->VectorControl = 0; + + MmEndUsingHHDM(); } diff --git a/boron/include/io/fcb.h b/boron/include/io/fcb.h index bc07e4d2..702b92d8 100644 --- a/boron/include/io/fcb.h +++ b/boron/include/io/fcb.h @@ -38,6 +38,7 @@ typedef struct _FCB uint32_t Flags; // Valid only for files and block devices. Otherwise it's zero. + __attribute__((aligned(8))) uint64_t FileLength; // FSD specific extension. When the FCB is initialized, the diff --git a/boron/include/ke.h b/boron/include/ke.h index 1e081b0f..226dff7f 100644 --- a/boron/include/ke.h +++ b/boron/include/ke.h @@ -33,3 +33,4 @@ Module name: #include #include #include +#include diff --git a/boron/include/ke/irq.h b/boron/include/ke/irq.h index 8f4e80f3..43a2942b 100644 --- a/boron/include/ke/irq.h +++ b/boron/include/ke/irq.h @@ -19,9 +19,19 @@ Module name: #include #include +#ifdef TARGET_AMD64 + // Allocate an interrupt vector for the specified IPL. int KeAllocateInterruptVector(KIPL Ipl); +#endif + +#ifdef TARGET_I386 + +#define SYSTEM_IRQ(irqNo) (PIC_INTERRUPT_BASE + (irqNo)) + +#endif + // // WARNING! // diff --git a/boron/include/ke/lpb.h b/boron/include/ke/lpb.h index e20c2a72..d33dd4f1 100644 --- a/boron/include/ke/lpb.h +++ b/boron/include/ke/lpb.h @@ -32,6 +32,7 @@ typedef struct uint32_t Pitch; // width of a single row in bytes uint32_t Width; uint32_t Height; + bool IsPhysicalAddress; uint8_t BitDepth; uint8_t RedMaskSize; uint8_t RedMaskShift; diff --git a/boron/include/ke/process.h b/boron/include/ke/process.h index e0685d76..069ae0fc 100644 --- a/boron/include/ke/process.h +++ b/boron/include/ke/process.h @@ -53,7 +53,7 @@ PKPROCESS KeGetSystemProcess(); void KeDeallocateProcess(PKPROCESS Process); // Initialize the process. -void KeInitializeProcess(PKPROCESS Process, int BasePriority, KAFFINITY BaseAffinity); +BSTATUS KeInitializeProcess(PKPROCESS Process, int BasePriority, KAFFINITY BaseAffinity); // Attach to or detach from a process, on behalf of the current thread. // diff --git a/boron/include/ke/sched.h b/boron/include/ke/sched.h index dad242ef..239f11db 100644 --- a/boron/include/ke/sched.h +++ b/boron/include/ke/sched.h @@ -67,6 +67,9 @@ typedef struct _KSCHEDULER int ThreadsOnQueueCount; +#if IS_32_BIT + __attribute__((aligned(8))) +#endif uint64_t TicksSpentNonIdle; // in ticks, copy of CurrentThread->QuantumUntil unless diff --git a/boron/include/ke/services.h b/boron/include/ke/services.h new file mode 100644 index 00000000..ac12f3c7 --- /dev/null +++ b/boron/include/ke/services.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +BSTATUS OSSetPebProcess(HANDLE ProcessHandle, void* PebPtr); + +BSTATUS OSSetCurrentPeb(void* Ptr); + +BSTATUS OSSetCurrentTeb(void* Ptr); + +void* OSGetCurrentPeb(); + +void* OSGetCurrentTeb(); + +BSTATUS OSGetTickCount(uint64_t* TickCount); + +BSTATUS OSGetTickFrequency(uint64_t* TickFrequency); + +BSTATUS OSGetVersionNumber(int* VersionNumber); + +BSTATUS OSOutputDebugString(const char* String, size_t StringLength); diff --git a/boron/include/mm/cache.h b/boron/include/mm/cache.h index 1a00c090..38d36fc9 100644 --- a/boron/include/mm/cache.h +++ b/boron/include/mm/cache.h @@ -32,24 +32,8 @@ Module name: // I will go for 4 levels of indirection for now. typedef struct _FCB FCB, *PFCB; -typedef struct _CCB_INDIRECTION CCB_INDIRECTION, *PCCB_INDIRECTION; -typedef union _CCB_ENTRY -{ - PCCB_INDIRECTION Indirection; - MMPFN Pfn; - uintptr_t Long; -} -CCB_ENTRY, *PCCB_ENTRY; - -static_assert(sizeof(CCB_ENTRY) == sizeof(uintptr_t)); - -struct _CCB_INDIRECTION -{ - CCB_ENTRY Entries[PAGE_SIZE / sizeof(CCB_ENTRY)]; -}; - -static_assert(sizeof(CCB_INDIRECTION) == PAGE_SIZE); +#define MM_INDIRECTION_COUNT (PAGE_SIZE / sizeof(MMPFN)) typedef struct _CCB { @@ -66,13 +50,13 @@ typedef struct _CCB uint64_t FirstModifiedPage; uint64_t LastModifiedPage; - CCB_ENTRY Direct[MM_DIRECT_PAGE_COUNT]; - PCCB_INDIRECTION Level1Indirect; - PCCB_INDIRECTION Level2Indirect; - PCCB_INDIRECTION Level3Indirect; - PCCB_INDIRECTION Level4Indirect; + MMPFN Direct[MM_DIRECT_PAGE_COUNT]; + MMPFN Level1Indirect; + MMPFN Level2Indirect; + MMPFN Level3Indirect; + MMPFN Level4Indirect; #if MM_INDIRECTION_LEVELS == 5 - PCCB_INDIRECTION Level5Indirect; + MMPFN Level5Indirect; #endif } CCB, *PCCB; @@ -94,6 +78,8 @@ void MmUnlockCcb(PCCB Ccb) KeReleaseMutex(&Ccb->Mutex); } +#if 0 + // Gets a pointer to an entry in the CCB. // If TryAllocateLowerLevels is true, it will attempt to allocate levels if they aren't // allocated. However, this might fail if out of memory, in which case NULL will be @@ -103,3 +89,23 @@ void MmUnlockCcb(PCCB Ccb) // // NOTE: The CCB must be locked. PCCB_ENTRY MmGetEntryPointerCcb(PCCB Ccb, uint64_t PageOffset, bool TryAllocateLowerLevels); + +#endif + +// Retrieves the page frame number at the specified page offset within the CCB. +// This returns a PFN whose reference count is incremented by one on retrieval. +// +// This is thread safe because the CCB mutex is used internally. +MMPFN MmGetEntryCcb(PCCB Ccb, uint64_t PageOffset); + +// Assigns a PFN to the specified page offset within the CCB. +// +// OutPrototypePtePointer is nullable. +// +// If Pfn is PFN_INVALID, then this serves to: +// 1) prepare the CCB for assignment in this slot (as a performance optimization), and +// 2) check if there is already a PFN assigned to this slot (to refault instead of doing +// a useless write) +// +// If the entry is already assigned, this returns STATUS_CONFLICTING_ADDRESSES. +BSTATUS MmSetEntryCcb(PCCB Ccb, uint64_t PageOffset, MMPFN Pfn, PMM_PROTOTYPE_PTE_PTR OutPrototypePtePointer); diff --git a/boron/include/mm/mdl.h b/boron/include/mm/mdl.h index d58ba2d6..9dc24796 100644 --- a/boron/include/mm/mdl.h +++ b/boron/include/mm/mdl.h @@ -49,7 +49,7 @@ typedef struct _MDL uintptr_t MappedStartVA; // The virtual address where this MDL is mapped into system memory PEPROCESS Process; // Process these pages belong to size_t NumberPages; // Size of the page frame number list - MMPFN Pages[]; + MMPFN Pages[0]; } MDL, *PMDL; diff --git a/boron/include/mm/pfn.h b/boron/include/mm/pfn.h index 1ba6852c..26e8b300 100644 --- a/boron/include/mm/pfn.h +++ b/boron/include/mm/pfn.h @@ -90,13 +90,18 @@ typedef struct { struct { + // This address is a **physical** address. Therefore, uint32_t _PrototypePte; uint32_t _Fcb; uint32_t _OffsetLower; - }; + } + PACKED + FileCache; }; + + uint32_t Dummy; // to make this a power of 2 #endif } PACKED @@ -104,13 +109,13 @@ MMPFDBE, *PMMPFDBE; #ifdef IS_64_BIT -#define PFDBE_PrototypePte(Pfdbe) ((uintptr_t*) (0xFFFF000000000000ULL | (Pfdbe)->FileCache._PrototypePte)) -#define PFDBE_Fcb(Pfdbe) ((PFCB) (0xFFFF000000000000ULL | (Pfdbe)->FileCache._Fcb)) +#define PFDBE_PrototypePte(Pfdbe) ((MMPFN*) (0xFFFF000000000000ULL | (Pfdbe)->FileCache._PrototypePte)) +#define PFDBE_Fcb(Pfdbe) ((PFCB) (0xFFFF000000000000ULL | (Pfdbe)->FileCache._Fcb)) #else -#define PFDBE_PrototypePte(Pfdbe) ((uintptr_t*) ((Pfdbe)->FileCache._PrototypePte)) -#define PFDBE_Fcb(Pfdbe) ((PFCB) ((Pfdbe)->FileCache._Fcb)) +#define PFDBE_PrototypePte(Pfdbe) ((MM_PROTOTYPE_PTE_PTR) ((Pfdbe)->FileCache._PrototypePte)) +#define PFDBE_Fcb(Pfdbe) ((PFCB)((Pfdbe)->FileCache._Fcb)) #endif @@ -130,6 +135,11 @@ enum #define PFN_INVALID ((MMPFN)-1) +// Returned by the page cache. Watch out! +#define MM_PFN_OUTOFMEMORY ((MMPFN) -2) + +#define IS_BAD_PFN(Pfn) ((Pfn) == PFN_INVALID || (Pfn) == MM_PFN_OUTOFMEMORY) + #ifdef IS_64_BIT static_assert((sizeof(MMPFDBE) & (sizeof(MMPFDBE) - 1)) == 0, "The page frame struct should be a power of two"); #endif diff --git a/boron/include/mm/pmm.h b/boron/include/mm/pmm.h index 8084c77c..b89d30bb 100644 --- a/boron/include/mm/pmm.h +++ b/boron/include/mm/pmm.h @@ -17,6 +17,22 @@ Module name: #include +#ifdef IS_64_BIT + +typedef MMPFN *MM_PROTOTYPE_PTE_PTR, **PMM_PROTOTYPE_PTE_PTR; +#define MM_PROTOTYPE_PTE_PTR_NONE (NULL) + +#else + +// 32-bit prototype PTE addresses should be OR'd with this value to +// make them virtual. +#define MM_PROTO_PTE_PTR_IS_VIRTUAL (1 << 0) +#define MM_VIRTUAL_PROTO_PTE_PTR(Ptr) ((uintptr_t)(Ptr) | 1) +#define MM_PROTOTYPE_PTE_PTR_NONE (0) +typedef uintptr_t MM_PROTOTYPE_PTE_PTR, *PMM_PROTOTYPE_PTE_PTR; + +#endif + typedef struct _FCB FCB, *PFCB; #ifdef KERNEL @@ -41,6 +57,20 @@ uintptr_t MmGetHHDMOffsetFromAddr(void* Addr); // Converts a physical address to a page frame number (PFN). MMPFN MmPhysPageToPFN(uintptr_t PhysAddr); +#define MmGetHHDMOffsetAddrPfn(Pfn) MmGetHHDMOffsetAddr(MmPFNToPhysPage(Pfn)) + +#ifdef IS_32_BIT + +void MmBeginUsingHHDM(void); +void MmEndUsingHHDM(void); + +#else + +#define MmBeginUsingHHDM() +#define MmEndUsingHHDM() + +#endif + // Converts a page frame number (PFN) to a physical page. uintptr_t MmPFNToPhysPage(MMPFN Pfn); @@ -52,9 +82,9 @@ MMPFN MmAllocatePhysicalPage(void); void MmPageAddReference(MMPFN Pfn); // Assign a prototype PTE address to the page frame. -void MmSetPrototypePtePfn(MMPFN Pfn, uintptr_t* PrototypePte); +void MmSetPrototypePtePfn(MMPFN Pfn, MM_PROTOTYPE_PTE_PTR PrototypePte); -// Assign a prototype PTE address, FCB pointer and offset, to the page frame. +// Assign an FCB pointer and offset, to the page frame. // // Note that the reference to the FCB is weak, i.e. it does not count towards // the FCB's reference count. When the FCB is deleted, the entire page cache @@ -62,7 +92,7 @@ void MmSetPrototypePtePfn(MMPFN Pfn, uintptr_t* PrototypePte); // // The offset is saved in multiples of page size, but the passed in offset // is in bytes. -void MmSetCacheDetailsPfn(MMPFN Pfn, uintptr_t* PrototypePte, PFCB Fcb, uint64_t Offset); +void MmSetCacheDetailsPfn(MMPFN Pfn, PFCB Fcb, uint64_t Offset); // Set an allocated page as modified. void MmSetModifiedPfn(MMPFN Pfn); diff --git a/boron/include/mm/pool.h b/boron/include/mm/pool.h index 41768954..ae84b01a 100644 --- a/boron/include/mm/pool.h +++ b/boron/include/mm/pool.h @@ -24,6 +24,10 @@ typedef int POOL_TYPE; // but the range itself is unmapped #define POOL_FLAG_CALLER_CONTROLLED (1 << 1) +// If this flag is set, then the PTEs will be unmapped automatically +// even if POOL_FLAG_CALLER_CONTROLLED is set. +#define POOL_FLAG_UNMAP_ANYWAY (1 << 2) + // Redundant, could just pass 0 #define POOL_PAGED (0) @@ -35,6 +39,7 @@ typedef int POOL_TYPE; void* MmAllocatePoolBig(int PoolFlags, size_t PageCount, int Tag); +// NOTE: This accepts any offset within the page. So even MmMapIoSpace mapped items can be freed with this. void MmFreePoolBig(void* Address); size_t MmGetSizeFromPoolAddress(void* Address); @@ -43,7 +48,7 @@ size_t MmGetSizeFromPoolAddress(void* Address); // simply call MmFreePoolBig. This function is thread-safe. // // The PermissionsAndCaching parameter is ORed onto the PTEs that will map this physical area. -void* MmMapIoSpace(uintptr_t PhysicalAddress, size_t NumberOfPages, uintptr_t PermissionsAndCaching, int Tag); +void* MmMapIoSpace(uintptr_t PhysicalAddress, size_t SizePages, uintptr_t PermissionsAndCaching, int Tag); // ******* Little Pool ******* // The little pool is a pool allocation system implemented on top diff --git a/boron/include/mm/pt.h b/boron/include/mm/pt.h index 3a9d4c97..2926864c 100644 --- a/boron/include/mm/pt.h +++ b/boron/include/mm/pt.h @@ -51,17 +51,11 @@ void MmUnlockSpace(KIPL OldIpl, uintptr_t DecidingAddress); HPAGEMAP MiGetCurrentPageMap(); // Creates a page mapping. -HPAGEMAP MiCreatePageMapping(HPAGEMAP OldPageMapping); +HPAGEMAP MiCreatePageMapping(); // Deletes a page mapping. void MiFreePageMapping(HPAGEMAP OldPageMapping); -// Resolves a page table entry pointer (virtual address offset by HHDM) relative to an address. -// Can allocate the missing page mapping levels on its way if the flag is set. -// If on its way, it hits a higher page size, currently it will return null since it's not really -// designed for that. -PMMPTE MiGetPTEPointer(HPAGEMAP Mapping, uintptr_t Address, bool AllocateMissingPMLs); - // Check if the PTE for a certain VA exists at the recursive PTE address, in the // current page mapping. // @@ -88,16 +82,16 @@ PMMPTE MmGetPteLocation(uintptr_t Address); PMMPTE MmGetPteLocationCheck(uintptr_t Address, bool GenerateMissingLevels); // Attempts to map a physical page into the specified address space. -bool MiMapAnonPage(HPAGEMAP Mapping, uintptr_t Address, uintptr_t Permissions, bool NonPaged); +bool MiMapAnonPage(uintptr_t Address, uintptr_t Permissions, bool NonPaged); // Attempts to map several anonymous pages into the specified address space. -bool MiMapAnonPages(HPAGEMAP Mapping, uintptr_t Address, size_t SizePages, uintptr_t Permissions, bool NonPaged); +bool MiMapAnonPages(uintptr_t Address, size_t SizePages, uintptr_t Permissions, bool NonPaged); // Attempts to map a known physical page into the specified address space. -bool MiMapPhysicalPage(HPAGEMAP Mapping, uintptr_t PhysicalPage, uintptr_t Address, uintptr_t Permissions); +bool MiMapPhysicalPage(uintptr_t PhysicalPage, uintptr_t Address, uintptr_t Permissions); // Unmaps some memory. Automatically frees it if it is handled by the PMM. -void MiUnmapPages(HPAGEMAP Mapping, uintptr_t Address, size_t LengthPages); +void MiUnmapPages(uintptr_t Address, size_t LengthPages); // Handles a page fault. Returns whether or not the page fault was handled. // TODO make it MiPageFault and export it only to ke/except diff --git a/boron/include/mm/services.h b/boron/include/mm/services.h index 3b04e3ed..870694cb 100644 --- a/boron/include/mm/services.h +++ b/boron/include/mm/services.h @@ -40,3 +40,16 @@ BSTATUS OSMapViewOfObject( uint64_t SectionOffset, int Protection ); + +BSTATUS OSWriteVirtualMemory( + HANDLE ProcessHandle, + void* TargetAddress, + const void* Source, + size_t ByteCount +); + +BSTATUS OSGetMappedFileHandle( + PHANDLE OutHandle, + HANDLE ProcessHandle, + uintptr_t Address +); diff --git a/boron/include/ob.h b/boron/include/ob.h index 56cec0b2..67c02604 100644 --- a/boron/include/ob.h +++ b/boron/include/ob.h @@ -147,6 +147,11 @@ struct _OBJECT_HEADER // among other things, to ensure that non-object-manager-managed objects aren't accidentally // used. int Signature; + +#ifdef IS_32_BIT + // This dummy is present to make sure the object header's struct size is aligned to 8 bytes. + int Dummy; +#endif #endif // Object behavior flags. diff --git a/boron/libgcc-i686.a b/boron/libgcc-i686.a new file mode 100644 index 00000000..8121787a Binary files /dev/null and b/boron/libgcc-i686.a differ diff --git a/boron/linker.ld b/boron/linker.amd64.ld similarity index 100% rename from boron/linker.ld rename to boron/linker.amd64.ld index a7a77a35..8d2c81ab 100644 --- a/boron/linker.ld +++ b/boron/linker.amd64.ld @@ -51,15 +51,6 @@ SECTIONS /* Move to the next memory page for .rodata */ . = ALIGN(CONSTANT(MAXPAGESIZE)); - .rodata : { - *(.rodata .rodata.*) - PROVIDE(KiSymbolTable = .); - PROVIDE(KiSymbolTableEnd = .); - } :rodata - - /* Move to the next memory page for .data */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - /* Global constructor array. */ .init_array : { g_init_array_start = .; @@ -74,6 +65,15 @@ SECTIONS g_fini_array_end = .; } + .rodata : { + *(.rodata .rodata.*) + PROVIDE(KiSymbolTable = .); + PROVIDE(KiSymbolTableEnd = .); + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + .data : { *(.data .data.*) } :data diff --git a/boron/linker.i386.ld b/boron/linker.i386.ld new file mode 100644 index 00000000..510ddfae --- /dev/null +++ b/boron/linker.i386.ld @@ -0,0 +1,104 @@ +/* Tell the linker that we want an ELF32 output file */ +OUTPUT_FORMAT(elf32-i386) +OUTPUT_ARCH(i386) + +/* Linker script for the OS */ + +OUTPUT_FORMAT(elf32-i386) +OUTPUT_ARCH(i386) + +ENTRY (KiBeforeSystemStartup) + +/* Define the program headers we want so the bootloader gives us the right */ +/* MMU permissions */ +PHDRS +{ + intext PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ + text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ + rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ + data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ +} + +/* Here is where all of the sections of the kernel are defined. */ +SECTIONS +{ + /* Begin loading at 0x100000, as that's where GRUB will place our data. */ + . = 1M; + + /* start blocking out writes from here */ + KiReadOnlyStart = . + 0xC0000000; + + .ipldata : + { + *(.ipldata .ipldata.*) + } :intext + + .ipltext : + { + *(.ipltext .ipltext.*) + } :intext + + . += 0xC0000000; + + .text.init ALIGN (4K) : AT (ADDR (.text.init) - 0xC0000000) + { + } :text + + .text.page ALIGN (4K) : AT (ADDR (.text.page) - 0xC0000000) + { + } :text + + .text ALIGN (4K) : AT (ADDR (.text) - 0xC0000000) + { + KiTextInitStart = .; + *(.text.init) + KiTextInitEnd = .; + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + KiTextPageStart = .; + *(.text.page) + KiTextPageEnd = .; + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + *(.text .text.*) + } :text + + .rodata ALIGN (4K) : AT (ADDR (.rodata) - 0xC0000000) + { + KiInitArrayStart = .; + *(.init_array .init_array.*) + KiInitArrayEnd = .; + KiFiniArrayStart = .; + *(.fini_array .fini_array.*) + KiFiniArrayEnd = .; + + *(.rodata .rodata.*) + PROVIDE(KiSymbolTable = .); + PROVIDE(KiSymbolTableEnd = .); + } :rodata + + KiReadOnlyEnd = .; + + .data ALIGN (4K) : AT (ADDR (.data) - 0xC0000000) + { + /* place the end right where data starts to get that nice page alignment :) */ + *(.data .data.*) + } :data + + .bss ALIGN (4K) : AT (ADDR (.bss) - 0xC0000000) + { + *(COMMON) + *(.bss .bss.*) + + /* Hack to keep the PsSystemProcess symbol while adding an object header on top */ + PROVIDE(PsSystemProcess = PspSystemProcessObject + 64); + } :data + + KiKernelEnd = .; + + /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */ + /DISCARD/ : { + *(.eh_frame) + *(.note .note.*) + } +} \ No newline at end of file diff --git a/boron/scripts/generate_symbols.py b/boron/scripts/generate_symbols.py index 54bc1315..a345bfdd 100644 --- a/boron/scripts/generate_symbols.py +++ b/boron/scripts/generate_symbols.py @@ -9,6 +9,17 @@ def SymKey(s): return s[0] # Return the address member +if len(sys.argv) < 1: + print('bad usage') + exit() + +Is32Bit = sys.argv[1] == 'i386' + +if Is32Bit: + DefineWord = 'dd' +else: + DefineWord = 'dq' + print('; ********** The Boron Operating System **********/') print('section .rodata') print('global KiSymbolTable') @@ -68,9 +79,9 @@ def SymKey(s): Size = SymbolList[Count + 1][0] - Address - print(f'dq 0x{Address:x}') - print(f'dq 0x{Size:x}') - print(f'dq name_{Count}') + print(f'{DefineWord} 0x{Address:x}') + print(f'{DefineWord} 0x{Size:x}') + print(f'{DefineWord} name_{Count}') Names += f'name_{Count}: db "{Name}", 0\n' Count += 1 diff --git a/boron/source/build_number b/boron/source/build_number index b0d73241..0e332519 100644 --- a/boron/source/build_number +++ b/boron/source/build_number @@ -1 +1 @@ -129 +650 diff --git a/boron/source/cc/vclru.c b/boron/source/cc/vclru.c index 7e72b051..82159e85 100644 --- a/boron/source/cc/vclru.c +++ b/boron/source/cc/vclru.c @@ -94,6 +94,7 @@ void CcPurgeViewsOverLimit(int LeaveSpaceFor) while (AtLoad(CcViewCacheLruSize) > Limit) { + // TODO: THIS DOES NOT WORK. You must NOT remove the head of view cache!!! PMMVAD Vad = CcRemoveHeadOfViewCacheLru(); MmUnmapViewOfFileInSystemSpace((void*) Vad->Node.StartVa, true); diff --git a/boron/source/ex/servsup.c b/boron/source/ex/servsup.c index 8b1416fd..7e270432 100644 --- a/boron/source/ex/servsup.c +++ b/boron/source/ex/servsup.c @@ -289,20 +289,34 @@ BSTATUS OSDummy() return 0; } +//#define SHOW_PID_IN_DEBUG_LOGS + // Prints a string to the debug console. BSTATUS OSOutputDebugString(const char* String, size_t StringLength) { BSTATUS Status; char* Memory; - Memory = MmAllocatePool(POOL_PAGED, StringLength + 1); +#ifdef SHOW_PID_IN_DEBUG_LOGS + const int OFFSET = 2 + 2 * sizeof(uintptr_t); +#else + const int OFFSET = 0; +#endif + Memory = MmAllocatePool(POOL_PAGED, StringLength + 1 + OFFSET); if (!Memory) return STATUS_INSUFFICIENT_MEMORY; - Memory[StringLength] = 0; - Status = MmSafeCopy(Memory, String, StringLength, KeGetPreviousMode(), false); + Memory[StringLength + OFFSET] = 0; + Status = MmSafeCopy(Memory + OFFSET, String, StringLength, KeGetPreviousMode(), false); if (SUCCEEDED(Status)) + { + #ifdef SHOW_PID_IN_DEBUG_LOGS + char PidBuffer[36]; + snprintf(PidBuffer, sizeof PidBuffer, "%p: ", KeGetCurrentProcess()); + memcpy(Memory, PidBuffer, OFFSET); + #endif DbgPrintStringLocked(Memory); + } MmFreePool(Memory); return Status; diff --git a/boron/source/hal/hal.c b/boron/source/hal/hal.c index 457063f3..32dbf944 100644 --- a/boron/source/hal/hal.c +++ b/boron/source/hal/hal.c @@ -15,6 +15,10 @@ Module name: #include #include +#if defined DEBUG && !defined CONFIG_SMP +#define TICK_DEBUG +#endif + HAL_VFTABLE HalpVftable; bool HalWasInitted() @@ -27,9 +31,9 @@ void HalSetVftable(const HAL_VFTABLE* Table) HalpVftable = *Table; } -void HalEndOfInterrupt() +void HalEndOfInterrupt(int InterruptNumber) { - HalpVftable.EndOfInterrupt(); + HalpVftable.EndOfInterrupt(InterruptNumber); } void HalRequestInterruptInTicks(uint64_t Ticks) @@ -84,7 +88,28 @@ uint64_t HalGetIntTimerFrequency() uint64_t HalGetTickCount() { - return HalpVftable.GetTickCount(); +#ifdef TICK_DEBUG + static uint64_t LastTickCount = 0; +#endif + + uint64_t TickCount = HalpVftable.GetTickCount(); + +#ifdef TICK_DEBUG + bool Restore = KeDisableInterrupts(); + if (LastTickCount > TickCount) + { + KeCrash( + "ERROR: LastTickCount: %lld, TickCount: %lld. Timer went backwards?", + LastTickCount, + TickCount + ); + } + + LastTickCount = TickCount; + KeRestoreInterrupts(Restore); +#endif + + return TickCount; } uint64_t HalGetTickFrequency() @@ -98,11 +123,30 @@ uint64_t HalGetIntTimerDeltaTicks() } #ifdef TARGET_AMD64 + void HalIoApicSetIrqRedirect(uint8_t Vector, uint8_t Irq, uint32_t LapicId, bool Status) { return HalpVftable.IoApicSetIrqRedirect(Vector, Irq, LapicId, Status); } +#endif // TARGET_AMD64 + +#ifdef TARGET_I386 + +void HalPicRegisterInterrupt(uint8_t Vector, KIPL Ipl) +{ + HalpVftable.PicRegisterInterrupt(Vector, Ipl); +} + +void HalPicDeregisterInterrupt(uint8_t Vector, KIPL Ipl) +{ + HalpVftable.PicDeregisterInterrupt(Vector, Ipl); +} + +#endif // TARGET_I386 + +#if defined TARGET_AMD64 || defined TARGET_I386 + BSTATUS HalPciEnumerate( bool LookUpByIds, diff --git a/boron/source/ke/amd64/boot.c b/boron/source/ke/amd64/boot.c index 13bdcc64..a5ea9dbd 100644 --- a/boron/source/ke/amd64/boot.c +++ b/boron/source/ke/amd64/boot.c @@ -148,13 +148,14 @@ static void KiConvertLimineFramebufferToLoaderFramebuffer( LoaderFb->GreenMaskShift = LimineFb->green_mask_shift; LoaderFb->BlueMaskSize = LimineFb->blue_mask_size; LoaderFb->BlueMaskShift = LimineFb->blue_mask_shift; + LoaderFb->IsPhysicalAddress = false; // TODO: transfer other details if needed. } // Allocate a contiguous area of memory from the memory map. INIT -static void* KiLimineAllocateMemoryFromMemMap(size_t Size) +static void* KiEarlyAllocateMemoryFromMemMap(size_t Size) { Size = (Size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); @@ -179,7 +180,7 @@ static void* KiLimineAllocateMemoryFromMemMap(size_t Size) return (void*) MmGetHHDMOffsetAddr(CurrAddr); } - KeCrashBeforeSMPInit("Error, out of memory in KiLimineAllocateMemoryFromMemMap"); + KeCrashBeforeSMPInit("Error, out of memory in KiEarlyAllocateMemoryFromMemMap"); } INIT @@ -202,6 +203,11 @@ void KiInitLoaderParameterBlock() CHECK_RESPONSE(KeLimineKernelFileRequest); CHECK_RESPONSE(KeLimineBootloaderInfoRequest); + // Initialize the recursive paging mechanism. + HPAGEMAP PageMap = MiGetCurrentPageMap(); + PMMPTE Pte = MmGetHHDMOffsetAddr(PageMap); + Pte[MI_RECURSIVE_PAGING_START] = PageMap | MM_PTE_PRESENT | MM_PTE_READWRITE | MM_PTE_NOEXEC; + // Initialize the memory regions. struct limine_memmap_response* MemMapResponse = KeLimineMemMapRequest.response; if (MemMapResponse->entry_count >= MAX_MEMORY_REGIONS) @@ -237,7 +243,7 @@ void KiInitLoaderParameterBlock() // Initialize the other modules. Lpb->ModuleInfo.Count = KeLimineModuleRequest.response->module_count; - Lpb->ModuleInfo.List = KiLimineAllocateMemoryFromMemMap(Lpb->ModuleInfo.Count * sizeof(LOADER_MODULE)); + Lpb->ModuleInfo.List = KiEarlyAllocateMemoryFromMemMap(Lpb->ModuleInfo.Count * sizeof(LOADER_MODULE)); for (size_t i = 0; i < Lpb->ModuleInfo.Count; i++) KiConvertLimineFileToLoaderModule(&Lpb->ModuleInfo.List[i], KeLimineModuleRequest.response->modules[i]); @@ -245,14 +251,14 @@ void KiInitLoaderParameterBlock() // Initialize the CPUs. Lpb->Multiprocessor.BootstrapHardwareId = KeLimineSmpRequest.response->bsp_lapic_id; Lpb->Multiprocessor.Count = KeLimineSmpRequest.response->cpu_count; - Lpb->Multiprocessor.List = KiLimineAllocateMemoryFromMemMap(Lpb->Multiprocessor.Count * sizeof(LOADER_AP)); + Lpb->Multiprocessor.List = KiEarlyAllocateMemoryFromMemMap(Lpb->Multiprocessor.Count * sizeof(LOADER_AP)); for (size_t i = 0; i < Lpb->Multiprocessor.Count; i++) KiConvertLimineApToLoaderAp(&Lpb->Multiprocessor.List[i], KeLimineSmpRequest.response->cpus[i]); // Initialize the frame buffers. Lpb->FramebufferCount = KeLimineFramebufferRequest.response->framebuffer_count; - Lpb->Framebuffers = KiLimineAllocateMemoryFromMemMap(Lpb->FramebufferCount * sizeof(LOADER_FRAMEBUFFER)); + Lpb->Framebuffers = KiEarlyAllocateMemoryFromMemMap(Lpb->FramebufferCount * sizeof(LOADER_FRAMEBUFFER)); for (size_t i = 0; i < Lpb->FramebufferCount; i++) KiConvertLimineFramebufferToLoaderFramebuffer(&Lpb->Framebuffers[i], KeLimineFramebufferRequest.response->framebuffers[i]); diff --git a/boron/source/ke/amd64/intobj.c b/boron/source/ke/amd64/intobj.c index b875f42b..790cb777 100644 --- a/boron/source/ke/amd64/intobj.c +++ b/boron/source/ke/amd64/intobj.c @@ -55,7 +55,7 @@ static PKREGISTERS KiInterruptDispatch(PKREGISTERS Regs) KeReleaseSpinLock(&InterruptList->Lock, Ipl); // Acknowledge the interrupt. - HalEndOfInterrupt(); + HalEndOfInterrupt((int) Regs->IntNumber); // No change in registers. return Regs; diff --git a/boron/source/ke/amd64/misc.asm b/boron/source/ke/amd64/misc.asm index 19eb714c..09d9c3f0 100644 --- a/boron/source/ke/amd64/misc.asm +++ b/boron/source/ke/amd64/misc.asm @@ -3,7 +3,7 @@ ; Copyright (C) 2023 iProgramInCpp ; ; Module name: -; ke/amd64/trap.asm +; ke/amd64/misc.asm ; ; Abstract: ; This module implements certain utility functions for diff --git a/boron/source/ke/amd64/pio.c b/boron/source/ke/amd64/pio.c index 2181ef29..eaf8a521 100644 --- a/boron/source/ke/amd64/pio.c +++ b/boron/source/ke/amd64/pio.c @@ -37,7 +37,6 @@ void KePortWriteWord(uint16_t portNo, uint16_t data) ASM("outw %0, %1"::"a"((uint16_t)data),"Nd"((uint16_t)portNo)); } - uint32_t KePortReadDword(uint16_t portNo) { uint32_t rv; diff --git a/boron/source/ke/amd64/syscall.asm b/boron/source/ke/amd64/syscall.asm index cee2c27b..07ba1941 100644 --- a/boron/source/ke/amd64/syscall.asm +++ b/boron/source/ke/amd64/syscall.asm @@ -132,6 +132,8 @@ KiSystemServiceTableEnd: ; R12 - Argument 7 ; R13 - Argument 8 ; R14 - Argument 9 +; +; RBX, RCX, and R15 are used by this handler and can't be provided. extern KiCheckTerminatedUserMode extern KiSystemServices diff --git a/boron/source/ke/amd64/tlbs.c b/boron/source/ke/amd64/tlbs.c index cf471b9c..c52ad58e 100644 --- a/boron/source/ke/amd64/tlbs.c +++ b/boron/source/ke/amd64/tlbs.c @@ -142,6 +142,6 @@ PKREGISTERS KiHandleTlbShootdownIpi(PKREGISTERS Regs) KeReleaseSpinLock(&Prcb->TlbsLock, IPL_NOINTS); - HalEndOfInterrupt(); + HalEndOfInterrupt((int) Regs->IntNumber); return Regs; } diff --git a/boron/source/ke/crash.c b/boron/source/ke/crash.c index 7f2323fa..761e3b65 100644 --- a/boron/source/ke/crash.c +++ b/boron/source/ke/crash.c @@ -58,7 +58,12 @@ void KeCrashConclusion(const char* Message) #endif // List each loaded DLL's base +#ifdef IS_64_BIT LogMsg("Dll Base Name"); +#else + LogMsg("Dll Base Name"); +#endif + for (int i = 0; i < KeLoadedDLLCount; i++) { PLOADED_DLL LoadedDll = &KeLoadedDLLs[i]; diff --git a/boron/source/ke/except.c b/boron/source/ke/except.c index 1a819f6b..263d2c1f 100644 --- a/boron/source/ke/except.c +++ b/boron/source/ke/except.c @@ -16,24 +16,36 @@ Module name: #include "ki.h" #include -#ifdef TARGET_AMD64 +#if defined TARGET_AMD64 + #define KI_EXCEPTION_HANDLER_INIT() \ UNUSED uint64_t FaultPC = TrapFrame->rip; \ UNUSED uint64_t FaultAddress = TrapFrame->cr2; \ UNUSED uint64_t FaultMode = TrapFrame->ErrorCode; \ + UNUSED int Vector = (int)TrapFrame->IntNumber + +#elif defined TARGET_I386 + +#define KI_EXCEPTION_HANDLER_INIT() \ + UNUSED uint32_t FaultPC = TrapFrame->Eip; \ + UNUSED uint32_t FaultAddress = TrapFrame->Cr2; \ + UNUSED uint64_t FaultMode = TrapFrame->ErrorCode; \ UNUSED int Vector = TrapFrame->IntNumber + #else + #error Go implement KI_EXCEPTION_HANDLER_INIT! + #endif void KeOnUnknownInterrupt(PKREGISTERS TrapFrame) { KI_EXCEPTION_HANDLER_INIT(); -#ifdef TARGET_AMD64 -#define SPECIFIER "%02x" +#if defined TARGET_AMD64 || defined TARGET_I386 +#define SPECIFIER "0x%02x" #else -#define SPECIFIER "%08x" +#define SPECIFIER "0x%08x" #endif DbgPrint("** Unknown interrupt " SPECIFIER " at %p on CPU %u", Vector, FaultPC, KeGetCurrentPRCB()->LapicId); KeCrash("Unknown interrupt " SPECIFIER " at %p on CPU %u", Vector, FaultPC, KeGetCurrentPRCB()->LapicId); @@ -116,6 +128,13 @@ void KeOnPageFault(PKREGISTERS TrapFrame) return; + #elif defined TARGET_I386 + + TrapFrame->Eip = (uint32_t) MmProbeAddressSubEarlyReturn; + TrapFrame->Eax = (uint32_t) STATUS_FAULT; + + return; + #else #error Hey! diff --git a/boron/source/ke/i386/boot.c b/boron/source/ke/i386/boot.c new file mode 100644 index 00000000..eb95cc8d --- /dev/null +++ b/boron/source/ke/i386/boot.c @@ -0,0 +1,418 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/boot.c + +Abstract: + This module contains the bootstrap code that converts + Multiboot bootloader data into kernel specific definitions. + +Author: + iProgramInCpp - 15 October 2025 +***/ +#include "../ki.h" +#include "../../mm/mi.h" +#include "mboot.h" + +extern uint32_t KiMultibootSignature; +extern multiboot_info_t* KiMultibootInfo; + +#define P2V(address) ((void*)(MI_IDENTMAP_START + (uintptr_t)(address))) + +LOADER_PARAMETER_BLOCK KeLoaderParameterBlock; + +INIT void KeMarkCrashedAp(UNUSED uint32_t ProcessorIndex) {} +INIT void KeJumpstartAp(UNUSED uint32_t ProcessorIndex) {} + +#define MAX_MEMORY_REGIONS 256 +static LOADER_MEMORY_REGION KiMemoryRegions[MAX_MEMORY_REGIONS]; + +// Allocate a contiguous area of memory from the memory map. +INIT +static void* KiEarlyAllocateMemoryFromMemMap(size_t Size) +{ + Size = (Size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + + PLOADER_PARAMETER_BLOCK Lpb = &KeLoaderParameterBlock; + for (uint64_t i = 0; i < Lpb->MemoryRegionCount; i++) + { + // if the entry isn't usable, skip it + PLOADER_MEMORY_REGION Entry = &Lpb->MemoryRegions[i]; + + if (Entry->Type != LOADER_MEM_FREE) + continue; + + // Note: Usable entries are guaranteed to be aligned to page size, and + // not overlap any other entries. + if (Entry->Size < Size) + continue; + + uintptr_t CurrAddr = Entry->Base; + Entry->Base += Size; + Entry->Size -= Size; + + ASSERT(CurrAddr < MI_IDENTMAP_SIZE); + return (void*) (MI_IDENTMAP_START + CurrAddr); + } + + KeCrashBeforeSMPInit("Error, out of memory in KiEarlyAllocateMemoryFromMemMap"); +} + +INIT +static void KiRemoveAreaFromMemMap(uintptr_t StartAddress, size_t Size) +{ + PLOADER_PARAMETER_BLOCK Lpb = &KeLoaderParameterBlock; + + // Ensure the start address and size greedily cover page boundaries. + Size += StartAddress & (PAGE_SIZE - 1); + StartAddress &= ~(PAGE_SIZE - 1); + Size = (Size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + + uintptr_t Start = StartAddress; + uintptr_t End = StartAddress + Size; + + for (size_t i = 0; i < Lpb->MemoryRegionCount; i++) + { + PLOADER_MEMORY_REGION OtherRegion = &KiMemoryRegions[i]; + if (OtherRegion->Type != LOADER_MEM_FREE) + continue; + + uintptr_t OrStart = OtherRegion->Base; + uintptr_t OrEnd = OtherRegion->Base + OtherRegion->Size; + + if (OrEnd <= Start || End <= OrStart) + { + // Not overlapping + continue; + } + + if (Start <= OrStart && OrEnd <= End) + { + // region completely swallowed, so nuke it + OtherRegion->Type = LOADER_MEM_RESERVED; + continue; + } + + if (OrStart <= Start && End <= OrEnd) + { + // The region we are trying to erase is completely within + // this memory region. + + // We need to create two memory regions: + // OrStart -- Start - End -- OrEnd + + // First, check the trivial cases + if (OrStart == Start) + { + // just set the start to the end + OtherRegion->Base = End; + OtherRegion->Size = OrEnd - End; + } + else if (OrEnd == End) + { + // just set the end to the start + OtherRegion->Size = Start - OrStart; + } + else + { + // need to create a separate region + OtherRegion->Size = Start - OrStart; + + if (Lpb->MemoryRegionCount >= MAX_MEMORY_REGIONS) + continue; + + PLOADER_MEMORY_REGION NewRegion = &KiMemoryRegions[Lpb->MemoryRegionCount++]; + NewRegion->Type = LOADER_MEM_FREE; + NewRegion->Base = End; + NewRegion->Size = OrEnd - End; + } + + continue; + } + + if (OrStart < End && End < OrEnd && Start < OrStart) + OrStart = End; + + if (Start < OrEnd && OrEnd < End && OrStart < Start) + OrEnd = Start; + + if (OrEnd < OrStart) + { + OtherRegion->Type = LOADER_MEM_RESERVED; + continue; + } + + OtherRegion->Base = OrStart; + OtherRegion->Size = OrEnd - OrStart; + } +} + +INIT +static void KiInitializeMemoryRegions() +{ + if (~KiMultibootInfo->flags & MULTIBOOT_INFO_MEM_MAP) + KeCrashBeforeSMPInit("ERROR: There is no memory map specified!"); + + PLOADER_PARAMETER_BLOCK Lpb = &KeLoaderParameterBlock; + multiboot_memory_map_t + *Mmap = P2V(KiMultibootInfo->mmap_addr), + *MmapStart = Mmap, + *MmapEnd = (void*)((uintptr_t)Mmap + KiMultibootInfo->mmap_length); + + // First, find all the available entries, and insert them. During this loop, + // any overlapping regions are resized/merged/erased. + size_t Index = 0; + for (; Mmap < MmapEnd; Mmap = (void*)((uintptr_t)Mmap + Mmap->size + sizeof(Mmap->size))) + { + if (Mmap->type != MULTIBOOT_MEMORY_AVAILABLE) + continue; + + // ignore ranges that start into 64-bit memory + if (Mmap->addr > 0x100000000) + continue; + + // cap ranges that start into 64-bit memory + if (Mmap->addr + Mmap->len > 0x100000000) + Mmap->len = 0x100000000 - Mmap->addr; + + PLOADER_MEMORY_REGION MemoryRegion = &KiMemoryRegions[Index]; + MemoryRegion->Base = Mmap->addr; + MemoryRegion->Size = Mmap->len; + + // Ensure the address and length are page size aligned. + uint32_t Bias = PAGE_SIZE - (MemoryRegion->Base & (PAGE_SIZE - 1)); + if (Bias != PAGE_SIZE) + { + MemoryRegion->Size -= Bias; + MemoryRegion->Base += Bias; // addr is now aligned + } + + MemoryRegion->Size = MemoryRegion->Size & ~(PAGE_SIZE - 1); + if (MemoryRegion->Size == 0) + continue; + + MemoryRegion->Type = LOADER_MEM_FREE; + + // Ensure this range doesn't overlap with anything else. + uintptr_t Start = MemoryRegion->Base; + uintptr_t End = MemoryRegion->Base + MemoryRegion->Size; + + for (size_t i = 0; i < Index; i++) + { + PLOADER_MEMORY_REGION OtherRegion = &KiMemoryRegions[i]; + + uintptr_t OrStart = OtherRegion->Base; + uintptr_t OrEnd = OtherRegion->Base + OtherRegion->Size; + + if (OrStart <= Start && End <= OrEnd) + { + // new region completely inside old, discard. + Start = End = 0; + break; + } + + if (Start <= OrStart && OrEnd <= End) + { + // the new segment completely swallows the old one. + OtherRegion->Base = Start; + OtherRegion->Size = End - Start; + Start = End = 0; + break; + } + + if (OrStart < End && Start <= OrStart && End <= OrEnd) + End = OrStart; + + if (Start < OrEnd && OrStart <= Start && OrEnd <= End) + Start = OrEnd; + } + + MemoryRegion->Base = Start; + + if (End < Start) + MemoryRegion->Size = 0; + else + MemoryRegion->Size = End - Start; + + if (MemoryRegion->Size != 0) + Index++; + else + MemoryRegion->Type = LOADER_MEM_RESERVED; + + if (Index >= MAX_MEMORY_REGIONS) + { + DbgPrint( + "BOOT WARNING: The bootloader provided %zu bytes of entries, but we have a maximum of %d " + "entries, and as such, some of the memory will be invisible to the OS.", + KiMultibootInfo->mmap_length, + MAX_MEMORY_REGIONS + ); + } + } + + Lpb->MemoryRegionCount = Index; + Lpb->MemoryRegions = KiMemoryRegions; + + Mmap = MmapStart; + for (; Mmap < MmapEnd; Mmap = (void*)((uintptr_t)Mmap + Mmap->size + sizeof(Mmap->size))) + { + if (Mmap->type == MULTIBOOT_MEMORY_AVAILABLE) + continue; + + KiRemoveAreaFromMemMap(Mmap->addr, Mmap->len); + } +} + +extern char KiKernelEnd[]; + +static LOADER_AP KiLoaderAp; +static LOADER_FRAMEBUFFER KiLoaderFramebuffer; +static void* KiLoaderApDummy; + +INIT +void KiInitLoaderParameterBlock() +{ + // Initialize the base identity mapping. + MiInitializeBaseIdentityMapping(); + PLOADER_PARAMETER_BLOCK Lpb = &KeLoaderParameterBlock; + + KiMultibootInfo = P2V(KiMultibootInfo); + + if (KiMultibootSignature != MULTIBOOT_BOOTLOADER_MAGIC) + KeCrashBeforeSMPInit("KiMultibootSignature is not %08x, it's %08x!", MULTIBOOT_BOOTLOADER_MAGIC, KiMultibootSignature); + + // Initialize the memory regions. + KiInitializeMemoryRegions(); + KiRemoveAreaFromMemMap(0x100000, (size_t) KiKernelEnd - 0xC0100000); + + // Initialize the kernel module. + Lpb->ModuleInfo.Kernel.Path = "kernel.elf"; + Lpb->ModuleInfo.Kernel.String = P2V(KiMultibootInfo->cmdline); + Lpb->ModuleInfo.Kernel.Address = (void*) 0xC0100000; // TODO: is the whole kernel (+ELF stuff) loaded here?? + Lpb->ModuleInfo.Kernel.Size = (size_t) KiKernelEnd - 0xC0100000; + + // Initialize the other modules. + if (KiMultibootInfo->flags & MULTIBOOT_INFO_MODS) + { + Lpb->ModuleInfo.Count = KiMultibootInfo->mods_count; + Lpb->ModuleInfo.List = KiEarlyAllocateMemoryFromMemMap(Lpb->ModuleInfo.Count * sizeof(LOADER_MODULE)); + + // QUIRK: Multiboot1 does *not* give you the name of modules! + // So their name has to be specified through the commandline. + // Wow, that sucks. Also Multiboot2 doesn't either. + multiboot_module_t* Module = P2V(KiMultibootInfo->mods_addr); + KiRemoveAreaFromMemMap(KiMultibootInfo->mods_addr, Lpb->ModuleInfo.Count * sizeof(multiboot_module_t)); + + for (size_t i = 0; i < Lpb->ModuleInfo.Count; i++) + { + PLOADER_MODULE Mod = &Lpb->ModuleInfo.List[i]; + Mod->Address = P2V(Module->mod_start); + Mod->Size = Module->mod_end - Module->mod_start; + Mod->String = ""; + + if (Module->cmdline == 0) + KeCrashBeforeSMPInit("ERROR: Cannot load module table. This module has no name."); + + Mod->Path = P2V(Module->cmdline); + + KiRemoveAreaFromMemMap(Module->mod_start, Module->mod_end - Module->mod_start); + Module++; + } + } + else + { + DbgPrint("Booted without modules, the system WILL NOT boot!"); + Lpb->ModuleInfo.Count = 0; + Lpb->ModuleInfo.List = NULL; + } + + // Initialize the CPUs. + Lpb->Multiprocessor.Count = 1; + Lpb->Multiprocessor.List = &KiLoaderAp; + Lpb->Multiprocessor.BootstrapHardwareId = 0; + + KiLoaderAp.ProcessorId = 0; + KiLoaderAp.HardwareId = 0; + KiLoaderAp.TrampolineJumpAddress = &KiLoaderApDummy; + KiLoaderAp.ExtraArgument = NULL; + + // Initialize the frame buffers. + if (KiMultibootInfo->flags & MULTIBOOT_INFO_FRAMEBUFFER_INFO) + { + Lpb->FramebufferCount = 1; + Lpb->Framebuffers = &KiLoaderFramebuffer; + + PLOADER_FRAMEBUFFER Fb = &KiLoaderFramebuffer; + if (KiMultibootInfo->framebuffer_addr > 0x100000000) + { + KeCrash( + "KiMultibootInfo->framebuffer_addr is %08x%08x, which is larger than 32-bit!", + (uint32_t)KiMultibootInfo->framebuffer_addr, + (uint32_t)(KiMultibootInfo->framebuffer_addr >> 32) + ); + } + + Fb->Address = (void*) (uint32_t) KiMultibootInfo->framebuffer_addr; + Fb->Pitch = KiMultibootInfo->framebuffer_pitch; + Fb->Width = KiMultibootInfo->framebuffer_width; + Fb->Height = KiMultibootInfo->framebuffer_height; + Fb->BitDepth = KiMultibootInfo->framebuffer_bpp; + + // @BROKEN: Limine RedMaskSize = KiMultibootInfo->u2.framebuffer_red_mask_size; + Fb->RedMaskShift = KiMultibootInfo->u2.framebuffer_red_field_position; + Fb->GreenMaskSize = KiMultibootInfo->u2.framebuffer_green_mask_size; + Fb->GreenMaskShift = KiMultibootInfo->u2.framebuffer_green_field_position; + Fb->BlueMaskSize = KiMultibootInfo->u2.framebuffer_blue_mask_size; + Fb->BlueMaskShift = KiMultibootInfo->u2.framebuffer_blue_field_position; + + // Quirk detection: If the values make no sense, believe this is Limine v10.x (or + // earlier) loading us. + if (Fb->RedMaskShift == Fb->GreenMaskShift || + Fb->RedMaskShift == Fb->BlueMaskShift || + Fb->GreenMaskShift == Fb->BlueMaskShift || + !Fb->RedMaskSize || + !Fb->GreenMaskSize || + !Fb->BlueMaskShift) + { + DbgPrint("Limine v10.x (or earlier) booted us, so correcting color information"); + Fb->RedMaskSize = KiMultibootInfo->framebuffer_colorinfo_b; + Fb->RedMaskShift = KiMultibootInfo->framebuffer_colorinfo_a; + Fb->GreenMaskSize = KiMultibootInfo->u2.framebuffer_red_mask_size; + Fb->GreenMaskShift = KiMultibootInfo->u2.framebuffer_red_field_position; + Fb->BlueMaskSize = KiMultibootInfo->u2.framebuffer_green_mask_size; + Fb->BlueMaskShift = KiMultibootInfo->u2.framebuffer_green_field_position; + } + } + else + { + DbgPrint("Booted without a framebuffer, things might go wrong!"); + Lpb->FramebufferCount = 0; + Lpb->Framebuffers = NULL; + } + + // Initialize the bootloader's information as well as the command line. + Lpb->CommandLine = P2V(KiMultibootInfo->cmdline); + + if (KiMultibootInfo->flags & MULTIBOOT_INFO_BOOT_LOADER_NAME) + { + Lpb->LoaderInfo.Name = P2V(KiMultibootInfo->boot_loader_name); + Lpb->LoaderInfo.Version = "v1.0"; + } + else + { + Lpb->LoaderInfo.Name = "Generic Multiboot1 compliant bootloader"; + Lpb->LoaderInfo.Version = "v1.0"; + } +} diff --git a/boron/source/ke/i386/cpu.c b/boron/source/ke/i386/cpu.c new file mode 100644 index 00000000..122302bf --- /dev/null +++ b/boron/source/ke/i386/cpu.c @@ -0,0 +1,195 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/cpu.c + +Abstract: + This module implements certain utility functions, + as well as certain parts of UP initialization code, + relating to CPU features such as the GDT and IDT. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include +#include +#include +#include +#include "../../ke/ki.h" + +extern void KeOnUpdateIPL(KIPL newIPL, KIPL oldIPL); // defined in x.asm + +static UNUSED uint32_t KepGetEflags() +{ + uint32_t eflags = 0; + ASM("pushfd\n" + "popd %0":"=r"(eflags)); + return eflags; +} + +// Model specific registers + +uint64_t KeGetMSR(uint32_t msr) +{ + uint32_t edx, eax; + + ASM("rdmsr":"=d"(edx),"=a"(eax):"c"(msr)); + + return ((uint64_t)edx << 32) | eax; +} + +void KeSetMSR(uint32_t msr, uint64_t value) +{ + uint32_t edx = (uint32_t)(value >> 32); + uint32_t eax = (uint32_t)(value); + + ASM("wrmsr"::"d"(edx),"a"(eax),"c"(msr)); +} + +#ifdef CONFIG_SMP // not supported but just in case you want it + +void KeSetCPUPointer(void* pGS) +{ + KeSetMSR(MSR_GS_BASE, (uint64_t) pGS); +} + +void* KeGetCPUPointer(void) +{ + return (void*) KeGetMSR(MSR_GS_BASE); +} + +#else + +static void* KiCPUPointer; + +void KeSetCPUPointer(void* pGS) +{ + KiCPUPointer = pGS; +} + +void* KeGetCPUPointer() +{ + return KiCPUPointer; +} + +#endif + + +extern void* KiIdtDescriptor; + +INIT +void KepLoadIdt() +{ + ASM("lidt (%0)"::"r"(&KiIdtDescriptor)); +} + +static uint64_t KepGdtEntries[] = +{ + 0x0000000000000000, // Null descriptor + 0x00cf9b000000ffff, // 32-bit ring-0 code + 0x00cf93000000ffff, // 32-bit ring-0 data + 0x00cff3000000ffff, // 32-bit ring-3 data + 0x00cffb000000ffff, // 32-bit ring-3 code +}; + +extern void KepLoadGdt(void* desc); +extern void KepLoadTss(int descriptor); + +INIT +static void KepSetupGdt(KARCH_DATA* Data) +{ + KGDT* Gdt = &Data->Gdt; + KTSS* Tss = &Data->Tss; + + for (int i = 0; i < C_GDT_SEG_COUNT; i++) + { + Gdt->Segments[i] = KepGdtEntries[i]; + } + + // setup the TSS entry: + uintptr_t TssAddress = (uintptr_t) Tss; + + Gdt->TssEntry.Limit1 = sizeof(KTSS); + Gdt->TssEntry.Base1 = TssAddress; + Gdt->TssEntry.Access = 0x89; + Gdt->TssEntry.Limit2 = 0x0; + Gdt->TssEntry.Flags = 0x0; + Gdt->TssEntry.Base2 = TssAddress >> 24; + + struct + { + uint16_t Length; + uint64_t Pointer; + } + PACKED GdtDescriptor; + + GdtDescriptor.Length = sizeof * Gdt; + GdtDescriptor.Pointer = (uint64_t) Gdt; + + void* Prcb = KeGetCPUPointer(); + KepLoadGdt(&GdtDescriptor); + + KeSetCPUPointer(Prcb); + + // also load the TSS + KepLoadTss(offsetof(KGDT, TssEntry)); +} + +INIT +static void KepSetupTss(KTSS* Tss) +{ + memset(Tss, 0, sizeof * Tss); + Tss->Ss0 = SEG_RING_0_DATA; +} + +extern void KiSystemServiceHandler(); + +INIT +void KeInitCPU() +{ + KiSwitchToAddressSpaceProcess(KeGetSystemProcess()); + + PKARCH_DATA Data = &KeGetCurrentPRCB()->ArchData; + memset(&Data->Gdt, 0, sizeof Data->Gdt); + + KepSetupTss(&Data->Tss); + KepSetupGdt(Data); + KepLoadIdt(); +/* + // Set up the system call parameters now. + // Enable the SYSCALL/SYSRET instructions and the NX bit. + KeSetMSR(MSR_IA32_EFER, KeGetMSR(MSR_IA32_EFER) | MSR_IA32_EFER_SCE | 0); + + // Set the system call handler. + KeSetMSR(MSR_IA32_LSTAR, (uint64_t) KiSystemServiceHandler); + + // Set the system call CS and SS. + // + // ARCHITECTURAL CRIMES AGAINST HUMANITY: + // For the kernel CS/SS, you cannot pick SS. It is automatically + // picked as STAR_47_32 + 8. But CS is the proper value of STAR_47_32. + // + // For the *user* CS/SS, you cannot pick SS. It is automatically + // picked as STAR_63_48 + 8. But CS is NOT the proper value + // of STAR_63_48, instead, it's STAR_63_48 + 16. Why AMD?! + // + // (Or was it Intel?) + uint64_t Star = SEG_RING_0_CODE | (((SEG_RING_3_DATA - 8) | 3) << 16); + KeSetMSR(MSR_IA32_STAR, Star << 32); + + // Set the mask to disable interrupts on syscall. + KeSetMSR(MSR_IA32_FMASK, 0x200); +*/ +} + +extern uintptr_t KiSystemServiceTable[]; + +// If system call tracing is enabled, this shows all of the system calls happening. +void KePrintSystemServiceDebug(size_t Syscall) +{ + // Format: "[ThreadPointer] - Syscall [Number] ([FunctionName])" + const char* FunctionName = DbgLookUpRoutineNameByAddressExact(KiSystemServiceTable[Syscall]); + DbgPrint("SYSCALL: %p - %d %s", KeGetCurrentThread(), (int) Syscall, FunctionName); +} diff --git a/boron/source/ke/i386/debug.c b/boron/source/ke/i386/debug.c new file mode 100644 index 00000000..a2a7943b --- /dev/null +++ b/boron/source/ke/i386/debug.c @@ -0,0 +1,297 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/debug.c + +Abstract: + This module implements architecture specific debugging + routines. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG2 +#define DONT_LOCK +#endif + +#define KERNEL_IMAGE_BASE (0xC0100000) +extern char KiKernelEnd[]; + +#ifdef DEBUG + +// if you want stack traces from user mode (NOT recommended for +// release because this is a vulnerability), do this: +#define DISABLE_USER_MODE_PREVENTION + +#endif + +// Defined in misc.asm: +uintptr_t KiGetEIP(); +uintptr_t KiGetEBP(); + +// Assume that EBP is the first thing pushed when entering a function. This is often +// the case because we specify -fno-omit-frame-pointer when compiling. +// If not, we are in trouble. +typedef struct STACK_FRAME_tag STACK_FRAME, *PSTACK_FRAME; + +struct STACK_FRAME_tag +{ + PSTACK_FRAME Next; + uintptr_t IP; +}; + +static void DbgResolveAddress(uintptr_t Address, char *SymbolName, size_t BufferSize) +{ + if (!Address) + { + // End of our stack trace + strcpy(SymbolName, "End"); + return; + } + + strcpy(SymbolName, "??"); + + uintptr_t BaseAddress = 0; + // Determine where that address came from. + if ((uintptr_t) KiKernelEnd > Address && Address >= KERNEL_IMAGE_BASE) + { + // Easy, it's in the kernel. + // Determine the symbol's name + const char* Name = DbgLookUpRoutineNameByAddress(Address, &BaseAddress); + + if (Name) + { + snprintf(SymbolName, + BufferSize, + "brn!%s+%x", + Name, + Address - BaseAddress); + } + return; + } + + // Determine which loaded DLL includes this address. + PLOADED_DLL Dll = NULL; + + for (int i = 0; i < KeLoadedDLLCount; i++) + { + PLOADED_DLL LoadedDll = &KeLoadedDLLs[i]; + if (LoadedDll->ImageBase <= Address && Address < LoadedDll->ImageBase + LoadedDll->ImageSize) + { + // It's the one! + Dll = LoadedDll; + break; + } + } + + if (!Dll) + return; + + Address -= Dll->ImageBase; + + const char* Name = LdrLookUpRoutineNameByAddress(Dll, Address, &BaseAddress); + + if (Name) + { + snprintf(SymbolName, + BufferSize, + "%s!%s+%x", + Dll->Name, + Name, + Address - BaseAddress); + } +} + +void DbgPrintDouble(const char* String) +{ + DbgPrintString(String); + HalDisplayString(String); +} + +void DbgPrintStackTrace(uintptr_t Ebp) +{ + if (Ebp == 0) + Ebp = KiGetEBP(); + + PSTACK_FRAME StackFrame = (PSTACK_FRAME) Ebp; + + int Depth = 30; + char Buffer[128]; + + DbgPrintDouble("\tAddress \tName\n"); + +#ifndef DISABLE_USER_MODE_PREVENTION + if (Ebp <= MM_USER_SPACE_END) + { + snprintf(Buffer, sizeof(Buffer), "\t%p\tUser Mode Address\n", (void*) Ebp); + DbgPrintDouble(Buffer); + return; + } +#endif + + // TODO: This might be broken and still access a user address. Debug this later + while (StackFrame && Depth > 0) + { + uintptr_t Address = StackFrame->IP; +#ifndef DISABLE_USER_MODE_PREVENTION + if (Address <= MM_USER_SPACE_END) + { + snprintf(Buffer, sizeof(Buffer), "\t%p\tUser Mode Address\n", (void*) Ebp); + DbgPrintDouble(Buffer); + return; + } +#endif + + char SymbolName[64]; + DbgResolveAddress(Address, SymbolName, sizeof SymbolName); + + snprintf(Buffer, sizeof(Buffer), "\t%p\t%s\n", (void*) Address, SymbolName); + DbgPrintDouble(Buffer); + + Depth--; + StackFrame = StackFrame->Next; + +#ifndef DISABLE_USER_MODE_PREVENTION + if ((uintptr_t)StackFrame <= MM_USER_SPACE_END) + { + snprintf(Buffer, sizeof(Buffer), "\t%p\tUser Mode Address\n", (void*) Ebp); + DbgPrintDouble(Buffer); + return; + } +#endif + } + + if (Depth == 0) + DbgPrintDouble("Warning, stack trace too deep, increase the depth in " __FILE__ " if you need it.\n"); +} + +#ifdef DEBUG + +//#define SERIAL +#ifdef SERIAL + +// Serial Port Defines - Copied from NanoShell +// +// We are using COM1 here. +#define PORT_BASE 0x3F8 +#define DLAB_ENABLE (1 << 7) +#define BAUD_DIVISOR (0x0003) +#define LCR_8BIT_DATA (3 << 0) // Data Bits - 8 +#define LCR_PAR_NONE (0 << 3) // No Parity. +#define FCR_ENABLE (1 << 0) +#define FCR_RFRES (1 << 1) +#define FCR_XFRES (1 << 2) +#define FCR_RXTRIG (3 << 6) +#define IER_NOINTS (0) +#define MCR_DTR (1 << 0) +#define MCR_RTS (1 << 1) +#define MCR_OUT1 (1 << 2) +#define MCR_LOOPBK (1 << 4) // loop-back +#define S_RBR 0x00 // Receive buffer register (read only) same as... +#define S_THR 0x00 // Transmitter holding register (write only) +#define S_IER 0x01 // Interrupt enable register +#define S_IIR 0x02 // Interrupt ident register (read only)... +#define S_FCR 0x02 // FIFO control register (write only) +#define S_LCR 0x03 // Line control register +#define S_MCR 0x04 // Modem control register +#define S_LSR 0x05 // Line status register +#define S_MSR 0x06 // Modem status register + +#define S_CHECK_BYTE 0xCA + +void DbgInit() +{ + KePortWriteByte(PORT_BASE+1, IER_NOINTS); + + // Set Divisor to 3 -- 38400 baud + KePortWriteByte(PORT_BASE+0, BAUD_DIVISOR & 0xFF); + KePortWriteByte(PORT_BASE+1, BAUD_DIVISOR >> 8); + + // Set the data parity and bit size. + KePortWriteByte(PORT_BASE+3, LCR_8BIT_DATA | LCR_PAR_NONE); + + // Enable FIFO + KePortWriteByte(PORT_BASE+2, FCR_RXTRIG | FCR_XFRES | FCR_RFRES | FCR_ENABLE); + + // Prepare modem control register + KePortWriteByte(PORT_BASE+4, MCR_OUT1 | MCR_RTS | MCR_DTR); // IRQs disabled, RTS/DSR set + + // set in loopback mode to test the serial chip + KePortWriteByte(PORT_BASE+4, MCR_LOOPBK | MCR_OUT1 | MCR_RTS); + + // Send a check byte, and check if we get it back + KePortWriteByte(PORT_BASE+0, S_CHECK_BYTE); + + if (KePortReadByte (PORT_BASE + 0) != S_CHECK_BYTE) + { + // Hope it still works + } + + // Set this serial PORT_BASE to normal operation + KePortWriteByte(PORT_BASE+4, MCR_OUT1 | MCR_RTS | MCR_DTR); // IRQs disabled, OUT#1 bit, no loop-back +} + +void DbgPrintChar(char c) +{ + while ((KePortReadByte(PORT_BASE + S_LSR) & 0x20) == 0) + __asm__("pause"); + + KePortWriteByte(PORT_BASE, c); +} + +void DbgPrintString(const char* str) +{ + while (*str) + { + if (*str == '\n') + DbgPrintChar('\r'); + DbgPrintChar(*str); + str++; + } +} + +#else + +void DbgInit() +{ + // E9 port doesn't need initialization. +} + +void DbgPrintString(const char* str) +{ + while (*str) + { + KePortWriteByte(0xE9, *str); + str++; + } +} + +#endif + +KSPIN_LOCK KiPrintLock; +KSPIN_LOCK KiDebugPrintLock; + +void DbgPrintStringLocked(const char* str) +{ +#ifndef DONT_LOCK + KIPL OldIpl; + KeAcquireSpinLock(&KiDebugPrintLock, &OldIpl); +#endif + + DbgPrintString(str); + +#ifndef DONT_LOCK + KeReleaseSpinLock(&KiDebugPrintLock, OldIpl); +#endif +} + +#endif diff --git a/boron/source/ke/i386/foreinit.asm b/boron/source/ke/i386/foreinit.asm new file mode 100644 index 00000000..3a3c3c17 --- /dev/null +++ b/boron/source/ke/i386/foreinit.asm @@ -0,0 +1,140 @@ +; +; The Boron Operating System +; Copyright (C) 2025 iProgramInCpp +; +; Module name: +; ke/i386/foreinit.asm +; +; Abstract: +; This module implements the entry point of the Boron kernel +; for the i386 architecture. +; +; Author: +; iProgramInCpp - 14 October 2025 +; +bits 32 + +; **** Initial Program Loader **** +; This was borrowed from NanoShell (https://github.com/iProgramMC/NanoShellOS) +; but it works well enough for it so it'll probably work here too. + +%define BASE_ADDRESS 0xC0000000 +%define V2P(k) ((k) - BASE_ADDRESS) + +section .ipldata + + ; Multiboot v0.6.96 specification + align 4 + + ; Header + dd 0x1BADB002 ; Signature + dd 7 ; Flags: MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_VIDEO_MODE + dd - (0x1BADB002 + 7) ; Check Sum + + ; A.out Kludge - blank because we're an ELF + dd 0 + dd 0 + dd 0 + dd 0 + dd 0 + + ; Video Mode + dd 0 ; Require a linear frame buffer + dd 1024 ; Width + dd 768 ; Height + dd 32 ; Bits per pixel + +section .ipltext +global KiBeforeSystemStartup +KiBeforeSystemStartup: + cli + + ; no need for a stack at this stage + xor ebp, ebp + + ; store the provided multiboot data + mov [V2P(KiMultibootSignature)], eax + mov [V2P(KiMultibootInfo)], ebx + + ; first address to map is 0x00000000. map 2048 pages + xor esi, esi + mov ecx, 2048 + + ; get the physical address of the bootstrap page directory + mov edi, V2P(KiBootstrapPageTables) + +.LoopFill: + mov edx, esi + or edx, 0x03 ; set present and r/w bits + mov [edi], edx + + add esi, 0x1000 + add edi, 4 + loop .LoopFill + + ; map the two page tables to both 0x00000000 and 0xC0000000, as well + ; as the page directory itself into the 1023rd entry + mov dword [V2P(KiBootstrapPageDirectory) + 0 * 4], V2P(KiBootstrapPageTables + 0) + 0x03 + mov dword [V2P(KiBootstrapPageDirectory) + 1 * 4], V2P(KiBootstrapPageTables + 4096) + 0x03 + mov dword [V2P(KiBootstrapPageDirectory) + 768 * 4], V2P(KiBootstrapPageTables + 0) + 0x03 + mov dword [V2P(KiBootstrapPageDirectory) + 769 * 4], V2P(KiBootstrapPageTables + 4096) + 0x03 + mov dword [V2P(KiBootstrapPageDirectory) +1023 * 4], V2P(KiBootstrapPageDirectory) + 0x03 + + ; set CR3 to the physical address of the page directory + mov ecx, V2P(KiBootstrapPageDirectory) + mov cr3, ecx + + ; set PG and WP bit + ; note: WP bit is ignored/reserved on 80386. should probably test for that? + mov ecx, cr0 + or ecx, 0x80010000 + mov cr0, ecx + + ; jump to higher half + mov ecx, (KiBeforeSystemStartupHigherHalf) + jmp ecx + +extern KiSystemStartup + +section .text +global KiBeforeSystemStartupHigherHalf +KiBeforeSystemStartupHigherHalf: + ; unmap the identity mapping, we don't need it anymore + mov dword [KiBootstrapPageDirectory + 0], 0 + mov dword [KiBootstrapPageDirectory + 4], 0 + + ; reload CR3 to force a TLB flush (we updated the page directory but TLB + ; isn't aware of that). NOTE: you can probably just use invlpg. + mov ecx, cr3 + mov cr3, ecx + + ; setup the initial stack + mov esp, KiInitialStack + 4096 + + ; GDT will be set up later + + call KiSystemStartup + + cli +.Stop: + hlt + jmp .Stop + +section .bss + +global KiMultibootSignature +global KiMultibootInfo +global KiBootstrapPageDirectory +global KiBootstrapPageTables +global KiHhdmWindowPageTables +global KiPoolHeadersPageTables + +KiMultibootSignature: resd 1 +KiMultibootInfo: resd 1 + +alignb 4096 +KiInitialStack: resb 4096 +KiBootstrapPageDirectory: resb 4096 +KiBootstrapPageTables: resb 64 * 4096 +KiHhdmWindowPageTables: resb 4 * 4096 +KiPoolHeadersPageTables: resb 2 * 4096 diff --git a/boron/source/ke/i386/init.c b/boron/source/ke/i386/init.c new file mode 100644 index 00000000..14037a9a --- /dev/null +++ b/boron/source/ke/i386/init.c @@ -0,0 +1,34 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/init.c + +Abstract: + This module implements the architecture specific UP-init + routines. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include +#include + +void KiSetupIdt(); +void KiInitializeInterruptSystem(); + +INIT +void KeInitArchUP() +{ + KiSetupIdt(); + KiInitializeInterruptSystem(); +} + +INIT +void KeInitArchMP() +{ + KeInitCPU(); + KeLowerIPL(IPL_NORMAL); + HalInitSystemMP(); +} diff --git a/boron/source/ke/i386/intlist.inc b/boron/source/ke/i386/intlist.inc new file mode 100644 index 00000000..0105e078 --- /dev/null +++ b/boron/source/ke/i386/intlist.inc @@ -0,0 +1,84 @@ +; +; The Boron Operating System +; Copyright (C) 2025 iProgramInCpp +; +; Module name: +; ke/i386/intlist.inc +; +; Abstract: +; This is a define file. It is used to loop through all +; 256 interrupt vectors through the macro INT,2 in a way +; that doesn't repeat itself too much. +; +; Author: +; iProgramInCpp - 14 October 2025 +; + +INT 00, N +INT 01, N +INT 02, N +INT 03, N +INT 04, N +INT 05, N +INT 06, N +INT 07, N +INT 08, Y +INT 09, N +INT 0A, Y +INT 0B, Y +INT 0C, Y +INT 0D, Y +INT 0E, Y +INT 0F, N +INT 10, N +INT 11, Y +INT 12, N +INT 13, N +INT 14, N +INT 15, N +INT 16, N +INT 17, Y +INT 18, N +INT 19, N +INT 1A, N +INT 1B, Y +INT 1C, Y +INT 1D, N +INT 1E, N +INT 1F, N + +%macro INTGRP 1 +INT %{1}0, N +INT %{1}1, N +INT %{1}2, N +INT %{1}3, N +INT %{1}4, N +INT %{1}5, N +INT %{1}6, N +INT %{1}7, N +INT %{1}8, N +INT %{1}9, N +INT %{1}A, N +INT %{1}B, N +INT %{1}C, N +INT %{1}D, N +INT %{1}E, N +INT %{1}F, N +%endmacro + +INTGRP 2 +INTGRP 3 +INTGRP 4 +INTGRP 5 +INTGRP 6 +INTGRP 7 +INTGRP 8 +INTGRP 9 +INTGRP A +INTGRP B +INTGRP C +INTGRP D +INTGRP E +INTGRP F + +%unmacro INTGRP 1 diff --git a/boron/source/ke/i386/intobj.c b/boron/source/ke/i386/intobj.c new file mode 100644 index 00000000..c6cf9c20 --- /dev/null +++ b/boron/source/ke/i386/intobj.c @@ -0,0 +1,178 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/intobj.c + +Abstract: + This module implements the interrupt object for + the i386 platform. + + N.B. The interval timer and DPC dispatch functions + do not use the interrupt object. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include +#include + +extern int8_t KiTrapIplList[]; // trap.asm +extern void* KiTrapCallList[]; // trap.asm + +typedef struct +{ + LIST_ENTRY List; + KSPIN_LOCK Lock; +} +INTERRUPT_LIST, *PINTERRUPT_LIST; + +static INTERRUPT_LIST KiInterruptList[256]; + +static PKREGISTERS KiInterruptDispatch(PKREGISTERS Regs) +{ + int Number = Regs->IntNumber; + PINTERRUPT_LIST InterruptList = &KiInterruptList[Number]; + KIPL Ipl; + + KeAcquireSpinLock(&InterruptList->Lock, &Ipl); + + for (PLIST_ENTRY Entry = InterruptList->List.Flink; + Entry != &InterruptList->List; + Entry = Entry->Flink) + { + PKINTERRUPT Interrupt = CONTAINING_RECORD(Entry, KINTERRUPT, Entry); + KIPL Unused; + KeAcquireSpinLock(Interrupt->SpinLock, &Unused); + + Interrupt->ServiceRoutine(Interrupt, Interrupt->ServiceContext); + + KeReleaseSpinLock(Interrupt->SpinLock, Unused); + } + + KeReleaseSpinLock(&InterruptList->Lock, Ipl); + + // Acknowledge the interrupt. + HalEndOfInterrupt(Regs->IntNumber); + + // No change in registers. + return Regs; +} + +INIT +void KiInitializeInterruptSystem() +{ + for (int i = 0; i < 256; i++) + { + InitializeListHead(&KiInterruptList[i].List); + KeInitializeSpinLock(&KiInterruptList[i].Lock); + } +} + +void KeInitializeInterrupt( + PKINTERRUPT Interrupt, + PKSERVICE_ROUTINE ServiceRoutine, + void* ServiceContext, + PKSPIN_LOCK SpinLock, + int Vector, + KIPL InterruptIpl, + bool SharedVector) +{ + if (Vector < PIC_INTERRUPT_BASE && Vector >= PIC_INTERRUPT_BASE + 16) + DbgPrint("WARNING: KeInitializeInterrupt -- interrupt vector %d may not be called", Vector); + + ASSERT(InterruptIpl > IPL_DPC && "The caller may not override this IPL"); + ASSERT(InterruptIpl < IPL_CLOCK && "The caller may not override this IPL"); + + Interrupt->Connected = false; + Interrupt->SharedVector = SharedVector; + Interrupt->Vector = Vector; + Interrupt->Ipl = InterruptIpl; + Interrupt->ServiceRoutine = ServiceRoutine; + Interrupt->ServiceContext = ServiceContext; + Interrupt->SpinLock = SpinLock; +} + +bool KeConnectInterrupt(PKINTERRUPT Interrupt) +{ + ASSERT(!Interrupt->Connected && "It's already connected!"); + + PINTERRUPT_LIST InterruptList = &KiInterruptList[Interrupt->Vector]; + + KIPL Ipl, IplUnused; + Ipl = KeRaiseIPL(Interrupt->Ipl); + KeAcquireSpinLock(&InterruptList->Lock, &IplUnused); + + // Check if the vector may be shared. + if (!IsListEmpty(&InterruptList->List)) + { + // If the head has SharedVector == false, return false here. + PKINTERRUPT Head = CONTAINING_RECORD(InterruptList->List.Flink, KINTERRUPT, Entry); + + if (!Head->SharedVector) + { + KeReleaseSpinLock(&InterruptList->Lock, IplUnused); + KeLowerIPL(Ipl); + return false; + } + } + else + { + HalPicRegisterInterrupt(Interrupt->Vector, Interrupt->Ipl); + } + + // Connect the interrupt now. + InsertTailList(&InterruptList->List, &Interrupt->Entry); + Interrupt->Connected = true; + + // In case this wasn't already done, wire up the interrupt + // dispatcher routine we specified. + KeRegisterInterrupt(Interrupt->Vector, KiInterruptDispatch); + + KeReleaseSpinLock(&InterruptList->Lock, IplUnused); + KeLowerIPL(Ipl); + + return true; +} + +void KeDisconnectInterrupt(PKINTERRUPT Interrupt) +{ + ASSERT(Interrupt->Connected && "You need to have connected the interrupt to disconnect it!"); + + PINTERRUPT_LIST InterruptList = &KiInterruptList[Interrupt->Vector]; + KIPL Ipl, IplUnused; + Ipl = KeRaiseIPL(Interrupt->Ipl); + KeAcquireSpinLock(&InterruptList->Lock, &IplUnused); + + // Disconnect the interrupt now. + RemoveEntryList(&Interrupt->Entry); + Interrupt->Connected = false; + + if (IsListEmpty(&InterruptList->List)) + HalPicDeregisterInterrupt(Interrupt->Vector, Interrupt->Ipl); + + KeReleaseSpinLock(&InterruptList->Lock, IplUnused); + KeLowerIPL(Ipl); +} + +int KeSynchronizeExecution( + PKINTERRUPT Interrupt, + KIPL SynchronizeIpl, + PKSYNCHRONIZE_ROUTINE Routine, + void* SynchronizeContext) +{ + if (SynchronizeIpl < Interrupt->Ipl) + SynchronizeIpl = Interrupt->Ipl; + + KIPL Ipl, IplUnused; + Ipl = KeRaiseIPL(SynchronizeIpl); + KeAcquireSpinLock(Interrupt->SpinLock, &IplUnused); + + int Result = Routine(SynchronizeContext); + + KeReleaseSpinLock(Interrupt->SpinLock, IplUnused); + KeLowerIPL(Ipl); + + return Result; +} diff --git a/boron/source/ke/i386/mboot.h b/boron/source/ke/i386/mboot.h new file mode 100644 index 00000000..4c15cd2d --- /dev/null +++ b/boron/source/ke/i386/mboot.h @@ -0,0 +1,263 @@ +/* Copyright (C) 1999,2003,2007,2008,2009 Free Software Foundation, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY + * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#ifndef MULTIBOOT_HEADER +#define MULTIBOOT_HEADER 1 + +#define MbCheckFlag(flags,bit) ((flags) & (1 << (bit))) + +/* How many bytes from the start of the file we search for the header. */ +#define MULTIBOOT_SEARCH 8192 + +/* The magic field should contain this. */ +#define MULTIBOOT_HEADER_MAGIC 0x1BADB002 + +/* This should be in %eax. */ +#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 + +/* The bits in the required part of flags field we don't support. */ +#define MULTIBOOT_UNSUPPORTED 0x0000fffc + +/* Alignment of multiboot modules. */ +#define MULTIBOOT_MOD_ALIGN 0x00001000 + +/* Alignment of the multiboot info structure. */ +#define MULTIBOOT_INFO_ALIGN 0x00000004 + +/* Flags set in the 'flags' member of the multiboot header. */ + +/* Align all boot modules on i386 page (4KB) boundaries. */ +#define MULTIBOOT_PAGE_ALIGN 0x00000001 + +/* Must pass memory information to OS. */ +#define MULTIBOOT_MEMORY_INFO 0x00000002 + +/* Must pass video information to OS. */ +#define MULTIBOOT_VIDEO_MODE 0x00000004 + +/* This flag indicates the use of the address fields in the header. */ +#define MULTIBOOT_AOUT_KLUDGE 0x00010000 + +/* Flags to be set in the 'flags' member of the multiboot info structure. */ + +/* is there basic lower/upper memory information? */ +#define MULTIBOOT_INFO_MEMORY 0x00000001 +/* is there a boot device set? */ +#define MULTIBOOT_INFO_BOOTDEV 0x00000002 +/* is the command-line defined? */ +#define MULTIBOOT_INFO_CMDLINE 0x00000004 +/* are there modules to do something with? */ +#define MULTIBOOT_INFO_MODS 0x00000008 + +/* These next two are mutually exclusive */ + +/* is there a symbol table loaded? */ +#define MULTIBOOT_INFO_AOUT_SYMS 0x00000010 +/* is there an ELF section header table? */ +#define MULTIBOOT_INFO_ELF_SHDR 0X00000020 + +/* is there a full memory map? */ +#define MULTIBOOT_INFO_MEM_MAP 0x00000040 + +/* Is there drive info? */ +#define MULTIBOOT_INFO_DRIVE_INFO 0x00000080 + +/* Is there a config table? */ +#define MULTIBOOT_INFO_CONFIG_TABLE 0x00000100 + +/* Is there a boot loader name? */ +#define MULTIBOOT_INFO_BOOT_LOADER_NAME 0x00000200 + +/* Is there an APM table? */ +#define MULTIBOOT_INFO_APM_TABLE 0x00000400 + +/* Is there video information? */ +#define MULTIBOOT_INFO_VIDEO_INFO 0x00000800 + +//small jerry-rig +#define MULTIBOOT_INFO_FRAMEBUFFER_INFO 0x00001000 + +#ifndef ASM_FILE + +typedef unsigned char MultibootUInt8; +typedef unsigned short MultibootUInt16; +typedef unsigned int MultibootUInt32; +typedef unsigned long long MultibootUInt64; + +struct multiboot_header +{ + /* Must be MULTIBOOT_MAGIC - see above. */ + MultibootUInt32 magic; + + /* Feature flags. */ + MultibootUInt32 flags; + + /* The above fields plus this one must equal 0 mod 2^32. */ + MultibootUInt32 checksum; + + /* These are only valid if MULTIBOOT_AOUT_KLUDGE is set. */ + MultibootUInt32 header_addr; + MultibootUInt32 load_addr; + MultibootUInt32 load_end_addr; + MultibootUInt32 bss_end_addr; + MultibootUInt32 entry_addr; + + /* These are only valid if MULTIBOOT_VIDEO_MODE is set. */ + MultibootUInt32 mode_type; + MultibootUInt32 width; + MultibootUInt32 height; + MultibootUInt32 depth; +}; + +/* The symbol table for a.out. */ +struct multiboot_aout_symbol_table +{ + MultibootUInt32 tabsize; + MultibootUInt32 strsize; + MultibootUInt32 addr; + MultibootUInt32 reserved; +}; +typedef struct multiboot_aout_symbol_table multiboot_aout_symbol_table_t; + +/* The section header table for ELF. */ +struct multiboot_elf_section_header_table +{ + MultibootUInt32 num; + MultibootUInt32 size; + MultibootUInt32 addr; + MultibootUInt32 shndx; +}; +typedef struct multiboot_elf_section_header_table multiboot_elf_section_header_table_t; + +struct multiboot_info +{ + /* Multiboot info version number */ + MultibootUInt32 flags; + + /* Available memory from BIOS */ + MultibootUInt32 mem_lower; + MultibootUInt32 mem_upper; + + /* "root" partition */ + MultibootUInt32 boot_device; + + /* Kernel command line */ + MultibootUInt32 cmdline; + + /* Boot-Module list */ + MultibootUInt32 mods_count; + MultibootUInt32 mods_addr; + + union + { + multiboot_aout_symbol_table_t aout_sym; + multiboot_elf_section_header_table_t elf_sec; + } u; + + /* Memory Mapping buffer */ + MultibootUInt32 mmap_length; + MultibootUInt32 mmap_addr; + + /* Drive Info buffer */ + MultibootUInt32 drives_length; + MultibootUInt32 drives_addr; + + /* ROM configuration table */ + MultibootUInt32 config_table; + + /* Boot Loader Name */ + MultibootUInt32 boot_loader_name; + + /* APM table */ + MultibootUInt32 apm_table; + + /* Video */ + MultibootUInt32 vbe_control_info; + MultibootUInt32 vbe_mode_info; + MultibootUInt16 vbe_mode; + MultibootUInt16 vbe_interface_seg; + MultibootUInt16 vbe_interface_off; + MultibootUInt16 vbe_interface_len; + + MultibootUInt64 framebuffer_addr; + MultibootUInt32 framebuffer_pitch; + MultibootUInt32 framebuffer_width; + MultibootUInt32 framebuffer_height; + MultibootUInt8 framebuffer_bpp; + MultibootUInt8 framebuffer_type; + + // @BROKEN: The Multiboot specification mentions that the color_info + // struct should begin at offset 110, which is not divisible by 4. + // + // However, the specification's *own definition*, as well as GRUB's, + // places all of these fields in a union that will inevitably get + // 4 byte alignment due to the framebuffer_palette_addr member. + // + // Meanwhile, Limine respects the *offset* of 110, and places color + // information there. So, we'll have to do some quirk detection. + MultibootUInt8 framebuffer_colorinfo_a; + MultibootUInt8 framebuffer_colorinfo_b; + + union { + struct { + MultibootUInt32 framebuffer_palette_addr; + MultibootUInt16 framebuffer_palette_num_colors; + }; + struct { + MultibootUInt8 framebuffer_red_field_position; + MultibootUInt8 framebuffer_red_mask_size; + MultibootUInt8 framebuffer_green_field_position; + MultibootUInt8 framebuffer_green_mask_size; + MultibootUInt8 framebuffer_blue_field_position; + MultibootUInt8 framebuffer_blue_mask_size; + }; + } u2; +}; +typedef struct multiboot_info multiboot_info_t; + +struct multiboot_mmap_entry +{ + MultibootUInt32 size; + MultibootUInt64 addr; + MultibootUInt64 len; +#define MULTIBOOT_MEMORY_AVAILABLE 1 +#define MULTIBOOT_MEMORY_RESERVED 2 + MultibootUInt32 type; +} __attribute__((packed)); +typedef struct multiboot_mmap_entry multiboot_memory_map_t; + +struct multiboot_mod_list +{ + /* the memory used goes from bytes 'mod_start' to 'mod_end-1' inclusive */ + MultibootUInt32 mod_start; + MultibootUInt32 mod_end; + + /* Module command line */ + MultibootUInt32 cmdline; + + /* padding to take it to 16 bytes (must be zero) */ + MultibootUInt32 pad; +}; +typedef struct multiboot_mod_list multiboot_module_t; + +#endif /* ! ASM_FILE */ + +#endif /* ! MULTIBOOT_HEADER */ \ No newline at end of file diff --git a/boron/source/ke/i386/misc.asm b/boron/source/ke/i386/misc.asm new file mode 100644 index 00000000..afef4c83 --- /dev/null +++ b/boron/source/ke/i386/misc.asm @@ -0,0 +1,196 @@ +; +; The Boron Operating System +; Copyright (C) 2025 iProgramInCpp +; +; Module name: +; ke/i386/misc.asm +; +; Abstract: +; This module implements certain utility functions for +; the i386 platform. +; +; Author: +; iProgramInCpp - 14 October 2025 +; +bits 32 + +%include "arch/i386.inc" + +; these functions get the EIP and EBP respectively +global KiGetEIP +global KiGetEBP + +KiGetEIP: + mov eax, [esp] + ret + +KiGetEBP: + mov eax, ebp + ret + +; these functions set the CR3 +global KeSetCurrentPageTable +global KeGetCurrentPageTable + +; void* KeGetCurrentPageTable() +KeGetCurrentPageTable: + mov eax, cr3 + ret + +; void KeSetCurrentPageTable(void* pt) +KeSetCurrentPageTable: + mov eax, [esp + 4] + mov cr3, eax + ret + +; int MmProbeAddressSub(void* Address, size_t Length, bool ProbeWrite); +global MmProbeAddressSub +MmProbeAddressSub: +; This function performs the actual work of probing the addresses. It is +; written in assembly to allow for predictability of the stack layout. This +; makes using the alternative exit, returning the STATUS_FAULT code, after +; an invalid page fault, much easier, since we can know the stack layout the +; entire time we're probing. + + push ebp + mov ebp, esp + push edi + push esi + + mov edi, [ebp + 8] + mov esi, [ebp + 12] + mov dl, [ebp + 16] + + ; edi - Address + ; esi - Length + ; dl - ProbeWrite + mov ecx, edi ; Load the address into ecx, a scratch register. + add ecx, esi ; Add the length to ecx. + dec ecx ; Decrement ecx. This will be the final byte of the range we're probing. + mov al, [ecx] ; Load a byte from that address. + test dl, dl ; Check if ProbeWrite is zero. + jz .StartLoop + mov [ecx], al ; If not, then store that byte to that address. +.StartLoop: + mov al, [edi] ; Load a byte from the start address. + test dl, dl ; Check if ProbeWrite is zero + jz .DontWrite + mov [edi], al ; If not, then store that byte to that address. +.DontWrite: + add edi, 4096 ; Add a page size to rdi. + cmp edi, ecx ; Check if the end address has been reached. + jb .StartLoop ; If not, then probe another page. + + xor eax, eax ; Return with an exit code of zero (STATUS_SUCCESS) + pop esi + pop edi + pop ebp + ret + +; int MmSafeCopySub(void* Destination, void* Source, size_t Length); +global MmSafeCopySub +MmSafeCopySub: + push ebp + mov ebp, esp + + cld ; we don't want the copy the other way + push edi + push esi + mov edi, [ebp + 8] + mov esi, [ebp + 12] + mov ecx, [ebp + 16] + rep movsb + xor eax, eax + ; fall through + +; void MmProbeAddressSubEarlyReturn() +; Returns early from MmProbeAddressSub and MmSafeCopySub. Called by the invalid page fault handler. +global MmProbeAddressSubEarlyReturn +MmProbeAddressSubEarlyReturn: + pop esi + pop edi + pop ebp + ret + +; void KiSwitchThreadStack(void** OldStack, void** NewStack) +global KiSwitchThreadStack +KiSwitchThreadStack: + push ebp + mov ebp, esp + + mov eax, [ebp + 8] + mov ecx, [ebp + 12] + + ; push System V ABI callee saved registers + pushfd + push ebx + push esi + push edi + + ; Store the current rsp into *OldStack. + mov dword [eax], esp + + ; Load the current rsp from NewStack. + mov esp, dword [ecx] + + ; Pop everything and return as usual. +KiPopEverythingAndReturn: + pop edi + pop esi + pop ebx + xor eax, eax + + ; Pop EFLAGS and return + popfd + pop ebp + ret + +; Arguments: +; edi - Thread entry point. +; esi - Thread context. +extern KiUnlockDispatcher +global KiThreadEntryPoint +KiThreadEntryPoint: + push esi + + push dword 0 + call KiUnlockDispatcher + add esp, 4 + + ; ebx saved because KiUnlockDispatcher is SysV compliant + ; eax still pushed-- will be used as the argument + call edi + +; Used for the init phase of the scheduler, before a thread is scheduled in. +global KiSwitchThreadStackForever +KiSwitchThreadStackForever: + mov esp, [esp + 4] + jmp KiPopEverythingAndReturn + +; void KepLoadGdt(GdtDescriptor* desc); +global KepLoadGdt +KepLoadGdt: + mov eax, [esp + 4] + lgdt [eax] + + ; update the code segment + push dword 0x08 ; code segment + lea eax, [rel .a] ; jump address + push eax + retfd ; return far - will go to .a now +.a: + ; update the segments + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + ret + +; void KepLoadTss(int descriptor) +global KepLoadTss +KepLoadTss: + mov ax, [esp + 4] + ltr ax + ret diff --git a/boron/source/ke/i386/pio.c b/boron/source/ke/i386/pio.c new file mode 100644 index 00000000..877ba1be --- /dev/null +++ b/boron/source/ke/i386/pio.c @@ -0,0 +1,50 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/pio.c + +Abstract: + This module implements x86 specific Port I/O functions. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include + +uint8_t KePortReadByte(uint16_t portNo) +{ + uint8_t rv; + ASM("inb %1, %0" : "=a" (rv) : "dN" (portNo)); + return rv; +} + +void KePortWriteByte(uint16_t portNo, uint8_t data) +{ + ASM("outb %0, %1"::"a"((uint8_t)data),"Nd"((uint16_t)portNo)); +} + +uint16_t KePortReadWord(uint16_t portNo) +{ + uint16_t rv; + ASM("inw %1, %0" : "=a" (rv) : "dN" (portNo)); + return rv; +} + +void KePortWriteWord(uint16_t portNo, uint16_t data) +{ + ASM("outw %0, %1"::"a"((uint16_t)data),"Nd"((uint16_t)portNo)); +} + +uint32_t KePortReadDword(uint16_t portNo) +{ + uint32_t rv; + ASM("inl %1, %0" : "=a" (rv) : "dN" (portNo)); + return rv; +} + +void KePortWriteDword(uint16_t portNo, uint32_t data) +{ + ASM("outl %0, %1"::"a"((uint32_t)data),"Nd"((uint16_t)portNo)); +} diff --git a/boron/source/ke/i386/probe.c b/boron/source/ke/i386/probe.c new file mode 100644 index 00000000..7d5b7d47 --- /dev/null +++ b/boron/source/ke/i386/probe.c @@ -0,0 +1,118 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/probe.c + +Abstract: + This module implements the high level routines for address probing. + + Probing a set of addresses checks that they are usable in kernel mode. + It also brings all of the demand-pages back in to memory. It works by + attempting to read from / write to the memory. If the memory is not + accessible, an invalid page fault is raised by hardware. This makes + probing about as cheap as just copying the raw data. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include +#include + +bool MmIsAddressRangeValid(uintptr_t Address, size_t Size, KPROCESSOR_MODE AccessMode) +{ + // Size=0 is invalid. + if (Size == 0) { + DbgPrint("MmIsAddressRangeValid FAILURE: Size 0"); + return false; + } + + // Check for overflow. + uintptr_t AddressEnd = Address + Size; + if (AddressEnd < Address) { + DbgPrint("MmIsAddressRangeValid FAILURE: AddressEnd %p < Address %p", AddressEnd, Address); + return false; + } + + if (AccessMode == MODE_USER && AddressEnd > MM_USER_SPACE_END) { + DbgPrint("MmIsAddressRangeValid FAILURE: AccessMode==MODEUSER AddressEnd %p Address %p Size: %zu RA:%p", AddressEnd, Address, Size, CallerAddress()); + return false; + } + + return true; +} + +// Defined in arch/i386/misc.asm +int MmProbeAddressSub(void* Address, size_t Length, bool ProbeWrite); + +// This is the front-end for the probing code. The actual probing +// is performed in assembly, because it's impossible to predict what +// kind of stack layout the C version would use. (It could differ +// depending on compiler version, for example.) +BSTATUS MmProbeAddress(void* Address, size_t Length, bool ProbeWrite, KPROCESSOR_MODE AccessMode) +{ + if (!MmIsAddressRangeValid((uintptr_t)Address, Length, AccessMode)) + return STATUS_INVALID_PARAMETER; + + const uintptr_t MaxPtr = (uintptr_t) ~0ULL; // 0b1111...1111 + const uintptr_t HalfMaxPtr = MaxPtr >> 1; // 0b0111...1111 + const uintptr_t MSBPtrSet = ~HalfMaxPtr; // 0b1000...0000 + + // If the size is bigger than or equal to MAX_ADDRESS>>1 + if (Length > HalfMaxPtr) + return STATUS_INVALID_PARAMETER; + + uintptr_t AddressLimit = (uintptr_t) Address + Length; + + // If the address and the address limit are in + // different halves of the address space + if (((uintptr_t)Address ^ AddressLimit) == MSBPtrSet) + return STATUS_INVALID_PARAMETER; + + KeGetCurrentThread()->Probing = true; + + int Code = MmProbeAddressSub (Address, Length, ProbeWrite); + + if (Code != STATUS_SUCCESS) + { + KeGetCurrentThread()->Probing = false; + return Code; + } + + KeGetCurrentThread()->Probing = false; + return STATUS_SUCCESS; +} + +// Defined in arch/amd64/misc.asm +int MmSafeCopySub(void* Address, const void* Source, size_t Length); + +BSTATUS MmSafeCopy(void* Address, const void* Source, size_t Length, KPROCESSOR_MODE AccessMode, bool VerifyDest) +{ + if (VerifyDest) + { + if (!MmIsAddressRangeValid((uintptr_t)Address, Length, AccessMode)) + return STATUS_INVALID_PARAMETER; + } + else + { + if (!MmIsAddressRangeValid((uintptr_t)Source, Length, AccessMode)) + return STATUS_INVALID_PARAMETER; + } + + // Let the page fault handler know we are probing. + KeGetCurrentThread()->Probing = true; + + // This is just a regular old memcpy. Nothing different about it, + // other than the return value. It's a five instruction marvel. + int Code = MmSafeCopySub(Address, Source, Length); + + // If it returned through a path different than usual (i.e. it was + // detoured through MmProbeAddressSubEarlyReturn), then it's going + // to return STATUS_FAULT, which we'll mirror when returning. + + // No longer probing. + KeGetCurrentThread()->Probing = false; + + return Code; +} diff --git a/boron/source/ke/i386/syscall.c b/boron/source/ke/i386/syscall.c new file mode 100644 index 00000000..731b5ca8 --- /dev/null +++ b/boron/source/ke/i386/syscall.c @@ -0,0 +1,176 @@ + +#include "../ki.h" + +#include +#include +#include +#include +#include + +//#define ENABLE_SYSCALL_TRACE + +typedef struct +{ + uint64_t SectionOffset; + int Protection; +} +MAP_VIEW_OF_OBJECT_PARAMS, *PMAP_VIEW_OF_OBJECT_PARAMS; + +typedef struct +{ + size_t Length; + uint32_t Flags; +} +READ_FILE_PARAMS, *PREAD_FILE_PARAMS; + +typedef struct +{ + size_t Length; + uint32_t Flags; + uint64_t* OutSize; +} +WRITE_FILE_PARAMS, *PWRITE_FILE_PARAMS; + +BSTATUS OSMapViewOfObject_Call( + HANDLE ProcessHandle, + HANDLE MappedObject, + void** BaseAddressInOut, + size_t ViewSize, + int AllocationType, + PMAP_VIEW_OF_OBJECT_PARAMS ExtraParamsPtr +) +{ + BSTATUS Status; + MAP_VIEW_OF_OBJECT_PARAMS ExtraParams; + + Status = MmSafeCopy(&ExtraParams, ExtraParamsPtr, sizeof(MAP_VIEW_OF_OBJECT_PARAMS), MODE_USER, false); + if (FAILED(Status)) + return Status; + + return OSMapViewOfObject( + ProcessHandle, + MappedObject, + BaseAddressInOut, + ViewSize, + AllocationType, + ExtraParams.SectionOffset, + ExtraParams.Protection + ); +} + +BSTATUS OSReadFile_Call( + PIO_STATUS_BLOCK Iosb, + HANDLE Handle, + uint64_t ByteOffset, // 2 parameters + void* Buffer, + PREAD_FILE_PARAMS ExtraParamsPtr +) +{ + BSTATUS Status; + READ_FILE_PARAMS ExtraParams; + + Status = MmSafeCopy(&ExtraParams, ExtraParamsPtr, sizeof(READ_FILE_PARAMS), MODE_USER, false); + if (FAILED(Status)) + return Status; + + return OSReadFile(Iosb, Handle, ByteOffset, Buffer, ExtraParams.Length, ExtraParams.Flags); +} + +BSTATUS OSWriteFile_Call( + PIO_STATUS_BLOCK Iosb, + HANDLE Handle, + uint64_t ByteOffset, // 2 parameters + const void* Buffer, + PWRITE_FILE_PARAMS ExtraParamsPtr +) +{ + BSTATUS Status; + WRITE_FILE_PARAMS ExtraParams; + + Status = MmSafeCopy(&ExtraParams, ExtraParamsPtr, sizeof(WRITE_FILE_PARAMS), MODE_USER, false); + if (FAILED(Status)) + return Status; + + return OSWriteFile(Iosb, Handle, ByteOffset, Buffer, ExtraParams.Length, ExtraParams.Flags, ExtraParams.OutSize); +} + +// N.B. extra arguments will be ignored by the called function. +typedef int(*KI_SYSCALL_HANDLER)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + +const void* const KiSystemServiceTable[] = +{ + OSAllocateVirtualMemory, + OSClose, + OSCreateEvent, + OSCreateMutex, + OSCreatePipe, + OSCreateProcess, + OSCreateThread, + OSDeviceIoControl, + OSDuplicateHandle, + OSExitProcess, + OSExitThread, + OSFreeVirtualMemory, + OSGetAlignmentFile, + OSGetCurrentPeb, + OSGetCurrentTeb, + OSGetExitCodeProcess, + OSGetLengthFile, + OSGetMappedFileHandle, + OSGetTickCount, + OSGetTickFrequency, + OSGetVersionNumber, + OSMapViewOfObject_Call, + OSOpenEvent, + OSOpenFile, + OSOpenMutex, + OSOutputDebugString, + OSPulseEvent, + OSQueryEvent, + OSQueryMutex, + OSReadDirectoryEntries, + OSReadFile_Call, + OSReleaseMutex, + OSResetDirectoryReadHead, + OSResetEvent, + OSSetCurrentPeb, + OSSetCurrentTeb, + OSSetEvent, + OSSetExitCode, + OSSetPebProcess, + OSSetSuspendedThread, + OSSleep, + OSTerminateThread, + OSTouchFile, + OSWaitForMultipleObjects, + OSWaitForSingleObject, + OSWriteFile_Call, + OSWriteVirtualMemory, +}; + +#define KI_SYSCALL_COUNT ARRAY_COUNT(KiSystemServiceTable) + +extern void KePrintSystemServiceDebug(size_t Call); // cpu.c +extern void KiCheckTerminatedUserMode(); // traps.c + +PKREGISTERS KiSystemServiceHandler(PKREGISTERS Regs) +{ + if (Regs->Eax >= KI_SYSCALL_COUNT) + { + Regs->Eax = STATUS_INVALID_PARAMETER; + return Regs; + } + +#ifdef ENABLE_SYSCALL_TRACE + KePrintSystemServiceDebug(Regs->Eax); +#endif + + // Now call the function. + KI_SYSCALL_HANDLER Handler = KiSystemServiceTable[Regs->Eax]; + int Return = Handler(Regs->Ebx, Regs->Ecx, Regs->Edx, Regs->Esi, Regs->Edi, Regs->Ebp); + Regs->Eax = Return; + + KiCheckTerminatedUserMode(); + + return Regs; +} diff --git a/boron/source/ke/i386/thredsup.c b/boron/source/ke/i386/thredsup.c new file mode 100644 index 00000000..f1f6381f --- /dev/null +++ b/boron/source/ke/i386/thredsup.c @@ -0,0 +1,36 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/thredsup.c + +Abstract: + This module implements the architecture specific + thread state setup routine. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include +#include +#include + +NO_RETURN void KiThreadEntryPoint(); + +void KiSetupRegistersThread(PKTHREAD Thread) +{ + // Subtract 10 from the stack pointer to keep the final stack frame valid. + uintptr_t StackBottom = ((uintptr_t) Thread->Stack.Top + (uintptr_t) Thread->Stack.Size - 0x10) & ~0xF; + uint32_t* StackPointer = (uint32_t*) StackBottom; + + // KiPopEverythingAndReturn pops these + *(--StackPointer) = (uint32_t) KiThreadEntryPoint; // Set return address + *(--StackPointer) = 0; // Set EBP + *(--StackPointer) = 0x200; // Set IF when entering the thread + *(--StackPointer) = 0; // Set EBX + *(--StackPointer) = (uint32_t) Thread->StartContext; // Set ESI + *(--StackPointer) = (uint32_t) Thread->StartRoutine; // Set EDI + + Thread->StackPointer = StackPointer; +} diff --git a/boron/source/ke/i386/tlbs.c b/boron/source/ke/i386/tlbs.c new file mode 100644 index 00000000..048258ec --- /dev/null +++ b/boron/source/ke/i386/tlbs.c @@ -0,0 +1,37 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/tlbs.c + +Abstract: + This module contains the i386 platform's specific + TLB shootdown routine. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include + +#ifdef CONFIG_SMP +#error If you want SMP 32-bit x86, you must copy the tlbs.c from AMD64! +#endif + +#define MAX_TLBS_LENGTH 4096 + +void KeIssueTLBShootDown(uintptr_t Address, size_t Length) +{ + if (Length == 0) + Length = 1; + + if (Length >= MAX_TLBS_LENGTH) + { + KeSetCurrentPageTable(KeGetCurrentPageTable()); + } + else + { + for (size_t i = 0; i < Length; i++) + KeInvalidatePage((void*)(Address + i * PAGE_SIZE)); + } +} diff --git a/boron/source/ke/i386/trap.asm b/boron/source/ke/i386/trap.asm new file mode 100644 index 00000000..8e230afe --- /dev/null +++ b/boron/source/ke/i386/trap.asm @@ -0,0 +1,171 @@ +; +; The Boron Operating System +; Copyright (C) 2025 iProgramInCpp +; +; Module name: +; ke/i386/trap.asm +; +; Abstract: +; This module contains the implementation for the common +; trap handler and the list of functions to enter when +; a certain interrupt vector is triggered. +; +; Author: +; iProgramInCpp - 14 October 2025 +; + +bits 32 + +%include "arch/i386.inc" + +extern KiTrapList +global KiTrapCallList + +section .text + +; int KiTryEnterHardwareInterrupt(int IntNum); +extern KiTryEnterHardwareInterrupt +; void KiExitHardwareInterrupt(PKREGISTERS Registers); +extern KiExitHardwareInterrupt + +; Return Value: +; rax - Old interrupt state +global KeDisableInterrupts +KeDisableInterrupts: + pushfd ; push RFLAGS register + pop eax ; pop it into RAX + and eax, 0x200 ; check the previous interrupt flag (1 << 9) + shr eax, 9 ; shift right by 9 + cli ; disable interrupts + ret ; return rax + +; Arguments: +; 1 - Old interrupt state +global KeRestoreInterrupts +KeRestoreInterrupts: + mov eax, [esp + 4] + test eax, eax ; check if old state is zero + jz .ret ; if it is, return immediately + sti ; restore interrupts +.ret: + ret ; done + +global KiTrapCommon +KiTrapCommon: + push eax + push ecx + push edx + + ; Check if we can enter the interrupt handler, based on the + ; current interrupt number. This interrupt number has an + ; IPL associated with it; if it's lower than the current one, + ; this function returns -1. + mov eax, [esp + 12] + push eax + call KiTryEnterHardwareInterrupt + add esp, 4 + + cmp eax, 0 ; If it returned -1, it means we're not allowed to enter yet. + jl .returnEarly + + ; Push the old IPL. + push eax + + ; Get the pointer to the IP, because we want to construct a + ; fake stack frame that includes it. + mov ecx, [esp + 24] + push ecx + push ebp + mov ebp, esp + + ; Push the rest of the state. + push ebx + push esi + push edi + mov eax, cr2 + push eax + + ; Then, find the current interrupt number again and run it. + ; The interrupt handler will, if it returns, return the pointer + ; to the new stack, so we'll load that instead of restoring state + ; like usual. + + ; Note: this 40 is carefully counted! + mov ecx, [esp + 40] + push esp + call [KiTrapCallList + ecx * 4] + mov esp, eax + + ; Exit the hardware interrupt now. + push esp + call KiExitHardwareInterrupt + + ; Pop the state and return. + add esp, 8 ; pop the argument we pushed for KiExitHardwareInterrupt + the CR2 + pop edi + pop esi + pop ebx + pop ebp + add esp, 8 ; pop SFRA + old IPL + +.returnEarly: + pop edx + pop ecx + pop eax + add esp, 8 ; pop IntNum + ErrorCode +KeIretdBreakpoint: + iretd + +global KeDescendIntoUserMode +KeDescendIntoUserMode: + mov edi, [esp + 4] + mov esi, [esp + 8] + mov edx, [esp + 12] + + ; put edx onto the stack, as well as a fake return address + ; this is so that parameter passing through this function works + sub esi, 8 + mov dword [esi], 0 + mov dword [esi + 4], edx + + ; EDI - Initial program counter + ; ESI - Initial stack pointer + ; EDX - User context + push dword SEG_RING_3_DATA | 3 ; push SS + push esi ; push RSP + push dword 0x202 ; push RFLAGS + push dword SEG_RING_3_CODE | 3 ; push CS + push edi ; push RIP + + ; clear all the registers + xor eax, eax + xor ebp, ebp + xor ebx, ebx + xor ecx, ecx + xor edx, edx + xor esi, esi + xor edi, edi + + ; finally, swap gs and return to user mode. + cli + iretd + +global KiCallSoftwareInterrupt +KiCallSoftwareInterrupt: + mov eax, [esp + 4] + mov ecx, [esp] + pushfd ; push eflags + push dword SEG_RING_0_CODE ; push cs + push ecx ; push eip - return value + cli ; ensure interrupts are disabled + jmp [KiTrapCallList + eax * 4] ; jump to the specific trap + ; note: the function will return directly to the return address + +section .bss +global KiTrapIplList +KiTrapIplList: + resb 256 ; Reserve a byte for each vector + +global KiTrapCallList +KiTrapCallList: + resq 256 ; Reserve a pointer for each vector diff --git a/boron/source/ke/i386/traplist.asm b/boron/source/ke/i386/traplist.asm new file mode 100644 index 00000000..969bd901 --- /dev/null +++ b/boron/source/ke/i386/traplist.asm @@ -0,0 +1,57 @@ +; +; The Boron Operating System +; Copyright (C) 2025 iProgramInCpp +; +; Module name: +; ke/i386/trap.asm +; +; Abstract: +; This module contains the implementation for each of the +; individual trap handlers, which call into the common +; trap handler. +; +; Author: +; iProgramInCpp - 14 October 2025 +; +bits 32 +section .text + +; NOTE: This uses however many bytes of data. Is that bad? I don't know. + +; Arguments: +; 0 - Interrupt number in hexadecimal +; 1 - If the interrupt has an error code, Y, otherwise, N +; %macro INT 2 + +extern KiTrapCommon + +%macro INT 2 +global KiTestTrap%1 +KiTestTrap%1: + %ifidn %2, N + push dword 0 ; Push a fake error code + %endif + push dword 0x%1 ; Push the interrupt number + jmp KiTrapCommon ; Jump to the common trap handler +%endmacro + +%include "ke/i386/intlist.inc" + +%unmacro INT 2 + +%macro INT 2 +extern KiTestTrap%1 +%endmacro +%include "ke/i386/intlist.inc" +%unmacro INT 2 + + +section .rodata + +global KiTrapList +KiTrapList: +%macro INT 2 + dd KiTestTrap%1 +%endmacro +%include "ke/i386/intlist.inc" +%unmacro INT 2 diff --git a/boron/source/ke/i386/traps.c b/boron/source/ke/i386/traps.c new file mode 100644 index 00000000..2f66725a --- /dev/null +++ b/boron/source/ke/i386/traps.c @@ -0,0 +1,268 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ke/i386/traps.c + +Abstract: + This header file implements support for the IDT (Interrupt + Dispatch Table). + +Author: + iProgramInCpp - 28 October 2025 +***/ +#include +#include +#include +#include +#include +#include "../../ke/ki.h" + +extern void* const KiTrapList[]; // traplist.asm +extern int8_t KiTrapIplList[]; // trap.asm +extern void* KiTrapCallList[]; // trap.asm + +extern void KiCallSoftwareInterrupt(int Vector); // trap.asm + +// The trap gate isn't likely to be used as it doesn't turn off +// interrupts when entering the interrupt handler. +enum KGATE_TYPE +{ + GATE_INT = 0xE, + GATE_TRAP = 0xF +}; + +// The IDT itself. +KIDT KiIdt; + +struct KIDT_DESCRIPTOR_tag +{ + uint16_t Limit; + PKIDT Base; +} +PACKED +KiIdtDescriptor; + +// The interrupt vector function type. It's not a function you can actually call from C. +typedef void(*KiInterruptVector)(); + +static UNUSED void KiSetInterruptDPL(PKIDT Idt, int Vector, int Ring) +{ + Idt->Entries[Vector].DPL = Ring; +} + +// This is also not likely to be used. We need interrupts to be disabled automatically +// before we raise the IPL to the interrupt's level, but trap gates will not disable interrupts. +// This is bad, since a keyboard interrupt could manage to sneak past an important clock interrupt +// before we manage to raise the IPL in the clock interrupt. +// Not to mention the performance gains would be minimal if at all existent. +static UNUSED void KiSetInterruptGateType(PKIDT Idt, int Vector, int GateType) +{ + Idt->Entries[Vector].GateType = GateType; +} + +// Set the IST of an interrupt vector. This is probably useful for the double fault handler, +// as it is triggered only when another interrupt failed to be called, which is useful in cases +// where the kernel stack went missing and bad (Which I hope there aren't any!) +static UNUSED void KiSetInterruptStackIndex(PKIDT Idt, int Vector, int Ist) +{ + Idt->Entries[Vector].IST = Ist; +} + +// This loads the interrupt vector handler into the IDT. +// Parameters: +// Idt - The IDT that the interrupt vector will be loaded in. +// Vector - The interrupt number that the handler will be assigned to. +static void KiLoadInterruptVector(PKIDT Idt, int Vector, KiInterruptVector Handler) +{ + PKIDT_ENTRY Entry = &Idt->Entries[Vector]; + memset(Entry, 0, sizeof * Entry); + + uintptr_t HandlerAddr = (uintptr_t) Handler; + + Entry->OffsetLow = HandlerAddr & 0xFFFF; + Entry->OffsetHigh = HandlerAddr >> 16; + + // The code segment that the interrupt handler will run in. + Entry->SegmentSel = SEG_RING_0_CODE; + + // Ist = 0 means the stack is not switched when the interrupt keeps the CPL the same. + // The IST should only be used in case of a fatal exception, such as a double fault. + Entry->IST = 0; + + // The ring that the interrupt can be called from. It's usually 0 (so you can't fake + // an exception from user mode), however, it can be set to 3 for userspace system calls. + // Usually we use `syscall` or `sysenter` for that, though. + Entry->DPL = 0; + + // This is an interrupt gate. + Entry->GateType = 0xE; + + // The entry is present. + Entry->Present = true; +} + +int KiTryEnterHardwareInterrupt(int IntNum) +{ + PKPRCB Prcb = KeGetCurrentPRCB(); + + PKIPL IplPtr = &Prcb->Ipl; + + // grab old IPL + int OldIpl = (int) *IplPtr; + + // figure out the new IPL + int NewIpl = KiTrapIplList[IntNum]; + + // Check if this is a lower priority interrupt than our current IPL. + if (NewIpl != -1) + { + // If this is a hardware interrupt and it's lower in our priority, then defer it. + if (OldIpl >= NewIpl && IntNum >= 0x20) + { + DbgPrint("Deferring int %d for later. TriedIpl: %d CurrIpl: %d", IntNum, NewIpl, OldIpl); + + if (IntNum == PIC_INTERRUPT_BASE) + KeCrash("Timer tick triggered twice??"); + + // Trying to get interrupted by a masked interrupt? No problem, enqueue + // this interrupt onto our queue and simply return. + if (Prcb->ArchData.InterruptQueuePlace >= KE_MAX_QUEUED_INTERRUPTS) + { + DbgPrint( + "ERROR: Got interrupt %d. More than %d interrupts enqueued " + "at once, so this interrupt will be dropped.", + KE_MAX_QUEUED_INTERRUPTS + ); + return -1; + } + + Prcb->ArchData.InterruptQueue[Prcb->ArchData.InterruptQueuePlace++] = IntNum; + return -1; + } + + *IplPtr = NewIpl; // specific to Amd64 + } + + // now that we've setup the hardware interrupt stuff, enable interrupts. + // we couldn't have done that before because the CPU would think that we're + // in a low IPL thing meanwhile we're not.. + if (NewIpl != -1 && NewIpl < IPL_CLOCK) + ENABLE_INTERRUPTS(); + + return OldIpl; +} + +void KiExitHardwareInterrupt(PKREGISTERS Registers) +{ + DISABLE_INTERRUPTS(); + PKPRCB Prcb = KeGetCurrentPRCB(); + int OldIpl = Registers->OldIpl; + Prcb->Ipl = OldIpl; + + // Check if there are any interrupts we still need to de-queue. + // Note that we don't scan using a for loop, because this could change. + while (Prcb->ArchData.InterruptQueuePlace != 0) + { + int Vector = Prcb->ArchData.InterruptQueue[--Prcb->ArchData.InterruptQueuePlace]; + DbgPrint("Calling enqueued software interrupt %d", Vector); + KiCallSoftwareInterrupt(Vector); + } + + // Check if the current thread is terminated and we are about to + // return to user mode. + PKTHREAD CurrentThread = KeGetCurrentThread(); + if (CurrentThread && + CurrentThread->PendingTermination && + Registers->OldIpl == IPL_NORMAL && + Registers->Cs == SEG_RING_3_CODE) + { + KiTerminateUserModeThread(CurrentThread->IncrementTerminated); + } + + // Note: safe to call here because KiDispatchSoftwareInterrupts + // preserves interrupt disable state across a call to it + if (Prcb->PendingSoftInterrupts >> OldIpl) + KiDispatchSoftwareInterrupts(OldIpl); +} + +void KiCheckTerminatedUserMode() +{ + // Before entering user mode, check if the thread was terminated first. + if (KeGetCurrentThread()->PendingTermination) + KiTerminateUserModeThread(KeGetCurrentThread()->IncrementTerminated); +} + +// ==== Interrupt Handlers ==== + +// Generic interrupt handler. Used in case an interrupt is not implemented +PKREGISTERS KiTrapUnknownHandler(PKREGISTERS Regs) +{ + KeOnUnknownInterrupt(Regs); + return Regs; +} + +PKREGISTERS KiHandleDoubleFault(PKREGISTERS Regs) +{ + KeOnDoubleFault(Regs); + return Regs; +} + +PKREGISTERS KiHandleProtectionFault(PKREGISTERS Regs) +{ + KeOnProtectionFault(Regs); + return Regs; +} + +PKREGISTERS KiHandlePageFault(PKREGISTERS Regs) +{ + KeOnPageFault(Regs); + return Regs; +} + +static KSPIN_LOCK KiTrapLock; + +extern PKREGISTERS KiSystemServiceHandler(PKREGISTERS Regs); + +// Run on the BSP only. +void KiSetupIdt() +{ + KiIdtDescriptor.Base = &KiIdt; + KiIdtDescriptor.Limit = sizeof(KiIdt) - 1; + + for (int i = 0; i < 0x100; i++) + { + KiLoadInterruptVector(&KiIdt, i, KiTrapList[i]); + + KiTrapIplList[i] = IPL_NOINTS; + KiTrapCallList[i] = KiTrapUnknownHandler; + } + + KeRegisterInterrupt(INTV_DBL_FAULT, KiHandleDoubleFault); + KeRegisterInterrupt(INTV_PROT_FAULT, KiHandleProtectionFault); + KeRegisterInterrupt(INTV_PAGE_FAULT, KiHandlePageFault); + KeRegisterInterrupt(INTV_SYSTEMCALL, KiSystemServiceHandler); + KeSetInterruptIPL(INTV_DBL_FAULT, IPL_NOINTS); + KeSetInterruptIPL(INTV_PROT_FAULT, IPL_NOINTS); + KeSetInterruptIPL(INTV_PAGE_FAULT, IPL_UNDEFINED); + KeSetInterruptIPL(INTV_SYSTEMCALL, IPL_UNDEFINED); + KiSetInterruptDPL(&KiIdt, INTV_SYSTEMCALL, 3); +} + +void KeRegisterInterrupt(int Vector, PKINTERRUPT_HANDLER Handler) +{ + KIPL Ipl; + KeAcquireSpinLock(&KiTrapLock, &Ipl); + KiTrapCallList[Vector] = Handler; + KeReleaseSpinLock(&KiTrapLock, Ipl); +} + +void KeSetInterruptIPL(int Vector, KIPL Ipl) +{ + KiTrapIplList[Vector] = Ipl; +} + +void KeOnUpdateIPL(UNUSED KIPL OldIpl, UNUSED KIPL NewIpl) +{ +} diff --git a/boron/source/ke/process.c b/boron/source/ke/process.c index 4ab93403..5c295459 100644 --- a/boron/source/ke/process.c +++ b/boron/source/ke/process.c @@ -46,11 +46,14 @@ PKPROCESS KeGetSystemProcess() return &PsGetSystemProcess()->Pcb; } -void KeInitializeProcess(PKPROCESS Process, int BasePriority, KAFFINITY BaseAffinity) +BSTATUS KeInitializeProcess(PKPROCESS Process, int BasePriority, KAFFINITY BaseAffinity) { KeInitializeDispatchHeader(&Process->Header, DISPATCH_PROCESS); - Process->PageMap = MiCreatePageMapping(KeGetCurrentPageTable()); + Process->PageMap = MiCreatePageMapping(); + + if (Process->PageMap == 0) + return STATUS_INSUFFICIENT_MEMORY; InitializeListHead(&Process->ThreadList); @@ -61,6 +64,7 @@ void KeInitializeProcess(PKPROCESS Process, int BasePriority, KAFFINITY BaseAffi Process->DefaultAffinity = BaseAffinity; Process->PebPointer = NULL; + return STATUS_SUCCESS; } PKPROCESS KeSetAttachedProcess(PKPROCESS Process) diff --git a/boron/source/ke/sched.c b/boron/source/ke/sched.c index 39a1c87e..3b245186 100644 --- a/boron/source/ke/sched.c +++ b/boron/source/ke/sched.c @@ -715,16 +715,21 @@ void KiSwitchToNextThread() uintptr_t StackBottom = (uintptr_t) Thread->Stack.Top + Thread->Stack.Size; KeGetCurrentPRCB()->SysCallStack = StackBottom; -#ifdef TARGET_AMD64 +#if defined TARGET_AMD64 || defined TARGET_I386 + // When an interrupt or exception happens and the CPL is ring 3, // this is fetched for a transition to ring 0. +#if defined TARGET_AMD64 KeGetCurrentPRCB()->ArchData.Tss.RSP[0] = StackBottom; +#elif defined TARGET_I386 + KeGetCurrentPRCB()->ArchData.Tss.Esp0 = StackBottom; +#endif // Set the relevant MSRs. - KeSetMSR(MSR_GS_BASE_KERNEL, (uint64_t) Thread->Process->PebPointer); - KeSetMSR(MSR_FS_BASE, (uint64_t) Thread->TebPointer); + KeSetMSR(MSR_GS_BASE_KERNEL, (uintptr_t) Thread->Process->PebPointer); + KeSetMSR(MSR_FS_BASE, (uintptr_t) Thread->TebPointer); #endif - + if (OldThread == Thread) { // The old thread is the same as the new thread, there's no need to @@ -766,6 +771,9 @@ void KiHandleQuantumEnd() void KeTimerTick() { + if (!KeGetCurrentThread()) + return; + // Check if the current thread's quantum has expired. If it has, set // the PENDING_YIELD pending event and issue a software interrupt. @@ -812,7 +820,7 @@ void KeReadyThread(PKTHREAD Thread) NO_RETURN void KeTerminateThread(KPRIORITY Increment) { - KIPL Ipl = KiLockDispatcher(); + UNUSED KIPL Ipl = KiLockDispatcher(); PKTHREAD Thread = KeGetCurrentThread(); @@ -828,7 +836,7 @@ NO_RETURN void KeTerminateThread(KPRIORITY Increment) // Unlock the dispatcher and request an end to current quantum. KiUnlockDispatcher(IPL_DPC); - KiHandleQuantumEnd(Ipl); + KiHandleQuantumEnd(); KeCrash("KeTerminateThread: After yielding, terminated thread was scheduled back in"); } diff --git a/boron/source/ke/smp.c b/boron/source/ke/smp.c index 1f748745..49389a0c 100644 --- a/boron/source/ke/smp.c +++ b/boron/source/ke/smp.c @@ -159,9 +159,20 @@ NO_RETURN void KeCrashBeforeSMPInit(const char* message, ...) void PsInitSystemProcess(); +#ifndef CONFIG_SMP +static KPRCB KiPrcb; +static PKPRCB KiPrcbList[1]; +#endif + NO_RETURN INIT void KeInitSMP() { +#ifdef CONFIG_SMP + #define UNI_OR_MULTI "Multi" +#else + #define UNI_OR_MULTI "Uni" +#endif + PLOADER_MP_INFO MpInfo = &KeLoaderParameterBlock.Multiprocessor; PLOADER_AP BspAp = NULL; @@ -173,12 +184,24 @@ void KeInitSMP() if (MpInfo->Count > ProcessorLimit) KeCrashBeforeSMPInit("Error, unsupported amount of CPUs: %llu (limit is %llu)", MpInfo->Count, ProcessorLimit); +#ifdef CONFIG_SMP int cpuListPFN = MmAllocatePhysicalPage(); if (cpuListPFN == PFN_INVALID) KeCrashBeforeSMPInit("Error, can't initialize CPU list, we don't have enough memory"); + // TODO: don't use MmGetHHDMOffsetAddr on 32-bit builds? KeProcessorList = MmGetHHDMOffsetAddr(MmPFNToPhysPage(cpuListPFN)); KeProcessorCount = MpInfo->Count; +#else + KeProcessorList = KiPrcbList; + KeProcessorCount = 1; + + if (MpInfo->Count > 1) + { + DbgPrint("%zu processors provided by the loader, but we only support one", MpInfo->Count); + MpInfo->Count = 1; + } +#endif // Initialize all the CPUs in series. for (uint64_t i = 0; i < MpInfo->Count; i++) @@ -193,7 +216,12 @@ void KeInitSMP() KeCrashBeforeSMPInit("Error, can't initialize CPUs, we don't have enough memory"); } + #ifdef CONFIG_SMP PKPRCB Prcb = MmGetHHDMOffsetAddr(MmPFNToPhysPage(PrcbPfn)); + #else + PKPRCB Prcb = &KiPrcb; + ASSERT(MpInfo->Count == 1); + #endif memset(Prcb, 0, sizeof *Prcb); // initialize the struct @@ -219,8 +247,8 @@ void KeInitSMP() PsInitSystemProcess(); int VersionNumber = KeGetVersionNumber(); - LogMsg("Boron (TM), October 2025 - v%d.%d.%d", VER_MAJOR(VersionNumber), VER_MINOR(VersionNumber), VER_BUILD(VersionNumber)); - LogMsg("%u System Processors [%u Kb System Memory] MultiProcessor Kernel", MpInfo->Count, MmTotalAvailablePages * PAGE_SIZE / 1024); + LogMsg("Boron (TM), October 2025 - v%d.%d.%d (%s)", VER_MAJOR(VersionNumber), VER_MINOR(VersionNumber), VER_BUILD(VersionNumber), BORON_TARGET); + LogMsg("%u System Processors [%u Kb System Memory] " UNI_OR_MULTI "Processor Kernel", MpInfo->Count, MmTotalAvailablePages * PAGE_SIZE / 1024); for (uint64_t i = 0; i < MpInfo->Count; i++) { diff --git a/boron/source/ke/tests.c b/boron/source/ke/tests.c index d59612b6..13f78a30 100644 --- a/boron/source/ke/tests.c +++ b/boron/source/ke/tests.c @@ -15,6 +15,9 @@ Module name: #include "../mm/mi.h" // horrible for now #include +// These tests are actually unused and they're only vestigial. - 14/10/2025 +#if 0 + static void KepTestAllMemory(void* Memory, size_t Size) { uint8_t* MemBytes = Memory; @@ -204,3 +207,5 @@ void KiPerformTests() KiPerformPageMapTest(); LogMsg("CPU %d finished all tests", KeGetCurrentPRCB()->LapicId); } + +#endif diff --git a/boron/source/ldr/dll.c b/boron/source/ldr/dll.c index 5c2773fd..5e48715a 100644 --- a/boron/source/ldr/dll.c +++ b/boron/source/ldr/dll.c @@ -56,8 +56,6 @@ static void LdriMapInProgramHeader(PLOADER_MODULE File, PELF_PROGRAM_HEADER Phdr Permissions |= MM_PTE_READWRITE; - HPAGEMAP PageMap = MiGetCurrentPageMap(); - // Now map it in. uintptr_t VirtAddrBackup = VirtAddr; for (size_t i = 0; i < SizePages; i++) @@ -70,7 +68,7 @@ static void LdriMapInProgramHeader(PLOADER_MODULE File, PELF_PROGRAM_HEADER Phdr // care of zero-filling everything. But not here. // Some entries overlap. Check if there's already a PTE beforehand. - PMMPTE Pte = MiGetPTEPointer(PageMap, VirtAddr, false); + PMMPTE Pte = MmGetPteLocationCheck(VirtAddr, false); if (Pte && (*Pte & MM_PTE_PRESENT)) { MMPTE OldPte = *Pte; @@ -97,8 +95,7 @@ static void LdriMapInProgramHeader(PLOADER_MODULE File, PELF_PROGRAM_HEADER Phdr uintptr_t Page = MmPFNToPhysPage(Pfn); - if (!MiMapPhysicalPage(PageMap, - Page, + if (!MiMapPhysicalPage(Page, VirtAddr, Permissions)) KeCrashBeforeSMPInit("Can't map in program header to virtual address %p!", VirtAddr); diff --git a/boron/source/ldr/initroot.c b/boron/source/ldr/initroot.c index a9c4d375..f6f31e27 100644 --- a/boron/source/ldr/initroot.c +++ b/boron/source/ldr/initroot.c @@ -82,7 +82,7 @@ POBJECT_DIRECTORY LdriCreateInitialDir() if (FAILED(Status)) { DbgPrint("Ldr: Failed to create /InitRoot directory (%d)", Status); - return false; + return NULL; } return RootDir; diff --git a/boron/source/ldr/loader.c b/boron/source/ldr/loader.c index dd079ef3..d4b27663 100644 --- a/boron/source/ldr/loader.c +++ b/boron/source/ldr/loader.c @@ -13,13 +13,21 @@ Module name: ***/ #include "ldri.h" -static uintptr_t LdrpCurrentBase = 0xFFFFF00000000000; - // TODO: Perhaps we could define it from the command line? Something like /HAL= #ifdef TARGET_AMD64 + +static uintptr_t LdrpCurrentBase = 0xFFFFF00000000000; static const char* LdrpHalPath = "halx86.sys"; + +#elif defined TARGET_I386 + +static uintptr_t LdrpCurrentBase = 0xD2000000; +static const char* LdrpHalPath = "hali386.sys"; // sorry bucko, halx86 is already taken + #else -#error Define your HAL path here. + +#error Define your loader base and HAL path here. + #endif INIT @@ -99,6 +107,7 @@ void LdrInit() INIT static void LdrpReclaimFile(PLOADER_MODULE File) { +#ifdef IS_64_BIT if ((uintptr_t)File->Address < (uintptr_t)MmGetHHDMBase() || (uintptr_t)File->Address >= MM_PFNDB_BASE) { @@ -112,6 +121,9 @@ static void LdrpReclaimFile(PLOADER_MODULE File) MmFreePhysicalPage(Pfn); Address += PAGE_SIZE; } +#else + (void) File; +#endif } // NOTE: For now, selectively reclaim certain pages. At some point, we'll reclaim everything, and scrap this function diff --git a/boron/source/mm/amd64/pt.c b/boron/source/mm/amd64/pt.c index 192af1bb..6253441a 100644 --- a/boron/source/mm/amd64/pt.c +++ b/boron/source/mm/amd64/pt.c @@ -15,11 +15,13 @@ Module name: #include #include -#include #include #include +#include "../mi.h" -#define MI_PTE_LOC(Address) (MI_PML1_LOCATION + ((Address & MI_PML_ADDRMASK) >> 12) * sizeof(MMPTE)) +// TODO: Rewrite this so that MmCheckPteLocation spawns the locations directly +// instead of relying on the ancient MiGetPTEPointer. +PMMPTE MiGetPTEPointer(HPAGEMAP Mapping, uintptr_t Address, bool AllocateMissingPMLs); PMMPTE MmGetPteLocation(uintptr_t Address) { @@ -78,9 +80,10 @@ PMMPTE MmGetPteLocationCheck(uintptr_t Address, bool GenerateMissingLevels) } // Creates a page mapping. -HPAGEMAP MiCreatePageMapping(HPAGEMAP OldPageMapping) +HPAGEMAP MiCreatePageMapping() { // Allocate the PML4. + HPAGEMAP OldPageMapping = KeGetCurrentPageTable(); int NewPageMappingPFN = MmAllocatePhysicalPage(); if (NewPageMappingPFN == PFN_INVALID) { @@ -168,7 +171,7 @@ bool MmpCloneUserHalfLevel(int Level, PMMPTE New, PMMPTE Old, int Index) // Clones a user page mapping HPAGEMAP MmClonePageMapping(HPAGEMAP OldPageMapping) { - HPAGEMAP NewPageMapping = MiCreatePageMapping(OldPageMapping); + HPAGEMAP NewPageMapping = MiCreatePageMapping(); // If the new page mapping is zero, we can't proceed! if (NewPageMapping == 0) @@ -268,7 +271,7 @@ PMMPTE MiGetPTEPointer(HPAGEMAP Mapping, uintptr_t Address, bool AllocateMissing MmFreePhysicalPage(PfnsAllocated[i]); } - return false; + return NULL; } PtesModified [NumPfnsAllocated] = EntryPointer; @@ -451,8 +454,9 @@ static bool MmpMapSingleAnonPageAtPte(PMMPTE Pte, uintptr_t Permissions, bool No Return value: Whether the mapping update was successful. ***/ -bool MiMapAnonPage(HPAGEMAP Mapping, uintptr_t Address, uintptr_t Permissions, bool NonPaged) +bool MiMapAnonPage(uintptr_t Address, uintptr_t Permissions, bool NonPaged) { + HPAGEMAP Mapping = MiGetCurrentPageMap(); PMMPTE Pte = MiGetPTEPointer(Mapping, Address, true); return MmpMapSingleAnonPageAtPte(Pte, Permissions, NonPaged); @@ -478,8 +482,9 @@ bool MiMapAnonPage(HPAGEMAP Mapping, uintptr_t Address, uintptr_t Permissions, b Return value: Whether the mapping update was successful. ***/ -bool MiMapPhysicalPage(HPAGEMAP Mapping, uintptr_t PhysicalPage, uintptr_t Address, uintptr_t Permissions) +bool MiMapPhysicalPage(uintptr_t PhysicalPage, uintptr_t Address, uintptr_t Permissions) { + HPAGEMAP Mapping = MiGetCurrentPageMap(); PMMPTE Pte = MiGetPTEPointer(Mapping, Address, true); if (!Pte) @@ -502,8 +507,10 @@ bool MiMapPhysicalPage(HPAGEMAP Mapping, uintptr_t PhysicalPage, uintptr_t Addre Return value: None. ***/ -void MiUnmapPages(HPAGEMAP Mapping, uintptr_t Address, size_t LengthPages) +void MiUnmapPages(uintptr_t Address, size_t LengthPages) { + HPAGEMAP Mapping = MiGetCurrentPageMap(); + // Step 1. Unset the PRESENT bit on all pages in the range. for (size_t i = 0; i < LengthPages; i++) { @@ -558,40 +565,6 @@ void MiUnmapPages(HPAGEMAP Mapping, uintptr_t Address, size_t LengthPages) } } -/*** - Function description: - Prepares the pool manager. Since this is hardware specific, - this was split away from the pool manager. - - Parameters: - The page map to be modified. Ideally, this function affects - ALL page maps. - - Return value: - None. - - Notes: - This function is run during uniprocessor system initialization. -***/ -void MiPrepareGlobalAreaForPool(HPAGEMAP PageMap) -{ - PMMPTE Ptes = MmGetHHDMOffsetAddr(PageMap); - - int Pfn = MmAllocatePhysicalPage(); - if (Pfn == PFN_INVALID) - { - KeCrashBeforeSMPInit("MiPrepareGlobalAreaForPool: Can't allocate global pml4"); - } - - Ptes[MI_GLOBAL_AREA_START] = - MM_PTE_PRESENT | - MM_PTE_READWRITE | - MM_PTE_GLOBAL | - MM_PTE_ISFROMPMM | - MM_PTE_NOEXEC | - MmPFNToPhysPage(Pfn); -} - /*** Function description: Gets the top of the pool managed area. This was split away @@ -639,8 +612,10 @@ uintptr_t MiGetTopOfPoolManagedArea() Return value: Whether the allocation was successful. ***/ -bool MiMapAnonPages(HPAGEMAP Mapping, uintptr_t Address, size_t SizePages, uintptr_t Permissions, bool NonPaged) +bool MiMapAnonPages(uintptr_t Address, size_t SizePages, uintptr_t Permissions, bool NonPaged) { + HPAGEMAP Mapping = MiGetCurrentPageMap(); + // As an optimization, we'll wait until the PML1 index rolls over to zero before reloading the PTE pointer. uint64_t CurrentPml1 = PML1_IDX(Address); size_t DonePages = 0; @@ -672,7 +647,7 @@ bool MiMapAnonPages(HPAGEMAP Mapping, uintptr_t Address, size_t SizePages, uintp ROLLBACK: // Unmap all the pages that we have mapped. - MiUnmapPages(Mapping, Address, DonePages); + MiUnmapPages(Address, DonePages); return false; } diff --git a/boron/source/mm/amd64/ptfree.c b/boron/source/mm/amd64/ptfree.c index 42f09247..f91b927e 100644 --- a/boron/source/mm/amd64/ptfree.c +++ b/boron/source/mm/amd64/ptfree.c @@ -84,7 +84,7 @@ void MiFreeUnusedMappingLevelsInCurrentMap(uintptr_t StartVa, size_t SizePages) continue; PMMPTE SubPte = MiGetSubPteAddress(Pte); - if (MmpIsPteListCompletelyEmpty(SubPte)) + if (MmpIsPteListCompletelyEmpty(SubPte) && ShouldUnmapPML4) { MmFreePhysicalPage((*Pte & MI_PML_ADDRMASK) >> 12); *Pte = 0; diff --git a/boron/source/mm/cache.c b/boron/source/mm/cache.c index 84824bd4..f8c3096e 100644 --- a/boron/source/mm/cache.c +++ b/boron/source/mm/cache.c @@ -14,11 +14,25 @@ Module name: ***/ #include "mi.h" +// TODO: There is probably going to be a lot of contention on the PFDB lock +// from here. Find a better way. + void MmInitializeCcb(PCCB Ccb) { memset(Ccb, 0, sizeof *Ccb); KeInitializeMutex(&Ccb->Mutex, MM_CCB_MUTEX_LEVEL); + + for (int i = 0; i < MM_DIRECT_PAGE_COUNT; i++) + Ccb->Direct[i] = PFN_INVALID; + + Ccb->Level1Indirect = PFN_INVALID; + Ccb->Level2Indirect = PFN_INVALID; + Ccb->Level3Indirect = PFN_INVALID; + Ccb->Level4Indirect = PFN_INVALID; +#if MM_INDIRECTION_LEVELS == 5 + Ccb->Level5Indirect = PFN_INVALID; +#endif } void MmTearDownCcb(PCCB Ccb) @@ -31,141 +45,276 @@ void MmTearDownCcb(PCCB Ccb) DbgPrint("TODO: MmTearDownCcb(%p)", Ccb); } -PCCB_ENTRY MmGetEntryPointerCcb(PCCB Ccb, uint64_t PageOffset, bool TryAllocateLowerLevels) +static MMPFN MiNextEntryCache(MMPFN PfnIndirection, uint64_t* PageOffset, bool AllocateIndirection) { - ASSERT(Ccb->Mutex.Header.Signaled > 0 && Ccb->Mutex.OwnerThread == KeGetCurrentThread()); + if (IS_BAD_PFN(PfnIndirection)) + return PFN_INVALID; - if (PageOffset < MM_DIRECT_PAGE_COUNT) - return &Ccb->Direct[PageOffset]; - PageOffset -= MM_DIRECT_PAGE_COUNT; + size_t InPageOffset = *PageOffset & (MM_INDIRECTION_COUNT - 1); + *PageOffset /= MM_INDIRECTION_COUNT; - size_t EntriesPerPage = ARRAY_COUNT(Ccb->Level1Indirect[0].Entries); - size_t Range = EntriesPerPage; + // This is slower than it probably should be. + // + // However, we have to guard against the PFN getting reclaimed + // from under our nose before we get to add our own reference to it. + MmBeginUsingHHDM(); + PMMPFN Indirection = MmGetHHDMOffsetAddrPfn(PfnIndirection); - // Level 1 - if (PageOffset < EntriesPerPage) + KIPL Ipl = MiLockPfdb(); + MMPFN Pfn = Indirection[InPageOffset]; + + if (Pfn == PFN_INVALID && AllocateIndirection) { - //DbgPrint("L1: OffsetL0=%04x", PageOffset); - if (!Ccb->Level1Indirect) + bool IsZeroed; + Pfn = MiAllocatePhysicalPageWithPfdbLocked(&IsZeroed); + if (Pfn == PFN_INVALID) { - if (!TryAllocateLowerLevels) - return NULL; - - if (!(Ccb->Level1Indirect = MmAllocatePhysicalPageHHDM())) - { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): could not allocate 1st level indirection table", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; - } + // still invalid, so no dice. + MiUnlockPfdb(Ipl); + MmEndUsingHHDM(); + return MM_PFN_OUTOFMEMORY; } - return &Ccb->Level1Indirect->Entries[PageOffset]; + // PFN is valid, so clear it + memset(MmGetHHDMOffsetAddrPfn(Pfn), 0xFF, PAGE_SIZE); + Indirection[InPageOffset] = Pfn; } - PageOffset -= EntriesPerPage; - Range *= EntriesPerPage; - // Level 2 - if (PageOffset < Range) + if (Pfn != PFN_INVALID) + MiPageAddReferenceWithPfdbLocked(Pfn); + + MiUnlockPfdb(Ipl); + MmEndUsingHHDM(); + return Pfn; +} + +// Input: PfnIndirection - the PFN of the indirection to store to, PageOffset - the offset inside this indirection +static BSTATUS MiAssignEntryToCache( + PMMPFN Pointer, + MMPFN Pfn, + bool IsHhdm, + PMM_PROTOTYPE_PTE_PTR OutPrototypePtePointer +) +{ + // If we're trying to set a PFN, and a PFN is already set: + if (!IS_BAD_PFN(*Pointer)) + return STATUS_CONFLICTING_ADDRESSES; + + // Assign the entry now. + if (!IS_BAD_PFN(Pfn)) { - uint64_t OffsetL1 = PageOffset % EntriesPerPage; - uint64_t OffsetL0 = PageOffset / EntriesPerPage; - //DbgPrint("L2: OffsetL0=%04x OffsetL1=%04x", OffsetL0, OffsetL1); + *Pointer = Pfn; + + MM_PROTOTYPE_PTE_PTR ProtoPtePtr; - if (!Ccb->Level2Indirect) - { - if (!TryAllocateLowerLevels) - return NULL; - - if (!(Ccb->Level2Indirect = MmAllocatePhysicalPageHHDM())) - { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): could not allocate 2nd level (root) indirection table", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; - } - } - if (!Ccb->Level2Indirect->Entries[OffsetL0].Indirection) - { - if (!TryAllocateLowerLevels) - return NULL; - - if (!(Ccb->Level2Indirect->Entries[OffsetL0].Indirection = MmAllocatePhysicalPageHHDM())) - { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): could not allocate 2nd level (L1) indirection table", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; - } - } + #ifdef IS_32_BIT + if (IsHhdm) + ProtoPtePtr = MmGetHHDMOffsetFromAddr(Pointer); + else + ProtoPtePtr = MM_VIRTUAL_PROTO_PTE_PTR(Pointer); + #else + (void) IsHhdm; + ProtoPtePtr = Pointer; + #endif + + MmSetPrototypePtePfn(Pfn, ProtoPtePtr); - return &Ccb->Level2Indirect->Entries[OffsetL0].Indirection->Entries[OffsetL1]; + if (OutPrototypePtePointer) + *OutPrototypePtePointer = ProtoPtePtr; } - PageOffset -= Range; - Range *= EntriesPerPage; - // Level 3 - if (PageOffset < Range) + return STATUS_SUCCESS; +} + +static BSTATUS MiAssignEntryToCacheIndirect( + MMPFN PfnIndirection, + uint64_t PageOffset, + MMPFN Pfn, + PMM_PROTOTYPE_PTE_PTR OutPrototypePtePointer +) +{ + MmBeginUsingHHDM(); + + PMMPFN Indirection = MmGetHHDMOffsetAddrPfn(PfnIndirection); + BSTATUS Result = MiAssignEntryToCache(Indirection + PageOffset, Pfn, false, OutPrototypePtePointer); + + MmEndUsingHHDM(); + return Result; +} + +static bool MmpEnsureIndirectionExists(PMMPFN Indirection, bool AllocateIndirection) +{ + if (!IS_BAD_PFN(*Indirection)) + return true; + + if (!AllocateIndirection) + return false; + + *Indirection = MmAllocatePhysicalPage(); + + MmBeginUsingHHDM(); + PMMPFN Memory = MmGetHHDMOffsetAddrPfn(*Indirection); + memset(Memory, 0xFF, PAGE_SIZE); + MmEndUsingHHDM(); + + return !IS_BAD_PFN(*Indirection); +} + +// Takes in the page offset as passed into MmGetEntryCcb and MmSetEntryCcb. +// However, this does nothing if PageOffset < MM_DIRECT_PAGE_COUNT. +static MMPFN MiWalkCacheUntilIndirection(PCCB Ccb, uint64_t* PageOffset, bool AllocateIndirection) +{ +#ifdef DEBUG + uint64_t OriginalPageOffset = *PageOffset; +#endif + const MMPFN Failure = AllocateIndirection ? MM_PFN_OUTOFMEMORY : PFN_INVALID; + + ASSERT(*PageOffset >= MM_DIRECT_PAGE_COUNT); + *PageOffset -= MM_DIRECT_PAGE_COUNT; + + // Level 1 + if (*PageOffset < MM_DIRECT_PAGE_COUNT) { - uint64_t OffsetL2 = PageOffset % EntriesPerPage; - uint64_t OffsetL1 = PageOffset / EntriesPerPage % EntriesPerPage; - uint64_t OffsetL0 = PageOffset / EntriesPerPage / EntriesPerPage; - //DbgPrint("L3: OffsetL0=%04x OffsetL1=%04x OffsetL2=%04x", OffsetL0, OffsetL1, OffsetL2); + if (!MmpEnsureIndirectionExists(&Ccb->Level1Indirect, AllocateIndirection)) + return Failure; - if (!Ccb->Level3Indirect) - { - if (!TryAllocateLowerLevels) - return NULL; - - if (!(Ccb->Level3Indirect = MmAllocatePhysicalPageHHDM())) - { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): could not allocate 3rd level (root) indirection table", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; - } - } - if (!Ccb->Level3Indirect->Entries[OffsetL0].Indirection) - { - if (!TryAllocateLowerLevels) - return NULL; - - if (!(Ccb->Level3Indirect->Entries[OffsetL0].Indirection = MmAllocatePhysicalPageHHDM())) - { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): could not allocate 3rd level (L1) indirection table", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; - } - } + // Well, this is trivial. + MmPageAddReference(Ccb->Level1Indirect); + return Ccb->Level1Indirect; + } + + // Level 2 + *PageOffset -= MM_DIRECT_PAGE_COUNT; + if (*PageOffset < MM_INDIRECTION_COUNT) + { + if (!MmpEnsureIndirectionExists(&Ccb->Level2Indirect, AllocateIndirection)) + return Failure; - if (!Ccb->Level3Indirect->Entries[OffsetL0].Indirection->Entries[OffsetL1].Indirection) - { - if (!TryAllocateLowerLevels) - return NULL; - - if (!(Ccb->Level3Indirect->Entries[OffsetL0].Indirection->Entries[OffsetL1].Indirection = MmAllocatePhysicalPageHHDM())) - { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): could not allocate 3rd level (L1) indirection table", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; - } - } + // This is also kind of trivial although less so. + return MiNextEntryCache(Ccb->Level2Indirect, PageOffset, AllocateIndirection); + } + + // Level 3 + *PageOffset -= MM_INDIRECTION_COUNT; + if (*PageOffset < MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT) + { + if (!MmpEnsureIndirectionExists(&Ccb->Level3Indirect, AllocateIndirection)) + return Failure; + + MMPFN Pfn3 = MiNextEntryCache(Ccb->Level3Indirect, PageOffset, AllocateIndirection); + if (IS_BAD_PFN(Pfn3)) + return Pfn3; - return &Ccb->Level3Indirect->Entries[OffsetL0].Indirection->Entries[OffsetL1].Indirection->Entries[OffsetL2]; + MMPFN Pfn2 = MiNextEntryCache(Pfn3, PageOffset, AllocateIndirection); + MmFreePhysicalPage(Pfn3); + return Pfn2; } - PageOffset -= Range; - Range *= EntriesPerPage; // Level 4 - if (PageOffset < Range) + *PageOffset -= MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT; + if (*PageOffset < MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT) { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): TODO: level 4", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; + if (!MmpEnsureIndirectionExists(&Ccb->Level4Indirect, AllocateIndirection)) + return Failure; + + MMPFN Pfn4 = MiNextEntryCache(Ccb->Level4Indirect, PageOffset, AllocateIndirection); + if (IS_BAD_PFN(Pfn4)) + return Pfn4; + + MMPFN Pfn3 = MiNextEntryCache(Pfn4, PageOffset, AllocateIndirection); + MmFreePhysicalPage(Pfn4); + if (IS_BAD_PFN(Pfn3)) + return Pfn3; + + MMPFN Pfn2 = MiNextEntryCache(Pfn3, PageOffset, AllocateIndirection); + MmFreePhysicalPage(Pfn3); + return Pfn2; } - PageOffset -= Range; - Range *= EntriesPerPage; #if MM_INDIRECTION_LEVELS == 5 // Level 5 - if (PageOffset < Range) + *PageOffset -= MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT; + if (*PageOffset < MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT * MM_INDIRECTION_COUNT) { - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): TODO: level 5", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; + if (!MmpEnsureIndirectionExists(&Ccb->Level5Indirect, AllocateIndirection)) + return Failure; + + MMPFN Pfn5 = MiNextEntryCache(Ccb->Level5Indirect, PageOffset, AllocateIndirection); + if (IS_BAD_PFN(Pfn5)) + return Pfn4; + + MMPFN Pfn4 = MiNextEntryCache(Pfn5, PageOffset, AllocateIndirection); + MmFreePhysicalPage(Pfn5); + if (IS_BAD_PFN(Pfn4)) + return Pfn4; + + MMPFN Pfn3 = MiNextEntryCache(Pfn4, PageOffset, AllocateIndirection); + MmFreePhysicalPage(Pfn4); + if (IS_BAD_PFN(Pfn3)) + return Pfn3; + + MMPFN Pfn2 = MiNextEntryCache(Pfn3, PageOffset, AllocateIndirection); + MmFreePhysicalPage(Pfn3); + return Pfn2; } - PageOffset -= Range; - Range *= EntriesPerPage; #endif + + DbgPrint("MiWalkCacheUntilIndirection: Page offset %zu is outside of the supported range.", OriginalPageOffset); + return PFN_INVALID; +} + +MMPFN MmGetEntryCcb(PCCB Ccb, uint64_t PageOffset) +{ + MMPFN Pfn = PFN_INVALID; + MmLockCcb(Ccb); + + if (PageOffset < MM_DIRECT_PAGE_COUNT) + { + KIPL Ipl = MiLockPfdb(); + Pfn = Ccb->Direct[PageOffset]; + + if (!IS_BAD_PFN(Pfn)) + MiPageAddReferenceWithPfdbLocked(Pfn); + + MiUnlockPfdb(Ipl); + goto Done; + } + + Pfn = MiWalkCacheUntilIndirection(Ccb, &PageOffset, false); + if (!IS_BAD_PFN(Pfn)) + { + MMPFN Pfn2 = Pfn; + Pfn = MiNextEntryCache(Pfn2, &PageOffset, false); + MmFreePhysicalPage(Pfn2); + } + + if (IS_BAD_PFN(Pfn)) + Pfn = PFN_INVALID; + +Done: + MmUnlockCcb(Ccb); + return Pfn; +} + +BSTATUS MmSetEntryCcb(PCCB Ccb, uint64_t PageOffset, MMPFN InPfn, PMM_PROTOTYPE_PTE_PTR OutPrototypePtePointer) +{ + BSTATUS Status = STATUS_INSUFFICIENT_MEMORY; + MmLockCcb(Ccb); + + if (PageOffset < MM_DIRECT_PAGE_COUNT) + { + Status = MiAssignEntryToCache(&Ccb->Direct[PageOffset], InPfn, false, OutPrototypePtePointer); + goto Exit; + } + + MMPFN Pfn = MiWalkCacheUntilIndirection(Ccb, &PageOffset, true); + if (!IS_BAD_PFN(Pfn)) + { + Status = MiAssignEntryToCacheIndirect(Pfn, PageOffset, InPfn, OutPrototypePtePointer); + MmFreePhysicalPage(Pfn); + } - DbgPrint("MmGetEntryPointerCcb(%p, %llu, %d): no more levels to check", Ccb, PageOffset, TryAllocateLowerLevels); - return NULL; +Exit: + MmUnlockCcb(Ccb); + return Status; } diff --git a/boron/source/mm/fault.c b/boron/source/mm/fault.c index 352dc9ee..9f744781 100644 --- a/boron/source/mm/fault.c +++ b/boron/source/mm/fault.c @@ -64,6 +64,30 @@ static BSTATUS MmpHandleFaultCommittedPage(PMMPTE PtePtr, MMPTE SupervisorBit) return STATUS_SUCCESS; } +static BSTATUS MmpAssignPfnToAddress(uintptr_t Va, MMPFN Pfn, bool SetCacheDetails, uint32_t VadFlagsLong, PFILE_OBJECT FileObject, uint64_t FileOffset) +{ + MMPTE SupervisorBit = Va >= MM_KERNEL_SPACE_BASE ? 0 : MM_PTE_USERACCESS; + + PMMPTE PtePtr = MmGetPteLocationCheck(Va, true); + if (!PtePtr) + return STATUS_INSUFFICIENT_MEMORY; + + MMPTE NewPte = MM_PTE_PRESENT | MM_PTE_ISFROMPMM | SupervisorBit | MmPFNToPhysPage(Pfn); + + MMVAD_FLAGS VadFlags; + VadFlags.LongFlags = VadFlagsLong; + + if (VadFlags.Cow) + NewPte |= MM_PTE_COW; + + *PtePtr = NewPte; + + if (SetCacheDetails) + MmSetCacheDetailsPfn(Pfn, FileObject->Fcb, FileOffset); + + return STATUS_SUCCESS; +} + static BSTATUS MmpHandleFaultCommittedMappedPage( uintptr_t Va, uintptr_t VaBase, @@ -93,12 +117,6 @@ static BSTATUS MmpHandleFaultCommittedMappedPage( // Now, this is obviously just user error, but can this be exploited? // Probably not. When the page fault finishes, BSTATUS Status; - PMMPTE PtePtr = NULL; - - MMPTE SupervisorBit = Va >= MM_KERNEL_SPACE_BASE ? 0 : MM_PTE_USERACCESS; - - MMVAD_FLAGS VadFlags; - VadFlags.LongFlags = VadFlagsLong; const uintptr_t PageMask = ~(PAGE_SIZE - 1); uint64_t FileOffset = (Va & PageMask) - VaBase + MappedOffset; @@ -114,7 +132,6 @@ static BSTATUS MmpHandleFaultCommittedMappedPage( { MMPFN Pfn = PFN_INVALID; PFILE_OBJECT FileObject = (PFILE_OBJECT) Object; - PCCB_ENTRY PCcbEntry = NULL; // First, check if the BackingMemory call is supported. IO_STATUS_BLOCK Iosb; @@ -164,66 +181,24 @@ static BSTATUS MmpHandleFaultCommittedMappedPage( } else { - MmLockCcb(PageCache); - - PCcbEntry = MmGetEntryPointerCcb(PageCache, FileOffset / PAGE_SIZE, false); - if (PCcbEntry && AtLoad(PCcbEntry->Long)) - { - // Lock the physical memory lock because the page we're looking for - // might have been freed. - // - // This is, as far as I know, the only way. Sure, if the CCB entry - // doesn't exist and it is zero, then you are able to skip over the - // PFN lock at no extra cost (you'd just be reloading the same page - // twice potentially) - KIPL Ipl = MiLockPfdb(); - - // After this, the CCB entry cannot be changed. This is because - // when a page is reclaimed the PFDB lock is held. - CCB_ENTRY CcbEntry; - if (PCcbEntry) - CcbEntry = *PCcbEntry; - - if (CcbEntry.Long) - { - // We managed to find the PFN in one piece. Add a reference to it as we - // will just map it directly. - Pfn = CcbEntry.Pfn; - MiPageAddReferenceWithPfdbLocked(Pfn); - } - - MiUnlockPfdb(Ipl); - } - - MmUnlockCcb(PageCache); + Pfn = MmGetEntryCcb(PageCache, FileOffset / PAGE_SIZE); } // Did we find the PFN already? if (Pfn != PFN_INVALID) { - // As it turns out, yes! - PtePtr = MmGetPteLocationCheck(Va, true); - if (!PtePtr) + Status = MmpAssignPfnToAddress(Va, Pfn, BackingMemory == NULL, VadFlagsLong, FileObject, FileOffset); + if (FAILED(Status)) { + MmFreePhysicalPage(Pfn); + DbgPrint("%s: out of memory because PTE couldn't be allocated (1)", __func__); + ASSERT(Status == STATUS_INSUFFICIENT_MEMORY); Status = STATUS_REFAULT_SLEEP; goto Exit; } - MMPTE NewPte = MM_PTE_PRESENT | MM_PTE_ISFROMPMM | SupervisorBit | MmPFNToPhysPage(Pfn); - - if (VadFlags.Cow) - NewPte |= MM_PTE_COW; - - *PtePtr = NewPte; - - if (!BackingMemory) - { - MmSetCacheDetailsPfn(Pfn, &PCcbEntry->Long, FileObject->Fcb, FileOffset); - } - - PFDbgPrint("%s: hooray! page fault fulfilled by cached fetch %p", __func__, PtePtr); - Status = STATUS_SUCCESS; + PFDbgPrint("%s: hooray! page fault fulfilled by cached fetch %p", __func__, Va); goto Exit; } @@ -235,11 +210,8 @@ static BSTATUS MmpHandleFaultCommittedMappedPage( ASSERT(!BackingMemory); // First, ensure the CCB entry can be there. - MmLockCcb(PageCache); - PCcbEntry = MmGetEntryPointerCcb(PageCache, FileOffset / PAGE_SIZE, true); - MmUnlockCcb(PageCache); - - if (!PCcbEntry) + int SetResult = MmSetEntryCcb(PageCache, FileOffset / PAGE_SIZE, PFN_INVALID, NULL); + if (SetResult == STATUS_INSUFFICIENT_MEMORY) { // Out of memory. DbgPrint("%s: out of memory because CCB entry couldn't be allocated (1)", __func__); @@ -247,13 +219,20 @@ static BSTATUS MmpHandleFaultCommittedMappedPage( goto Exit; } + if (SetResult == STATUS_CONFLICTING_ADDRESSES) + { + // Already assigned. + DbgPrint("%s: CCB entry was found to be assigned, refaulting", __func__); + Status = STATUS_REFAULT; + goto Exit; + } + // Allocate a PFN now, do a read, and then put it into the CCB. Pfn = MmAllocatePhysicalPage(); if (Pfn == PFN_INVALID) { // Out of memory. DbgPrint("%s: out of memory because a page frame couldn't be allocated", __func__); - MmUnlockCcb(PageCache); Status = STATUS_REFAULT_SLEEP; goto Exit; } @@ -276,7 +255,6 @@ static BSTATUS MmpHandleFaultCommittedMappedPage( // Ran out of memory trying to read this file. DbgPrint("%s: cannot answer page fault because I/O failed with code %d", __func__, Status); MmFreePhysicalPage(Pfn); - MmUnlockCcb(PageCache); if (Status == STATUS_INSUFFICIENT_MEMORY) { @@ -288,87 +266,49 @@ static BSTATUS MmpHandleFaultCommittedMappedPage( } // The I/O operation has succeeded. - // Prepare the CCB entry. - CCB_ENTRY CcbEntryNew; - CcbEntryNew.Long = 0; - CcbEntryNew.Pfn = Pfn; - // It's time to put this in the CCB and then map. - MmLockCcb(PageCache); - PCcbEntry = MmGetEntryPointerCcb(PageCache, FileOffset / PAGE_SIZE, true); - if (!PCcbEntry) - { - // Out of memory. Did someone remove our prior allocation? - // Well, we've got almost no choice but to throw away our result. - // - // (We could instead beg the memory manager for more memory, but - // I don't feel like writing that right now.) - DbgPrint("%s: out of memory because CCB entry couldn't be allocated (2)", __func__); - MmFreePhysicalPage(Pfn); - MmUnlockCcb(PageCache); - Status = STATUS_REFAULT_SLEEP; - goto Exit; - } - - // There you are! - // - // If the CCB entry is zero, then it'll be populated with the - // new CCB entry. Otherwise, there's something already there - // and we have simply wasted our time and we need to refault. - uint64_t Expected = 0; - if (AtCompareExchange(&PCcbEntry->Long, &Expected, CcbEntryNew.Long)) + Status = MmSetEntryCcb(PageCache, FileOffset / PAGE_SIZE, Pfn, NULL); + if (FAILED(Status)) { - // Compare-exchange successful. Note: we are surrendering - // our reference of the physical page to the CCB. - MmUnlockCcb(PageCache); - - // Acquire the address space lock now. - SpaceUnlockIpl = MmLockSpaceExclusive(Va); - - // We need to refetch the PTE pointer because it may have been - // freed in the meantime. - PtePtr = MmGetPteLocationCheck(Va, true); - if (!PtePtr) + if (Status == STATUS_CONFLICTING_ADDRESSES) { - DbgPrint("%s: out of memory because PTE couldn't be allocated (2)", __func__); - MmFreePhysicalPage(Pfn); - MmUnlockCcb(PageCache); - Status = STATUS_REFAULT_SLEEP; - goto Exit; + // Need to throw away our hard work as someone finished it before us. + DbgPrint("%s: CCB entry was found to be assigned by the time IO was made (%p), refaulting (2)", __func__, Va); + Status = STATUS_REFAULT; } - - MMPTE NewPte = *PtePtr; - - // Are you already present?! - if (NewPte & MM_PTE_PRESENT) + else { - // Yeah, you seem to already be present. So we just - // wasted all of our time. - DbgPrint("%s: va already valid by the time IO was made (%p)", __func__, Va); - MmFreePhysicalPage(Pfn); - Status = STATUS_SUCCESS; - goto Exit; + // Out of memory. Did someone remove our prior allocation? + // Well, we've got almost no choice but to throw away our result. + // + // (We could instead beg the memory manager for more memory, but + // I don't feel like writing that right now.) + ASSERT(Status == STATUS_INSUFFICIENT_MEMORY); + DbgPrint("%s: out of memory because CCB entry couldn't be allocated (2)", __func__); + Status = STATUS_REFAULT_SLEEP; } - NewPte = MM_PTE_PRESENT | MM_PTE_ISFROMPMM | SupervisorBit | MmPFNToPhysPage(Pfn); - - if (VadFlags.Cow) - NewPte |= MM_PTE_COW; - - MmSetCacheDetailsPfn(Pfn, &PCcbEntry->Long, FileObject->Fcb, FileOffset); - - *PtePtr = NewPte; - - PFDbgPrint("%s: hooray! page fault fulfilled by I/O read", __func__); - Status = STATUS_SUCCESS; + MmFreePhysicalPage(Pfn); goto Exit; } - // Something's already there! - // It's most likely a good page, so let's just refault. - DbgPrint("%s: ccb already found by the time IO was made (%p), refaulting", __func__, Va); - Status = STATUS_REFAULT; - MmUnlockCcb(PageCache); + // Success! This is the right PFN, so assign it. + + // Acquire the address space lock now. + SpaceUnlockIpl = MmLockSpaceExclusive(Va); + + Status = MmpAssignPfnToAddress(Va, Pfn, true, VadFlagsLong, FileObject, FileOffset); + if (FAILED(Status)) + { + DbgPrint("%s: out of memory because PTE couldn't be allocated (2)", __func__); + MmFreePhysicalPage(Pfn); + Status = STATUS_REFAULT_SLEEP; + goto Exit; + } + + PFDbgPrint("%s: hooray! page fault fulfilled by I/O read", __func__); + Status = STATUS_SUCCESS; + goto Exit; } Exit: @@ -538,8 +478,10 @@ BSTATUS MiWriteFault(UNUSED PEPROCESS Process, uintptr_t Va, PMMPTE PtePtr) return STATUS_REFAULT_SLEEP; } + MmBeginUsingHHDM(); void* Address = MmGetHHDMOffsetAddr(MmPFNToPhysPage(NewPfn)); memcpy(Address, (void*)(Va & ~(PAGE_SIZE - 1)), PAGE_SIZE); + MmEndUsingHHDM(); // Now assign the new PFN. *PtePtr = @@ -676,6 +618,7 @@ BSTATUS MmPageFault(UNUSED uintptr_t FaultPC, uintptr_t FaultAddress, uintptr_t BSTATUS Status = KeWaitForSingleObject(&Timer, true, TIMEOUT_INFINITE, KeGetPreviousMode()); if (FAILED(Status)) { + DbgPrint("MmPageFault: Failed to sleep?! %s (%d)", RtlGetStatusString(Status), Status); KeCancelTimer(&Timer); } else diff --git a/boron/source/mm/heap.c b/boron/source/mm/heap.c index b43d29a3..56b80c11 100644 --- a/boron/source/mm/heap.c +++ b/boron/source/mm/heap.c @@ -310,7 +310,11 @@ void MmDebugDumpHeap() PRBTREE_ENTRY Entry = GetFirstEntryRbTree(&Heap->Tree); +#ifdef IS_64_BIT DbgPrint("HeapPtr Start End"); +#else + DbgPrint("HeapPtr Start End"); +#endif if (!Entry) DbgPrint("There are no heap entries."); diff --git a/boron/source/mm/i386/idmap.c b/boron/source/mm/i386/idmap.c new file mode 100644 index 00000000..359c4bab --- /dev/null +++ b/boron/source/mm/i386/idmap.c @@ -0,0 +1,61 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + mm/i386/idmap.c + +Abstract: + This module implements identity mapping management for + the i386 platform. + +Author: + iProgramInCpp - 15 October 2025 +***/ +#include "../mi.h" + +#define P2V(n) ((void*)(MI_IDENTMAP_START + (n)) +#define V2P(p) ((uintptr_t)(p) - MI_IDENTMAP_START) + +extern uint8_t KiBootstrapPageTables[]; +extern uint8_t KiHhdmWindowPageTables[]; +extern uint8_t KiPoolHeadersPageTables[]; + +void MiInitializeBaseIdentityMapping() +{ + for (size_t i = 0; i < MI_IDENTMAP_SIZE; i += PAGE_SIZE) + { + uintptr_t Address = MI_IDENTMAP_START + i; + + PMMPTE Level2 = (PMMPTE) MI_PTE_LOC(MI_PTE_LOC(Address)); + PMMPTE Level1 = (PMMPTE) MI_PTE_LOC(Address); + + *Level2 = V2P(&KiBootstrapPageTables[(i >> 22) * PAGE_SIZE]) + | MM_PTE_READWRITE + | MM_PTE_PRESENT; + + *Level1 = i | MM_PTE_READWRITE | MM_PTE_PRESENT; + } + + // Map the level 2 PTs for the HHDM window. + for (size_t i = 0; i < MI_FASTMAP_SIZE; i += PAGE_SIZE * PAGE_SIZE / sizeof(MMPTE)) + { + uintptr_t Address = MI_FASTMAP_START + i; + + PMMPTE Level2 = (PMMPTE) MI_PTE_LOC(MI_PTE_LOC(Address)); + *Level2 = V2P(&KiHhdmWindowPageTables[(i >> 22) * PAGE_SIZE]) + | MM_PTE_READWRITE + | MM_PTE_PRESENT; + } + + // Map the level 2 PTs for the pool headers. + for (size_t i = 0; i < MI_POOL_HEADERS_SIZE; i += PAGE_SIZE * PAGE_SIZE / sizeof(MMPTE)) + { + uintptr_t Address = MI_POOL_HEADERS_START + i; + + PMMPTE Level2 = (PMMPTE) MI_PTE_LOC(MI_PTE_LOC(Address)); + *Level2 = V2P(&KiPoolHeadersPageTables[(i >> 22) * PAGE_SIZE]) + | MM_PTE_READWRITE + | MM_PTE_PRESENT; + } +} diff --git a/boron/source/mm/i386/pt.c b/boron/source/mm/i386/pt.c new file mode 100644 index 00000000..3768d601 --- /dev/null +++ b/boron/source/mm/i386/pt.c @@ -0,0 +1,279 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + mm/i386/pt.c + +Abstract: + This module implements page table management for + the i386 platform. + +Author: + iProgramInCpp - 15 October 2025 +***/ + +#include +#include +#include +#include +#include "../mi.h" + +PMMPTE MmGetPteLocation(uintptr_t Address) +{ + PMMPTE PtePtr = (PMMPTE)MI_PTE_LOC(Address); + + // HACK: Instead of just invalidating everything in the function + // MiFreeUnusedMappingLevelsInCurrentMap like I am supposed to, + // I will invalidate the TLB here. + // + // I know this is bad, but come on, when are we *ever* going to + // *not* go through this function? + KeInvalidatePage(PtePtr); + + return PtePtr; +} + +bool MmCheckPteLocation(uintptr_t Address, bool GenerateMissingLevels) +{ + PMMPTE Pte; + MMPTE SupervisorBit; + + ASSERT(Address < MI_PML1_LOCATION || (uint64_t)Address >= MI_PML1_LOC_END); + + if (Address >= MM_KERNEL_SPACE_BASE) + SupervisorBit = 0; + else + SupervisorBit = MM_PTE_USERACCESS; + + // Check the presence of the PT + Pte = MmGetPteLocation(MI_PTE_LOC(Address)); + if (~(*Pte) & MM_PTE_PRESENT) + { + if (!GenerateMissingLevels) + return false; + + MMPFN PtAllocated = MmAllocatePhysicalPage(); + if (PtAllocated == PFN_INVALID) + return false; + + *Pte = MmPFNToPhysPage(PtAllocated) | MM_PTE_PRESENT | MM_PTE_READWRITE | SupervisorBit; + } + + // Page table exists. + return true; +} + +PMMPTE MmGetPteLocationCheck(uintptr_t Address, bool GenerateMissingLevels) +{ + if (!MmCheckPteLocation(Address, GenerateMissingLevels)) + return NULL; + + return MmGetPteLocation(Address); +} + +// Creates a page mapping. +HPAGEMAP MiCreatePageMapping() +{ + // Allocate the PML2. + int NewPageMappingPFN = MmAllocatePhysicalPage(); + if (NewPageMappingPFN == PFN_INVALID) + { + LogMsg("Error, can't create a new page mapping. Can't allocate PML4"); + return 0; + } + + uintptr_t NewPageMappingResult = MmPFNToPhysPage (NewPageMappingPFN); + + // Lock the kernel space's lock to not get any surprises. + MmLockKernelSpaceShared(); + MmBeginUsingHHDM(); + + PMMPTE NewPageMappingAccess = MmGetHHDMOffsetAddr (NewPageMappingResult); + PMMPTE OldPageDirectory = (PMMPTE) MI_PML2_LOCATION; + + // zero out the first 512 + for (int i = 0; i < 512; i++) + NewPageMappingAccess[i] = 0; + + // then copy out the kernel's latter 512 entries + for (int i = 512; i < 1024; i++) + NewPageMappingAccess[i] = OldPageDirectory[i]; + + // and replace that last entry with the pointer to this one. + NewPageMappingAccess[MI_RECURSIVE_PAGING_START] = (uintptr_t)NewPageMappingResult | MM_PTE_PRESENT | MM_PTE_READWRITE | MM_PTE_NOEXEC; + + MmEndUsingHHDM(); + MmUnlockKernelSpace(); + return (HPAGEMAP) NewPageMappingResult; +} + +static void MmpFreeVacantPageTables(uintptr_t Address) +{ + if (!MmCheckPteLocation(Address, false)) + // already freed + return; + + // check if the page table this address' PTE is in is vacant + PMMPTE PtePT = MmGetPteLocation(Address); + PtePT = (PMMPTE)((uintptr_t)PtePT & ~(PAGE_SIZE - 1)); + for (int i = 0; i < 1024; i++) + { + if (PtePT[i] != 0) + // isn't vacant + return; + } + + PMMPTE PtePD = MmGetPteLocation(MI_PTE_LOC(Address)); + MMPFN Pfn = MmPhysPageToPFN(*PtePD & MM_PTE_ADDRESSMASK); + *PtePD = 0; + MmFreePhysicalPage(Pfn); +} + +static bool MmpMapSingleAnonPageAtPte(PMMPTE Pte, uintptr_t Permissions, bool NonPaged) +{ + if (!Pte) + return false; + + if (MM_DBG_NO_DEMAND_PAGING || NonPaged) + { + int pfn = MmAllocatePhysicalPage(); + if (pfn == PFN_INVALID) + { + //DbgPrint("MiMapAnonPage(%p, %p) failed because we couldn't allocate physical memory", Mapping, Address); + return false; + } + + if (!Pte) + { + //DbgPrint("MiMapAnonPage(%p, %p) failed because PTE couldn't be retrieved", Mapping, Address); + return false; + } + + *Pte = MM_PTE_PRESENT | MM_PTE_ISFROMPMM | Permissions | MmPFNToPhysPage(pfn); + return true; + } + + *Pte = MM_DPTE_COMMITTED | Permissions; + return true; +} + +bool MiMapAnonPage(uintptr_t Address, uintptr_t Permissions, bool NonPaged) +{ + PMMPTE Pte = MmGetPteLocationCheck(Address, true); + return MmpMapSingleAnonPageAtPte(Pte, Permissions, NonPaged); +} + +bool MiMapPhysicalPage(uintptr_t PhysicalPage, uintptr_t Address, uintptr_t Permissions) +{ + PMMPTE Pte = MmGetPteLocationCheck(Address, true); + if (!Pte) + return false; + + *Pte = (PhysicalPage & MM_PTE_ADDRESSMASK) | Permissions | MM_PTE_PRESENT; + return true; +} + +void MiUnmapPages(uintptr_t Address, size_t LengthPages) +{ + // Step 1. Unset the PRESENT bit on all pages in the range. + for (size_t i = 0; i < LengthPages; i++) + { + PMMPTE pPTE = MmGetPteLocationCheck(Address + i * PAGE_SIZE, false); + if (!pPTE) + continue; + + *pPTE &= ~MM_DPTE_COMMITTED; + + if (*pPTE & MM_PTE_PRESENT) + { + *pPTE &= ~MM_PTE_PRESENT; + *pPTE |= MM_DPTE_WASPRESENT; + } + else + { + *pPTE &= ~MM_DPTE_WASPRESENT; + } + } + + // Step 2. Issue a single TLB shootdown command to all CPUs to flush the TLB. + // TODO: This could be optimized, but eh, it's fine for now.. + MmIssueTLBShootDown(Address, LengthPages); + + // Step 3. If needed, free the PMM pages related to this page mapping. + for (size_t i = 0; i < LengthPages; i++) + { + PMMPTE pPTE = MmGetPteLocationCheck(Address + i * PAGE_SIZE, false); + if (!pPTE) + continue; + + uintptr_t Flags = MM_DPTE_WASPRESENT | MM_PTE_ISFROMPMM; + + if ((*pPTE & Flags) == Flags) + { + uintptr_t PhysPage = *pPTE & MM_PTE_ADDRESSMASK; + MmFreePhysicalPage(MmPhysPageToPFN(PhysPage)); + *pPTE = 0; + } + } +} + +uintptr_t MiGetTopOfPoolManagedArea() +{ + return MI_GLOBAL_AREA_START << 22; +} + +uintptr_t MiGetTopOfSecondPoolManagedArea() +{ + return MI_GLOBAL_AREA_START_2ND << 22; +} + +bool MiMapAnonPages(uintptr_t Address, size_t SizePages, uintptr_t Permissions, bool NonPaged) +{ + // As an optimization, we'll wait until the PML1 index rolls over to zero before reloading the PTE pointer. + uint64_t CurrentPml1 = PML1_IDX(Address); + size_t DonePages = 0; + + PMMPTE PtePtr = MmGetPteLocationCheck(Address, true); + + for (size_t i = 0; i < SizePages; i++) + { + // If one of these fails, then we should roll back. + if (!MmpMapSingleAnonPageAtPte(PtePtr, Permissions, NonPaged)) + goto ROLLBACK; + + // Increase the address size, get the next PTE pointer, update the current PML1, and + // increment the number of mapped pages (since this one was successfully mapped). + Address += PAGE_SIZE; + PtePtr++; + CurrentPml1++; + DonePages++; + + if (CurrentPml1 % (PAGE_SIZE / sizeof(MMPTE)) == 0) + { + // We have rolled over. + PtePtr = MmGetPteLocationCheck(Address, true); + } + } + + // All allocations have succeeded! Let the caller know and don't undo our work. :) + return true; + +ROLLBACK: + // Unmap all the pages that we have mapped. + MiUnmapPages(Address, DonePages); + return false; +} + +MMPTE MmGetPteBitsFromProtection(int Protection) +{ + MMPTE Pte = 0; + + if (Protection & PAGE_WRITE) + Pte |= MM_PTE_READWRITE; + + if (~Protection & PAGE_EXECUTE) + Pte |= MM_PTE_NOEXEC; + + return Pte; +} diff --git a/boron/source/mm/i386/ptfree.c b/boron/source/mm/i386/ptfree.c new file mode 100644 index 00000000..9ea36888 --- /dev/null +++ b/boron/source/mm/i386/ptfree.c @@ -0,0 +1,79 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + mm/i386/ptfree.c + +Abstract: + This module implements the function that frees unused + page table mapping levels. + +Author: + iProgramInCpp - 14 October 2025 +***/ +#include "../mi.h" + +#define PTES_PER_LEVEL (PAGE_SIZE / sizeof(MMPTE)) // 1024 +#define PTES_COVERED_BY_ONE_PT (PTES_PER_LEVEL * PTES_PER_LEVEL) // 1048576 + +// Gets an address down the tree. +// +// For example, if you're in the PML2, this will give you +// the relevant PML1 address. +PMMPTE MiGetSubPteAddress(PMMPTE PteAddress) +{ + MMADDRESS_CONVERT Address; + Address.Long = (uintptr_t) PteAddress; + + Address.Level2Index = Address.Level1Index; + Address.Level1Index = Address.PageOffset / sizeof(PMMPTE); + Address.PageOffset = 0; + + return (PMMPTE) Address.Long; +} + +static bool MmpIsPteListCompletelyEmpty(PMMPTE Pte) +{ + bool AllZeroes = true; + + for (size_t PteIndex = 0; PteIndex < PTES_PER_LEVEL; PteIndex++) + { + if (Pte[PteIndex] != 0) + { + AllZeroes = false; + break; + } + } + + return AllZeroes; +} + +// NOTE: StartVa and SizePages are only roughly followed. +// +// NOTE: The address space lock of the process *must* be held. +void MiFreeUnusedMappingLevelsInCurrentMap(uintptr_t StartVa, size_t SizePages) +{ + if (StartVa >= MM_KERNEL_SPACE_BASE) + return; + + MMADDRESS_CONVERT Address; + PMMPTE Pte; + + Address.Long = StartVa; + Pte = (PMMPTE) MI_PML2_LOCATION + Address.Level2Index; + + // Scan each page table in the range. + for (size_t i = 0; i < SizePages; i += PTES_COVERED_BY_ONE_PT, ++Pte) + { + if (~(*Pte) & MM_PTE_PRESENT) + continue; + + PMMPTE SubPte = MiGetSubPteAddress(Pte); + if (MmpIsPteListCompletelyEmpty(SubPte)) + { + MmFreePhysicalPage((*Pte & MI_PML_ADDRMASK) >> 12); + *Pte = 0; + } + } +} diff --git a/boron/source/mm/mdl.c b/boron/source/mm/mdl.c index 99623171..9da4694d 100644 --- a/boron/source/mm/mdl.c +++ b/boron/source/mm/mdl.c @@ -21,7 +21,7 @@ void MmUnmapPagesMdl(PMDL Mdl) if (!Mdl->MappedStartVA) return; - MiUnmapPages(Mdl->Process->Pcb.PageMap, Mdl->MappedStartVA, Mdl->NumberPages); + MiUnmapPages(Mdl->MappedStartVA, Mdl->NumberPages); Mdl->Flags &= ~MDL_FLAG_MAPPED; } @@ -77,8 +77,6 @@ BSTATUS MmMapPinnedPagesMdl(PMDL Mdl, void** OutAddress) uintptr_t Address = MapAddress; size_t Index = 0; - HPAGEMAP PageMap = Mdl->Process->Pcb.PageMap; - MmLockKernelSpaceExclusive(); for (; Index < Mdl->NumberPages; Address += PAGE_SIZE, Index++) @@ -86,10 +84,10 @@ BSTATUS MmMapPinnedPagesMdl(PMDL Mdl, void** OutAddress) // Add a reference to the page. MmPageAddReference(Mdl->Pages[Index]); - if (!MiMapPhysicalPage(PageMap, Mdl->Pages[Index] * PAGE_SIZE, Address, Permissions)) + if (!MiMapPhysicalPage(Mdl->Pages[Index] * PAGE_SIZE, Address, Permissions)) { // Unmap everything mapped so far. - MiUnmapPages(PageMap, MapAddress, Index); + MiUnmapPages(MapAddress, Index); MmUnlockKernelSpace(); @@ -158,6 +156,7 @@ BSTATUS MmProbeAndPinPagesMdl(PMDL Mdl, KPROCESSOR_MODE AccessMode, bool IsWrite uintptr_t EndPage = (VirtualAddress + Mdl->ByteOffset + Size + 0xFFF) & ~0xFFF; BSTATUS FailureReason = STATUS_SUCCESS; + // TODO: Arbitrary size limitation that we should remove! if (Size >= MDL_MAX_SIZE) return STATUS_INVALID_PARAMETER; @@ -167,7 +166,9 @@ BSTATUS MmProbeAndPinPagesMdl(PMDL Mdl, KPROCESSOR_MODE AccessMode, bool IsWrite if (AccessMode == MODE_USER && MM_USER_SPACE_END < EndPage) return STATUS_INVALID_PARAMETER; - HPAGEMAP PageMap = Mdl->Process->Pcb.PageMap; + PEPROCESS Restore = NULL; + if (Mdl->Process != PsGetAttachedProcess()) + Restore = PsSetAttachedProcess(Mdl->Process); // Fault all the pages in. for (uintptr_t Address = StartPage; Address < EndPage; Address += PAGE_SIZE) @@ -182,7 +183,12 @@ BSTATUS MmProbeAndPinPagesMdl(PMDL Mdl, KPROCESSOR_MODE AccessMode, bool IsWrite } if (FailureReason) + { + if (Restore) + PsSetAttachedProcess(Restore); + return FailureReason; + } // TODO(WORKINGSET): Check if the working set (when we add it) can even fit all of these pages. // TODO(possibly related to above): Ensure proper failure if the whole buffer doesn't fit in system memory! @@ -193,7 +199,7 @@ BSTATUS MmProbeAndPinPagesMdl(PMDL Mdl, KPROCESSOR_MODE AccessMode, bool IsWrite while (true) { KIPL OldIpl = MmLockSpaceShared(Address); - PMMPTE PtePtr = MiGetPTEPointer(PageMap, Address, false); + PMMPTE PtePtr = MmGetPteLocationCheck(Address, false); bool TryFault = false; if (!PtePtr) @@ -285,6 +291,9 @@ BSTATUS MmProbeAndPinPagesMdl(PMDL Mdl, KPROCESSOR_MODE AccessMode, bool IsWrite else Mdl->Flags &= ~MDL_FLAG_WRITE; + if (Restore) + PsSetAttachedProcess(Restore); + if (FailureReason) { // Unpin only up to the current index, the rest weren't filled in due to the failure. @@ -339,6 +348,11 @@ void MmCopyIntoMdl(PMDL Mdl, uintptr_t Offset, const void* SourceBuffer, size_t // code below to pretend that the starting pointer is page aligned. Offset += Mdl->ByteOffset; +#ifdef IS_32_BIT + // TODO: Get rid of this entirely by re-engineering the HHDM system + char* Temporary = MmAllocatePool(POOL_NONPAGED, 4096); +#endif + while (Size) { size_t PageIndex = Offset / PAGE_SIZE; @@ -354,22 +368,43 @@ void MmCopyIntoMdl(PMDL Mdl, uintptr_t Offset, const void* SourceBuffer, size_t else CopyAmount = BytesTillNext; + // on 32-bit, MmBeginUsingHHDM and MmEndUsingHHDM raise and lower IPL, which + // means we CANNOT! take page faults. This is why I opted for this hack. +#ifdef IS_32_BIT + memcpy(Temporary, SourceBufferChr, CopyAmount); +#else + const char* Temporary = SourceBufferChr; +#endif + + MmBeginUsingHHDM(); char* PageDest = MmGetHHDMOffsetAddr(MmPFNToPhysPage(Mdl->Pages[PageIndex])); - memcpy(PageDest + PageOffs, SourceBufferChr, CopyAmount); + memcpy(PageDest + PageOffs, Temporary, CopyAmount); + MmEndUsingHHDM(); SourceBufferChr += CopyAmount; Offset += CopyAmount; Size -= CopyAmount; } + +#ifdef IS_32_BIT + MmFreePool(Temporary); +#endif } -void MmSetIntoMdl(PMDL Mdl, uintptr_t Offset, uint8_t ToSet, size_t Size) +void MmCopyFromMdl(PMDL Mdl, uintptr_t Offset, void* DestinationBuffer, size_t Size) { + char* DestBufferChr = (char*) DestinationBuffer; + // NOTE: The MDL's starting pointer isn't necessarily page aligned. // As such, push the offset forward by Mdl->ByteOffset to allow the // code below to pretend that the starting pointer is page aligned. Offset += Mdl->ByteOffset; +#ifdef IS_32_BIT + // TODO: Get rid of this entirely by re-engineering the HHDM system + char* Temporary = MmAllocatePool(POOL_NONPAGED, 4096); +#endif + while (Size) { size_t PageIndex = Offset / PAGE_SIZE; @@ -385,18 +420,33 @@ void MmSetIntoMdl(PMDL Mdl, uintptr_t Offset, uint8_t ToSet, size_t Size) else CopyAmount = BytesTillNext; + // on 32-bit, MmBeginUsingHHDM and MmEndUsingHHDM raise and lower IPL, which + // means we CANNOT! take page faults. This is why I opted for this hack. +#ifndef IS_32_BIT + char* Temporary = DestBufferChr; +#endif + + MmBeginUsingHHDM(); char* PageDest = MmGetHHDMOffsetAddr(MmPFNToPhysPage(Mdl->Pages[PageIndex])); - memset(PageDest + PageOffs, ToSet, CopyAmount); + memcpy(Temporary, PageDest + PageOffs, CopyAmount); + MmEndUsingHHDM(); +#ifdef IS_32_BIT + memcpy(DestBufferChr, Temporary, CopyAmount); +#endif + + DestBufferChr += CopyAmount; Offset += CopyAmount; Size -= CopyAmount; } + +#ifdef IS_32_BIT + MmFreePool(Temporary); +#endif } -void MmCopyFromMdl(PMDL Mdl, uintptr_t Offset, void* DestinationBuffer, size_t Size) +void MmSetIntoMdl(PMDL Mdl, uintptr_t Offset, uint8_t ToSet, size_t Size) { - char* DestBufferChr = (char*) DestinationBuffer; - // NOTE: The MDL's starting pointer isn't necessarily page aligned. // As such, push the offset forward by Mdl->ByteOffset to allow the // code below to pretend that the starting pointer is page aligned. @@ -417,10 +467,11 @@ void MmCopyFromMdl(PMDL Mdl, uintptr_t Offset, void* DestinationBuffer, size_t S else CopyAmount = BytesTillNext; + MmBeginUsingHHDM(); char* PageDest = MmGetHHDMOffsetAddr(MmPFNToPhysPage(Mdl->Pages[PageIndex])); - memcpy(DestBufferChr, PageDest + PageOffs, CopyAmount); + memset(PageDest + PageOffs, ToSet, CopyAmount); + MmEndUsingHHDM(); - DestBufferChr += CopyAmount; Offset += CopyAmount; Size -= CopyAmount; } diff --git a/boron/source/mm/mi.h b/boron/source/mm/mi.h index 67945c4b..773cdeef 100644 --- a/boron/source/mm/mi.h +++ b/boron/source/mm/mi.h @@ -35,6 +35,10 @@ KIPL MiLockPfdb(); // Unlocks the page frame database's spinlock. void MiUnlockPfdb(KIPL Ipl); +// This does the same as MmAllocatePhysicalPage, but expects the PFDB +// lock to be locked. +MMPFN MiAllocatePhysicalPageWithPfdbLocked(bool* IsZeroed); + // Gets the reference count of a page by PFN. // The PFN lock must be held. int MiGetReferenceCountPfn(MMPFN Pfn); @@ -67,7 +71,6 @@ struct MISLAB_CONTAINER_tag; #define MI_SLAB_ITEM_CHECK (0x424C5342) // "BSLB" -#define MI_MIN_SIZE_SLAB (8) #define MI_MAX_SIZE_SLAB (32768) typedef struct MISLAB_ITEM_tag @@ -80,17 +83,26 @@ typedef struct MISLAB_ITEM_tag // into the rest. union { - uint64_t Bitmap[4]; // Supports down to 16 byte sized items + uint64_t Bitmap[4]; // 32 bytes - Supports down to 16 byte sized items struct { - uint64_t Bitmap2[1]; - RBTREE_ENTRY TreeEntry; + #ifdef IS_64_BIT + uint64_t Bitmap2[1]; // 8 + RBTREE_ENTRY TreeEntry; // 24 + #else + uint64_t Bitmap2[2]; // 16 + RBTREE_ENTRY TreeEntry; // 12 + #endif }; }; LIST_ENTRY ListEntry; struct MISLAB_CONTAINER_tag *Parent; +#if IS_32_BIT + int Dummy; // alignment +#endif + char Data[0]; } MISLAB_ITEM, *PMISLAB_ITEM; @@ -164,29 +176,27 @@ HUGE_MEMORY_BLOCK, *PHUGE_MEMORY_BLOCK; // ===== Pool Allocator ===== -#ifdef TARGET_AMD64 - -// One PML4 entry can map up to 1<<39 (512GB) of memory. -// Thus, our pool will be 512 GB in size. -#define MI_POOL_LOG2_SIZE (39) - -#else - -#error "Define the pool size for your platform!" - -#endif - typedef struct MIPOOL_ENTRY_tag { - LIST_ENTRY ListEntry; // Qword 0, 1 - int Flags; // Qword 2 + LIST_ENTRY ListEntry; + int Flags; int Tag; - uintptr_t UserData; // Qword 3 - uintptr_t Address; // Qword 4 - size_t Size; // Qword 5, size is in pages. + uintptr_t UserData; + uintptr_t Address; + size_t Size; +#ifdef IS_32_BIT + int Dummy; + #define MIPOOL_DUMMY_SIGNATURE 0x12345678 +#endif } MIPOOL_ENTRY, *PMIPOOL_ENTRY; +#ifdef IS_64_BIT +static_assert(sizeof(MIPOOL_ENTRY) == 48); +#else +static_assert(sizeof(MIPOOL_ENTRY) == 32); +#endif + typedef enum MIPOOL_ENTRY_FLAGS_tag { MI_POOL_ENTRY_ALLOCATED = (1 << 0), @@ -234,10 +244,15 @@ MIPOOL_SPACE_HANDLE MiGetPoolSpaceHandleFromAddress(void* Address); void MiDumpPoolInfo(); // ===== Pool entry allocator ===== -// Really simple allocator that dishes out pool entries. To get rid of the pool allocator's dependency on the slab allocator. +// Really simple allocator that dishes out pool entries. To get rid of the pool allocator's +// dependency on the slab allocator. // The dependency chart will now look like this: // [PoolEntryAllocator] <----- [PoolAllocator] <----- [SlabAllocator] +#ifdef IS_32_BIT +#define MI_POOL_HEADERS_START (0xD1000000U) +#define MI_POOL_HEADERS_SIZE (0x00800000U) +#endif PMIPOOL_ENTRY MiCreatePoolEntry(); @@ -251,6 +266,11 @@ void MiPrepareGlobalAreaForPool(HPAGEMAP PageMap); // Get the top of the area managed by the pool allocator. uintptr_t MiGetTopOfPoolManagedArea(); +#ifdef TARGET_I386 +// Get the top of the second area managed by the pool allocator. +uintptr_t MiGetTopOfSecondPoolManagedArea(); +#endif + // Gets the PTE's location in the recursive PTE. PMMPTE MmGetPteLocation(uintptr_t Address); @@ -339,4 +359,15 @@ void MiReleaseVad(PMMVAD Vad); // uncommitted and certain code paths are skipped. void MiDecommitVad(PMMVAD_LIST VadList, PMMVAD Vad, uintptr_t StartVa, size_t SizePages); +// ===== Memory Initialization ===== +#ifdef IS_32_BIT +void MiInitializeBaseIdentityMapping(); +#endif + +// ===== Hardware Specific ===== + +#if defined TARGET_I386 || defined TARGET_AMD64 +#define MI_PTE_LOC(Address) (MI_PML1_LOCATION + (((Address) & MI_PML_ADDRMASK) >> 12) * sizeof(MMPTE)) +#endif + #endif//NS64_MI_H diff --git a/boron/source/mm/pmm.c b/boron/source/mm/pmm.c index 4a5f92ee..7139ab50 100644 --- a/boron/source/mm/pmm.c +++ b/boron/source/mm/pmm.c @@ -25,9 +25,29 @@ Module name: #define PmmDbgPrint(...) do {} while (0) #endif -//extern volatile struct limine_hhdm_request KeLimineHhdmRequest; -//extern volatile struct limine_memmap_request KeLimineMemMapRequest; +// LOCKING: +// +// You might think that I would need to establish a locking +// order between the PFN lock and the HHDM lock. But I won't. +// Why? Well, let's take a look at the two cases: +// +// 64-bit: The HHDM lock doesn't exist. Like, at all. So, +// MmBeginUsingHHDM and MmEndUsingHHDM are no-ops, and as +// such, both orderings work. +// +// 32-bit: The HHDM lock exists. However, since we're on a +// non-SMP system (assert that later), that means that +// regardless of what lock we grab first, the other is +// guaranteed to be unlocked. So there is no locking- +// inversion-caused deadlock. + +#if defined IS_32_BIT && defined CONFIG_SMP +#error You should fix this locking inversion bug! It could result in nasty deadlocks! +#endif +static KSPIN_LOCK MmPfnLock; + +// Free page statistics size_t MmTotalAvailablePages; size_t MmTotalFreePages; @@ -35,13 +55,16 @@ size_t MmTotalFreePages; size_t MmReclaimedPageCount; #endif -uintptr_t MmHHDMBase; - size_t MmGetTotalFreePages() { return MmTotalFreePages; } +// HHDM +#ifdef IS_64_BIT + +uintptr_t MmHHDMBase; + uint8_t* MmGetHHDMBase() { return (uint8_t*)MmHHDMBase; @@ -57,6 +80,88 @@ uintptr_t MmGetHHDMOffsetFromAddr(void* addr) return (uintptr_t) addr - (uintptr_t) MmGetHHDMBase(); } +#else // IS_64_BIT + +#ifdef CONFIG_SMP +#error TODO: Add spinlocks or per-core separation! +#endif + +uintptr_t MmHHDMWindowBase; +static KSPIN_LOCK MiHHDMWindowLock; +static KIPL MiHHDMWindowIpl; + +void MmBeginUsingHHDM() +{ + KIPL Ipl; + KeAcquireSpinLock(&MiHHDMWindowLock, &Ipl); + MiHHDMWindowIpl = Ipl; +} + +void MmEndUsingHHDM() +{ + ASSERT(MiHHDMWindowLock.Locked); + KeReleaseSpinLock(&MiHHDMWindowLock, MiHHDMWindowIpl); +} + +static void MiUpdateHHDMWindowBase(uintptr_t PhysAddr) +{ + ASSERT(MiHHDMWindowLock.Locked); + KIPL Ipl = MiLockPfdb(); + + const int PtesPerLevel = PAGE_SIZE / sizeof(MMPTE); + PMMPTE Ptes = (PMMPTE)(MI_PML1_LOCATION); + + PhysAddr &= MI_FASTMAP_MASK; + MmHHDMWindowBase = PhysAddr; + + for (size_t i = 0; i < MI_FASTMAP_SIZE; i += PAGE_SIZE) + { + uintptr_t Address = MI_FASTMAP_START + i; + + MMADDRESS_CONVERT Convert; + Convert.Long = Address; + + Ptes[Convert.Level2Index * PtesPerLevel + Convert.Level1Index] = MM_PTE_PRESENT | MM_PTE_READWRITE | MM_PTE_NOEXEC | (PhysAddr + i); + KeInvalidatePage((void*)Address); + } + + MiUnlockPfdb(Ipl); +} + +void* MmGetHHDMOffsetAddr(uintptr_t PhysAddr) +{ + ASSERT(!MmPfnLock.Locked); + ASSERT(MiHHDMWindowLock.Locked); + + if (PhysAddr < MI_IDENTMAP_SIZE) + return (void*)(MI_IDENTMAP_START + PhysAddr); + + if ((PhysAddr & MI_FASTMAP_MASK) != MmHHDMWindowBase) + MiUpdateHHDMWindowBase(PhysAddr); + + return (void*)(MI_FASTMAP_START + (PhysAddr & ~MI_FASTMAP_MASK)); +} + +uintptr_t MmGetHHDMOffsetFromAddr(void* Addr) +{ + ASSERT(!MmPfnLock.Locked); + ASSERT(MiHHDMWindowLock.Locked); + + uintptr_t AddrInt = (uintptr_t) Addr; + if (AddrInt >= MI_IDENTMAP_START && AddrInt < MI_IDENTMAP_START + MI_IDENTMAP_SIZE) + return AddrInt - MI_IDENTMAP_START; + + if ((AddrInt & MI_FASTMAP_MASK) != MmHHDMWindowBase) + { + DbgPrint("MmGetHHDMOffsetFromAddr: Address %p isn't in the currently selected window!", Addr); + return 0xFFFFFFFF; + } + + return MmHHDMWindowBase + (AddrInt & ~MI_FASTMAP_MASK); +} + +#endif // IS_64_BIT + // Allocates a page from the memmap for eternity during init. Used to prepare the PFN database. // Also used in the initial DLL loader. INIT @@ -173,6 +278,48 @@ static bool MiMapNewPageAtAddressIfNeeded(uintptr_t pageTable, uintptr_t address } } + return true; +#elif defined TARGET_I386 + (void)pageTable; // unused + + MMADDRESS_CONVERT Convert; + Convert.Long = address; + + PMMPTE Level1, Level2; + + Level2 = (PMMPTE)MI_PML2_LOCATION; + Level1 = (PMMPTE)(MI_PML1_LOCATION + 4096 * Convert.Level2Index); + + if (~Level2[Convert.Level2Index] & MM_PTE_PRESENT) + { + uintptr_t Addr = MiAllocatePageFromMemMap(); + + if (!Addr) + { + // TODO: Allow rollback + return false; + } + + Level2[Convert.Level2Index] = Addr | MM_PTE_PRESENT | MM_PTE_READWRITE; + } + + if (~Level1[Convert.Level1Index] & MM_PTE_PRESENT) + { + uintptr_t Addr = MiAllocatePageFromMemMap(); + + if (!Addr) + { + // TODO: Allow rollback + return false; + } + + MmBeginUsingHHDM(); + memset(MmGetHHDMOffsetAddr(Addr), 0, PAGE_SIZE); + MmEndUsingHHDM(); + + Level1[Convert.Level1Index] = Addr | MM_PTE_PRESENT | MM_PTE_READWRITE; + } + return true; #else #error "Implement this for your platform!" @@ -222,7 +369,6 @@ static MMPFN MiFirstZeroPFN = PFN_INVALID, MiLastZeroPFN = PFN_INVALID; static MMPFN MiFirstFreePFN = PFN_INVALID, MiLastFreePFN = PFN_INVALID; static MMPFN MiFirstStandbyPFN = PFN_INVALID, MiLastStandbyPFN = PFN_INVALID; static MMPFN MiFirstModifiedPFN = PFN_INVALID, MiLastModifiedPFN = PFN_INVALID; -static KSPIN_LOCK MmPfnLock; KIPL MiLockPfdb() { @@ -247,7 +393,9 @@ void MiInitPMM() DbgPrint("WARNING: The HHDM isn't at 0xFFFF 8000 0000 0000, things may go wrong! (It's actually at %p)", (void*) KeLoaderParameterBlock.HhdmBase); #endif +#ifdef IS_64_BIT MmHHDMBase = KeLoaderParameterBlock.HhdmBase; +#endif uintptr_t currPageTablePhys = KeGetCurrentPageTable(); @@ -545,10 +693,30 @@ static void MmpInitializePfn(PMMPFDBE Pfdbe) // MiDetransitionPfn(PFN(pte)) // release PTE lock // } + // + // ^^^^^ this is old as hell and probably outdated TODO: clarify or remove ASSERT(Pfdbe->FileCache._PrototypePte); - uintptr_t* Ptr = PFDBE_PrototypePte(Pfdbe); + MM_PROTOTYPE_PTE_PTR Ptr = PFDBE_PrototypePte(Pfdbe); + +#ifdef IS_64_BIT + AtStore(*Ptr, 0); + +#else + if (Ptr & MM_PROTO_PTE_PTR_IS_VIRTUAL) + { + // Virtual + AtStore(*(PMMPFN)(Ptr & ~MM_PROTO_PTE_PTR_IS_VIRTUAL), 0); + } + else + { + // Physical + MmBeginUsingHHDM(); + AtStore(*(PMMPFN)MmGetHHDMOffsetAddr(Ptr), 0); + MmEndUsingHHDM(); + } +#endif } Pfdbe->Type = PF_TYPE_USED; @@ -570,28 +738,39 @@ static MMPFN MmpAllocateFromFreeList(PMMPFN First, PMMPFN Last) MmpRemovePfnFromList(First, Last, *First); MmpInitializePfn(pPF); + ASSERT(currPFN != PFN_INVALID && currPFN != MM_PFN_OUTOFMEMORY); return currPFN; } -MMPFN MmAllocatePhysicalPage() +MMPFN MiAllocatePhysicalPageWithPfdbLocked(bool* IsZeroed) { - KIPL OldIpl; - KeAcquireSpinLock(&MmPfnLock, &OldIpl); - - bool FromZero = true; + *IsZeroed = true; MMPFN currPFN = MmpAllocateFromFreeList(&MiFirstZeroPFN, &MiLastZeroPFN); if (currPFN == PFN_INVALID) { - FromZero = false; + *IsZeroed = false; currPFN = MmpAllocateFromFreeList(&MiFirstFreePFN, &MiLastFreePFN); if (currPFN == PFN_INVALID) currPFN = MmpAllocateFromFreeList(&MiFirstStandbyPFN, &MiLastStandbyPFN); } + return currPFN; +} + +MMPFN MmAllocatePhysicalPage() +{ + bool FromZero; + KIPL OldIpl; + KeAcquireSpinLock(&MmPfnLock, &OldIpl); + MMPFN currPFN = MiAllocatePhysicalPageWithPfdbLocked(&FromZero); KeReleaseSpinLock(&MmPfnLock, OldIpl); if (!FromZero && currPFN != PFN_INVALID) + { + MmBeginUsingHHDM(); memset(MmGetHHDMOffsetAddr(MmPFNToPhysPage(currPFN)), 0, PAGE_SIZE); + MmEndUsingHHDM(); + } #ifdef PMMDEBUG DbgPrint("MmAllocatePhysicalPage() => %d (RA:%p)", currPFN, __builtin_return_address(0)); @@ -618,7 +797,7 @@ MMPFN MiRemoveOneModifiedPfn() return Pfn; } -void MmSetPrototypePtePfn(MMPFN Pfn, uintptr_t* PrototypePte) +void MmSetPrototypePtePfn(MMPFN Pfn, MM_PROTOTYPE_PTE_PTR PrototypePte) { ASSERT(Pfn != PFN_INVALID); @@ -627,12 +806,12 @@ void MmSetPrototypePtePfn(MMPFN Pfn, uintptr_t* PrototypePte) // If this page is freed after this operation, then the prototype // PTE will be atomically set to zero when reclaimed. - MmGetPageFrameFromPFN(Pfn)->FileCache._PrototypePte = (uint64_t) PrototypePte; + MmGetPageFrameFromPFN(Pfn)->FileCache._PrototypePte = (uintptr_t) PrototypePte; KeReleaseSpinLock(&MmPfnLock, OldIpl); } -void MmSetCacheDetailsPfn(MMPFN Pfn, uintptr_t* PrototypePte, PFCB Fcb, uint64_t Offset) +void MmSetCacheDetailsPfn(MMPFN Pfn, PFCB Fcb, uint64_t Offset) { ASSERT(Pfn != PFN_INVALID); @@ -643,8 +822,7 @@ void MmSetCacheDetailsPfn(MMPFN Pfn, uintptr_t* PrototypePte, PFCB Fcb, uint64_t PMMPFDBE Pfdbe = MmGetPageFrameFromPFN(Pfn); - Pfdbe->FileCache._PrototypePte = (uint64_t) PrototypePte; - Pfdbe->FileCache._Fcb = (uint64_t) Fcb; + Pfdbe->FileCache._Fcb = (uintptr_t) Fcb; Pfdbe->FileCache._OffsetLower = Offset; Pfdbe->_OffsetUpper = (Offset >> 32); Pfdbe->IsFileCache = 1; @@ -801,56 +979,50 @@ void MiReinsertIntoModifiedList(MMPFN Pfn) } } -// Zeroes out a free PFN, takes it off the free PFN list and adds it to -// the zero PFN list. -static void MmpZeroOutPFN(MMPFN pfn) +// Zeroes out the first free PFN, takes it off the free PFN list and +// adds it to the zero PFN list. +void MmZeroOutFirstPFN() { - ASSERT(pfn != PFN_INVALID); - PMMPFDBE pPF = MmGetPageFrameFromPFN(pfn); - if (pPF->Type == PF_TYPE_ZEROED) - return; + // step 1. find the first free PFN, if it exists. + KIPL OldIpl; + KeAcquireSpinLock(&MmPfnLock, &OldIpl); - if (pPF->Type != PF_TYPE_FREE) + if (MiFirstFreePFN == PFN_INVALID) { - DbgPrint("Error, attempting to zero out pfn %d which is used", pfn); + Return: + KeReleaseSpinLock(&MmPfnLock, OldIpl); return; } + + MMPFN pfn = MiFirstFreePFN; + PMMPFDBE pPF = MmGetPageFrameFromPFN(pfn); + + if (pPF->Type == PF_TYPE_ZEROED) + goto Return; + +#ifdef DEBUG + if (pPF->Type != PF_TYPE_FREE) + KeCrash("Error, attempting to zero out pfn %d which is used", pfn); +#endif pPF->Type = PF_TYPE_ZEROED; MmpRemovePfnFromList(&MiFirstFreePFN, &MiLastFreePFN, pfn); + KeReleaseSpinLock(&MmPfnLock, OldIpl); - // zero out the page itself + // step 2. free the PFN. + MmBeginUsingHHDM(); uint8_t* mem = MmGetHHDMOffsetAddr(MmPFNToPhysPage(pfn)); memset(mem, 0, PAGE_SIZE); + MmEndUsingHHDM(); - MmpAddPfnToList(&MiFirstZeroPFN, &MiLastZeroPFN, pfn); -} - -void MmZeroOutPFN(MMPFN pfn) -{ - ASSERT(pfn != PFN_INVALID); - KIPL OldIpl; + // step 3. add this PFN to the zero list KeAcquireSpinLock(&MmPfnLock, &OldIpl); - MmpZeroOutPFN(pfn); + MmpAddPfnToList(&MiFirstZeroPFN, &MiLastZeroPFN, pfn); KeReleaseSpinLock(&MmPfnLock, OldIpl); } -void MmZeroOutFirstPFN() -{ - KIPL OldIpl; - KeAcquireSpinLock(&MmPfnLock, &OldIpl); - - if (MiFirstFreePFN == PFN_INVALID) - { - KeReleaseSpinLock(&MmPfnLock, OldIpl); - return; - } - - MmpZeroOutPFN(MiFirstFreePFN); - - KeReleaseSpinLock(&MmPfnLock, OldIpl); -} +#ifdef IS_64_BIT void* MmAllocatePhysicalPageHHDM() { @@ -867,6 +1039,20 @@ void MmFreePhysicalPageHHDM(void* page) return MmFreePhysicalPage(MmPhysPageToPFN(MmGetHHDMOffsetFromAddr(page))); } +#else + +void* MmAllocatePhysicalPageHHDM() +{ + KeCrash("NYI MmAllocatePhysicalPageHHDM"); +} + +void MmFreePhysicalPageHHDM(void* Page) +{ + KeCrash("NYI MmFreePhysicalPageHHDM(%p)", Page); +} + +#endif + void MiPageAddReferenceWithPfdbLocked(MMPFN Pfn) { ASSERT(Pfn != PFN_INVALID); diff --git a/boron/source/mm/pool.c b/boron/source/mm/pool.c index baa071eb..90197c5a 100644 --- a/boron/source/mm/pool.c +++ b/boron/source/mm/pool.c @@ -43,7 +43,6 @@ void* MmAllocatePoolBig(int PoolFlags, size_t PageCount, int Tag) // Map the memory in! This will affect ALL page maps if (!MiMapAnonPages( - MiGetCurrentPageMap(), (uintptr_t) OutputAddress, PageCount, MM_PTE_READWRITE | MM_PTE_GLOBAL, @@ -65,14 +64,16 @@ void MmFreePoolBig(void* Address) MIPOOL_SPACE_HANDLE Handle = MiGetPoolSpaceHandleFromAddress(Address); int PoolFlags = (int) MiGetUserDataFromPoolSpaceHandle(Handle); - if (~PoolFlags & POOL_FLAG_CALLER_CONTROLLED) + if ((~PoolFlags & POOL_FLAG_CALLER_CONTROLLED) || + (PoolFlags & POOL_FLAG_UNMAP_ANYWAY)) { MmLockKernelSpaceExclusive(); // De-allocate the memory first. Ideally this will affect ALL page maps - MiUnmapPages(MiGetCurrentPageMap(), - (uintptr_t)Address, - MiGetSizeFromPoolSpaceHandle(Handle)); + MiUnmapPages( + (uintptr_t)Address, + MiGetSizeFromPoolSpaceHandle(Handle) + ); MmUnlockKernelSpace(); } @@ -91,12 +92,22 @@ void MmFreePool(void* Pointer) MiSlabFree(Pointer); } -void* MmMapIoSpace(uintptr_t PhysicalAddress, size_t NumberOfPages, uintptr_t PermissionsAndCaching, int Tag) +void* MmMapIoSpace(uintptr_t PhysicalAddress, size_t Size, uintptr_t PermissionsAndCaching, int Tag) { if (Tag == 0) Tag = POOL_TAG("MMIS"); + // Ensure the starting address is page aligned. + Size += PhysicalAddress & (PAGE_SIZE - 1); + PhysicalAddress &= ~(PAGE_SIZE - 1); + + size_t SizePages = (Size + PAGE_SIZE - 1) / PAGE_SIZE; + // Allocate some pool space. - void* Space = MmAllocatePoolBig(POOL_FLAG_CALLER_CONTROLLED, NumberOfPages, Tag); + void* Space = MmAllocatePoolBig( + POOL_FLAG_CALLER_CONTROLLED | POOL_FLAG_UNMAP_ANYWAY, + SizePages, + Tag + ); uintptr_t VirtualAddress = (uintptr_t) Space; if (!Space) @@ -104,13 +115,11 @@ void* MmMapIoSpace(uintptr_t PhysicalAddress, size_t NumberOfPages, uintptr_t Pe MmLockKernelSpaceExclusive(); - HPAGEMAP PageMap = MiGetCurrentPageMap(); - - for (size_t i = 0; i < NumberOfPages; i++, PhysicalAddress += PAGE_SIZE, VirtualAddress += PAGE_SIZE) + for (size_t i = 0; i < SizePages; i++, PhysicalAddress += PAGE_SIZE, VirtualAddress += PAGE_SIZE) { - if (!MiMapPhysicalPage(PageMap, PhysicalAddress, VirtualAddress, PermissionsAndCaching)) + if (!MiMapPhysicalPage(PhysicalAddress, VirtualAddress, PermissionsAndCaching)) { - NumberOfPages = i; + SizePages = i; goto Rollback; } } @@ -119,7 +128,7 @@ void* MmMapIoSpace(uintptr_t PhysicalAddress, size_t NumberOfPages, uintptr_t Pe return Space; Rollback: - MiUnmapPages(PageMap, (uintptr_t) Space, NumberOfPages); + MiUnmapPages((uintptr_t) Space, SizePages); MmUnlockKernelSpace(); MmFreePoolBig(Space); return NULL; diff --git a/boron/source/mm/poolhdr.c b/boron/source/mm/poolhdr.c index 78b30e58..33a41b07 100644 --- a/boron/source/mm/poolhdr.c +++ b/boron/source/mm/poolhdr.c @@ -17,17 +17,28 @@ Module name: typedef struct { + // UPDATE MmpAllocateFromPoolEntrySlab if changing these. + +#ifdef IS_64_BIT + #define POOL_ENTRY_COUNT 84 // Size of a MIPOOL_ENTRY is 48. - // UPDATE MmpAllocateFromPoolEntrySlab if changing this. - MIPOOL_ENTRY Entries[84]; + // Size of additional data is 32 bytes. (2xPtr=16 + 2xU64=16) + // Note: Free space is 32 bytes +#else + #define POOL_ENTRY_COUNT 64 + // Size of a MIPOOL_ENTRY is 32 + // Size of additional data is 24 bytes (2xPtr=8 + 2xU64=16) + // Note: Free space is 8 bytes +#endif + + MIPOOL_ENTRY Entries[POOL_ENTRY_COUNT]; LIST_ENTRY ListEntry; uint64_t Bitmap[2]; - - // Note - Free space: 32 bytes } MIPOOL_ENTRY_SLAB, *PMIPOOL_ENTRY_SLAB; static_assert(sizeof(MIPOOL_ENTRY_SLAB) <= PAGE_SIZE); +static_assert((sizeof(MIPOOL_ENTRY) & 0x7) == 0); static LIST_ENTRY MmpPoolSlabList; static KSPIN_LOCK MmpPoolSlabListLock; @@ -47,10 +58,10 @@ PMIPOOL_ENTRY MmpAllocateFromPoolEntrySlab(PMIPOOL_ENTRY_SLAB Slab) } } - const uint64_t FirstTwentyBitsSet = (1ULL << 20) - 1; + const uint64_t FirstTwentyBitsSet = (1ULL << (POOL_ENTRY_COUNT - 64)) - 1; if ((Slab->Bitmap[1] & FirstTwentyBitsSet) != FirstTwentyBitsSet) { - for (int i = 0, j = 64; i < 20; i++, j++) + for (int i = 0, j = 64; i < POOL_ENTRY_COUNT - 64; i++, j++) { if (~Slab->Bitmap[1] & (1ULL << i)) { @@ -70,6 +81,113 @@ void MiInitPoolEntryAllocator() InitializeListHead(&MmpPoolSlabList); } +#ifdef IS_64_BIT + +PMIPOOL_ENTRY_SLAB MiAllocatePoolHeaderSlab() +{ + MMPFN Pfn = MmAllocatePhysicalPage(); + if (Pfn == PFN_INVALID) + { + DbgPrint("WARNING in MiAllocatePoolHeaderSlab: No physical memory left!"); + return NULL; + } + + // HHDM is supported on 64-bit, so just return this + return MmGetHHDMOffsetAddr(MmPFNToPhysPage(Pfn)); +} + +void MiFreePoolHeaderSlab(PMIPOOL_ENTRY_SLAB Address) +{ + MMPFN Pfn = MmPhysPageToPFN(MmGetHHDMOffsetFromAddr(Address)); + MmFreePhysicalPage(Pfn); +} + +#else + +#define POOL_HDR_MAX_PFNS_MAPPED (MI_POOL_HEADERS_SIZE / PAGE_SIZE) + +static KSPIN_LOCK MiPoolHeaderMapLock; +static uint32_t MiPoolHeaderPfnsMappedBitmap[POOL_HDR_MAX_PFNS_MAPPED / 32]; +static_assert(ARRAY_COUNT(MiPoolHeaderPfnsMappedBitmap) == 64); + +PMIPOOL_ENTRY_SLAB MiAllocatePoolHeaderSlab() +{ + MMPFN Pfn = MmAllocatePhysicalPage(); + if (Pfn == PFN_INVALID) + { + DbgPrint("WARNING in MiAllocatePoolHeaderSlab: No physical memory left!"); + return NULL; + } + + KIPL Ipl; + KeAcquireSpinLock(&MiPoolHeaderMapLock, &Ipl); + + // Find a place in the bitmap + const size_t NotFound = 0xFFFFFFFF; + size_t IndexFound = NotFound; + + for (size_t i = 0; i < POOL_HDR_MAX_PFNS_MAPPED / 32 && IndexFound == NotFound; i++) + { + if (MiPoolHeaderPfnsMappedBitmap[i] == 0xFFFFFFFF) + continue; + + // claim the first clear bit + for (int k = 0; k < 32; k++) + { + if (~MiPoolHeaderPfnsMappedBitmap[i] & (1 << k)) + { + MiPoolHeaderPfnsMappedBitmap[i] |= 1 << k; + IndexFound = i * 32 + k; + break; + } + } + } + + if (IndexFound == NotFound) + { + DbgPrint("WARNING in MiAllocatePoolHeaderSlab: No more space left!"); + KeReleaseSpinLock(&MiPoolHeaderMapLock, Ipl); + MmFreePhysicalPage(Pfn); + return NULL; + } + + void* Address = (void*)(MI_POOL_HEADERS_START + IndexFound * PAGE_SIZE); + + PMMPTE Pte = (PMMPTE) MI_PTE_LOC((uintptr_t) Address); + *Pte = MmPFNToPhysPage(Pfn) | MM_PTE_PRESENT | MM_PTE_READWRITE; + KeInvalidatePage(Pte); + + KeReleaseSpinLock(&MiPoolHeaderMapLock, Ipl); + return Address; +} + +void MiFreePoolHeaderSlab(PMIPOOL_ENTRY_SLAB Address) +{ + KIPL Ipl; + KeAcquireSpinLock(&MiPoolHeaderMapLock, &Ipl); + + PMMPTE Pte = (PMMPTE) MI_PTE_LOC((uintptr_t) Address); + MMPFN Pfn = MmPhysPageToPFN(*Pte & MM_PTE_ADDRESSMASK); + + // clear the PTE + *Pte = 0; + KeInvalidatePage(Address); + + // then free + MmFreePhysicalPage(Pfn); + + // and mark as unmapped + uint32_t Index = ((uintptr_t)Address - MI_POOL_HEADERS_START) / PAGE_SIZE; + uint32_t BIndex = Index >> 5, BSubIndex = Index & 0x1F; + + ASSERT(MiPoolHeaderPfnsMappedBitmap[BIndex] & (1 << BSubIndex)); + MiPoolHeaderPfnsMappedBitmap[BIndex] &= ~(1 << BSubIndex); + + KeReleaseSpinLock(&MiPoolHeaderMapLock, Ipl); +} + +#endif + PMIPOOL_ENTRY MiCreatePoolEntry() { KIPL Ipl; @@ -85,6 +203,9 @@ PMIPOOL_ENTRY MiCreatePoolEntry() if (PoolEntry) { memset(PoolEntry, 0, sizeof *PoolEntry); + #ifdef MIPOOL_DUMMY_SIGNATURE + PoolEntry->Dummy = MIPOOL_DUMMY_SIGNATURE; + #endif KeReleaseSpinLock(&MmpPoolSlabListLock, Ipl); return PoolEntry; } @@ -93,31 +214,41 @@ PMIPOOL_ENTRY MiCreatePoolEntry() } // Have to allocate a new entry. - int Pfn = MmAllocatePhysicalPage(); - if (Pfn == PFN_INVALID) + PMIPOOL_ENTRY_SLAB Item = MiAllocatePoolHeaderSlab(); + if (!Item) { - DbgPrint("MmpSlabContainerAllocate: Run out of memory! What will we do?!"); - // TODO: invoke the out of memory handler here, then try again KeReleaseSpinLock(&MmpPoolSlabListLock, Ipl); return NULL; } - PMIPOOL_ENTRY_SLAB Item = MmGetHHDMOffsetAddr(MmPFNToPhysPage(Pfn)); memset(Item, 0, sizeof *Item); // Add it to the list. InsertHeadList(&MmpPoolSlabList, &Item->ListEntry); - // Attempt to allocate as usual. + // Attempt to allocate as usual. This should succeed, + // because we just freshly allocated a new entry. PMIPOOL_ENTRY PoolEntry = MmpAllocateFromPoolEntrySlab(Item); + ASSERT(PoolEntry); - KeReleaseSpinLock(&MmpPoolSlabListLock, Ipl); + memset(PoolEntry, 0, sizeof *PoolEntry); +#ifdef MIPOOL_DUMMY_SIGNATURE + PoolEntry->Dummy = MIPOOL_DUMMY_SIGNATURE; +#endif + KeReleaseSpinLock(&MmpPoolSlabListLock, Ipl); return PoolEntry; } void MiDeletePoolEntry(PMIPOOL_ENTRY Entry) { +#ifdef MIPOOL_DUMMY_SIGNATURE + ASSERT(Entry->Dummy == MIPOOL_DUMMY_SIGNATURE); + + ASSERT(Entry->ListEntry.Flink == NULL); + ASSERT(Entry->ListEntry.Blink == NULL); +#endif + // Get the containing slab. PMIPOOL_ENTRY_SLAB Slab = (PMIPOOL_ENTRY_SLAB) ((uintptr_t) Entry & ~(PAGE_SIZE - 1)); @@ -134,9 +265,6 @@ void MiDeletePoolEntry(PMIPOOL_ENTRY Entry) if (Slab->Bitmap[0] == 0 && Slab->Bitmap[1] == 0) { RemoveEntryList(&Slab->ListEntry); - - int Pfn = MmPhysPageToPFN(MmGetHHDMOffsetFromAddr(Slab)); - - MmFreePhysicalPage(Pfn); + MiFreePoolHeaderSlab(Slab); } } diff --git a/boron/source/mm/poolsup.c b/boron/source/mm/poolsup.c index a068e213..df6d6958 100644 --- a/boron/source/mm/poolsup.c +++ b/boron/source/mm/poolsup.c @@ -14,6 +14,78 @@ Module name: ***/ #include "mi.h" +#ifdef IS_32_BIT + +// the structure of the pool header PTE if this is set is as follows: +// +// [Bits 31..12] 1 [Bits 11..10] 0 [Bits 9..3] 0 +// +// - Bit 8 is cleared because MM_DPTE_COMMITTED shouldn't conflict +// with this scheme. +// +// - Bit 0 is cleared because MM_PTE_PRESENT conflicts +// +// - Bit 11 is set because that's MM_PTE_ISPOOLHDR + +typedef union +{ + MMPTE Pte; + + struct + { + uintptr_t Present : 1; // MUST be zero + uintptr_t B3to9 : 7; + uintptr_t Committed : 1; // MUST be zero + uintptr_t B10to11 : 2; + uintptr_t IsPoolHdr : 1; // MUST be ONE + uintptr_t B12to31 : 20; + } + PACKED; +} +MMPTE_POOLHEADER; + +static_assert(sizeof(MMPTE_POOLHEADER) == sizeof(uint32_t)); +static_assert(MM_DPTE_COMMITTED == (1 << 8)); +static_assert(MM_PTE_ISPOOLHDR == (1 << 11)); + +MMPTE MiCalculatePoolHeaderPte(uintptr_t Handle) +{ + ASSERT(!(Handle & 0x7)); + MMPTE_POOLHEADER PteHeader; + PteHeader.Pte = 0; + + PteHeader.B3to9 = (Handle >> 3) & 0x7F; + PteHeader.B10to11 = (Handle >> 10) & 0x3; + PteHeader.B12to31 = (Handle >> 12); + PteHeader.IsPoolHdr = true; + + return PteHeader.Pte; +} + +FORCE_INLINE +uintptr_t MiReconstructPoolHandleFromPte(MMPTE Pte) +{ + MMPTE_POOLHEADER PteHeader; + PteHeader.Pte = Pte; + + ASSERT(!PteHeader.Present); + ASSERT(!PteHeader.Committed); + ASSERT(PteHeader.IsPoolHdr); + + return + PteHeader.B3to9 << 3 | + PteHeader.B10to11 << 10 | + PteHeader.B12to31 << 12; +} + +#else + +#define MiCalculatePoolHeaderPte(Handle) (((uintptr_t)(Handle) - MM_KERNEL_SPACE_BASE) | MM_PTE_ISPOOLHDR) + +#define MiReconstructPoolHandleFromPte(Pte) ((MIPOOL_SPACE_HANDLE)(((Pte) & ~MM_PTE_ISPOOLHDR) + MM_KERNEL_SPACE_BASE)) + +#endif + // // TODO: This could be improved, however, it's probably OK for now. // @@ -32,9 +104,60 @@ static LIST_ENTRY MmpPoolList; #define MI_EMPTY_TAG MI_TAG(" ") +#ifdef TARGET_AMD64 + +// One PML4 entry can map up to 1<<39 (512GB) of memory. +// Thus, our pool will be 512 GB in size. +#define MI_POOL_LOG2_SIZE (39) + +#elif defined TARGET_I386 + +// There will actually be two arenas of pool space: +// 0x80000000 - 0xC0000000 and 0xD0000000 - 0xF0000000 +#define MI_POOL_LOG2_SIZE (30) + +#define MI_POOL_LOG2_SIZE_2ND (28) + +#else + +#error "Define the pool size for your platform!" + +#endif + +#ifdef IS_32_BIT + +void MiInitializeRootPageTable(int Idx) +{ + PMMPTE Pte = (PMMPTE)MI_PML2_LOCATION + Idx; + MMPFN Pfn = MmAllocatePhysicalPage(); + + if (Pfn == PFN_INVALID) + KeCrashBeforeSMPInit("MiCalculatePoolHeaderPte ERROR: Out of memory!"); + + *Pte = MmPFNToPhysPage(Pfn) | MM_PTE_PRESENT | MM_PTE_READWRITE; +} + +void MiInitializePoolPageTables() +{ + int Size1 = 1 << (MI_POOL_LOG2_SIZE - 22); + int Size2 = 1 << (MI_POOL_LOG2_SIZE_2ND - 22); + + for (int i = MI_GLOBAL_AREA_START; i < MI_GLOBAL_AREA_START + Size1; i++) + MiInitializeRootPageTable(i); + + for (int i = MI_GLOBAL_AREA_START_2ND; i < MI_GLOBAL_AREA_START_2ND + Size2; i++) + MiInitializeRootPageTable(i); +} + +#endif + INIT void MiInitPool() { +#ifdef IS_32_BIT + MiInitializePoolPageTables(); +#endif + InitializeListHead(&MmpPoolList); PMIPOOL_ENTRY Entry = MiCreatePoolEntry(); @@ -43,6 +166,18 @@ void MiInitPool() Entry->Size = 1ULL << (MI_POOL_LOG2_SIZE - 12); Entry->Address = MiGetTopOfPoolManagedArea(); InsertTailList(&MmpPoolList, &Entry->ListEntry); + +#ifdef TARGET_I386 + + // TODO: Will other 32-bit platforms look similar? + Entry = MiCreatePoolEntry(); + Entry->Flags = 0; + Entry->Tag = MI_EMPTY_TAG; + Entry->Size = 1ULL << (MI_POOL_LOG2_SIZE_2ND - 12); + Entry->Address = MiGetTopOfSecondPoolManagedArea(); + InsertTailList(&MmpPoolList, &Entry->ListEntry); + +#endif } MIPOOL_SPACE_HANDLE MmpSplitEntry(PMIPOOL_ENTRY PoolEntry, size_t SizeInPages, void** OutputAddress, int Tag, uintptr_t UserData) @@ -64,14 +199,15 @@ MIPOOL_SPACE_HANDLE MmpSplitEntry(PMIPOOL_ENTRY PoolEntry, size_t SizeInPages, v // This entry manages the area directly after the PoolEntry does. PMIPOOL_ENTRY NewEntry = MiCreatePoolEntry(); - // TODO: Debug the firework test driver. When removing this, it doesn't throw up a nice - // page fault, but rather hits some weird code which sets the stack pointer to 0x000000017FFFFFFF - // and triple faults the kernel. if (!NewEntry) return 0; // Link it such that: // PoolEntry ====> NewEntry ====> PoolEntry->Flink + ASSERT(NewEntry); + ASSERT(PoolEntry); + ASSERT(PoolEntry->ListEntry.Flink); + ASSERT(PoolEntry->ListEntry.Blink); InsertHeadList(&PoolEntry->ListEntry, &NewEntry->ListEntry); // Assign the other properties @@ -106,6 +242,11 @@ MIPOOL_SPACE_HANDLE MiReservePoolSpaceTaggedSub(size_t SizeInPages, void** Outpu while (CurrentEntry != &MmpPoolList) { +#ifdef DEBUG + if (CurrentEntry == NULL) + KeCrash("HUH?!? CurrentEntry is NULL"); +#endif + // Skip allocated entries. PMIPOOL_ENTRY Current = MIP_CURRENT(CurrentEntry); @@ -122,6 +263,11 @@ MIPOOL_SPACE_HANDLE MiReservePoolSpaceTaggedSub(size_t SizeInPages, void** Outpu return Handle; } +#ifdef DEBUG + if (CurrentEntry->Flink == NULL) + KeCrash("HUH?!? CurrentEntry->Flink is NULL! CurrentEntry: %p"); +#endif + CurrentEntry = CurrentEntry->Flink; } @@ -150,7 +296,11 @@ static void MmpTryConnectEntryWithItsFlink(PMIPOOL_ENTRY Entry) Entry->Size += Flink->Size; // remove the 'flink' entry - RemoveHeadList(&Entry->ListEntry); + RemoveEntryList(&Flink->ListEntry); + ASSERT(Entry->ListEntry.Flink != NULL); + ASSERT(Entry->ListEntry.Blink != NULL); + ASSERT(Flink->ListEntry.Flink == NULL); + ASSERT(Flink->ListEntry.Blink == NULL); MiDeletePoolEntry(Flink); } @@ -163,6 +313,9 @@ void MiFreePoolSpaceSub(MIPOOL_SPACE_HANDLE Handle) // Get the handle to the pool entry. PMIPOOL_ENTRY Entry = (PMIPOOL_ENTRY) Handle; + ASSERT(!(Handle & 0x7)); + ASSERT(Handle >= MM_KERNEL_SPACE_BASE); + ASSERT(MiReconstructPoolHandleFromPte(MiCalculatePoolHeaderPte(Handle)) == Handle); if (~Entry->Flags & MI_POOL_ENTRY_ALLOCATED) { @@ -173,7 +326,8 @@ void MiFreePoolSpaceSub(MIPOOL_SPACE_HANDLE Handle) Entry->Tag = MI_EMPTY_TAG; MmpTryConnectEntryWithItsFlink(Entry); - MmpTryConnectEntryWithItsFlink(MIP_BLINK(&Entry->ListEntry)); + if (Entry->ListEntry.Blink != &MmpPoolList) + MmpTryConnectEntryWithItsFlink(MIP_BLINK(&Entry->ListEntry)); KeReleaseSpinLock(&MmpPoolLock, OldIpl); } @@ -185,9 +339,9 @@ void MiFreePoolSpace(MIPOOL_SPACE_HANDLE Handle) // Acquire the kernel space lock and zero out its PTE. MmLockKernelSpaceExclusive(); - PMMPTE PtePtr = MiGetPTEPointer(MiGetCurrentPageMap(), Address, false); + PMMPTE PtePtr = MmGetPteLocationCheck(Address, false); ASSERT(PtePtr); - ASSERT(*PtePtr == ((Handle - MM_KERNEL_SPACE_BASE) | MM_PTE_ISPOOLHDR)); + ASSERT(*PtePtr == MiCalculatePoolHeaderPte(Handle)); *PtePtr = 0; MmUnlockKernelSpace(); @@ -218,7 +372,7 @@ MIPOOL_SPACE_HANDLE MiReservePoolSpaceTagged(size_t SizeInPages, void** OutputAd // Acquire the kernel space lock and place the address of the handle into the first part of the PTE. MmLockKernelSpaceExclusive(); - PMMPTE PtePtr = MiGetPTEPointer(MiGetCurrentPageMap(), (uintptr_t) OutputAddressSub, true); + PMMPTE PtePtr = MmGetPteLocationCheck((uintptr_t) OutputAddressSub, true); if (!PtePtr) { // TODO: Handle this in a nicer way. @@ -231,7 +385,11 @@ MIPOOL_SPACE_HANDLE MiReservePoolSpaceTagged(size_t SizeInPages, void** OutputAd ASSERT(*PtePtr == 0); ASSERT((Handle & MM_PTE_PRESENT) == 0); - *PtePtr = (Handle - MM_KERNEL_SPACE_BASE) | MM_PTE_ISPOOLHDR; + MMPTE Pte = MiCalculatePoolHeaderPte(Handle); + ASSERT(MiReconstructPoolHandleFromPte(Pte) == Handle); + + *PtePtr = Pte; + MmUnlockKernelSpace(); *OutputAddress = (void*) ((uintptr_t) OutputAddressSub + PAGE_SIZE); @@ -297,7 +455,7 @@ MIPOOL_SPACE_HANDLE MiGetPoolSpaceHandleFromAddress(void* AddressV) MmLockKernelSpaceExclusive(); - PMMPTE PtePtr = MiGetPTEPointer(MiGetCurrentPageMap(), Address - PAGE_SIZE, false); + PMMPTE PtePtr = MmGetPteLocationCheck(Address - PAGE_SIZE, false); if (!PtePtr) { MmUnlockKernelSpace(); @@ -305,9 +463,15 @@ MIPOOL_SPACE_HANDLE MiGetPoolSpaceHandleFromAddress(void* AddressV) return (MIPOOL_SPACE_HANDLE) NULL; } - ASSERT(*PtePtr & MM_PTE_ISPOOLHDR); - - MIPOOL_SPACE_HANDLE Handle = ((*PtePtr) & ~MM_PTE_ISPOOLHDR) + MM_KERNEL_SPACE_BASE; + // N.B. This kind of relies on the notion that the address doesn't have + // the valid bit set. + uintptr_t PAddress = *PtePtr; + if (~PAddress & MM_PTE_ISPOOLHDR) { + KeCrash("Trying to access pool space handle from address %p, but its PTE says %p", AddressV, PAddress); + } + ASSERT(PAddress & MM_PTE_ISPOOLHDR); + PAddress = MiReconstructPoolHandleFromPte(PAddress); + MIPOOL_SPACE_HANDLE Handle = PAddress; MmUnlockKernelSpace(); return Handle; } diff --git a/boron/source/mm/reclaim.c b/boron/source/mm/reclaim.c index 575de4ea..d8445524 100644 --- a/boron/source/mm/reclaim.c +++ b/boron/source/mm/reclaim.c @@ -24,7 +24,7 @@ void MiReclaimInitText() MmLockKernelSpaceExclusive(); for (uintptr_t i = (uintptr_t) KiTextInitStart; i != (uintptr_t) KiTextInitEnd; i += PAGE_SIZE) { - PMMPTE PtePtr = MiGetPTEPointer(MiGetCurrentPageMap(), i, false); + PMMPTE PtePtr = MmGetPteLocationCheck(i, false); ASSERT(PtePtr); MMPTE Pte = *PtePtr; @@ -39,6 +39,7 @@ void MiReclaimInitText() MmFreePhysicalPage(Pfn); Reclaimed++; } + MmUnlockKernelSpace(); #ifdef DEBUG diff --git a/boron/source/mm/slab.c b/boron/source/mm/slab.c index 5a0462ff..bbd844c9 100644 --- a/boron/source/mm/slab.c +++ b/boron/source/mm/slab.c @@ -55,7 +55,7 @@ static size_t MmpSlabItemDetermineLength(int ItemSize) return PAGE_SIZE; // Allow at least 4 items to fit. - return (ItemSize * 4 + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; + return (ItemSize * 4 + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); } static bool MmpRequiresRbTreeEntry(int ItemSize) @@ -130,7 +130,7 @@ int MmGetSmallestSlabSizeThatFitsSize(size_t Size) void* MmpSlabItemTryAllocate(PMISLAB_ITEM Item, int EntrySize) { - int EntriesPerItem = (Item->Length - sizeof(Item)) / EntrySize; + int EntriesPerItem = (Item->Length - sizeof(*Item)) / EntrySize; int BitmapWrdsToCheck = (EntriesPerItem + 63) / 64; int LastBitmapBitCount = EntriesPerItem % 64; @@ -266,7 +266,11 @@ void MmpSlabContainerFree(PMISLAB_CONTAINER Container, PMISLAB_ITEM Item, void* bool RequiresRbTreeEntry = MmpRequiresRbTreeEntry(Container->ItemSize); // Check if it's all zero: +#ifdef IS_64_BIT if (!Item->Bitmap[0] && (RequiresRbTreeEntry || (!Item->Bitmap[1] && !Item->Bitmap[2] && !Item->Bitmap[3]))) +#else + if (!Item->Bitmap[0] && !Item->Bitmap[1] && (RequiresRbTreeEntry || (!Item->Bitmap[2] && !Item->Bitmap[3]))) +#endif { RemoveEntryList(&Item->ListEntry); @@ -332,6 +336,10 @@ void MiSlabFree(void* Ptr) PHUGE_MEMORY_BLOCK Hmb = PageAlignedPtr; if (Hmb->Check == MI_HUGE_MEMORY_CHECK) { + // FIX ASAP + // TODO: this is vulnerable to attacks where someone fills the memory with this + // TODO TODO TODO + // Free it as a huge memory block. MmpFreeHuge(Hmb); return; diff --git a/boron/source/mm/teardown.c b/boron/source/mm/teardown.c index 10347842..c46387fc 100644 --- a/boron/source/mm/teardown.c +++ b/boron/source/mm/teardown.c @@ -61,14 +61,26 @@ void MmTearDownProcess(PEPROCESS Process) MiFreeUnusedMappingLevelsInCurrentMap(0, (MM_USER_SPACE_END + 1) >> 12); -#if defined(DEBUG) && defined(TARGET_AMD64) +#ifdef DEBUG +#ifdef TARGET_AMD64 PMMPTE PteScan = MmGetHHDMOffsetAddr(Process->Pcb.PageMap); for (int i = 0; i < 256; i++) ASSERT(~PteScan[i] & MM_PTE_PRESENT); +#endif // TARGET_AMD64 + +#ifdef TARGET_I386 + MmBeginUsingHHDM(); + PMMPTE PteScan = MmGetHHDMOffsetAddr(Process->Pcb.PageMap); + + for (int i = 0; i < 256; i++) + ASSERT(~PteScan[i] & MM_PTE_PRESENT); + + MmEndUsingHHDM(); +#endif // TARGET_I386 -#endif +#endif // DEBUG if (Process->Pcb.PageMap != 0) MmFreePhysicalPage(MmPhysPageToPFN(Process->Pcb.PageMap)); diff --git a/boron/source/mm/view.c b/boron/source/mm/view.c index 4a4f83dc..dd949b9c 100644 --- a/boron/source/mm/view.c +++ b/boron/source/mm/view.c @@ -35,11 +35,7 @@ BSTATUS MmMapViewOfFile( PMMVAD_LIST VadList; void* BaseAddress = *BaseAddressInOut; - - // Remove the base address' offset inside a page and add it to the region size. - size_t VaOffset = (size_t)((uintptr_t)BaseAddress & (PAGE_SIZE - 1)); BaseAddress = (void*)((uintptr_t)BaseAddress & ~(PAGE_SIZE - 1)); - ViewSize += VaOffset; size_t PageOffset = SectionOffset & (PAGE_SIZE - 1); size_t ViewSizePages = (ViewSize + PageOffset + PAGE_SIZE - 1) / PAGE_SIZE; diff --git a/boron/source/ps/init.c b/boron/source/ps/init.c index 14aa8c03..24da2618 100644 --- a/boron/source/ps/init.c +++ b/boron/source/ps/init.c @@ -67,12 +67,15 @@ void PsInitSystemProcess() PspSystemProcessNpHeader.NormalHeader = Hdr; // Initialize the kernel side process. - KeInitializeProcess( + BSTATUS Status = KeInitializeProcess( &PsSystemProcess.Pcb, PRIORITY_NORMAL, AFFINITY_ALL ); + if (FAILED(Status)) + KeCrashBeforeSMPInit("KeInitializeProcess failed!"); + // Use the new page mapping. KeSetCurrentPageTable(PsSystemProcess.Pcb.PageMap); diff --git a/boron/source/ps/initproc.c b/boron/source/ps/initproc.c index 9b6a9f96..7793e949 100644 --- a/boron/source/ps/initproc.c +++ b/boron/source/ps/initproc.c @@ -1,6 +1,6 @@ /*** The Boron Operating System - Copyright (C) 2023 iProgramInCpp + Copyright (C) 2025 iProgramInCpp Module name: ps/initproc.c @@ -99,6 +99,10 @@ void PsStartInitialProcess(UNUSED void* ContextUnused) if (FAILED(Status)) KeCrash("%s: Failed to create initial process: %d (%s)", Status, RtlGetStatusString(Status)); + PEPROCESS Process = NULL; + Status = ExReferenceObjectByHandle(ProcessHandle, PsProcessObjectType, (void**) &Process); + ASSERT(SUCCEEDED(Status)); + FileAttributes.RootDirectory = HANDLE_NONE; FileAttributes.ObjectName = BoronDllPath; FileAttributes.ObjectNameLength = strlen(BoronDllPath); @@ -155,6 +159,10 @@ void PsStartInitialProcess(UNUSED void* ContextUnused) FirstAddr = (uintptr_t) ~0ULL; LargestAddr = 0; + Status = RtlCheckValidity(&ElfHeader); + if (FAILED(Status)) + KeCrash("%s: libboron.so has an invalid header: %s (%d)", Func, RtlGetStatusString(Status), Status); + // Find the dynamic program header; PELF_PROGRAM_HEADER DynamicPhdr = PspLdrFindDynamicPhdr( ProgramHeaders, @@ -248,6 +256,22 @@ void PsStartInitialProcess(UNUSED void* ContextUnused) BaseAddress ); } + + // If the size in file doesn't match the in-memory size, clear the rest to zero. + // This will trigger CoW faults. + if (ProgramHeader->SizeInFile < ProgramHeader->SizeInMemory) + { + // Temporarily attach to the process. + // TODO: Optimize. Don't switch the address space every time. + size_t SizeInFile = ProgramHeader->SizeInFile; + size_t SizeInMemory = ProgramHeader->SizeInMemory; + + PEPROCESS Restore = PsSetAttachedProcess(Process); + + memset((char*)BaseAddress + SizeInFile, 0, SizeInMemory - SizeInFile); + + PsSetAttachedProcess(Restore); + } } // Program headers have been mapped. @@ -279,10 +303,6 @@ void PsStartInitialProcess(UNUSED void* ContextUnused) KeCrash("%s: Failed to allocate PEB: %d (%s)", Func, Status, RtlGetStatusString(Status)); // Attach to this process so that we can write to the PEB. - PEPROCESS Process = NULL; - Status = ExReferenceObjectByHandle(ProcessHandle, PsProcessObjectType, (void**) &Process); - ASSERT(SUCCEEDED(Status)); - PEPROCESS OldAttached = PsSetAttachedProcess(Process); PPEB PPeb = PebPtr; diff --git a/boron/source/ps/process.c b/boron/source/ps/process.c index 83b78e3a..6db1bf1b 100644 --- a/boron/source/ps/process.c +++ b/boron/source/ps/process.c @@ -83,15 +83,15 @@ BSTATUS PspInitializeProcessObject(void* ProcessV, void* Context) Process->Pcb.PageMap = 0; // Initialize the kernel side process. - KeInitializeProcess( + Status = KeInitializeProcess( &Process->Pcb, PRIORITY_NORMAL, AFFINITY_ALL ); // If the initial page map couldn't be created, throw an out of memory error. - if (!Process->Pcb.PageMap) - return STATUS_INSUFFICIENT_MEMORY; + if (FAILED(Status)) + return Status; MmInitializeVadList(&Process->VadList); diff --git a/boron/source/ps/psp.h b/boron/source/ps/psp.h index 08b7f805..29473f8e 100644 --- a/boron/source/ps/psp.h +++ b/boron/source/ps/psp.h @@ -22,8 +22,17 @@ Module name: #define USER_STACK_SIZE (256 * 1024) // Initial Virtual Address Range -#define INITIAL_BEG_VA 0x0000000000001000 -#define INITIAL_END_VA 0x00007FFFFFFFF000 +#ifdef IS_64_BIT + +#define INITIAL_BEG_VA (0x0000000000001000) +#define INITIAL_END_VA (0x00007FFFFFFFF000) + +#else + +#define INITIAL_BEG_VA (0x00001000U) +#define INITIAL_END_VA (0x7FFFF000U) + +#endif typedef struct { diff --git a/boron/source/rtl/elf.c b/boron/source/rtl/elf.c index 956fa6ec..51c50089 100644 --- a/boron/source/rtl/elf.c +++ b/boron/source/rtl/elf.c @@ -50,6 +50,7 @@ static bool RtlpComputeRelocation( switch (Type) { + // I prefer to go here with the "Add as you go with no plan" method #ifdef TARGET_AMD64 case R_X86_64_64: *Value = Addend + Symbol; @@ -64,14 +65,25 @@ static bool RtlpComputeRelocation( *Length = sizeof(uint64_t); break; case R_X86_64_GLOB_DAT: - *Value = Symbol; - *Length = sizeof(uint64_t); - break; case R_X86_64_JUMP_SLOT: *Value = Symbol; *Length = sizeof(uint64_t); break; - // I prefer to go here with the "Add as you go with no plan" method +#elif defined TARGET_I386 + case R_386_32: + *Value = Symbol + Addend; + *Length = sizeof(uint32_t); + break; + case R_386_RELATIVE: + *Value = Base + Addend; + *Length = sizeof(uint32_t); + break; + case R_386_GLOB_DAT: + case R_386_JUMP_SLOT: + *Value = Symbol; + *Length = sizeof(uint32_t); + break; + // TODO #else #error Hey! Add ELF relocation types here #endif @@ -130,14 +142,14 @@ static bool RtlpApplyRelocation( } // If there is no pre-resolved symbol and we actually have a symbol index, then look it up - if (ResolvedSymbol == 0 && (Rela.Info >> 32) != 0) - ResolvedSymbol = RtlpResolveSymbolAddress(DynInfo, Rela.Info >> 32, LoadBase); + if (ResolvedSymbol == 0 && ELF_R_SYM(Rela.Info) != 0) + ResolvedSymbol = RtlpResolveSymbolAddress(DynInfo, ELF_R_SYM(Rela.Info), LoadBase); uintptr_t Place = LoadBase + Rela.Offset; uintptr_t Addend = Rela.Addend; uintptr_t Value, Length; - uint32_t RelType = (uint32_t) Rela.Info; // ELF64_R_TYPE(x) => (x & 0xFFFFFFFF) + uint32_t RelType = ELF_R_TYPE(Rela.Info); if (!RtlpComputeRelocation(RelType, Addend, @@ -284,7 +296,7 @@ bool RtlLinkPlt(PELF_DYNAMIC_INFO DynInfo, uintptr_t LoadBase, UNUSED const char // NOTE: PELF_RELA and PELF_REL need to have the same starting members!! PELF_REL Rel = (PELF_REL)((uintptr_t)DynInfo->PltRelocations + i); - PELF_SYMBOL Symbol = &DynInfo->DynSymTable[Rel->Info >> 32]; + PELF_SYMBOL Symbol = &DynInfo->DynSymTable[ELF_R_SYM(Rel->Info)]; uintptr_t SymbolOffset = Symbol->Name; const char* SymbolName = DynInfo->DynStrTable + SymbolOffset; @@ -409,6 +421,55 @@ void RtlRelocateRelrEntries(PELF_DYNAMIC_INFO DynInfo, uintptr_t ImageBase) } } +BSTATUS RtlCheckValidity(PELF_HEADER Header) +{ + if (memcmp(Header->Identifier, "\x7F" "ELF", 4) != 0) + { + DbgPrint("Rtl: Elf has invalid header."); + return STATUS_INVALID_EXECUTABLE; + } + +#ifdef IS_64_BIT + if (Header->Identifier[ELF_IDENT_CLASS] != ELF_MCLASS_64BIT) +#else + if (Header->Identifier[ELF_IDENT_CLASS] != ELF_MCLASS_32BIT) +#endif + { + DbgPrint("Rtl: Elf isn't %d bit", sizeof(uintptr_t) * 8); + return STATUS_INVALID_ARCHITECTURE; + } + + // N.B. we don't support big endian, and probably will never + if (Header->Identifier[ELF_IDENT_DATA] != ELF_MDATA_LSB) + { + DbgPrint("Rtl: Elf is opposite endianness"); + return STATUS_INVALID_ARCHITECTURE; + } + +#if defined TARGET_AMD64 + const int Arch = ELF_ARCH_AMD64; +#elif defined TARGET_I386 + const int Arch = ELF_ARCH_386; +#endif + + if (Header->Machine != Arch) + return STATUS_INVALID_ARCHITECTURE; + + if (Header->Type != ELF_TYPE_EXECUTABLE && Header->Type != ELF_TYPE_DYNAMIC) + { + DbgPrint("Rtl: Elf is type %d which we don't know about"); + return STATUS_INVALID_EXECUTABLE; + } + + if (Header->ProgramHeaderCount == 0) + { + DbgPrint("Rtl: Elf has no program headers, probably invalid for our purposes"); + return STATUS_INVALID_EXECUTABLE; + } + + return STATUS_SUCCESS; +} + uint32_t RtlElfHash(const char* Name) { const uint8_t* NameU = (const uint8_t*) Name; diff --git a/boron/source/rtl/print.c b/boron/source/rtl/print.c index 81560d36..d47d65a7 100644 --- a/boron/source/rtl/print.c +++ b/boron/source/rtl/print.c @@ -18,6 +18,10 @@ Module name: #include #include +#ifdef DEBUG2 +#define DONT_LOCK +#endif + #ifdef KERNEL #include #include @@ -65,13 +69,13 @@ void DbgPrint(const char* msg, ...) #ifdef KERNEL // This one goes to the debug log. // Debug2 turns off the spin locks associated with the debug prints. -#ifndef DEBUG2 +#ifndef DONT_LOCK KIPL OldIpl; KeAcquireSpinLock(&KiDebugPrintLock, &OldIpl); #endif HalPrintStringDebug(buffer); -#ifndef DEBUG2 +#ifndef DONT_LOCK KeReleaseSpinLock(&KiDebugPrintLock, OldIpl); #endif diff --git a/boron/source/rtl/rbtree.c b/boron/source/rtl/rbtree.c index 5ec78203..06a742f3 100644 --- a/boron/source/rtl/rbtree.c +++ b/boron/source/rtl/rbtree.c @@ -31,6 +31,7 @@ int RtlCompareRbTreeNodes(PRBTREE_ENTRY EntryA, PRBTREE_ENTRY EntryB) // // Disable the strict aliasing warning because I'm pretty sure it'll be fine. #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic push RB_GENERATE_INTERNAL(_RBTREE_HEAD, _RBTREE_ENTRY, Entry, RtlCompareRbTreeNodes, static inline ALWAYS_INLINE); diff --git a/boron/source/rtl/string.c b/boron/source/rtl/string.c index 2ceb6d9d..a9d071a4 100644 --- a/boron/source/rtl/string.c +++ b/boron/source/rtl/string.c @@ -64,6 +64,10 @@ void* memset(void* dst, int c, size_t n) size_t strlen(const char * s) { +#if defined KERNEL && defined DEBUG + if (!s) + KeCrash("strlen(NULL)"); +#endif // Count the amount of non-zero characters in the null-terminated string. size_t sz = 0; while (*s++) sz++; diff --git a/common/include/elf.h b/common/include/elf.h index 71b8c83f..0da67348 100644 --- a/common/include/elf.h +++ b/common/include/elf.h @@ -32,6 +32,34 @@ enum ELF_IDENT_ABI_VERSION, }; +enum +{ + ELF_MCLASS_NONE, + ELF_MCLASS_32BIT, + ELF_MCLASS_64BIT, +}; + +enum +{ + ELF_MDATA_NONE, + ELF_MDATA_LSB, + ELF_MDATA_MSB, +}; + +enum +{ + ELF_ARCH_SPARC = 2, + ELF_ARCH_386 = 3, + ELF_ARCH_M68K = 4, + ELF_ARCH_MIPS = 8, + ELF_ARCH_MIPS_RS3_LE = 10, + ELF_ARCH_PPC = 20, + ELF_ARCH_PPC64 = 21, + ELF_ARCH_ARM = 40, + ELF_ARCH_IA64 = 50, + ELF_ARCH_AMD64 = 62, +}; + enum { ELF_TYPE_NONE, @@ -120,6 +148,19 @@ enum R_X86_64_8, R_X86_64_8S, //... +#elif defined TARGET_I386 + R_386_NONE, // none + R_386_32, // S + A + R_386_PC32, // S + A - P + R_386_GOT32, // G + A + R_386_PLT32, // L + A - P + R_386_COPY, // None + R_386_GLOB_DAT, // S + R_386_JUMP_SLOT, // S + R_386_RELATIVE, // B + A + R_386_GOTOFF, // S + A - GOT + R_386_GOTPC, // GOT + A - P + R_386_32PLT, // L + A #else #error Hey! Add ELF relocation types here #endif @@ -239,4 +280,12 @@ typedef struct } ELF_HASH_TABLE, *PELF_HASH_TABLE; +#ifdef IS_64_BIT +#define ELF_R_SYM(x) ((x) >> 32) +#define ELF_R_TYPE(x) ((x) & 0xFFFFFFFF) +#else +#define ELF_R_SYM(x) ((x) >> 8) +#define ELF_R_TYPE(x) ((x) & 0xFF) +#endif + #endif//BORON_ELF_H \ No newline at end of file diff --git a/common/include/mms.h b/common/include/mms.h index 6408437c..e60a0f49 100644 --- a/common/include/mms.h +++ b/common/include/mms.h @@ -36,7 +36,7 @@ enum ACCESS_FLAG }; // Page Size definition -#ifdef TARGET_AMD64 +#if defined TARGET_AMD64 || defined TARGET_I386 #define PAGE_SIZE (0x1000) #else #error Define page size here! diff --git a/common/include/rtl/check64.h b/common/include/rtl/check64.h index 3d8f5a60..e3a2543e 100644 --- a/common/include/rtl/check64.h +++ b/common/include/rtl/check64.h @@ -14,10 +14,18 @@ Module name: ***/ #pragma once -#ifdef TARGET_AMD64 -#define IS_64_BIT +#if defined TARGET_AMD64 + +#define IS_64_BIT 1 + +#elif defined TARGET_I386 + +#define IS_32_BIT 1 + #else + #error Add your platform here! + #endif // In the future it should look something like this: diff --git a/common/include/rtl/elf.h b/common/include/rtl/elf.h index 8aafc086..51870901 100644 --- a/common/include/rtl/elf.h +++ b/common/include/rtl/elf.h @@ -55,6 +55,8 @@ void RtlRelocateRelrEntries(PELF_DYNAMIC_INFO DynInfo, uintptr_t LoadBase); uint32_t RtlElfHash(const char* Name); +BSTATUS RtlCheckValidity(PELF_HEADER Header); + #ifdef __cplusplus } #endif diff --git a/drivers/CommonMakefile b/drivers/CommonMakefile index 468c3a24..cacd5cfc 100644 --- a/drivers/CommonMakefile +++ b/drivers/CommonMakefile @@ -19,14 +19,14 @@ LINKER_FILE = linker.ld DRIVER_ENTRY ?= DriverEntry -ARCHITECTURE = AMD64 +TARGET ?= AMD64 # This sucks. -ARCHITECTUREL=$(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(ARCHITECTURE))))))))))))))))))))))))))) +TARGETL=$(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(TARGET))))))))))))))))))))))))))) # This is the name that our final driver executable will have. # Change as needed. -override TARGET := $(BUILD_DIR)/out.$(ARCHITECTUREL).sys +override TARGET_DRIVER := $(BUILD_DIR)/out.$(TARGETL).sys # Convenience macro to reliably declare overridable command variables. define DEFAULT_VAR = @@ -50,20 +50,13 @@ ifeq ($(DEBUG2), yes) DEFINES += -DDEBUG2 endif -# It is highly recommended to use a custom built cross toolchain to build a driver. -# We are only using "cc" as a placeholder here. It may work by using -# the host system's toolchain, but this is not guaranteed. -$(eval $(call DEFAULT_VAR,CC,cc)) -$(eval $(call DEFAULT_VAR,CXX,c++)) - -# Same thing for "ld" (the linker). -$(eval $(call DEFAULT_VAR,LD,ld)) +include ../../tools/toolchain.mk # User controllable CFLAGS. -CFLAGS ?= $(OPT) -pipe -Wall -Wextra -DTARGET_$(ARCHITECTURE) -DIS_KERNEL_MODE $(DEFINES) $(USER_DEFINES) +CFLAGS ?= $(OPT) -pipe -Wall -Wextra -DTARGET_$(TARGET) -DBORON_TARGET=\"$(TARGETL)\" -DIS_KERNEL_MODE $(DEFINES) $(USER_DEFINES) # User controllable CXXFLAGS. -CXXFLAGS ?= $(OPT) -pipe -Wall -Wextra -DTARGET_$(ARCHITECTURE) -DIS_KERNEL_MODE $(DEFINES) $(USER_DEFINES) +CXXFLAGS ?= $(OPT) -pipe -Wall -Wextra -DTARGET_$(TARGET) -DBORON_TARGET=\"$(TARGETL)\" -DIS_KERNEL_MODE $(DEFINES) $(USER_DEFINES) # User controllable preprocessor flags. CPPFLAGS ?= -I $(INC_DIR) -I $(KE_DIR) -I $(DDK_DIR) @@ -75,28 +68,21 @@ NASMFLAGS ?= -F dwarf -I$(SRC_DIR) -I$(INC_DIR) -I $(KE_DIR) -I $(DDK_DIR) LDFLAGS ?= # Internal C flags that should not be changed by the user. -override CFLAGS += \ +CFLAGS += \ -fno-omit-frame-pointer \ - -std=c11 \ + -std=c2x \ -ffreestanding \ -fno-stack-protector \ -fno-stack-check \ -fno-lto \ -fPIC \ - -m64 \ - -march=x86-64 \ - -mabi=sysv \ - -mno-80387 \ - -mno-mmx \ - -mno-sse \ - -mno-sse2 \ - -mno-red-zone \ -MMD \ -MP \ - -I. + -I. \ + $(ARCH_CFLAGS) # Internal C++ flags that should not be changed by the user. -override CXXFLAGS += \ +CXXFLAGS += \ -fno-omit-frame-pointer \ -std=c++17 \ -ffreestanding \ @@ -104,40 +90,33 @@ override CXXFLAGS += \ -fno-stack-check \ -fno-lto \ -fPIC \ - -m64 \ - -march=x86-64 \ - -mabi=sysv \ - -mno-80387 \ - -mno-mmx \ - -mno-sse \ - -mno-sse2 \ - -mno-red-zone \ + -fno-reorder-functions \ -MMD \ -MP \ -fno-exceptions \ -fno-rtti \ - -I. + -I. \ + $(ARCH_CFLAGS) -override LDFLAGSBASE += \ +LDFLAGSBASE += \ -nostdlib -shared \ -e $(DRIVER_ENTRY) \ - -m elf_x86_64 \ - -z max-page-size=0x1000 + -m $(LINK_ARCH) \ + $(ARCH_LDFLAGS) # Internal linker flags that should not be changed by the user. -override LDFLAGS += \ +LDFLAGS += \ $(LDFLAGSBASE) # Internal nasm flags that should not be changed by the user. -override NASMFLAGS += \ - -f elf64 +NASMFLAGS += $(ARCH_ASFLAGS) # Use find to glob all *.c, *.S, and *.asm files in the directory and extract the object names. -override CFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.c') -override CXXFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.cpp') -override ASFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.S') -override NASMFILES := $(shell find $(SRC_DIR) -not -path '*/.*' -type f -name '*.asm') -override OBJ := $(patsubst %.o,%.$(ARCHITECTUREL).o,$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(CFILES:.c=.o) $(CXXFILES:.cpp=.o) $(ASFILES:.S=.o) $(NASMFILES:.asm=.o))) +override CFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -name '*.c') +override CXXFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -name '*.cpp') +override ASFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -name '*.S') +override NASMFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -name '*.asm') +override OBJ := $(patsubst %.o,%.$(TARGETL).o,$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(CFILES:.c=.o) $(CXXFILES:.cpp=.o) $(ASFILES:.S=.o) $(NASMFILES:.asm=.o))) override HEADER_DEPS := $(patsubst %.o,%.d,$(OBJ)) # Default target. @@ -145,41 +124,41 @@ override HEADER_DEPS := $(patsubst %.o,%.d,$(OBJ)) all: driver # Link rules for the final driver executable. -$(TARGET): $(OBJ) - @echo "[LD]\tBuilding $(TARGET)" - @$(LD) $(OBJ) $(LDFLAGS) -o $@ +$(TARGET_DRIVER): $(OBJ) + @echo "[LD]\tBuilding $(TARGET_DRIVER)" + @$(BLD) $(OBJ) $(LDFLAGS) -o $@ # Include header dependencies. -include $(HEADER_DEPS) # Compilation rules for *.c files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.c +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.c @echo "[CC]\tCompiling $<" @mkdir -p $(dir $@) - @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + @$(BCC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ # Compilation rules for *.cpp files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.cpp +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.cpp @echo "[CXX]\tCompiling $<" @mkdir -p $(dir $@) - @$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ + @$(BCXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ # Compilation rules for *.S files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.S +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.S @echo "[AS]\tCompiling $<" @mkdir -p $(dir $@) - @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + @$(BCC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ # Compilation rules for *.asm (nasm) files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.asm +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.asm @echo "[AS]\tCompiling $<" @mkdir -p $(dir $@) - @nasm $(NASMFLAGS) $< -o $@ + @$(BASM) $(NASMFLAGS) $< -o $@ # Remove object files and the final executable. .PHONY: clean clean: @echo "Cleaning" - @rm -rf $(TARGET) $(OBJ) $(HEADER_DEPS) + @rm -rf $(TARGET_DRIVER) $(OBJ) $(HEADER_DEPS) -driver: $(TARGET) +driver: $(TARGET_DRIVER) diff --git a/drivers/ext2fs/source/inode.c b/drivers/ext2fs/source/inode.c index 5e3faa29..e45c49bd 100644 --- a/drivers/ext2fs/source/inode.c +++ b/drivers/ext2fs/source/inode.c @@ -210,6 +210,7 @@ BSTATUS Ext2OpenInode(PEXT2_FILE_SYSTEM FileSystem, uint32_t InodeNumber, PFCB* if (InodeNumber >= FileSystem->SuperBlock.InodeCount) return STATUS_INVALID_PARAMETER; + PRBTREE_ENTRY Entry = NULL; PFCB Fcb = NULL; BSTATUS Status = STATUS_INVALID_PARAMETER; @@ -217,7 +218,7 @@ BSTATUS Ext2OpenInode(PEXT2_FILE_SYSTEM FileSystem, uint32_t InodeNumber, PFCB* AcquireInodeTreeMutex(FileSystem); Retry: - PRBTREE_ENTRY Entry = LookUpItemRbTree(&FileSystem->InodeTree, InodeNumber); + Entry = LookUpItemRbTree(&FileSystem->InodeTree, InodeNumber); if (Entry) { // Get the FCB and reference it. diff --git a/drivers/framebuf/source/main.c b/drivers/framebuf/source/main.c index a77d1947..e012f287 100644 --- a/drivers/framebuf/source/main.c +++ b/drivers/framebuf/source/main.c @@ -62,7 +62,27 @@ BSTATUS FramebufferRead(PIO_STATUS_BLOCK Iosb, UNUSED PFCB Fcb, uint64_t Offset, if (Size == 0) goto SuccessZero; +#ifdef IS_64_BIT + MmCopyIntoMdl(MdlBuffer, 0, MmGetHHDMOffsetAddr(Ext->Address + Offset), Size); + +#else + + void *TempMapping = MmMapIoSpace( + Ext->Address + Offset, + Size, + MM_PTE_READWRITE | MM_PTE_CDISABLE, + POOL_TAG("FBCP") + ); + + if (!TempMapping) + return IOSB_STATUS(Iosb, STATUS_INSUFFICIENT_MEMORY); + + MmCopyIntoMdl(MdlBuffer, 0, TempMapping, Size); + + MmFreePoolBig(TempMapping); + +#endif Iosb->BytesRead = Size; return IOSB_STATUS(Iosb, STATUS_SUCCESS); @@ -92,7 +112,27 @@ BSTATUS FramebufferWrite(PIO_STATUS_BLOCK Iosb, PFCB Fcb, uint64_t Offset, PMDL if (Size == 0) goto SuccessZero; +#ifdef IS_64_BIT + MmCopyFromMdl(MdlBuffer, 0, MmGetHHDMOffsetAddr(Ext->Address + Offset), Size); + +#else + + void *TempMapping = MmMapIoSpace( + Ext->Address + Offset, + Size, + MM_PTE_READWRITE | MM_PTE_CDISABLE, + POOL_TAG("FBCP") + ); + + if (!TempMapping) + return IOSB_STATUS(Iosb, STATUS_INSUFFICIENT_MEMORY); + + MmCopyFromMdl(MdlBuffer, 0, TempMapping, Size); + + MmFreePoolBig(TempMapping); + +#endif Iosb->BytesWritten = Size; return IOSB_STATUS(Iosb, STATUS_SUCCESS); @@ -173,7 +213,13 @@ BSTATUS CreateFrameBufferObject(int Index) snprintf(Name, sizeof Name, "FrameBuffer%d", Index); PLOADER_FRAMEBUFFER Framebuffer = &KeLoaderParameterBlock.Framebuffers[Index]; + +#ifdef IS_64_BIT FbAddress = MmGetHHDMOffsetFromAddr(Framebuffer->Address); +#else + FbAddress = (uintptr_t) Framebuffer->Address; +#endif + FbWidth = Framebuffer->Width; FbHeight = Framebuffer->Height; FbPitch = Framebuffer->Pitch; diff --git a/drivers/hali386/Makefile b/drivers/hali386/Makefile new file mode 100644 index 00000000..644ea9f4 --- /dev/null +++ b/drivers/hali386/Makefile @@ -0,0 +1,10 @@ +# The Boron Operating System +# Common makefile for all driver targets + +DRIVER_NAME = hali386 +# DRIVER_ENTRY = DriverEntry +# DEBUG = yes +# DEBUG2 = no +USER_DEFINES = -DIS_HAL -DFLANTERM_FB_DISABLE_CANVAS -DFLANTERM_FB_DISABLE_BUMP_ALLOC + +include ../CommonMakefile diff --git a/drivers/hali386/linker.ld b/drivers/hali386/linker.ld new file mode 100644 index 00000000..a2c76e0e --- /dev/null +++ b/drivers/hali386/linker.ld @@ -0,0 +1,65 @@ +/* Tell the linker that we want an x86_64 ELF64 output file */ +OUTPUT_FORMAT(elf64-x86-64) +OUTPUT_ARCH(i386:x86-64) + +/* We want the symbol DriverEntry to be our entry point */ +ENTRY(DriverEntry) + +/* Define the program headers we want so the bootloader gives us the right */ +/* MMU permissions */ +PHDRS +{ + text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ + rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ + data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ +} + +SECTIONS +{ + /* We want to be placed in the topmost 2GiB of the address space, for optimizations, and because that is what the Limine spec mandates. */ + /* Any address in this region will do, but often 0xffffffff80000000 is chosen as that is the beginning of the region. */ + . = 0xffffffff80000000; + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + /* Global constructor array. */ + .init_array : { + g_init_array_start = .; + *(.init_array) + g_init_array_end = .; + } + + /* Global destructor array. */ + .fini_array : { + g_fini_array_start = .; + *(.fini_array) + g_fini_array_end = .; + } + + .data : { + *(.data .data.*) + } :data + + .bss : { + *(COMMON) + *(.bss .bss.*) + } :data + + /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */ + /DISCARD/ : { + *(.eh_frame) + *(.note .note.*) + } +} diff --git a/drivers/hali386/source/crash.c b/drivers/hali386/source/crash.c new file mode 100644 index 00000000..9b5aac15 --- /dev/null +++ b/drivers/hali386/source/crash.c @@ -0,0 +1,26 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ha/crash.c + +Abstract: + This module contains the i386 platform's specific crash routine. + +Author: + iProgramInCpp - 17 October 2025 +***/ +#include +#include "hali.h" + +HAL_API void HalProcessorCrashed() +{ + KeStopCurrentCPU(); +} + +void HalCrashSystem(const char* Message) +{ + DISABLE_INTERRUPTS(); + KeCrashConclusion(Message); +} diff --git a/drivers/hali386/source/flanterm/backends/fb.c b/drivers/hali386/source/flanterm/backends/fb.c new file mode 120000 index 00000000..27ed2932 --- /dev/null +++ b/drivers/hali386/source/flanterm/backends/fb.c @@ -0,0 +1 @@ +../../../../halx86/source/flanterm/backends/fb.c \ No newline at end of file diff --git a/drivers/hali386/source/flanterm/backends/fb.h b/drivers/hali386/source/flanterm/backends/fb.h new file mode 120000 index 00000000..e6425e4f --- /dev/null +++ b/drivers/hali386/source/flanterm/backends/fb.h @@ -0,0 +1 @@ +../../../../halx86/source/flanterm/backends/fb.h \ No newline at end of file diff --git a/drivers/hali386/source/flanterm/flanterm.c b/drivers/hali386/source/flanterm/flanterm.c new file mode 120000 index 00000000..91ceed44 --- /dev/null +++ b/drivers/hali386/source/flanterm/flanterm.c @@ -0,0 +1 @@ +../../../halx86/source/flanterm/flanterm.c \ No newline at end of file diff --git a/drivers/hali386/source/flanterm/flanterm.h b/drivers/hali386/source/flanterm/flanterm.h new file mode 120000 index 00000000..d1e636d5 --- /dev/null +++ b/drivers/hali386/source/flanterm/flanterm.h @@ -0,0 +1 @@ +../../../halx86/source/flanterm/flanterm.h \ No newline at end of file diff --git a/drivers/hali386/source/font.h b/drivers/hali386/source/font.h new file mode 120000 index 00000000..329925a8 --- /dev/null +++ b/drivers/hali386/source/font.h @@ -0,0 +1 @@ +../../halx86/source/font.h \ No newline at end of file diff --git a/drivers/hali386/source/hali.h b/drivers/hali386/source/hali.h new file mode 100644 index 00000000..9d02a5c1 --- /dev/null +++ b/drivers/hali386/source/hali.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#define HAL_API // specify calling convention here if needed + +#define PIT_TICK_FREQUENCY (1193182) +#define PIT_CHANNEL_0_PORT (0x40) +#define PIT_COMMAND_PORT (0x43) + +#define PIC1_COMMAND (0x20) +#define PIC1_DATA (0x21) +#define PIC2_COMMAND (0xA0) +#define PIC2_DATA (0xA1) + +#define PIC_CMD_EOI (0x20) + +// From the 8259A datasheet. +#define PIC_ICW1_ENABLE_ICW4 (1 << 0) +#define PIC_ICW1_INITIALIZE (1 << 4) // always 1 in the data sheet +#define PIC_ICW3_PRI_CONFIG (1 << 2) // there is a sub PIC and it's IRQ 2 +#define PIC_ICW3_SUB_CONFIG (2) // the sub PIC cascades at IRQ 2 +#define PIC_ICW4_8086_MODE (1 << 0) + +#define PIT_CMD_CHANNEL_0 (0 << 6) +#define PIT_CMD_ACCESS_2_BYTES (3 << 4) +#define PIT_CMD_RATE_GENERATOR (2 << 1) + +// ====== Port Utils ====== +void KePortWriteByteWait(uint16_t Port, uint8_t Data); + +// ====== Timer ====== +void HalUpdatePitClock(); + +HAL_API uint64_t HalGetTickFrequency(); + +HAL_API uint64_t HalGetTickCount(); + +HAL_API bool HalUseOneShotIntTimer(); + +HAL_API uint64_t HalGetIntTimerFrequency(); + +HAL_API void HalRequestInterruptInTicks(uint64_t ticks); + +HAL_API uint64_t HalGetIntTimerDeltaTicks(); + +void HalInitTimer(); + +// ====== Interrupt Controller ====== +HAL_API void HalInitPic(); + +HAL_API void HalEndOfInterrupt(int InterruptNumber); + +HAL_API void HalRegisterInterrupt(uint8_t Vector, KIPL Ipl); + +HAL_API void HalDeregisterInterrupt(uint8_t Vector, KIPL Ipl); + + + diff --git a/drivers/hali386/source/init.c b/drivers/hali386/source/init.c new file mode 100644 index 00000000..bf1060e4 --- /dev/null +++ b/drivers/hali386/source/init.c @@ -0,0 +1,70 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ha/init.c + +Abstract: + This module contains the two initialization functions + (UP-init and MP-init) + +Author: + iProgramInCpp - 16 October 2025 +***/ +#include +#include "hali.h" +#include "pci.h" + +static const HAL_VFTABLE HalpVfTable = +{ + .EndOfInterrupt = HalEndOfInterrupt, + .RequestInterruptInTicks = HalRequestInterruptInTicks, + .RequestIpi = HalRequestIpi, + .InitSystemUP = HalInitSystemUP, + .InitSystemMP = HalInitSystemMP, + .DisplayString = HalDisplayString, + .CrashSystem = HalCrashSystem, + .ProcessorCrashed = HalProcessorCrashed, + .UseOneShotIntTimer = HalUseOneShotIntTimer, + .GetIntTimerFrequency = HalGetIntTimerFrequency, + .GetTickCount = HalGetTickCount, + .GetTickFrequency = HalGetTickFrequency, + .GetIntTimerDeltaTicks = HalGetIntTimerDeltaTicks, + .PicRegisterInterrupt = HalPicRegisterInterrupt, + .PicDeregisterInterrupt = HalPicDeregisterInterrupt, + .PciEnumerate = HalPciEnumerate, + .PciConfigReadDword = HalPciConfigReadDword, + .PciConfigReadWord = HalPciConfigReadWord, + .PciConfigWriteDword = HalPciConfigWriteDword, + .PciReadDeviceIdentifier = HalPciReadDeviceIdentifier, + .PciReadBar = HalPciReadBar, + .PciReadBarAddress = HalPciReadBarAddress, + .PciReadBarIoAddress = HalPciReadBarIoAddress, + .Flags = HAL_VFTABLE_LOADED, +}; + +// Initialize the HAL on the BSP, for all processors. +HAL_API void HalInitSystemUP() +{ + HalInitPic(); + HalInitTerminal(); +} + +// Initialize the HAL separately for each processor. +// This function is run on ALL processors. +HAL_API void HalInitSystemMP() +{ + HalInitTimer(); +} + +BSTATUS DriverEntry(UNUSED PDRIVER_OBJECT Object) +{ + // Note! The HAL's driver object is kind of useless, it doesn't do anything actually. + + // Hook the HAL's functions. + HalSetVftable(&HalpVfTable); + + // And return, that's all we need. + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/drivers/hali386/source/pci.c b/drivers/hali386/source/pci.c new file mode 100644 index 00000000..cd52ea73 --- /dev/null +++ b/drivers/hali386/source/pci.c @@ -0,0 +1,335 @@ +/*** + The Boron Operating System + Copyright (C) 2023 iProgramInCpp + +Module name: + pci.c + +Abstract: + This module defines the base PCI functions. + +Author: + iProgramInCpp - 4 July 2024 +***/ +#include "pio.h" +#include "pci.h" + +static void PcipWriteAddress(PPCI_ADDRESS Address, uint8_t Offset) +{ + KePortWriteDword( + PCI_CONFIG_ADDRESS, + (Address->Bus << 16) | + (Address->Slot << 11) | + (Address->Function << 8) | + (Offset & 0xFC) | + 0x80000000 + ); +} + +uint32_t HalPciConfigReadDword(PPCI_ADDRESS Address, uint8_t Offset) +{ + PcipWriteAddress(Address, Offset); + return KePortReadDword(PCI_CONFIG_DATA); +} + +void HalPciConfigWriteDword(PPCI_ADDRESS Address, uint8_t Offset, uint32_t Data) +{ + PcipWriteAddress(Address, Offset); + KePortWriteDword(PCI_CONFIG_DATA, Data); +} + +uint16_t HalPciConfigReadWord(PPCI_ADDRESS Address, uint8_t Offset) +{ + PcipWriteAddress(Address, Offset); + return (uint16_t)(KePortReadDword(PCI_CONFIG_DATA) >> (8 * (Offset & 2))); +} + +void HalPciReadDeviceIdentifier(PPCI_ADDRESS Address, PPCI_IDENTIFIER OutIdentifier) +{ + OutIdentifier->VendorAndDeviceId = HalPciConfigReadDword(Address, PCI_OFFSET_DEVICE_IDENTIFIER); +} + +uint32_t HalPciReadBar(PPCI_ADDRESS Address, int BarIndex) +{ + return HalPciConfigReadDword(Address, PCI_OFFSET_BAR0 + 4 * BarIndex); +} + +uintptr_t HalPciReadBarAddress(PPCI_ADDRESS Address, int BarIndex) +{ + uint32_t LowBar = HalPciReadBar(Address, BarIndex); + + // Check if the BAR is actually for an I/O space device. + if (LowBar & 1) + return 0; + + // Check the type of BAR. + int Type = (int) (LowBar & 0b110) >> 1; + + // NOTE: This source file will probably be shared between the 32-bit and + // 64-bit x86 HALs, explaining this check. +#ifndef IS_64_BIT + if (Type == 2) + Type = 0; +#endif + + if (Type == 0) + // 32-bit BAR + return LowBar & ~0xF; + + if (Type != 2) + // Unknown type of BAR + return 0; + + return ((uint64_t)HalPciReadBar(Address, BarIndex + 1) << 32) | (LowBar & ~0xF); +} + +uint32_t HalPciReadBarIoAddress(PPCI_ADDRESS Address, int BarIndex) +{ + uint32_t Bar = HalPciReadBar(Address, BarIndex); + + // Check if the BAR is actually for a memory space device. + if (~Bar & 1) + return 0; + + return Bar & ~0x3; +} + +PPCI_DEVICE HalpPciDevices; +size_t HalpPciDeviceCount, HalpPciDeviceCapacity; +KSPIN_LOCK HalpPciDeviceSpinLock; + +static void HalpPciAddDevice(PPCI_DEVICE Device) +{ + // Process this device's capabilities list, if it exists. + uint16_t Status = HalPciConfigReadWord(&Device->Address, PCI_OFFSET_STATUS_COMMAND + 2); + + if (Status & PCI_STA_CAPABILITIESLIST) + { + // The capabilities pointer is stored at offset 0x34. + // Trim the upper 24 bits as the header is only 256 bytes in size, and also the lower 4 bits. + uint32_t CapabilityOffset = HalPciConfigReadDword(&Device->Address, PCI_OFFSET_CAPABILITIES_PTR); + CapabilityOffset &= 0xFC; + + // The capability offset linked list is terminated with a zero. + while (CapabilityOffset != 0) + { + uint32_t UpperDword = HalPciConfigReadDword(&Device->Address, CapabilityOffset); + uint8_t Capability = UpperDword & 0xFF; + + // Determine the capability's ID. + switch (Capability) + { + case PCI_CAP_MSI: + { + Device->MsiData.Exists = true; + Device->MsiData.CapabilityOffset = CapabilityOffset; + break; + } + + case PCI_CAP_MSI_X: + { + Device->MsixData.Exists = true; + Device->MsixData.CapabilityOffset = CapabilityOffset; + + // Disable interrupts on the capability, and read information from it. + UpperDword |= PCI_MSIX_MC_ENABLE << 16; + UpperDword |= PCI_MSIX_MC_FUNMASK << 16; + + HalPciConfigWriteDword(&Device->Address, CapabilityOffset, UpperDword); + + uint32_t Command = HalPciConfigReadDword(&Device->Address, PCI_OFFSET_STATUS_COMMAND); + Command |= PCI_CMD_INTERRUPTDISABLE; + HalPciConfigWriteDword(&Device->Address, PCI_OFFSET_STATUS_COMMAND, Command); + + Device->MsixData.TableSize = PCI_MSIX_MC_TABLESIZE(UpperDword >> 16); + + uint32_t Bir = HalPciConfigReadDword(&Device->Address, CapabilityOffset + 0x04); + Device->MsixData.Bir = Bir & 0x7; + Device->MsixData.TableOffset = Bir & ~0x7; + + uint32_t Pbir = HalPciConfigReadDword(&Device->Address, CapabilityOffset + 0x04); + Device->MsixData.Pbir = Pbir & 0x7; + Device->MsixData.PendingTableOffset = Pbir & ~0x7; + + break; + } + + default: + { + PciDbgPrint( + "PCI device %04x_%04x at %d.%d.%d has unrecognised capability %02x", + Device->Identifier.VendorId, + Device->Identifier.DeviceId, + Device->Address.Bus, + Device->Address.Slot, + Device->Address.Function, + Capability + ); + break; + } + } + + // Jump to the next capability offset. + CapabilityOffset = (UpperDword >> 8) & 0xFC; + } + } + + // Now, add it into the list of known PCI devices. + KIPL Ipl; + KeAcquireSpinLock(&HalpPciDeviceSpinLock, &Ipl); + + size_t Index; + if ((Index = HalpPciDeviceCount++) == HalpPciDeviceCapacity) + { + size_t OldCapacity = HalpPciDeviceCapacity; + + if (HalpPciDeviceCapacity < 16) + HalpPciDeviceCapacity = 16; + else + HalpPciDeviceCapacity *= 2; + + PPCI_DEVICE NewDevices = MmAllocatePool(POOL_NONPAGED, sizeof(PCI_DEVICE) * HalpPciDeviceCapacity); + if (!NewDevices) + { + KeCrash( + "WARNING: Could not add PCI device with address %d,%d,%d due to an out of memory condition", + Device->Address.Bus, + Device->Address.Slot, + Device->Address.Function + ); + } + + memset(NewDevices + OldCapacity, 0, sizeof(PCI_DEVICE) * (HalpPciDeviceCapacity - OldCapacity)); + + if (HalpPciDevices) + { + memcpy(NewDevices, HalpPciDevices, sizeof(PCI_DEVICE) * OldCapacity); + MmFreePool(HalpPciDevices); + } + HalpPciDevices = NewDevices; + } + + HalpPciDevices[Index] = *Device; + + KeReleaseSpinLock(&HalpPciDeviceSpinLock, Ipl); +} + +void HalPciProbe() +{ + PciDbgPrint("Probing PCI devices..."); + + // TODO: Better way to probe devices. + PCI_DEVICE Device; + + for (int Bus = 0; Bus < PCI_MAX_BUS; Bus++) + { + for (int Slot = 0; Slot < PCI_MAX_SLOT; Slot++) + { + for (int Function = 0; Function < PCI_MAX_FUNC; Function++) + { + memset(&Device, 0, sizeof Device); + + Device.Address.Bus = Bus; + Device.Address.Slot = Slot; + Device.Address.Function = Function; + + HalPciReadDeviceIdentifier(&Device.Address, &Device.Identifier); + + if (Device.Identifier.VendorId == 0xFFFF) + continue; + + //DbgPrint("Found PCI device. Vendor ID: %04x, Device ID: %04x", Device.Identifier.VendorId, Device.Identifier.DeviceId); + + // Also fetch its class register + Device.Class.Register = HalPciConfigReadDword(&Device.Address, PCI_OFFSET_REVISION_CLASS); + + HalpPciAddDevice(&Device); + } + } + } + + // List all devices. + KIPL Ipl; + KeAcquireSpinLock(&HalpPciDeviceSpinLock, &Ipl); + +#ifdef PCIDEBUG + for (size_t i = 0; i < HalpPciDeviceCount; i++) + { + PPCI_DEVICE Device = &HalpPciDevices[i]; + + PciDbgPrint( + "DEVICE: Bus %02x, Slot %02x, Function %1x, Vendor ID: %04x, Device ID: %04x, Class: %02x, SubClass: %02x, ProgIF: %02x, Rev: %02x. Ints: %s", + Device->Address.Bus, + Device->Address.Slot, + Device->Address.Function, + Device->Identifier.VendorId, + Device->Identifier.DeviceId, + Device->Class.Class, + Device->Class.SubClass, + Device->Class.ProgIF, + Device->Class.Revision, + Device->MsixData.Exists ? "MSI-X" : (Device->MsiData.Exists ? "MSI" : "No Interrupts") + ); + } +#endif + + KeReleaseSpinLock(&HalpPciDeviceSpinLock, Ipl); + + DbgPrint("PCI device probe complete."); +} + +BSTATUS +HalPciEnumerate( + bool LookUpByIds, + size_t IdCount, + PPCI_IDENTIFIER Identifiers, + uint8_t ClassCode, + uint8_t SubClassCode, + PHAL_PCI_ENUMERATE_CALLBACK Callback, + void* CallbackContext +) +{ + bool FoundDevices = false; + + for (size_t i = 0; i < HalpPciDeviceCount; i++) + { + PPCI_DEVICE Device = &HalpPciDevices[i]; + + // Check if the device matches. + if (LookUpByIds) + { + bool Is = false; + + for (size_t j = 0; !Is && j < IdCount; j++) + { + if (Device->Identifier.VendorAndDeviceId == Identifiers[j].VendorAndDeviceId) + Is = true; + } + + if (Is) + { + if (Callback(Device, CallbackContext)) + FoundDevices = true; + } + } + else + { + if (ClassCode == Device->Class.Class && + (SubClassCode == PCI_SUBCLASS_ANY || SubClassCode == Device->Class.SubClass)) + { + if (Callback(Device, CallbackContext)) + FoundDevices = true; + } + } + } + + if (FoundDevices) + return STATUS_SUCCESS; + else + return STATUS_NO_SUCH_DEVICES; +} + +void HalInitPci() +{ + HalPciProbe(); +} \ No newline at end of file diff --git a/drivers/hali386/source/pci.h b/drivers/hali386/source/pci.h new file mode 100644 index 00000000..a0eb3b7b --- /dev/null +++ b/drivers/hali386/source/pci.h @@ -0,0 +1,55 @@ +/*** + The Boron Operating System + Copyright (C) 2024 iProgramInCpp + +Module name: + pci.h + +Abstract: + This header defines PCI-related structures and + function prototypes. + +Author: + iProgramInCpp - 4 July 2024 +***/ +#pragma once + +#include +#include +#include +#include // includes hal/pci.h which defines certain types +#include + +#define PCI_CONFIG_ADDRESS (0xCF8) +#define PCI_CONFIG_DATA (0xCFC) + +#define PCI_VENDOR_INVALID (0xFFFF) + +// PCI capability IDs. +#define PCI_CAP_PCI_EXPRESS (0x01) +#define PCI_CAP_MSI (0x05) +#define PCI_CAP_MSI_X (0x11) + +#define PCI_MAX_BUS (256) +#define PCI_MAX_SLOT (32) +#define PCI_MAX_FUNC (8) + +#ifdef DEBUG2 +#define PCIDEBUG +#endif + +#ifdef PCIDEBUG +#define PciDbgPrint(...) DbgPrint(__VA_ARGS__) +#else +#define PciDbgPrint(...) do { } while (0) +#endif + +void HalInitPci(); + +uint32_t HalPciConfigReadDword(PPCI_ADDRESS Address, uint8_t Offset); +uint16_t HalPciConfigReadWord(PPCI_ADDRESS Address, uint8_t Offset); +void HalPciConfigWriteDword(PPCI_ADDRESS Address, uint8_t Offset, uint32_t Data); +void HalPciReadDeviceIdentifier(PPCI_ADDRESS Address, PPCI_IDENTIFIER OutIdentifier); +uint32_t HalPciReadBar(PPCI_ADDRESS Address, int BarIndex); +uint32_t HalPciReadBarIoAddress(PPCI_ADDRESS Address, int BarIndex); +uintptr_t HalPciReadBarAddress(PPCI_ADDRESS Address, int BarIndex); \ No newline at end of file diff --git a/drivers/hali386/source/pic.c b/drivers/hali386/source/pic.c new file mode 100644 index 00000000..6c28f751 --- /dev/null +++ b/drivers/hali386/source/pic.c @@ -0,0 +1,116 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ha/pic.c + +Abstract: + This module contains support routines for the PIC. + +Author: + iProgramInCpp - 16 October 2025 +***/ +#include +#include "hali.h" + +uint16_t KiIplTable[] = { + 0xFFFF, // IPL_NORMAL + 0xFFFF, // IPL_1 + 0xFFFF, // IPL_2 + 0xFFFF, // IPL_APC + 0xFFFF, // IPL_DPC + 0xFFFF, // IPL_DEVICES0 + 0xFFFF, // IPL_DEVICES1 + 0xFFFF, // IPL_DEVICES2 + 0xFFFF, // IPL_DEVICES3 + 0xFFFF, // IPL_DEVICES4 + 0xFFFF, // IPL_DEVICES5 + 0xFFFF, // IPL_DEVICES6 + 0xFFFF, // IPL_DEVICES7 + 0xFFFF, // IPL_DEVICES8 + 0xFFFF, // IPL_CLOCK + 0xFFFF, // IPL_NOINTS +}; +static_assert(ARRAY_COUNT(KiIplTable) == IPL_COUNT); + +void KePortWriteByteWait(uint16_t Port, uint8_t Data) +{ + KePortWriteByte(Port, Data); + KeSpinningHint(); +} + +HAL_API void HalRequestIpi(UNUSED uint32_t HardwareId, UNUSED uint32_t Flags, UNUSED int Vector) +{ + DbgPrint("HalRequestIpi not implemented"); +} + +HAL_API void HalEndOfInterrupt(int InterruptNumber) +{ + if (InterruptNumber >= 0x28) + KePortWriteByte(PIC2_COMMAND, PIC_CMD_EOI); + + KePortWriteByte(PIC1_COMMAND, PIC_CMD_EOI); +} + +HAL_API void HalPicRegisterInterrupt(uint8_t Vector, KIPL Ipl) +{ + if (Vector < PIC_INTERRUPT_BASE || Vector >= PIC_INTERRUPT_BASE + 16) + { + DbgPrint("HalRegisterInterrupt: Dropping vector %zu", Vector); + return; + } + + bool Restore = KeDisableInterrupts(); + + Vector -= PIC_INTERRUPT_BASE; + for (KIPL i = 0; i < Ipl; i++) + KiIplTable[i] &= ~(1 << Vector); + + KeRestoreInterrupts(Restore); + + // TODO: just unmask everything for now. Should we even use the other masks? + KePortWriteByteWait(PIC1_DATA, KiIplTable[0] & 0xFF); + KePortWriteByteWait(PIC2_DATA, KiIplTable[0] >> 8); +} + +// NOTE: There are no more uses of this interrupt once this function is called. I hope. +HAL_API void HalPicDeregisterInterrupt(uint8_t Vector, KIPL Ipl) +{ + if (Vector < PIC_INTERRUPT_BASE || Vector >= PIC_INTERRUPT_BASE + 16) + { + DbgPrint("HalRegisterInterrupt: Dropping vector %zu", Vector); + return; + } + + bool Restore = KeDisableInterrupts(); + + Vector -= PIC_INTERRUPT_BASE; + for (KIPL i = 0; i < Ipl; i++) + KiIplTable[i] |= 1 << Vector; + + KeRestoreInterrupts(Restore); +} + +HAL_API void HalInitPic() +{ + // ICW1: Initialization Command + KePortWriteByteWait(PIC1_COMMAND, PIC_ICW1_INITIALIZE | PIC_ICW1_ENABLE_ICW4); + KePortWriteByteWait(PIC2_COMMAND, PIC_ICW1_INITIALIZE | PIC_ICW1_ENABLE_ICW4); + + // ICW2: Base offset of interrupts + KePortWriteByteWait(PIC1_DATA, PIC_INTERRUPT_BASE); + KePortWriteByteWait(PIC2_DATA, PIC_INTERRUPT_BASE + 8); + + // ICW3: Primary/secondary relationship + KePortWriteByteWait(PIC1_DATA, PIC_ICW3_PRI_CONFIG); + KePortWriteByteWait(PIC2_DATA, PIC_ICW3_SUB_CONFIG); + + // ICW4: Mode + KePortWriteByteWait(PIC1_DATA, PIC_ICW4_8086_MODE); + KePortWriteByteWait(PIC2_DATA, PIC_ICW4_8086_MODE); + + // Mask ALL interrupts + KePortWriteByteWait(PIC1_DATA, 0xFF); + KePortWriteByteWait(PIC2_DATA, 0xFF); +} diff --git a/drivers/hali386/source/pio.h b/drivers/hali386/source/pio.h new file mode 100644 index 00000000..95bd3dac --- /dev/null +++ b/drivers/hali386/source/pio.h @@ -0,0 +1,27 @@ +/*** + The Boron Operating System + Copyright (C) 2023 iProgramInCpp + +Module name: + arch/amd64/pio.h + +Abstract: + This module implements x86 specific Port I/O functions. + +Author: + iProgramInCpp - 7 September 2023 +***/ + +#ifndef NS64_PIO_H +#define NS64_PIO_H + +#include + +uint8_t KePortReadByte(uint16_t portNo); +void KePortWriteByte(uint16_t portNo, uint8_t data); +uint16_t KePortReadWord(uint16_t portNo); +void KePortWriteWord(uint16_t portNo, uint16_t data); +uint32_t KePortReadDword(uint16_t portNo); +void KePortWriteDword(uint16_t portNo, uint32_t data); + +#endif//NS64_PIO_H diff --git a/drivers/hali386/source/term.c b/drivers/hali386/source/term.c new file mode 100644 index 00000000..3986ec50 --- /dev/null +++ b/drivers/hali386/source/term.c @@ -0,0 +1,139 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ha/term.c + +Abstract: + This module implements the terminal functions for the i386 + platform. + +Author: + iProgramInCpp - 17 October 2025 +***/ +#include +#include +#include "hali.h" +#include "flanterm/flanterm.h" +#include "flanterm/backends/fb.h" +#include "font.h" + +// NOTE: Initialization done on the BSP. So no need to sync anything +uint8_t* HalpTerminalMemory; +size_t HalpTerminalMemoryHead; +size_t HalpTerminalMemorySize; + +static struct flanterm_context* HalpTerminalContext; + +bool HalIsTerminalInitted() +{ + return HalpTerminalContext != NULL; +} + +static void* HalpTerminalMemAlloc(size_t sz) +{ + if (HalpTerminalMemoryHead + sz > HalpTerminalMemorySize) + { + DbgPrint("Error, running out of memory in the terminal heap"); + return NULL; + } + + uint8_t* pCurMem = &HalpTerminalMemory[HalpTerminalMemoryHead]; + HalpTerminalMemoryHead += sz; + return pCurMem; +} + +static void HalpTerminalFree(UNUSED void* pMem, UNUSED size_t sz) +{ +} + +bool HalpIsSerialAvailable; + +void HalInitTerminal() +{ + if (KeLoaderParameterBlock.FramebufferCount == 0) + KeCrashBeforeSMPInit("HAL: No framebuffers found"); + + PLOADER_FRAMEBUFFER Framebuffer = &KeLoaderParameterBlock.Framebuffers[0]; + uint32_t defaultBG = 0x0000007f; + uint32_t defaultFG = 0x00ffffff; + + // on a 1280x800 screen, the term will have a rez of 160x50 (8000 chars). + // 52 bytes per character. + + const int charWidth = 8, charHeight = 16; + int charScale = 1; + + int termBufWidth = Framebuffer->Width / charWidth / charScale; + int termBufHeight = Framebuffer->Height / charHeight / charScale; + + const int usagePerChar = 52; // I calculated it + const int fontBoolMemUsage = charWidth * charHeight * 256; // there are 256 chars + const int fontDataMemUsage = charWidth * charHeight * 256 / 8; + const int contextSize = sizeof(struct flanterm_fb_context); + + int totalMemUsage = contextSize + fontDataMemUsage + fontBoolMemUsage + termBufWidth * termBufHeight * usagePerChar; + size_t sizePages = (totalMemUsage + PAGE_SIZE - 1) / PAGE_SIZE; + + void* FramebufferMemory = MmMapIoSpace( + (uintptr_t)Framebuffer->Address, + Framebuffer->Pitch * Framebuffer->Height, + MM_PTE_READWRITE | MM_PTE_CDISABLE, + POOL_TAG("HAFB") + ); + + DbgPrint("Screen resolution: %d by %d. Will use %d Bytes. Framebuffer mapped at %p.", Framebuffer->Width, Framebuffer->Height, sizePages * PAGE_SIZE, FramebufferMemory); + + HalpTerminalMemory = MmAllocatePoolBig(POOL_FLAG_NON_PAGED, sizePages, POOL_TAG("Term")); + HalpTerminalMemoryHead = 0; + HalpTerminalMemorySize = sizePages * PAGE_SIZE; + + HalpTerminalContext = flanterm_fb_init( + &HalpTerminalMemAlloc, + &HalpTerminalFree, + FramebufferMemory, + Framebuffer->Width, + Framebuffer->Height, + Framebuffer->Pitch, + Framebuffer->RedMaskSize, Framebuffer->RedMaskShift, // red mask size and shift + Framebuffer->GreenMaskSize, Framebuffer->GreenMaskShift, // green mask size and shift + Framebuffer->BlueMaskSize, Framebuffer->BlueMaskShift, // blue mask size and shift + NULL, // canvas + NULL, // ansi colors + NULL, // ansi bright colors + &defaultBG, // default background + &defaultFG, // default foreground + NULL, // default background bright + NULL, // default fontground bright + HalpBuiltInFont, // font pointer + charWidth, // font width + charHeight, // font height + 0, // character spacing X + charScale, // character scale width + charScale, // character scale height + 0 // character spacing Y + ); + + if (!HalpTerminalContext) + { + KeCrashBeforeSMPInit("Error, no terminal context"); + } +} + +HAL_API void HalDisplayString(const char* Message) +{ + if (!HalpTerminalContext) + { + HalPrintStringDebug(Message); + return; + } + + size_t Length = strlen(Message); + + static KSPIN_LOCK SpinLock; + KIPL Ipl; + KeAcquireSpinLock(&SpinLock, &Ipl); + flanterm_write(HalpTerminalContext, Message, Length); + KeReleaseSpinLock(&SpinLock, Ipl); +} diff --git a/drivers/hali386/source/timer.c b/drivers/hali386/source/timer.c new file mode 100644 index 00000000..8a1b5376 --- /dev/null +++ b/drivers/hali386/source/timer.c @@ -0,0 +1,106 @@ +/*** + The Boron Operating System + Copyright (C) 2025 iProgramInCpp + +Module name: + ha/timer.c + +Abstract: + This module contains the two initialization functions + (UP-init and MP-init) + +Author: + iProgramInCpp - 16 October 2025 +***/ +#include +#include "hali.h" + +// TODO: Decide on a good tick rate. For slower systems, increased +// time slice time would be better. +#define TIMER_RELOAD_VALUE 15000 + +// This value is incremented by TIMER_RELOAD_VALUE on every timer interrupt. +static uint64_t PitTicksPassed; +static bool PitInitialized; + +HAL_API uint64_t HalGetTickFrequency() +{ + return PIT_TICK_FREQUENCY; +} + +HAL_API uint64_t HalGetTickCount() +{ + if (!PitInitialized) + return 0; + + KePortWriteByte(PIT_COMMAND_PORT, 0x00); // latch + + bool Restore = KeDisableInterrupts(); + static uint64_t LastValue = 0; + + uint8_t Low, High; + Low = KePortReadByte(PIT_CHANNEL_0_PORT); + High = KePortReadByte(PIT_CHANNEL_0_PORT); + + uint16_t ReadIn = (Low | (High << 8)); + uint16_t Timer = TIMER_RELOAD_VALUE - ReadIn; + uint64_t Value = PitTicksPassed + Timer; + + if (Value < LastValue) + { + // Seems like there's been an extended period of time where interrupts + // were disabled? Not sure. But what I know for sure is that timers + // can't go backwards. (unless they overflow) + Value += TIMER_RELOAD_VALUE; + } + + LastValue = Value; + KeRestoreInterrupts(Restore); + return Value; +} + +HAL_API uint64_t HalGetIntTimerFrequency() +{ + return PIT_TICK_FREQUENCY; +} + +HAL_API bool HalUseOneShotIntTimer() +{ + return false; +} + +HAL_API void HalRequestInterruptInTicks(UNUSED uint64_t Ticks) +{ + DbgPrint("HalRequestInterruptInTicks not implemented"); +} + +HAL_API uint64_t HalGetIntTimerDeltaTicks() +{ + return TIMER_RELOAD_VALUE; +} + +PKREGISTERS HalTimerTick(PKREGISTERS Regs) +{ + PitTicksPassed += TIMER_RELOAD_VALUE; + + KeTimerTick(); + + HalEndOfInterrupt((int) Regs->IntNumber); + return Regs; +} + +void HalInitTimer() +{ + KeRegisterInterrupt(SYSTEM_IRQ(0), HalTimerTick); + HalPicRegisterInterrupt(SYSTEM_IRQ(0), IPL_CLOCK); + KeSetInterruptIPL(SYSTEM_IRQ(0), IPL_CLOCK); + + bool Restore = KeDisableInterrupts(); + + KePortWriteByteWait(PIT_COMMAND_PORT, PIT_CMD_CHANNEL_0 | PIT_CMD_ACCESS_2_BYTES | PIT_CMD_RATE_GENERATOR); + KePortWriteByteWait(PIT_CHANNEL_0_PORT, TIMER_RELOAD_VALUE & 0xFF); + KePortWriteByteWait(PIT_CHANNEL_0_PORT, TIMER_RELOAD_VALUE >> 8); + + KeRestoreInterrupts(Restore); + PitInitialized = true; +} diff --git a/drivers/halx86/source/acpi.c b/drivers/halx86/source/acpi.c index de04112f..f041fb19 100644 --- a/drivers/halx86/source/acpi.c +++ b/drivers/halx86/source/acpi.c @@ -18,8 +18,7 @@ Module name: #include "pio.h" // TODO: Add a way to do that without breaking into Mm's internal functions -HPAGEMAP MiGetCurrentPageMap(); -bool MiMapPhysicalPage(HPAGEMAP Mapping, uintptr_t PhysicalPage, uintptr_t Address, uintptr_t Permissions); +bool MiMapPhysicalPage(uintptr_t PhysicalPage, uintptr_t Address, uintptr_t Permissions); static PRSDP_DESCRIPTION HalpRsdp; static PRSDT_TABLE HalpRsdt; @@ -92,20 +91,15 @@ void AcpiInitPmt() uintptr_t Addr = Header->X_PMTimerBlock.Address; uintptr_t OffsetWithinPage = Addr & 0xFFF; - void* PageAddress = MmAllocatePoolBig(POOL_FLAG_CALLER_CONTROLLED, 1, POOL_TAG("APMT")); + void* PageAddress = MmMapIoSpace( + Addr, + 1, // SizePages + MM_PTE_READWRITE | MM_PTE_CDISABLE | MM_PTE_GLOBAL | MM_PTE_NOEXEC, + POOL_TAG("APMT") + ); + if (!PageAddress) - { - CRASH_BECAUSE_FAILURE_TO_MAP: KeCrashBeforeSMPInit("Could not map ACPI PMT timer as uncacheable"); - } - - if (!MiMapPhysicalPage(MiGetCurrentPageMap(), - Addr, - (uintptr_t) PageAddress, - MM_PTE_READWRITE | MM_PTE_CDISABLE | MM_PTE_GLOBAL | MM_PTE_NOEXEC)) - { - goto CRASH_BECAUSE_FAILURE_TO_MAP; - } uintptr_t PageAddress2 = (uintptr_t) PageAddress; PageAddress2 += OffsetWithinPage; diff --git a/drivers/halx86/source/apic.c b/drivers/halx86/source/apic.c index 85bc41c1..5fd78f4a 100644 --- a/drivers/halx86/source/apic.c +++ b/drivers/halx86/source/apic.c @@ -435,7 +435,7 @@ void HalInitApicMP() } -HAL_API void HalEndOfInterrupt() +HAL_API void HalEndOfInterrupt(UNUSED int InterruptNumber) { ApicEndOfInterrupt(); } diff --git a/drivers/halx86/source/apic.h b/drivers/halx86/source/apic.h index 13f1ac8d..ec474bba 100644 --- a/drivers/halx86/source/apic.h +++ b/drivers/halx86/source/apic.h @@ -15,8 +15,7 @@ Module name: #ifndef NS64_HAL_APIC_H #define NS64_HAL_APIC_H -void HalEndOfInterrupt(); - +void HalEndOfInterrupt(int InterruptNumber); void HalSendIpi(uint32_t Processor, int Vector); void HalBroadcastIpi(int Vector, bool IncludeSelf); void HalInitApicUP(); diff --git a/drivers/halx86/source/hpet.c b/drivers/halx86/source/hpet.c index ee76317c..9dc4f9a4 100644 --- a/drivers/halx86/source/hpet.c +++ b/drivers/halx86/source/hpet.c @@ -49,21 +49,16 @@ void HpetInitialize() uintptr_t HpetAddress = Hpet->Address.Address; // Map the HPET as uncacheable. - void* Address = MmAllocatePoolBig(POOL_FLAG_CALLER_CONTROLLED, 1, POOL_TAG("HPET")); + void* Address = MmMapIoSpace( + HpetAddress, + 1, // SizePages + MM_PTE_READWRITE | MM_PTE_CDISABLE | MM_PTE_GLOBAL | MM_PTE_NOEXEC, + POOL_TAG("HPET") + ); + if (!Address) - { - CRASH_BECAUSE_FAILURE_TO_MAP: KeCrashBeforeSMPInit("Could not map HPET as uncacheable"); - } - - if (!MiMapPhysicalPage(MiGetCurrentPageMap(), - HpetAddress, - (uintptr_t) Address, - MM_PTE_READWRITE | MM_PTE_CDISABLE | MM_PTE_GLOBAL | MM_PTE_NOEXEC)) - { - goto CRASH_BECAUSE_FAILURE_TO_MAP; - } - + HpetpIsAvailable = true; HpetpRegisters = (PHPET_REGISTERS) ((uintptr_t) Address + (HpetAddress & 0xFFF)); diff --git a/drivers/halx86/source/init.c b/drivers/halx86/source/init.c index abefb347..c3ba1691 100644 --- a/drivers/halx86/source/init.c +++ b/drivers/halx86/source/init.c @@ -21,7 +21,7 @@ Module name: #include "ioapic.h" #include "pci.h" -void HalEndOfInterrupt(); +void HalEndOfInterrupt(int InterruptNumber); void HalRequestInterruptInTicks(uint64_t Ticks); void HalRequestIpi(uint32_t LapicId, uint32_t Flags, int Vector); void HalInitSystemUP(); diff --git a/drivers/i8042prt/source/kbd.c b/drivers/i8042prt/source/kbd.c index d73e6abc..04a76675 100644 --- a/drivers/i8042prt/source/kbd.c +++ b/drivers/i8042prt/source/kbd.c @@ -182,7 +182,7 @@ void KbdInitialize(int Vector, KIPL Ipl) BSTATUS KbdRead( PIO_STATUS_BLOCK Iosb, UNUSED PFCB Fcb, - UNUSED uintptr_t Offset, + UNUSED uint64_t Offset, PMDL Mdl, uint32_t Flags ) diff --git a/drivers/i8042prt/source/main.c b/drivers/i8042prt/source/main.c index f1a37c3f..8ead0aa6 100644 --- a/drivers/i8042prt/source/main.c +++ b/drivers/i8042prt/source/main.c @@ -21,6 +21,8 @@ Module name: PDRIVER_OBJECT I8042DriverObject; +#ifdef TARGET_AMD64 + int AllocateVector(PKIPL Ipl, KIPL Default) { int Vector = -1; @@ -32,6 +34,8 @@ int AllocateVector(PKIPL Ipl, KIPL Default) return Vector; } +#endif + BSTATUS InitializeDevice() { // Disable primary and secondary PS/2 ports. @@ -65,6 +69,8 @@ BSTATUS InitializeDevice() (void) 0; // Allocate an interrupt for the keyboard. +#ifdef TARGET_AMD64 + KIPL IplKbd, IplMou; int VectorKbd = AllocateVector(&IplKbd, IPL_DEVICES0); int VectorMou = AllocateVector(&IplMou, IPL_DEVICES0); @@ -76,12 +82,22 @@ BSTATUS InitializeDevice() // Ok, now set the IRQ redirects. HalIoApicSetIrqRedirect(VectorKbd, I8042_IRQ_KBD, LapicId, true); HalIoApicSetIrqRedirect(VectorMou, I8042_IRQ_MOU, LapicId, true); + +#elif defined TARGET_I386 + + const KIPL IplKbd = IPL_DEVICES0; + const int VectorKbd = SYSTEM_IRQ(1); + + bool Restore = KeDisableInterrupts(); + +#else +#error "If you're using the i8042prt driver for another architecture, please define the interrupt method for it." +#endif // Initialize the keyboard. KbdInitialize(VectorKbd, IplKbd); KeRestoreInterrupts(Restore); - return STATUS_SUCCESS; } diff --git a/drivers/stornvme/source/commands.c b/drivers/stornvme/source/commands.c index d1056726..2f0057b4 100644 --- a/drivers/stornvme/source/commands.c +++ b/drivers/stornvme/source/commands.c @@ -16,15 +16,20 @@ Module name: #include // NOTE: Here, EntryPair's Event field will be replaced with an address that'll be stale on exit. -BSTATUS NvmeSendAndWait(PQUEUE_CONTROL_BLOCK Qcb, PQUEUE_ENTRY_PAIR EntryPair) +// +// TODO: Make this event's wait cancelable. +BSTATUS NvmeSendAndWait(PQUEUE_CONTROL_BLOCK Qcb, PQUEUE_ENTRY_PAIR EntryPair, bool Alertable) { + BSTATUS Status; KEVENT Event; KeInitializeEvent(&Event, EVENT_NOTIFICATION, false); EntryPair->Event = &Event; - NvmeSend(Qcb, EntryPair); + Status = NvmeSend(Qcb, EntryPair, Alertable); + if (FAILED(Status)) + return Status; - BSTATUS Status = KeWaitForSingleObject(&Event, false, TIMEOUT_INFINITE, MODE_KERNEL); + Status = KeWaitForSingleObject(&Event, false, TIMEOUT_INFINITE, MODE_KERNEL); if (FAILED(Status)) return Status; @@ -55,11 +60,9 @@ BSTATUS NvmeIdentify(PCONTROLLER_EXTENSION ContExtension, void* IdentifyBuffer, QueueEntry.Sub.Dword10.Identify.Cns = Cns; - BSTATUS Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry); + BSTATUS Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry, false); if (!FAILED(Status)) - { memcpy(IdentifyBuffer, MmGetHHDMOffsetAddr(MmPFNToPhysPage(Page)), PAGE_SIZE); - } MmFreePhysicalPage(Page); return Status; @@ -76,7 +79,7 @@ BSTATUS NvmeSetFeature(PCONTROLLER_EXTENSION ContExtension, int FeatureIdentifie QueueEntry.Sub.DataPointer[0] = DataPointer; QueueEntry.Sub.Dword10.SetFeatures.FeatureIdentifier = FeatureIdentifier; - return NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry); + return NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry, false); } BSTATUS NvmeAllocateIoQueues(PCONTROLLER_EXTENSION ContExtension, size_t QueueCount, size_t* OutQueueCount) @@ -93,7 +96,7 @@ BSTATUS NvmeAllocateIoQueues(PCONTROLLER_EXTENSION ContExtension, size_t QueueCo QueueEntry.Sub.Dword11.SetFeatures.SubQueueCount = QueueCount; QueueEntry.Sub.Dword11.SetFeatures.ComQueueCount = QueueCount; - BSTATUS Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry); + BSTATUS Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry, false); if (FAILED(Status)) return Status; @@ -142,7 +145,7 @@ BSTATUS NvmeInitializeIoQueue(PCONTROLLER_EXTENSION ContExtension, PQUEUE_CONTRO QueueEntry.Sub.Dword11.CreateIoCompQueue.PhysicallyContiguous = 1; QueueEntry.Sub.Dword11.CreateIoCompQueue.InterruptVector = Id; - BSTATUS Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry); + BSTATUS Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry, false); if (FAILED(Status)) { DbgPrint("StorNvme: failed to create I/O completion queue %zu: status %d", Id, Status); @@ -162,7 +165,7 @@ BSTATUS NvmeInitializeIoQueue(PCONTROLLER_EXTENSION ContExtension, PQUEUE_CONTRO QueueEntry.Sub.Dword11.CreateIoSubQueue.CompletionQueueId = Id; QueueEntry.Sub.Dword11.CreateIoSubQueue.PhysicallyContiguous = 1; - Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry); + Status = NvmeSendAndWait(&ContExtension->AdminQueue, &QueueEntry, false); if (FAILED(Status)) { DbgPrint("StorNvme: failed to create I/O submission queue %zu: status %d", Id, Status); @@ -193,11 +196,11 @@ BSTATUS NvmeSendRead(PDEVICE_EXTENSION DeviceExtension, uint64_t Prp[2], uint64_ if (Wait) { - return NvmeSendAndWait(NvmeChooseIoQueue(ContExtension), QueueEntryPtr); + return NvmeSendAndWait(NvmeChooseIoQueue(ContExtension), QueueEntryPtr, true); } else { - NvmeSend(NvmeChooseIoQueue(ContExtension), QueueEntryPtr); + NvmeSend(NvmeChooseIoQueue(ContExtension), QueueEntryPtr, true); return STATUS_SUCCESS; } } @@ -217,11 +220,11 @@ BSTATUS NvmeSendWrite(PDEVICE_EXTENSION DeviceExtension, uint64_t Prp[2], uint64 if (Wait) { - return NvmeSendAndWait(NvmeChooseIoQueue(ContExtension), QueueEntryPtr); + return NvmeSendAndWait(NvmeChooseIoQueue(ContExtension), QueueEntryPtr, true); } else { - NvmeSend(NvmeChooseIoQueue(ContExtension), QueueEntryPtr); + NvmeSend(NvmeChooseIoQueue(ContExtension), QueueEntryPtr, true); return STATUS_SUCCESS; } } diff --git a/drivers/stornvme/source/nvme.h b/drivers/stornvme/source/nvme.h index 08c7f7ee..7d5aad83 100644 --- a/drivers/stornvme/source/nvme.h +++ b/drivers/stornvme/source/nvme.h @@ -506,7 +506,7 @@ extern IO_DISPATCH_TABLE NvmeDispatchTable; // // After sending, one may wait on the event passed into EntryPair. Once the operation is // complete, the event will be set. -void NvmeSend(PQUEUE_CONTROL_BLOCK Qcb, PQUEUE_ENTRY_PAIR EntryPair); +BSTATUS NvmeSend(PQUEUE_CONTROL_BLOCK Qcb, PQUEUE_ENTRY_PAIR EntryPair, bool Alertable); // NOTE: Ownership of the SubmissionQueuePhysical and CompletionQueuePhysical pages is transferred to the queue. void NvmeSetupQueue( diff --git a/drivers/stornvme/source/queue.c b/drivers/stornvme/source/queue.c index 00b4d6e3..0edf65f0 100644 --- a/drivers/stornvme/source/queue.c +++ b/drivers/stornvme/source/queue.c @@ -91,16 +91,27 @@ static void NvmeCreateInterruptForQueue(PQUEUE_CONTROL_BLOCK Qcb, int MsixIndex) HalPciMsixSetFunctionMask(Device, false); } -// Send a command to a queue and wait for it to finish. +// Send a command to a queue. // // NOTE: The PKEVENT Event field of EntryPair must point to a valid event. -void NvmeSend(PQUEUE_CONTROL_BLOCK Qcb, PQUEUE_ENTRY_PAIR EntryPair) +// The event will be signalled once the command finishes. +BSTATUS NvmeSend(PQUEUE_CONTROL_BLOCK Qcb, PQUEUE_ENTRY_PAIR EntryPair, bool Alertable) { // Wait on the semaphore to ensure there's space for our request. // // The semaphore will get released by the DPC routine when an operation // has completed. - KeWaitForSingleObject(&Qcb->Semaphore, false, TIMEOUT_INFINITE, MODE_KERNEL); + BSTATUS Status; + Status = KeWaitForSingleObject( + &Qcb->Semaphore, + Alertable, + TIMEOUT_INFINITE, + Alertable ? KeGetPreviousMode() : MODE_KERNEL + ); + + if (FAILED(Status)) + // Thread was alerted, abort this request. + return Status; KIPL Ipl; KeAcquireSpinLock(&Qcb->SpinLock, &Ipl); @@ -126,6 +137,7 @@ void NvmeSend(PQUEUE_CONTROL_BLOCK Qcb, PQUEUE_ENTRY_PAIR EntryPair) *Qcb->SubmissionQueue.DoorBell = Qcb->SubmissionQueue.Index; KeReleaseSpinLock(&Qcb->SpinLock, Ipl); + return Status; } void NvmeSetupQueue( diff --git a/drivers/test/source/apctst.c b/drivers/test/source/apctst.c index bffc3ea5..910a6912 100644 --- a/drivers/test/source/apctst.c +++ b/drivers/test/source/apctst.c @@ -28,7 +28,7 @@ static void ApcNormalRoutine(UNUSED void* Context, UNUSED void* SystemArgument1, KeSetEvent(&Event, 1); } -static NO_RETURN void ApcTestRoutine() +static NO_RETURN void ApcTestRoutine(UNUSED void* Parameter) { LogMsg("Hello from ApcTestRoutine! My thread ptr is %p", KeGetCurrentThread()); diff --git a/drivers/test/source/balltst.c b/drivers/test/source/balltst.c index 1043eab4..1d7bb68f 100644 --- a/drivers/test/source/balltst.c +++ b/drivers/test/source/balltst.c @@ -22,7 +22,7 @@ Module name: // Updates for each processor ID. int Updates[64]; -NO_RETURN void TestThread1() +NO_RETURN void TestThread1(UNUSED void* Parameter) { // Display a simple run timer for (int i = 0; ; i++) @@ -32,7 +32,7 @@ NO_RETURN void TestThread1() } } -NO_RETURN void TestThread2() +NO_RETURN void TestThread2(UNUSED void* Parameter) { for (int i = 0; i < 20; i++) { @@ -98,7 +98,7 @@ int RNGRange(int Min, int Max) return RNG() % (Max - Min) + Min; } -NO_RETURN void BallTest() +NO_RETURN void BallTest(UNUSED void* Parameter) { KDPC DrvDpc; KeInitializeDpc(&DrvDpc, BallTestDpc, KeGetCurrentThread()); diff --git a/drivers/test/source/ccbtst.c b/drivers/test/source/ccbtst.c index 976e4c43..7a56502e 100644 --- a/drivers/test/source/ccbtst.c +++ b/drivers/test/source/ccbtst.c @@ -16,6 +16,8 @@ Module name: #include #include "utils.h" +#if 0 + void PerformCcbTest() { LogMsg("Delaying half a second"); @@ -47,3 +49,5 @@ void PerformCcbTest() } + +#endif diff --git a/drivers/test/source/fworktst.c b/drivers/test/source/fworktst.c index 8c14248a..3f6ac0de 100644 --- a/drivers/test/source/fworktst.c +++ b/drivers/test/source/fworktst.c @@ -120,7 +120,17 @@ void Init() { PLOADER_FRAMEBUFFER Framebuffer = &KeLoaderParameterBlock.Framebuffers[0]; +#ifdef IS_32_BIT + PixBuff = MmMapIoSpace( + (uintptr_t)Framebuffer->Address, + Framebuffer->Pitch * Framebuffer->Height, + MM_PTE_READWRITE | MM_PTE_CDISABLE, + POOL_TAG("FWFB") + ); +#else PixBuff = Framebuffer->Address; +#endif + PixWidth = Framebuffer->Width; PixHeight = Framebuffer->Height; PixPitch = Framebuffer->Pitch; @@ -131,13 +141,13 @@ void Init() uint64_t ReadTsc() { - uint64_t low, high; + uintptr_t low, high; // note: The rdtsc instruction is specified to zero out the top 32 bits of rax and rdx. ASM("rdtsc":"=a"(low), "=d"(high)); // So something like this is fine. - return high << 32 | low; + return ((uint64_t)high << 32) | low; } unsigned RandTscBased() @@ -269,9 +279,6 @@ NO_RETURN void T_Particle(void* Parameter) NO_RETURN void T_Explodeable(UNUSED void* Parameter) { - KTIMER Timer; - KeInitializeTimer(&Timer); - FIREWORK_DATA Data; memset(&Data, 0, sizeof Data); @@ -365,9 +372,6 @@ void PerformFireworksTest() // The main thread occupies itself with spawning explodeables // from time to time, to keep things interesting. - KTIMER Timer; - KeInitializeTimer(&Timer); - while (true) { int SpawnCount = Rand() % 20 + 1; diff --git a/drivers/test/source/main.c b/drivers/test/source/main.c index ec2f2d94..afaf27df 100644 --- a/drivers/test/source/main.c +++ b/drivers/test/source/main.c @@ -35,7 +35,11 @@ void PerformDelay(int Ms, PKDPC Dpc) KeInitializeTimer(&Timer); KeSetTimer(&Timer, Ms, Dpc); - KeWaitForSingleObject(&Timer.Header, false, TIMEOUT_INFINITE, MODE_KERNEL); + BSTATUS Status = KeWaitForSingleObject(&Timer.Header, false, TIMEOUT_INFINITE, MODE_KERNEL); + if (FAILED(Status)) + DbgPrint("PerformDelay FAILED. %d (%s)", Status, RtlGetStatusString(Status)); + + KeCancelTimer(&Timer); } void DumpHex(void* DataV, size_t DataSize, bool LogScreen) @@ -88,7 +92,7 @@ NO_RETURN void DriverTestThread(UNUSED void* Parameter) //PerformObjectTest(); //PerformMdlTest(); //PerformIntTest(); - //PerformKeyboardTest(); + PerformKeyboardTest(); //PerformStorageTest(); //PerformExObTest(); //PerformCcbTest(); @@ -98,7 +102,7 @@ NO_RETURN void DriverTestThread(UNUSED void* Parameter) //PerformMm4Test(); //PerformMm5Test(); //PerformFs1Test(); - PerformPipeTest(); + //PerformPipeTest(); LogMsg(ANSI_GREEN "*** All tests have concluded." ANSI_RESET); KeTerminateThread(0); diff --git a/drivers/test/source/mdltst.c b/drivers/test/source/mdltst.c index aae7c665..e3498dca 100644 --- a/drivers/test/source/mdltst.c +++ b/drivers/test/source/mdltst.c @@ -29,12 +29,10 @@ void PerformMdlTest() KeWaitForSingleObject(&Timer, false, TIMEOUT_INFINITE, MODE_KERNEL); */ - HPAGEMAP PageMap = MiGetCurrentPageMap(); uintptr_t FixedAddr = 0x40000000; size_t FixedSize = 0x10000; if (!MiMapAnonPages( - PageMap, FixedAddr, FixedSize / PAGE_SIZE, MM_PTE_READWRITE, @@ -94,7 +92,7 @@ void PerformMdlTest() LogMsg("Memory Pages Available Now: %zu", MmGetTotalFreePages()); - MiUnmapPages(PageMap, FixedAddr, FixedSize); + MiUnmapPages(FixedAddr, FixedSize); // note: the variation of 3 pages is actually normal at this point // the pages are allocated during the initial mapping diff --git a/drivers/test/source/mm1tst.c b/drivers/test/source/mm1tst.c index c58e5b13..0e6d0d38 100644 --- a/drivers/test/source/mm1tst.c +++ b/drivers/test/source/mm1tst.c @@ -46,7 +46,6 @@ void Reboot() { void PerformDemandPageTest() { LogMsg(">> Demand page test"); - HPAGEMAP Map = MiGetCurrentPageMap(); void* PoolAddr = MmAllocatePoolBig(POOL_FLAG_CALLER_CONTROLLED, 1, POOL_TAG("Mts1")); ASSERT(PoolAddr); @@ -56,7 +55,7 @@ void PerformDemandPageTest() // Make the PTE a demand page PTE. MmLockKernelSpaceExclusive(); - PMMPTE Pte = MiGetPTEPointer(Map, Va, true); + PMMPTE Pte = MmGetPteLocationCheck(Va, true); ASSERT(Pte); *Pte = MM_DPTE_COMMITTED | MM_PTE_READWRITE; @@ -68,7 +67,7 @@ void PerformDemandPageTest() LogMsg("Va read: %08x", *((uint32_t*)Va)); MmLockKernelSpaceExclusive(); - MiUnmapPages(Map, Va, 1); + MiUnmapPages(Va, 1); MmUnlockKernelSpace(); MmFreePoolBig(PoolAddr); @@ -77,8 +76,7 @@ void PerformDemandPageTest() void PerformCopyOnWriteTest() { LogMsg(">> Copy on write test"); - HPAGEMAP Map = MiGetCurrentPageMap(); - + void* PoolAddr = MmAllocatePoolBig(POOL_FLAG_CALLER_CONTROLLED, 2, POOL_TAG("Mts2")); ASSERT(PoolAddr); @@ -96,8 +94,8 @@ void PerformCopyOnWriteTest() // Now map them in. MmLockKernelSpaceExclusive(); - bool Res1 = MiMapPhysicalPage(Map, MmPFNToPhysPage(Pfn), Va1, MM_PTE_ISFROMPMM | MM_PTE_COW); - bool Res2 = MiMapPhysicalPage(Map, MmPFNToPhysPage(Pfn), Va2, MM_PTE_ISFROMPMM | MM_PTE_COW); + bool Res1 = MiMapPhysicalPage(MmPFNToPhysPage(Pfn), Va1, MM_PTE_ISFROMPMM | MM_PTE_COW); + bool Res2 = MiMapPhysicalPage(MmPFNToPhysPage(Pfn), Va2, MM_PTE_ISFROMPMM | MM_PTE_COW); ASSERT(Res1 && Res2); MmUnlockKernelSpace(); @@ -115,7 +113,7 @@ void PerformCopyOnWriteTest() // Unmap everything. MmLockKernelSpaceExclusive(); - MiUnmapPages(Map, Va1, 2); + MiUnmapPages(Va1, 2); MmUnlockKernelSpace(); // Free the pool space. diff --git a/drivers/test/source/pipetst.c b/drivers/test/source/pipetst.c index 0f89d73a..5dc1becb 100644 --- a/drivers/test/source/pipetst.c +++ b/drivers/test/source/pipetst.c @@ -25,7 +25,7 @@ void PerformPipeTest() IO_STATUS_BLOCK Iosb; // memcpy from the start of the HHDM to get some data going - memcpy(SomeData, (void*) 0xFFFF800000000000, sizeof SomeData); + memcpy(SomeData, (void*) MmGetHHDMOffsetAddr(0), sizeof SomeData); const size_t BufferSize = 4096; diff --git a/drivers/test/source/proctst.c b/drivers/test/source/proctst.c index ed9813a7..a8f1c748 100644 --- a/drivers/test/source/proctst.c +++ b/drivers/test/source/proctst.c @@ -31,9 +31,7 @@ void ProcessTestRoutine(UNUSED void* Ptr) // TODO: locking? - HPAGEMAP Map = MiGetCurrentPageMap(); - - MiMapAnonPages(Map, (uintptr_t) TheMemory, SizeOfTheMemory / PAGE_SIZE, MM_PTE_READWRITE, true); + MiMapAnonPages((uintptr_t) TheMemory, SizeOfTheMemory / PAGE_SIZE, MM_PTE_READWRITE, true); // Probe the memory. int Status = MmProbeAddress(TheMemory, SizeOfTheMemory, true, MODE_KERNEL); @@ -50,7 +48,7 @@ void ProcessTestRoutine(UNUSED void* Ptr) KeWaitForSingleObject(&Evnt, false, TIMEOUT_INFINITE, MODE_KERNEL); // Unmap the memory. - MiUnmapPages(Map, (uintptr_t) TheMemory, SizeOfTheMemory / PAGE_SIZE); + MiUnmapPages((uintptr_t) TheMemory, SizeOfTheMemory / PAGE_SIZE); Status = MmProbeAddress(TheMemory, SizeOfTheMemory, true, MODE_KERNEL); LogMsg("Status In Process: %d (after unmapping memory)", Status); diff --git a/limine.cfg b/limine.amd64.cfg similarity index 93% rename from limine.cfg rename to limine.amd64.cfg index 19e302c4..b15996ff 100644 --- a/limine.cfg +++ b/limine.amd64.cfg @@ -1,7 +1,7 @@ TIMEOUT=3 VERBOSE=yes -:The Boron Operating System +:Boron (amd64) PROTOCOL=limine KERNEL_PATH=boot:///kernel.elf #CMDLINE=/noinit diff --git a/limine.i386.cfg b/limine.i386.cfg new file mode 100644 index 00000000..68cc3cb5 --- /dev/null +++ b/limine.i386.cfg @@ -0,0 +1,34 @@ +TIMEOUT=3 +VERBOSE=yes + +:Boron (i386) + PROTOCOL=multiboot + KERNEL_PATH=boot:///kernel.elf + #CMDLINE=/noinit + + MODULE_PATH=boot:///hali386.sys + MODULE_STRING=hali386.sys + + MODULE_PATH=boot:///framebuf.sys + MODULE_STRING=framebuf.sys + + MODULE_PATH=boot:///i8042prt.sys + MODULE_STRING=i8042prt.sys + + #MODULE_PATH=boot:///stornvme.sys + #MODULE_STRING=stornvme.sys + + #MODULE_PATH=boot:///ext2fs.sys + #MODULE_STRING=ext2fs.sys + + #MODULE_PATH=boot:///test.sys + #MODULE_STRING=test.sys + + MODULE_PATH=boot:///libboron.so + MODULE_STRING=libboron.so + MODULE_PATH=boot:///libtest.so + MODULE_STRING=libtest.so + MODULE_PATH=boot:///init.exe + MODULE_STRING=init.exe + MODULE_PATH=boot:///test.exe + MODULE_STRING=test.exe diff --git a/readme.md b/readme.md index b984936d..4325cf7b 100644 --- a/readme.md +++ b/readme.md @@ -3,7 +3,7 @@ Boron is a 64-bit operating system designed with SMP in mind. It borrows heavily from Windows NT, but does not aim to be a total clone. -Note! Boron is currently not even in a minimally usable state. Don't expect it to do anything. +NOTE: Boron is currently not even in a minimally usable state. Don't expect it to do anything. This project is licensed under the three clause BSD license, **except the following**: - Flanterm (drivers/halx86/source/flanterm): https://github.com/mintsuki/flanterm - Licensed under the 2 clause BSD license @@ -21,6 +21,21 @@ Currently this uses Limine v7.x. * The chemical element with the same name has the atomic number of 5. Coincidentally this is also my 5th operating system project. (three of them flopped, and the other one that didn't is [NanoShell](https://github.com/iProgramMC/NanoShellOS)) +### Supported platforms + +Currently, Boron supports running on: +- x86_64 +- x86 (32-bit) + +Note: The supporting code for 32-bit x86 is called `i386`, but there is a long way to actually running on the original 80386.) + +### Planned ports + +Boron is planned to be ported to: +- PowerPC (32-bit) +- ARMv6 (32-bit) +- AArch64 (64-bit) + ## Building In a terminal, run the following commands: ``` @@ -42,6 +57,8 @@ Currently, the OS's source is structured into the following: * `drivers/` - The actual drivers themselves. +* `user/` - Boron's native userspace distribution lives here. + Kernel DLL exports will use the prefix `OS`. I know this isn't the chemical symbol for boron (that being B), but it is what it is. @@ -78,7 +95,7 @@ all present related to it. * [x] Reclamation of pages occupied by initialization code * [x] Capture of physical regions for extended use * [x] Mapping and unmapping anonymous memory - * [ ] File backed memory + * [x] File backed memory * [ ] Swap file support * [ ] Swap out page tables * [ ] Swap out kernel code @@ -100,7 +117,7 @@ all present related to it. * [x] Dynamic linked library loader (`Ldr`) * [x] HAL separate from kernel * [x] Load additional kernel side modules - * [ ] Map the Boron system service DLL (BoronDLL.dll) + * [x] Map the Boron system service DLL (libboron.so) * [ ] File system manager * [ ] ... @@ -111,10 +128,10 @@ all present related to it. * [ ] Security subsystem (later) * [ ] ... -* [ ] Boron DLL / Librarian / System service handler - * [ ] Load fixed modules - * [ ] Load relocatable modules - * [ ] Link modules with each other by resolving undefined dependencies +* [x] Boron DLL / Librarian / System service handler + * [x] Load fixed modules + * [x] Load relocatable modules + * [x] Link modules with each other by resolving undefined dependencies * [ ] User space * [ ] Command line shell diff --git a/run.sh b/run.sh deleted file mode 100644 index 46755ad1..00000000 --- a/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -# works only in wsl :) - -cmd.exe /k "run.bat && exit" diff --git a/tools/build_iso_limine.mk b/tools/build_iso_limine.mk new file mode 100644 index 00000000..31969ad6 --- /dev/null +++ b/tools/build_iso_limine.mk @@ -0,0 +1,14 @@ + +$(IMAGE_TARGET): kernel drivers apps limine_config + @echo "[MK]\tBuilding iso..." + @rm -rf $(ISO_DIR) + @mkdir -p $(ISO_DIR) + @cp $(KERNEL_ELF) $(ISO_DIR)/$(KERNEL_NAME) + @cp -r $(BUILD_DIR)/*.exe $(BUILD_DIR)/*.sys $(BUILD_DIR)/*.so limine/limine-bios.sys limine/limine-bios-cd.bin $(ISO_DIR) + @cp limine.$(TARGETL).cfg $(ISO_DIR)/limine.cfg + @xorriso -as mkisofs -b limine-bios-cd.bin -no-emul-boot -boot-load-size 4 -boot-info-table --protective-msdos-label $(ISO_DIR) -o $@ 2>/dev/null + @limine/limine-deploy $@ 2>/dev/null + @rm -rf $(ISO_DIR) + +limine_config: limine.$(TARGETL).cfg + @echo "[MK]\tlimine.$(TARGETL).cfg was updated" diff --git a/run.bat b/tools/run-amd64.bat similarity index 100% rename from run.bat rename to tools/run-amd64.bat diff --git a/tools/run-amd64.sh b/tools/run-amd64.sh new file mode 100644 index 00000000..31ee3dfd --- /dev/null +++ b/tools/run-amd64.sh @@ -0,0 +1,3 @@ +# works only in wsl :) + +cmd.exe /k "tools\run-amd64.bat && exit" diff --git a/tools/run-i386.bat b/tools/run-i386.bat new file mode 100644 index 00000000..a54df744 --- /dev/null +++ b/tools/run-i386.bat @@ -0,0 +1,48 @@ +@rem Run script + +@echo off + +set backupPath=%path% +set NSPath=%CD% +cd /d c:\Program Files\qemu +set path=%path%;%NSPath% + +if exist %nspath%\vdiske2.vdi ( + set DriveOptions=-cdrom %nspath%\build\image.i386.iso -drive id=nvm,file=%nspath%\vdiske2.vdi,if=none -device nvme,serial=deadbeef,drive=nvm +) else ( + set DriveOptions=-cdrom %nspath%\build\image.i386.iso +) + +qemu-system-i386.exe -no-reboot -no-shutdown -d int -M smm=off ^ +-M q35 ^ +-m 1024M ^ +-boot d ^ +-display sdl ^ +-accel tcg ^ +-monitor telnet:127.0.0.1:56789,server,nowait ^ +-debugcon stdio ^ +-trace *nvme* -trace *msi* -D %nspath%\keep\nvmelog.txt ^ +-s ^ +%DriveOptions% + +:-debugcon stdio ^ +:-d cpu_reset ^ +: -s -S -- for debugging with GDB +: -serial COM7 -- to output the serial port to somewhere real +: -kernel %nspath%/kernel.bin +: -debugcon stdio +: -monitor telnet:127.0.0.1:55555,server,nowait -- to use the QEMU console +: +:-d int ^ +:-D %nspath%\keep/things.txt ^ +:qemu-system-i386 -m 16M -drive file=\\.\PHYSICALDRIVE1,format=raw +rem -s -S + +:-drive id=disk,file=%nspath%\vdisk.vdi,if=none ^ +:-device ahci,id=ahci ^ +:-device ide-hd,drive=disk,bus=ahci.0 ^ + +rem go back +cd /d %NSPath% + +set path=%backupPath% diff --git a/tools/run-i386.sh b/tools/run-i386.sh new file mode 100644 index 00000000..3a3bfb4a --- /dev/null +++ b/tools/run-i386.sh @@ -0,0 +1,3 @@ +# works only in wsl :) + +cmd.exe /k "tools\run-i386.bat && exit" diff --git a/run-unix.sh b/tools/run-unix-amd64.sh similarity index 100% rename from run-unix.sh rename to tools/run-unix-amd64.sh diff --git a/tools/run-unix-i386.sh b/tools/run-unix-i386.sh new file mode 100644 index 00000000..0d427ce0 --- /dev/null +++ b/tools/run-unix-i386.sh @@ -0,0 +1,8 @@ +qemu-system-i386 \ + -no-reboot \ + -no-shutdown \ + -M q35 \ + -m 256M \ + -boot d \ + -cdrom build/image.i386.iso \ + -debugcon stdio diff --git a/tools/run_rule_amd64.mk b/tools/run_rule_amd64.mk new file mode 100644 index 00000000..9bdec72b --- /dev/null +++ b/tools/run_rule_amd64.mk @@ -0,0 +1,8 @@ + +run: image + @echo "Running..." + @./tools/run-unix-amd64.sh + +runw: image + @echo "Invoking WSL to run the OS..." + @./tools/run-amd64.sh diff --git a/tools/run_rule_i386.mk b/tools/run_rule_i386.mk new file mode 100644 index 00000000..fc0dd397 --- /dev/null +++ b/tools/run_rule_i386.mk @@ -0,0 +1,8 @@ + +run: image + @echo "Running..." + @./tools/run-unix-i386.sh + +runw: image + @echo "Invoking WSL to run the OS..." + @./tools/run-i386.sh diff --git a/tools/toolchain.mk b/tools/toolchain.mk new file mode 100644 index 00000000..84744fda --- /dev/null +++ b/tools/toolchain.mk @@ -0,0 +1,70 @@ + +# Determine the build tools used automatically. +ifeq ($(TARGETL), amd64) + # Compiler Toolchain + BCC ?= gcc + BCXX ?= g++ + BLD ?= ld + BASM ?= nasm + + # Compiler and linker flags + # + # NOTE 7.7.2024 -- No-reorder-functions was added because a certain functions + # was generating an "unlikely" section, which was placed at different addresses + # in kernel.elf and kernel2.elf, screwing up the symbol table... That's pretty + # bad. + # + # TODO: fix above ^^^ + ARCH_CFLAGS = \ + -m64 \ + -march=x86-64 \ + -mabi=sysv \ + -mno-80387 \ + -mno-mmx \ + -mno-sse \ + -mno-sse2 \ + -mno-red-zone \ + -fno-reorder-functions + + ARCH_LDFLAGS = \ + -z max-page-size=0x1000 + + ARCH_ASFLAGS = \ + -f elf64 + + LINK_ARCH = elf_x86_64 + SMP = yes + + ifeq ($(IS_KERNEL), yes) + ARCH_CFLAGS += -mcmodel=kernel + endif + +else ifeq ($(TARGETL), i386) + # Compiler Toolchain + BCC ?= clang + BCXX ?= clang++ + BLD ?= ld + BASM ?= nasm + + # Compiler and linker flags + ARCH_CFLAGS = \ + -target i686-elf \ + -mno-80387 \ + -mno-mmx \ + -mno-sse \ + -mno-sse2 + + ARCH_LDFLAGS = \ + -z max-page-size=0x1000 \ + -L$(DDK_DIR)/../../boron \ + -lgcc-i686 + + ARCH_ASFLAGS = \ + -f elf32 + + LINK_ARCH = elf_i386 + SMP = no + +else + $(error You cannot build for this architecture right now.) +endif diff --git a/user/CommonMakefile b/user/CommonMakefile index 714108ef..caa280af 100644 --- a/user/CommonMakefile +++ b/user/CommonMakefile @@ -16,10 +16,10 @@ DDK_DIR = ../../common/include SDK_DIR = ../../user/include LINKER_FILE = linker.ld -ARCHITECTURE = AMD64 +TARGET ?= AMD64 # This sucks. -ARCHITECTUREL=$(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(ARCHITECTURE))))))))))))))))))))))))))) +TARGETL=$(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(TARGET))))))))))))))))))))))))))) ifeq ($(IS_LIBRARY),yes) SUFFIX = so @@ -32,8 +32,8 @@ INSTALL_DIR = ../../build # This is the name that our final application executable will have. # Change as needed. -override TARGET := $(BUILD_DIR)/$(APP_NAME).$(ARCHITECTUREL).$(SUFFIX) -override TARGET_INSTALL := $(INSTALL_DIR)/$(ARCHITECTUREL)/$(APP_NAME).$(SUFFIX) +override TARGET_FILE := $(BUILD_DIR)/$(APP_NAME).$(TARGETL).$(SUFFIX) +override TARGET_INSTALL := $(INSTALL_DIR)/$(TARGETL)/$(APP_NAME).$(SUFFIX) # Convenience macro to reliably declare overridable command variables. define DEFAULT_VAR = @@ -57,22 +57,15 @@ ifeq ($(DEBUG2), yes) DEFINES += -DDEBUG2 endif -# It is highly recommended to use a custom built cross toolchain to build a driver. -# We are only using "cc" as a placeholder here. It may work by using -# the host system's toolchain, but this is not guaranteed. -$(eval $(call DEFAULT_VAR,CC,cc)) -$(eval $(call DEFAULT_VAR,CXX,c++)) - -# Same thing for "ld" (the linker). -$(eval $(call DEFAULT_VAR,LD,ld)) +include ../../tools/toolchain.mk # User controllable CFLAGS. CFLAGS ?= -CFLAGS += $(OPT) -pipe -Wall -Wextra -DTARGET_$(ARCHITECTURE) -DIS_USER_MODE $(DEFINES) $(USER_DEFINES) +CFLAGS += $(OPT) -pipe -Wall -Wextra -DTARGET_$(TARGET) -DBORON_TARGET=\"$(TARGETL)\" -DIS_USER_MODE $(DEFINES) $(USER_DEFINES) # User controllable CXXFLAGS. CXXFLAGS ?= -CXXFLAGS += $(OPT) -pipe -Wall -Wextra -DTARGET_$(ARCHITECTURE) -DIS_USER_MODE $(DEFINES) $(USER_DEFINES) +CXXFLAGS += $(OPT) -pipe -Wall -Wextra -DTARGET_$(TARGET) -DBORON_TARGET=\"$(TARGETL)\" -DIS_USER_MODE $(DEFINES) $(USER_DEFINES) # User controllable preprocessor flags. CPPFLAGS ?= @@ -86,7 +79,7 @@ NASMFLAGS += -F dwarf -I$(SRC_DIR) -I$(INC_DIR) -I $(SDK_DIR) -I $(DDK_DIR) LDFLAGS ?= # Internal C flags that should not be changed by the user. -override CFLAGS += \ +CFLAGS += \ -fno-omit-frame-pointer \ -std=c11 \ -ffreestanding \ @@ -94,53 +87,37 @@ override CFLAGS += \ -fno-stack-check \ -fno-lto \ -fPIC \ - -m64 \ - -march=x86-64 \ - -mabi=sysv \ - -mno-80387 \ - -mno-mmx \ - -mno-sse \ - -mno-sse2 \ - -mno-red-zone \ -MMD \ -MP \ - -I. + -I. \ + $(ARCH_CFLAGS) # Internal C++ flags that should not be changed by the user. -override CXXFLAGS += \ +CXXFLAGS += \ -fno-omit-frame-pointer \ -std=c++17 \ -ffreestanding \ -fno-stack-protector \ -fno-stack-check \ -fno-lto \ - -m64 \ - -march=x86-64 \ - -mabi=sysv \ - -mno-80387 \ - -mno-mmx \ - -mno-sse \ - -mno-sse2 \ - -mno-red-zone \ - -MMD \ - -MP \ -fno-exceptions \ -fno-rtti \ - -I. + -I. \ + $(ARCH_CFLAGS) -override LDFLAGSBASE += \ +LDFLAGSBASE += \ + --dynamic-linker libboron.so \ -nostdlib \ - -m elf_x86_64 \ -z max-page-size=0x1000 \ - --dynamic-linker libboron.so + -m $(LINK_ARCH) \ + $(ARCH_LDFLAGS) # Internal linker flags that should not be changed by the user. LDFLAGS += \ $(LDFLAGSBASE) # Internal nasm flags that should not be changed by the user. -NASMFLAGS += \ - -f elf64 +NASMFLAGS += $(ARCH_ASFLAGS) ifeq ($(IS_LIBRARY),yes) LDFLAGS += \ @@ -152,7 +129,7 @@ ifeq ($(INCLUDE_LIBBORON),no) else LDFLAGS += \ -lboron \ - -L../../build/$(ARCHITECTUREL) + -L../../build/$(TARGETL) endif # Use find to glob all *.c, *.S, and *.asm files in the directory and extract the object names. @@ -160,7 +137,7 @@ override CFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -na override CXXFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -name '*.cpp') override ASFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -name '*.S') override NASMFILES := $(shell find -L $(SRC_DIR) -not -path '*/.*' -type f -name '*.asm') -override OBJ := $(patsubst %.o,%.$(ARCHITECTUREL).o,$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(CFILES:.c=.o) $(CXXFILES:.cpp=.o) $(ASFILES:.S=.o) $(NASMFILES:.asm=.o))) +override OBJ := $(patsubst %.o,%.$(TARGETL).o,$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(CFILES:.c=.o) $(CXXFILES:.cpp=.o) $(ASFILES:.S=.o) $(NASMFILES:.asm=.o))) override HEADER_DEPS := $(patsubst %.o,%.d,$(OBJ)) # Default target. @@ -168,45 +145,45 @@ override HEADER_DEPS := $(patsubst %.o,%.d,$(OBJ)) all: application # Link rules for the final driver executable. -$(TARGET): $(OBJ) - @echo "[LD]\tBuilding $(TARGET)" - @$(LD) $(OBJ) $(LDFLAGS) -o $@ +$(TARGET_FILE): $(OBJ) + @echo "[LD]\tBuilding $(TARGET_FILE)" + @$(BLD) $(OBJ) $(LDFLAGS) -o $@ # Include header dependencies. -include $(HEADER_DEPS) # Compilation rules for *.c files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.c +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.c @echo "[CC]\tCompiling $<" @mkdir -p $(dir $@) - @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + @$(BCC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ # Compilation rules for *.cpp files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.cpp +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.cpp @echo "[CXX]\tCompiling $<" @mkdir -p $(dir $@) - @$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ + @$(BCXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ # Compilation rules for *.S files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.S +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.S @echo "[AS]\tCompiling $<" @mkdir -p $(dir $@) - @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + @$(BCC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ # Compilation rules for *.asm (nasm) files. -$(BUILD_DIR)/%.$(ARCHITECTUREL).o: $(SRC_DIR)/%.asm +$(BUILD_DIR)/%.$(TARGETL).o: $(SRC_DIR)/%.asm @echo "[AS]\tCompiling $<" @mkdir -p $(dir $@) - @nasm $(NASMFLAGS) $< -o $@ + @$(BASM) $(NASMFLAGS) $< -o $@ # Remove object files and the final executable. .PHONY: clean clean: @echo "Cleaning" - @rm -rf $(TARGET) $(OBJ) $(HEADER_DEPS) + @rm -rf $(TARGET_FILE) $(OBJ) $(HEADER_DEPS) -application: $(TARGET) install_application +application: $(TARGET_FILE) install_application install_application: - @mkdir -p $(INSTALL_DIR)/$(ARCHITECTUREL) - @cp -f $(TARGET) $(TARGET_INSTALL) + @mkdir -p $(INSTALL_DIR)/$(TARGETL) + @cp -f $(TARGET_FILE) $(TARGET_INSTALL) diff --git a/user/include/svcs.h b/user/include/svcs.h index 338b0436..2b09757c 100644 --- a/user/include/svcs.h +++ b/user/include/svcs.h @@ -20,6 +20,8 @@ BSTATUS OSCreateEvent(PHANDLE OutHandle, POBJECT_ATTRIBUTES ObjectAttributes, in BSTATUS OSCreateMutex(PHANDLE OutHandle, POBJECT_ATTRIBUTES ObjectAttributes); +BSTATUS OSCreatePipe(PHANDLE OutHandle, POBJECT_ATTRIBUTES ObjectAttributes, size_t BufferSize, bool NonBlock); + #ifdef IS_BORON_DLL BSTATUS OSCreateProcessInternal( @@ -64,8 +66,12 @@ BSTATUS OSFreeVirtualMemory( BSTATUS OSGetAlignmentFile(HANDLE Handle, size_t* AlignmentOut); +void* OSGetCurrentPeb(); + void* OSGetCurrentTeb(); +BSTATUS OSGetExitCodeProcess(HANDLE ProcessHandle, int* ExitCodeOut); + BSTATUS OSGetLengthFile(HANDLE FileHandle, uint64_t* Length); BSTATUS OSGetMappedFileHandle(PHANDLE OutHandle, HANDLE ProcessHandle, uintptr_t Address); @@ -110,10 +116,14 @@ BSTATUS OSResetDirectoryReadHead(HANDLE FileHandle); BSTATUS OSResetEvent(HANDLE EventHandle); +BSTATUS OSSetCurrentPeb(void* Ptr); + BSTATUS OSSetCurrentTeb(void* Ptr); BSTATUS OSSetEvent(HANDLE EventHandle); +BSTATUS OSSetExitCode(int ExitCode); + BSTATUS OSSetPebProcess(HANDLE ProcessHandle, void* PebPtr); BSTATUS OSSetSuspendedThread(HANDLE ThreadHandle, bool IsSuspended); diff --git a/user/init/source/main.c b/user/init/source/main.c index 20162004..7c77f8ac 100644 --- a/user/init/source/main.c +++ b/user/init/source/main.c @@ -16,8 +16,8 @@ int _start() //RunTest2(); //RunTest3(); //RunTest4(); - //RunTest5(); - RunTest6(); + RunTest5(); + //RunTest6(); OSExitProcess(0); } diff --git a/user/libboron/source/calls.S b/user/libboron/source/calls.S index 438509c6..7e901c9f 100644 --- a/user/libboron/source/calls.S +++ b/user/libboron/source/calls.S @@ -4,31 +4,9 @@ .section .text .att_syntax -.macro CALL number, name - .globl \name - .type \name, @function -\name: - pushq %rbp - movq %rsp, %rbp - pushq %rbx - pushq %r12 - pushq %r13 - pushq %r14 - pushq %r15 - movq %rcx, %r10 - movq $\number, %rax - syscall - popq %r15 - popq %r14 - popq %r13 - popq %r12 - popq %rbx - popq %rbp - ret -.endm +#ifdef TARGET_AMD64 -// Unlike the previous call macro, this loads 3 extra arguments on the stack into r12, r13, and r14. -.macro CALLEX number, name +.macro CALL number, argcount, name .globl \name .type \name, @function \name: @@ -39,12 +17,23 @@ pushq %r13 pushq %r14 pushq %r15 - movq 16(%rbp), %r12 - movq 24(%rbp), %r13 - movq 32(%rbp), %r14 - movq %rcx, %r10 + + .if \argcount > 6 + movq 16(%rbp), %r12 + .endif + .if \argcount > 7 + movq 24(%rbp), %r13 + .endif + .if \argcount > 8 + movq 32(%rbp), %r14 + .endif + .if \argcount > 3 + movq %rcx, %r10 + .endif + movq $\number, %rax syscall + popq %r15 popq %r14 popq %r13 @@ -54,50 +43,56 @@ ret .endm -CALL 0, OSAllocateVirtualMemory -CALL 1, OSClose -CALL 2, OSCreateEvent -CALL 3, OSCreateMutex -CALL 4, OSCreatePipe -CALL 5, OSCreateProcessInternal -CALL 6, OSCreateThread -CALL 7, OSDeviceIoControl -CALL 8, OSDuplicateHandle -CALL 9, OSExitProcess -CALL 10, OSExitThread -CALL 11, OSFreeVirtualMemory -CALL 12, OSGetAlignmentFile -CALL 13, OSGetCurrentPeb -CALL 14, OSGetCurrentTeb -CALL 15, OSGetExitCodeProcess -CALL 16, OSGetLengthFile -CALL 17, OSGetMappedFileHandle -CALL 18, OSGetTickCount -CALL 19, OSGetTickFrequency -CALL 20, OSGetVersionNumber -CALLEX 21, OSMapViewOfObject -CALL 22, OSOpenEvent -CALL 23, OSOpenFile -CALL 24, OSOpenMutex -CALL 25, OSOutputDebugString -CALL 26, OSPulseEvent -CALL 27, OSQueryEvent -CALL 28, OSQueryMutex -CALL 29, OSReadDirectoryEntries -CALL 30, OSReadFile -CALL 31, OSReleaseMutex -CALL 32, OSResetDirectoryReadHead -CALL 33, OSResetEvent -CALL 34, OSSetCurrentPeb -CALL 35, OSSetCurrentTeb -CALL 36, OSSetEvent -CALL 37, OSSetExitCode -CALL 38, OSSetPebProcess -CALL 39, OSSetSuspendedThread -CALL 40, OSSleep -CALL 41, OSTerminateThread -CALL 42, OSTouchFile -CALL 43, OSWaitForMultipleObjects -CALL 44, OSWaitForSingleObject -CALLEX 45, OSWriteFile -CALL 46, OSWriteVirtualMemory +#elif defined TARGET_I386 + +.macro CALL number, argcount, name + .globl \name + .type \name, @function +\name: + pushl %ebp + movl %esp, %ebp + pushl %ebx + pushl %esi + pushl %edi + + movl $\number, %eax + + .if \argcount > 0 + movl 8(%ebp), %ebx + .endif + .if \argcount > 1 + movl 12(%ebp), %ecx + .endif + .if \argcount > 2 + movl 16(%ebp), %edx + .endif + .if \argcount > 3 + movl 20(%ebp), %esi + .endif + .if \argcount > 4 + movl 24(%ebp), %edi + .endif + + .if \argcount == 6 + /* if we have *exactly* 6 arguments, then just pass the */ + /* last one into EBP */ + movl 28(%ebp), %ebp + .elseif \argcount > 6 + /* if we have *more* than 6 arguments, we need to pass a */ + /* pointer to the last arguments which we'll copy from */ + /* the stack */ + leal 28(%ebp), %ebp + .endif + + int $0x80 + + popl %edi + popl %esi + popl %ebx + popl %ebp + ret +.endmacro + +#endif + +#include "calltbl.h" diff --git a/user/libboron/source/calltbl.h b/user/libboron/source/calltbl.h new file mode 100644 index 00000000..c8e7078b --- /dev/null +++ b/user/libboron/source/calltbl.h @@ -0,0 +1,58 @@ + +CALL 0, 5, OSAllocateVirtualMemory +CALL 1, 1, OSClose +CALL 2, 4, OSCreateEvent +CALL 3, 2, OSCreateMutex +CALL 4, 4, OSCreatePipe +CALL 5, 4, OSCreateProcessInternal +CALL 6, 6, OSCreateThread +CALL 7, 6, OSDeviceIoControl +CALL 8, 4, OSDuplicateHandle +CALL 9, 1, OSExitProcess +CALL 10, 0, OSExitThread +CALL 11, 4, OSFreeVirtualMemory +CALL 12, 2, OSGetAlignmentFile +CALL 13, 0, OSGetCurrentPeb +CALL 14, 0, OSGetCurrentTeb +CALL 15, 2, OSGetExitCodeProcess +CALL 16, 2, OSGetLengthFile +CALL 17, 3, OSGetMappedFileHandle +CALL 18, 1, OSGetTickCount +CALL 19, 1, OSGetTickFrequency +CALL 20, 1, OSGetVersionNumber +// 21 OSMapViewOfObject +CALL 22, 2, OSOpenEvent +CALL 23, 2, OSOpenFile +CALL 24, 2, OSOpenMutex +CALL 25, 2, OSOutputDebugString +CALL 26, 1, OSPulseEvent +CALL 27, 2, OSQueryEvent +CALL 28, 2, OSQueryMutex +CALL 29, 4, OSReadDirectoryEntries +// 30 OSReadFile +CALL 31, 1, OSReleaseMutex +CALL 32, 1, OSResetDirectoryReadHead +CALL 33, 1, OSResetEvent +CALL 34, 1, OSSetCurrentPeb +CALL 35, 1, OSSetCurrentTeb +CALL 36, 1, OSSetEvent +CALL 37, 1, OSSetExitCode +CALL 38, 2, OSSetPebProcess +CALL 39, 2, OSSetSuspendedThread +CALL 40, 1, OSSleep +CALL 41, 1, OSTerminateThread +CALL 42, 1, OSTouchFile +CALL 43, 5, OSWaitForMultipleObjects +CALL 44, 3, OSWaitForSingleObject +// 45 OSWriteFile +CALL 46, 4, OSWriteVirtualMemory + +#ifdef IS_64_BIT +CALL 21, 7, OSMapViewOfObject +CALL 30, 6, OSReadFile +CALL 45, 7, OSWriteFile +#else +CALL 21, 8, OSMapViewOfObject +CALL 30, 7, OSReadFile +CALL 45, 8, OSWriteFile +#endif diff --git a/user/libboron/source/dll.h b/user/libboron/source/dll.h index 4f434ea2..13ee6425 100644 --- a/user/libboron/source/dll.h +++ b/user/libboron/source/dll.h @@ -12,11 +12,19 @@ #define LdrDbgPrint(...) do {} while (0) #endif +enum +{ + FILE_KIND_MAIN_EXECUTABLE, + FILE_KIND_DYNAMIC_LIBRARY, + FILE_KIND_INTERPRETER, +}; + typedef struct { LIST_ENTRY ListEntry; char Name[32]; + int FileKind; uintptr_t ImageBase; PELF_DYNAMIC_ITEM DynamicTable; ELF_DYNAMIC_INFO DynamicInfo; @@ -43,7 +51,7 @@ BSTATUS OSDLLMapElfFile( HANDLE FileHandle, const char* Name, ELF_ENTRY_POINT2* OutEntryPoint, - bool IsMainExecutable + int FileKind ); // Opens a file by name, scanning the PATH environment variable. diff --git a/user/libboron/source/init.c b/user/libboron/source/init.c index 7e68138c..c93da6e9 100644 --- a/user/libboron/source/init.c +++ b/user/libboron/source/init.c @@ -93,10 +93,10 @@ BSTATUS OSDLLMapElfFile( HANDLE Handle, const char* Name, ELF_ENTRY_POINT2* OutEntryPoint, - bool IsMainExecutable + int FileKind ) { - BSTATUS Status; + BSTATUS Status = STATUS_SUCCESS; IO_STATUS_BLOCK Iosb; ELF_HEADER ElfHeader; ELF_PROGRAM_HEADER ElfProgramHeader; @@ -107,6 +107,7 @@ BSTATUS OSDLLMapElfFile( bool NeedFreeProgramHeaders = false; bool NeedReadAndMapFile = true; bool IsSeparateProcess = ProcessHandle != CURRENT_PROCESS_HANDLE; + bool IsMainExecutable = FileKind == FILE_KIND_MAIN_EXECUTABLE; Name = OSDLLOffsetPathAway(Name); LdrDbgPrint("OSDLL: Mapping ELF file %s.", Name); @@ -230,7 +231,7 @@ BSTATUS OSDLLMapElfFile( } // Found it. - LdrDbgPrint("OSDLL: %s's image base is %p", Name, BaseAddress); + LdrDbgPrint("OSDLL: %s's image base is %p (size is %zu)", Name, BaseAddress, RegionSize); ImageBase = (uintptr_t) BaseAddress; // TODO: Now, free the reserved memory. But we may need to avoid a race condition @@ -355,7 +356,7 @@ BSTATUS OSDLLMapElfFile( void* OldAddress = Address; #endif - LdrDbgPrint("OSDLL: Initialized data at offset %p.", OldAddress); + LdrDbgPrint("OSDLL: Initialized data at offset %p, size %zu.", OldAddress, ElfProgramHeader.SizeInMemory); // Initialized data. Status = OSMapViewOfObject( @@ -373,7 +374,9 @@ BSTATUS OSDLLMapElfFile( if (FAILED(Status)) { DbgPrint( - "OSDLL: Failed to map initialized data for the program: %s (%d)", + "OSDLL: Failed to map initialized data for the program: %p %zu %s (%d)", + OldAddress, + ElfProgramHeader.SizeInMemory, RtlGetStatusString(Status), Status ); @@ -383,6 +386,36 @@ BSTATUS OSDLLMapElfFile( if (IsMainExecutable && ElfProgramHeader.Offset == 0) Peb->Loader.FileHeader = Address; + if (ElfProgramHeader.SizeInFile < ElfProgramHeader.SizeInMemory) + { + // TODO: add a system call for this instead! + size_t Offset = ElfProgramHeader.SizeInFile; + size_t Size = ElfProgramHeader.SizeInMemory - ElfProgramHeader.SizeInFile; + + uint8_t* Data = OSAllocate(Size); + if (!Data) + { + DbgPrint("OSDLL: Failed to map part of uninitialized data for the program due to insufficient memory"); + Status = STATUS_INSUFFICIENT_MEMORY; + goto EarlyExit; + } + + memset(Data, 0, Size); + + Status = OSWriteVirtualMemory(ProcessHandle, (char*) Address + Offset, Data, Size); + OSFree(Data); + + if (FAILED(Status)) + { + DbgPrint( + "OSDLL: Failed to map part of uninitialized data for the program: %s (%d)", + RtlGetStatusString(Status), + Status + ); + goto EarlyExit; + } + } + break; } case PROG_DYNAMIC: @@ -429,6 +462,7 @@ BSTATUS OSDLLMapElfFile( } LoadedImage = OSAllocate(sizeof(LOADED_IMAGE)); + LoadedImage->FileKind = FileKind; StringCopySafe(LoadedImage->Name, Name, sizeof(LoadedImage->Name)); // Parse the dynamic segment, if it exists. @@ -473,7 +507,7 @@ BSTATUS OSDLLLoadDynamicLibrary(PPEB Peb, const char* FileName) } ELF_ENTRY_POINT2 EntryPoint; - Status = OSDLLMapElfFile(Peb, CURRENT_PROCESS_HANDLE, Handle, FileName, &EntryPoint, false); + Status = OSDLLMapElfFile(Peb, CURRENT_PROCESS_HANDLE, Handle, FileName, &EntryPoint, FILE_KIND_DYNAMIC_LIBRARY); OSClose(Handle); if (FAILED(Status)) return Status; @@ -489,6 +523,7 @@ void OSDLLAddSelfToDllList() LoadedImage->ImageBase = RtlGetImageBase(); LoadedImage->DynamicTable = _DYNAMIC; + LoadedImage->FileKind = FILE_KIND_INTERPRETER; #ifdef DEBUG // Assert that DYN_NEEDED is not specified. @@ -548,7 +583,7 @@ BSTATUS OSDLLRunImage(PPEB Peb, ELF_ENTRY_POINT2* OutEntryPoint) return Status; } - Status = OSDLLMapElfFile(Peb, CURRENT_PROCESS_HANDLE, FileHandle, Peb->ImageName, OutEntryPoint, true); + Status = OSDLLMapElfFile(Peb, CURRENT_PROCESS_HANDLE, FileHandle, Peb->ImageName, OutEntryPoint, FILE_KIND_MAIN_EXECUTABLE); OSClose(FileHandle); if (FAILED(Status)) @@ -559,6 +594,7 @@ BSTATUS OSDLLRunImage(PPEB Peb, ELF_ENTRY_POINT2* OutEntryPoint) Status, RtlGetStatusString(Status) ); + return Status; } // Load the DLLs in the queue. @@ -611,18 +647,23 @@ BSTATUS OSDLLRunImage(PPEB Peb, ELF_ENTRY_POINT2* OutEntryPoint) { PLOADED_IMAGE LoadedImage = CONTAINING_RECORD(DllEntry, LOADED_IMAGE, ListEntry); - if (!RtlPerformRelocations(&LoadedImage->DynamicInfo, LoadedImage->ImageBase)) + // The interpreter knows how to relocate themselves and has already done so. + if (LoadedImage->FileKind != FILE_KIND_INTERPRETER) { - DbgPrint("OSDLL: Cannot perform relocations on module %s.", LoadedImage->Name); - return STATUS_INVALID_EXECUTABLE; - } - - RtlRelocateRelrEntries(&LoadedImage->DynamicInfo, LoadedImage->ImageBase); - - if (!RtlLinkPlt(&LoadedImage->DynamicInfo, LoadedImage->ImageBase, LoadedImage->Name)) - { - DbgPrint("OSDLL: Module %s could not be linked.", LoadedImage->Name); - return STATUS_INVALID_EXECUTABLE; + LdrDbgPrint("OSDLL: Linking %s, file kind: %d...", LoadedImage->Name, LoadedImage->FileKind); + if (!RtlPerformRelocations(&LoadedImage->DynamicInfo, LoadedImage->ImageBase)) + { + DbgPrint("OSDLL: Cannot perform relocations on module %s.", LoadedImage->Name); + return STATUS_INVALID_EXECUTABLE; + } + + RtlRelocateRelrEntries(&LoadedImage->DynamicInfo, LoadedImage->ImageBase); + + if (!RtlLinkPlt(&LoadedImage->DynamicInfo, LoadedImage->ImageBase, LoadedImage->Name)) + { + DbgPrint("OSDLL: Module %s could not be linked.", LoadedImage->Name); + return STATUS_INVALID_EXECUTABLE; + } } // TODO: Change permissions so that the code segments cannot be written to anymore. diff --git a/user/libboron/source/process.c b/user/libboron/source/process.c index f037eeff..057baf3a 100644 --- a/user/libboron/source/process.c +++ b/user/libboron/source/process.c @@ -220,7 +220,7 @@ BSTATUS OSCreateProcess( // Now map the main image inside. LdrDbgPrint("OSCreateProcess: Mapping main image %s.", ImageName); - Status = OSDLLMapElfFile(Peb, ProcessHandle, FileHandle, ImageName, &EntryPoint, true); + Status = OSDLLMapElfFile(Peb, ProcessHandle, FileHandle, ImageName, &EntryPoint, FILE_KIND_MAIN_EXECUTABLE); OSClose(FileHandle); if (FAILED(Status)) @@ -254,7 +254,7 @@ BSTATUS OSCreateProcess( //Status = OSDLLMapSelfIntoProcess(ProcessHandle, InterpreterFileHandle, PebSize, &EntryPoint); LdrDbgPrint("OSCreateProcess: Mapping interpreter %s.", Interpreter); - Status = OSDLLMapElfFile(Peb, ProcessHandle, InterpreterFileHandle, Interpreter, &EntryPoint, false); + Status = OSDLLMapElfFile(Peb, ProcessHandle, InterpreterFileHandle, Interpreter, &EntryPoint, FILE_KIND_INTERPRETER); OSClose(InterpreterFileHandle); if (FAILED(Status)) diff --git a/user/libboron/source/reloc.c b/user/libboron/source/reloc.c index 3b22f85e..a9b02b7d 100644 --- a/user/libboron/source/reloc.c +++ b/user/libboron/source/reloc.c @@ -72,42 +72,46 @@ void RelocateSelf(PPEB Peb) { PELF_RELA Rela = (PELF_RELA) (ImageBase + RelaOffset + i); - #ifndef TARGET_AMD64 - #error TODO! - #else uint32_t RelType = (uint32_t) Rela->Info; + #if defined TARGET_AMD64 if (RelType != R_X86_64_RELATIVE) + #elif defined TARGET_I386 + if (RelType != R_386_RELATIVE) + #else + #error TODO! + #endif // Libboron.so, in addition to being restricted in so many // different ways already, also cannot import things from // other libraries. As such, the only relocation type you - // should see is R_X86_64_RELATIVE. + // should see is R_*_RELATIVE. __builtin_trap(); uintptr_t* Reloc = (uintptr_t*) (ImageBase + Rela->Offset); *Reloc = ImageBase + Rela->Addend; - #endif } for (size_t i = 0; i < RelSize; i += sizeof(ELF_REL)) { PELF_REL Rel = (PELF_REL) (ImageBase + RelOffset + i); - #ifndef TARGET_AMD64 - #error TODO! - #else uint32_t RelType = (uint32_t) Rel->Info; + #if defined TARGET_AMD64 if (RelType != R_X86_64_RELATIVE) + #elif defined TARGET_I386 + if (RelType != R_386_RELATIVE) + #else + #error TODO! + #endif // Libboron.so, in addition to being restricted in so many // different ways already, also cannot import things from // other libraries. As such, the only relocation type you - // should see is R_X86_64_RELATIVE. + // should see is R_*_RELATIVE. __builtin_trap(); uintptr_t* Reloc = (uintptr_t*) (ImageBase + Rel->Offset); *Reloc += ImageBase; - #endif } // Thanks to mlibc for this one.