19 Commits

Author SHA1 Message Date
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
42 changed files with 1689 additions and 45 deletions

1
.gitignore vendored
View File

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

View File

@@ -3,6 +3,7 @@ ARCH ?= x86_64
include src/arch/$(ARCH)/config.mk
BUILD_D := build
KERN_D := src/kernel
ARCH_D := src/arch/$(ARCH)
VERSION ?= $(shell git describe --dirty --always)
GITSHA ?= $(shell git rev-parse --short HEAD)
@@ -10,7 +11,7 @@ GITSHA ?= $(shell git rev-parse --short HEAD)
KERNEL_FILENAME:= popcorn.bin
KERNEL_FONT := assets/fonts/tamsyn8x16r.psf
MODULES := main
MODULES := kutil
EFI_DIR := external/gnu-efi
@@ -57,13 +58,14 @@ ASFLAGS += -p $(BUILD_D)/versions.s
CFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS)
CFLAGS += -DGIT_VERSION="\"$(VERSION)\""
CFLAGS += -std=c11
CFLAGS += -std=c11 -mcmodel=large
CXXFLAGS := $(INCLUDES) $(DEPENDFLAGS) $(BASEFLAGS) $(WARNFLAGS)
CXXFLAGS += -DGIT_VERSION="\"$(VERSION)\""
CXXFLAGS += -std=c++14
CXXFLAGS += -std=c++14 -mcmodel=large
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 += -DGIT_VERSION_WIDE="L\"$(VERSION)\""
BOOT_CFLAGS += -DGNU_EFI_USE_MS_ABI -DHAVE_USE_MS_ABI
@@ -92,12 +94,15 @@ INIT_DEP := $(BUILD_D)/.builddir
BOOT_SRCS := $(wildcard src/boot/*.c)
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)
ARCH_SRCS += $(wildcard $(ARCH_D)/*.c)
KOBJS += $(patsubst $(ARCH_D)/%,$(BUILD_D)/arch/%,$(patsubst %,%.o,$(ARCH_SRCS)))
DEPS := $(patsubst $(ARCH_D)/%,$(BUILD_D)/arch/%,$(patsubst %,%.d,$(ARCH_SRCS)))
KERN_SRCS := $(wildcard $(KERN_D)/*.s)
KERN_SRCS += $(wildcard $(KERN_D)/*.c)
KERN_SRCS += $(wildcard $(KERN_D)/*.cpp)
KERN_SRCS += $(wildcard $(ARCH_D)/*.s)
KERN_SRCS += $(wildcard $(ARCH_D)/*.c)
KERN_OBJS := $(patsubst src/%, $(BUILD_D)/%, $(patsubst %,%.o,$(KERN_SRCS)))
MOD_TARGETS :=
PARTED ?= /sbin/parted
@@ -120,19 +125,24 @@ init: $(INIT_DEP)
$(INIT_DEP):
mkdir -p $(BUILD_D) $(patsubst %,$(BUILD_D)/d.%,$(MODULES))
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)
clean:
rm -rf $(BUILD_D)/* $(BUILD_D)/.version $(BUILD_D)/.builddir
vars:
@echo "KERN_SRCS: " $(KERN_SRCS)
@echo "KERN_OBJS: " $(KERN_OBJS)
dist-clean: clean
make -C external/gnu-efi clean
dump: $(BUILD_D)/kernel.dump
vim $<
.PHONY: all clean dist-clean init dump
.PHONY: all clean dist-clean init dump vars
$(BUILD_D)/.version:
echo '$(VERSION)' | cmp -s - $@ || echo '$(VERSION)' > $@
@@ -141,7 +151,7 @@ $(BUILD_D)/versions.s:
./parse_version.py "$(VERSION)" "$(GITSHA)" > $@
-include x $(patsubst %,src/modules/%/module.mk,$(MODULES))
-include x $(DEPS)
-include x $(shell find $(BUILD_D) -type f -name '*.d')
$(EFI_LIB):
make -C external/gnu-efi all
@@ -169,24 +179,27 @@ $(BUILD_D)/boot.dump: $(BUILD_D)/boot.efi
$(OBJD) -D -S $< > $@
$(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)
$(CC) $(BOOT_CFLAGS) -c -o $@ $<
$(BUILD_D)/kernel.elf: $(KOBJS) $(MOD_TARGETS) $(ARCH_D)/kernel.ld
$(LD) $(LDFLAGS) -u _header -T $(ARCH_D)/kernel.ld -o $@ $(patsubst %,-l%,$(MODULES)) $(KOBJS)
$(BUILD_D)/kernel.elf: $(KERN_OBJS) $(MOD_TARGETS) $(ARCH_D)/kernel.ld
$(LD) $(LDFLAGS) -u _header -T $(ARCH_D)/kernel.ld -o $@ $(KERN_OBJS) $(patsubst %,-l%,$(MODULES))
$(OBJC) --only-keep-debug $@ $@.sym
$(BUILD_D)/kernel.dump: $(BUILD_D)/kernel.elf
$(OBJD) -D -S $< > $@
$(BUILD_D)/arch/%.s.o: $(ARCH_D)/%.s $(BUILD_D)/versions.s $(INIT_DEP)
$(AS) $(ASFLAGS) -o $@ $<
$(BUILD_D)/%.s.o: src/%.s $(BUILD_D)/versions.s $(INIT_DEP)
$(AS) $(ASFLAGS) -i $(ARCH_D)/ -i $(KERN_D)/ -o $@ $<
$(BUILD_D)/arch/%.c.o: $(ARCH_D)/%.c $(INIT_DEP)
$(BUILD_D)/%.c.o: src/%.c $(INIT_DEP)
$(CC) $(CFLAGS) -c -o $@ $<
$(BUILD_D)/%.cpp.o: src/%.cpp $(INIT_DEP)
$(CXX) $(CXXFLAGS) -c -o $@ $<
$(BUILD_D)/flash.img: $(OVMF)
cp $^ $@

View File

@@ -1,10 +1,7 @@
# Design / WIP notes
## Bootloader / UEFI
* 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?
## TODO
- Better page-allocation model
- Allow for more than one IOAPIC in ACPI module

View File

@@ -29,7 +29,7 @@ $(MOD_BUILD_D)/%.cpp.o: $(MOD_SRC_D)/%.cpp $(INIT_DEP)
$(CXX) $(CXXFLAGS) -c -o $@ $<
$(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)))

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 -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)
SECTIONS
{
. = 0x100000;
OFFSET = 0xFFFF800000000000;
. = OFFSET + 0x100000;
.header : {
header = .;

View File

@@ -8,6 +8,10 @@
#define KERNEL_PHYS_ADDRESS 0x100000
#endif
#ifndef KERNEL_VIRT_ADDRESS
#define KERNEL_VIRT_ADDRESS 0xFFFF800000000000
#endif
#ifndef VIRTUAL_OFFSET
#define VIRTUAL_OFFSET 0xf00000000
#endif
@@ -32,6 +36,10 @@
#define KERNEL_LOG_PAGES 4
#endif
#ifndef KERNEL_PT_MEMTYPE
#define KERNEL_PT_MEMTYPE 0x80000004
#endif
#ifndef KERNEL_FILENAME
#define KERNEL_FILENAME L"kernel.bin"
#endif

View File

@@ -37,6 +37,7 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
{
EFI_STATUS status;
EFI_BOOT_SERVICES *bootsvc = system_table->BootServices;
EFI_RUNTIME_SERVICES *runsvc = system_table->RuntimeServices;
// When checking console initialization, use CHECK_EFI_STATUS_OR_RETURN
// because we can't be sure if the console was fully set up
@@ -44,6 +45,8 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
CHECK_EFI_STATUS_OR_RETURN(status, "con_initialize");
// 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.
//
void *acpi_table = NULL;
@@ -100,10 +103,12 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
con_printf(L" Entrypoint 0x%x\r\n", version->entrypoint);
void (*kernel_main)() = version->entrypoint;
memory_mark_pointer_fixup((void **)&kernel_main);
// Set up the kernel data pages to pass to the kernel
//
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->version = DATA_HEADER_VERSION;
@@ -113,16 +118,24 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
data_header->font = load.font;
data_header->font_length = load.font_length;
memory_mark_pointer_fixup((void **)&data_header->font);
data_header->data = load.data;
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;
memory_mark_pointer_fixup((void **)&data_header->log);
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;
memory_mark_pointer_fixup((void **)&data_header->acpi_table);
data_header->_reserved0 = 0;
data_header->_reserved1 = 0;
@@ -139,6 +152,7 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
&data_header->gmask,
&data_header->bmask);
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.
//
@@ -149,11 +163,16 @@ efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
status = memory_get_map(bootsvc, &map);
CHECK_EFI_STATUS_OR_FAIL(status);
data_header->memory_map_length = map.length;
data_header->memory_map_desc_size = map.size;
// bootsvc->Stall(5000000);
status = bootsvc->ExitBootServices(image_handle, map.key);
CHECK_EFI_STATUS_OR_ASSERT(status, 0);
memory_virtualize(runsvc, &map);
// Hand control to the kernel
//
kernel_main(data_header);

View File

@@ -8,6 +8,10 @@
#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[] = {
L"EfiReservedMemoryType",
L"EfiLoaderCode",
@@ -37,6 +41,49 @@ memory_type_name(UINT32 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
EFI_PHYSICAL_ADDRESS addr = 0;
status = bootsvc->AllocatePages(AllocateAnyPages, EfiLoaderData, 4, &addr);
CHECK_EFI_STATUS_OR_RETURN(status, "Failed to allocate PML4 page.");
new_pml4 = (uint64_t *)addr;
}
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
copy_desc(EFI_MEMORY_DESCRIPTOR *src, EFI_MEMORY_DESCRIPTOR *dst, size_t len)
{
@@ -111,3 +158,48 @@ memory_dump_map(struct memory_map *map)
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:
case KERNEL_LOG_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_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(EFI_BOOT_SERVICES *bootsvc, 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

@@ -26,6 +26,9 @@ struct popcorn_data {
size_t log_length;
void *memory_map;
size_t memory_map_length;
size_t memory_map_desc_size;
void *runtime;
void *acpi_table;

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

@@ -17,6 +17,8 @@ section .text
align 16
global _start:function (_start.end - _start)
_start:
cli
extern kernel_main
call kernel_main
@@ -26,3 +28,13 @@ _start:
hlt
jmp .hang
.end:
global interrupts_enable
interrupts_enable:
sti
ret
global interrupts_disable
interrupts_disable:
cli
ret

View File

@@ -2,6 +2,8 @@
const char digits[] = "0123456789abcdef";
console *console::default_console = nullptr;
console::console(const font &f, const screen &s, void *scratch, size_t len) :
m_font(f),
m_screen(s),
@@ -73,6 +75,9 @@ console::console(const font &f, const screen &s, void *scratch, size_t len) :
}
repaint();
if (default_console == nullptr)
default_console = this;
}
char *

View File

@@ -1,8 +1,8 @@
#pragma once
#include "kutil/coord.h"
#include "font.h"
#include "screen.h"
#include "util.h"
struct console_data;
@@ -22,7 +22,10 @@ public:
template <typename T>
void put_hex(T x);
void put_dec(uint32_t x);
template <typename T>
void put_dec(T x);
static console * get() { return default_console; }
private:
char * line_pointer(unsigned line);
@@ -31,8 +34,8 @@ private:
font m_font;
screen m_screen;
coord<unsigned> m_size;
coord<unsigned> m_pos;
kutil::coord<unsigned> m_size;
kutil::coord<unsigned> m_pos;
screen::pixel_t m_fg, m_bg;
uint16_t m_attr;
@@ -42,6 +45,8 @@ private:
char *m_data;
uint16_t *m_attrs;
screen::pixel_t *m_palette;
static console *default_console;
};
extern const char digits[];
@@ -57,3 +62,17 @@ void console::put_hex(T x)
message[chars] = 0;
puts(message);
}
template <typename T>
void console::put_dec(T x)
{
static const int chars = sizeof(x) * 3;
char message[chars + 1];
char *p = message + chars;
*p-- = 0;
do {
*p-- = digits[x % 10];
x /= 10;
} while (x != 0);
puts(++p);
}

View File

@@ -0,0 +1,156 @@
#include <stddef.h>
#include <stdint.h>
#include "kutil/memory.h"
#include "acpi_tables.h"
#include "assert.h"
#include "device_manager.h"
#include "console.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)
{
console *cons = console::get();
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(console *cons, uint32_t type)
{
char sig[5] = {0,0,0,0,0};
for (int j=0; j<4; ++j)
sig[j] = reinterpret_cast<char *>(&type)[j];
cons->puts(sig);
}
void
device_manager::load_xsdt(const acpi_xsdt *xsdt)
{
kassert(xsdt && acpi_validate(xsdt), "Invalid ACPI XSDT.");
console *cons = console::get();
cons->puts("ACPI 2.0 tables loading: ");
put_sig(cons, xsdt->header.type);
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];
cons->puts(" ");
put_sig(cons, header->type);
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;
}
}
cons->puts("\n");
}
void
device_manager::load_apic(const acpi_apic *apic)
{
console *cons = console::get();
m_local_apic = reinterpret_cast<uint32_t *>(apic->local_address);
cons->puts(" ");
cons->put_hex(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];
cons->puts(" ");
cons->put_hex(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);
cons->puts(" ");
cons->put_hex((uint64_t)m_io_apic);
cons->puts(" ");
cons->put_hex(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
#include <stdint.h>
#include "kutil/coord.h"
#include "screen.h"
#include "util.h"
class font
{
@@ -28,7 +28,7 @@ private:
font();
font(unsigned height, unsigned width, unsigned count, uint8_t const *data);
coord<unsigned> m_size;
kutil::coord<unsigned> m_size;
unsigned m_count;
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);

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

@@ -0,0 +1,353 @@
#include <stddef.h>
#include <stdint.h>
#include "kutil/memory.h"
#include "console.h"
#include "interrupts.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();
}
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->puts(" " name ": "); \
cons->put_hex((value)); \
cons->puts("\n");
void
isr_handler(registers regs)
{
console *cons = console::get();
cons->puts("received ISR interrupt:\n");
print_reg("ISR", regs.interrupt);
print_reg("ERR", regs.errorcode);
console::get()->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);
console::get()->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->puts("received IRQ interrupt:\n");
print_reg("ISR", regs.interrupt);
print_reg("ERR", regs.errorcode);
console::get()->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);
console::get()->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)
{
console *cons = console::get();
cons->puts("Loaded GDT at: ");
cons->put_hex(table.base);
cons->puts(" size: ");
cons->put_dec(table.limit + 1);
cons->puts(" bytes\n");
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;
cons->puts(" Entry ");
cons->put_dec(i);
cons->puts(": Base ");
cons->put_hex(base);
cons->puts(" Limit ");
cons->put_hex(limit);
cons->puts(" Privs ");
cons->put_dec((gdt[i].flags >> 5) & 0x03);
cons->puts(" Flags ");
if (gdt[i].flags & 0x80) {
cons->puts("P ");
if (gdt[i].flags & 0x08)
cons->puts("EX ");
else
cons->puts(" ");
if (gdt[i].flags & 0x04)
cons->puts("DC ");
else
cons->puts(" ");
if (gdt[i].flags & 0x02)
cons->puts("RW ");
else
cons->puts(" ");
if (gdt[i].granularity & 0x80)
cons->puts("KB ");
else
cons->puts(" B ");
if ((gdt[i].granularity & 0x60) == 0x20)
cons->puts("64");
else if ((gdt[i].granularity & 0x60) == 0x40)
cons->puts("32");
else
cons->puts("16");
}
cons->puts("\n");
}
}
void
idt_dump(const table_ptr &table)
{
console *cons = console::get();
cons->puts("Loaded IDT at: ");
cons->put_hex(table.base);
cons->puts(" size: ");
cons->put_dec(table.limit + 1);
cons->puts(" bytes\n");
int count = (table.limit + 1) / sizeof(idt_descriptor);
const idt_descriptor *idt =
reinterpret_cast<const idt_descriptor *>(table.base);
if (count > 32) count = 32;
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;
cons->puts(" Entry ");
cons->put_dec(i);
cons->puts(": Base ");
cons->put_hex(base);
cons->puts(" Sel(");
cons->put_dec(idt[i].selector & 0x3);
cons->puts(",");
cons->put_dec((idt[i].selector & 0x4) >> 2);
cons->puts(",");
cons->put_dec(idt[i].selector >> 3);
cons->puts(") ");
cons->puts("IST ");
cons->put_dec(idt[i].ist);
switch (idt[i].flags & 0xf) {
case 0x5: cons->puts(" 32tsk "); break;
case 0x6: cons->puts(" 16int "); break;
case 0x7: cons->puts(" 16trp "); break;
case 0xe: cons->puts(" 32int "); break;
case 0xf: cons->puts(" 32trp "); break;
default:
cons->puts(" ?");
cons->put_hex(idt[i].flags & 0xf);
cons->puts(" ");
break;
}
cons->puts("DPL ");
cons->put_dec((idt[i].flags >> 5) & 0x3);
if (idt[i].flags & 0x80)
cons->puts(" P");
cons->puts("\n");
}
}

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"

View File

@@ -2,8 +2,11 @@
#include <stdint.h>
#include "console.h"
#include "device_manager.h"
#include "font.h"
#include "interrupts.h"
#include "kernel_data.h"
#include "memory.h"
#include "screen.h"
extern "C" {
@@ -14,7 +17,7 @@ extern "C" {
console
load_console(const popcorn_data *header)
{
return console{
console cons{
font::load(header->font),
screen{
header->frame_buffer,
@@ -25,6 +28,13 @@ load_console(const popcorn_data *header)
header->bmask},
header->log,
header->log_length};
cons.set_color(0x21, 0x00);
cons.puts("Popcorn OS ");
cons.set_color(0x08, 0x00);
cons.puts(GIT_VERSION " booting...\n");
return cons;
}
void
@@ -32,10 +42,21 @@ 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");
memory_manager::create(
header->memory_map,
header->memory_map_length,
header->memory_map_desc_size);
interrupts_init();
interrupts_enable();
cons.puts("Interrupts initialized.\n");
device_manager devices(header->acpi_table);
// int x = 1 / 0;
// __asm__ __volatile__("int $15");
cons.puts("boogity!");
do_the_set_registers(header);
}

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

@@ -0,0 +1,10 @@
#include "assert.h"
#include "console.h"
#include "memory.h"
#include "memory_pages.h"
memory_manager *memory_manager::s_instance = nullptr;
memory_manager::memory_manager(page_block *free, page_block *used, void *scratch, size_t scratch_len)
{
}

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

@@ -0,0 +1,21 @@
#pragma once
#include <stddef.h>
struct page_block;
class memory_manager
{
public:
static void create(const void *memory_map, size_t map_length, size_t desc_length);
static memory_manager * get() { return s_instance; }
private:
memory_manager(page_block *free, page_block *used, void *scratch, size_t scratch_len);
memory_manager() = delete;
memory_manager(const memory_manager &) = delete;
static memory_manager * s_instance;
};

View File

@@ -0,0 +1,296 @@
#include "assert.h"
#include "console.h"
#include "memory.h"
#include "memory_pages.h"
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);
}
struct page_table
{
uint64_t entries[512];
page_table * next(int i) const { return reinterpret_cast<page_table *>(entries[i] & ~0xfffull); }
};
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 * 0x1000)};
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];
}
void
memory_manager::create(const void *memory_map, size_t map_length, size_t desc_length)
{
console *cons = console::get();
// The bootloader reserved 4 pages for page tables, which we'll use to bootstrap.
// The first one is the already-installed PML4, so grab it from CR3.
page_table *tables = nullptr;
__asm__ __volatile__ ( "mov %%cr3, %0" : "=r" (tables) );
// Now go through EFi's memory map and find a 4MiB region of free space to
// use as a scratch space. We'll use the 2MiB that fits naturally aligned
// into a single page table.
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) {
if (desc->type == efi_memory_type::available && desc->pages >= 1024)
break;
desc = desc_incr(desc, desc_length);
}
kassert(desc < end, "Couldn't find 4MiB of contiguous scratch space.");
uint64_t free_region = (desc->physical_start & 0x1fffff) == 0 ?
desc->physical_start :
desc->physical_start + 0x1fffff & ~0x1fffffull;
// Offset-map this region into the higher half.
uint64_t next_free = free_region + 0xffff800000000000;
cons->puts("Found region: ");
cons->put_hex(free_region);
cons->puts("\n");
// 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};
fr_idx[0] += 256; // Flip the highest bit of the address
if (tables[0].entries[fr_idx[0]] & 0x1) {
page_table *old_pdpt = tables[0].next(fr_idx[0]);
for (int i = 0; i < 512; ++i) tables[1].entries[i] = old_pdpt->entries[i];
} else {
for (int i = 0; i < 512; ++i) tables[1].entries[i] = 0;
}
tables[0].entries[fr_idx[0]] = reinterpret_cast<uint64_t>(&tables[1]) | 0xb;
if (tables[1].entries[fr_idx[1]] & 0x1) {
page_table *old_pdt = tables[1].next(fr_idx[1]);
for (int i = 0; i < 512; ++i) tables[2].entries[i] = old_pdt->entries[i];
} else {
for (int i = 0; i < 512; ++i) tables[2].entries[i] = 0;
}
tables[1].entries[fr_idx[1]] = reinterpret_cast<uint64_t>(&tables[2]) | 0xb;
for (int i = 0; i < 512; ++i)
tables[3].entries[i] = (free_region + 0x1000 * i) | 0xb;
tables[2].entries[fr_idx[2]] = reinterpret_cast<uint64_t>(&tables[3]) | 0xb;
// We now have 2MiB starting at "free_region" to bootstrap ourselves. Start by
// taking inventory of free pages.
page_block *block_list = reinterpret_cast<page_block *>(next_free);
int i = 0;
page_block *free_head = nullptr, **free = &free_head;
page_block *used_head = nullptr, **used = &used_head;
desc = reinterpret_cast<efi_memory_descriptor const *>(memory_map);
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:
if (free_region >= block->physical_address && free_region < block->end()) {
// This is the scratch memory block, split off what we're not using
block->virtual_address = block->physical_address + 0xffff800000000000;
block->flags = page_block_flags::used
| page_block_flags::mapped
| page_block_flags::pending_free;
if (block->count > 1024) {
page_block *rest = &block_list[i++];
rest->physical_address = desc->physical_start + (1024*0x1000);
rest->virtual_address = 0;
rest->count = desc->pages - 1024;
rest->next = nullptr;
*free = rest;
free = &rest->next;
block->count = 1024;
}
} else {
block->flags = page_block_flags::free;
}
break;
case efi_memory_type::acpi_reclaim:
block->flags = page_block_flags::used | page_block_flags::acpi_wait;
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 != 0)
block->flags |= page_block_flags::mapped;
*used = block;
used = &block->next;
} else {
*free = block;
free = &block->next;
}
desc = desc_incr(desc, desc_length);
}
// Update the pointer to the next free page
next_free += i * sizeof(page_block);
next_free = ((next_free - 1) & ~0xfffull) + 0x1000;
// Now go back through these lists and consolidate
free_head->list_consolidate();
used_head->list_consolidate();
// Ok, now build an acutal set of kernel page tables that just contains
// what the kernel actually has mapped.
unsigned table_page_count = count_table_pages_needed(used_head);
cons->puts("To map currently-mapped pages, we need ");
cons->put_dec(table_page_count);
cons->puts(" pages of tables.\n");
}

View File

@@ -0,0 +1,74 @@
#include "console.h"
#include "memory_pages.h"
page_block *
page_block::list_consolidate()
{
page_block *cur = this;
page_block *freed_head = nullptr, **freed = &freed_head;
while (cur) {
page_block *next = cur->next;
if (next && cur->flags == next->flags &&
cur->end() == next->physical_address)
{
cur->count += next->count;
cur->next = next->next;
next->next = 0;
*freed = next;
freed = &next->next;
continue;
}
cur = next;
}
return freed_head;
}
void
page_block::list_dump(const char *name)
{
console *cons = console::get();
cons->puts("Block list");
if (name) {
cons->puts(" ");
cons->puts(name);
}
cons->puts(":\n");
int count = 0;
for (page_block *cur = this; cur; cur = cur->next) {
cons->puts(" ");
cons->put_hex(cur->physical_address);
cons->puts(" ");
cons->put_hex((uint32_t)cur->flags);
if (cur->virtual_address) {
cons->puts(" ");
cons->put_hex(cur->virtual_address);
}
cons->puts(" [");
cons->put_dec(cur->count);
cons->puts("]\n");
count += 1;
}
cons->puts(" Total: ");
cons->put_dec(count);
cons->puts("\n");
}
void
page_table_indices::dump()
{
console *cons = console::get();
cons->puts("{");
for (int i = 0; i < 4; ++i) {
if (i) cons->puts(", ");
cons->put_dec(index[i]);
}
cons->puts("}");
}

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

@@ -0,0 +1,54 @@
#pragma once
#include <stdint.h>
#include "kutil/enum_bitfields.h"
enum class page_block_flags : uint32_t
{
// Not a flag value, but for comparison
free = 0x00000000,
used = 0x00000001,
mapped = 0x00000002,
pending_free = 0x00000004,
nonvolatile = 0x00000010,
acpi_wait = 0x00000020,
permanent = 0x80000000,
max_flags
};
IS_BITFIELD(page_block_flags);
struct page_block
{
uint64_t physical_address;
uint64_t virtual_address;
uint32_t count;
page_block_flags flags;
page_block *next;
bool has_flag(page_block_flags f) const { return bitfield_contains(flags, f); }
uint64_t end() const { return physical_address + (count * 0x1000); }
page_block * list_consolidate();
void list_dump(const char *name = nullptr);
};
struct page_table_indices
{
page_table_indices(uint64_t v) :
index{
(v >> 39) & 0x1ff,
(v >> 30) & 0x1ff,
(v >> 21) & 0x1ff,
(v >> 12) & 0x1ff }
{}
uint64_t & operator[](size_t i) { return index[i]; }
uint64_t index[4];
void dump();
};

View File

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

View File

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

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,13 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
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