48 Commits

Author SHA1 Message Date
Justin C. Miller
b3e49590a7 Add logging framework 2018-04-28 19:18:53 -07:00
Justin C. Miller
358837ed69 Implement first-pass simple virtual memory manager 2018-04-28 02:17:17 -07:00
Justin C. Miller
2a353830c2 Update notes 2018-04-28 02:12:46 -07:00
Justin C. Miller
14f51436d7 Load ELF file with bootloader instead of flat binary 2018-04-28 02:12:08 -07:00
Justin C. Miller
57e5465c2d Add -fno-exceptions and QEMU logging to Makefile 2018-04-27 22:20:46 -07:00
Justin C. Miller
d9619e65a2 Fix indirection bug with marked offset pointer mapping 2018-04-26 17:27:55 -07:00
Justin C. Miller
9754994e0c Standardize types used in memory_pages 2018-04-26 16:47:51 -07:00
Justin C. Miller
34c894b15d Enable allocation and mapping of pages 2018-04-26 11:10:32 -07:00
Justin C. Miller
a2665d9247 Fix printf bug in page_block::dump 2018-04-26 11:09:34 -07:00
Justin C. Miller
1e3ae67646 Add -no-reboot to QEMUOPTS 2018-04-26 11:07:58 -07:00
Justin C. Miller
25b9625635 paging finally works 2018-04-25 19:53:22 -07:00
Justin C. Miller
2404b22c1f support widths in printf 2018-04-25 19:52:27 -07:00
Justin C. Miller
bed882f41c Enable paging WIP 2018-04-25 10:48:14 -07:00
Justin C. Miller
fd9e0944cb Add rudimentary printf to console 2018-04-25 10:43:17 -07:00
Justin C. Miller
7e462319c9 Fix inconsistenly-named frame_buffer_size 2018-04-24 09:50:07 -07:00
Justin C. Miller
94de87ef86 Refactor screen ouput from main console code 2018-04-24 09:32:57 -07:00
Justin C. Miller
eb13f1f4fb Fix missing return 2018-04-24 08:54:38 -07:00
Justin C. Miller
0a6c39ded4 Remove -ggdb from LDFLAGS 2018-04-23 20:37:15 -07:00
Justin C. Miller
ff1aac64c1 Use our own stack space, not efi's. 2018-04-23 10:22:43 -07:00
Justin C. Miller
ef24894211 Add stupid first serial output 2018-04-23 10:22:02 -07:00
Justin C. Miller
1113164505 Join page_block insert methods into one 2018-04-22 23:27:15 -07:00
Justin C. Miller
1de73de2e3 Move page table allocation to top 256GiB.
I forgot to account for tracking page table physical addresses, so
this is a bit of an overhaul. Major changes:
- Refactor bootstrap code into more functions and:
  - Only allocate 32 pages of scratch space
  - Remap remaining space into top 256GiB, the "page table space"
- Use the page table space to directly offset-map page table pages
  from their physical addresses, to avoid tracking overhead.
- Refactor page_block list functions into static functions to better
  handle null/empty lists
2018-04-22 21:52:59 -07:00
Justin C. Miller
571cc5a1da Add mmu logging to qemu.bat 2018-04-22 21:50:35 -07:00
Justin C. Miller
8cb0803605 Make page_manager::unmap_pages() handle multiple blocks 2018-04-22 13:37:44 -07:00
Justin C. Miller
95d52b87f4 Initialize page_manager.
Page manager now:
- Caches mapped pages and page_block structs
- Can unmap memory ranges
- Unmaps extra kernel memory during it's init
2018-04-22 02:48:45 -07:00
Justin C. Miller
07fd3abe2c Move page size and higher half offset to constants 2018-04-21 20:58:58 -07:00
Justin C. Miller
5dedd2e0e0 Finish memory bootstrap sequence.
Now we're setting up all our own page tables, and handing off to
page_manager's init function. (Which is still NYI.)
2018-04-21 19:32:39 -07:00
Justin C. Miller
57abb03deb Rearrange memory manager into two classes.
page_manager and memory_manager are now separate, and are also pre
allocated in the kernel so they don't have to allocate themselves.
2018-04-21 17:34:33 -07:00
Justin C. Miller
4a38a74b16 Refactor memory code.
Break out some more bootstrap code into functions. Add the start of
some Doxygen doc comments to help organize my thoughts.
2018-04-21 16:49:39 -07:00
Justin C. Miller
9a45ea562b Updating notes 2018-04-21 02:53:18 -07:00
Justin C. Miller
da404f520d Rearrange memory bootstrapping code.
Move EFI-related code and initial memory manager bootstrap code
to memory_bootstrap.cpp, move memory page structs to their own
memory_page.h/cpp files.
2018-04-21 02:52:52 -07:00
Justin C. Miller
799ad8b264 Add operator| to enum_bitfields 2018-04-21 02:47:37 -07:00
Justin C. Miller
818b51d57c Make qemu.bat log to popcorn.log 2018-04-21 02:45:33 -07:00
Justin C. Miller
e8866abc7a Bootstrap in-kernel memory management 2018-04-20 02:15:56 -07:00
Justin C. Miller
3b560c063a Move kernel to higher half.
Return to having the bootloader re-map the kernel into the higher
half before jumping into the kernel entrypoint, so we don't have
to juggle pointers inside the kernel.
2018-04-19 01:37:34 -07:00
Justin C. Miller
a27b8d6a3a Add/move code into kutil library 2018-04-17 23:41:28 -07:00
Justin C. Miller
2050b89334 Move src/modules/main -> src/kernel 2018-04-17 09:45:05 -07:00
Justin C. Miller
504de44ff3 Make APIC addresses uint32_t*
APIC registers need to be read/written 32 bits at a time, so
best to keep these as pointers to that size.
2018-04-17 09:20:56 -07:00
Justin C. Miller
71a6f13fa5 Make interrupt_isrs.inc the one source of ISRs 2018-04-17 01:39:21 -07:00
Justin C. Miller
f62fbefe54 Parse APIC addresses out of ACPI tables. 2018-04-16 23:56:03 -07:00
Justin C. Miller
6c29024eac Remove broken console QEMU bat file. 2018-04-16 01:17:11 -07:00
Justin C. Miller
b7f18c0d31 Parse the ACPI XSDT and find ACPI tables. 2018-04-16 01:15:42 -07:00
Justin C. Miller
696c29086b Clean up isr references in main.cpp 2018-04-15 18:44:47 -07:00
Justin C. Miller
bce281606e Finish ISR code for CPU faults (ISRs 0-31) 2018-04-15 18:40:21 -07:00
Justin C. Miller
2388a92085 Add initial IDT and GDT setup.
WIP interrupt handling. Interrupts do not yet return.
2018-04-15 15:46:20 -07:00
Justin C. Miller
447991e82b Add comment to assert 2018-04-15 09:14:02 -07:00
Justin C. Miller
a8984350da Add new kassert macro. 2018-04-14 18:34:34 -07:00
Justin C. Miller
4d5da72e2e Add parse_memmap.py script 2018-04-13 23:54:34 -07:00
52 changed files with 3501 additions and 423 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ build
*.bak *.bak
tags tags
.gdbinit .gdbinit
popcorn.log

View File

@@ -3,14 +3,15 @@ ARCH ?= x86_64
include src/arch/$(ARCH)/config.mk include src/arch/$(ARCH)/config.mk
BUILD_D := build BUILD_D := build
KERN_D := src/kernel
ARCH_D := src/arch/$(ARCH) ARCH_D := src/arch/$(ARCH)
VERSION ?= $(shell git describe --dirty --always) VERSION ?= $(shell git describe --dirty --always)
GITSHA ?= $(shell git rev-parse --short HEAD) GITSHA ?= $(shell git rev-parse --short HEAD)
KERNEL_FILENAME:= popcorn.bin KERNEL_FILENAME:= popcorn.elf
KERNEL_FONT := assets/fonts/tamsyn8x16r.psf KERNEL_FONT := assets/fonts/tamsyn8x16r.psf
MODULES := main MODULES := kutil
EFI_DIR := external/gnu-efi EFI_DIR := external/gnu-efi
@@ -50,20 +51,21 @@ WARNFLAGS += -Wno-attributes -Wno-sign-compare -Wno-multichar
WARNFLAGS += -Wno-div-by-zero -Wno-endif-labels -Wno-pragmas WARNFLAGS += -Wno-div-by-zero -Wno-endif-labels -Wno-pragmas
WARNFLAGS += -Wno-format-extra-args -Wno-unused-result WARNFLAGS += -Wno-format-extra-args -Wno-unused-result
WARNFLAGS += -Wno-deprecated-declarations -Wno-unused-function WARNFLAGS += -Wno-deprecated-declarations -Wno-unused-function
WARNFLAGS += -Wno-unused-but-set-parameter
ASFLAGS ?= ASFLAGS ?=
ASFLAGS += -p $(BUILD_D)/versions.s ASFLAGS += -p $(BUILD_D)/versions.s
CFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS) CFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS)
CFLAGS += -DGIT_VERSION="\"$(VERSION)\"" CFLAGS += -DGIT_VERSION="\"$(VERSION)\""
CFLAGS += -std=c11 CFLAGS += -std=c11 -mcmodel=large
CXXFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS) CXXFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS)
CXXFLAGS += -DGIT_VERSION="\"$(VERSION)\"" CXXFLAGS += -DGIT_VERSION="\"$(VERSION)\""
CXXFLAGS += -std=c++14 CXXFLAGS += -std=c++14 -mcmodel=large
CXXFLAGS += -fno-exceptions -fno-rtti
BOOT_CFLAGS := -I src/boot $(CFLAGS) -fPIC -fshort-wchar BOOT_CFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS)
BOOT_CFLAGS += -std=c11 -I src/boot -fPIC -fshort-wchar
BOOT_CFLAGS += -DKERNEL_FILENAME="L\"$(KERNEL_FILENAME)\"" BOOT_CFLAGS += -DKERNEL_FILENAME="L\"$(KERNEL_FILENAME)\""
BOOT_CFLAGS += -DGIT_VERSION_WIDE="L\"$(VERSION)\"" BOOT_CFLAGS += -DGIT_VERSION_WIDE="L\"$(VERSION)\""
BOOT_CFLAGS += -DGNU_EFI_USE_MS_ABI -DHAVE_USE_MS_ABI BOOT_CFLAGS += -DGNU_EFI_USE_MS_ABI -DHAVE_USE_MS_ABI
@@ -74,7 +76,7 @@ ifdef MAX_HRES
BOOT_CFLAGS += -DMAX_HRES=$(MAX_HRES) BOOT_CFLAGS += -DMAX_HRES=$(MAX_HRES)
endif endif
LDFLAGS := -L $(BUILD_D) -ggdb LDFLAGS := -L $(BUILD_D) -g
LDFLAGS += -nostdlib -znocombreloc -Bsymbolic -nostartfiles LDFLAGS += -nostdlib -znocombreloc -Bsymbolic -nostartfiles
BOOT_LDFLAGS := $(LDFLAGS) -shared BOOT_LDFLAGS := $(LDFLAGS) -shared
@@ -92,12 +94,15 @@ INIT_DEP := $(BUILD_D)/.builddir
BOOT_SRCS := $(wildcard src/boot/*.c) BOOT_SRCS := $(wildcard src/boot/*.c)
BOBJS += $(patsubst src/boot/%,$(BUILD_D)/boot/%,$(patsubst %,%.o,$(BOOT_SRCS))) BOBJS += $(patsubst src/boot/%,$(BUILD_D)/boot/%,$(patsubst %,%.o,$(BOOT_SRCS)))
BDEPS := $(patsubst src/boot/%,$(BUILD_D)/boot/%,$(patsubst %,%.d,$(BOOT_SRCS)))
ARCH_SRCS := $(wildcard $(ARCH_D)/*.s) KERN_SRCS := $(wildcard $(KERN_D)/*.s)
ARCH_SRCS += $(wildcard $(ARCH_D)/*.c) KERN_SRCS += $(wildcard $(KERN_D)/*.c)
KOBJS += $(patsubst $(ARCH_D)/%,$(BUILD_D)/arch/%,$(patsubst %,%.o,$(ARCH_SRCS))) KERN_SRCS += $(wildcard $(KERN_D)/*.cpp)
DEPS := $(patsubst $(ARCH_D)/%,$(BUILD_D)/arch/%,$(patsubst %,%.d,$(ARCH_SRCS))) KERN_SRCS += $(wildcard $(ARCH_D)/*.s)
KERN_SRCS += $(wildcard $(ARCH_D)/*.c)
KERN_OBJS := $(patsubst src/%, $(BUILD_D)/%, $(patsubst %,%.o,$(KERN_SRCS)))
MOD_TARGETS := MOD_TARGETS :=
PARTED ?= /sbin/parted PARTED ?= /sbin/parted
@@ -108,9 +113,9 @@ OVMF ?= assets/ovmf/x64/OVMF.fd
QEMUOPTS := -pflash $(BUILD_D)/flash.img QEMUOPTS := -pflash $(BUILD_D)/flash.img
QEMUOPTS += -drive file=$(BUILD_D)/fs.img,format=raw QEMUOPTS += -drive file=$(BUILD_D)/fs.img,format=raw
QEMUOPTS += -smp $(CPUS) QEMUOPTS += -smp $(CPUS) -m 512
QEMUOPTS += -m 512 QEMUOPTS += -d mmu,guest_errors,int -D popcorn.log
QEMUOPTS += -d guest_errors QEMUOPTS += -no-reboot
QEMUOPTS += $(QEMUEXTRA) QEMUOPTS += $(QEMUEXTRA)
@@ -120,19 +125,21 @@ init: $(INIT_DEP)
$(INIT_DEP): $(INIT_DEP):
mkdir -p $(BUILD_D) $(patsubst %,$(BUILD_D)/d.%,$(MODULES)) mkdir -p $(BUILD_D) $(patsubst %,$(BUILD_D)/d.%,$(MODULES))
mkdir -p $(BUILD_D)/boot mkdir -p $(BUILD_D)/boot
mkdir -p $(BUILD_D)/arch mkdir -p $(patsubst src/%,$(BUILD_D)/%,$(ARCH_D))
mkdir -p $(patsubst src/%,$(BUILD_D)/%,$(KERN_D))
touch $(INIT_DEP) touch $(INIT_DEP)
clean: clean:
rm -rf $(BUILD_D)/* $(BUILD_D)/.version $(BUILD_D)/.builddir rm -rf $(BUILD_D)/* $(BUILD_D)/.version $(BUILD_D)/.builddir
vars:
@echo "KERN_SRCS: " $(KERN_SRCS)
@echo "KERN_OBJS: " $(KERN_OBJS)
dist-clean: clean dist-clean: clean
make -C external/gnu-efi clean make -C external/gnu-efi clean
dump: $(BUILD_D)/kernel.dump .PHONY: all clean dist-clean init vars
vim $<
.PHONY: all clean dist-clean init dump
$(BUILD_D)/.version: $(BUILD_D)/.version:
echo '$(VERSION)' | cmp -s - $@ || echo '$(VERSION)' > $@ echo '$(VERSION)' | cmp -s - $@ || echo '$(VERSION)' > $@
@@ -141,7 +148,7 @@ $(BUILD_D)/versions.s:
./parse_version.py "$(VERSION)" "$(GITSHA)" > $@ ./parse_version.py "$(VERSION)" "$(GITSHA)" > $@
-include x $(patsubst %,src/modules/%/module.mk,$(MODULES)) -include x $(patsubst %,src/modules/%/module.mk,$(MODULES))
-include x $(DEPS) -include x $(shell find $(BUILD_D) -type f -name '*.d')
$(EFI_LIB): $(EFI_LIB):
make -C external/gnu-efi all make -C external/gnu-efi all
@@ -162,38 +169,36 @@ $(BUILD_D)/boot.debug.efi: $(BUILD_D)/boot.elf
-j .debug_aranges -j .debug_line -j .debug_macinfo \ -j .debug_aranges -j .debug_line -j .debug_macinfo \
--target=efi-app-$(ARCH) $^ $@ --target=efi-app-$(ARCH) $^ $@
$(BUILD_D)/%.bin: $(BUILD_D)/%.elf
$(OBJC) $< -O binary $@
$(BUILD_D)/boot.dump: $(BUILD_D)/boot.efi $(BUILD_D)/boot.dump: $(BUILD_D)/boot.efi
$(OBJD) -D -S $< > $@ $(OBJD) -D -S $< > $@
$(BUILD_D)/boot/%.s.o: src/boot/%.s $(BUILD_D)/versions.s $(INIT_DEP) $(BUILD_D)/boot/%.s.o: src/boot/%.s $(BUILD_D)/versions.s $(INIT_DEP)
$(AS) $(ASFLAGS) -o $@ $< $(AS) $(ASFLAGS) -i src/boot/ -o $@ $<
$(BUILD_D)/boot/%.c.o: src/boot/%.c $(INIT_DEP) $(BUILD_D)/boot/%.c.o: src/boot/%.c $(INIT_DEP)
$(CC) $(BOOT_CFLAGS) -c -o $@ $< $(CC) $(BOOT_CFLAGS) -c -o $@ $<
$(BUILD_D)/kernel.elf: $(KOBJS) $(MOD_TARGETS) $(ARCH_D)/kernel.ld $(BUILD_D)/kernel.elf: $(KERN_OBJS) $(MOD_TARGETS) $(ARCH_D)/kernel.ld
$(LD) $(LDFLAGS) -u _header -T $(ARCH_D)/kernel.ld -o $@ $(patsubst %,-l%,$(MODULES)) $(KOBJS) $(LD) $(LDFLAGS) -u _header -T $(ARCH_D)/kernel.ld -o $@ $(KERN_OBJS) $(patsubst %,-l%,$(MODULES))
$(OBJC) --only-keep-debug $@ $@.sym $(OBJC) --only-keep-debug $@ $@.sym
$(OBJD) -D -S $@ > $@.dump
$(BUILD_D)/kernel.dump: $(BUILD_D)/kernel.elf $(BUILD_D)/%.s.o: src/%.s $(BUILD_D)/versions.s $(INIT_DEP)
$(OBJD) -D -S $< > $@ $(AS) $(ASFLAGS) -i $(ARCH_D)/ -i $(KERN_D)/ -o $@ $<
$(BUILD_D)/arch/%.s.o: $(ARCH_D)/%.s $(BUILD_D)/versions.s $(INIT_DEP) $(BUILD_D)/%.c.o: src/%.c $(INIT_DEP)
$(AS) $(ASFLAGS) -o $@ $<
$(BUILD_D)/arch/%.c.o: $(ARCH_D)/%.c $(INIT_DEP)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(BUILD_D)/%.cpp.o: src/%.cpp $(INIT_DEP)
$(CXX) $(CXXFLAGS) -c -o $@ $<
$(BUILD_D)/flash.img: $(OVMF) $(BUILD_D)/flash.img: $(OVMF)
cp $^ $@ cp $^ $@
$(BUILD_D)/screenfont.psf: $(KERNEL_FONT) $(BUILD_D)/screenfont.psf: $(KERNEL_FONT)
cp $^ $@ cp $^ $@
$(BUILD_D)/fs.img: $(BUILD_D)/boot.efi $(BUILD_D)/kernel.bin $(BUILD_D)/screenfont.psf $(BUILD_D)/fs.img: $(BUILD_D)/boot.efi $(BUILD_D)/kernel.elf $(BUILD_D)/screenfont.psf
$(eval TEMPFILE := $(shell mktemp --suffix=.img)) $(eval TEMPFILE := $(shell mktemp --suffix=.img))
dd if=/dev/zero of=$@.tmp bs=512 count=93750 dd if=/dev/zero of=$@.tmp bs=512 count=93750
$(PARTED) $@.tmp -s -a minimal mklabel gpt $(PARTED) $@.tmp -s -a minimal mklabel gpt
@@ -204,7 +209,7 @@ $(BUILD_D)/fs.img: $(BUILD_D)/boot.efi $(BUILD_D)/kernel.bin $(BUILD_D)/screenfo
mmd -i $(TEMPFILE) ::/EFI mmd -i $(TEMPFILE) ::/EFI
mmd -i $(TEMPFILE) ::/EFI/BOOT mmd -i $(TEMPFILE) ::/EFI/BOOT
mcopy -i $(TEMPFILE) $(BUILD_D)/boot.efi ::/EFI/BOOT/BOOTX64.efi mcopy -i $(TEMPFILE) $(BUILD_D)/boot.efi ::/EFI/BOOT/BOOTX64.efi
mcopy -i $(TEMPFILE) $(BUILD_D)/kernel.bin ::$(KERNEL_FILENAME) mcopy -i $(TEMPFILE) $(BUILD_D)/kernel.elf ::$(KERNEL_FILENAME)
mcopy -i $(TEMPFILE) $(BUILD_D)/screenfont.psf ::screenfont.psf mcopy -i $(TEMPFILE) $(BUILD_D)/screenfont.psf ::screenfont.psf
mlabel -i $(TEMPFILE) ::Popcorn_OS mlabel -i $(TEMPFILE) ::Popcorn_OS
dd if=$(TEMPFILE) of=$@.tmp bs=512 count=91669 seek=2048 conv=notrunc dd if=$(TEMPFILE) of=$@.tmp bs=512 count=91669 seek=2048 conv=notrunc
@@ -217,12 +222,15 @@ $(BUILD_D)/fs.iso: $(BUILD_D)/fs.img
xorriso -as mkisofs -R -f -e fs.img -no-emul-boot -o $@ $(BUILD_D)/iso xorriso -as mkisofs -R -f -e fs.img -no-emul-boot -o $@ $(BUILD_D)/iso
qemu: $(BUILD_D)/fs.img $(BUILD_D)/flash.img qemu: $(BUILD_D)/fs.img $(BUILD_D)/flash.img
-rm popcorn.log
"$(QEMU)" $(QEMUOPTS) -nographic "$(QEMU)" $(QEMUOPTS) -nographic
qemu-window: $(BUILD_D)/fs.img $(BUILD_D)/flash.img qemu-window: $(BUILD_D)/fs.img $(BUILD_D)/flash.img
-rm popcorn.log
"$(QEMU)" $(QEMUOPTS) "$(QEMU)" $(QEMUOPTS)
qemu-gdb: $(BUILD_D)/fs.img $(BUILD_D)/boot.debug.efi $(BUILD_D)/flash.img $(BUILD_D)/kernel.elf qemu-gdb: $(BUILD_D)/fs.img $(BUILD_D)/boot.debug.efi $(BUILD_D)/flash.img $(BUILD_D)/kernel.elf
"$(QEMU)" $(QEMUOPTS) -d mmu,guest_errors,page -D popcorn.log -s -nographic -rm popcorn.log
"$(QEMU)" $(QEMUOPTS) -s -nographic
# vim: ft=make ts=4 # vim: ft=make ts=4

View File

@@ -1,10 +1,9 @@
# Design / WIP notes # Design / WIP notes
## Bootloader / UEFI ## TODO
* What is the interface between the UEFI boot portion and the kernel?
* Allocate pages, use UEFI reserved mapping types (0x70000000 - 0x7fffffff)
* Passing memory map: Allocate earliest pages and fill with UEFI's memory map
stuctures?
- Better page-allocation model
- Allow for more than one IOAPIC in ACPI module
- Slab allocator for kernel structures
- mark kernel memory pages global

View File

@@ -29,7 +29,7 @@ $(MOD_BUILD_D)/%.cpp.o: $(MOD_SRC_D)/%.cpp $(INIT_DEP)
$(CXX) $(CXXFLAGS) -c -o $@ $< $(CXX) $(CXXFLAGS) -c -o $@ $<
$(MOD_BUILD_D)/%.s.o: $(MOD_SRC_D)/%.s $(BUILD_D)/versions.s $(INIT_DEP) $(MOD_BUILD_D)/%.s.o: $(MOD_SRC_D)/%.s $(BUILD_D)/versions.s $(INIT_DEP)
$(AS) $(ASFLAGS) -o $@ $< $(AS) $(ASFLAGS) -i $(MOD_SRC_D)/ -o $@ $<
DEPS += $(patsubst %.o,%.d,$(OBJS_$(MOD_NAME))) DEPS += $(patsubst %.o,%.d,$(OBJS_$(MOD_NAME)))

View File

@@ -1,2 +0,0 @@
call make.bat
qemu-system-x86_64.exe -bios .\assets\ovmf\x64\OVMF.fd -hda .\build\fs.img -m 512 -vga cirrus

View File

@@ -1 +1,3 @@
qemu-system-x86_64.exe -bios .\assets\ovmf\x64\OVMF.fd -hda .\build\fs.img -m 512 -nographic -echr 23 call make.bat
del popcorn.log
qemu-system-x86_64.exe -bios .\assets\ovmf\x64\OVMF.fd -hda .\build\fs.img -m 512 -vga cirrus -d guest_errors,int,mmu -D popcorn.log -no-reboot

60
scripts/parse_memmap.py Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
from struct import unpack_from, calcsize
import sys
memory_type_names = {
0: "EfiReservedMemoryType",
1: "EfiLoaderCode",
2: "EfiLoaderData",
3: "EfiBootServicesCode",
4: "EfiBootServicesData",
5: "EfiRuntimeServicesCode",
6: "EfiRuntimeServicesData",
7: "EfiConventionalMemory",
8: "EfiUnusableMemory",
9: "EfiACPIReclaimMemory",
10: "EfiACPIMemoryNVS",
11: "EfiMemoryMappedIO",
12: "EfiMemoryMappedIOPortSpace",
13: "EfiPalCode",
14: "EfiPersistentMemory",
0x80000000: "Kernel Image",
0x80000001: "Kernel Data",
}
EFI_MEMORY_UC = 0x0000000000000001
EFI_MEMORY_WC = 0x0000000000000002
EFI_MEMORY_WT = 0x0000000000000004
EFI_MEMORY_WB = 0x0000000000000008
EFI_MEMORY_UCE = 0x0000000000000010
EFI_MEMORY_WP = 0x0000000000001000
EFI_MEMORY_RP = 0x0000000000002000
EFI_MEMORY_XP = 0x0000000000004000
EFI_MEMORY_NV = 0x0000000000008000
EFI_MEMORY_MORE_RELIABLE = 0x0000000000010000
EFI_MEMORY_RO = 0x0000000000020000
EFI_MEMORY_RUNTIME = 0x8000000000000000
fmt = "LQQQQQ"
size = calcsize(fmt)
print("Descriptor size: {} bytes\n".format(size))
if size != 48:
sys.exit(1)
data = open(sys.argv[1], 'rb').read()
length = len(data)
consumed = 0
while length - consumed > size:
memtype, phys, virt, pages, attr, pad = unpack_from(fmt, data, consumed)
consumed += size
if pages == 0: break
memtype = memory_type_names.get(memtype, "{:016x}".format(memtype))
runtime = {EFI_MEMORY_RUNTIME: "*"}.get(attr & EFI_MEMORY_RUNTIME, " ")
print("{:>23}{} {:016x} {:016x} [{:4d}]".format(memtype, runtime, phys, virt, pages))

View File

@@ -1,7 +1,8 @@
ENTRY(_start) ENTRY(_start)
SECTIONS SECTIONS
{ {
. = 0x100000; OFFSET = 0xFFFF800000000000;
. = OFFSET + 0x100000;
.header : { .header : {
header = .; header = .;

77
src/boot/elf.h Normal file
View File

@@ -0,0 +1,77 @@
#pragma once
#include <stdint.h>
#ifndef ELF_VERSION
#define ELF_VERSION 1
#endif
#ifndef ELF_WORDSIZE
#define ELF_WORDSIZE 2
#endif
#ifndef ELF_ENDIAN
#define ELF_ENDIAN 1
#endif
#ifndef ELF_OSABI
#define ELF_OSABI 0
#endif
#ifndef ELF_MACHINE
#define ELF_MACHINE 0x3e
#endif
const unsigned ELF_PT_LOAD = 1;
struct elf_ident
{
char magic[4];
uint8_t word_size;
uint8_t endianness;
uint8_t version;
uint8_t os_abi;
uint64_t reserved;
} __attribute__ ((packed));
struct elf_header
{
struct elf_ident ident;
uint16_t type;
uint16_t machine;
uint32_t version;
uint64_t entrypoint;
uint64_t ph_offset;
uint64_t sh_offset;
uint32_t flags;
uint16_t eh_size;
uint16_t ph_entsize;
uint16_t ph_num;
uint16_t sh_entsize;
uint16_t sh_num;
uint16_t sh_str_idx;
} __attribute__ ((packed));
struct elf_program_header
{
uint32_t type;
uint32_t flags;
uint64_t offset;
uint64_t vaddr;
uint64_t paddr;
uint64_t file_size;
uint64_t mem_size;
uint64_t align;
} __attribute__ ((packed));

View File

@@ -1,3 +1,4 @@
#include "elf.h"
#include "guids.h" #include "guids.h"
#include "loader.h" #include "loader.h"
#include "memory.h" #include "memory.h"
@@ -13,8 +14,7 @@ loader_alloc_pages(
EFI_BOOT_SERVICES *bootsvc, EFI_BOOT_SERVICES *bootsvc,
EFI_MEMORY_TYPE mem_type, EFI_MEMORY_TYPE mem_type,
size_t *length, size_t *length,
void **pages, void **pages)
void **next)
{ {
EFI_STATUS status; EFI_STATUS status;
@@ -33,31 +33,26 @@ loader_alloc_pages(
*length = page_count * PAGE_SIZE; *length = page_count * PAGE_SIZE;
*pages = (void *)addr; *pages = (void *)addr;
*next = (void*)(addr + *length);
return EFI_SUCCESS; return EFI_SUCCESS;
} }
EFI_STATUS EFI_STATUS
loader_load_file( loader_load_font(
EFI_BOOT_SERVICES *bootsvc, EFI_BOOT_SERVICES *bootsvc,
EFI_FILE_PROTOCOL *root, EFI_FILE_PROTOCOL *root,
const CHAR16 *filename, struct loader_data *data)
EFI_MEMORY_TYPE mem_type,
void **data,
size_t *length,
void **next)
{ {
EFI_STATUS status; EFI_STATUS status;
EFI_FILE_PROTOCOL *file = NULL; EFI_FILE_PROTOCOL *file = NULL;
status = root->Open(root, &file, (CHAR16 *)filename, EFI_FILE_MODE_READ, status = root->Open(root, &file, (CHAR16 *)font_name, EFI_FILE_MODE_READ,
EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM); EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM);
if (status == EFI_NOT_FOUND) if (status == EFI_NOT_FOUND)
return status; return status;
CHECK_EFI_STATUS_OR_RETURN(status, L"Opening file %s", filename); CHECK_EFI_STATUS_OR_RETURN(status, L"Opening file %s", font_name);
char info[sizeof(EFI_FILE_INFO) + 100]; char info[sizeof(EFI_FILE_INFO) + 100];
size_t info_length = sizeof(info); size_t info_length = sizeof(info);
@@ -65,12 +60,16 @@ loader_load_file(
status = file->GetInfo(file, &guid_file_info, &info_length, info); status = file->GetInfo(file, &guid_file_info, &info_length, info);
CHECK_EFI_STATUS_OR_RETURN(status, L"Getting file info"); CHECK_EFI_STATUS_OR_RETURN(status, L"Getting file info");
*length = ((EFI_FILE_INFO *)info)->FileSize; data->font_length = ((EFI_FILE_INFO *)info)->FileSize;
status = loader_alloc_pages(bootsvc, mem_type, length, data, next); status = loader_alloc_pages(
bootsvc,
KERNEL_FONT_MEMTYPE,
&data->font_length,
&data->font);
CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating pages"); CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating pages");
status = file->Read(file, length, *data); status = file->Read(file, &data->font_length, data->font);
CHECK_EFI_STATUS_OR_RETURN(status, L"Reading file"); CHECK_EFI_STATUS_OR_RETURN(status, L"Reading file");
status = file->Close(file); status = file->Close(file);
@@ -78,7 +77,96 @@ loader_load_file(
return EFI_SUCCESS; return EFI_SUCCESS;
} }
EFI_STATUS
loader_load_elf(
EFI_BOOT_SERVICES *bootsvc,
EFI_FILE_PROTOCOL *root,
struct loader_data *data)
{
EFI_STATUS status;
EFI_FILE_PROTOCOL *file = NULL;
status = root->Open(root, &file, (CHAR16 *)kernel_name, EFI_FILE_MODE_READ,
EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM);
if (status == EFI_NOT_FOUND)
return status;
uint64_t length = 0;
data->kernel = 0;
data->kernel_entry = 0;
data->kernel_length = 0;
CHECK_EFI_STATUS_OR_RETURN(status, L"Opening file %s", kernel_name);
struct elf_header header;
length = sizeof(struct elf_header);
status = file->Read(file, &length, &header);
CHECK_EFI_STATUS_OR_RETURN(status, L"Reading ELF header");
if (length < sizeof(struct elf_header))
CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"Incomplete read of ELF header");
static const char expected[] = {0x7f, 'E', 'L', 'F'};
for (int i = 0; i < sizeof(expected); ++i) {
if (header.ident.magic[i] != expected[i])
CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"Bad ELF magic number");
}
if (header.ident.word_size != ELF_WORDSIZE)
CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"ELF load error: 32 bit ELF not supported");
if (header.ph_entsize != sizeof(struct elf_program_header))
CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"ELF load error: program header size mismatch");
if (header.ident.version != ELF_VERSION ||
header.version != ELF_VERSION)
CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"ELF load error: wrong ELF version");
if (header.ident.endianness != 1 ||
header.ident.os_abi != 0 ||
header.machine != 0x3e)
CHECK_EFI_STATUS_OR_RETURN(EFI_LOAD_ERROR, L"ELF load error: wrong machine architecture");
data->kernel_entry = (void *)header.entrypoint;
struct elf_program_header prog_header;
for (int i = 0; i < header.ph_num; ++i) {
status = file->SetPosition(file, header.ph_offset + i * header.ph_entsize);
CHECK_EFI_STATUS_OR_RETURN(status, L"Setting ELF file position");
length = header.ph_entsize;
status = file->Read(file, &length, &prog_header);
CHECK_EFI_STATUS_OR_RETURN(status, L"Reading ELF program header");
if (prog_header.type != ELF_PT_LOAD) continue;
status = file->SetPosition(file, prog_header.offset);
CHECK_EFI_STATUS_OR_RETURN(status, L"Setting ELF file position");
length = prog_header.mem_size;
void *addr = (void *)(prog_header.vaddr - KERNEL_VIRT_ADDRESS);
status = loader_alloc_pages(bootsvc, KERNEL_MEMTYPE, &length, &addr);
CHECK_EFI_STATUS_OR_RETURN(status, L"Allocating kernel pages");
if (data->kernel == 0)
data->kernel = addr;
data->kernel_length = (uint64_t)addr + length - (uint64_t)data->kernel;
length = prog_header.file_size;
status = file->Read(file, &length, addr);
CHECK_EFI_STATUS_OR_RETURN(status, L"Reading file");
}
status = file->Close(file);
CHECK_EFI_STATUS_OR_RETURN(status, L"Closing file handle");
return EFI_SUCCESS;
}
EFI_STATUS EFI_STATUS
loader_load_kernel( loader_load_kernel(
@@ -105,54 +193,26 @@ loader_load_kernel(
status = fileSystem->OpenVolume(fileSystem, &root); status = fileSystem->OpenVolume(fileSystem, &root);
CHECK_EFI_STATUS_OR_RETURN(status, L"OpenVolume"); CHECK_EFI_STATUS_OR_RETURN(status, L"OpenVolume");
void *next = NULL; status = loader_load_elf(bootsvc, root, data);
data->kernel = (void *)KERNEL_PHYS_ADDRESS;
status = loader_load_file(
bootsvc,
root,
kernel_name,
KERNEL_MEMTYPE,
&data->kernel,
&data->kernel_length,
&next);
if (status == EFI_NOT_FOUND) if (status == EFI_NOT_FOUND)
continue; continue;
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_file: %s", kernel_name); CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_file: %s", kernel_name);
data->font = next; data->font = (void *)((uint64_t)data->kernel + data->kernel_length);
status = loader_load_file( status = loader_load_font(bootsvc, root, data);
bootsvc,
root,
font_name,
KERNEL_FONT_MEMTYPE,
&data->font,
&data->font_length,
&next);
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_file: %s", font_name); CHECK_EFI_STATUS_OR_RETURN(status, L"loader_load_file: %s", font_name);
data->data = next; data->data = (void *)((uint64_t)data->font + data->font_length);
data->data_length += PAGE_SIZE; // extra page for map growth data->data_length += PAGE_SIZE; // extra page for map growth
status = loader_alloc_pages( status = loader_alloc_pages(
bootsvc, bootsvc,
KERNEL_DATA_MEMTYPE, KERNEL_DATA_MEMTYPE,
&data->data_length, &data->data_length,
&data->data, &data->data);
&next);
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_alloc_pages: kernel data"); CHECK_EFI_STATUS_OR_RETURN(status, L"loader_alloc_pages: kernel data");
data->log = next;
data->log_length = KERNEL_LOG_PAGES * PAGE_SIZE;
status = loader_alloc_pages(
bootsvc,
KERNEL_LOG_MEMTYPE,
&data->log_length,
&data->log,
&next);
CHECK_EFI_STATUS_OR_RETURN(status, L"loader_alloc_pages: kernel log");
return EFI_SUCCESS; return EFI_SUCCESS;
} }

View File

@@ -8,6 +8,10 @@
#define KERNEL_PHYS_ADDRESS 0x100000 #define KERNEL_PHYS_ADDRESS 0x100000
#endif #endif
#ifndef KERNEL_VIRT_ADDRESS
#define KERNEL_VIRT_ADDRESS 0xFFFF800000000000
#endif
#ifndef VIRTUAL_OFFSET #ifndef VIRTUAL_OFFSET
#define VIRTUAL_OFFSET 0xf00000000 #define VIRTUAL_OFFSET 0xf00000000
#endif #endif
@@ -24,16 +28,12 @@
#define KERNEL_DATA_MEMTYPE 0x80000002 #define KERNEL_DATA_MEMTYPE 0x80000002
#endif #endif
#ifndef KERNEL_LOG_MEMTYPE #ifndef KERNEL_PT_MEMTYPE
#define KERNEL_LOG_MEMTYPE 0x80000003 #define KERNEL_PT_MEMTYPE 0x80000004
#endif
#ifndef KERNEL_LOG_PAGES
#define KERNEL_LOG_PAGES 4
#endif #endif
#ifndef KERNEL_FILENAME #ifndef KERNEL_FILENAME
#define KERNEL_FILENAME L"kernel.bin" #define KERNEL_FILENAME L"kernel.elf"
#endif #endif
#ifndef KERNEL_FONT #ifndef KERNEL_FONT
@@ -42,6 +42,7 @@
struct loader_data { struct loader_data {
void *kernel; void *kernel;
void *kernel_entry;
size_t kernel_length; size_t kernel_length;
void *font; void *font;
@@ -49,9 +50,6 @@ struct loader_data {
void *data; void *data;
size_t data_length; size_t data_length;
void *log;
size_t log_length;
}; };
EFI_STATUS loader_load_kernel(EFI_BOOT_SERVICES *bootsvc, struct loader_data *data); EFI_STATUS loader_load_kernel(EFI_BOOT_SERVICES *bootsvc, struct loader_data *data);

View File

@@ -27,8 +27,6 @@ struct kernel_header {
uint8_t minor; uint8_t minor;
uint16_t patch; uint16_t patch;
uint32_t gitsha; uint32_t gitsha;
void *entrypoint;
}; };
#pragma pack(pop) #pragma pack(pop)
@@ -37,6 +35,7 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
{ {
EFI_STATUS status; EFI_STATUS status;
EFI_BOOT_SERVICES *bootsvc = system_table->BootServices; EFI_BOOT_SERVICES *bootsvc = system_table->BootServices;
EFI_RUNTIME_SERVICES *runsvc = system_table->RuntimeServices;
// When checking console initialization, use CHECK_EFI_STATUS_OR_RETURN // When checking console initialization, use CHECK_EFI_STATUS_OR_RETURN
// because we can't be sure if the console was fully set up // because we can't be sure if the console was fully set up
@@ -44,6 +43,8 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
CHECK_EFI_STATUS_OR_RETURN(status, "con_initialize"); CHECK_EFI_STATUS_OR_RETURN(status, "con_initialize");
// From here on out, we can use CHECK_EFI_STATUS_OR_FAIL instead // From here on out, we can use CHECK_EFI_STATUS_OR_FAIL instead
memory_init_pointer_fixup(bootsvc, runsvc);
// Find ACPI tables. Ignore ACPI 1.0 if a 2.0 table is found. // Find ACPI tables. Ignore ACPI 1.0 if a 2.0 table is found.
// //
void *acpi_table = NULL; void *acpi_table = NULL;
@@ -74,8 +75,6 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
// Load the kernel image from disk and check it // Load the kernel image from disk and check it
// //
void *kernel_image = NULL, *kernel_data = NULL;
uint64_t kernel_length = 0;
con_printf(L"Loading kernel into memory...\r\n"); con_printf(L"Loading kernel into memory...\r\n");
struct loader_data load; struct loader_data load;
@@ -86,7 +85,6 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
con_printf(L" %u image bytes at 0x%x\r\n", load.kernel_length, load.kernel); con_printf(L" %u image bytes at 0x%x\r\n", load.kernel_length, load.kernel);
con_printf(L" %u font bytes at 0x%x\r\n", load.font_length, load.font); con_printf(L" %u font bytes at 0x%x\r\n", load.font_length, load.font);
con_printf(L" %u data bytes at 0x%x\r\n", load.data_length, load.data); con_printf(L" %u data bytes at 0x%x\r\n", load.data_length, load.data);
con_printf(L" %u log bytes at 0x%x\r\n", load.log_length, load.log);
struct kernel_header *version = (struct kernel_header *)load.kernel; struct kernel_header *version = (struct kernel_header *)load.kernel;
if (version->magic != KERNEL_HEADER_MAGIC) { if (version->magic != KERNEL_HEADER_MAGIC) {
@@ -97,13 +95,15 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
con_printf(L" Kernel version %d.%d.%d %x%s\r\n", con_printf(L" Kernel version %d.%d.%d %x%s\r\n",
version->major, version->minor, version->patch, version->gitsha & 0x0fffffff, version->major, version->minor, version->patch, version->gitsha & 0x0fffffff,
version->gitsha & 0xf0000000 ? "*" : ""); version->gitsha & 0xf0000000 ? "*" : "");
con_printf(L" Entrypoint 0x%x\r\n", version->entrypoint); con_printf(L" Entrypoint 0x%x\r\n", load.kernel_entry);
void (*kernel_main)() = version->entrypoint; void (*kernel_main)() = load.kernel_entry;
memory_mark_pointer_fixup((void **)&kernel_main);
// Set up the kernel data pages to pass to the kernel // Set up the kernel data pages to pass to the kernel
// //
struct popcorn_data *data_header = (struct popcorn_data *)load.data; struct popcorn_data *data_header = (struct popcorn_data *)load.data;
memory_mark_pointer_fixup((void **)&data_header);
data_header->magic = DATA_HEADER_MAGIC; data_header->magic = DATA_HEADER_MAGIC;
data_header->version = DATA_HEADER_VERSION; data_header->version = DATA_HEADER_VERSION;
@@ -113,16 +113,20 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
data_header->font = load.font; data_header->font = load.font;
data_header->font_length = load.font_length; data_header->font_length = load.font_length;
memory_mark_pointer_fixup((void **)&data_header->font);
data_header->data = load.data; data_header->data = load.data;
data_header->data_length = load.data_length; data_header->data_length = load.data_length;
memory_mark_pointer_fixup((void **)&data_header->data);
data_header->log = load.log;
data_header->log_length = load.log_length;
data_header->memory_map = (EFI_MEMORY_DESCRIPTOR *)(data_header + 1); data_header->memory_map = (EFI_MEMORY_DESCRIPTOR *)(data_header + 1);
data_header->runtime = system_table->RuntimeServices; memory_mark_pointer_fixup((void **)&data_header->memory_map);
data_header->runtime = runsvc;
memory_mark_pointer_fixup((void **)&data_header->runtime);
data_header->acpi_table = acpi_table; data_header->acpi_table = acpi_table;
memory_mark_pointer_fixup((void **)&data_header->acpi_table);
data_header->_reserved0 = 0; data_header->_reserved0 = 0;
data_header->_reserved1 = 0; data_header->_reserved1 = 0;
@@ -132,13 +136,14 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
status = con_get_framebuffer( status = con_get_framebuffer(
bootsvc, bootsvc,
&data_header->frame_buffer, &data_header->frame_buffer,
&data_header->frame_buffer_size, &data_header->frame_buffer_length,
&data_header->hres, &data_header->hres,
&data_header->vres, &data_header->vres,
&data_header->rmask, &data_header->rmask,
&data_header->gmask, &data_header->gmask,
&data_header->bmask); &data_header->bmask);
CHECK_EFI_STATUS_OR_FAIL(status); CHECK_EFI_STATUS_OR_FAIL(status);
memory_mark_pointer_fixup((void **)&data_header->frame_buffer);
// Save the memory map and tell the firmware we're taking control. // Save the memory map and tell the firmware we're taking control.
// //
@@ -149,11 +154,16 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
status = memory_get_map(bootsvc, &map); status = memory_get_map(bootsvc, &map);
CHECK_EFI_STATUS_OR_FAIL(status); CHECK_EFI_STATUS_OR_FAIL(status);
data_header->memory_map_length = map.length;
data_header->memory_map_desc_size = map.size;
// bootsvc->Stall(5000000); // bootsvc->Stall(5000000);
status = bootsvc->ExitBootServices(image_handle, map.key); status = bootsvc->ExitBootServices(image_handle, map.key);
CHECK_EFI_STATUS_OR_ASSERT(status, 0); CHECK_EFI_STATUS_OR_ASSERT(status, 0);
memory_virtualize(runsvc, &map);
// Hand control to the kernel // Hand control to the kernel
// //
kernel_main(data_header); kernel_main(data_header);

View File

@@ -8,6 +8,10 @@
#define INCREMENT_DESC(p, b) (EFI_MEMORY_DESCRIPTOR*)(((uint8_t*)(p))+(b)) #define INCREMENT_DESC(p, b) (EFI_MEMORY_DESCRIPTOR*)(((uint8_t*)(p))+(b))
size_t fixup_pointer_index = 0;
void **fixup_pointers[64];
uint64_t *new_pml4 = 0;
const CHAR16 *memory_type_names[] = { const CHAR16 *memory_type_names[] = {
L"EfiReservedMemoryType", L"EfiReservedMemoryType",
L"EfiLoaderCode", L"EfiLoaderCode",
@@ -37,6 +41,51 @@ memory_type_name(UINT32 value)
return memory_type_names[value]; return memory_type_names[value];
} }
void EFIAPI
memory_update_marked_addresses(EFI_EVENT UNUSED *event, void *context)
{
EFI_RUNTIME_SERVICES *runsvc = (EFI_RUNTIME_SERVICES*)context;
for (size_t i = 0; i < fixup_pointer_index; ++i) {
if (fixup_pointers[i])
runsvc->ConvertPointer(0, fixup_pointers[i]);
}
}
EFI_STATUS
memory_init_pointer_fixup(EFI_BOOT_SERVICES *bootsvc, EFI_RUNTIME_SERVICES *runsvc)
{
EFI_STATUS status;
EFI_EVENT event;
status = bootsvc->CreateEvent(
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
TPL_CALLBACK,
(EFI_EVENT_NOTIFY)&memory_update_marked_addresses,
runsvc,
&event);
CHECK_EFI_STATUS_OR_RETURN(status, "Failed to initialize pointer update event.");
// Reserve a page for our replacement PML4, plus some pages for the kernel to use
// as page tables while it gets started.
EFI_PHYSICAL_ADDRESS addr = 0;
status = bootsvc->AllocatePages(AllocateAnyPages, EfiLoaderData, 16, &addr);
CHECK_EFI_STATUS_OR_RETURN(status, "Failed to allocate page table pages.");
new_pml4 = (uint64_t *)addr;
return EFI_SUCCESS;
}
void
memory_mark_pointer_fixup(void **p)
{
if (fixup_pointer_index == 0) {
const size_t count = sizeof(fixup_pointers) / sizeof(void*);
for (size_t i = 0; i < count; ++i) fixup_pointers[i] = 0;
}
fixup_pointers[fixup_pointer_index++] = p;
}
void void
copy_desc(EFI_MEMORY_DESCRIPTOR *src, EFI_MEMORY_DESCRIPTOR *dst, size_t len) copy_desc(EFI_MEMORY_DESCRIPTOR *src, EFI_MEMORY_DESCRIPTOR *dst, size_t len)
{ {
@@ -111,3 +160,47 @@ memory_dump_map(struct memory_map *map)
return EFI_SUCCESS; return EFI_SUCCESS;
} }
void
memory_virtualize(EFI_RUNTIME_SERVICES *runsvc, struct memory_map *map)
{
memory_mark_pointer_fixup((void **)&runsvc);
memory_mark_pointer_fixup((void **)&map);
// Get the pointer to the start of PML4
uint64_t* cr3 = 0;
__asm__ __volatile__ ( "mov %%cr3, %0" : "=r" (cr3) );
// PML4 is indexed with bits 39:47 of the virtual address
uint64_t offset = (KERNEL_VIRT_ADDRESS >> 39) & 0x1ff;
// Double map the lower half pages that are present into the higher half
for (unsigned i = 0; i < offset; ++i) {
if (cr3[i] & 0x1)
new_pml4[i] = new_pml4[offset+i] = cr3[i];
else
new_pml4[i] = new_pml4[offset+i] = 0;
}
// Write our new PML4 pointer back to CR3
__asm__ __volatile__ ( "mov %0, %%cr3" :: "r" (new_pml4) );
EFI_MEMORY_DESCRIPTOR *end = INCREMENT_DESC(map->entries, map->length);
EFI_MEMORY_DESCRIPTOR *d = map->entries;
while (d < end) {
switch (d->Type) {
case KERNEL_MEMTYPE:
case KERNEL_FONT_MEMTYPE:
case KERNEL_DATA_MEMTYPE:
d->Attribute |= EFI_MEMORY_RUNTIME;
default:
if (d->Attribute & EFI_MEMORY_RUNTIME) {
d->VirtualStart = d->PhysicalStart + KERNEL_VIRT_ADDRESS;
}
}
d = INCREMENT_DESC(d, map->size);
}
runsvc->SetVirtualAddressMap(map->length, map->size, map->version, map->entries);
}

View File

@@ -9,6 +9,11 @@ struct memory_map {
EFI_MEMORY_DESCRIPTOR *entries; EFI_MEMORY_DESCRIPTOR *entries;
}; };
EFI_STATUS memory_init_pointer_fixup(EFI_BOOT_SERVICES *bootsvc, EFI_RUNTIME_SERVICES *runsvc);
void memory_mark_pointer_fixup(void **p);
EFI_STATUS memory_get_map_length(EFI_BOOT_SERVICES *bootsvc, size_t *size); EFI_STATUS memory_get_map_length(EFI_BOOT_SERVICES *bootsvc, size_t *size);
EFI_STATUS memory_get_map(EFI_BOOT_SERVICES *bootsvc, struct memory_map *map); EFI_STATUS memory_get_map(EFI_BOOT_SERVICES *bootsvc, struct memory_map *map);
EFI_STATUS memory_dump_map(struct memory_map *map); EFI_STATUS memory_dump_map(struct memory_map *map);
void memory_virtualize(EFI_RUNTIME_SERVICES *runsvc, struct memory_map *map);

View File

@@ -22,16 +22,16 @@ struct popcorn_data {
void *data; void *data;
size_t data_length; size_t data_length;
void *log;
size_t log_length;
void *memory_map; void *memory_map;
size_t memory_map_length;
size_t memory_map_desc_size;
void *runtime; void *runtime;
void *acpi_table; void *acpi_table;
void *frame_buffer; void *frame_buffer;
size_t frame_buffer_size; size_t frame_buffer_length;
uint32_t hres; uint32_t hres;
uint32_t vres; uint32_t vres;
uint32_t rmask; uint32_t rmask;

52
src/kernel/acpi_tables.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "kutil/coord.h"
#include "kutil/misc.h"
struct acpi_table_header
{
uint32_t type;
uint32_t length;
uint8_t revision;
uint8_t checksum;
char oem_id[6];
char oem_table[8];
uint32_t oem_revision;
uint32_t creator_id;
uint32_t creator_revision;
bool validate(uint32_t expected_type = 0) const;
} __attribute__ ((packed));
#define TABLE_HEADER(signature) \
static const uint32_t type_id = kutil::byteswap(signature); \
acpi_table_header header;
template <typename T>
bool acpi_validate(const T *t) { return t->header.validate(T::type_id); }
template <typename T>
size_t acpi_table_entries(const T *t, size_t size)
{
return (t->header.length - sizeof(T)) / size;
}
struct acpi_xsdt
{
TABLE_HEADER('XSDT');
acpi_table_header *headers[0];
} __attribute__ ((packed));
struct acpi_apic
{
TABLE_HEADER('APIC');
uint32_t local_address;
uint32_t flags;
uint8_t controller_data[0];
} __attribute__ ((packed));

29
src/kernel/assert.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "assert.h"
#include "console.h"
[[noreturn]] void
__kernel_assert(const char *file, unsigned line, const char *message)
{
console *cons = console::get();
if (cons) {
cons->set_color(9 , 0);
cons->puts("\n\n ERROR: ");
cons->puts(file);
cons->puts(":");
cons->put_dec(line);
cons->puts(": ");
cons->puts(message);
}
__asm__ __volatile__(
"movq %0, %%r8;"
"movq %1, %%r9;"
"movq %2, %%r10;"
"movq $0, %%rdx;"
"divq %%rdx;"
: // no outputs
: "r"((uint64_t)line), "r"(file), "r"(message)
: "rax", "rdx", "r8", "r9", "r10");
while (1);
}

5
src/kernel/assert.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
[[noreturn]] void __kernel_assert(const char *file, unsigned line, const char *message);
#define kassert(stmt, message) if(!(stmt)) { __kernel_assert(__FILE__, __LINE__, (message)); } else {}

View File

@@ -6,17 +6,19 @@ global _header
_header: _header:
dd MAGIC ; Kernel header magic dd MAGIC ; Kernel header magic
dw 1 ; Header version 1 dw 1 ; Header version 1
dw 1 ; Kernel header length dw 16 ; Kernel header length
db VERSION_MAJOR ; Kernel version db VERSION_MAJOR ; Kernel version
db VERSION_MINOR db VERSION_MINOR
dw VERSION_PATCH dw VERSION_PATCH
dd VERSION_GITSHA dd VERSION_GITSHA
dq _start ; Kernel entrypoint
section .text section .text
align 16 align 16
global _start:function (_start.end - _start) global _start:function (_start.end - _start)
_start: _start:
cli
mov rsp, stack_end
extern kernel_main extern kernel_main
call kernel_main call kernel_main
@@ -26,3 +28,18 @@ _start:
hlt hlt
jmp .hang jmp .hang
.end: .end:
global interrupts_enable
interrupts_enable:
sti
ret
global interrupts_disable
interrupts_disable:
cli
ret
section .bss
stack_begin:
resb 0x4000 ; 16KiB stack space
stack_end:

348
src/kernel/console.cpp Normal file
View File

@@ -0,0 +1,348 @@
#include "console.h"
#include "io.h"
const char digits[] = "0123456789abcdef";
console g_console;
class console_out_screen
{
public:
console_out_screen(const font &f, const screen &s, void *scratch, size_t len) :
m_font(f),
m_screen(s),
m_size(s.width() / f.width(), s.height() / f.height()),
m_fg(0xffffff),
m_bg(0),
m_first(0),
m_length(len),
m_data(nullptr),
m_attrs(nullptr),
m_palette(nullptr)
{
const unsigned count = m_size.size();
const size_t palette_size = sizeof(screen::pixel_t) * 256;
const size_t attrs_size = 2 * count;
if (len >= count) {
// We have enough scratch space to keep the contents of the screen
m_data = static_cast<char *>(scratch);
for (unsigned i = 0; i < count; ++i)
m_data[i] = 0;
}
if (len >= count + palette_size + attrs_size) {
// We have enough scratch space to also keep the colors of the text
m_palette = reinterpret_cast<screen::pixel_t *>(m_data + count);
unsigned index = 0;
// Manually add the 16 basic ANSI colors
m_palette[index++] = m_screen.color(0x00, 0x00, 0x00);
m_palette[index++] = m_screen.color(0xcd, 0x00, 0x00);
m_palette[index++] = m_screen.color(0x00, 0xcd, 0x00);
m_palette[index++] = m_screen.color(0xcd, 0xcd, 0x00);
m_palette[index++] = m_screen.color(0x00, 0x00, 0xee);
m_palette[index++] = m_screen.color(0xcd, 0x00, 0xcd);
m_palette[index++] = m_screen.color(0x00, 0xcd, 0xcd);
m_palette[index++] = m_screen.color(0xe5, 0xe5, 0xe5);
m_palette[index++] = m_screen.color(0x7f, 0x7f, 0x7f);
m_palette[index++] = m_screen.color(0xff, 0x00, 0x00);
m_palette[index++] = m_screen.color(0x00, 0xff, 0x00);
m_palette[index++] = m_screen.color(0xff, 0xff, 0x00);
m_palette[index++] = m_screen.color(0x00, 0x50, 0xff);
m_palette[index++] = m_screen.color(0xff, 0x00, 0xff);
m_palette[index++] = m_screen.color(0x00, 0xff, 0xff);
m_palette[index++] = m_screen.color(0xff, 0xff, 0xff);
// Build the high-color portion of the table
const uint32_t intensity[] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
const uint32_t intensities = sizeof(intensity) / sizeof(uint32_t);
for (uint32_t r = 0; r < intensities; ++r) {
for (uint32_t g = 0; g < intensities; ++g) {
for (uint32_t b = 0; b < intensities; ++b) {
m_palette[index++] = m_screen.color(
intensity[r], intensity[g], intensity[b]);
}
}
}
// Build the greyscale portion of the table
for (uint8_t i = 0x08; i <= 0xee; i += 10)
m_palette[index++] = m_screen.color(i, i, i);
set_color(7, 0); // Grey on black default
m_attrs = reinterpret_cast<uint16_t *>(m_data + count + palette_size);
for (unsigned i = 0; i < count; ++i)
m_attrs[i] = m_attr;
}
repaint();
}
void repaint()
{
m_screen.fill(m_bg);
if (!m_data) return;
for (unsigned y = 0; y < m_size.y; ++y) {
const char *line = line_pointer(y);
const uint16_t *attrs = attr_pointer(y);
for (unsigned x = 0; x < m_size.x; ++x) {
const uint16_t attr = attrs[x];
set_color(static_cast<uint8_t>(attr),
static_cast<uint8_t>(attr >> 8));
m_font.draw_glyph(
m_screen,
line[x] ? line[x] : ' ',
m_fg,
m_bg,
x * m_font.width(),
y * m_font.height());
}
}
}
void scroll(unsigned lines)
{
if (!m_data) {
m_pos.x = 0;
m_pos.y = 0;
} else {
unsigned bytes = lines * m_size.x;
char *line = line_pointer(0);
for (unsigned i = 0; i < bytes; ++i)
*line++ = 0;
m_first = (m_first + lines) % m_size.y;
m_pos.y -= lines;
}
repaint();
}
void set_color(uint8_t fg, uint8_t bg)
{
if (!m_palette) return;
m_bg = m_palette[bg];
m_fg = m_palette[fg];
m_attr = (bg << 8) | fg;
}
void putc(char c)
{
char *line = line_pointer(m_pos.y);
uint16_t *attrs = attr_pointer(m_pos.y);
switch (c) {
case '\t':
m_pos.x = (m_pos.x + 4) / 4 * 4;
break;
case '\r':
m_pos.x = 0;
break;
case '\n':
m_pos.x = 0;
m_pos.y++;
break;
default: {
if (line) line[m_pos.x] = c;
if (attrs) attrs[m_pos.x] = m_attr;
const unsigned x = m_pos.x * m_font.width();
const unsigned y = m_pos.y * m_font.height();
m_font.draw_glyph(m_screen, c, m_fg, m_bg, x, y);
m_pos.x++;
}
}
if (m_pos.x >= m_size.x) {
m_pos.x = m_pos.x % m_size.x;
m_pos.y++;
}
if (m_pos.y >= m_size.y) {
scroll(1);
line = line_pointer(m_pos.y);
}
}
private:
char * line_pointer(unsigned line)
{
if (!m_data) return nullptr;
return m_data + ((m_first + line) % m_size.y) * m_size.x;
}
uint16_t * attr_pointer(unsigned line)
{
if (!m_attrs) return nullptr;
return m_attrs + ((m_first + line) % m_size.y) * m_size.x;
}
font m_font;
screen m_screen;
kutil::coord<unsigned> m_size;
kutil::coord<unsigned> m_pos;
screen::pixel_t m_fg, m_bg;
uint16_t m_attr;
size_t m_first;
size_t m_length;
char *m_data;
uint16_t *m_attrs;
screen::pixel_t *m_palette;
};
console_out_screen *
console_get_screen_out(const font &f, const screen &s, void *scratch, size_t len)
{
return nullptr;
//return new console_out_screen(f, s, scratch, len);
}
static bool
serial_ready()
{
return (inb(COM1 + 5) & 0x20) != 0;
}
static void
serial_write(char c) {
while (!serial_ready());
outb(COM1, c);
}
console::console() :
m_screen(nullptr)
{
const char *fgseq = "\x1b[2J";
while (*fgseq)
serial_write(*fgseq++);
}
void
console::set_color(uint8_t fg, uint8_t bg)
{
if (m_screen)
m_screen->set_color(fg, bg);
const char *fgseq = "\x1b[38;5;";
while (*fgseq)
serial_write(*fgseq++);
if (fg >= 100) serial_write('0' + (fg/100));
if (fg >= 10) serial_write('0' + (fg/10) % 10);
serial_write('0' + fg % 10);
serial_write('m');
const char *bgseq = "\x1b[48;5;";
while (*bgseq)
serial_write(*bgseq++);
if (bg >= 100) serial_write('0' + (bg/100));
if (bg >= 10) serial_write('0' + (bg/10) % 10);
serial_write('0' + bg % 10);
serial_write('m');
}
void
console::puts(const char *message)
{
while (message && *message) {
char c = *message++;
if (m_screen) m_screen->putc(c);
serial_write(c);
if (c == '\n') serial_write('\r');
}
}
void console::vprintf(const char *fmt, va_list args)
{
static const unsigned buf_size = 256;
char buffer[256];
const char *r = fmt;
char *w = buffer;
char *wend = buffer + buf_size;
#define flush() do { *w = 0; puts(buffer); w = buffer; } while(0)
while (r && *r) {
if (w == wend) flush();
if (*r != '%') {
*w++ = *r++;
continue;
}
r++; // chomp the %
flush();
bool done = false;
bool right = true;
int width = 0;
while (!done) {
char c = *r++;
switch (c) {
case '%': *w = '%'; done = true; break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': width = width * 10 + (c - '0'); break;
case '-': right = !right; break;
case 'x': put_hex<uint32_t>(va_arg(args, uint32_t), right ? width : -width); done = true; break;
case 'd':
case 'u': put_dec<uint32_t>(va_arg(args, uint32_t), right ? width : -width); done = true; break;
case 's': {
const char *s = va_arg(args, const char*);
if (s) puts(s);
}
done = true;
break;
case 'l':
switch (*r++) {
case 'x': put_hex<uint64_t>(va_arg(args, uint64_t), right ? width : -width); done = true; break;
case 'd':
case 'u': put_dec<uint32_t>(va_arg(args, uint64_t), right ? width : -width); done = true; break;
default:
done = true;
break;
}
break;
default:
done = true;
break;
}
}
}
flush();
}

84
src/kernel/console.h Normal file
View File

@@ -0,0 +1,84 @@
#pragma once
#include <stdarg.h>
#include "kutil/coord.h"
#include "font.h"
#include "screen.h"
class console_out_screen;
class console
{
public:
console();
void set_color(uint8_t fg = 7, uint8_t bg = 0);
void puts(const char *message);
void vprintf(const char *fmt, va_list args);
inline void printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
template <typename T>
void put_hex(T x, int width = 0);
template <typename T>
void put_dec(T x, int width = 0);
void set_screen(console_out_screen *out) { m_screen = out; }
static console * get();
private:
console_out_screen *m_screen;
};
extern console g_console;
inline console * console::get() { return &g_console; }
console_out_screen * console_get_screen_out(
const font &f, const screen &s, void *scratch, size_t len);
extern const char digits[];
template <typename T>
void console::put_hex(T x, int width)
{
static const int chars = sizeof(x) * 2;
char message[chars + 1];
for (int i=0; i<chars; ++i) {
message[chars - i - 1] = digits[(x >> (i*4)) & 0xf];
}
message[chars] = 0;
if (width > chars) for(int i=0; i<(width-chars); ++i) puts(" ");
puts(message);
if (-width > chars) for(int i=0; i<(-width-chars); ++i) puts(" ");
}
template <typename T>
void console::put_dec(T x, int width)
{
static const int chars = sizeof(x) * 3;
char message[chars + 1];
char *p = message + chars;
int length = 0;
*p-- = 0;
do {
*p-- = digits[x % 10];
x /= 10;
length += 1;
} while (x != 0);
if (width > length) for(int i=0; i<(width-length); ++i) puts(" ");
puts(++p);
if (-width > length) for(int i=0; i<(-width-length); ++i) puts(" ");
}

View File

@@ -0,0 +1,146 @@
#include <stddef.h>
#include <stdint.h>
#include "kutil/memory.h"
#include "acpi_tables.h"
#include "assert.h"
#include "device_manager.h"
#include "log.h"
static const char expected_signature[] = "RSD PTR ";
struct acpi1_rsdp
{
char signature[8];
uint8_t checksum;
char oem_id[6];
uint8_t revision;
uint32_t rsdt_address;
} __attribute__ ((packed));
struct acpi2_rsdp
{
char signature[8];
uint8_t checksum10;
char oem_id[6];
uint8_t revision;
uint32_t rsdt_address;
uint32_t length;
uint64_t xsdt_address;
uint8_t checksum20;
uint8_t reserved[3];
} __attribute__ ((packed));
uint8_t
acpi_checksum(const void *p, size_t len, size_t off = 0)
{
uint8_t sum = 0;
const uint8_t *c = reinterpret_cast<const uint8_t *>(p);
for (int i = off; i < len; ++i) sum += c[i];
return sum;
}
bool
acpi_table_header::validate(uint32_t expected_type) const
{
if (acpi_checksum(this, length) != 0) return false;
return !expected_type || (expected_type == type);
}
device_manager::device_manager(const void *root_table) :
m_local_apic(nullptr),
m_io_apic(nullptr),
m_global_interrupt_base(0)
{
kassert(root_table != 0, "ACPI root table pointer is null.");
const acpi1_rsdp *acpi1 =
reinterpret_cast<const acpi1_rsdp *>(root_table);
for (int i = 0; i < sizeof(acpi1->signature); ++i)
kassert(acpi1->signature[i] == expected_signature[i],
"ACPI RSDP table signature mismatch");
uint8_t sum = acpi_checksum(acpi1, sizeof(acpi1_rsdp), 0);
kassert(sum == 0, "ACPI 1.0 RSDP checksum mismatch.");
kassert(acpi1->revision > 1, "ACPI 1.0 not supported.");
const acpi2_rsdp *acpi2 =
reinterpret_cast<const acpi2_rsdp *>(acpi1);
sum = acpi_checksum(acpi2, sizeof(acpi2_rsdp), sizeof(acpi1_rsdp));
kassert(sum == 0, "ACPI 2.0 RSDP checksum mismatch.");
load_xsdt(reinterpret_cast<const acpi_xsdt *>(acpi2->xsdt_address));
}
static void
put_sig(char *into, uint32_t type)
{
for (int j=0; j<4; ++j) into[j] = reinterpret_cast<char *>(&type)[j];
}
void
device_manager::load_xsdt(const acpi_xsdt *xsdt)
{
kassert(xsdt && acpi_validate(xsdt), "Invalid ACPI XSDT.");
char sig[5] = {0,0,0,0,0};
log::info(logs::devices, "ACPI 2.0 tables loading:");
put_sig(sig, xsdt->header.type);
log::info(logs::devices, " Found table %s", sig);
size_t num_tables = acpi_table_entries(xsdt, sizeof(void*));
for (size_t i = 0; i < num_tables; ++i) {
const acpi_table_header *header = xsdt->headers[i];
put_sig(sig, header->type);
log::info(logs::devices, " Found table %s", sig);
kassert(header->validate(), "Table failed validation.");
switch (header->type) {
case acpi_apic::type_id:
load_apic(reinterpret_cast<const acpi_apic *>(header));
break;
default:
break;
}
}
}
void
device_manager::load_apic(const acpi_apic *apic)
{
m_local_apic = reinterpret_cast<uint32_t *>(apic->local_address);
log::info(logs::devices, " APIC local address %lx", apic->local_address);
uint8_t const *p = apic->controller_data;
uint8_t const *end = p + acpi_table_entries(apic, 1);
while (p < end) {
const uint8_t type = p[0];
const uint8_t length = p[1];
log::debug(logs::devices, " APIC entry type %d", type);
switch (type) {
case 0: // Local APIC
break;
case 1: // I/O APIC
m_io_apic = reinterpret_cast<uint32_t *>(kutil::read_from<uint32_t>(p+4));
m_global_interrupt_base = kutil::read_from<uint32_t>(p+8);
log::info(logs::devices, " IO APIC address %lx base %d",
m_io_apic, m_global_interrupt_base);
break;
}
p += length;
}
}

View File

@@ -0,0 +1,25 @@
#pragma once
struct acpi_xsdt;
struct acpi_apic;
class device_manager
{
public:
device_manager(const void *root_table);
device_manager() = delete;
device_manager(const device_manager &) = delete;
uint8_t * local_apic() const;
uint8_t * io_apic() const;
private:
uint32_t *m_local_apic;
uint32_t *m_io_apic;
uint32_t m_global_interrupt_base;
void load_xsdt(const acpi_xsdt *xsdt);
void load_apic(const acpi_apic *apic);
};

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include "kutil/coord.h"
#include "screen.h" #include "screen.h"
#include "util.h"
class font class font
{ {
@@ -28,7 +28,7 @@ private:
font(); font();
font(unsigned height, unsigned width, unsigned count, uint8_t const *data); font(unsigned height, unsigned width, unsigned count, uint8_t const *data);
coord<unsigned> m_size; kutil::coord<unsigned> m_size;
unsigned m_count; unsigned m_count;
uint8_t const *m_data; uint8_t const *m_data;
}; };

View File

@@ -0,0 +1,51 @@
ISR ( 0, isr0);
ISR ( 1, isr1);
ISR ( 2, isr2);
ISR ( 3, isr3);
ISR ( 4, isr4);
ISR ( 5, isr5);
ISR ( 6, isr6);
ISR ( 7, isr7);
EISR( 8, isr8);
ISR ( 9, isr9);
EISR(10, isr10);
EISR(11, isr11);
EISR(12, isr12);
EISR(13, isr13);
EISR(14, isr14);
ISR (15, isr15);
ISR (16, isr16);
ISR (17, isr17);
ISR (18, isr18);
ISR (19, isr19);
ISR (20, isr20);
ISR (21, isr21);
ISR (22, isr22);
ISR (23, isr23);
ISR (24, isr24);
ISR (25, isr25);
ISR (26, isr26);
ISR (27, isr27);
ISR (28, isr28);
ISR (29, isr29);
ISR (30, isr30);
ISR (31, isr31);
IRQ (64, 0, irq0);
IRQ (65, 1, irq1);
IRQ (66, 2, irq2);
IRQ (67, 3, irq3);
IRQ (68, 4, irq4);
IRQ (69, 5, irq5);
IRQ (70, 6, irq6);
IRQ (71, 7, irq7);
IRQ (72, 8, irq8);
IRQ (73, 9, irq9);
IRQ (74, 10, irq10);
IRQ (75, 11, irq11);
IRQ (76, 12, irq12);
IRQ (77, 13, irq13);
IRQ (78, 14, irq14);
IRQ (79, 15, irq15);
ISR (0xff, isrSpurious);

302
src/kernel/interrupts.cpp Normal file
View File

@@ -0,0 +1,302 @@
#include <stddef.h>
#include <stdint.h>
#include "kutil/memory.h"
#include "console.h"
#include "interrupts.h"
#include "log.h"
enum class gdt_flags : uint8_t
{
ac = 0x01,
rw = 0x02,
dc = 0x04,
ex = 0x08,
r1 = 0x20,
r2 = 0x40,
r3 = 0x60,
pr = 0x80,
res_1 = 0x10
};
inline gdt_flags
operator | (gdt_flags lhs, gdt_flags rhs)
{
return static_cast<gdt_flags>(
static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
}
struct gdt_descriptor
{
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t flags;
uint8_t granularity;
uint8_t base_high;
} __attribute__ ((packed));
struct idt_descriptor
{
uint16_t base_low;
uint16_t selector;
uint8_t ist;
uint8_t flags;
uint16_t base_mid;
uint32_t base_high;
uint32_t reserved; // must be zero
} __attribute__ ((packed));
struct table_ptr
{
uint16_t limit;
uint64_t base;
} __attribute__ ((packed));
gdt_descriptor g_gdt_table[10];
idt_descriptor g_idt_table[256];
table_ptr g_gdtr;
table_ptr g_idtr;
struct registers;
extern "C" {
void idt_write();
void idt_load();
void gdt_write();
void gdt_load();
void isr_handler(registers);
void irq_handler(registers);
#define ISR(i, name) extern void name ()
#define EISR(i, name) extern void name ()
#define IRQ(i, q, name) extern void name ()
#include "interrupt_isrs.inc"
#undef IRQ
#undef EISR
#undef ISR
}
void idt_dump(const table_ptr &table);
void gdt_dump(const table_ptr &table);
void
set_gdt_entry(uint8_t i, uint32_t base, uint32_t limit, bool is64, gdt_flags flags)
{
g_gdt_table[i].limit_low = limit & 0xffff;
g_gdt_table[i].base_low = base & 0xffff;
g_gdt_table[i].base_mid = (base >> 16) & 0xff;
g_gdt_table[i].base_high = (base >> 24) & 0xff;
g_gdt_table[i].granularity =
((limit >> 16) & 0xf) | (is64 ? 0xa0 : 0xc0);
g_gdt_table[i].flags = static_cast<uint8_t>(flags | gdt_flags::res_1 | gdt_flags::pr);
}
void
set_idt_entry(uint8_t i, uint64_t addr, uint16_t selector, uint8_t flags)
{
g_idt_table[i].base_low = addr & 0xffff;
g_idt_table[i].base_mid = (addr >> 16) & 0xffff;
g_idt_table[i].base_high = (addr >> 32) & 0xffffffff;
g_idt_table[i].selector = selector;
g_idt_table[i].flags = flags;
g_idt_table[i].ist = 0;
g_idt_table[i].reserved = 0;
}
void
interrupts_init()
{
kutil::memset(&g_gdt_table, 0, sizeof(g_gdt_table));
kutil::memset(&g_idt_table, 0, sizeof(g_idt_table));
g_gdtr.limit = sizeof(g_gdt_table) - 1;
g_gdtr.base = reinterpret_cast<uint64_t>(&g_gdt_table);
set_gdt_entry(1, 0, 0xfffff, false, gdt_flags::rw);
set_gdt_entry(2, 0, 0xfffff, false, gdt_flags::rw | gdt_flags::ex | gdt_flags::dc);
set_gdt_entry(3, 0, 0xfffff, false, gdt_flags::rw);
set_gdt_entry(4, 0, 0xfffff, false, gdt_flags::rw | gdt_flags::ex);
set_gdt_entry(6, 0, 0xfffff, false, gdt_flags::rw);
set_gdt_entry(7, 0, 0xfffff, true, gdt_flags::rw | gdt_flags::ex);
gdt_write();
g_idtr.limit = sizeof(g_idt_table) - 1;
g_idtr.base = reinterpret_cast<uint64_t>(&g_idt_table);
#define ISR(i, name) set_idt_entry(i, reinterpret_cast<uint64_t>(& name), 0x38, 0x8e);
#define EISR(i, name) set_idt_entry(i, reinterpret_cast<uint64_t>(& name), 0x38, 0x8e);
#define IRQ(i, q, name) set_idt_entry(i, reinterpret_cast<uint64_t>(& name), 0x38, 0x8e);
#include "interrupt_isrs.inc"
#undef IRQ
#undef EISR
#undef ISR
idt_write();
log::info(logs::interrupt, "Interrupts enabled.");
gdt_dump(g_gdtr);
idt_dump(g_idtr);
}
struct registers
{
uint64_t ds;
uint64_t rdi, rsi, rbp, rsp, rbx, rdx, rcx, rax;
uint64_t interrupt, errorcode;
uint64_t rip, cs, eflags, user_esp, ss;
};
#define print_reg(name, value) cons->printf(" %s: %lx\n", name, (value));
void
isr_handler(registers regs)
{
console *cons = console::get();
cons->set_color(9);
cons->puts("\nReceived ISR interrupt:\n");
cons->set_color();
uint64_t cr2 = 0;
__asm__ __volatile__ ("mov %%cr2, %0" : "=r"(cr2));
print_reg("ISR", regs.interrupt);
print_reg("ERR", regs.errorcode);
print_reg("CR2", cr2);
cons->puts("\n");
print_reg(" ds", regs.ds);
print_reg("rdi", regs.rdi);
print_reg("rsi", regs.rsi);
print_reg("rbp", regs.rbp);
print_reg("rsp", regs.rsp);
print_reg("rbx", regs.rbx);
print_reg("rdx", regs.rdx);
print_reg("rcx", regs.rcx);
print_reg("rax", regs.rax);
cons->puts("\n");
print_reg("rip", regs.rip);
print_reg(" cs", regs.cs);
print_reg(" ef", regs.eflags);
print_reg("esp", regs.user_esp);
print_reg(" ss", regs.ss);
while(1) asm("hlt");
}
void
irq_handler(registers regs)
{
console *cons = console::get();
cons->set_color(9);
cons->puts("\nReceived IRQ interrupt:\n");
cons->set_color();
print_reg("ISR", regs.interrupt);
print_reg("ERR", regs.errorcode);
cons->puts("\n");
print_reg(" ds", regs.ds);
print_reg("rdi", regs.rdi);
print_reg("rsi", regs.rsi);
print_reg("rbp", regs.rbp);
print_reg("rsp", regs.rsp);
print_reg("rbx", regs.rbx);
print_reg("rdx", regs.rdx);
print_reg("rcx", regs.rcx);
print_reg("rax", regs.rax);
cons->puts("\n");
print_reg("rip", regs.rip);
print_reg(" cs", regs.cs);
print_reg(" ef", regs.eflags);
print_reg("esp", regs.user_esp);
print_reg(" ss", regs.ss);
while(1) asm("hlt");
}
void
gdt_dump(const table_ptr &table)
{
log::info(logs::interrupt, "Loaded GDT at: %lx size: %d bytes", table.base, table.limit+1);
int count = (table.limit + 1) / sizeof(gdt_descriptor);
const gdt_descriptor *gdt =
reinterpret_cast<const gdt_descriptor *>(table.base);
for (int i = 0; i < count; ++i) {
uint32_t base =
(gdt[i].base_high << 24) |
(gdt[i].base_mid << 16) |
gdt[i].base_low;
uint32_t limit =
static_cast<uint32_t>(gdt[i].granularity & 0x0f) << 16 |
gdt[i].limit_low;
if (gdt[i].flags & 0x80) {
log::debug(logs::interrupt,
" Entry %3d: Base %x limit %x privs %d flags %s%s%s%s%s%s",
i, base, limit, ((gdt[i].flags >> 5) & 0x03),
(gdt[i].flags & 0x80) ? "P " : " ",
(gdt[i].flags & 0x08) ? "ex " : " ",
(gdt[i].flags & 0x04) ? "dc " : " ",
(gdt[i].flags & 0x02) ? "rw " : " ",
(gdt[i].granularity & 0x80) ? "kb " : "b ",
(gdt[i].granularity & 0x60) == 0x60 ? "64" :
(gdt[i].granularity & 0x60) == 0x40 ? "32" : "16"
);
}
}
}
void
idt_dump(const table_ptr &table)
{
log::info(logs::interrupt, "Loaded IDT at: %lx size: %d bytes", table.base, table.limit+1);
int count = (table.limit + 1) / sizeof(idt_descriptor);
const idt_descriptor *idt =
reinterpret_cast<const idt_descriptor *>(table.base);
for (int i = 0; i < count; ++i) {
uint64_t base =
(static_cast<uint64_t>(idt[i].base_high) << 32) |
(static_cast<uint64_t>(idt[i].base_mid) << 16) |
idt[i].base_low;
char const *type;
switch (idt[i].flags & 0xf) {
case 0x5: type = " 32tsk "; break;
case 0x6: type = " 16int "; break;
case 0x7: type = " 16trp "; break;
case 0xe: type = " 32int "; break;
case 0xf: type = " 32trp "; break;
default: type = " ????? "; break;
}
if (idt[i].flags & 0x80) {
log::debug(logs::interrupt,
" Entry %3d: Base:%lx Sel(rpl %d, ti %d, %3d) IST:%d %s DPL:%d", i, base,
(idt[i].selector & 0x3),
((idt[i].selector & 0x4) >> 2),
(idt[i].selector >> 3),
idt[i].ist,
type,
((idt[i].flags >> 5) & 0x3));
}
}
}

8
src/kernel/interrupts.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
extern "C" {
void interrupts_enable();
void interrupts_disable();
}
void interrupts_init();

121
src/kernel/interrupts.s Normal file
View File

@@ -0,0 +1,121 @@
extern g_idtr
extern g_gdtr
global idt_write
idt_write:
lidt [rel g_idtr]
ret
global idt_load
idt_load:
sidt [rel g_idtr]
ret
global gdt_write
gdt_write:
lgdt [rel g_gdtr]
ret
global gdt_load
gdt_load:
sgdt [rel g_gdtr]
ret
%macro push_all_and_segments 0
push rax
push rcx
push rdx
push rbx
push rsp
push rbp
push rsi
push rdi
mov ax, ds
push rax
%endmacro
%macro pop_all_and_segments 0
pop rax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
pop rdi
pop rsi
pop rbp
pop rsp
pop rbx
pop rdx
pop rcx
pop rax
%endmacro
%macro load_kernel_segments 0
mov ax, 0x10 ; load the kernel data segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
%endmacro
extern isr_handler
global isr_handler_prelude
isr_handler_prelude:
push_all_and_segments
load_kernel_segments
call isr_handler
pop_all_and_segments
add rsp, 16 ; because the ISRs added err/num
sti
iretq
extern irq_handler
global irq_handler_prelude
irq_handler_prelude:
push_all_and_segments
load_kernel_segments
call irq_handler
pop_all_and_segments
add rsp, 16 ; because the ISRs added err/num
sti
iretq
%macro EMIT_ISR 2
global %1
%1:
cli
push byte 0
push byte %2
jmp isr_handler_prelude
%endmacro
%macro EMIT_EISR 2
global %1
%1:
cli
push byte %2
jmp isr_handler_prelude
%endmacro
%macro EMIT_IRQ 2
global %1
%1:
cli
push byte 0
push byte %2
jmp irq_handler_prelude
%endmacro
%define EISR(i, name) EMIT_EISR name, i
%define ISR(i, name) EMIT_ISR name, i
%define IRQ(i, q, name) EMIT_IRQ name, i
%include "interrupt_isrs.inc"

16
src/kernel/io.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include "io.h"
uint8_t
inb(uint16_t port)
{
uint8_t val;
__asm__ __volatile__ ( "inb %1, %0" : "=a"(val) : "Nd"(port) );
return val;
}
void
outb(uint16_t port, uint8_t val)
{
__asm__ __volatile__ ( "outb %0, %1" :: "a"(val), "Nd"(port) );
}

19
src/kernel/io.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
extern "C" {
/// Read a byte from an IO port.
/// \arg port The address of the IO port
/// \returns One byte read from the port
uint8_t inb(uint16_t port);
/// Write a byte to an IO port.
/// \arg port The addres of the IO port
/// \arg val The byte to write
void outb(uint16_t port, uint8_t val);
}
const uint16_t COM1 = 0x03f8;

103
src/kernel/log.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include <type_traits>
#include "kutil/memory.h"
#include "console.h"
#include "log.h"
static const uint64_t default_enabled[] = {0x09, 0x0f, 0x0f, 0x0f};
static const uint8_t level_colors[] = {0x07, 0x0f, 0x0b, 0x09};
static const char *levels[] = {"debug", " info", " warn", "error", "fatal"};
static const char *areas[] = {
"boot",
"mem ",
"intr",
"dev ",
nullptr
};
log log::s_log;
log::log() :
m_cons(nullptr)
{
for (int i = 0; i < sizeof(m_enabled) / sizeof(uint64_t); ++i)
m_enabled[i] = default_enabled[i];
}
log::log(console *cons) :
m_cons(cons)
{
for (int i = 0; i < sizeof(m_enabled) / sizeof(uint64_t); ++i)
m_enabled[i] = default_enabled[i];
}
void
log::init(console *cons)
{
new (&s_log) log(cons);
log::info(logs::boot, "Logging system initialized.");
}
static inline uint64_t
bit_mask(logs area) { return 1 << static_cast<uint64_t>(area); }
void
log::enable(logs type, level at_level)
{
using under_t = std::underlying_type<level>::type;
under_t at = static_cast<under_t>(at_level);
under_t max = sizeof(m_enabled) / sizeof(under_t);
for (under_t i = 0; i < max; ++i) {
if (i <= at)
s_log.m_enabled[i] |= bit_mask(type);
else
s_log.m_enabled[i] &= ~bit_mask(type);
}
}
template <>
void
log::trylog<log::level::fatal>(logs area, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
s_log.output(level::fatal, area, fmt, args);
va_end(args);
while(1) __asm__ ("hlt");
}
template <log::level L>
void
log::trylog(logs area, const char *fmt, ...)
{
auto i = static_cast<std::underlying_type<level>::type>(L);
if ((s_log.m_enabled[i] & bit_mask(area)) == 0) return;
va_list args;
va_start(args, fmt);
s_log.output(L, area, fmt, args);
va_end(args);
}
void
log::output(level severity, logs area, const char *fmt, va_list args)
{
m_cons->set_color(level_colors[static_cast<int>(severity)]);
m_cons->printf("%s %s: ",
areas[static_cast<int>(area)],
levels[static_cast<int>(severity)]);
m_cons->vprintf(fmt, args);
m_cons->set_color();
m_cons->puts("\n");
}
const log::trylog_p log::debug = &trylog<level::debug>;
const log::trylog_p log::info = &trylog<level::info>;
const log::trylog_p log::warn = &trylog<level::warn>;
const log::trylog_p log::error = &trylog<level::error>;
const log::trylog_p log::fatal = &trylog<level::fatal>;

48
src/kernel/log.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <stdarg.h>
#include <stdint.h>
class console;
enum class logs
{
boot,
memory,
interrupt,
devices,
max
};
class log
{
public:
enum class level {debug, info, warn, error, fatal, max};
static void init(console *cons);
static void enable(logs type, level at_level);
template <level L>
static void trylog(logs area, const char *fmt, ...);
using trylog_p = void (*)(logs area, const char *fmt, ...);
static const trylog_p debug;
static const trylog_p info;
static const trylog_p warn;
static const trylog_p error;
static const trylog_p fatal;
private:
void output(level severity, logs area, const char *fmt, va_list args);
/// Bitmasks for what categories are enabled. fatal is
/// always enabled, so leave it out.
uint64_t m_enabled[static_cast<uint64_t>(level::max) - 1];
console *m_cons;
log();
log(console *cons);
static log s_log;
};

73
src/kernel/main.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include <stddef.h>
#include <stdint.h>
#include "kutil/memory.h"
#include "console.h"
#include "device_manager.h"
#include "font.h"
#include "interrupts.h"
#include "kernel_data.h"
#include "log.h"
#include "memory.h"
#include "memory_pages.h"
#include "screen.h"
extern "C" {
void do_the_set_registers(popcorn_data *header);
void kernel_main(popcorn_data *header);
}
/*
console
load_console(const popcorn_data *header)
{
console cons{
font::load(header->font),
screen{
header->frame_buffer,
header->hres,
header->vres,
header->rmask,
header->gmask,
header->bmask},
header->log,
header->log_length};
return cons;
}
*/
void
kernel_main(popcorn_data *header)
{
console *cons = new (&g_console) console();
cons->set_color(0x21, 0x00);
cons->puts("Popcorn OS ");
cons->set_color(0x08, 0x00);
cons->puts(GIT_VERSION " booting...\n");
log::init(cons);
page_manager *pager = new (&g_page_manager) page_manager;
pager->mark_offset_pointer(&header->frame_buffer, header->frame_buffer_length);
memory_initialize_managers(
header->memory_map,
header->memory_map_length,
header->memory_map_desc_size);
size_t n = 5000;
void *p = kalloc(n);
log::info(logs::memory, "kalloc'd %d bytes at %lx", n, p);
interrupts_init();
interrupts_enable();
device_manager devices(header->acpi_table);
// int x = 1 / 0;
// __asm__ __volatile__("int $15");
g_console.puts("boogity!");
do_the_set_registers(header);
}

51
src/kernel/memory.cpp Normal file
View File

@@ -0,0 +1,51 @@
#include "assert.h"
#include "memory.h"
#include "memory_pages.h"
memory_manager g_kernel_memory_manager;
struct memory_allocation_header
{
uint64_t pages;
uint64_t reserved;
uint8_t data[0];
} __attribute__ ((packed));
memory_manager::memory_manager() :
m_start(nullptr),
m_length(0)
{
}
memory_manager::memory_manager(void *start, size_t length) :
m_start(start),
m_length(length)
{
}
void *
memory_manager::allocate(size_t length)
{
length = page_align(length + sizeof(memory_allocation_header));
if (length > m_length) return nullptr;
m_length -= length;
memory_allocation_header *header =
reinterpret_cast<memory_allocation_header *>(m_start);
m_start = reinterpret_cast<uint8_t *>(m_start) + length;
size_t pages = length / page_manager::page_size;
g_page_manager.map_pages(
reinterpret_cast<page_manager::addr_t>(header),
pages);
header->pages = pages;
return &header->data;
}
void
memory_manager::free(void *p)
{
// In this simple version, we don't care about freed pointers
}

46
src/kernel/memory.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
/// \file memory.h
/// The block memory manager and related definitions.
#include <stddef.h>
#include <stdint.h>
struct memory_block_node;
/// Manager for allocation of virtual memory.
class memory_manager
{
public:
memory_manager();
memory_manager(void *start, size_t length);
/// Allocate memory from the area managed.
/// \arg length The amount of memory to allocate, in bytes
/// \returns A pointer to the allocated memory, or nullptr if
/// allocation failed.
void * allocate(size_t length);
/// Free a previous allocation.
/// \arg p A pointer previously retuned by allocate()
void free(void *p);
private:
friend class page_manager;
// Simple incrementing pointer.
void *m_start;
size_t m_length;
memory_manager(const memory_manager &) = delete;
};
extern memory_manager g_kernel_memory_manager;
/// Bootstrap the memory managers.
void memory_initialize_managers(const void *memory_map, size_t map_length, size_t desc_length);
/// Allocate kernel space memory.
/// \arg length The amount of memory to allocate, in bytes
/// \returns A pointer to the allocated memory, or nullptr if
/// allocation failed.
inline void * kalloc(size_t length) { return g_kernel_memory_manager.allocate(length); }

View File

@@ -0,0 +1,531 @@
#include "kutil/memory.h"
#include "assert.h"
#include "memory.h"
#include "memory_pages.h"
const unsigned efi_page_size = 0x1000;
enum class efi_memory_type : uint32_t
{
reserved,
loader_code,
loader_data,
boot_services_code,
boot_services_data,
runtime_services_code,
runtime_services_data,
available,
unusable,
acpi_reclaim,
acpi_nvs,
mmio,
mmio_port,
pal_code,
persistent,
efi_max,
popcorn_kernel = 0x80000000,
popcorn_font,
popcorn_data,
popcorn_log,
popcorn_pml4,
popcorn_max
};
const char *efi_memory_type_names[] = {
" reserved",
" loader_code",
" loader_data",
" boot_services_code",
" boot_services_data",
"runtime_services_code",
"runtime_services_data",
" available",
" unusable",
" acpi_reclaim",
" acpi_nvs",
" mmio",
" mmio_port",
" pal_code",
" popcorn_kernel",
" popcorn_font",
" popcorn_data",
" popcorn_log",
" popcorn_pml4",
};
static const char *
get_efi_name(efi_memory_type t)
{
static const unsigned offset =
(unsigned)efi_memory_type::popcorn_kernel - (unsigned)efi_memory_type::efi_max;
return t >= efi_memory_type::popcorn_kernel ?
efi_memory_type_names[(unsigned)t - offset] :
efi_memory_type_names[(unsigned)t];
}
enum class efi_memory_flag : uint64_t
{
can_mark_uc = 0x0000000000000001, // uc = un-cacheable
can_mark_wc = 0x0000000000000002, // wc = write-combining
can_mark_wt = 0x0000000000000004, // wt = write through
can_mark_wb = 0x0000000000000008, // wb = write back
can_mark_uce = 0x0000000000000010, // uce = un-cacheable exported
can_mark_wp = 0x0000000000001000, // wp = write protected
can_mark_rp = 0x0000000000002000, // rp = read protected
can_mark_xp = 0x0000000000004000, // xp = exceute protected
can_mark_ro = 0x0000000000020000, // ro = read only
non_volatile = 0x0000000000008000,
more_reliable = 0x0000000000010000,
runtime = 0x8000000000000000
};
IS_BITFIELD(efi_memory_flag);
struct efi_memory_descriptor
{
efi_memory_type type;
uint32_t pad;
uint64_t physical_start;
uint64_t virtual_start;
uint64_t pages;
efi_memory_flag flags;
};
static const efi_memory_descriptor *
desc_incr(const efi_memory_descriptor *d, size_t desc_length)
{
return reinterpret_cast<const efi_memory_descriptor *>(
reinterpret_cast<const uint8_t *>(d) + desc_length);
}
static unsigned
count_table_pages_needed(page_block *used)
{
page_table_indices last_idx{~0ull};
unsigned counts[] = {1, 0, 0, 0};
for (page_block *cur = used; cur; cur = cur->next) {
if (!cur->has_flag(page_block_flags::mapped))
continue;
page_table_indices start{cur->virtual_address};
page_table_indices end{cur->virtual_address + (cur->count * page_manager::page_size)};
counts[1] +=
((start[0] == last_idx[0]) ? 0 : 1) +
(end[0] - start[0]);
counts[2] +=
((start[0] == last_idx[0] &&
start[1] == last_idx[1]) ? 0 : 1) +
(end[1] - start[1]);
counts[3] +=
((start[0] == last_idx[0] &&
start[1] == last_idx[1] &&
start[2] == last_idx[2]) ? 0 : 1) +
(end[2] - start[2]);
last_idx = end;
}
return counts[0] + counts[1] + counts[2] + counts[3];
}
page_block *
remove_block_for(page_block **list, uint64_t phys_start, uint64_t pages, page_block **cache)
{
// This is basically just the removal portion of page_manager::unmap_pages,
// but with physical addresses, and only ever removing a single block.
page_block *prev = nullptr;
page_block *cur = *list;
while (cur && !cur->contains_physical(phys_start)) {
prev = cur;
cur = cur->next;
}
kassert(cur, "Couldn't find block to remove");
uint64_t size = page_manager::page_size * pages;
uint64_t end = phys_start + size;
uint64_t leading = phys_start - cur->physical_address;
uint64_t trailing = cur->physical_end() - end;
if (leading) {
uint64_t pages = leading / page_manager::page_size;
page_block *lead_block = *cache;
*cache = (*cache)->next;
lead_block->copy(cur);
lead_block->next = cur;
lead_block->count = pages;
cur->count -= pages;
cur->physical_address += leading;
if (cur->virtual_address)
cur->virtual_address += leading;
if (prev) {
prev->next = lead_block;
} else {
prev = lead_block;
*list = prev;
}
}
if (trailing) {
uint64_t pages = trailing / page_manager::page_size;
page_block *trail_block = *cache;
*cache = (*cache)->next;
trail_block->copy(cur);
trail_block->next = cur->next;
trail_block->count = pages;
trail_block->physical_address += size;
if (cur->virtual_address)
trail_block->virtual_address += size;
cur->count -= pages;
cur->next = trail_block;
}
prev->next = cur->next;
cur->next = nullptr;
return cur;
}
uint64_t
gather_block_lists(
uint64_t scratch_virt,
const void *memory_map,
size_t map_length,
size_t desc_length,
page_block **free_head,
page_block **used_head)
{
int i = 0;
page_block *free = nullptr;
page_block *used = nullptr;
page_block *block_list = reinterpret_cast<page_block *>(scratch_virt);
efi_memory_descriptor const *desc = reinterpret_cast<efi_memory_descriptor const *>(memory_map);
efi_memory_descriptor const *end = desc_incr(desc, map_length);
while (desc < end) {
page_block *block = &block_list[i++];
block->physical_address = desc->physical_start;
block->virtual_address = desc->virtual_start;
block->count = desc->pages;
block->next = nullptr;
switch (desc->type) {
case efi_memory_type::loader_code:
case efi_memory_type::loader_data:
block->flags = page_block_flags::used | page_block_flags::pending_free;
break;
case efi_memory_type::boot_services_code:
case efi_memory_type::boot_services_data:
case efi_memory_type::available:
block->flags = page_block_flags::free;
break;
case efi_memory_type::acpi_reclaim:
block->flags =
page_block_flags::used |
page_block_flags::mapped |
page_block_flags::acpi_wait;
block->virtual_address = block->physical_address;
break;
case efi_memory_type::persistent:
block->flags = page_block_flags::nonvolatile;
break;
default:
block->flags = page_block_flags::used | page_block_flags::permanent;
break;
}
if (block->has_flag(page_block_flags::used)) {
if (block->virtual_address || !block->physical_address)
block->flags |= page_block_flags::mapped;
used = page_block::insert(used, block);
} else {
free = page_block::insert(free, block);
}
desc = desc_incr(desc, desc_length);
}
*free_head = free;
*used_head = used;
return reinterpret_cast<uint64_t>(&block_list[i]);
}
page_block *
fill_page_with_blocks(uint64_t start)
{
uint64_t end = page_align(start);
uint64_t count = (end - start) / sizeof(page_block);
if (count == 0) return nullptr;
page_block *blocks = reinterpret_cast<page_block *>(start);
for (unsigned i = 0; i < count; ++i)
blocks[i].zero(&blocks[i+1]);
blocks[count - 1].next = nullptr;
return blocks;
}
void
copy_new_table(page_table *base, unsigned index, page_table *new_table)
{
uint64_t entry = base->entries[index];
// If this is a large page and not a a table, bail out.
if(entry & 0x80) return;
if (entry & 0x1) {
page_table *old_next = reinterpret_cast<page_table *>(
base->entries[index] & ~0xffful);
for (int i = 0; i < 512; ++i) new_table->entries[i] = old_next->entries[i];
} else {
for (int i = 0; i < 512; ++i) new_table->entries[i] = 0;
}
base->entries[index] = reinterpret_cast<uint64_t>(new_table) | 0xb;
}
static uint64_t
find_efi_free_aligned_pages(const void *memory_map, size_t map_length, size_t desc_length, unsigned pages)
{
efi_memory_descriptor const *desc =
reinterpret_cast<efi_memory_descriptor const *>(memory_map);
efi_memory_descriptor const *end = desc_incr(desc, map_length);
const unsigned want_space = pages * page_manager::page_size;
uint64_t start_phys = 0;
for (; desc < end; desc = desc_incr(desc, desc_length)) {
if (desc->type != efi_memory_type::available)
continue;
// See if the first wanted pages fit in one page table. If we
// find free memory at zero, skip ahead because we're not ready
// to deal with 0 being a valid pointer yet.
start_phys = desc->physical_start;
if (start_phys == 0)
start_phys += efi_page_size;
const uint64_t desc_end =
desc->physical_start + desc->pages * efi_page_size;
uint64_t end = start_phys + want_space;
if (end < desc_end) {
page_table_indices start_idx{start_phys};
page_table_indices end_idx{end};
if (start_idx[0] == end_idx[0] &&
start_idx[1] == end_idx[1] &&
start_idx[2] == end_idx[2])
break;
// Try seeing if the page-table-aligned version fits
start_phys = page_table_align(start_phys);
end = start_phys + want_space;
if (end < desc_end)
break;
}
}
kassert(desc < end, "Couldn't find wanted pages of aligned scratch space.");
return start_phys;
}
static unsigned
check_needs_page_ident(page_table *table, unsigned index, page_table **free_pages)
{
if (table->entries[index] & 0x1 == 1) return 0;
kassert(*free_pages, "check_needs_page_ident needed to allocate but had no free pages");
page_table *new_table = (*free_pages)++;
for (int i=0; i<512; ++i) new_table->entries[i] = 0;
table->entries[index] = reinterpret_cast<uint64_t>(new_table) | 0xb;
return 1;
}
static unsigned
page_in_ident(
page_table *pml4,
uint64_t phys_addr,
uint64_t virt_addr,
uint64_t count,
page_table *free_pages)
{
page_table_indices idx{virt_addr};
page_table *tables[4] = {pml4, nullptr, nullptr, nullptr};
unsigned pages_consumed = 0;
for (; idx[0] < 512; idx[0] += 1) {
pages_consumed += check_needs_page_ident(tables[0], idx[0], &free_pages);
tables[1] = reinterpret_cast<page_table *>(
tables[0]->entries[idx[0]] & ~0xfffull);
for (; idx[1] < 512; idx[1] += 1, idx[2] = 0, idx[3] = 0) {
pages_consumed += check_needs_page_ident(tables[1], idx[1], &free_pages);
tables[2] = reinterpret_cast<page_table *>(
tables[1]->entries[idx[1]] & ~0xfffull);
for (; idx[2] < 512; idx[2] += 1, idx[3] = 0) {
pages_consumed += check_needs_page_ident(tables[2], idx[2], &free_pages);
tables[3] = reinterpret_cast<page_table *>(
tables[2]->entries[idx[2]] & ~0xfffull);
for (; idx[3] < 512; idx[3] += 1) {
tables[3]->entries[idx[3]] = phys_addr | 0xb;
phys_addr += page_manager::page_size;
if (--count == 0) return pages_consumed;
}
}
}
}
kassert(0, "Ran to end of page_in_ident");
}
void
memory_initialize_managers(const void *memory_map, size_t map_length, size_t desc_length)
{
// The bootloader reserved 16 pages for page tables, which we'll use to bootstrap.
// The first one is the already-installed PML4, so grab it from CR3.
uint64_t cr3;
__asm__ __volatile__ ( "mov %%cr3, %0" : "=r" (cr3) );
page_table *tables = reinterpret_cast<page_table *>(cr3 & ~0xfffull);
// Now go through EFi's memory map and find a region of scratch space.
const unsigned want_pages = 32;
uint64_t free_region_start_phys =
find_efi_free_aligned_pages(memory_map, map_length, desc_length, want_pages);
// Offset-map this region into the higher half.
uint64_t free_region_start_virt =
free_region_start_phys + page_manager::high_offset;
uint64_t free_next = free_region_start_virt;
// We'll need to copy any existing tables (except the PML4 which the
// bootloader gave us) into our 4 reserved pages so we can edit them.
page_table_indices fr_idx{free_region_start_virt};
copy_new_table(&tables[0], fr_idx[0], &tables[1]);
copy_new_table(&tables[1], fr_idx[1], &tables[2]);
copy_new_table(&tables[2], fr_idx[2], &tables[3]);
page_in_ident(&tables[0], free_region_start_phys, free_region_start_virt, want_pages, nullptr);
// We now have pages starting at "free_next" to bootstrap ourselves. Start by
// taking inventory of free pages.
page_block *free_head = nullptr;
page_block *used_head = nullptr;
free_next = gather_block_lists(
free_next, memory_map, map_length, desc_length,
&free_head, &used_head);
// Unused page_block structs go here - finish out the current page with them
page_block *cache_head = fill_page_with_blocks(free_next);
free_next = page_align(free_next);
// Now go back through these lists and consolidate
page_block *freed = page_block::consolidate(free_head);
cache_head = page_block::append(cache_head, freed);
freed = page_block::consolidate(used_head);
cache_head = page_block::append(cache_head, freed);
// Pull out the block that represents the bootstrap pages we've used
uint64_t used = free_next - free_region_start_virt;
uint64_t used_pages = used / page_manager::page_size;
uint64_t remaining_pages = want_pages - used_pages;
page_block *removed = remove_block_for(
&free_head,
free_region_start_phys,
used_pages,
&cache_head);
kassert(removed, "remove_block_for didn't find the bootstrap region.");
kassert(removed->physical_address == free_region_start_phys,
"remove_block_for found the wrong region.");
// Add it to the used list
removed->virtual_address = free_region_start_virt;
removed->flags = page_block_flags::used | page_block_flags::mapped;
used_head = page_block::insert(used_head, removed);
// Pull out the block that represents the rest
uint64_t free_next_phys = free_region_start_phys + used;
removed = remove_block_for(
&free_head,
free_next_phys,
remaining_pages,
&cache_head);
kassert(removed, "remove_block_for didn't find the page table region.");
kassert(removed->physical_address == free_next_phys,
"remove_block_for found the wrong region.");
uint64_t pt_start_phys = removed->physical_address;
uint64_t pt_start_virt = removed->physical_address + page_manager::page_offset;
// Record that we're about to remap it into the page table address space
removed->virtual_address = pt_start_virt;
removed->flags = page_block_flags::used | page_block_flags::mapped;
used_head = page_block::insert(used_head, removed);
page_manager *pm = &g_page_manager;
// Actually remap them into page table space
pm->page_out(&tables[0], free_next, remaining_pages);
page_table_indices pg_idx{pt_start_virt};
copy_new_table(&tables[0], pg_idx[0], &tables[4]);
copy_new_table(&tables[4], pg_idx[1], &tables[5]);
copy_new_table(&tables[5], pg_idx[2], &tables[6]);
page_in_ident(&tables[0], pt_start_phys, pt_start_virt, remaining_pages, tables + 4);
// Finally, build an acutal set of kernel page tables that just contains
// what the kernel actually has mapped, but making everything writable
// (especially the page tables themselves)
page_table *pml4 = reinterpret_cast<page_table *>(pt_start_virt);
for (int i=0; i<512; ++i) pml4->entries[i] = 0;
// Give the rest to the page_manager's cache for use in page_in
pm->free_table_pages(pml4 + 1, remaining_pages - 1);
for (page_block *cur = used_head; cur; cur = cur->next) {
if (!cur->has_flag(page_block_flags::mapped)) continue;
pm->page_in(pml4, cur->physical_address, cur->virtual_address, cur->count);
}
// Put our new PML4 into CR3 to start using it
page_manager::set_pml4(pml4);
// We now have all used memory mapped ourselves. Let the page_manager take
// over from here.
g_page_manager.init(free_head, used_head, cache_head);
}

552
src/kernel/memory_pages.cpp Normal file
View File

@@ -0,0 +1,552 @@
#include <algorithm>
#include "assert.h"
#include "log.h"
#include "memory.h"
#include "memory_pages.h"
page_manager g_page_manager;
using addr_t = page_manager::addr_t;
static addr_t
pt_to_phys(page_table *pt)
{
return reinterpret_cast<addr_t>(pt) - page_manager::page_offset;
}
static page_table *
pt_from_phys(addr_t p)
{
return reinterpret_cast<page_table *>((p + page_manager::page_offset) & ~0xfffull);
}
struct free_page_header
{
free_page_header *next;
size_t count;
};
size_t
page_block::length(page_block *list)
{
size_t i = 0;
for (page_block *b = list; b; b = b->next) ++i;
return i;
}
page_block *
page_block::append(page_block *list, page_block *extra)
{
if (list == nullptr) return extra;
else if (extra == nullptr) return list;
page_block *cur = list;
while (cur->next)
cur = cur->next;
cur->next = extra;
return list;
}
page_block *
page_block::insert(page_block *list, page_block *block)
{
if (list == nullptr) return block;
else if (block == nullptr) return list;
page_block *cur = list;
page_block *prev = nullptr;
while (cur && page_block::compare(block, cur) > 0) {
prev = cur;
cur = cur->next;
}
block->next = cur;
if (prev) {
prev->next = block;
return list;
}
return block;
}
int
page_block::compare(const page_block *lhs, const page_block *rhs)
{
if (lhs->virtual_address < rhs->virtual_address)
return -1;
else if (lhs->virtual_address > rhs->virtual_address)
return 1;
if (lhs->physical_address < rhs->physical_address)
return -1;
else if (lhs->physical_address > rhs->physical_address)
return 1;
return 0;
}
page_block *
page_block::consolidate(page_block *list)
{
page_block *freed = nullptr;
page_block *cur = list;
while (cur) {
page_block *next = cur->next;
if (next &&
cur->flags == next->flags &&
cur->physical_end() == next->physical_address &&
(!cur->has_flag(page_block_flags::mapped) ||
cur->virtual_end() == next->virtual_address)) {
cur->count += next->count;
cur->next = next->next;
next->zero(freed);
freed = next;
continue;
}
cur = cur->next;
}
return freed;
}
void
page_block::dump(page_block *list, const char *name, bool show_unmapped)
{
log::debug(logs::memory, "Block list %s:", name);
int count = 0;
for (page_block *cur = list; cur; cur = cur->next) {
count += 1;
if (!(show_unmapped || cur->has_flag(page_block_flags::mapped)))
continue;
if (cur->virtual_address) {
page_table_indices start{cur->virtual_address};
log::debug(logs::memory, " %lx %x [%6d] %lx (%d,%d,%d,%d)",
cur->physical_address,
cur->flags,
cur->count,
cur->virtual_address,
start[0], start[1], start[2], start[3]);
} else {
page_table_indices start{cur->virtual_address};
log::debug(logs::memory, " %lx %x [%6d]",
cur->physical_address,
cur->flags,
cur->count);
}
}
log::debug(logs::memory, " Total: %d", count);
}
void
page_block::zero(page_block *set_next)
{
physical_address = 0;
virtual_address = 0;
count = 0;
flags = page_block_flags::free;
next = set_next;
}
void
page_block::copy(page_block *other)
{
physical_address = other->physical_address;
virtual_address = other->virtual_address;
count = other->count;
flags = other->flags;
next = other->next;
}
page_manager::page_manager() :
m_free(nullptr),
m_used(nullptr),
m_block_cache(nullptr),
m_page_cache(nullptr)
{
kassert(this == &g_page_manager, "Attempt to create another page_manager.");
}
void
page_manager::init(
page_block *free,
page_block *used,
page_block *block_cache)
{
m_free = free;
m_used = used;
m_block_cache = block_cache;
// For now we're ignoring that we've got the scratch pages
// allocated, full of page_block structs. Eventually hand
// control of that to a slab allocator.
page_table *pml4 = get_pml4();
// Fix up the offset-marked pointers
for (unsigned i = 0; i < m_marked_pointer_count; ++i) {
addr_t *p = reinterpret_cast<addr_t *>(m_marked_pointers[i]);
addr_t v = *p + page_offset;
addr_t c = (m_marked_pointer_lengths[i] / page_size) + 1;
// TODO: cleanly search/split this as a block out of used/free if possible
page_block *block = get_block();
// TODO: page-align
block->physical_address = *p;
block->virtual_address = v;
block->count = c;
block->flags =
page_block_flags::used |
page_block_flags::mapped |
page_block_flags::mmio;
log::info(logs::memory, "Fixing up pointer %lx [%3d] -> %lx", *p, c, v);
m_used = page_block::insert(m_used, block);
page_in(pml4, *p, v, c);
*p = v;
}
consolidate_blocks();
page_block::dump(m_used, "used", true);
page_block::dump(m_free, "free", true);
// Initialize the kernel memory manager
addr_t end = 0;
for (page_block *b = m_used; b; b = b->next) {
if (b->virtual_address < page_offset)
end = b->virtual_end();
else
break;
}
new (&g_kernel_memory_manager) memory_manager(
reinterpret_cast<void *>(end),
page_offset - end);
}
void
page_manager::mark_offset_pointer(void **pointer, size_t length)
{
m_marked_pointers[m_marked_pointer_count] = pointer;
m_marked_pointer_lengths[m_marked_pointer_count++] = length;
}
page_block *
page_manager::get_block()
{
page_block *block = m_block_cache;
if (block) {
m_block_cache = block->next;
block->next = 0;
return block;
} else {
kassert(0, "NYI: page_manager::get_block() needed to allocate.");
}
}
void
page_manager::free_blocks(page_block *block)
{
if (!block) return;
page_block *cur = block;
while (cur) {
page_block *next = cur->next;
cur->zero(cur->next ? cur->next : m_block_cache);
cur = next;
}
m_block_cache = block;
}
page_table *
page_manager::get_table_page()
{
if (!m_page_cache) {
addr_t phys = 0;
size_t n = pop_pages(32, &phys);
addr_t virt = phys + page_offset;
page_block *block = get_block();
block->physical_address = phys;
block->virtual_address = virt;
block->count = n;
page_block::insert(m_used, block);
page_in(get_pml4(), phys, virt, n);
m_page_cache = reinterpret_cast<free_page_header *>(virt);
// The last one needs to be null, so do n-1
addr_t end = virt + (n-1) * page_size;
while (virt < end) {
reinterpret_cast<free_page_header *>(virt)->next =
reinterpret_cast<free_page_header *>(virt + page_size);
virt += page_size;
}
reinterpret_cast<free_page_header *>(virt)->next = nullptr;
log::info(logs::memory, "Mappd %d new page table pages at %lx", n, phys);
}
free_page_header *page = m_page_cache;
m_page_cache = page->next;
return reinterpret_cast<page_table *>(page);
}
void
page_manager::free_table_pages(void *pages, size_t count)
{
addr_t start = reinterpret_cast<addr_t>(pages);
for (size_t i = 0; i < count; ++i) {
addr_t addr = start + (i * page_size);
free_page_header *header = reinterpret_cast<free_page_header *>(addr);
header->count = 1;
header->next = m_page_cache;
m_page_cache = header;
}
}
void
page_manager::consolidate_blocks()
{
m_block_cache = page_block::append(m_block_cache, page_block::consolidate(m_free));
m_block_cache = page_block::append(m_block_cache, page_block::consolidate(m_used));
}
void *
page_manager::map_pages(addr_t address, size_t count)
{
void *ret = reinterpret_cast<void *>(address);
page_table *pml4 = get_pml4();
while (count) {
kassert(m_free, "page_manager::map_pages ran out of free pages!");
addr_t phys = 0;
size_t n = pop_pages(count, &phys);
page_block *block = get_block();
block->physical_address = phys;
block->virtual_address = address;
block->count = n;
page_block::insert(m_used, block);
page_in(pml4, phys, address, n);
address += n * page_size;
count -= n;
}
return ret;
}
void
page_manager::unmap_pages(addr_t address, size_t count)
{
page_block **prev = &m_used;
page_block *cur = m_used;
while (cur && !cur->contains(address)) {
prev = &cur->next;
cur = cur->next;
}
kassert(cur, "Couldn't find existing mapped pages to unmap");
size_t size = page_size * count;
addr_t end = address + size;
while (cur && cur->contains(address)) {
size_t leading = address - cur->virtual_address;
size_t trailing =
end > cur->virtual_end() ?
0 : (cur->virtual_end() - end);
if (leading) {
size_t pages = leading / page_size;
page_block *lead_block = get_block();
lead_block->copy(cur);
lead_block->next = cur;
lead_block->count = pages;
cur->count -= pages;
cur->physical_address += leading;
cur->virtual_address += leading;
*prev = lead_block;
prev = &lead_block->next;
}
if (trailing) {
size_t pages = trailing / page_size;
page_block *trail_block = get_block();
trail_block->copy(cur);
trail_block->next = cur->next;
trail_block->count = pages;
trail_block->physical_address += size;
trail_block->virtual_address += size;
cur->count -= pages;
cur->next = trail_block;
}
address += cur->count * page_size;
page_block *next = cur->next;
*prev = cur->next;
cur->next = nullptr;
cur->virtual_address = 0;
cur->flags = cur->flags & ~(page_block_flags::used | page_block_flags::mapped);
m_free = page_block::insert(m_free, cur);
cur = next;
}
}
void
page_manager::check_needs_page(page_table *table, unsigned index)
{
if (table->entries[index] & 0x1 == 1) return;
page_table *new_table = get_table_page();
for (int i=0; i<512; ++i) new_table->entries[i] = 0;
table->entries[index] = pt_to_phys(new_table) | 0xb;
}
void
page_manager::page_in(page_table *pml4, addr_t phys_addr, addr_t virt_addr, size_t count)
{
page_table_indices idx{virt_addr};
page_table *tables[4] = {pml4, nullptr, nullptr, nullptr};
for (; idx[0] < 512; idx[0] += 1) {
check_needs_page(tables[0], idx[0]);
tables[1] = tables[0]->get(idx[0]);
for (; idx[1] < 512; idx[1] += 1, idx[2] = 0, idx[3] = 0) {
check_needs_page(tables[1], idx[1]);
tables[2] = tables[1]->get(idx[1]);
for (; idx[2] < 512; idx[2] += 1, idx[3] = 0) {
check_needs_page(tables[2], idx[2]);
tables[3] = tables[2]->get(idx[2]);
for (; idx[3] < 512; idx[3] += 1) {
tables[3]->entries[idx[3]] = phys_addr | 0xb;
phys_addr += page_manager::page_size;
if (--count == 0) return;
}
}
}
}
kassert(0, "Ran to end of page_in");
}
void
page_manager::page_out(page_table *pml4, addr_t virt_addr, size_t count)
{
page_table_indices idx{virt_addr};
page_table *tables[4] = {pml4, nullptr, nullptr, nullptr};
for (; idx[0] < 512; idx[0] += 1) {
tables[1] = reinterpret_cast<page_table *>(
tables[0]->entries[idx[0]] & ~0xfffull);
for (; idx[1] < 512; idx[1] += 1) {
tables[2] = reinterpret_cast<page_table *>(
tables[1]->entries[idx[1]] & ~0xfffull);
for (; idx[2] < 512; idx[2] += 1) {
tables[3] = reinterpret_cast<page_table *>(
tables[2]->entries[idx[2]] & ~0xfffull);
for (; idx[3] < 512; idx[3] += 1) {
tables[3]->entries[idx[3]] = 0;
if (--count == 0) return;
}
}
}
}
kassert(0, "Ran to end of page_out");
}
size_t
page_manager::pop_pages(size_t count, addr_t *address)
{
kassert(m_free, "page_manager::pop_pages ran out of free pages!");
unsigned n = std::min(count, static_cast<size_t>(m_free->count));
*address = m_free->physical_address;
m_free->physical_address += n * page_size;
m_free->count -= n;
if (m_free->count == 0) {
page_block *block = m_free;
m_free = m_free->next;
block->zero(m_block_cache);
m_block_cache = block;
}
return n;
}
void
page_table::dump(int level, uint64_t offset)
{
log::info(logs::memory, "Level %d page table @ %lx (off %lx):", level, this, offset);
for (int i=0; i<512; ++i) {
uint64_t ent = entries[i];
if (ent == 0) continue;
if (ent & 0x1 == 0) {
log::info(logs::memory, " %3d: %lx NOT PRESENT", i, ent);
continue;
}
if ((level == 2 || level == 3) && (ent & 0x80) == 0x80) {
log::info(logs::memory, " %3d: %lx -> Large page at %lx",
i, ent, ent & ~0xfffull);
continue;
} else if (level == 1) {
log::info(logs::memory, " %3d: %lx -> Page at %lx",
i, ent, ent & ~0xfffull);
} else {
log::info(logs::memory, " %3d: %lx -> Level %d table at %lx",
i, ent, level - 1, (ent & ~0xfffull) + offset);
continue;
}
}
if (--level > 0) {
for (int i=0; i<512; ++i) {
uint64_t ent = entries[i];
if ((ent & 0x1) == 0) continue;
if ((ent & 0x80)) continue;
page_table *next = reinterpret_cast<page_table *>((ent & ~0xffful) + offset);
next->dump(level, offset);
}
}
}

293
src/kernel/memory_pages.h Normal file
View File

@@ -0,0 +1,293 @@
#pragma once
/// \file memory_pages.h
/// The page memory manager and related definitions.
#include <stddef.h>
#include <stdint.h>
#include "kutil/enum_bitfields.h"
struct page_block;
struct page_table;
struct free_page_header;
/// Manager for allocation of physical pages.
class page_manager
{
public:
using addr_t = uint64_t;
/// Size of a single page.
static const size_t page_size = 0x1000;
/// Start of the higher half.
static const addr_t high_offset = 0xffff800000000000;
/// Offset from physical where page tables are mapped.
static const addr_t page_offset = 0xffffff8000000000;
page_manager();
/// Allocate and map pages into virtual memory.
/// \arg address The virtual address at which to map the pages
/// \arg count The number of pages to map
/// \returns A pointer to the start of the mapped region
void * map_pages(addr_t address, size_t count);
/// Unmap existing pages from memory.
/// \arg address The virtual address of the memory to unmap
/// \arg count The number of pages to unmap
void unmap_pages(addr_t address, size_t count);
/// Mark a pointer and range to be offset-mapped. This pointer will
/// automatically get updated once page_manager::init() is called.
/// \arg pointer Pointer to a pointer to the memory area to be mapped
/// \arg length Length of the memory area to be mapped
void mark_offset_pointer(void **pointer, size_t length);
private:
friend void memory_initialize_managers(const void *, size_t, size_t);
/// Set up the memory manager from bootstraped memory
void init(
page_block *free,
page_block *used,
page_block *block_cache);
/// Initialize the virtual memory manager based on this object's state
void init_memory_manager();
/// Create a `page_block` struct or pull one from the cache.
/// \returns An empty `page_block` struct
page_block * get_block();
/// Return a list of `page_block` structs to the cache.
/// \arg block A list of `page_block` structs
void free_blocks(page_block *block);
/// Allocate a page for a page table, or pull one from the cache
/// \returns An empty page mapped in page space
page_table * get_table_page();
/// Return a set of mapped contiguous pages to the page cache.
/// \arg pages Pointer to the first page to be returned
/// \arg count Number of pages in the range
void free_table_pages(void *pages, size_t count);
/// Consolidate the free and used block lists. Return freed blocks
/// to the cache.
void consolidate_blocks();
/// Helper to read the PML4 table from CR3.
/// \returns A pointer to the current PML4 table.
static inline page_table * get_pml4()
{
addr_t pml4 = 0;
__asm__ __volatile__ ( "mov %%cr3, %0" : "=r" (pml4) );
return reinterpret_cast<page_table *>((pml4 & ~0xfffull) + page_offset);
}
/// Helper to set the PML4 table pointer in CR3.
/// \arg pml4 A pointer to the PML4 table to install.
static inline void set_pml4(page_table *pml4)
{
addr_t p = reinterpret_cast<addr_t>(pml4) - page_offset;
__asm__ __volatile__ ( "mov %0, %%cr3" :: "r" (p & ~0xfffull) );
}
/// Helper function to allocate a new page table. If table entry `i` in
/// table `base` is empty, allocate a new page table and point `base[i]` at
/// it.
/// \arg base Existing page table being indexed into
/// \arg i Index into the existing table to check
void check_needs_page(page_table *base, unsigned i);
/// Low-level routine for mapping a number of pages into the given page table.
/// \arg pml4 The root page table to map into
/// \arg phys_addr The starting physical address of the pages to be mapped
/// \arg virt_addr The starting virtual address ot the memory to be mapped
/// \arg count The number of pages to map
void page_in(
page_table *pml4,
addr_t phys_addr,
addr_t virt_addr,
size_t count);
/// Low-level routine for unmapping a number of pages from the given page table.
/// \arg pml4 The root page table for this mapping
/// \arg virt_addr The starting virtual address ot the memory to be unmapped
/// \arg count The number of pages to unmap
void page_out(
page_table *pml4,
addr_t virt_addr,
size_t count);
/// Get free pages from the free list. Only pages from the first free block
/// are returned, so the number may be less than requested, but they will
/// be contiguous. Pages will not be mapped into virtual memory.
/// \arg count The maximum number of pages to get
/// \arg address [out] The address of the first page
/// \returns The number of pages retrieved
size_t pop_pages(size_t count, addr_t *address);
page_block *m_free; ///< Free pages list
page_block *m_used; ///< In-use pages list
page_block *m_block_cache; ///< Cache of unused page_block structs
free_page_header *m_page_cache; ///< Cache of free pages to use for tables
static const unsigned marked_pointer_max = 16;
unsigned m_marked_pointer_count;
void **m_marked_pointers[marked_pointer_max];
size_t m_marked_pointer_lengths[marked_pointer_max];
page_manager(const page_manager &) = delete;
};
/// Global page manager.
extern page_manager g_page_manager;
/// Flags used by `page_block`.
enum class page_block_flags : uint32_t
{
free = 0x00000000, ///< Not a flag, value for free memory
used = 0x00000001, ///< Memory is in use
mapped = 0x00000002, ///< Memory is mapped to virtual address
mmio = 0x00000010, ///< Memory is a MMIO region
nonvolatile = 0x00000020, ///< Memory is non-volatile storage
pending_free = 0x10000000, ///< Memory should be freed
acpi_wait = 0x40000000, ///< Memory should be freed after ACPI init
permanent = 0x80000000, ///< Memory is permanently unusable
max_flags
};
IS_BITFIELD(page_block_flags);
/// A block of contiguous pages. Each `page_block` represents contiguous
/// physical pages with the same attributes. A `page_block *` is also a
/// linked list of such structures.
struct page_block
{
using addr_t = page_manager::addr_t;
addr_t physical_address;
addr_t virtual_address;
uint32_t count;
page_block_flags flags;
page_block *next;
inline bool has_flag(page_block_flags f) const { return bitfield_contains(flags, f); }
inline addr_t physical_end() const { return physical_address + (count * page_manager::page_size); }
inline addr_t virtual_end() const { return virtual_address + (count * page_manager::page_size); }
inline bool contains(addr_t vaddr) const { return vaddr >= virtual_address && vaddr < virtual_end(); }
inline bool contains_physical(addr_t addr) const { return addr >= physical_address && addr < physical_end(); }
/// Helper to zero out a block and optionally set the next pointer.
/// \arg next [optional] The value for the `next` pointer
void zero(page_block *set_next = nullptr);
/// Helper to copy a bock from another block
/// \arg other The block to copy from
void copy(page_block *other);
/// \name Page block linked list functions
/// Functions to act on a `page_block *` as a linked list
/// @{
/// Count the items in the given linked list.
/// \arg list The list to count
/// \returns The number of entries in the list.
static size_t length(page_block *list);
/// Append a block or list to the given list.
/// \arg list The list to append to
/// \arg extra The list or block to be appended
/// \returns The new list head
static page_block * append(page_block *list, page_block *extra);
/// Sorted-insert of a block into the list by address.
/// \arg list The list to insert into
/// \arg block The single block to insert
/// \returns The new list head
static page_block * insert(page_block *list, page_block *block);
/// Compare two blocks by address.
/// \arg lhs The left-hand comparator
/// \arg rhs The right-hand comparator
/// \returns <0 if lhs is sorts earlier, >0 if lhs sorts later, 0 for equal
static int compare(const page_block *lhs, const page_block *rhs);
/// Traverse the list, joining adjacent blocks where possible.
/// \arg list The list to consolidate
/// \returns A linked list of freed page_block structures.
static page_block * consolidate(page_block *list);
/// Traverse the list, printing debug info on this list.
/// \arg list The list to print
/// \arg name [optional] String to print as the name of this list
/// \arg show_permanent [optional] If false, hide unmapped blocks
static void dump(page_block *list, const char *name = nullptr, bool show_unmapped = false);
/// @}
};
/// Struct to allow easy accessing of a memory page being used as a page table.
struct page_table
{
using pm = page_manager;
uint64_t entries[512];
inline page_table * get(int i) const {
uint64_t entry = entries[i];
if ((entry & 0x1) == 0) return nullptr;
return reinterpret_cast<page_table *>((entry & ~0xfffull) + pm::page_offset);
}
inline void set(int i, page_table *p, uint16_t flags) {
entries[i] = (reinterpret_cast<uint64_t>(p) - pm::page_offset) | (flags & 0xfff);
}
void dump(int level = 4, uint64_t offset = page_manager::page_offset);
};
/// Helper struct for computing page table indices of a given address.
struct page_table_indices
{
page_table_indices(uint64_t v = 0) :
index{
(v >> 39) & 0x1ff,
(v >> 30) & 0x1ff,
(v >> 21) & 0x1ff,
(v >> 12) & 0x1ff }
{}
/// Get the index for a given level of page table.
uint64_t & operator[](size_t i) { return index[i]; }
uint64_t index[4]; ///< Indices for each level of tables.
};
/// Calculate a page-aligned address.
/// \arg p The address to align.
/// \returns The next page-aligned address _after_ `p`.
template <typename T> inline T page_align(T p)
{
return ((p - 1) & ~(page_manager::page_size - 1)) + page_manager::page_size;
}
/// Calculate a page-table-aligned address. That is, an address that is
/// page-aligned to the first page in a page table.
/// \arg p The address to align.
/// \returns The next page-table-aligned address _after_ `p`.
template <typename T> inline T page_table_align(T p) { return ((p - 1) & ~0x1fffffull) + 0x200000; }

View File

@@ -2,7 +2,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "util.h" #include "kutil/coord.h"
class screen class screen
{ {
@@ -36,5 +36,5 @@ private:
pixel_t *m_framebuffer; pixel_t *m_framebuffer;
color_masks m_masks; color_masks m_masks;
coord<unsigned> m_resolution; kutil::coord<unsigned> m_resolution;
}; };

View File

@@ -1,9 +1,14 @@
#pragma once #pragma once
namespace kutil {
template <typename T> template <typename T>
struct coord { struct coord
{
T x, y; T x, y;
coord() : x(T{}), y(T{}) {} coord() : x(T{}), y(T{}) {}
coord(T x, T y) : x(x), y(y) {} coord(T x, T y) : x(x), y(y) {}
T size() const { return x * y; } T size() const { return x * y; }
}; };
} // namespace kutil

View File

@@ -0,0 +1,3 @@
extern "C" {
void __cxa_pure_virtual() { while(1); }
}

View File

@@ -0,0 +1,90 @@
#pragma once
#include <type_traits>
template<typename E>
struct is_enum_bitfield { static constexpr bool value = false; };
#define IS_BITFIELD(name) \
template<> struct is_enum_bitfield<name> {static constexpr bool value=true;}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,E>::type
operator & (E lhs, E rhs)
{
return static_cast<E> (
static_cast<typename std::underlying_type<E>::type>(lhs) &
static_cast<typename std::underlying_type<E>::type>(rhs));
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,E>::type
operator | (E lhs, E rhs)
{
return static_cast<E> (
static_cast<typename std::underlying_type<E>::type>(lhs) |
static_cast<typename std::underlying_type<E>::type>(rhs));
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,E>::type
operator ^ (E lhs, E rhs)
{
return static_cast<E> (
static_cast<typename std::underlying_type<E>::type>(lhs) ^
static_cast<typename std::underlying_type<E>::type>(rhs));
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,E>::type
operator ~ (E rhs)
{
return static_cast<E>(~static_cast<typename std::underlying_type<E>::type>(rhs));
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,E>::type&
operator |= (E &lhs, E rhs)
{
lhs = static_cast<E>(
static_cast<typename std::underlying_type<E>::type>(lhs) |
static_cast<typename std::underlying_type<E>::type>(rhs));
return lhs;
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,E>::type&
operator &= (E &lhs, E rhs)
{
lhs = static_cast<E>(
static_cast<typename std::underlying_type<E>::type>(lhs) &
static_cast<typename std::underlying_type<E>::type>(rhs));
return lhs;
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,E>::type&
operator ^= (E &lhs, E rhs)
{
lhs = static_cast<E>(
static_cast<typename std::underlying_type<E>::type>(lhs) ^
static_cast<typename std::underlying_type<E>::type>(rhs));
return lhs;
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,bool>::type
operator ! (E rhs)
{
return static_cast<typename std::underlying_type<E>::type>(rhs) == 0;
}
template <typename E>
typename std::enable_if<is_enum_bitfield<E>::value,bool>::type
bitfield_contains(E set, E flag)
{
return (set & flag) == flag;
}

View File

@@ -0,0 +1,13 @@
#include "memory.h"
namespace kutil {
void *
memset(void *s, uint8_t v, size_t n)
{
uint8_t *p = reinterpret_cast<uint8_t *>(s);
for (int i = 0; i < n; ++i) p[i] = 0;
return s;
}
} // namespace kutil

View File

@@ -0,0 +1,17 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
inline void * operator new (size_t, void *p) throw() { return p; }
namespace kutil {
void * memset(void *p, uint8_t v, size_t n);
template <typename T>
T read_from(const void *p) { return *reinterpret_cast<const T *>(p); }
} // namespace kutil

12
src/modules/kutil/misc.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
namespace kutil {
constexpr uint32_t
byteswap(uint32_t x)
{
return ((x >> 24) & 0x000000ff) | ((x >> 8) & 0x0000ff00)
| ((x << 8) & 0x00ff0000) | ((x << 24) & 0xff000000);
}
}

View File

@@ -1,2 +1,2 @@
MOD_NAME := main MOD_NAME := kutil
include modules.mk include modules.mk

View File

@@ -1,194 +0,0 @@
#include "console.h"
const char digits[] = "0123456789abcdef";
console::console(const font &f, const screen &s, void *scratch, size_t len) :
m_font(f),
m_screen(s),
m_size(s.width() / f.width(), s.height() / f.height()),
m_fg(0xffffff),
m_bg(0),
m_first(0),
m_length(len),
m_data(nullptr),
m_attrs(nullptr),
m_palette(nullptr)
{
const unsigned count = m_size.size();
const size_t palette_size = sizeof(screen::pixel_t) * 256;
const size_t attrs_size = 2 * count;
if (len >= count) {
// We have enough scratch space to keep the contents of the screen
m_data = static_cast<char *>(scratch);
for (unsigned i = 0; i < count; ++i)
m_data[i] = 0;
}
if (len >= count + palette_size + attrs_size) {
// We have enough scratch space to also keep the colors of the text
m_palette = reinterpret_cast<screen::pixel_t *>(m_data + count);
unsigned index = 0;
// Manually add the 16 basic ANSI colors
m_palette[index++] = m_screen.color(0x00, 0x00, 0x00);
m_palette[index++] = m_screen.color(0xcd, 0x00, 0x00);
m_palette[index++] = m_screen.color(0x00, 0xcd, 0x00);
m_palette[index++] = m_screen.color(0xcd, 0xcd, 0x00);
m_palette[index++] = m_screen.color(0x00, 0x00, 0xee);
m_palette[index++] = m_screen.color(0xcd, 0x00, 0xcd);
m_palette[index++] = m_screen.color(0x00, 0xcd, 0xcd);
m_palette[index++] = m_screen.color(0xe5, 0xe5, 0xe5);
m_palette[index++] = m_screen.color(0x7f, 0x7f, 0x7f);
m_palette[index++] = m_screen.color(0xff, 0x00, 0x00);
m_palette[index++] = m_screen.color(0x00, 0xff, 0x00);
m_palette[index++] = m_screen.color(0xff, 0xff, 0x00);
m_palette[index++] = m_screen.color(0x00, 0x50, 0xff);
m_palette[index++] = m_screen.color(0xff, 0x00, 0xff);
m_palette[index++] = m_screen.color(0x00, 0xff, 0xff);
m_palette[index++] = m_screen.color(0xff, 0xff, 0xff);
// Build the high-color portion of the table
const uint32_t intensity[] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
const uint32_t intensities = sizeof(intensity) / sizeof(uint32_t);
for (uint32_t r = 0; r < intensities; ++r) {
for (uint32_t g = 0; g < intensities; ++g) {
for (uint32_t b = 0; b < intensities; ++b) {
m_palette[index++] = m_screen.color(
intensity[r], intensity[g], intensity[b]);
}
}
}
// Build the greyscale portion of the table
for (uint8_t i = 0x08; i <= 0xee; i += 10)
m_palette[index++] = m_screen.color(i, i, i);
set_color(7, 0); // Grey on black default
m_attrs = reinterpret_cast<uint16_t *>(m_data + count + palette_size);
for (unsigned i = 0; i < count; ++i)
m_attrs[i] = m_attr;
}
repaint();
}
char *
console::line_pointer(unsigned line)
{
if (!m_data) return nullptr;
return m_data + ((m_first + line) % m_size.y) * m_size.x;
}
uint16_t *
console::attr_pointer(unsigned line)
{
if (!m_attrs) return nullptr;
return m_attrs + ((m_first + line) % m_size.y) * m_size.x;
}
void
console::repaint()
{
m_screen.fill(m_bg);
if (!m_data) return;
for (unsigned y = 0; y < m_size.y; ++y) {
const char *line = line_pointer(y);
const uint16_t *attrs = attr_pointer(y);
for (unsigned x = 0; x < m_size.x; ++x) {
const uint16_t attr = attrs[x];
set_color(static_cast<uint8_t>(attr),
static_cast<uint8_t>(attr >> 8));
m_font.draw_glyph(
m_screen,
line[x] ? line[x] : ' ',
m_fg,
m_bg,
x * m_font.width(),
y * m_font.height());
}
}
}
void
console::set_color(uint8_t fg, uint8_t bg)
{
if (!m_palette) return;
m_bg = m_palette[bg];
m_fg = m_palette[fg];
m_attr = (bg << 8) | fg;
}
void
console::scroll(unsigned lines)
{
if (!m_data) {
m_pos.x = 0;
m_pos.y = 0;
} else {
unsigned bytes = lines * m_size.x;
char *line = line_pointer(0);
for (unsigned i = 0; i < bytes; ++i)
*line++ = 0;
m_first = (m_first + lines) % m_size.y;
m_pos.y -= lines;
}
repaint();
}
size_t
console::puts(const char *message)
{
char *line = line_pointer(m_pos.y);
uint16_t *attrs = attr_pointer(m_pos.y);
size_t count = 0;
while (message && *message) {
const unsigned x = m_pos.x * m_font.width();
const unsigned y = m_pos.y * m_font.height();
const char c = *message++;
++count;
switch (c) {
case '\t':
m_pos.x = (m_pos.x + 4) / 4 * 4;
break;
case '\r':
m_pos.x = 0;
break;
case '\n':
m_pos.x = 0;
m_pos.y++;
break;
default:
if (line) line[m_pos.x] = c;
if (attrs) attrs[m_pos.x] = m_attr;
m_font.draw_glyph(m_screen, c, m_fg, m_bg, x, y);
m_pos.x++;
}
if (m_pos.x >= m_size.x) {
m_pos.x = m_pos.x % m_size.x;
m_pos.y++;
}
if (m_pos.y >= m_size.y) {
scroll(1);
line = line_pointer(m_pos.y);
}
}
return count;
}

View File

@@ -1,59 +0,0 @@
#pragma once
#include "font.h"
#include "screen.h"
#include "util.h"
struct console_data;
class console
{
public:
console(const font &f, const screen &s, void *scratch, size_t len);
void repaint();
void scroll(unsigned lines);
void set_color(uint8_t fg, uint8_t bg);
size_t puts(const char *message);
size_t printf(const char *fmt, ...);
template <typename T>
void put_hex(T x);
void put_dec(uint32_t x);
private:
char * line_pointer(unsigned line);
uint16_t * attr_pointer(unsigned line);
font m_font;
screen m_screen;
coord<unsigned> m_size;
coord<unsigned> m_pos;
screen::pixel_t m_fg, m_bg;
uint16_t m_attr;
size_t m_first;
size_t m_length;
char *m_data;
uint16_t *m_attrs;
screen::pixel_t *m_palette;
};
extern const char digits[];
template <typename T>
void console::put_hex(T x)
{
static const int chars = sizeof(x) * 2;
char message[chars + 1];
for (int i=0; i<chars; ++i) {
message[chars - i - 1] = digits[(x >> (i*4)) & 0xf];
}
message[chars] = 0;
puts(message);
}

View File

@@ -1,41 +0,0 @@
#include <stddef.h>
#include <stdint.h>
#include "console.h"
#include "font.h"
#include "kernel_data.h"
#include "screen.h"
extern "C" {
void do_the_set_registers(popcorn_data *header);
void kernel_main(popcorn_data *header);
}
console
load_console(const popcorn_data *header)
{
return console{
font::load(header->font),
screen{
header->frame_buffer,
header->hres,
header->vres,
header->rmask,
header->gmask,
header->bmask},
header->log,
header->log_length};
}
void
kernel_main(popcorn_data *header)
{
console cons = load_console(header);
cons.set_color(0x21, 0x00);
cons.puts("Popcorn OS ");
cons.set_color(0x08, 0x00);
cons.puts(GIT_VERSION " booting...\n");
do_the_set_registers(header);
}