first commit

boot ok
vga ok
console soon
interrupt mapping ok
isr triggers ok but doesn't iret

still lots to port from rust
This commit is contained in:
Jack Halford 2019-05-11 01:11:34 +02:00
commit 14ff4db74a
20 changed files with 1265 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
zig-cache

25
README.md Normal file
View file

@ -0,0 +1,25 @@
## hobby kernel in zig
slowly porting from rust.
### features
- vga frame buffer
- interrupts
- todo: terminal console
- todo: memory mapping
- todo: cfs scheduler
### dependencies
`ziglang` compiler
### compile
`zig build` compile and links a multiboot kernel, without a bootloader.
### test
`./run.sh qemu start`
`./run.sh qemu monitor`
`./run.sh gdb`

18
build.zig Normal file
View file

@ -0,0 +1,18 @@
const Builder = @import("std").build.Builder;
const builtin = @import("builtin");
pub fn build(b: *Builder) void {
const kernel = b.addExecutable("bzImage", "src/arch/x86/main.zig");
kernel.addPackagePath("kernel", "src/index.zig");
kernel.addPackagePath("arch", "src/arch/x86/lib/index.zig");
kernel.setOutputDir("build");
kernel.addAssemblyFile("src/arch/x86/gdt.s");
kernel.addAssemblyFile("src/arch/x86/isr.s");
kernel.setBuildMode(b.standardReleaseOptions());
kernel.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.none);
kernel.setLinkerScriptPath("src/arch/x86/linker.ld");
b.default_step.dependOn(&kernel.step);
}

38
run.sh Executable file
View file

@ -0,0 +1,38 @@
QEMU_SOCKET=/tmp/qemu.sock
QEMU_MONITOR="socat - unix-connect:${QEMU_SOCKET}"
QEMU_GDB_PORT=4242
KERNEL=build/bzImage
qemu() {
start() {
sudo qemu-system-i386\
-kernel ${KERNEL}\
-gdb tcp::${QEMU_GDB_PORT}\
-monitor unix:${QEMU_SOCKET},server,nowait\
-display curses\
-enable-kvm\
$*
}
monitor() {
sudo ${QEMU_MONITOR}
}
reload() {
echo "stop" | ${QEMU_MONITOR} &>/dev/null
echo "change ide1-cd0 ${KERNEL}" | ${QEMU_MONITOR} &>/dev/null
echo "system_reset" | ${QEMU_MONITOR} &>/dev/null
}
"$@"
}
gdb() {
gdb\
-q\
-symbols "${KERNEL}" \
-ex "target remote :${QEMU_GDB_PORT}"\
-ex "set arch i386"
}
"$@"

23
src/arch/x86/gdt.s Normal file
View file

@ -0,0 +1,23 @@
.type loadGDT, @function
//
// Load the GDT into the system registers.
//
// Arguments:
// gdtr: Pointer to the GDTR.
//
loadGDT:
mov +4(%esp), %eax // Fetch the gdtr parameter.
lgdt (%eax) // Load the new GDT.
// Reload data segments (GDT entry 2: kernel data).
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
// Reload code segment (GDT entry 1: kernel code).
ljmp $0x08, $1f
1: ret

133
src/arch/x86/gdt.zig Normal file
View file

@ -0,0 +1,133 @@
// const tty = @import("tty.zig");
const x86 = @import("lib/index.zig");
// GDT segment selectors.
pub const KERNEL_CODE = 0x08;
pub const KERNEL_DATA = 0x10;
pub const USER_CODE = 0x18;
pub const USER_DATA = 0x20;
pub const TSS_DESC = 0x28;
// Privilege level of segment selector.
pub const KERNEL_RPL = 0b00;
pub const USER_RPL = 0b11;
// Access byte values.
const KERNEL = 0x90;
const USER = 0xF0;
const CODE = 0x0A;
const DATA = 0x02;
const TSS_ACCESS = 0x89;
// Segment flags.
const PROTECTED = (1 << 2);
const BLOCKS_4K = (1 << 3);
// Structure representing an entry in the GDT.
const GDTEntry = packed struct {
limit_low: u16,
base_low: u16,
base_mid: u8,
access: u8,
limit_high: u4,
flags: u4,
base_high: u8,
};
// GDT descriptor register.
const GDTRegister = packed struct {
limit: u16,
base: *const GDTEntry,
};
// Task State Segment.
const TSS = packed struct {
unused1: u32,
esp0: u32, // Stack to use when coming to ring 0 from ring > 0.
ss0: u32, // Segment to use when coming to ring 0 from ring > 0.
unused2: [22]u32,
unused3: u16,
iomap_base: u16, // Base of the IO bitmap.
};
////
// Generate a GDT entry structure.
//
// Arguments:
// base: Beginning of the segment.
// limit: Size of the segment.
// access: Access byte.
// flags: Segment flags.
//
fn makeEntry(base: usize, limit: usize, access: u8, flags: u4) GDTEntry {
return GDTEntry{
.limit_low = @truncate(u16, limit),
.base_low = @truncate(u16, base),
.base_mid = @truncate(u8, base >> 16),
.access = @truncate(u8, access),
.limit_high = @truncate(u4, limit >> 16),
.flags = @truncate(u4, flags),
.base_high = @truncate(u8, base >> 24),
};
}
// Fill in the GDT.
var gdt align(4) = []GDTEntry{
makeEntry(0, 0, 0, 0),
makeEntry(0, 0xFFFFF, KERNEL | CODE, PROTECTED | BLOCKS_4K),
makeEntry(0, 0xFFFFF, KERNEL | DATA, PROTECTED | BLOCKS_4K),
makeEntry(0, 0xFFFFF, USER | CODE, PROTECTED | BLOCKS_4K),
makeEntry(0, 0xFFFFF, USER | DATA, PROTECTED | BLOCKS_4K),
makeEntry(0, 0, 0, 0), // TSS (fill in at runtime).
};
// GDT descriptor register pointing at the GDT.
var gdtr = GDTRegister{
.limit = u16(@sizeOf(@typeOf(gdt))),
.base = &gdt[0],
};
// Instance of the Task State Segment.
var tss = TSS{
.unused1 = 0,
.esp0 = undefined,
.ss0 = KERNEL_DATA,
.unused2 = []u32{0} ** 22,
.unused3 = 0,
.iomap_base = @sizeOf(TSS),
};
////
// Set the kernel stack to use when interrupting user mode.
//
// Arguments:
// esp0: Stack for Ring 0.
//
pub fn setKernelStack(esp0: usize) void {
tss.esp0 = esp0;
}
////
// Load the GDT into the system registers (defined in assembly).
//
// Arguments:
// gdtr: Pointer to the GDTR.
//
extern fn loadGDT(gdtr: *const GDTRegister) void;
////
// Initialize the Global Descriptor Table.
//
pub fn initialize() void {
// tty.step("Setting up the Global Descriptor Table");
// Initialize GDT.
loadGDT(&gdtr);
// Initialize TSS.
const tss_entry = makeEntry(@ptrToInt(&tss), @sizeOf(TSS) - 1, TSS_ACCESS, PROTECTED);
gdt[TSS_DESC / @sizeOf(GDTEntry)] = tss_entry;
x86.ltr(TSS_DESC);
// tty.stepOK();
}

58
src/arch/x86/idt.zig Normal file
View file

@ -0,0 +1,58 @@
// https://wiki.osdev.org/IDT
const x86 = @import("lib/index.zig");
const interrupt = @import("interrupt.zig");
const gdt = @import("gdt.zig");
// Types of gates.
pub const INTERRUPT_GATE = 0x8E;
pub const SYSCALL_GATE = 0xEE;
// Structure representing an entry in the IDT.
const IDTEntry = packed struct {
offset_low: u16,
selector: u16,
zero: u8,
flags: u8,
offset_high: u16,
};
// IDT descriptor register.
const IDTRegister = packed struct {
limit: u16,
base: *[256]IDTEntry,
};
// Interrupt Descriptor Table.
var idt: [256]IDTEntry = undefined;
// IDT descriptor register pointing at the IDT.
const idtr = IDTRegister{
.limit = u16(@sizeOf(@typeOf(idt))),
.base = &idt,
};
////
// Setup an IDT entry.
//
// Arguments:
// n: Index of the gate.
// flags: Type and attributes.
// offset: Address of the ISR.
//
pub fn setGate(n: u8, flags: u8, offset: extern fn () void) void {
const intOffset = @ptrToInt(offset);
idt[n].offset_low = @truncate(u16, intOffset);
idt[n].offset_high = @truncate(u16, intOffset >> 16);
idt[n].flags = flags;
idt[n].zero = 0;
idt[n].selector = gdt.KERNEL_CODE;
}
////
// Initialize the Interrupt Descriptor Table.
//
pub fn initialize() void {
interrupt.initialize();
x86.lidt(@ptrToInt(&idtr));
}

188
src/arch/x86/interrupt.zig Normal file
View file

@ -0,0 +1,188 @@
const x86 = @import("lib/index.zig");
const isr = @import("isr.zig");
// PIC ports.
const PIC1_CMD = 0x20;
const PIC1_DATA = 0x21;
const PIC2_CMD = 0xA0;
const PIC2_DATA = 0xA1;
// PIC commands:
const ISR_READ = 0x0B; // Read the In-Service Register.
const ACK = 0x20; // Acknowledge interrupt.
// Initialization Control Words commands.
const ICW1_INIT = 0x10;
const ICW1_ICW4 = 0x01;
const ICW4_8086 = 0x01;
// write 0 to wait
const WAIT_PORT = 0x80;
// Interrupt Vector offsets of exceptions.
const EXCEPTION_0 = 0;
const EXCEPTION_31 = EXCEPTION_0 + 31;
// Interrupt Vector offsets of IRQs.
const IRQ_0 = EXCEPTION_31 + 1;
const IRQ_15 = IRQ_0 + 15;
// Interrupt Vector offsets of syscalls.
const SYSCALL = 128;
// Registered interrupt handlers. (see isr.s)
var handlers = []fn () void{unhandled} ** 48;
// Registered IRQ subscribers. (see isr.s)
var irq_subscribers = []MailboxId{MailboxId.Kernel} ** 16;
fn unhandled() noreturn {
const n = isr.context.interrupt_n;
// if (n >= IRQ_0) {
// tty.panic("unhandled IRQ number {d}", n - IRQ_0);
// } else {
// tty.panic("unhandled exception number {d}", n);
// }
x86.hang();
}
inline fn picwait() void {
x86.outb(WAIT_PORT, 0);
}
////
// Call the correct handler based on the interrupt number.
//
export fn interruptDispatch() void {
const n = @intCast(u8, isr.context.interrupt_n);
switch (n) {
// Exceptions.
EXCEPTION_0...EXCEPTION_31 => {
handlers[n]();
},
// IRQs.
IRQ_0...IRQ_15 => {
const irq = n - IRQ_0;
// if (spuriousIRQ(irq)) return;
startOfInterrupt(irq);
handlers[n]();
endOfInterrupt(irq);
},
// Syscalls.
// SYSCALL => {
// const syscall_n = isr.context.registers.eax;
// if (syscall_n < syscall.handlers.len) {
// syscall.handlers[syscall_n]();
// } else {
// syscall.invalid();
// }
// },
else => unreachable,
}
// If no user thread is ready to run, halt here and wait for interrupts.
// if (scheduler.current() == null) {
// x86.sti();
// x86.hlt();
// }
}
inline fn spuriousIRQ(irq: u8) bool {
// Only IRQ 7 and IRQ 15 can be spurious.
if (irq != 7) return false;
// TODO: handle spurious IRQ15.
// Read the value of the In-Service Register.
x86.outb(PIC1_CMD, ISR_READ);
const in_service = x86.inb(PIC1_CMD);
// Verify whether IRQ7 is set in the ISR.
return (in_service & (1 << 7)) == 0;
}
inline fn startOfInterrupt(irq: u8) void {
// mask the irq and then ACK
if (irq >= 8) {
maskIRQ(irq, true);
x86.outb(PIC1_CMD, ACK);
x86.outb(PIC2_CMD, ACK);
} else {
maskIRQ(irq, true);
x86.outb(PIC1_CMD, ACK);
}
}
inline fn endOfInterrupt(irq: u8) void {
// unmask the irq and then ACK
if (irq >= 8) {
maskIRQ(irq, false);
x86.outb(PIC2_CMD, ACK);
} else {
maskIRQ(irq, false);
x86.outb(PIC1_CMD, ACK);
}
}
pub fn register(n: u8, handler: fn () void) void {
handlers[n] = handler;
}
pub fn registerIRQ(irq: u8, handler: fn () void) void {
register(IRQ_0 + irq, handler);
maskIRQ(irq, false); // Unmask the IRQ.
}
fn remapPIC() void {
// ICW1: start initialization sequence.
x86.outb(PIC1_CMD, ICW1_INIT | ICW1_ICW4);
picwait();
x86.outb(PIC2_CMD, ICW1_INIT | ICW1_ICW4);
picwait();
// ICW2: Interrupt Vector offsets of IRQs.
x86.outb(PIC1_DATA, IRQ_0); // IRQ 0..7 -> Interrupt 32..39
picwait();
x86.outb(PIC2_DATA, IRQ_0 + 8); // IRQ 8..15 -> Interrupt 40..47
picwait();
// ICW3: IRQ line 2 to connect master to slave PIC.
x86.outb(PIC1_DATA, 1 << 2);
picwait();
x86.outb(PIC2_DATA, 2);
picwait();
// ICW4: 80x86 mode.
x86.outb(PIC1_DATA, ICW4_8086);
picwait();
x86.outb(PIC2_DATA, ICW4_8086);
picwait();
// Mask all IRQs.
x86.outb(PIC1_DATA, 0xFF);
picwait();
x86.outb(PIC2_DATA, 0xFF);
picwait();
}
pub fn maskIRQ(irq: u8, mask: bool) void {
if (irq > 15) {
return;
}
// Figure out if master or slave PIC owns the IRQ.
const port = if (irq < 8) u16(PIC1_DATA) else u16(PIC2_DATA);
const old = x86.inb(port); // Retrieve the current mask.
// Mask or unmask the interrupt.
const shift = @intCast(u3, irq % 8); // TODO: waiting for Andy to fix this.
if (mask) {
x86.outb(port, old | (u8(1) << shift));
} else {
x86.outb(port, old & ~(u8(1) << shift));
}
}
////
// Initialize interrupts.
//
pub fn initialize() void {
remapPIC();
isr.install();
}

102
src/arch/x86/isr.s Normal file
View file

@ -0,0 +1,102 @@
// Kernel stack for interrupt handling.
KERNEL_STACK = 0x80000
// GDT selectors.
KERNEL_DS = 0x10
USER_DS = 0x23
// Template for the Interrupt Service Routines.
.macro isrGenerate n
.align 4
.type isr\n, @function
isr\n:
// Push a dummy error code for interrupts that don't have one.
.if (\n != 8 && !(\n >= 10 && \n <= 14) && \n != 17)
push $0
.endif
push $\n // Push the interrupt number.
jmp isrCommon // Jump to the common handler.
.endmacro
// Common code for all Interrupt Service Routines.
isrCommon:
pusha // Save the registers state.
// Setup kernel data segment.
mov $KERNEL_DS, %ax
mov %ax, %ds
mov %ax, %es
// Save the pointer to the current context and switch to the kernel stack.
mov %esp, context
mov $KERNEL_STACK, %esp
call interruptDispatch // Handle the interrupt event.
// Restore the pointer to the context (of a different thread, potentially).
mov context, %esp
// Setup user data segment.
mov $USER_DS, %ax
mov %ax, %ds
mov %ax, %es
popa // Restore the registers state.
add $8, %esp // Remove interrupt number and error code from stack.
iret
.type isrCommon, @function
// Exceptions.
isrGenerate 0
isrGenerate 1
isrGenerate 2
isrGenerate 3
isrGenerate 4
isrGenerate 5
isrGenerate 6
isrGenerate 7
isrGenerate 8
isrGenerate 9
isrGenerate 10
isrGenerate 11
isrGenerate 12
isrGenerate 13
isrGenerate 14
isrGenerate 15
isrGenerate 16
isrGenerate 17
isrGenerate 18
isrGenerate 19
isrGenerate 20
isrGenerate 21
isrGenerate 22
isrGenerate 23
isrGenerate 24
isrGenerate 25
isrGenerate 26
isrGenerate 27
isrGenerate 28
isrGenerate 29
isrGenerate 30
isrGenerate 31
// IRQs.
isrGenerate 32
isrGenerate 33
isrGenerate 34
isrGenerate 35
isrGenerate 36
isrGenerate 37
isrGenerate 38
isrGenerate 39
isrGenerate 40
isrGenerate 41
isrGenerate 42
isrGenerate 43
isrGenerate 44
isrGenerate 45
isrGenerate 46
isrGenerate 47
// Syscalls.
isrGenerate 128

158
src/arch/x86/isr.zig Normal file
View file

@ -0,0 +1,158 @@
const idt = @import("idt.zig");
// Interrupt Service Routines defined externally in assembly.
extern fn isr0() void;
extern fn isr1() void;
extern fn isr2() void;
extern fn isr3() void;
extern fn isr4() void;
extern fn isr5() void;
extern fn isr6() void;
extern fn isr7() void;
extern fn isr8() void;
extern fn isr9() void;
extern fn isr10() void;
extern fn isr11() void;
extern fn isr12() void;
extern fn isr13() void;
extern fn isr14() void;
extern fn isr15() void;
extern fn isr16() void;
extern fn isr17() void;
extern fn isr18() void;
extern fn isr19() void;
extern fn isr20() void;
extern fn isr21() void;
extern fn isr22() void;
extern fn isr23() void;
extern fn isr24() void;
extern fn isr25() void;
extern fn isr26() void;
extern fn isr27() void;
extern fn isr28() void;
extern fn isr29() void;
extern fn isr30() void;
extern fn isr31() void;
extern fn isr32() void;
extern fn isr33() void;
extern fn isr34() void;
extern fn isr35() void;
extern fn isr36() void;
extern fn isr37() void;
extern fn isr38() void;
extern fn isr39() void;
extern fn isr40() void;
extern fn isr41() void;
extern fn isr42() void;
extern fn isr43() void;
extern fn isr44() void;
extern fn isr45() void;
extern fn isr46() void;
extern fn isr47() void;
extern fn isr128() void;
// Context saved by Interrupt Service Routines.
pub const Context = packed struct {
registers: Registers, // General purpose registers.
interrupt_n: u32, // Number of the interrupt.
error_code: u32, // Associated error code (or 0).
// CPU status:
eip: u32,
cs: u32,
eflags: u32,
esp: u32,
ss: u32,
pub inline fn setReturnValue(self: *volatile Context, value: var) void {
self.registers.eax = if (@typeOf(value) == bool) @boolToInt(value) else @intCast(u32, value);
}
};
// Structure holding general purpose registers as saved by PUSHA.
pub const Registers = packed struct {
edi: u32,
esi: u32,
ebp: u32,
esp: u32,
ebx: u32,
edx: u32,
ecx: u32,
eax: u32,
pub fn init() Registers {
return Registers{
.edi = 0,
.esi = 0,
.ebp = 0,
.esp = 0,
.ebx = 0,
.edx = 0,
.ecx = 0,
.eax = 0,
};
}
};
// Pointer to the current saved context.
pub export var context: *volatile Context = undefined;
////
// Install the Interrupt Service Routines in the IDT.
//
pub fn install() void {
// Exceptions.
idt.setGate(0, idt.INTERRUPT_GATE, isr0);
idt.setGate(1, idt.INTERRUPT_GATE, isr1);
idt.setGate(2, idt.INTERRUPT_GATE, isr2);
idt.setGate(3, idt.INTERRUPT_GATE, isr3);
idt.setGate(4, idt.INTERRUPT_GATE, isr4);
idt.setGate(5, idt.INTERRUPT_GATE, isr5);
idt.setGate(6, idt.INTERRUPT_GATE, isr6);
idt.setGate(7, idt.INTERRUPT_GATE, isr7);
idt.setGate(8, idt.INTERRUPT_GATE, isr8);
idt.setGate(9, idt.INTERRUPT_GATE, isr9);
idt.setGate(10, idt.INTERRUPT_GATE, isr10);
idt.setGate(11, idt.INTERRUPT_GATE, isr11);
idt.setGate(12, idt.INTERRUPT_GATE, isr12);
idt.setGate(13, idt.INTERRUPT_GATE, isr13);
idt.setGate(14, idt.INTERRUPT_GATE, isr14);
idt.setGate(15, idt.INTERRUPT_GATE, isr15);
idt.setGate(16, idt.INTERRUPT_GATE, isr16);
idt.setGate(17, idt.INTERRUPT_GATE, isr17);
idt.setGate(18, idt.INTERRUPT_GATE, isr18);
idt.setGate(19, idt.INTERRUPT_GATE, isr19);
idt.setGate(20, idt.INTERRUPT_GATE, isr20);
idt.setGate(21, idt.INTERRUPT_GATE, isr21);
idt.setGate(22, idt.INTERRUPT_GATE, isr22);
idt.setGate(23, idt.INTERRUPT_GATE, isr23);
idt.setGate(24, idt.INTERRUPT_GATE, isr24);
idt.setGate(25, idt.INTERRUPT_GATE, isr25);
idt.setGate(26, idt.INTERRUPT_GATE, isr26);
idt.setGate(27, idt.INTERRUPT_GATE, isr27);
idt.setGate(28, idt.INTERRUPT_GATE, isr28);
idt.setGate(29, idt.INTERRUPT_GATE, isr29);
idt.setGate(30, idt.INTERRUPT_GATE, isr30);
idt.setGate(31, idt.INTERRUPT_GATE, isr31);
// IRQs.
idt.setGate(32, idt.INTERRUPT_GATE, isr32);
idt.setGate(33, idt.INTERRUPT_GATE, isr33);
idt.setGate(34, idt.INTERRUPT_GATE, isr34);
idt.setGate(35, idt.INTERRUPT_GATE, isr35);
idt.setGate(36, idt.INTERRUPT_GATE, isr36);
idt.setGate(37, idt.INTERRUPT_GATE, isr37);
idt.setGate(38, idt.INTERRUPT_GATE, isr38);
idt.setGate(39, idt.INTERRUPT_GATE, isr39);
idt.setGate(40, idt.INTERRUPT_GATE, isr40);
idt.setGate(41, idt.INTERRUPT_GATE, isr41);
idt.setGate(42, idt.INTERRUPT_GATE, isr42);
idt.setGate(43, idt.INTERRUPT_GATE, isr43);
idt.setGate(44, idt.INTERRUPT_GATE, isr44);
idt.setGate(45, idt.INTERRUPT_GATE, isr45);
idt.setGate(46, idt.INTERRUPT_GATE, isr46);
idt.setGate(47, idt.INTERRUPT_GATE, isr47);
// Syscalls.
idt.setGate(128, idt.SYSCALL_GATE, isr128);
}

View file

@ -0,0 +1,2 @@
use @import("io.zig");
use @import("instructions.zig");

View file

@ -0,0 +1,39 @@
////
// Load a new Task Register.
//
// Arguments:
// desc: Segment selector of the TSS.
//
pub inline fn ltr(desc: u16) void {
asm volatile ("ltr %[desc]"
:
: [desc] "r" (desc)
);
}
////
// Completely stop the computer.
//
pub inline fn hang() noreturn {
asm volatile ("cli");
while (true) {
asm volatile ("hlt");
}
}
pub inline fn sti() void {
asm volatile ("sti");
}
////
// Load a new Interrupt Descriptor Table.
//
// Arguments:
// idtr: Address of the IDTR register.
//
pub inline fn lidt(idtr: usize) void {
asm volatile ("lidt (%[idtr])"
:
: [idtr] "r" (idtr)
);
}

30
src/arch/x86/lib/io.zig Normal file
View file

@ -0,0 +1,30 @@
////
// Read a byte from a port.
//
// Arguments:
// port: Port from where to read.
//
// Returns:
// The read byte.
//
pub inline fn inb(port: u16) u8 {
return asm volatile ("inb %[port], %[result]"
: [result] "={al}" (-> u8)
: [port] "N{dx}" (port)
);
}
////
// Write a byte on a port.
//
// Arguments:
// port: Port where to write the value.
// value: Value to be written.
//
pub inline fn outb(port: u16, value: u8) void {
asm volatile ("outb %[value], %[port]"
:
: [value] "{al}" (value),
[port] "N{dx}" (port)
);
}

80
src/arch/x86/linker.ld Normal file
View file

@ -0,0 +1,80 @@
ENTRY(_start)
/* OUTPUT_FORMAT(elf32-i386) */
SECTIONS {
/* VGA, cannot use section for this */
VGA_PTR = 0xb8000;
. = 0xb8000;
. += 80 * 25 * 2;
. = 1M;
/* ensure that the multiboot header is at the beginning */
.multiboot :
{
/* KEEP otherwise it gets garbage collected by linker */
KEEP(*(.multiboot))
. = ALIGN(4K);
}
.text :
{
*(.text .text.*)
. = ALIGN(4K);
}
.rodata :
{
*(.rodata .rodata.*)
. = ALIGN(4K);
}
.data :
{
*(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*) *(.data.*)
. = ALIGN(4K);
}
/* NOT A GOOD IDEA TO GROUP debug_* SYMBOLS ! */
/* .debug : */
/* { */
/* /1* KEEP(*(.debug_*)) *1/ */
/* *(.debug_*) */
/* . = ALIGN(4K); */
/* } */
.gdt :
{
*(.gdt)
. = ALIGN(4K);
}
.got :
{
*(.got)
. = ALIGN(4K);
}
.got.plt :
{
*(.got.plt)
. = ALIGN(4K);
}
.bss :
{
*(.bss .bss.*)
. = ALIGN(4K);
}
/* .stab : */
/* { */
/* KEEP(*(.stab)) */
/* . = ALIGN(4K); */
/* } */
/* .stabstr : */
/* { */
/* KEEP(*(.stabstr)) */
/* . = ALIGN(4K); */
/* } */
}

13
src/arch/x86/main.zig Normal file
View file

@ -0,0 +1,13 @@
use @import("kernel").main;
const idt = @import("idt.zig");
const gdt = @import("gdt.zig");
const x86 = @import("lib/index.zig");
/// x86 specific intialization
/// first entry point (see linker.ld)
export nakedcc fn _start() noreturn {
gdt.initialize();
idt.initialize();
x86.sti();
kmain();
}

15
src/console.zig Normal file
View file

@ -0,0 +1,15 @@
const interrupt = @import("arch/x86/interrupt.zig");
use @import("vga.zig");
var vga = VGA.init(VRAM_ADDR);
pub fn keyboard_handler() void {
vga.writeString("hello");
}
pub fn initialize() void {
vga.clear();
vga.writeString("zzzzzzzzzzzzzzzz");
interrupt.registerIRQ(1, keyboard_handler);
vga.writeString("aaaa");
}

2
src/index.zig Normal file
View file

@ -0,0 +1,2 @@
pub const vga = @import("vga.zig");
pub const main = @import("main.zig");

9
src/main.zig Normal file
View file

@ -0,0 +1,9 @@
use @import("multiboot.zig");
const console = @import("console.zig");
const arch = @import("arch/x86/lib/index.zig");
// platform independant initialization
pub fn kmain() noreturn {
console.initialize();
while (true) {}
}

128
src/multiboot.zig Normal file
View file

@ -0,0 +1,128 @@
// zig fmt: off
const tty = @import("tty.zig");
const cstr = @import("std").cstr;
const Process = @import("process.zig").Process;
// This should be in EAX.
pub const MULTIBOOT_BOOTLOADER_MAGIC = 0x2BADB002;
// Is there basic lower/upper memory information?
pub const MULTIBOOT_INFO_MEMORY = 0x00000001;
// Is there a full memory map?
pub const MULTIBOOT_INFO_MEM_MAP = 0x00000040;
// System information structure passed by the bootloader.
pub const MultibootInfo = packed struct {
// Multiboot info version number.
flags: u32,
// Available memory from BIOS.
mem_lower: u32,
mem_upper: u32,
// "root" partition.
boot_device: u32,
// Kernel command line.
cmdline: u32,
// Boot-Module list.
mods_count: u32,
mods_addr: u32,
// TODO: use the real types here.
u: u128,
// Memory Mapping buffer.
mmap_length: u32,
mmap_addr: u32,
// Drive Info buffer.
drives_length: u32,
drives_addr: u32,
// ROM configuration table.
config_table: u32,
// Boot Loader Name.
boot_loader_name: u32,
// APM table.
apm_table: u32,
// Video.
vbe_control_info: u32,
vbe_mode_info: u32,
vbe_mode: u16,
vbe_interface_seg: u16,
vbe_interface_off: u16,
vbe_interface_len: u16,
////
// Return the ending address of the last module.
//
pub fn lastModuleEnd(self: *const MultibootInfo) usize {
const mods = @intToPtr([*]MultibootModule, self.mods_addr);
return mods[self.mods_count - 1].mod_end;
}
////
// Load all the modules passed by the bootloader.
//
pub fn loadModules(self: *const MultibootInfo) void {
const mods = @intToPtr([*]MultibootModule, self.mods_addr)[0..self.mods_count];
for (mods) |mod| {
const cmdline = cstr.toSlice(@intToPtr([*]u8, mod.cmdline));
tty.step("Loading \"{}\"", cmdline);
_ = Process.create(mod.mod_start, null);
// TODO: deallocate the original memory.
tty.stepOK();
}
}
};
// Types of memory map entries.
pub const MULTIBOOT_MEMORY_AVAILABLE = 1;
pub const MULTIBOOT_MEMORY_RESERVED = 2;
// Entries in the memory map.
pub const MultibootMMapEntry = packed struct {
size: u32,
addr: u64,
len: u64,
type: u32,
};
pub const MultibootModule = packed struct {
// The memory used goes from bytes 'mod_start' to 'mod_end-1' inclusive.
mod_start: u32,
mod_end: u32,
cmdline: u32, // Module command line.
pad: u32, // Padding to take it to 16 bytes (must be zero).
};
// Multiboot structure to be read by the bootloader.
const MultibootHeader = packed struct {
magic: u32, // Must be equal to header magic number.
flags: u32, // Feature flags.
checksum: u32, // Above fields plus this one must equal 0 mod 2^32.
};
// NOTE: this structure is incomplete.
// Place the header at the very beginning of the binary.
export const multiboot_header align(4) linksection(".multiboot") = multiboot: {
const MAGIC = u32(0x1BADB002); // Magic number for validation.
const ALIGN = u32(1 << 0); // Align loaded modules.
const MEMINFO = u32(1 << 1); // Receive a memory map from the bootloader.
const FLAGS = ALIGN | MEMINFO; // Combine the flags.
break :multiboot MultibootHeader {
.magic = MAGIC,
.flags = FLAGS,
.checksum = ~(MAGIC +% FLAGS) +% 1,
};
};

203
src/vga.zig Normal file
View file

@ -0,0 +1,203 @@
const builtin = @import("builtin");
const mem = @import("std").mem;
const arch = @import("arch/x86/lib/index.zig");
// VRAM buffer address in physical memory.
pub const VRAM_ADDR = 0xB8000;
pub const VRAM_SIZE = 0x8000;
// Screen size.
pub const VGA_WIDTH = 80;
pub const VGA_HEIGHT = 25;
pub const VGA_SIZE = VGA_WIDTH * VGA_HEIGHT;
// Color codes.
pub const Color = enum(u4) {
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Magenta = 5,
Brown = 6,
LightGrey = 7,
DarkGrey = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
LightMagenta = 13,
LightBrown = 14,
White = 15,
};
// Character with attributes.
pub const VGAEntry = packed struct {
char: u8,
foreground: Color,
background: Color,
};
////
// Enable hardware cursor.
//
pub fn enableCursor() void {
outb(0x3D4, 0x0A);
outb(0x3D5, 0x00);
}
////
// Disable hardware cursor.
//
pub fn disableCursor() void {
outb(0x3D4, 0x0A);
outb(0x3D5, 1 << 5);
}
// VGA status.
pub const VGA = struct {
vram: []VGAEntry,
cursor: usize,
foreground: Color,
background: Color,
////
// Initialize the VGA status.
//
// Arguments:
// vram: The address of the VRAM buffer.
//
// Returns:
// A structure holding the VGA status.
//
pub fn init(vram: usize) VGA {
return VGA{
.vram = @intToPtr([*]VGAEntry, vram)[0..0x4000],
.cursor = 0,
.foreground = Color.Black,
.background = Color.Brown,
};
}
////
// Clear the screen.
//
pub fn clear(self: *VGA) void {
mem.set(VGAEntry, self.vram[0..VGA_SIZE], self.entry(' '));
self.cursor = 0;
self.updateCursor();
}
////
// Print a character to the screen.
//
// Arguments:
// char: Character to be printed.
//
fn writeChar(self: *VGA, char: u8) void {
if (self.cursor == VGA_WIDTH * VGA_HEIGHT - 1) {
self.scrollDown();
}
switch (char) {
// Newline.
'\n' => {
self.writeChar(' ');
while (self.cursor % VGA_WIDTH != 0)
self.writeChar(' ');
},
// Tab.
'\t' => {
self.writeChar(' ');
while (self.cursor % 4 != 0)
self.writeChar(' ');
},
// Backspace.
// FIXME: hardcoded 8 here is horrible.
8 => {
self.cursor -= 1;
self.writeChar(' ');
self.cursor -= 1;
},
// Any other character.
else => {
self.vram[self.cursor] = self.entry(char);
self.cursor += 1;
},
}
}
////
// Print a string to the screen.
//
// Arguments:
// string: String to be printed.
//
pub fn writeString(self: *VGA, string: []const u8) void {
for (string) |char| {
self.writeChar(char);
}
self.updateCursor();
}
////
// Scroll the screen one line down.
//
fn scrollDown(self: *VGA) void {
const first = VGA_WIDTH; // Index of first line.
const last = VGA_SIZE - VGA_WIDTH; // Index of last line.
// Copy all the screen (apart from the first line) up one line.
mem.copy(VGAEntry, self.vram[0..last], self.vram[first..VGA_SIZE]);
// Clean the last line.
mem.set(VGAEntry, self.vram[last..VGA_SIZE], self.entry(' '));
// Bring the cursor back to the beginning of the last line.
self.cursor -= VGA_WIDTH;
}
////
// Update the position of the hardware cursor.
// Use the software cursor as the source of truth.
//
fn updateCursor(self: *const VGA) void {
arch.outb(0x3D4, 0x0F);
arch.outb(0x3D5, @truncate(u8, self.cursor));
arch.outb(0x3D4, 0x0E);
arch.outb(0x3D5, @truncate(u8, self.cursor >> 8));
}
////
// Update the position of the software cursor.
// Use the hardware cursor as the source of truth.
//
pub fn fetchCursor(self: *VGA) void {
var cursor: usize = 0;
arch.outb(0x3D4, 0x0E);
cursor |= usize(arch.inb(0x3D5)) << 8;
arch.outb(0x3D4, 0x0F);
cursor |= arch.inb(0x3D5);
self.cursor = cursor;
}
////
// Build a VGAEntry with current foreground and background.
//
// Arguments:
// char: The character of the entry.
//
// Returns:
// The requested VGAEntry.
//
fn entry(self: *VGA, char: u8) VGAEntry {
return VGAEntry{
.char = char,
.foreground = self.foreground,
.background = self.background,
};
}
};