248 lines
5.4 KiB
Zig
248 lines
5.4 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const builtin = @import("builtin");
|
|
const gdt = @import("gdt.zig");
|
|
const idt = @import("idt.zig");
|
|
const irq = @import("irq.zig");
|
|
const isr = @import("isr.zig");
|
|
const pit = @import("pit.zig");
|
|
const paging = @import("paging.zig");
|
|
const syscalls = @import("syscalls.zig");
|
|
const mem = @import("../../mem.zig");
|
|
const log = @import("../../log.zig");
|
|
const MemProfile = mem.MemProfile;
|
|
|
|
/// The interrupt context that is given to a interrupt handler. It contains most of the registers
|
|
/// and the interrupt number and error code (if there is one).
|
|
pub const InterruptContext = struct {
|
|
// Extra segments
|
|
gs: u32,
|
|
fs: u32,
|
|
es: u32,
|
|
ds: u32,
|
|
|
|
// Destination, source, base pointer
|
|
edi: u32,
|
|
esi: u32,
|
|
ebp: u32,
|
|
esp: u32,
|
|
|
|
// General registers
|
|
ebx: u32,
|
|
edx: u32,
|
|
ecx: u32,
|
|
eax: u32,
|
|
|
|
// Interrupt number and error code
|
|
int_num: u32,
|
|
error_code: u32,
|
|
|
|
// Instruction pointer, code segment and flags
|
|
eip: u32,
|
|
cs: u32,
|
|
eflags: u32,
|
|
user_esp: u32,
|
|
ss: u32,
|
|
};
|
|
|
|
///
|
|
/// Assembly to write to a given port with a byte of data.
|
|
///
|
|
/// Arguments:
|
|
/// IN port: u16 - The port to write to.
|
|
/// IN data: u8 - The byte of data that will be sent.
|
|
///
|
|
pub fn outb(port: u16, data: u8) void {
|
|
asm volatile ("outb %[data], %[port]"
|
|
:
|
|
: [port] "{dx}" (port),
|
|
[data] "{al}" (data)
|
|
);
|
|
}
|
|
|
|
///
|
|
/// Assembly that reads data from a given port and returns its value.
|
|
///
|
|
/// Arguments:
|
|
/// IN port: u16 - The port to read data from.
|
|
///
|
|
/// Return: u8
|
|
/// The data that the port returns.
|
|
///
|
|
pub fn inb(port: u16) u8 {
|
|
return asm volatile ("inb %[port], %[result]"
|
|
: [result] "={al}" (-> u8)
|
|
: [port] "N{dx}" (port)
|
|
);
|
|
}
|
|
|
|
///
|
|
/// A simple way of waiting for I/O event to happen by doing an I/O event to flush the I/O
|
|
/// event being waited.
|
|
///
|
|
pub fn ioWait() void {
|
|
// Port 0x80 is free to use
|
|
outb(0x80, 0);
|
|
}
|
|
|
|
///
|
|
/// Load the GDT and refreshing the code segment with the code segment offset of the kernel as we
|
|
/// are still in kernel land. Also loads the kernel data segment into all the other segment
|
|
/// registers.
|
|
///
|
|
/// Arguments:
|
|
/// IN gdt_ptr: *gdt.GdtPtr - The address to the GDT.
|
|
///
|
|
pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void {
|
|
// Load the GDT into the CPU
|
|
asm volatile ("lgdt (%%eax)"
|
|
:
|
|
: [gdt_ptr] "{eax}" (gdt_ptr)
|
|
);
|
|
|
|
// Load the kernel data segment, index into the GDT
|
|
asm volatile ("mov %%bx, %%ds"
|
|
:
|
|
: [KERNEL_DATA_OFFSET] "{bx}" (gdt.KERNEL_DATA_OFFSET)
|
|
);
|
|
|
|
asm volatile ("mov %%bx, %%es");
|
|
asm volatile ("mov %%bx, %%fs");
|
|
asm volatile ("mov %%bx, %%gs");
|
|
asm volatile ("mov %%bx, %%ss");
|
|
|
|
// Load the kernel code segment into the CS register
|
|
asm volatile (
|
|
\\ljmp $0x08, $1f
|
|
\\1:
|
|
);
|
|
}
|
|
|
|
///
|
|
/// Get the previously loaded GDT from the CPU.
|
|
///
|
|
/// Return: gdt.GdtPtr
|
|
/// The previously loaded GDT from the CPU.
|
|
///
|
|
pub fn sgdt() gdt.GdtPtr {
|
|
var gdt_ptr = gdt.GdtPtr{ .limit = 0, .base = 0 };
|
|
asm volatile ("sgdt %[tab]"
|
|
: [tab] "=m" (gdt_ptr)
|
|
);
|
|
return gdt_ptr;
|
|
}
|
|
|
|
///
|
|
/// Tell the CPU where the TSS is located in the GDT.
|
|
///
|
|
/// Arguments:
|
|
/// IN offset: u16 - The offset in the GDT where the TSS segment is located.
|
|
///
|
|
pub fn ltr(offset: u16) void {
|
|
asm volatile ("ltr %%ax"
|
|
:
|
|
: [offset] "{ax}" (offset)
|
|
);
|
|
}
|
|
|
|
///
|
|
/// Load the IDT into the CPU.
|
|
///
|
|
/// Arguments:
|
|
/// IN idt_ptr: *const idt.IdtPtr - The address of the iDT.
|
|
///
|
|
pub fn lidt(idt_ptr: *const idt.IdtPtr) void {
|
|
asm volatile ("lidt (%%eax)"
|
|
:
|
|
: [idt_ptr] "{eax}" (idt_ptr)
|
|
);
|
|
}
|
|
|
|
///
|
|
/// Get the previously loaded IDT from the CPU.
|
|
///
|
|
/// Return: idt.IdtPtr
|
|
/// The previously loaded IDT from the CPU.
|
|
///
|
|
pub fn sidt() idt.IdtPtr {
|
|
var idt_ptr = idt.IdtPtr{ .limit = 0, .base = 0 };
|
|
asm volatile ("sidt %[tab]"
|
|
: [tab] "=m" (idt_ptr)
|
|
);
|
|
return idt_ptr;
|
|
}
|
|
|
|
///
|
|
/// Enable interrupts.
|
|
///
|
|
pub fn enableInterrupts() void {
|
|
asm volatile ("sti");
|
|
}
|
|
|
|
///
|
|
/// Disable interrupts.
|
|
///
|
|
pub fn disableInterrupts() void {
|
|
asm volatile ("cli");
|
|
}
|
|
|
|
///
|
|
/// Halt the CPU, but interrupts will still be called.
|
|
///
|
|
pub fn halt() void {
|
|
asm volatile ("hlt");
|
|
}
|
|
|
|
///
|
|
/// Wait the kernel but still can handle interrupts.
|
|
///
|
|
pub fn spinWait() noreturn {
|
|
while (true) {
|
|
enableInterrupts();
|
|
halt();
|
|
disableInterrupts();
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Halt the kernel. No interrupts will be handled.
|
|
///
|
|
pub fn haltNoInterrupts() noreturn {
|
|
while (true) {
|
|
disableInterrupts();
|
|
halt();
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Initialise the architecture
|
|
///
|
|
/// Arguments:
|
|
/// IN mem_profile: *const MemProfile - The memory profile of the computer. Used to set up
|
|
/// paging.
|
|
/// IN allocator: *Allocator - The allocator use to handle memory.
|
|
/// IN comptime options: type - The build options that is passed to the kernel to be
|
|
/// used for run time testing.
|
|
///
|
|
pub fn init(mem_profile: *const MemProfile, allocator: *Allocator, comptime options: type) void {
|
|
disableInterrupts();
|
|
|
|
gdt.init();
|
|
idt.init();
|
|
|
|
isr.init();
|
|
irq.init();
|
|
|
|
pit.init();
|
|
|
|
paging.init(mem_profile, allocator);
|
|
|
|
syscalls.init(options);
|
|
|
|
enableInterrupts();
|
|
}
|
|
|
|
test "" {
|
|
_ = @import("gdt.zig");
|
|
_ = @import("idt.zig");
|
|
}
|