Ready. Set. Go!
Microkernel development in Zig, should be fun! =]
This commit is contained in:
commit
13ec4bee87
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
**/.zig-cache
|
||||
boot/core
|
||||
boot/init
|
||||
tools/bin/
|
||||
tools/include/
|
||||
tools/share/
|
||||
astryon.iso
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "easyboot"]
|
||||
path = easyboot
|
||||
url = https://gitlab.com/bztsrc/easyboot
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal 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
25
LICENSE
Normal 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
66
README.md
Normal 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
7
boot/easyboot/menu.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
framebuffer 800 600 32
|
||||
|
||||
default 1 1000
|
||||
|
||||
menuentry Astryon default
|
||||
kernel core
|
||||
module init
|
14
build.zig
Normal file
14
build.zig
Normal 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
46
core/build.zig
Normal 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
35
core/src/arch/debug.zig
Normal 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;
|
||||
}
|
9
core/src/arch/interrupts.zig
Normal file
9
core/src/arch/interrupts.zig
Normal 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");
|
||||
},
|
||||
};
|
9
core/src/arch/platform.zig
Normal file
9
core/src/arch/platform.zig
Normal 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
9
core/src/arch/vmm.zig
Normal 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");
|
||||
},
|
||||
};
|
23
core/src/arch/x86_64/debug.zig
Normal file
23
core/src/arch/x86_64/debug.zig
Normal 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;
|
||||
}
|
135
core/src/arch/x86_64/gdt.zig
Normal file
135
core/src/arch/x86_64/gdt.zig
Normal 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);
|
||||
}
|
156
core/src/arch/x86_64/idt.zig
Normal file
156
core/src/arch/x86_64/idt.zig
Normal 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),
|
||||
);
|
||||
}
|
147
core/src/arch/x86_64/interrupts.zig
Normal file
147
core/src/arch/x86_64/interrupts.zig
Normal 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;
|
||||
}
|
44
core/src/arch/x86_64/ioports.zig
Normal file
44
core/src/arch/x86_64/ioports.zig
Normal 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),
|
||||
);
|
||||
}
|
55
core/src/arch/x86_64/pic.zig
Normal file
55
core/src/arch/x86_64/pic.zig
Normal 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);
|
||||
}
|
18
core/src/arch/x86_64/platform.zig
Normal file
18
core/src/arch/x86_64/platform.zig
Normal 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();
|
||||
}
|
203
core/src/arch/x86_64/vmm.zig
Normal file
203
core/src/arch/x86_64/vmm.zig
Normal 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
138
core/src/lib/bitmap.zig
Normal 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
33
core/src/link.ld
Normal 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
56
core/src/main.zig
Normal 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
61
core/src/mmap.zig
Normal 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
231
core/src/multiboot.zig
Normal 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
103
core/src/pmm.zig
Normal 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
7
core/src/sys/print.zig
Normal 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
28
core/src/sys/syscall.zig
Normal 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
1
easyboot
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 31d20593cf83bb212bbe73418211d8058bb23007
|
9
system/build.zig
Normal file
9
system/build.zig
Normal 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
43
system/init/build.zig
Normal 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
13
system/init/main.zig
Normal 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
7
tools/iso.sh
Executable 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
5
tools/run.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd $(realpath $(dirname $0)/..)
|
||||
|
||||
qemu-system-x86_64 -cdrom astryon.iso -serial stdio -enable-kvm $@
|
Loading…
x
Reference in New Issue
Block a user