Ready. Set. Go!

Microkernel development in Zig, should be fun! =]
This commit is contained in:
Gabriel 2025-02-13 22:39:48 +01:00
commit 13ec4bee87
34 changed files with 1754 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
**/.zig-cache
boot/core
boot/init
tools/bin/
tools/include/
tools/share/
astryon.iso

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "easyboot"]
path = easyboot
url = https://gitlab.com/bztsrc/easyboot

8
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"editor.formatOnSave": true,
"editor.tabSize": 4,
"files.trimFinalNewlines": true,
"files.insertFinalNewline": true,
"git.inputValidationLength": 72,
"git.inputValidationSubjectLength": 72,
}

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2025, asleepymoon.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

66
README.md Normal file
View File

@ -0,0 +1,66 @@
# Astryon - a microkernel-based operating system project in Zig
Note: not guaranteed to be the project's final name.
## Goals
This project is in its very early stages, so don't expect much yet.
I've started this project to try something new in the world of OS development, after working on a classic monolithic system for some time. I've been wanting to make a microkernel-based system for a while. I've also been wanting to try out Zig, so this was a perfect opportunity to combine both.
- [x] Fully written in Zig
- [ ] Simple microkernel that only manages memory, scheduling, and basic IPC (in progress)
- [ ] IPC system using shared memory ring buffers
- [ ] Init process that can load other services and connect processes to each other (sort of like dbus)
- [ ] Permission manager, VFS system, etc... all in userspace
- [ ] Decently POSIX-compatible (with a compatibility layer and libc)
- [ ] Window server and GUI system
- [ ] Sandbox most regular userspace processes for security
## Setup
Install [Zig](https://ziglang.org/), version `0.13.0`.
When cloning the repo, make sure to use `git clone --recursive` or run `git submodule update --init` after cloning.
If done correctly, you should have the bootloader cloned as a submodule in the `easyboot` folder. Extract `easyboot/distrib/easyboot-x86_64-linux.tgz` into the `tools` folder (Linux only).
The directory tree should look like this:
```tools
- bin
- include
- share
iso.sh
run.sh
...
```
On other operating systems, you're going to have to build the bootloader manually.
## Building
Simply run `zig build -p .`
Built binaries will end up in `base/usr/bin`, with the exception of the kernel and core modules, which will be installed in `boot`.
## Running
### Creating the image
Use `tools/iso.sh` to generate an ISO image containing the previously built binaries.
This script assumes that you have the easyboot tool installed at `tools/bin/easyboot`. If this is not the case, you'll have to run easyboot manually. Here's the command:
`/path/to/easyboot -e boot astryon.iso`
### Running the image in QEMU
Then, to run the image in QEMU, you can use the convenience script `tools/run.sh` or run the following command:
`qemu-system-x86_64 -cdrom astryon.iso -serial stdio -enable-kvm`
If you prefer another virtualization system (like Oracle VirtualBox or VMWare), simply import `astryon.iso` into it. Keep in mind you're going to have to do this every time you build a new image.
## License
The bootloader, `easyboot` by [bzt](https://gitlab.com/bztsrc/), is licensed under the GPLv3+ [LICENSE](easyboot/LICENSE).
The Astryon operating system is licensed under the BSD-2-Clause [LICENSE](LICENSE).

7
boot/easyboot/menu.cfg Normal file
View File

@ -0,0 +1,7 @@
framebuffer 800 600 32
default 1 1000
menuentry Astryon default
kernel core
module init

14
build.zig Normal file
View File

@ -0,0 +1,14 @@
const std = @import("std");
const core = @import("core/build.zig");
const system = @import("system/build.zig");
pub fn build(b: *std.Build) void {
const build_step = b.step("all", "Build and install everything");
const optimize = b.standardOptimizeOption(.{});
core.build(b, build_step, optimize);
system.build(b, build_step, optimize);
b.default_step = build_step;
}

46
core/build.zig Normal file
View File

@ -0,0 +1,46 @@
const std = @import("std");
const here = "core";
pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
var disabled_features = std.Target.Cpu.Feature.Set.empty;
var enabled_features = std.Target.Cpu.Feature.Set.empty;
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.mmx));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.sse));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.sse2));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.avx));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.avx2));
enabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.soft_float));
const target_query = std.Target.Query{
.cpu_arch = std.Target.Cpu.Arch.x86_64,
.os_tag = std.Target.Os.Tag.freestanding,
.abi = std.Target.Abi.none,
.cpu_features_sub = disabled_features,
.cpu_features_add = enabled_features,
};
const core = b.addExecutable(.{
.name = "core",
.root_source_file = b.path(here ++ "/src/main.zig"),
.target = b.resolveTargetQuery(target_query),
.optimize = optimize,
.code_model = .kernel,
});
core.addIncludePath(b.path(here ++ "/../easyboot/"));
core.setLinkerScript(b.path(here ++ "/src/link.ld"));
const install = b.addInstallArtifact(core, .{
.dest_dir = .{
.override = .{ .custom = "boot/" },
},
});
var kernel_step = b.step("core", "Build the core microkernel");
kernel_step.dependOn(&core.step);
kernel_step.dependOn(&install.step);
build_step.dependOn(kernel_step);
}

35
core/src/arch/debug.zig Normal file
View File

@ -0,0 +1,35 @@
const std = @import("std");
const target = @import("builtin").target;
const arch = switch (target.cpu.arch) {
.x86_64 => @import("x86_64/debug.zig"),
else => {
@compileError("unsupported architecture");
},
};
const DebugWriter = struct {
const Writer = std.io.Writer(
*DebugWriter,
error{},
write,
);
fn write(
_: *DebugWriter,
data: []const u8,
) error{}!usize {
return arch.write(data);
}
fn writer(self: *DebugWriter) Writer {
return .{ .context = self };
}
};
/// Print a formatted string to the platform's debug output.
pub fn print(comptime fmt: []const u8, args: anytype) void {
var debug_writer = DebugWriter{};
var writer = debug_writer.writer();
writer.print(fmt, args) catch return;
}

View File

@ -0,0 +1,9 @@
const std = @import("std");
const target = @import("builtin").target;
pub const arch = switch (target.cpu.arch) {
.x86_64 => @import("x86_64/interrupts.zig"),
else => {
@compileError("unsupported architecture");
},
};

View File

@ -0,0 +1,9 @@
const std = @import("std");
const target = @import("builtin").target;
pub const arch = switch (target.cpu.arch) {
.x86_64 => @import("x86_64/platform.zig"),
else => {
@compileError("unsupported architecture");
},
};

9
core/src/arch/vmm.zig Normal file
View File

@ -0,0 +1,9 @@
const std = @import("std");
const target = @import("builtin").target;
pub const arch = switch (target.cpu.arch) {
.x86_64 => @import("x86_64/vmm.zig"),
else => {
@compileError("unsupported architecture");
},
};

View File

@ -0,0 +1,23 @@
const io = @import("ioports.zig");
const COM1: u16 = 0x3f8;
fn serialWait() void {
while ((io.inb(COM1 + 5) & 0x20) == 0) {
asm volatile ("pause");
}
}
fn serialPutchar(c: u8) void {
serialWait();
io.outb(COM1, c);
}
/// Write data to the platform's debug output.
pub fn write(s: []const u8) usize {
for (s) |character| {
serialPutchar(character);
}
return s.len;
}

View File

@ -0,0 +1,135 @@
const std = @import("std");
const platform = @import("platform.zig");
const GDTR align(4096) = packed struct {
size: u16,
offset: u64,
};
const GDTEntry = packed struct {
limit0: u16,
base0: u16,
base1: u8,
access: u8,
limit1_flags: u8,
base2: u8,
};
fn createGDTEntry(limit0: u16, base0: u16, base1: u8, access: u8, limit1_flags: u8, base2: u8) GDTEntry {
return GDTEntry{
.limit0 = limit0,
.base0 = base0,
.base1 = base1,
.access = access,
.limit1_flags = limit1_flags,
.base2 = base2,
};
}
const HighGDTEntry = packed struct {
base_high: u32,
reserved: u32,
};
const GlobalDescriptorTable = packed struct {
null: GDTEntry,
kernel_code: GDTEntry,
kernel_data: GDTEntry,
user_code: GDTEntry,
user_data: GDTEntry,
tss: GDTEntry,
tss2: HighGDTEntry,
};
fn setBase(entry: *GDTEntry, base: u32) void {
entry.base0 = @intCast(base & 0xFFFF);
entry.base1 = @intCast((base >> 16) & 0xFF);
entry.base2 = @intCast((base >> 24) & 0xFF);
}
fn setLimit(entry: *GDTEntry, limit: u20) void {
entry.limit0 = @intCast(limit & 0xFFFF);
entry.limit1_flags = (entry.limit1_flags & 0xF0) | (@as(u8, @intCast(limit >> 16)) & 0xF);
}
fn setTSSBase(tss1: *GDTEntry, tss2: *HighGDTEntry, addr: u64) void {
setBase(tss1, @intCast(addr & 0xffffffff));
tss2.base_high = @intCast(addr >> 32);
}
const TSS = packed struct {
reserved0: u32,
rsp0: u64,
rsp1: u64,
rsp2: u64,
reserved1: u64,
ist0: u64,
ist1: u64,
ist2: u64,
ist3: u64,
ist4: u64,
ist5: u64,
ist6: u64,
reserved2: u64,
reserved3: u16,
iomap_base: u16,
};
fn stackTop(begin: usize, size: usize) usize {
return (begin + size) - 16;
}
fn setupTSS(gdt: *GlobalDescriptorTable, tss: *TSS, stack: [*]u8, stack_length: usize) void {
tss.iomap_base = @sizeOf(TSS);
tss.ist0 = stackTop(@intFromPtr(stack), stack_length);
setTSSBase(&gdt.tss, &gdt.tss2, @intFromPtr(tss));
setLimit(&gdt.tss, @sizeOf(TSS) - 1);
}
fn loadGDT() callconv(.Naked) void {
asm volatile (
\\ cli
\\ lgdt (%rdi)
\\ mov $0x10, %ax
\\ mov %ax, %ds
\\ mov %ax, %es
\\ mov %ax, %fs
\\ mov %ax, %gs
\\ mov %ax, %ss
\\ push $8
\\ lea .reload_CS(%rip), %rax
\\ push %rax
\\ lretq
\\.reload_CS:
\\ ret
);
}
fn loadTR() callconv(.Naked) void {
asm volatile (
\\ mov %rdi, %rax
\\ ltr %ax
\\ ret
);
}
/// Setup the Global Descriptor Table.
pub fn setupGDT() void {
// Store all these as static variables, as they won't be needed outside this function but need to stay alive.
const state = struct {
var gdt = GlobalDescriptorTable{ .null = std.mem.zeroes(GDTEntry), .kernel_code = createGDTEntry(0xffff, 0x0000, 0x00, 0x9a, 0xaf, 0x00), .kernel_data = createGDTEntry(0xffff, 0x0000, 0x00, 0x92, 0xcf, 0x00), .user_code = createGDTEntry(0xffff, 0x0000, 0x00, 0xfa, 0xaf, 0x00), .user_data = createGDTEntry(0xffff, 0x0000, 0x00, 0xf2, 0xcf, 0x00), .tss = createGDTEntry(0x0000, 0x0000, 0x00, 0xe9, 0x0f, 0x00), .tss2 = HighGDTEntry{ .base_high = 0x00000000, .reserved = 0x00000000 } };
var gdtr = std.mem.zeroes(GDTR);
var tss = std.mem.zeroes(TSS);
var alternate_stack: [platform.PAGE_SIZE * 4]u8 = std.mem.zeroes([platform.PAGE_SIZE * 4]u8);
};
state.gdtr.offset = @intFromPtr(&state.gdt);
state.gdtr.size = @sizeOf(GlobalDescriptorTable);
setupTSS(&state.gdt, &state.tss, @ptrCast(&state.alternate_stack[0]), @sizeOf(@TypeOf(state.alternate_stack)));
// Hackish way to call naked functions which we know conform to SysV ABI.
const lgdt: *const fn (g: *GDTR) callconv(.C) void = @ptrCast(&loadGDT);
lgdt(&state.gdtr);
const ltr: *const fn (t: u16) callconv(.C) void = @ptrCast(&loadTR);
ltr(0x2b);
}

View File

@ -0,0 +1,156 @@
const std = @import("std");
const IDTEntry = packed struct {
offset0: u16,
selector: u16,
ist: u8,
type_attr: u8,
offset1: u16,
offset2: u32,
ignore: u32,
};
fn setOffset(entry: *IDTEntry, offset: u64) void {
entry.offset0 = @as(u16, @intCast(offset & 0x000000000000ffff));
entry.offset1 = @as(u16, @intCast((offset & 0x00000000ffff0000) >> 16));
entry.offset2 = @as(u32, @intCast((offset & 0xffffffff00000000) >> 32));
}
fn getOffset(entry: *IDTEntry) u64 {
var offset: u64 = 0;
offset |= @as(u64, entry.offset0);
offset |= @as(u64, entry.offset1) << 16;
offset |= @as(u64, entry.offset2) << 32;
return offset;
}
const IDT_TA_InterruptGate = 0b10001110;
const IDT_TA_UserCallableInterruptGate = 0b11101110;
const IDT_TA_TrapGate = 0b10001111;
const IDTR = packed struct {
limit: u16,
offset: u64,
};
fn addIDTHandler(idt: *[256]IDTEntry, num: u32, handler: *const anyopaque, type_attr: u8, ist: u8) void {
var entry_for_handler: *IDTEntry = &idt.*[num];
entry_for_handler.selector = 0x08;
entry_for_handler.type_attr = type_attr;
entry_for_handler.ist = ist;
setOffset(entry_for_handler, @intFromPtr(handler));
}
fn createISRHandler(comptime num: u32) *const fn () callconv(.Naked) void {
return struct {
fn handler() callconv(.Naked) void {
asm volatile (
\\ push $0
\\ push %[num]
\\ jmp asmInterruptEntry
:
: [num] "n" (num),
);
}
}.handler;
}
fn createISRHandlerWithErrorCode(comptime num: u32) *const fn () callconv(.Naked) void {
return struct {
fn handler() callconv(.Naked) void {
asm volatile (
\\ push %[num]
\\ jmp asmInterruptEntry
:
: [num] "n" (num),
);
}
}.handler;
}
fn createIRQHandler(comptime num: u32, comptime irq: u32) *const fn () callconv(.Naked) void {
return struct {
fn handler() callconv(.Naked) void {
asm volatile (
\\ push %[irq]
\\ push %[num]
\\ jmp asmInterruptEntry
:
: [num] "n" (num),
[irq] "n" (irq),
);
}
}.handler;
}
/// Setup the Interrupt Descriptor Table.
pub fn setupIDT() void {
// Store these as static variables, as they won't be needed outside this function but need to stay alive.
const state = struct {
var idtr = std.mem.zeroes(IDTR);
var idt: [256]IDTEntry = std.mem.zeroes([256]IDTEntry);
};
comptime var i: u32 = 0;
// ISR 0-7 (no error code)
inline while (i < 8) : (i += 1) {
const handler: *const anyopaque = @ptrCast(createISRHandler(i));
addIDTHandler(&state.idt, i, handler, IDT_TA_TrapGate, 1);
}
// ISR 8 #DF (error code)
const handler_8: *const anyopaque = @ptrCast(createISRHandlerWithErrorCode(8));
addIDTHandler(&state.idt, 8, handler_8, IDT_TA_TrapGate, 1);
// ISR 9 obsolete
i = 10;
// ISR 10-14 (error code)
inline while (i < 15) : (i += 1) {
const handler: *const anyopaque = @ptrCast(createISRHandlerWithErrorCode(i));
addIDTHandler(&state.idt, i, handler, IDT_TA_TrapGate, 1);
}
// ISR 15 reserved
// ISR 16 #MF (no error code)
const handler_16: *const anyopaque = @ptrCast(createISRHandler(16));
addIDTHandler(&state.idt, 16, handler_16, IDT_TA_TrapGate, 1);
// ISR 17 #AC (error code)
const handler_17: *const anyopaque = @ptrCast(createISRHandlerWithErrorCode(17));
addIDTHandler(&state.idt, 17, handler_17, IDT_TA_TrapGate, 1);
i = 18;
// ISR 18-20 (no error code)
inline while (i < 21) : (i += 1) {
const handler: *const anyopaque = @ptrCast(createISRHandler(i));
addIDTHandler(&state.idt, i, handler, IDT_TA_TrapGate, 1);
}
// ISR 21 #CP (error code)
const handler_21: *const anyopaque = @ptrCast(createISRHandlerWithErrorCode(21));
addIDTHandler(&state.idt, 21, handler_21, IDT_TA_TrapGate, 1);
// ISR 22-31 reserved
i = 0;
// ISR 32-47 (IRQs 0-16 after remapping the PIC)
inline while (i < 16) : (i += 1) {
const handler: *const anyopaque = @ptrCast(createIRQHandler(32 + i, i));
addIDTHandler(&state.idt, 32 + i, handler, IDT_TA_InterruptGate, 0);
}
// ISR 66 (syscall)
const sys_handler: *const anyopaque = @ptrCast(createISRHandler(66));
addIDTHandler(&state.idt, 66, sys_handler, IDT_TA_UserCallableInterruptGate, 0);
state.idtr.limit = 0x0FFF;
state.idtr.offset = @intFromPtr(&state.idt[0]);
asm volatile ("lidt (%[idtr])"
:
: [idtr] "{rax}" (&state.idtr),
);
}

View File

@ -0,0 +1,147 @@
const std = @import("std");
const pic = @import("pic.zig");
const debug = @import("../debug.zig");
const sys = @import("../../sys/syscall.zig");
pub const InterruptStackFrame = packed struct {
r15: u64,
r14: u64,
r13: u64,
r12: u64,
r11: u64,
r10: u64,
r9: u64,
r8: u64,
rbp: u64,
rdi: u64,
rsi: u64,
rdx: u64,
rcx: u64,
rbx: u64,
rax: u64,
isr: u64,
error_or_irq: u64,
rip: u64,
cs: u64,
rflags: u64,
rsp: u64,
ss: u64,
};
const IRQHandler = *const fn (u32, *InterruptStackFrame) void;
var irq_handlers: [16]?IRQHandler = std.mem.zeroes([16]?IRQHandler);
export fn asmInterruptEntry() callconv(.Naked) void {
asm volatile (
\\ push %rax
\\ push %rbx
\\ push %rcx
\\ push %rdx
\\ push %rsi
\\ push %rdi
\\ push %rbp
\\ push %r8
\\ push %r9
\\ push %r10
\\ push %r11
\\ push %r12
\\ push %r13
\\ push %r14
\\ push %r15
\\ cld
\\ mov %rsp, %rdi
\\ call interruptEntry
\\asmInterruptExit:
\\ pop %r15
\\ pop %r14
\\ pop %r13
\\ pop %r12
\\ pop %r11
\\ pop %r10
\\ pop %r9
\\ pop %r8
\\ pop %rbp
\\ pop %rdi
\\ pop %rsi
\\ pop %rdx
\\ pop %rcx
\\ pop %rbx
\\ pop %rax
\\ add $16, %rsp
\\ iretq
);
}
const SYSCALL_INTERRUPT = 66;
export fn interruptEntry(frame: *InterruptStackFrame) callconv(.C) void {
debug.print("Caught interrupt {d}\n", .{frame.isr});
switch (frame.isr) {
SYSCALL_INTERRUPT => {
var args = sys.Arguments{ .arg0 = frame.rdi, .arg1 = frame.rsi, .arg2 = frame.rdx, .arg3 = frame.r10, .arg4 = frame.r8, .arg5 = frame.r9 };
sys.invokeSyscall(frame.rax, frame, &args, @ptrFromInt(@as(usize, @intFromPtr(&frame.rax))));
},
else => {},
}
}
/// Disable interrupts (except for NMIs).
pub fn disableInterrupts() void {
asm volatile ("cli");
}
/// Enable interrupts.
pub fn enableInterrupts() void {
asm volatile ("sti");
}
/// Check whether interrupts are enabled.
pub fn saveInterrupts() bool {
var flags: u64 = 0;
asm volatile ("pushfq; pop %[flags]"
: [flags] "=r" (flags),
);
return (flags & 0x200) != 0;
}
/// Enable or disable interrupts depending on the boolean value passed.
pub fn restoreInterrupts(saved: bool) void {
switch (saved) {
true => {
enableInterrupts();
},
false => {
disableInterrupts();
},
}
}
/// Update the PIC masks according to which IRQ handlers are registered.
pub fn syncInterrupts() void {
var pic1_mask: u8 = 0b11111111;
var pic2_mask: u8 = 0b11111111;
var i: u8 = 0;
while (i < 8) : (i += 1) {
if (irq_handlers[i] != null) pic1_mask &= (~(@as(u8, 1) << @as(u3, @intCast(i))));
if (irq_handlers[i + 8] != null) pic2_mask &= (~(@as(u8, 1) << @as(u3, @intCast(i))));
}
if (pic2_mask != 0b11111111) pic1_mask &= 0b11111011;
const saved: bool = saveInterrupts();
disableInterrupts();
pic.changePICMasks(pic1_mask, pic2_mask);
restoreInterrupts(saved);
}
/// Register an IRQ handler.
pub fn registerIRQ(num: u32, handler: IRQHandler) bool {
if (irq_handlers[num] != null) return false;
irq_handlers[num] = handler;
syncInterrupts();
return true;
}

View File

@ -0,0 +1,44 @@
pub fn inb(port: u16) u8 {
return asm volatile ("inb %[port], %[result]"
: [result] "={al}" (-> u8),
: [port] "N{dx}" (port),
);
}
pub fn outb(port: u16, value: u8) void {
return asm volatile ("outb %[data], %[port]"
:
: [port] "{dx}" (port),
[data] "{al}" (value),
);
}
pub fn inw(port: u16) u16 {
return asm volatile ("inw %[port], %[result]"
: [result] "={ax}" (-> u16),
: [port] "N{dx}" (port),
);
}
pub fn outw(port: u16, value: u16) void {
return asm volatile ("outw %[data], %[port]"
:
: [port] "{dx}" (port),
[data] "{ax}" (value),
);
}
pub fn inl(port: u16) u32 {
return asm volatile ("inl %[port], %[result]"
: [result] "={eax}" (-> u16),
: [port] "N{dx}" (port),
);
}
pub fn outl(port: u16, value: u32) void {
return asm volatile ("outw %[data], %[port]"
:
: [port] "{dx}" (port),
[data] "{eax}" (value),
);
}

View File

@ -0,0 +1,55 @@
const io = @import("ioports.zig");
const PIC1_COMMAND = 0x20;
const PIC1_DATA = 0x21;
const PIC2_COMMAND = 0xA0;
const PIC2_DATA = 0xA1;
const PIC_EOI = 0x20;
const ICW1_INIT = 0x10;
const ICW1_ICW4 = 0x01;
const ICW4_8086 = 0x01;
inline fn ioDelay() void {
io.outb(0x80, 0);
}
/// Remap the PIC so that all IRQs are remapped to 0x20-0x2f.
pub fn remapPIC() void {
io.outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4);
ioDelay();
io.outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
ioDelay();
io.outb(PIC1_DATA, 0x20);
ioDelay();
io.outb(PIC2_DATA, 0x28);
ioDelay();
io.outb(PIC1_DATA, 4);
ioDelay();
io.outb(PIC2_DATA, 2);
ioDelay();
io.outb(PIC1_DATA, ICW4_8086);
ioDelay();
io.outb(PIC2_DATA, ICW4_8086);
ioDelay();
changePICMasks(0b11111111, 0b11111111);
}
/// Update the PIC masks.
pub fn changePICMasks(pic1_mask: u8, pic2_mask: u8) void {
io.outb(PIC1_DATA, pic1_mask);
ioDelay();
io.outb(PIC2_DATA, pic2_mask);
ioDelay();
}
/// Send an end-of-interrupt signal to the PIC.
pub fn picEOI(irq: u8) void {
if (irq >= 8) io.outb(PIC2_COMMAND, PIC_EOI);
io.outb(PIC1_COMMAND, PIC_EOI);
}

View File

@ -0,0 +1,18 @@
const gdt = @import("gdt.zig");
const idt = @import("idt.zig");
const pic = @import("pic.zig");
const interrupts = @import("interrupts.zig");
pub const PAGE_SIZE = 4096;
// Initialize platform-specific components.
pub fn platformInit() void {
gdt.setupGDT();
idt.setupIDT();
}
// Initialize platform-specific components just before beginning multitasking.
pub fn platformEndInit() void {
pic.remapPIC();
interrupts.syncInterrupts();
}

View File

@ -0,0 +1,203 @@
const std = @import("std");
const easyboot = @cImport(@cInclude("easyboot.h"));
const mmap = @import("../../mmap.zig");
const pmm = @import("../../pmm.zig");
const USER_ADDRESS_RANGE_END = 0x0000_7fff_ffff_ffff;
const PHYSICAL_MAPPING_BASE = 0xffff_8000_0000_0000;
const HUGE_PAGE_SIZE = 0x200000; // 2 MiB
pub const PageTableEntry = packed struct {
present: u1,
read_write: u1,
user: u1,
write_through: u1,
cache_disabled: u1,
accessed: u1,
ignore0: u1,
larger_pages: u1,
global: u1,
available: u3,
address: u48,
available2: u3,
no_execute: u1,
pub fn set_address(self: *PageTableEntry, address: u64) void {
self.address = @intCast(address >> 12);
}
pub fn get_address(self: *PageTableEntry) u64 {
return self.address << 12;
}
pub fn clear(self: *PageTableEntry) void {
self = std.mem.zeroes(@TypeOf(self));
}
};
pub const PageDirectory = struct {
entries: [512]PageTableEntry,
};
const Flags = enum(u32) {
None = 0,
ReadWrite = 1,
User = 2,
NoExecute = 4,
WriteThrough = 8,
CacheDisable = 16,
Global = 32,
};
const PageTableIndexes = struct {
level4: u24,
level3: u24,
level2: u24,
level1: u24,
};
fn calculatePageTableIndexes(address: usize) PageTableIndexes {
return .{ .level4 = @intCast((address >> 39) & 0o777), .level3 = @intCast((address >> 30) & 0o777), .level2 = @intCast((address >> 21) & 0o777), .level1 = @intCast((address >> 12) & 0o777) };
}
fn hasFlag(flags: u32, flag: Flags) u1 {
return switch ((flags & @intFromEnum(flag)) > 0) {
true => 1,
false => 0,
};
}
fn updatePageTableEntry(entry: *PageTableEntry, phys: pmm.PhysFrame, flags: u32) void {
entry.present = 1;
entry.read_write = hasFlag(flags, Flags.ReadWrite);
entry.user = hasFlag(flags, Flags.User);
entry.write_through = hasFlag(flags, Flags.WriteThrough);
entry.cache_disabled = hasFlag(flags, Flags.CacheDisable);
entry.no_execute = hasFlag(flags, Flags.NoExecute);
entry.global = hasFlag(flags, Flags.Global);
entry.set_address(phys.address);
}
fn setUpParentPageTableEntry(allocator: *pmm.FrameAllocator, pte: *PageTableEntry, flags: u32, base: usize) !void {
if (pte.present == 0) {
const frame = try pmm.allocFrame(allocator);
pte.present = 1;
pte.set_address(frame.address);
getTable(pte, base).* = std.mem.zeroes(PageDirectory);
}
if (hasFlag(flags, Flags.ReadWrite) == 1) pte.read_write = 1;
if (hasFlag(flags, Flags.User) == 1) pte.user = 1;
}
fn getTable(pte: *PageTableEntry, base: usize) *allowzero PageDirectory {
const frame = pmm.PhysFrame{ .address = pte.get_address() };
return @ptrFromInt(frame.virtualAddress(base));
}
pub fn map(allocator: *pmm.FrameAllocator, directory: *PageDirectory, base: usize, virt_address: u64, phys: pmm.PhysFrame, flags: u32, use_huge_pages: bool) !void {
const indexes = calculatePageTableIndexes(virt_address);
const l4 = &directory.entries[indexes.level4];
try setUpParentPageTableEntry(allocator, l4, flags, base);
const l3 = &getTable(l4, base).entries[indexes.level3];
if (l3.larger_pages == 1) return error.MemoryAlreadyInUse;
try setUpParentPageTableEntry(allocator, l3, flags, base);
const l2 = &getTable(l3, base).entries[indexes.level2];
if (l2.larger_pages == 1) return error.MemoryAlreadyInUse;
if (use_huge_pages) {
l2.larger_pages = 1;
updatePageTableEntry(l2, phys, flags);
return;
}
try setUpParentPageTableEntry(allocator, l2, flags, base);
const l1 = &getTable(l2, base).entries[indexes.level1];
if (l1.present == 1) return error.MemoryAlreadyInUse;
updatePageTableEntry(l1, phys, flags);
}
fn mapPhysicalMemory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, directory: *PageDirectory, base: usize, flags: u32) !void {
const address_space_size = mmap.getAddressSpaceSize(tag) orelse return error.InvalidMemoryMap;
const address_space_pages = address_space_size / HUGE_PAGE_SIZE;
var index: usize = 0;
while (index < address_space_pages) : (index += 1) {
try map(allocator, directory, 0, base + index * HUGE_PAGE_SIZE, pmm.PhysFrame{ .address = index * HUGE_PAGE_SIZE }, flags, true);
}
}
fn lockPageDirectoryFrames(allocator: *pmm.FrameAllocator, directory: *PageDirectory, index: u8) !void {
if (index > 1) {
var i: u64 = 0;
while (i < 512) : (i += 1) {
const pte = &directory.entries[i];
if (pte.present == 0) continue;
if ((index < 4) and (pte.larger_pages == 1)) continue;
try pmm.lockFrame(allocator, pte.get_address());
const child_table: *PageDirectory = @ptrFromInt(pte.get_address());
try lockPageDirectoryFrames(allocator, child_table, index - 1);
}
}
}
fn lockPageDirectory(allocator: *pmm.FrameAllocator, directory: *PageDirectory) !void {
try pmm.lockFrame(allocator, @intFromPtr(directory));
try lockPageDirectoryFrames(allocator, directory, 4);
}
fn setUpKernelPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t) !*PageDirectory {
const directory = readPageDirectory();
try lockPageDirectory(allocator, directory);
try mapPhysicalMemory(allocator, tag, directory, PHYSICAL_MAPPING_BASE, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.Global));
return directory;
}
fn setUpInitialUserPageDirectory(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, kernel_directory: *PageDirectory, user_directory: *PageDirectory) !usize {
const physical_address_space_size = mmap.getAddressSpaceSize(tag) orelse return error.InvalidMemoryMap;
user_directory.* = std.mem.zeroes(PageDirectory);
const directory_upper_half: *[256]PageTableEntry = kernel_directory.entries[256..];
const user_directory_upper_half: *[256]PageTableEntry = user_directory.entries[256..];
@memcpy(user_directory_upper_half, directory_upper_half);
const user_physical_address_base = (USER_ADDRESS_RANGE_END + 1) - physical_address_space_size;
try mapPhysicalMemory(allocator, tag, user_directory, user_physical_address_base, @intFromEnum(Flags.ReadWrite) | @intFromEnum(Flags.NoExecute) | @intFromEnum(Flags.User));
return user_physical_address_base;
}
pub fn createInitialMappings(allocator: *pmm.FrameAllocator, tag: *easyboot.multiboot_tag_mmap_t, user_directory: *PageDirectory) !usize {
const directory = try setUpKernelPageDirectory(allocator, tag);
const base = try setUpInitialUserPageDirectory(allocator, tag, directory, user_directory);
setPageDirectory(directory);
allocator.bitmap.location = @ptrFromInt(@as(usize, PHYSICAL_MAPPING_BASE) + @intFromPtr(allocator.bitmap.location));
return base;
}
pub fn readPageDirectory() *PageDirectory {
var directory: *PageDirectory = undefined;
asm volatile ("mov %%cr3, %[dir]"
: [dir] "=r" (directory),
);
return directory;
}
pub fn setPageDirectory(directory: *PageDirectory) void {
asm volatile ("mov %[dir], %%cr3"
:
: [dir] "{rdi}" (directory),
);
}

138
core/src/lib/bitmap.zig Normal file
View File

@ -0,0 +1,138 @@
const std = @import("std");
const BitmapError = error{
OutOfRange,
};
pub const Bitmap = struct {
location: [*]u8,
byte_size: usize,
pub fn bit_size(self: *Bitmap) usize {
return self.byte_size * 8;
}
pub fn set(self: *Bitmap, index: usize, value: u1) BitmapError!void {
if (index >= self.bit_size()) return error.OutOfRange;
const byte_index = index / 8;
const bit_mask = @as(u8, 0b10000000) >> @as(u3, @intCast(index % 8));
self.location[byte_index] &= ~bit_mask;
if (value == 1) {
self.location[byte_index] |= bit_mask;
}
}
pub fn get(self: *Bitmap, index: usize) BitmapError!u1 {
if (index >= self.bit_size()) return error.OutOfRange;
const byte_index = index / 8;
const bit_mask = @as(u8, 0b10000000) >> @as(u3, @intCast(index % 8));
if ((self.location[byte_index] & bit_mask) > 0) return 1;
return 0;
}
pub fn clear(self: *Bitmap, value: u1) void {
@memset(self.location[0..self.byte_size], byteThatOnlyContainsBit(value));
}
};
pub fn createBitmap(location: [*]u8, byte_size: usize) Bitmap {
return Bitmap{ .location = location, .byte_size = byte_size };
}
// Self-explanatory.
fn byteThatDoesNotContainBit(value: u1) u8 {
return switch (value) {
1 => 0x00,
0 => 0xff,
};
}
fn byteThatOnlyContainsBit(value: u1) u8 {
return switch (value) {
1 => 0xff,
0 => 0x00,
};
}
pub fn findInBitmap(bitmap: *Bitmap, value: u1, begin: usize) BitmapError!?usize {
if (begin >= bitmap.bit_size()) return error.OutOfRange;
var index = begin;
while ((index % 8) != 0) {
if (try bitmap.get(index) == value) return index;
index += 1;
}
if (index == bitmap.bit_size()) return null;
var i: usize = index / 8;
const byte_that_does_not_contain_value = byteThatDoesNotContainBit(value);
while (i < bitmap.byte_size) {
if (bitmap.location[i] == byte_that_does_not_contain_value) {
i += 1;
continue;
}
var j: usize = i * 8;
const end: usize = j + 8;
while (j < end) {
if (try bitmap.get(j) == value) return j;
j += 1;
}
// Once we've located a byte that contains the value, we should succeed in finding it.
unreachable;
}
return null;
}
pub fn findInBitmapAndToggle(bitmap: *Bitmap, value: u1, begin: usize) BitmapError!?usize {
const index = try findInBitmap(bitmap, value, begin);
switch (value) {
0 => try bitmap.set(index, 1),
1 => try bitmap.set(index, 0),
}
return index;
}
pub fn updateBitmapRegion(bitmap: *Bitmap, begin: usize, count: usize, value: u1) BitmapError!void {
if ((begin + count) > bitmap.bit_size()) return error.OutOfRange;
if (count == 0) return;
var index = begin; // The bit index we're updating.
var bits_remaining = count; // The number of bits left to update.
// If the index is in the middle of a byte, update individual bits until we reach a byte.
while ((index % 8) > 0 and bits_remaining > 0) {
try bitmap.set(index, value);
index += 1;
bits_remaining -= 1;
}
// Clear out the rest in bytes. We calculate the number of bytes to update, and then memset them all.
const bytes: usize = bits_remaining / 8;
if (bytes > 0) {
const start = index / 8;
const end = start + bytes;
@memset(bitmap.location[start..end], byteThatOnlyContainsBit(value));
// Update the counting variables after the memset.
index += bytes * 8;
bits_remaining -= bytes * 8;
}
// Set the remaining individual bits.
while (bits_remaining > 0) {
try bitmap.set(index, value);
index += 1;
bits_remaining -= 1;
}
}

33
core/src/link.ld Normal file
View File

@ -0,0 +1,33 @@
ENTRY(_start)
OUTPUT_FORMAT(elf64-x86-64)
PHDRS
{
boot PT_LOAD; /* one single loadable segment */
}
SECTIONS
{
. = 0xffffffffffe00000;
kernel_start = .;
.text : {
KEEP(*(.text.boot)) *(.text .text.*) /* code */
. = ALIGN(0x1000);
start_of_kernel_rodata = .;
*(.rodata .rodata.*) /* read-only data */
end_of_kernel_rodata = .;
. = ALIGN(0x1000);
start_of_kernel_data = .; /* data */
*(.data .data.*)
} :boot
.bss (NOLOAD) : { /* bss */
*(.bss .bss.*)
*(COMMON)
} :boot
end_of_kernel_data = .;
kernel_end = .;
/DISCARD/ : { *(.eh_frame) *(.comment) }
}

56
core/src/main.zig Normal file
View File

@ -0,0 +1,56 @@
const std = @import("std");
const easyboot = @cImport(@cInclude("easyboot.h"));
const debug = @import("arch/debug.zig");
const platform = @import("arch/platform.zig").arch;
const interrupts = @import("arch/interrupts.zig").arch;
const vmm = @import("arch/vmm.zig").arch;
const multiboot = @import("multiboot.zig");
const pmm = @import("pmm.zig");
const MultibootInfo = [*c]u8;
export fn _start(magic: u32, info: MultibootInfo) callconv(.C) noreturn {
if (magic != easyboot.MULTIBOOT2_BOOTLOADER_MAGIC) {
debug.print("Invalid magic number: {x}\n", .{magic});
while (true) {}
}
debug.print("Hello world from the kernel!\n", .{});
multiboot.parseMultibootTags(@ptrCast(info));
interrupts.disableInterrupts();
platform.platformInit();
debug.print("GDT initialized\n", .{});
platform.platformEndInit();
interrupts.enableInterrupts();
if (multiboot.findMultibootTag(easyboot.multiboot_tag_mmap_t, @ptrCast(info))) |tag| {
var allocator = pmm.initializeFrameAllocator(tag) catch |err| {
debug.print("Error while initializing frame allocator: {}\n", .{err});
while (true) {}
};
var init_directory = std.mem.zeroes(vmm.PageDirectory);
const base: usize = vmm.createInitialMappings(&allocator, tag, &init_directory) catch |err| {
debug.print("Error while creating initial mappings: {}\n", .{err});
while (true) {}
};
debug.print("Physical memory base mapping for init: {x}\n", .{base});
} else {
debug.print("No memory map multiboot tag found!\n", .{});
}
asm volatile ("int3");
while (true) {}
}
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
debug.print("--- KERNEL PANIC! ---\n", .{});
debug.print("{s}\n", .{message});
while (true) {}
}

61
core/src/mmap.zig Normal file
View File

@ -0,0 +1,61 @@
const easyboot = @cImport(@cInclude("easyboot.h"));
const MemoryMapIterator = struct {
tag: *easyboot.multiboot_tag_mmap_t,
entry: ?*easyboot.multiboot_mmap_entry_t,
end: usize,
pub fn next(self: *MemoryMapIterator) ?*easyboot.multiboot_mmap_entry_t {
if (self.entry) |e| {
const current_entry = self.entry;
var new_entry: [*c]u8 = @ptrCast(e);
new_entry += self.tag.entry_size;
self.entry = @alignCast(@ptrCast(new_entry));
if (@intFromPtr(self.entry) >= self.end) self.entry = null;
return current_entry;
}
return null;
}
};
pub fn createMemoryMapIterator(tag: *easyboot.multiboot_tag_mmap_t) MemoryMapIterator {
return MemoryMapIterator{ .tag = tag, .entry = @alignCast(@ptrCast(tag.entries())), .end = @intFromPtr(tag) + tag.size };
}
pub fn findLargestFreeEntry(tag: *easyboot.multiboot_tag_mmap_t) ?*easyboot.multiboot_mmap_entry_t {
var max_length: u64 = 0;
var biggest_entry: ?*easyboot.multiboot_mmap_entry_t = null;
var iter = createMemoryMapIterator(tag);
while (iter.next()) |entry| {
if (entry.type == easyboot.MULTIBOOT_MEMORY_AVAILABLE and entry.length > max_length) {
max_length = entry.length;
biggest_entry = entry;
}
}
return biggest_entry;
}
pub fn findHighestEntry(tag: *easyboot.multiboot_tag_mmap_t) ?*easyboot.multiboot_mmap_entry_t {
var highest_entry: ?*easyboot.multiboot_mmap_entry_t = null;
var iter = createMemoryMapIterator(tag);
while (iter.next()) |entry| {
highest_entry = entry;
}
return highest_entry;
}
pub fn getAddressSpaceSize(tag: *easyboot.multiboot_tag_mmap_t) ?usize {
const highest_entry = findHighestEntry(tag) orelse return null;
return highest_entry.base_addr + highest_entry.length;
}

231
core/src/multiboot.zig Normal file
View File

@ -0,0 +1,231 @@
const std = @import("std");
const easyboot = @cImport(@cInclude("easyboot.h"));
const debug = @import("arch/debug.zig");
fn dumpUUID(uuid: [16]u8) void {
debug.print("{x:0^2}{x:0^2}{x:0^2}{x:0^2}-{x:0^2}{x:0^2}-{x:0^2}{x:0^2}-{x:0^2}{x:0^2}{x:0^2}{x:0^2}{x:0^2}{x:0^2}{x:0^2}{x:0^2}\n", .{ uuid[3], uuid[2], uuid[1], uuid[0], uuid[5], uuid[4], uuid[7], uuid[6], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15] });
}
const MultibootInfo = [*]u8;
/// Return the first multiboot tag of the given type.
pub fn findMultibootTag(comptime Type: type, info: MultibootInfo) ?*Type {
const mb_tag: *easyboot.multiboot_info_t = @alignCast(@ptrCast(info));
const mb_size = mb_tag.total_size;
var tag: *easyboot.multiboot_tag_t = @alignCast(@ptrCast(info + 8));
const last = @intFromPtr(info) + mb_size;
while ((@intFromPtr(tag) < last) and (tag.type != easyboot.MULTIBOOT_TAG_TYPE_END)) {
switch (tag.type) {
easyboot.MULTIBOOT_TAG_TYPE_CMDLINE => {
if (Type == easyboot.multiboot_tag_cmdline_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME => {
if (Type == easyboot.multiboot_tag_loader_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_MODULE => {
if (Type == easyboot.multiboot_tag_module_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_MMAP => {
if (Type == easyboot.multiboot_tag_mmap_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_FRAMEBUFFER => {
if (Type == easyboot.multiboot_tag_framebuffer_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_EFI64 => {
if (Type == easyboot.multiboot_tag_efi64_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_EFI64_IH => {
if (Type == easyboot.multiboot_tag_efi64_ih_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_SMBIOS => {
if (Type == easyboot.multiboot_tag_smbios_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_ACPI_OLD => {
if (Type == easyboot.multiboot_tag_old_acpi_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_ACPI_NEW => {
if (Type == easyboot.multiboot_tag_new_acpi_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_SMP => {
if (Type == easyboot.multiboot_tag_smp_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_PARTUUID => {
if (Type == easyboot.multiboot_tag_partuuid_t) return @alignCast(@ptrCast(tag));
},
easyboot.MULTIBOOT_TAG_TYPE_EDID => {
if (Type == easyboot.multiboot_tag_edid_t) return @alignCast(@ptrCast(tag));
},
else => {},
}
var new_tag: [*]u8 = @ptrCast(tag);
new_tag += ((tag.size + 7) & ~@as(usize, 7));
tag = @alignCast(@ptrCast(new_tag));
}
return null;
}
/// Find every multiboot tag of the given type.
pub fn findMultibootTags(comptime Type: type, info: MultibootInfo, callback: *const fn (tag: *Type) void) void {
const mb_tag: *easyboot.multiboot_info_t = @alignCast(@ptrCast(info));
const mb_size = mb_tag.total_size;
var tag: *easyboot.multiboot_tag_t = @alignCast(@ptrCast(info + 8));
const last = @intFromPtr(info) + mb_size;
while ((@intFromPtr(tag) < last) and (tag.type != easyboot.MULTIBOOT_TAG_TYPE_END)) {
switch (tag.type) {
easyboot.MULTIBOOT_TAG_TYPE_CMDLINE => {
if (Type == easyboot.multiboot_tag_cmdline_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME => {
if (Type == easyboot.multiboot_tag_loader_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_MODULE => {
if (Type == easyboot.multiboot_tag_module_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_MMAP => {
if (Type == easyboot.multiboot_tag_mmap_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_FRAMEBUFFER => {
if (Type == easyboot.multiboot_tag_framebuffer_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_EFI64 => {
if (Type == easyboot.multiboot_tag_efi64_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_EFI64_IH => {
if (Type == easyboot.multiboot_tag_efi64_ih_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_SMBIOS => {
if (Type == easyboot.multiboot_tag_smbios_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_ACPI_OLD => {
if (Type == easyboot.multiboot_tag_old_acpi_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_ACPI_NEW => {
if (Type == easyboot.multiboot_tag_new_acpi_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_SMP => {
if (Type == easyboot.multiboot_tag_smp_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_PARTUUID => {
if (Type == easyboot.multiboot_tag_partuuid_t) callback(@alignCast(@ptrCast(tag)));
},
easyboot.MULTIBOOT_TAG_TYPE_EDID => {
if (Type == easyboot.multiboot_tag_edid_t) callback(@alignCast(@ptrCast(tag)));
},
else => {},
}
var new_tag: [*]u8 = @ptrCast(tag);
new_tag += ((tag.size + 7) & ~@as(usize, 7));
tag = @alignCast(@ptrCast(new_tag));
}
}
/// Log every multiboot tag in a multiboot struct.
pub fn parseMultibootTags(info: MultibootInfo) void {
const mb_tag: *easyboot.multiboot_info_t = @alignCast(@ptrCast(info));
const mb_size = mb_tag.total_size;
var tag: *easyboot.multiboot_tag_t = @alignCast(@ptrCast(info + 8));
const last = @intFromPtr(info) + mb_size;
while ((@intFromPtr(tag) < last) and (tag.type != easyboot.MULTIBOOT_TAG_TYPE_END)) {
switch (tag.type) {
easyboot.MULTIBOOT_TAG_TYPE_CMDLINE => {
var cmdline: *easyboot.multiboot_tag_cmdline_t = @alignCast(@ptrCast(tag));
debug.print("Command line = {s}\n", .{std.mem.sliceTo(cmdline.string(), 0)});
},
easyboot.MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME => {
var bootloader: *easyboot.multiboot_tag_loader_t = @alignCast(@ptrCast(tag));
debug.print("Boot loader name = {s}\n", .{std.mem.sliceTo(bootloader.string(), 0)});
},
easyboot.MULTIBOOT_TAG_TYPE_MODULE => {
var module: *easyboot.multiboot_tag_module_t = @alignCast(@ptrCast(tag));
debug.print("Module at {x}-{x}. Command line {s}\n", .{ module.mod_start, module.mod_end, std.mem.sliceTo(module.string(), 0) });
},
easyboot.MULTIBOOT_TAG_TYPE_MMAP => {
var mmap: *easyboot.multiboot_tag_mmap_t = @alignCast(@ptrCast(tag));
debug.print("Memory map:\n", .{});
var entry: *easyboot.multiboot_mmap_entry_t = @alignCast(@ptrCast(mmap.entries()));
const end = @intFromPtr(tag) + tag.size;
while (@intFromPtr(entry) < end) {
debug.print(" base_addr = {x}, length = {x}, type = {x} {s}, res = {x}\n", .{ entry.base_addr, entry.length, entry.type, switch (entry.type) {
easyboot.MULTIBOOT_MEMORY_AVAILABLE => "free",
easyboot.MULTIBOOT_MEMORY_ACPI_RECLAIMABLE => "ACPI",
easyboot.MULTIBOOT_MEMORY_NVS => "ACPI NVS",
else => "used",
}, entry.reserved });
var new_entry: [*c]u8 = @ptrCast(entry);
new_entry += mmap.entry_size;
entry = @alignCast(@ptrCast(new_entry));
}
},
easyboot.MULTIBOOT_TAG_TYPE_FRAMEBUFFER => {
const fb: *easyboot.multiboot_tag_framebuffer_t = @alignCast(@ptrCast(tag));
debug.print("Framebuffer: \n", .{});
debug.print(" address {x} pitch {d}\n", .{ fb.framebuffer_addr, fb.framebuffer_pitch });
debug.print(" width {d} height {d} depth {d} bpp\n", .{ fb.framebuffer_width, fb.framebuffer_height, fb.framebuffer_bpp });
debug.print(" red channel: at {d}, {d} bits\n", .{ fb.framebuffer_red_field_position, fb.framebuffer_red_mask_size });
debug.print(" green channel: at {d}, {d} bits\n", .{ fb.framebuffer_green_field_position, fb.framebuffer_green_mask_size });
debug.print(" blue channel: at {d}, {d} bits\n", .{ fb.framebuffer_blue_field_position, fb.framebuffer_blue_mask_size });
},
easyboot.MULTIBOOT_TAG_TYPE_EFI64 => {
const efi: *easyboot.multiboot_tag_efi64_t = @alignCast(@ptrCast(tag));
debug.print("EFI system table {x}\n", .{efi.pointer});
},
easyboot.MULTIBOOT_TAG_TYPE_EFI64_IH => {
const efi_ih: *easyboot.multiboot_tag_efi64_t = @alignCast(@ptrCast(tag));
debug.print("EFI image handle {x}\n", .{efi_ih.pointer});
},
easyboot.MULTIBOOT_TAG_TYPE_SMBIOS => {
const smbios: *easyboot.multiboot_tag_smbios_t = @alignCast(@ptrCast(tag));
debug.print("SMBIOS table major {d} minor {d}\n", .{ smbios.major, smbios.minor });
},
easyboot.MULTIBOOT_TAG_TYPE_ACPI_OLD => {
const acpi: *easyboot.multiboot_tag_old_acpi_t = @alignCast(@ptrCast(tag));
const rsdp = @intFromPtr(acpi.rsdp());
debug.print("ACPI table (1.0, old RSDP) at {x}\n", .{rsdp});
},
easyboot.MULTIBOOT_TAG_TYPE_ACPI_NEW => {
const acpi: *easyboot.multiboot_tag_new_acpi_t = @alignCast(@ptrCast(tag));
const rsdp = @intFromPtr(acpi.rsdp());
debug.print("ACPI table (2.0, new RSDP) at {x}\n", .{rsdp});
},
easyboot.MULTIBOOT_TAG_TYPE_SMP => {
const smp: *easyboot.multiboot_tag_smp_t = @alignCast(@ptrCast(tag));
debug.print("SMP supported\n", .{});
debug.print(" {d} core(s)\n", .{smp.numcores});
debug.print(" {d} running\n", .{smp.running});
debug.print(" {x} bsp id\n", .{smp.bspid});
},
easyboot.MULTIBOOT_TAG_TYPE_PARTUUID => {
const part: *easyboot.multiboot_tag_partuuid_t = @alignCast(@ptrCast(tag));
debug.print("Partition UUIDs\n", .{});
debug.print(" boot ", .{});
dumpUUID(part.bootuuid);
if (tag.size >= 40) {
debug.print(" root ", .{});
dumpUUID(part.rootuuid);
}
},
easyboot.MULTIBOOT_TAG_TYPE_EDID => {
const edid_tag: *easyboot.multiboot_tag_edid_t = @alignCast(@ptrCast(tag));
const edid: []u8 = edid_tag.edid()[0 .. tag.size - @sizeOf(easyboot.multiboot_tag_t)];
debug.print("EDID info\n", .{});
debug.print(" manufacturer ID {x}{x}\n", .{ edid[8], edid[9] });
debug.print(" EDID ID {x}{x} Version {d} Rev {d}\n", .{ edid[10], edid[11], edid[18], edid[19] });
debug.print(" monitor type {x} size {d} cm x {d} cm\n", .{ edid[20], edid[21], edid[22] });
},
else => {
debug.print("Unknown MBI tag, this shouldn't happen with Simpleboot/Easyboot!---\n", .{});
},
}
var new_tag: [*]u8 = @ptrCast(tag);
new_tag += ((tag.size + 7) & ~@as(usize, 7));
tag = @alignCast(@ptrCast(new_tag));
}
}

103
core/src/pmm.zig Normal file
View File

@ -0,0 +1,103 @@
const std = @import("std");
const easyboot = @cImport(@cInclude("easyboot.h"));
const platform = @import("arch/platform.zig").arch;
const mmap = @import("mmap.zig");
const bmap = @import("lib/bitmap.zig");
const FrameAllocatorError = error{
InvalidMemoryMap,
MemoryAlreadyInUse,
MemoryNotInUse,
OutOfMemory,
};
pub const FrameAllocator = struct {
bitmap: bmap.Bitmap,
free_memory: u64,
used_memory: u64,
reserved_memory: u64,
start_index: usize,
};
pub const PhysFrame = struct {
address: usize,
pub fn virtualAddress(self: *const PhysFrame, base: usize) usize {
return base + self.address;
}
};
pub fn lockFrame(allocator: *FrameAllocator, address: usize) !void {
const index = address / platform.PAGE_SIZE;
if (try allocator.bitmap.get(index) == 1) return error.MemoryAlreadyInUse;
try allocator.bitmap.set(index, 1);
allocator.used_memory += platform.PAGE_SIZE;
allocator.free_memory -= platform.PAGE_SIZE;
}
pub fn lockFrames(allocator: *FrameAllocator, address: usize, pages: usize) !void {
var index: usize = 0;
while (index < pages) : (index += 1) {
try lockFrame(allocator, address + (index * platform.PAGE_SIZE));
}
}
pub fn freeFrame(allocator: *FrameAllocator, address: usize) !void {
const index = address / platform.PAGE_SIZE;
if (try allocator.bitmap.get(index) == 0) return error.MemoryNotInUse;
try allocator.bitmap.set(index, 0);
allocator.used_memory -= platform.PAGE_SIZE;
allocator.free_memory += platform.PAGE_SIZE;
if (allocator.start_index > index) allocator.start_index = index;
}
pub fn freeFrames(allocator: *FrameAllocator, address: usize, pages: usize) !void {
const index: usize = 0;
while (index < pages) : (index += 1) {
try freeFrame(allocator, address + (index * platform.PAGE_SIZE));
}
}
pub fn allocFrame(allocator: *FrameAllocator) !PhysFrame {
const index: usize = try bmap.findInBitmap(&allocator.bitmap, 0, allocator.start_index) orelse return error.OutOfMemory;
const address = index * platform.PAGE_SIZE;
try lockFrame(allocator, address);
allocator.start_index = index + 1;
return PhysFrame{ .address = address };
}
pub fn initializeFrameAllocator(tag: *easyboot.multiboot_tag_mmap_t) !FrameAllocator {
const largest_free = mmap.findLargestFreeEntry(tag) orelse return error.InvalidMemoryMap;
const physical_address_space_size = mmap.getAddressSpaceSize(tag) orelse return error.InvalidMemoryMap;
const bitmap_base_address: [*]u8 = @ptrFromInt(largest_free.base_addr);
const bitmap_bit_size = physical_address_space_size / @as(usize, platform.PAGE_SIZE);
const bitmap_size: usize = try std.math.divCeil(usize, bitmap_bit_size, 8);
var allocator: FrameAllocator = FrameAllocator{ .bitmap = bmap.createBitmap(bitmap_base_address, bitmap_size), .free_memory = 0, .used_memory = 0, .reserved_memory = 0, .start_index = 0 };
allocator.bitmap.clear(1); // Set all pages to used/reserved by default, then clear out the free ones
var iter = mmap.createMemoryMapIterator(tag);
while (iter.next()) |entry| {
const index = entry.base_addr / platform.PAGE_SIZE;
const pages = entry.length / platform.PAGE_SIZE;
if (entry.type != easyboot.MULTIBOOT_MEMORY_AVAILABLE) {
allocator.reserved_memory += entry.length;
continue;
}
allocator.free_memory += entry.length;
try bmap.updateBitmapRegion(&allocator.bitmap, index, pages, 0);
}
const frames_to_lock = try std.math.divCeil(usize, bitmap_size, platform.PAGE_SIZE);
try lockFrames(&allocator, @intFromPtr(bitmap_base_address), frames_to_lock);
return allocator;
}

7
core/src/sys/print.zig Normal file
View File

@ -0,0 +1,7 @@
const interrupts = @import("../arch/interrupts.zig").arch;
const sys = @import("syscall.zig");
const debug = @import("../arch/debug.zig");
pub fn print(_: *interrupts.InterruptStackFrame, args: *sys.Arguments, _: *isize) anyerror!void {
debug.print("The userspace program gave us the number {x}\n", .{args.arg0});
}

28
core/src/sys/syscall.zig Normal file
View File

@ -0,0 +1,28 @@
const std = @import("std");
const interrupts = @import("../arch/interrupts.zig").arch;
const print = @import("print.zig").print;
pub const Arguments = struct {
arg0: usize,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
};
const SystemCall = *const fn (frame: *interrupts.InterruptStackFrame, args: *Arguments, retval: *isize) anyerror!void;
const syscalls = [_]SystemCall{print};
pub fn invokeSyscall(number: usize, frame: *interrupts.InterruptStackFrame, args: *Arguments, retval: *isize) void {
if (number >= syscalls.len) {
retval.* = -1;
return;
}
syscalls[number](frame, args, retval) catch {
retval.* = -1;
return;
};
}

1
easyboot Submodule

@ -0,0 +1 @@
Subproject commit 31d20593cf83bb212bbe73418211d8058bb23007

9
system/build.zig Normal file
View File

@ -0,0 +1,9 @@
const std = @import("std");
const init = @import("init/build.zig");
pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const system_step = b.step("system", "Build core system services");
init.build(b, system_step, optimize);
build_step.dependOn(system_step);
}

43
system/init/build.zig Normal file
View File

@ -0,0 +1,43 @@
const std = @import("std");
const here = "system/init";
pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
var disabled_features = std.Target.Cpu.Feature.Set.empty;
var enabled_features = std.Target.Cpu.Feature.Set.empty;
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.mmx));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.sse));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.sse2));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.avx));
disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.avx2));
enabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.soft_float));
const target_query = std.Target.Query{
.cpu_arch = std.Target.Cpu.Arch.x86_64,
.os_tag = std.Target.Os.Tag.freestanding,
.abi = std.Target.Abi.none,
.cpu_features_sub = disabled_features,
.cpu_features_add = enabled_features,
};
const init = b.addExecutable(.{
.name = "init",
.root_source_file = b.path(here ++ "/main.zig"),
.target = b.resolveTargetQuery(target_query),
.optimize = optimize,
.code_model = .default,
});
const install = b.addInstallArtifact(init, .{
.dest_dir = .{
.override = .{ .custom = "boot/" },
},
});
var init_step = b.step("init", "Build the init service");
init_step.dependOn(&init.step);
init_step.dependOn(&install.step);
build_step.dependOn(init_step);
}

13
system/init/main.zig Normal file
View File

@ -0,0 +1,13 @@
fn syscall(num: u64, arg: u64) void {
asm volatile ("int $66"
:
: [num] "{rax}" (num),
[arg] "{rdi}" (arg),
);
}
export fn _start(base: u64) callconv(.C) noreturn {
syscall(0, base);
while (true) {}
}

7
tools/iso.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
set -e
cd $(realpath $(dirname $0)/..)
tools/bin/easyboot -e boot astryon.iso

5
tools/run.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
cd $(realpath $(dirname $0)/..)
qemu-system-x86_64 -cdrom astryon.iso -serial stdio -enable-kvm $@