wip: initial commit
commit
d43d7caf15
@ -0,0 +1,5 @@
|
|||||||
|
*.bin
|
||||||
|
*.iso
|
||||||
|
*.o
|
||||||
|
*.sw*
|
||||||
|
iso/boot/*.bin
|
@ -0,0 +1,45 @@
|
|||||||
|
ASFLAGS := --32
|
||||||
|
CXXFLAGS := -m32 -ggdb -g3 -O0 -fno-pic -ffreestanding -fno-rtti -fno-exceptions -std=c++23
|
||||||
|
LDFLAGS := -m32 -static -T link.ld -ffreestanding -nostdlib
|
||||||
|
|
||||||
|
ASFILES := boot.s
|
||||||
|
CXXFILES := gdt.cpp \
|
||||||
|
idt.cpp \
|
||||||
|
memory.cpp \
|
||||||
|
multiboot.cpp \
|
||||||
|
pic.cpp \
|
||||||
|
pit.cpp \
|
||||||
|
tasking.cpp \
|
||||||
|
vgaterminal.cpp \
|
||||||
|
kernel.cpp
|
||||||
|
|
||||||
|
OBJS := $(subst .s,.o,$(ASFILES)) \
|
||||||
|
$(subst .cpp,.o,$(CXXFILES))
|
||||||
|
|
||||||
|
all: myos.iso
|
||||||
|
|
||||||
|
myos.iso: myos.bin iso/boot/grub/grub.cfg
|
||||||
|
@echo " ISO " $@
|
||||||
|
@cp myos.bin iso/boot/
|
||||||
|
@grub-mkrescue -o myos.iso iso/
|
||||||
|
|
||||||
|
myos.bin: $(OBJS) link.ld
|
||||||
|
@echo " LD " $@
|
||||||
|
@g++ $(LDFLAGS) -o $@ $(OBJS)
|
||||||
|
|
||||||
|
%.o: %.s
|
||||||
|
@echo " AS " $<
|
||||||
|
@as $(ASFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
%.o: %.cpp
|
||||||
|
@echo " CXX " $<
|
||||||
|
@g++ $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo " CLEAN"
|
||||||
|
@rm -f $(OBJS) myos.bin myos.iso
|
||||||
|
|
||||||
|
run: myos.iso
|
||||||
|
@echo " QEMU"
|
||||||
|
@qemu-system-i386 -cdrom $< -monitor stdio -no-reboot -s -S #-d int
|
||||||
|
|
@ -0,0 +1,127 @@
|
|||||||
|
/* Declare constants for the multiboot header. */
|
||||||
|
.set MAGIC, 0xE85250D6
|
||||||
|
.set FLAGS, 0x0
|
||||||
|
.set LENGTH, 16
|
||||||
|
.set CHECKSUM, -(MAGIC + FLAGS + LENGTH)
|
||||||
|
|
||||||
|
.section .multiboot2
|
||||||
|
.align 8
|
||||||
|
.int MAGIC
|
||||||
|
.int FLAGS
|
||||||
|
.int LENGTH
|
||||||
|
.int CHECKSUM
|
||||||
|
|
||||||
|
/* info request */
|
||||||
|
.align 8
|
||||||
|
.hword 1, 0
|
||||||
|
.int 12
|
||||||
|
.int 4
|
||||||
|
|
||||||
|
/* end tag */
|
||||||
|
.align 8
|
||||||
|
.hword 0, 0
|
||||||
|
.int 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
The multiboot standard does not define the value of the stack pointer register
|
||||||
|
(esp) and it is up to the kernel to provide a stack. This allocates room for a
|
||||||
|
small stack by creating a symbol at the bottom of it, then allocating 16384
|
||||||
|
bytes for it, and finally creating a symbol at the top. The stack grows
|
||||||
|
downwards on x86. The stack is in its own section so it can be marked nobits,
|
||||||
|
which means the kernel file is smaller because it does not contain an
|
||||||
|
uninitialized stack. The stack on x86 must be 16-byte aligned according to the
|
||||||
|
System V ABI standard and de-facto extensions. The compiler will assume the
|
||||||
|
stack is properly aligned and failure to align the stack will result in
|
||||||
|
undefined behavior.
|
||||||
|
*/
|
||||||
|
.section .bss
|
||||||
|
.align 16
|
||||||
|
stack_bottom:
|
||||||
|
.skip 16384 # 16 KiB
|
||||||
|
stack_top:
|
||||||
|
|
||||||
|
/*
|
||||||
|
The linker script specifies _start as the entry point to the kernel and the
|
||||||
|
bootloader will jump to this position once the kernel has been loaded. It
|
||||||
|
doesn't make sense to return from this function as the bootloader is gone.
|
||||||
|
*/
|
||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
.type _start, @function
|
||||||
|
_start:
|
||||||
|
/*
|
||||||
|
The bootloader has loaded us into 32-bit protected mode on a x86
|
||||||
|
machine. Interrupts are disabled. Paging is disabled. The processor
|
||||||
|
state is as defined in the multiboot standard. The kernel has full
|
||||||
|
control of the CPU. The kernel can only make use of hardware features
|
||||||
|
and any code it provides as part of itself. There's no printf
|
||||||
|
function, unless the kernel provides its own <stdio.h> header and a
|
||||||
|
printf implementation. There are no security restrictions, no
|
||||||
|
safeguards, no debugging mechanisms, only what the kernel provides
|
||||||
|
itself. It has absolute and complete power over the
|
||||||
|
machine.
|
||||||
|
*/
|
||||||
|
mov %eax, multiboot_magic
|
||||||
|
mov %ebx, multiboot_ptr
|
||||||
|
|
||||||
|
/*
|
||||||
|
To set up a stack, we set the esp register to point to the top of the
|
||||||
|
stack (as it grows downwards on x86 systems). This is necessarily done
|
||||||
|
in assembly as languages such as C cannot function without a stack.
|
||||||
|
*/
|
||||||
|
mov $stack_top, %esp
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a good place to initialize crucial processor state before the
|
||||||
|
high-level kernel is entered. It's best to minimize the early
|
||||||
|
environment where crucial features are offline. Note that the
|
||||||
|
processor is not fully initialized yet: Features such as floating
|
||||||
|
point instructions and instruction set extensions are not initialized
|
||||||
|
yet. The GDT should be loaded here. Paging should be enabled here.
|
||||||
|
C++ features such as global constructors and exceptions will require
|
||||||
|
runtime support to work as well.
|
||||||
|
*/
|
||||||
|
mov $__init_array_start, %eax
|
||||||
|
.again:
|
||||||
|
cmp $__init_array_end, %eax
|
||||||
|
je .next
|
||||||
|
push %eax
|
||||||
|
call *(%eax)
|
||||||
|
pop %eax
|
||||||
|
add $0x4, %eax
|
||||||
|
jmp .again
|
||||||
|
|
||||||
|
.next:
|
||||||
|
|
||||||
|
/*
|
||||||
|
Enter the high-level kernel. The ABI requires the stack is 16-byte
|
||||||
|
aligned at the time of the call instruction (which afterwards pushes
|
||||||
|
the return pointer of size 4 bytes). The stack was originally 16-byte
|
||||||
|
aligned above and we've pushed a multiple of 16 bytes to the
|
||||||
|
stack since (pushed 0 bytes so far), so the alignment has thus been
|
||||||
|
preserved and the call is well defined.
|
||||||
|
*/
|
||||||
|
call kernel_main
|
||||||
|
|
||||||
|
/*
|
||||||
|
If the system has nothing more to do, put the computer into an
|
||||||
|
infinite loop. To do that:
|
||||||
|
1) Disable interrupts with cli (clear interrupt enable in eflags).
|
||||||
|
They are already disabled by the bootloader, so this is not needed.
|
||||||
|
Mind that you might later enable interrupts and return from
|
||||||
|
kernel_main (which is sort of nonsensical to do).
|
||||||
|
2) Wait for the next interrupt to arrive with hlt (halt instruction).
|
||||||
|
Since they are disabled, this will lock up the computer.
|
||||||
|
3) Jump to the hlt instruction if it ever wakes up due to a
|
||||||
|
non-maskable interrupt occurring or due to system management mode.
|
||||||
|
*/
|
||||||
|
cli
|
||||||
|
1: hlt
|
||||||
|
jmp 1b
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the size of the _start symbol to the current location '.' minus its start.
|
||||||
|
This is useful when debugging or when you implement call tracing.
|
||||||
|
*/
|
||||||
|
.size _start, . - _start
|
||||||
|
|
@ -0,0 +1,81 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct gdt_entry_bits {
|
||||||
|
std::uint32_t limit_low : 16;
|
||||||
|
std::uint32_t base_low : 24;
|
||||||
|
std::uint32_t accessed : 1;
|
||||||
|
std::uint32_t read_write : 1; // readable for code, writable for data
|
||||||
|
std::uint32_t conforming_expand_down : 1; // conforming for code, expand down for data
|
||||||
|
std::uint32_t code : 1; // 1 for code, 0 for data
|
||||||
|
std::uint32_t code_data_segment : 1; // should be 1 for everything but TSS and LDT
|
||||||
|
std::uint32_t DPL : 2; // privilege level
|
||||||
|
std::uint32_t present : 1;
|
||||||
|
std::uint32_t limit_high : 4;
|
||||||
|
std::uint32_t available : 1; // only used in software; has no effect on hardware
|
||||||
|
std::uint32_t long_mode : 1;
|
||||||
|
std::uint32_t big : 1; // 32-bit opcodes for code, uint32_t stack for data
|
||||||
|
std::uint32_t gran : 1; // 1 to use 4k page addressing, 0 for byte addressing
|
||||||
|
std::uint32_t base_high : 8;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
constinit static const std::array<gdt_entry_bits, 3> gdt {{
|
||||||
|
{},
|
||||||
|
/* kernel_code = */ {
|
||||||
|
.limit_low = 0xFFFF,
|
||||||
|
.base_low = 0x0000,
|
||||||
|
.accessed = 0,
|
||||||
|
.read_write = 1,
|
||||||
|
.conforming_expand_down = 0,
|
||||||
|
.code = 1,
|
||||||
|
.code_data_segment = 1,
|
||||||
|
.DPL = 0,
|
||||||
|
.present = 1,
|
||||||
|
.limit_high = 0xF,
|
||||||
|
.available = 0,
|
||||||
|
.long_mode = 0,
|
||||||
|
.big = 1,
|
||||||
|
.gran = 1,
|
||||||
|
.base_high = 0x00
|
||||||
|
},
|
||||||
|
/* kernel_data = */ {
|
||||||
|
.limit_low = 0xFFFF,
|
||||||
|
.base_low = 0x0000,
|
||||||
|
.accessed = 0,
|
||||||
|
.read_write = 1,
|
||||||
|
.conforming_expand_down = 0,
|
||||||
|
.code = 0,
|
||||||
|
.code_data_segment = 1,
|
||||||
|
.DPL = 0,
|
||||||
|
.present = 1,
|
||||||
|
.limit_high = 0xF,
|
||||||
|
.available = 0,
|
||||||
|
.long_mode = 0,
|
||||||
|
.big = 1,
|
||||||
|
.gran = 1,
|
||||||
|
.base_high = 0x00
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
void gdt_initialize()
|
||||||
|
{
|
||||||
|
auto gdtr = reinterpret_cast<std::uint64_t>(gdt.data());
|
||||||
|
gdtr <<= 16;
|
||||||
|
gdtr |= gdt.size() * sizeof(gdt[0]);
|
||||||
|
|
||||||
|
asm volatile(R"(
|
||||||
|
lgdt %0
|
||||||
|
pushl $0x8
|
||||||
|
push $.setcs
|
||||||
|
ljmp *(%%esp)
|
||||||
|
.setcs:
|
||||||
|
add $8, %%esp
|
||||||
|
mov $0x10, %%eax
|
||||||
|
mov %%eax, %%ds
|
||||||
|
mov %%eax, %%es
|
||||||
|
mov %%eax, %%fs
|
||||||
|
mov %%eax, %%gs
|
||||||
|
mov %%eax, %%ss
|
||||||
|
)" :: "m"(gdtr));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
#ifndef GDT_HPP
|
||||||
|
#define GDT_HPP
|
||||||
|
|
||||||
|
void gdt_initialize();
|
||||||
|
|
||||||
|
#endif // GDT_HPP
|
||||||
|
|
@ -0,0 +1,107 @@
|
|||||||
|
#include "idt.hpp"
|
||||||
|
#include "portio.hpp"
|
||||||
|
#include "textoutput.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
extern TextOutput& term;
|
||||||
|
|
||||||
|
static constexpr std::uint8_t TaskGate = 0x5;
|
||||||
|
static constexpr std::uint8_t IntrGate16 = 0x6;
|
||||||
|
static constexpr std::uint8_t TrapGate16 = 0x7;
|
||||||
|
static constexpr std::uint8_t IntrGate32 = 0xE;
|
||||||
|
static constexpr std::uint8_t TrapGate32 = 0xF;
|
||||||
|
|
||||||
|
struct idt_entry_bits {
|
||||||
|
std::uint32_t offset_low : 16;
|
||||||
|
std::uint32_t segment_selector : 16;
|
||||||
|
std::uint32_t rsvd : 8;
|
||||||
|
std::uint32_t gate_type : 4;
|
||||||
|
std::uint32_t rsvd2 : 1;
|
||||||
|
std::uint32_t dpl : 2;
|
||||||
|
std::uint32_t present : 1;
|
||||||
|
std::uint32_t offset_high : 16;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
static std::array<Callback, 48> callbacks;
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
void interruptGeneralHandler(Registers regs)
|
||||||
|
{
|
||||||
|
const auto& inum = regs.inum;
|
||||||
|
|
||||||
|
if (inum >= 32) {
|
||||||
|
if (inum >= 40)
|
||||||
|
outb(0xA0, 0x20);
|
||||||
|
|
||||||
|
outb(0x20, 0x20);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inum < callbacks.size()) {
|
||||||
|
if (auto cb = callbacks[inum]; cb)
|
||||||
|
cb(regs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t N>
|
||||||
|
struct StubEntry
|
||||||
|
{
|
||||||
|
static constexpr bool HasError = N == 8 || (N >= 10 && N <= 14) || N == 17 || N == 30;
|
||||||
|
|
||||||
|
__attribute__((naked))
|
||||||
|
static void stub() {
|
||||||
|
if constexpr (!HasError)
|
||||||
|
asm volatile("push $0x0");
|
||||||
|
|
||||||
|
asm volatile(R"(
|
||||||
|
pusha
|
||||||
|
push %0
|
||||||
|
cld
|
||||||
|
call interruptGeneralHandler
|
||||||
|
pop %%eax
|
||||||
|
popa
|
||||||
|
add $0x4, %%esp
|
||||||
|
iret
|
||||||
|
)" :: "i"(N));
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::uint32_t segment(std::uint16_t gdt_idx, bool useLdt, std::uint16_t rpl) {
|
||||||
|
return gdt_idx | (useLdt ? 0x4 : 0x0) | (rpl & 0x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
idt_entry_bits entry = {
|
||||||
|
.offset_low = (uint32_t)stub & 0xFFFF,
|
||||||
|
.segment_selector = segment(0x8, false, 0),
|
||||||
|
.gate_type = IntrGate32,
|
||||||
|
.dpl = 0,
|
||||||
|
.present = 1,
|
||||||
|
.offset_high = (uint32_t)stub >> 16
|
||||||
|
};
|
||||||
|
|
||||||
|
operator idt_entry_bits() const noexcept {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto idt =
|
||||||
|
[]<std::size_t... ints>(std::index_sequence<ints...>) {
|
||||||
|
return std::array<idt_entry_bits, 256> { StubEntry<ints>()... };
|
||||||
|
}(std::make_index_sequence<48>{});
|
||||||
|
|
||||||
|
void idt_initialize()
|
||||||
|
{
|
||||||
|
auto idtr = reinterpret_cast<std::uint64_t>(idt.data());
|
||||||
|
idtr <<= 16;
|
||||||
|
idtr |= idt.size() * sizeof(idt[0]);
|
||||||
|
|
||||||
|
asm volatile("lidt %0" :: "m"(idtr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void idt_register_callback(std::size_t num, Callback cb)
|
||||||
|
{
|
||||||
|
if (num < callbacks.size())
|
||||||
|
callbacks[num] = cb;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
#ifndef IDT_HPP
|
||||||
|
#define IDT_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct Registers
|
||||||
|
{
|
||||||
|
std::uint32_t inum;
|
||||||
|
std::uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
|
||||||
|
std::uint32_t error;
|
||||||
|
std::uint32_t eip, cs, eflags;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
using Callback = void (*)(const Registers&);
|
||||||
|
|
||||||
|
void idt_initialize();
|
||||||
|
void idt_register_callback(std::size_t num, Callback cb);
|
||||||
|
|
||||||
|
#endif // IDT_HPP
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
menuentry "myos" {
|
||||||
|
multiboot2 /boot/myos.bin
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
|||||||
|
#include "gdt.hpp"
|
||||||
|
#include "idt.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
#include "multiboot.hpp"
|
||||||
|
#include "pic.hpp"
|
||||||
|
#include "pit.hpp"
|
||||||
|
#include "tasking.hpp"
|
||||||
|
#include "vgaterminal.hpp"
|
||||||
|
|
||||||
|
static VGATerminal vga;
|
||||||
|
TextOutput& term = vga;
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
void kernel_main(void)
|
||||||
|
{
|
||||||
|
term.write("Clyne's kernel, v2024\n\n");
|
||||||
|
|
||||||
|
if (!multiboot_initialize())
|
||||||
|
for (;;);
|
||||||
|
|
||||||
|
idt_register_callback(14, [](const Registers& regs) {
|
||||||
|
term.write("Page fault! eip=");
|
||||||
|
term.write(regs.eip);
|
||||||
|
term.write('\n');
|
||||||
|
for (;;);
|
||||||
|
});
|
||||||
|
|
||||||
|
memory_initialize();
|
||||||
|
gdt_initialize();
|
||||||
|
pic_initialize();
|
||||||
|
idt_initialize();
|
||||||
|
pit_initialize(50);
|
||||||
|
asm volatile("sti");
|
||||||
|
tasking_initialize();
|
||||||
|
term.write("Tasking enabled.\n");
|
||||||
|
|
||||||
|
tasking_spawn([] {
|
||||||
|
for (;;)
|
||||||
|
term.write('B');
|
||||||
|
}, 256);
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
term.write('A');
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
void memmove(char* dst, char* src, size_t sz) {
|
||||||
|
while (sz) {
|
||||||
|
*dst = *src;
|
||||||
|
++dst;
|
||||||
|
++src;
|
||||||
|
--sz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
|||||||
|
/* The bootloader will look at this image and start execution at the symbol
|
||||||
|
designated as the entry point. */
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
/* Tell where the various sections of the object files will be put in the final
|
||||||
|
kernel image. */
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
/* It used to be universally recommended to use 1M as a start offset,
|
||||||
|
as it was effectively guaranteed to be available under BIOS systems.
|
||||||
|
However, UEFI has made things more complicated, and experimental data
|
||||||
|
strongly suggests that 2M is a safer place to load. In 2016, a new
|
||||||
|
feature was introduced to the multiboot2 spec to inform bootloaders
|
||||||
|
that a kernel can be loaded anywhere within a range of addresses and
|
||||||
|
will be able to relocate itself to run from such a loader-selected
|
||||||
|
address, in order to give the loader freedom in selecting a span of
|
||||||
|
memory which is verified to be available by the firmware, in order to
|
||||||
|
work around this issue. This does not use that feature, so 2M was
|
||||||
|
chosen as a safer option than the traditional 1M. */
|
||||||
|
. = 2M;
|
||||||
|
|
||||||
|
/* First put the multiboot header, as it is required to be put very early
|
||||||
|
in the image or the bootloader won't recognize the file format.
|
||||||
|
Next we'll put the .text section. */
|
||||||
|
.text BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
*(.multiboot2)
|
||||||
|
*(.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read-only data. */
|
||||||
|
.rodata BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
*(.rodata)
|
||||||
|
}
|
||||||
|
|
||||||
|
.init_array :
|
||||||
|
{
|
||||||
|
__init_array_start = .;
|
||||||
|
*(.init_array)
|
||||||
|
__init_array_end = .;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read-write data (initialized) */
|
||||||
|
.data BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
*(.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read-write data (uninitialized) and stack */
|
||||||
|
.bss BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
*(COMMON)
|
||||||
|
*(.bss)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The compiler may produce other sections, by default it will put them in
|
||||||
|
a segment with the same name. Simply add stuff here as needed. */
|
||||||
|
|
||||||
|
.note :
|
||||||
|
{
|
||||||
|
*(.note)
|
||||||
|
*(.note*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
|||||||
|
#include "textoutput.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct PageDirectory
|
||||||
|
{
|
||||||
|
static constexpr std::uint32_t NotPresent = 0x2;
|
||||||
|
|
||||||
|
PageDirectory(): value(NotPresent) {}
|
||||||
|
PageDirectory(void *addr): value(reinterpret_cast<std::uint32_t>(addr) | 3) {}
|
||||||
|
|
||||||
|
std::uint32_t value;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(PageDirectory) == sizeof(std::uint32_t));
|
||||||
|
|
||||||
|
extern std::uint32_t lowerMem;
|
||||||
|
extern std::uint32_t upperMem;
|
||||||
|
extern TextOutput& term;
|
||||||
|
|
||||||
|
static std::uintptr_t lowerFree = 0x400;
|
||||||
|
static std::uintptr_t upperFree = 0x100000;
|
||||||
|
|
||||||
|
alignas(4096)
|
||||||
|
static std::array<PageDirectory, 1024> pageDirectory;
|
||||||
|
|
||||||
|
alignas(4096)
|
||||||
|
static std::array<std::uint32_t, 1024> pageTable;
|
||||||
|
|
||||||
|
void memory_initialize()
|
||||||
|
{
|
||||||
|
lowerMem -= 1024;
|
||||||
|
|
||||||
|
const auto totalKb = (lowerMem + upperMem) / 1024u;
|
||||||
|
|
||||||
|
term.write("Claiming ");
|
||||||
|
term.write(totalKb);
|
||||||
|
term.write(" kB for allocations...\n");
|
||||||
|
|
||||||
|
std::uint32_t addr = 0;
|
||||||
|
for (auto& p : pageTable) {
|
||||||
|
p = addr | 3; // supervisor, r/w, present
|
||||||
|
addr += 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageDirectory[0] = PageDirectory(pageTable.data());
|
||||||
|
|
||||||
|
asm volatile(R"(
|
||||||
|
mov %%eax, %%cr3
|
||||||
|
mov %%cr0, %%eax
|
||||||
|
or $0x80000000, %%eax
|
||||||
|
mov %%eax, %%cr0
|
||||||
|
)" :: "a"(pageDirectory.data()));
|
||||||
|
|
||||||
|
term.write("Paging enabled.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *memory_alloc(std::size_t size)
|
||||||
|
{
|
||||||
|
void *ret = nullptr;
|
||||||
|
|
||||||
|
if (lowerMem > size) {
|
||||||
|
ret = reinterpret_cast<void *>(lowerFree);
|
||||||
|
lowerFree += size;
|
||||||
|
lowerMem -= size;
|
||||||
|
} else if (upperMem > size) {
|
||||||
|
ret = reinterpret_cast<void *>(upperFree);
|
||||||
|
upperFree += size;
|
||||||
|
upperMem -= size;
|
||||||
|
} else {
|
||||||
|
// Uh oh!
|
||||||
|
term.write("!!! Kernel allocation failed !!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *operator new(std::size_t size)
|
||||||
|
{
|
||||||
|
return memory_alloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *operator new[](std::size_t size)
|
||||||
|
{
|
||||||
|
return memory_alloc(size);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
#ifndef MEMORY_HPP
|
||||||
|
#define MEMORY_HPP
|
||||||
|
|
||||||
|
void memory_initialize();
|
||||||
|
|
||||||
|
#endif // MEMORY_HPP
|
||||||
|
|
@ -0,0 +1,41 @@
|
|||||||
|
#include "textoutput.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
extern TextOutput& term;
|
||||||
|
|
||||||
|
std::uint32_t multiboot_magic;
|
||||||
|
std::uint32_t *multiboot_ptr;
|
||||||
|
|
||||||
|
std::uint32_t lowerMem = 0;
|
||||||
|
std::uint32_t upperMem = 0;
|
||||||
|
|
||||||
|
bool multiboot_initialize()
|
||||||
|
{
|
||||||
|
if (multiboot_magic != 0x36d76289) {
|
||||||
|
term.write("Not multiboot!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
term.write("Found multiboot headers: ");
|
||||||
|
|
||||||
|
auto ptr = multiboot_ptr + 2;
|
||||||
|
while (ptr[0] != 0 && ptr[1] != 8) {
|
||||||
|
term.write(ptr[0]);
|
||||||
|
term.write(", ");
|
||||||
|
|
||||||
|
if (ptr[0] == 4) {
|
||||||
|
lowerMem = ptr[2] * 1024;
|
||||||
|
upperMem = ptr[3] * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto next = reinterpret_cast<std::uintptr_t>(ptr);
|
||||||
|
next += ptr[1];
|
||||||
|
next = (next + 7) & ~7;
|
||||||
|
ptr = reinterpret_cast<std::uint32_t *>(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
term.write('\n');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
#ifndef MULTIBOOT_HPP
|
||||||
|
#define MULTIBOOT_HPP
|
||||||
|
|
||||||
|
bool multiboot_initialize();
|
||||||
|
|
||||||
|
#endif // MULTIBOOT_HPP
|
||||||
|
|
@ -0,0 +1,63 @@
|
|||||||
|
#include "pic.hpp"
|
||||||
|
|
||||||
|
#include "portio.hpp"
|
||||||
|
|
||||||
|
#define PIC1 0x20 /* IO base address for master PIC */
|
||||||
|
#define PIC2 0xA0 /* IO base address for slave PIC */
|
||||||
|
#define PIC1_COMMAND PIC1
|
||||||
|
#define PIC1_DATA (PIC1+1)
|
||||||
|
#define PIC2_COMMAND PIC2
|
||||||
|
#define PIC2_DATA (PIC2+1)
|
||||||
|
|
||||||
|
#define PIC_EOI 0x20 /* End-of-interrupt command code */
|
||||||
|
|
||||||
|
#define ICW1_ICW4 0x01 /* Indicates that ICW4 will be present */
|
||||||
|
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
|
||||||
|
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
|
||||||
|
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
|
||||||
|
#define ICW1_INIT 0x10 /* Initialization - required! */
|
||||||
|
|
||||||
|
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
|
||||||
|
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
|
||||||
|
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
|
||||||
|
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
|
||||||
|
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
|
||||||
|
|
||||||
|
void pic_initialize()
|
||||||
|
{
|
||||||
|
constexpr int offset1 = 0x20, offset2 = 0x28;
|
||||||
|
std::uint8_t a1, a2;
|
||||||
|
|
||||||
|
a1 = inb(PIC1_DATA); // save masks
|
||||||
|
a2 = inb(PIC2_DATA);
|
||||||
|
|
||||||
|
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode)
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
|
||||||
|
io_wait();
|
||||||
|
outb(PIC1_DATA, offset1); // ICW2: Master PIC vector offset
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_DATA, offset2); // ICW2: Slave PIC vector offset
|
||||||
|
io_wait();
|
||||||
|
outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010)
|
||||||
|
io_wait();
|
||||||
|
|
||||||
|
outb(PIC1_DATA, ICW4_8086); // ICW4: have the PICs use 8086 mode (and not 8080 mode)
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_DATA, ICW4_8086);
|
||||||
|
io_wait();
|
||||||
|
|
||||||
|
outb(PIC1_DATA, a1); // restore saved masks.
|
||||||
|
outb(PIC2_DATA, a2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pic_eoi(std::uint8_t irq)
|
||||||
|
{
|
||||||
|
if (irq >= 8)
|
||||||
|
outb(PIC2_COMMAND, PIC_EOI);
|
||||||
|
|
||||||
|
outb(PIC1_COMMAND, PIC_EOI);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef PIC_HPP
|
||||||
|
#define PIC_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/* reinitialize the PIC controllers, giving them specified vector offsets
|
||||||
|
rather than 8h and 70h, as configured by default */
|
||||||
|
void pic_initialize();
|
||||||
|
void pic_eoi(std::uint8_t irq);
|
||||||
|
|
||||||
|
#endif // PIC_HPP
|
||||||
|
|
@ -0,0 +1,39 @@
|
|||||||
|
#include "pit.hpp"
|
||||||
|
#include "idt.hpp"
|
||||||
|
#include "portio.hpp"
|
||||||
|
#include "tasking.hpp"
|
||||||
|
|
||||||
|
static volatile std::uint32_t ticks = 0;
|
||||||
|
|
||||||
|
static void timer_callback(const Registers& regs)
|
||||||
|
{
|
||||||
|
ticks = ticks + 1;
|
||||||
|
|
||||||
|
schedule(const_cast<Registers&>(regs));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pit_initialize(std::uint32_t frequency)
|
||||||
|
{
|
||||||
|
// Firstly, register our timer callback.
|
||||||
|
idt_register_callback(32, timer_callback);
|
||||||
|
|
||||||
|
// The value we send to the PIT is the value to divide it's input clock
|
||||||
|
// (1193180 Hz) by, to get our required frequency. Important to note is
|
||||||
|
// that the divisor must be small enough to fit into 16-bits.
|
||||||
|
auto divisor = 1193180 / frequency;
|
||||||
|
|
||||||
|
// Send the command byte.
|
||||||
|
outb(0x43, 0x36);
|
||||||
|
|
||||||
|
// Send the frequency divisor.
|
||||||
|
outb(0x40, divisor & 0xFF);
|
||||||
|
outb(0x40, (divisor >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pit_busy_wait(std::int32_t tks)
|
||||||
|
{
|
||||||
|
const auto end = ticks + tks;
|
||||||
|
while (end - ticks > 0)
|
||||||
|
asm volatile("nop");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef PIT_HPP
|
||||||
|
#define PIT_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
void pit_initialize(std::uint32_t frequency);
|
||||||
|
void pit_busy_wait(std::int32_t tks);
|
||||||
|
|
||||||
|
#endif // PIT_HPP
|
||||||
|
|
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef PORTIO_HPP
|
||||||
|
#define PORTIO_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
inline void outb(std::uint16_t port, std::uint8_t val)
|
||||||
|
{
|
||||||
|
asm volatile("outb %b0, %w1" :: "a"(val), "Nd"(port) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::uint8_t inb(std::uint16_t port)
|
||||||
|
{
|
||||||
|
std::uint8_t val;
|
||||||
|
asm volatile("inb %w1, %b0" : "=a"(val) : "Nd"(port) : "memory");
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void io_wait()
|
||||||
|
{
|
||||||
|
outb(0x80, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PORTIO_HPP
|
||||||
|
|
@ -0,0 +1,60 @@
|
|||||||
|
#include "tasking.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
struct Task
|
||||||
|
{
|
||||||
|
Registers regs;
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::array<Task, 4> tasks;
|
||||||
|
static int current = -1;
|
||||||
|
|
||||||
|
void schedule(Registers& regs)
|
||||||
|
{
|
||||||
|
if (current < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tasks[current].regs = regs;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (++current >= tasks.size())
|
||||||
|
current = 0;
|
||||||
|
} while (!tasks[current].valid);
|
||||||
|
|
||||||
|
regs = tasks[current].regs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tasking_initialize()
|
||||||
|
{
|
||||||
|
tasks[0].valid = true;
|
||||||
|
current = 0;
|
||||||
|
asm volatile("int $0x20");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tasking_spawn(void (*entry)(), unsigned ssize)
|
||||||
|
{
|
||||||
|
int i = -1;
|
||||||
|
for (i = 0; i < tasks.size(); ++i) {
|
||||||
|
if (!tasks[i].valid)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
tasks[i] = Task();
|
||||||
|
|
||||||
|
auto& r = tasks[i].regs;
|
||||||
|
auto stack = reinterpret_cast<std::uint32_t>(new std::uint8_t[ssize]);
|
||||||
|
r.ebp = stack + ssize;
|
||||||
|
r.esp = r.ebp;
|
||||||
|
r.eip = reinterpret_cast<std::uint32_t>(entry);
|
||||||
|
r.cs = 0x8;
|
||||||
|
r.eflags = tasks[current].regs.eflags;
|
||||||
|
|
||||||
|
tasks[i].valid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef TASKING_HPP
|
||||||
|
#define TASKING_HPP
|
||||||
|
|
||||||
|
#include "idt.hpp"
|
||||||
|
|
||||||
|
void tasking_initialize();
|
||||||
|
bool tasking_spawn(void (*entry)(), unsigned ssize);
|
||||||
|
|
||||||
|
void schedule(Registers& regs);
|
||||||
|
|
||||||
|
#endif // TASKING_HPP
|
||||||
|
|
@ -0,0 +1,44 @@
|
|||||||
|
#ifndef TEXTOUTPUT_HPP
|
||||||
|
#define TEXTOUTPUT_HPP
|
||||||
|
|
||||||
|
class TextOutput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void write(char c) noexcept = 0;
|
||||||
|
|
||||||
|
void write(const char *s) noexcept {
|
||||||
|
if (s) {
|
||||||
|
while (*s)
|
||||||
|
write(*s++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(int n) noexcept {
|
||||||
|
char buf[32];
|
||||||
|
auto ptr = buf + sizeof(buf);
|
||||||
|
|
||||||
|
*--ptr = '\0';
|
||||||
|
do {
|
||||||
|
*--ptr = "0123456789"[n % 10];
|
||||||
|
n /= 10;
|
||||||
|
} while (n);
|
||||||
|
|
||||||
|
write(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(unsigned n) noexcept {
|
||||||
|
char buf[32];
|
||||||
|
auto ptr = buf + sizeof(buf);
|
||||||
|
|
||||||
|
*--ptr = '\0';
|
||||||
|
do {
|
||||||
|
*--ptr = "0123456789"[n % 10];
|
||||||
|
n /= 10;
|
||||||
|
} while (n);
|
||||||
|
|
||||||
|
write(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TEXTOUTPUT_HPP
|
||||||
|
|
@ -0,0 +1,54 @@
|
|||||||
|
#include "portio.hpp"
|
||||||
|
#include "vgaterminal.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
void VGATerminal::write(char c) noexcept
|
||||||
|
{
|
||||||
|
switch (c) {
|
||||||
|
case '\n':
|
||||||
|
offset += Width;
|
||||||
|
[[fallthrough]];
|
||||||
|
case '\r':
|
||||||
|
offset -= offset % Width;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
checkpos();
|
||||||
|
put(c);
|
||||||
|
updatecursor();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VGATerminal::put(char c) noexcept
|
||||||
|
{
|
||||||
|
std::uint16_t cell = c
|
||||||
|
| (std::to_underlying(foreground) << 8)
|
||||||
|
| (std::to_underlying(background) << 12);
|
||||||
|
|
||||||
|
auto ptr = reinterpret_cast<std::uint16_t *>(Videoram);
|
||||||
|
ptr[offset++] = cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VGATerminal::checkpos() noexcept
|
||||||
|
{
|
||||||
|
if (offset >= Width * Height) {
|
||||||
|
auto ptr = reinterpret_cast<std::uint16_t *>(Videoram);
|
||||||
|
const auto end = ptr + Width * Height;
|
||||||
|
std::copy(ptr + Width, end, ptr);
|
||||||
|
std::fill(end - Width, end, 0);
|
||||||
|
offset = Width * Height - Width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VGATerminal::updatecursor() const noexcept
|
||||||
|
{
|
||||||
|
outb(0x03d4, 0x0f);
|
||||||
|
outb(0x03d5, static_cast<std::uint8_t>(offset));
|
||||||
|
outb(0x03d4, 0x0e);
|
||||||
|
outb(0x03d5, static_cast<std::uint8_t>(offset >> 8));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef VGATERMINAL_HPP
|
||||||
|
#define VGATERMINAL_HPP
|
||||||
|
|
||||||
|
#include "textoutput.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class VGATerminal : public TextOutput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class Color : std::uint8_t
|
||||||
|
{
|
||||||
|
Black = 0,
|
||||||
|
Blue,
|
||||||
|
Green,
|
||||||
|
Cyan,
|
||||||
|
Red,
|
||||||
|
Magenta,
|
||||||
|
Brown,
|
||||||
|
LightGray,
|
||||||
|
DarkGray,
|
||||||
|
LightBlue,
|
||||||
|
LightGreen,
|
||||||
|
LightCyan,
|
||||||
|
LightRed,
|
||||||
|
LightMagenta,
|
||||||
|
LightBrown,
|
||||||
|
White
|
||||||
|
};
|
||||||
|
|
||||||
|
using enum Color;
|
||||||
|
|
||||||
|
virtual void write(char c) noexcept final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr std::uintptr_t Videoram = 0xB8000;
|
||||||
|
static constexpr unsigned Width = 80;
|
||||||
|
static constexpr unsigned Height = 25;
|
||||||
|
|
||||||
|
unsigned offset = 0;
|
||||||
|
Color foreground = LightGray;
|
||||||
|
Color background = Black;
|
||||||
|
|
||||||
|
void put(char c) noexcept;
|
||||||
|
void checkpos() noexcept;
|
||||||
|
void updatecursor() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VGATERMINAL_HPP
|
||||||
|
|
Loading…
Reference in New Issue