Added gdt unit and runtime tests
Updated runtime tests Added doc comments for runtime tests PR review WIP Fixed testing Import GDT to run the unit tests Removed redundant arch tests Removed whitespace
This commit is contained in:
parent
9c35de8673
commit
07cc1ae89b
18 changed files with 1141 additions and 590 deletions
|
|
@ -1,17 +1,19 @@
|
|||
// Zig version: 0.4.0
|
||||
|
||||
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 log = @import("../../log.zig");
|
||||
const pit = @import("pit.zig");
|
||||
const paging = @import("paging.zig");
|
||||
const MemProfile = @import("../../mem.zig").MemProfile;
|
||||
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,
|
||||
|
|
@ -44,29 +46,7 @@ pub const InterruptContext = struct {
|
|||
};
|
||||
|
||||
///
|
||||
/// Initialise the architecture
|
||||
///
|
||||
pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator, comptime options: type) void {
|
||||
disableInterrupts();
|
||||
|
||||
gdt.init();
|
||||
idt.init();
|
||||
|
||||
isr.init();
|
||||
irq.init();
|
||||
|
||||
pit.init();
|
||||
|
||||
paging.init(mem_profile, allocator);
|
||||
|
||||
syscalls.init(options);
|
||||
|
||||
// Enable interrupts
|
||||
enableInterrupts();
|
||||
}
|
||||
|
||||
///
|
||||
/// Inline assembly to write to a given port with a byte of data.
|
||||
/// Assembly to write to a given port with a byte of data.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN port: u16 - The port to write to.
|
||||
|
|
@ -81,12 +61,12 @@ pub fn outb(port: u16, data: u8) void {
|
|||
}
|
||||
|
||||
///
|
||||
/// Inline assembly that reads data from a given port and returns its value.
|
||||
/// Assembly that reads data from a given port and returns its value.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN port: u16 - The port to read data from.
|
||||
///
|
||||
/// Return:
|
||||
/// Return: u8
|
||||
/// The data that the port returns.
|
||||
///
|
||||
pub fn inb(port: u16) u8 {
|
||||
|
|
@ -101,6 +81,7 @@ pub fn inb(port: u16) u8 {
|
|||
/// event being waited.
|
||||
///
|
||||
pub fn ioWait() void {
|
||||
// Port 0x80 is free to use
|
||||
outb(0x80, 0);
|
||||
}
|
||||
|
||||
|
|
@ -114,13 +95,22 @@ pub fn ioWait() void {
|
|||
///
|
||||
pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void {
|
||||
// Load the GDT into the CPU
|
||||
asm volatile ("lgdt (%%eax)" : : [gdt_ptr] "{eax}" (gdt_ptr));
|
||||
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, %%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
|
||||
|
|
@ -129,33 +119,61 @@ pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void {
|
|||
}
|
||||
|
||||
///
|
||||
/// Load the TSS into the CPU.
|
||||
/// Get the previously loaded GDT from the CPU.
|
||||
///
|
||||
pub fn ltr() void {
|
||||
asm volatile ("ltr %%ax" : : [TSS_OFFSET] "{ax}" (gdt.TSS_OFFSET));
|
||||
/// Return: gdt.GdtPtr
|
||||
/// The previously loaded GDT from the CPU.
|
||||
///
|
||||
pub fn sgdt() gdt.GdtPtr {
|
||||
var gdt_ptr: gdt.GdtPtr = 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));
|
||||
asm volatile ("lidt (%%eax)"
|
||||
:
|
||||
: [idt_ptr] "{eax}" (idt_ptr)
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Enable interrupts
|
||||
/// Enable interrupts.
|
||||
///
|
||||
pub fn enableInterrupts() void {
|
||||
asm volatile ("sti");
|
||||
}
|
||||
|
||||
///
|
||||
/// Disable interrupts
|
||||
/// Disable interrupts.
|
||||
///
|
||||
pub fn disableInterrupts() void {
|
||||
asm volatile ("cli");
|
||||
}
|
||||
|
||||
///
|
||||
/// Halt the CPU, but interrupts will still be called
|
||||
/// Halt the CPU, but interrupts will still be called.
|
||||
///
|
||||
pub fn halt() void {
|
||||
asm volatile ("hlt");
|
||||
|
|
@ -173,7 +191,7 @@ pub fn spinWait() noreturn {
|
|||
}
|
||||
|
||||
///
|
||||
/// Halt the kernel.
|
||||
/// Halt the kernel. No interrupts will be handled.
|
||||
///
|
||||
pub fn haltNoInterrupts() noreturn {
|
||||
while (true) {
|
||||
|
|
@ -183,12 +201,29 @@ pub fn haltNoInterrupts() noreturn {
|
|||
}
|
||||
|
||||
///
|
||||
/// Register an interrupt handler. The interrupt number should be the arch-specific number.
|
||||
/// Initialise the architecture
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN int: u16 - The arch-specific interrupt number to register for.
|
||||
/// IN handler: fn (ctx: *InterruptContext) void - The handler to assign to the interrupt.
|
||||
/// 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 registerInterruptHandler(int: u16, handler: fn (ctx: *InterruptContext) void) void {
|
||||
irq.registerIrq(int, handler);
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
const constants = @import("constants");
|
||||
|
||||
/// The multiboot header
|
||||
const MultiBoot = packed struct {
|
||||
magic: i32,
|
||||
flags: i32,
|
||||
checksum: i32,
|
||||
};
|
||||
|
||||
const ALIGN = 1 << 0;
|
||||
const MEMINFO = 1 << 1;
|
||||
const MAGIC = 0x1BADB002;
|
||||
|
|
@ -9,14 +16,6 @@ const KERNEL_PAGE_NUMBER = constants.KERNEL_ADDR_OFFSET >> 22;
|
|||
// The number of pages occupied by the kernel, will need to be increased as we add a heap etc.
|
||||
const KERNEL_NUM_PAGES = 1;
|
||||
|
||||
extern fn kmain() void;
|
||||
|
||||
const MultiBoot = packed struct {
|
||||
magic: i32,
|
||||
flags: i32,
|
||||
checksum: i32,
|
||||
};
|
||||
|
||||
export var multiboot align(4) linksection(".rodata.boot") = MultiBoot{
|
||||
.magic = MAGIC,
|
||||
.flags = FLAGS,
|
||||
|
|
@ -65,19 +64,23 @@ export var boot_page_directory: [1024]u32 align(4096) linksection(".rodata.boot"
|
|||
|
||||
export var kernel_stack: [16 * 1024]u8 align(16) linksection(".bss.stack") = undefined;
|
||||
|
||||
extern fn kmain() void;
|
||||
|
||||
export nakedcc fn _start() align(16) linksection(".text.boot") noreturn {
|
||||
// Seth the page directory to the boot directory
|
||||
// Set the page directory to the boot directory
|
||||
asm volatile (
|
||||
\\.extern boot_page_directory
|
||||
\\mov $boot_page_directory, %%ecx
|
||||
\\mov %%ecx, %%cr3
|
||||
);
|
||||
|
||||
// Enable 4 MiB pages
|
||||
asm volatile (
|
||||
\\mov %%cr4, %%ecx
|
||||
\\or $0x00000010, %%ecx
|
||||
\\mov %%ecx, %%cr4
|
||||
);
|
||||
|
||||
// Enable paging
|
||||
asm volatile (
|
||||
\\mov %%cr0, %%ecx
|
||||
|
|
@ -91,12 +94,14 @@ export nakedcc fn _start() align(16) linksection(".text.boot") noreturn {
|
|||
export nakedcc fn start_higher_half() noreturn {
|
||||
// Invalidate the page for the first 4MiB as it's no longer needed
|
||||
asm volatile ("invlpg (0)");
|
||||
|
||||
// Setup the stack
|
||||
asm volatile (
|
||||
\\.extern KERNEL_STACK_END
|
||||
\\mov $KERNEL_STACK_END, %%esp
|
||||
\\mov %%esp, %%ebp
|
||||
);
|
||||
|
||||
// Push the bootloader magic number and multiboot header address with virtual offset
|
||||
asm volatile (
|
||||
\\.extern KERNEL_ADDR_OFFSET
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
/// The virtual address where the kernel will be loaded. This is at 3GB.
|
||||
pub const KERNEL_ADDR_OFFSET = 0xC0000000;
|
||||
|
|
|
|||
|
|
@ -1,77 +1,65 @@
|
|||
// Zig version: 0.4.0
|
||||
const std = @import("std");
|
||||
const expect = std.testing.expect;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const builtin = @import("builtin");
|
||||
const is_test = builtin.is_test;
|
||||
|
||||
const arch = @import("arch.zig");
|
||||
const log = @import("../../log.zig");
|
||||
const build_options = @import("build_options");
|
||||
const arch = if (is_test) @import(build_options.arch_mock_path ++ "arch_mock.zig") else @import("arch.zig");
|
||||
const log = if (is_test) @import(build_options.arch_mock_path ++ "log_mock.zig") else @import("../../log.zig");
|
||||
|
||||
const NUMBER_OF_ENTRIES: u16 = 0x06;
|
||||
const TABLE_SIZE: u16 = @sizeOf(GdtEntry) * NUMBER_OF_ENTRIES - 1;
|
||||
/// The access bits for a GDT entry.
|
||||
const AccessBits = packed struct {
|
||||
/// Whether the segment has been access. This shouldn't be set as it is set by the CPU when the
|
||||
/// segment is accessed.
|
||||
accessed: u1,
|
||||
|
||||
// The indexes into the GDT where each segment resides.
|
||||
/// For code segments, when set allows the code segment to be readable. Code segments are
|
||||
/// always executable. For data segments, when set allows the data segment to be writeable.
|
||||
/// Data segments are always readable.
|
||||
read_write: u1,
|
||||
|
||||
/// The index of the NULL GDT entry.
|
||||
const NULL_INDEX: u16 = 0x00;
|
||||
/// For code segments, when set allows this code segments to be executed from a equal or lower
|
||||
/// privilege level. The privilege bits represent the highest privilege level that is allowed
|
||||
/// to execute this segment. If not set, then the code segment can only be executed from the
|
||||
/// same ring level specified in the privilege level bits. For data segments, when set the data
|
||||
/// segment grows downwards. When not set, the data segment grows upwards. So for both code and
|
||||
/// data segments, this shouldn't be set.
|
||||
direction_conforming: u1,
|
||||
|
||||
/// The index of the kernel code GDT entry.
|
||||
const KERNEL_CODE_INDEX: u16 = 0x01;
|
||||
/// When set, the segment can be executed, a code segments. When not set, the segment can't be
|
||||
/// executed, data segment.
|
||||
executable: u1,
|
||||
|
||||
/// The index of the kernel data GDT entry.
|
||||
const KERNEL_DATA_INDEX: u16 = 0x02;
|
||||
/// Should be set for code and data segments, but not set for TSS.
|
||||
descriptor: u1,
|
||||
|
||||
/// The index of the user code GDT entry.
|
||||
const USER_CODE_INDEX: u16 = 0x03;
|
||||
/// Privilege/ring level. The kernel level is level 3, the highest privilege. The user level is
|
||||
/// level 0, the lowest privilege.
|
||||
privilege: u2,
|
||||
|
||||
/// The index of the user data GDT entry.
|
||||
const USER_DATA_INDEX: u16 = 0x04;
|
||||
/// Whether the segment is present. This must be set for all valid selectors, not the null
|
||||
/// segment.
|
||||
present: u1,
|
||||
};
|
||||
|
||||
/// The index of the task state segment GDT entry.
|
||||
const TSS_INDEX: u16 = 0x05;
|
||||
/// The flag bits for a GDT entry.
|
||||
const FlagBits = packed struct {
|
||||
/// The lowest bits must be 0 as this is reserved for future use.
|
||||
reserved_zero: u1,
|
||||
|
||||
// The offsets into the GDT where each segment resides.
|
||||
/// When set indicates the segment is a x86-64 segment. If set, then the IS_32_BIT flag must
|
||||
/// not be set. If both are set, then will throw an exception.
|
||||
is_64_bit: u1,
|
||||
|
||||
/// The offset of the NULL GDT entry.
|
||||
pub const NULL_OFFSET: u16 = 0x00;
|
||||
/// When set indicates the segment is a 32 bit protected mode segment. When not set, indicates
|
||||
/// the segment is a 16 bit protected mode segment.
|
||||
is_32_bit: u1,
|
||||
|
||||
/// The offset of the kernel code GDT entry.
|
||||
pub const KERNEL_CODE_OFFSET: u16 = 0x08;
|
||||
|
||||
/// The offset of the kernel data GDT entry.
|
||||
pub const KERNEL_DATA_OFFSET: u16 = 0x10;
|
||||
|
||||
/// The offset of the user code GDT entry.
|
||||
pub const USER_CODE_OFFSET: u16 = 0x18;
|
||||
|
||||
/// The offset of the user data GDT entry.
|
||||
pub const USER_DATA_OFFSET: u16 = 0x20;
|
||||
|
||||
/// The offset of the TTS GDT entry.
|
||||
pub const TSS_OFFSET: u16 = 0x28;
|
||||
|
||||
// The access bits
|
||||
const ACCESSED_BIT = 0x01; // 00000001
|
||||
const WRITABLE_BIT = 0x02; // 00000010
|
||||
const DIRECTION_CONFORMING_BIT = 0x04; // 00000100
|
||||
const EXECUTABLE_BIT = 0x08; // 00001000
|
||||
const DESCRIPTOR_BIT = 0x10; // 00010000
|
||||
|
||||
const PRIVILEGE_RING_0 = 0x00; // 00000000
|
||||
const PRIVILEGE_RING_1 = 0x20; // 00100000
|
||||
const PRIVILEGE_RING_2 = 0x40; // 01000000
|
||||
const PRIVILEGE_RING_3 = 0x60; // 01100000
|
||||
|
||||
const PRESENT_BIT = 0x80; // 10000000
|
||||
|
||||
const KERNEL_SEGMENT = PRESENT_BIT | PRIVILEGE_RING_0 | DESCRIPTOR_BIT;
|
||||
const USER_SEGMENT = PRESENT_BIT | PRIVILEGE_RING_3 | DESCRIPTOR_BIT;
|
||||
|
||||
const CODE_SEGMENT = EXECUTABLE_BIT | WRITABLE_BIT;
|
||||
const DATA_SEGMENT = WRITABLE_BIT;
|
||||
|
||||
const TSS_SEGMENT = PRESENT_BIT | EXECUTABLE_BIT | ACCESSED_BIT;
|
||||
|
||||
// The flag bits
|
||||
const IS_64_BIT = 0x02; // 0010
|
||||
const IS_32_BIT = 0x04; // 0100
|
||||
const IS_LIMIT_4K_BIT = 0x08; // 1000
|
||||
/// The granularity bit. When set the limit is in 4KB blocks (page granularity). When not set,
|
||||
/// then limit is in 1B blocks (byte granularity). This should be set as we are doing paging.
|
||||
granularity: u1,
|
||||
};
|
||||
|
||||
/// The structure that contains all the information that each GDT entry needs.
|
||||
const GdtEntry = packed struct {
|
||||
|
|
@ -81,41 +69,20 @@ const GdtEntry = packed struct {
|
|||
/// The lower 24 bits of the base address. Describes the start of memory for the entry.
|
||||
base_low: u24,
|
||||
|
||||
/// Bit 0 : accessed - The CPU will set this when the GDT entry is accessed.
|
||||
/// Bit 1 : writable - The writable bit to say if the memory region is writable. If set, then memory region is readable and writable. If not set, then the memory region is just readable.
|
||||
/// Bit 2 : direction_conforming - For a code segment: if set (1), then the code segment can be executed from a lower ring level. If unset (0), then the code segment can only be executed from the same ring level in the privilege flag. For the data segment: if set (1), then the data segment grows downwards. If unset (0), then the data segment grows upwards.
|
||||
/// Bit 3 : executable - The execution bit to say that the memory region is executable.
|
||||
/// Bit 4 : descriptor_bit - The descriptor bit.
|
||||
/// Bit 5-6 : privilege - The ring level of the memory region.
|
||||
/// Bit 7 : present - The present bit to tell that this GDT entry is present.
|
||||
access: u8,
|
||||
/// The access bits, see AccessBits for all the options. 8 bits.
|
||||
access: AccessBits,
|
||||
|
||||
/// The upper 4 bits of the limit address. Describes the size of memory that can be addressed.
|
||||
limit_high: u4,
|
||||
|
||||
/// Bit 0 : reserved_zero - This must always be zero.
|
||||
/// Bit 1 : is_64bits - Whether this is a 64 bit system.
|
||||
/// Bit 2 : is_32bits - Whether this is a 32 bit system.
|
||||
/// Bit 3 : is_limit_4K - Whether paging is turned on, and each address is addressed as if it is a page number not physical/logical linear address.
|
||||
flags: u4,
|
||||
/// The flag bits, see above for all the options. 4 bits.
|
||||
flags: FlagBits,
|
||||
|
||||
/// The upper 8 bits of the base address. Describes the start of memory for the entry.
|
||||
base_high: u8,
|
||||
};
|
||||
|
||||
/// The GDT pointer structure that contains the pointer to the beginning of the GDT and the number
|
||||
/// of the table (minus 1). Used to load the GDT with LGDT instruction.
|
||||
pub const GdtPtr = packed struct {
|
||||
/// 16bit entry for the number of entries (minus 1).
|
||||
limit: u16,
|
||||
|
||||
/// 32bit entry for the base address for the GDT.
|
||||
base: *GdtEntry,
|
||||
};
|
||||
|
||||
///
|
||||
/// The TSS entry structure
|
||||
///
|
||||
const TtsEntry = packed struct {
|
||||
/// Pointer to the previous TSS entry
|
||||
prev_tss: u32,
|
||||
|
|
@ -199,60 +166,186 @@ const TtsEntry = packed struct {
|
|||
io_permissions_base_offset: u16,
|
||||
};
|
||||
|
||||
///
|
||||
/// Make a GDT entry.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN access: u8 - The access bits for the descriptor.
|
||||
/// IN flags: u4 - The flag bits for the descriptor.
|
||||
///
|
||||
/// Return:
|
||||
/// A new GDT entry with the give access and flag bits set with the base at 0x00000000 and limit at 0xFFFFF.
|
||||
///
|
||||
fn makeEntry(base: u32, limit: u20, access: u8, flags: u4) GdtEntry {
|
||||
return GdtEntry{
|
||||
.limit_low = @truncate(u16, limit),
|
||||
.base_low = @truncate(u24, base),
|
||||
.access = access,
|
||||
.limit_high = @truncate(u4, limit >> 16),
|
||||
.flags = flags,
|
||||
.base_high = @truncate(u8, base >> 24),
|
||||
};
|
||||
}
|
||||
/// The GDT pointer structure that contains the pointer to the beginning of the GDT and the number
|
||||
/// of the table (minus 1). Used to load the GDT with LGDT instruction.
|
||||
pub const GdtPtr = packed struct {
|
||||
/// 16bit entry for the number of entries (minus 1).
|
||||
limit: u16,
|
||||
|
||||
/// 32bit entry for the base address for the GDT.
|
||||
base: u32,
|
||||
};
|
||||
|
||||
/// The total number of entries in the GTD: null, kernel code, kernel data, user code, user data
|
||||
/// and TSS
|
||||
const NUMBER_OF_ENTRIES: u16 = 0x06;
|
||||
|
||||
/// The size of the GTD in bytes (minus 1).
|
||||
const TABLE_SIZE: u16 = @sizeOf(GdtEntry) * NUMBER_OF_ENTRIES - 1;
|
||||
|
||||
// ----------
|
||||
// The indexes into the GDT where each segment resides.
|
||||
// ----------
|
||||
|
||||
/// The index of the NULL GDT entry.
|
||||
const NULL_INDEX: u16 = 0x00;
|
||||
|
||||
/// The index of the kernel code GDT entry.
|
||||
const KERNEL_CODE_INDEX: u16 = 0x01;
|
||||
|
||||
/// The index of the kernel data GDT entry.
|
||||
const KERNEL_DATA_INDEX: u16 = 0x02;
|
||||
|
||||
/// The index of the user code GDT entry.
|
||||
const USER_CODE_INDEX: u16 = 0x03;
|
||||
|
||||
/// The index of the user data GDT entry.
|
||||
const USER_DATA_INDEX: u16 = 0x04;
|
||||
|
||||
/// The index of the task state segment GDT entry.
|
||||
const TSS_INDEX: u16 = 0x05;
|
||||
|
||||
/// The null segment, everything is set to zero.
|
||||
const NULL_SEGMENT: AccessBits = AccessBits{
|
||||
.accessed = 0,
|
||||
.read_write = 0,
|
||||
.direction_conforming = 0,
|
||||
.executable = 0,
|
||||
.descriptor = 0,
|
||||
.privilege = 0,
|
||||
.present = 0,
|
||||
};
|
||||
|
||||
/// This bit pattern represents a kernel code segment with bits: readable, executable, descriptor,
|
||||
/// privilege 0, and present set.
|
||||
const KERNEL_SEGMENT_CODE: AccessBits = AccessBits{
|
||||
.accessed = 0,
|
||||
.read_write = 1,
|
||||
.direction_conforming = 0,
|
||||
.executable = 1,
|
||||
.descriptor = 1,
|
||||
.privilege = 0,
|
||||
.present = 1,
|
||||
};
|
||||
|
||||
/// This bit pattern represents a kernel data segment with bits: writeable, descriptor, privilege 0,
|
||||
/// and present set.
|
||||
const KERNEL_SEGMENT_DATA: AccessBits = AccessBits{
|
||||
.accessed = 0,
|
||||
.read_write = 1,
|
||||
.direction_conforming = 0,
|
||||
.executable = 0,
|
||||
.descriptor = 1,
|
||||
.privilege = 0,
|
||||
.present = 1,
|
||||
};
|
||||
|
||||
/// This bit pattern represents a user code segment with bits: readable, executable, descriptor,
|
||||
/// privilege 3, and present set.
|
||||
const USER_SEGMENT_CODE: AccessBits = AccessBits{
|
||||
.accessed = 0,
|
||||
.read_write = 1,
|
||||
.direction_conforming = 0,
|
||||
.executable = 1,
|
||||
.descriptor = 1,
|
||||
.privilege = 3,
|
||||
.present = 1,
|
||||
};
|
||||
|
||||
/// This bit pattern represents a user data segment with bits: writeable, descriptor, privilege 3,
|
||||
/// and present set.
|
||||
const USER_SEGMENT_DATA: AccessBits = AccessBits{
|
||||
.accessed = 0,
|
||||
.read_write = 1,
|
||||
.direction_conforming = 0,
|
||||
.executable = 0,
|
||||
.descriptor = 1,
|
||||
.privilege = 3,
|
||||
.present = 1,
|
||||
};
|
||||
|
||||
/// This bit pattern represents a TSS segment with bits: accessed, executable and present set.
|
||||
const TSS_SEGMENT: AccessBits = AccessBits{
|
||||
.accessed = 1,
|
||||
.read_write = 0,
|
||||
.direction_conforming = 0,
|
||||
.executable = 1,
|
||||
.descriptor = 0,
|
||||
.privilege = 0,
|
||||
.present = 1,
|
||||
};
|
||||
|
||||
/// The bit pattern for all bits set to zero.
|
||||
const NULL_FLAGS: FlagBits = FlagBits{
|
||||
.reserved_zero = 0,
|
||||
.is_64_bit = 0,
|
||||
.is_32_bit = 0,
|
||||
.granularity = 0,
|
||||
};
|
||||
|
||||
/// The bit pattern for all segments where we are in 32 bit protected mode and paging enabled.
|
||||
const PAGING_32_BIT: FlagBits = FlagBits{
|
||||
.reserved_zero = 0,
|
||||
.is_64_bit = 0,
|
||||
.is_32_bit = 1,
|
||||
.granularity = 1,
|
||||
};
|
||||
|
||||
// ----------
|
||||
// The offsets into the GDT where each segment resides.
|
||||
// ----------
|
||||
|
||||
/// The offset of the NULL GDT entry.
|
||||
pub const NULL_OFFSET: u16 = 0x00;
|
||||
|
||||
/// The offset of the kernel code GDT entry.
|
||||
pub const KERNEL_CODE_OFFSET: u16 = 0x08;
|
||||
|
||||
/// The offset of the kernel data GDT entry.
|
||||
pub const KERNEL_DATA_OFFSET: u16 = 0x10;
|
||||
|
||||
/// The offset of the user code GDT entry.
|
||||
pub const USER_CODE_OFFSET: u16 = 0x18;
|
||||
|
||||
/// The offset of the user data GDT entry.
|
||||
pub const USER_DATA_OFFSET: u16 = 0x20;
|
||||
|
||||
/// The offset of the TTS GDT entry.
|
||||
pub const TSS_OFFSET: u16 = 0x28;
|
||||
|
||||
/// The GDT entry table of NUMBER_OF_ENTRIES entries.
|
||||
var gdt_entries: [NUMBER_OF_ENTRIES]GdtEntry = [_]GdtEntry{
|
||||
// Null descriptor
|
||||
makeEntry(0, 0, 0, 0),
|
||||
makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS),
|
||||
|
||||
// Kernel Code
|
||||
makeEntry(0, 0xFFFFF, KERNEL_SEGMENT | CODE_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT),
|
||||
makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_CODE, PAGING_32_BIT),
|
||||
|
||||
// Kernel Data
|
||||
makeEntry(0, 0xFFFFF, KERNEL_SEGMENT | DATA_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT),
|
||||
makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_DATA, PAGING_32_BIT),
|
||||
|
||||
// User Code
|
||||
makeEntry(0, 0xFFFFF, USER_SEGMENT | CODE_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT),
|
||||
makeEntry(0, 0xFFFFF, USER_SEGMENT_CODE, PAGING_32_BIT),
|
||||
|
||||
// User Data
|
||||
makeEntry(0, 0xFFFFF, USER_SEGMENT | DATA_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT),
|
||||
makeEntry(0, 0xFFFFF, USER_SEGMENT_DATA, PAGING_32_BIT),
|
||||
|
||||
// Fill in TSS at runtime
|
||||
makeEntry(0, 0, 0, 0),
|
||||
makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS),
|
||||
};
|
||||
|
||||
/// The GDT pointer that the CPU is loaded with that contains the base address of the GDT and the
|
||||
/// size.
|
||||
const gdt_ptr: GdtPtr = GdtPtr{
|
||||
var gdt_ptr: GdtPtr = GdtPtr{
|
||||
.limit = TABLE_SIZE,
|
||||
.base = &gdt_entries[0],
|
||||
.base = undefined,
|
||||
};
|
||||
|
||||
/// The task state segment entry.
|
||||
var tss: TtsEntry = TtsEntry{
|
||||
.prev_tss = 0,
|
||||
.esp0 = undefined,
|
||||
.ss0 = KERNEL_DATA_OFFSET,
|
||||
.esp0 = 0,
|
||||
.ss0 = u32(KERNEL_DATA_OFFSET),
|
||||
.esp1 = 0,
|
||||
.ss1 = 0,
|
||||
.esp2 = 0,
|
||||
|
|
@ -276,31 +369,316 @@ var tss: TtsEntry = TtsEntry{
|
|||
.gs = 0,
|
||||
.ldtr = 0,
|
||||
.trap = 0,
|
||||
.io_permissions_base_offset = @sizeOf(TtsEntry),
|
||||
.io_permissions_base_offset = u16(@sizeOf(TtsEntry)),
|
||||
};
|
||||
|
||||
///
|
||||
/// Set the stack pointer in the TSS entry
|
||||
/// Make a GDT entry.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN esp0: u32 - The stack pointer
|
||||
/// IN base: u32 - The linear address where the segment begins.
|
||||
/// IN limit: u20 - The maximum addressable unit whether it is 1B units or page units.
|
||||
/// IN access: AccessBits - The access bits for the descriptor.
|
||||
/// IN flags: FlagBits - The flag bits for the descriptor.
|
||||
///
|
||||
/// Return: GdtEntry
|
||||
/// A new GDT entry with the give access and flag bits set with the base at 0x00000000 and
|
||||
/// limit at 0xFFFFF.
|
||||
///
|
||||
fn makeEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntry {
|
||||
return GdtEntry{
|
||||
.limit_low = @truncate(u16, limit),
|
||||
.base_low = @truncate(u24, base),
|
||||
.access = AccessBits{
|
||||
.accessed = access.accessed,
|
||||
.read_write = access.read_write,
|
||||
.direction_conforming = access.direction_conforming,
|
||||
.executable = access.executable,
|
||||
.descriptor = access.descriptor,
|
||||
.privilege = access.privilege,
|
||||
.present = access.present,
|
||||
},
|
||||
.limit_high = @truncate(u4, limit >> 16),
|
||||
.flags = FlagBits{
|
||||
.reserved_zero = flags.reserved_zero,
|
||||
.is_64_bit = flags.is_64_bit,
|
||||
.is_32_bit = flags.is_32_bit,
|
||||
.granularity = flags.granularity,
|
||||
},
|
||||
.base_high = @truncate(u8, base >> 24),
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the stack pointer in the TSS entry.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN esp0: u32 - The stack pointer.
|
||||
///
|
||||
pub fn setTssStack(esp0: u32) void {
|
||||
tss.esp0 = esp0;
|
||||
}
|
||||
|
||||
///
|
||||
/// Initialise the Global Descriptor table
|
||||
/// Initialise the Global Descriptor table.
|
||||
///
|
||||
pub fn init() void {
|
||||
log.logInfo("Init gdt\n");
|
||||
// Initiate TSS
|
||||
gdt_entries[TSS_INDEX] = makeEntry(@ptrToInt(&tss), @sizeOf(TtsEntry) - 1, TSS_SEGMENT, 0);
|
||||
gdt_entries[TSS_INDEX] = makeEntry(@intCast(u32, @ptrToInt(&tss)), @sizeOf(TtsEntry) - 1, TSS_SEGMENT, NULL_FLAGS);
|
||||
|
||||
// Set the base address where all the GDT entries are.
|
||||
gdt_ptr.base = @intCast(u32, @ptrToInt(&gdt_entries[0]));
|
||||
|
||||
// Load the GDT
|
||||
arch.lgdt(&gdt_ptr);
|
||||
|
||||
// Load the TSS
|
||||
arch.ltr();
|
||||
arch.ltr(TSS_OFFSET);
|
||||
|
||||
log.logInfo("Done\n");
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
}
|
||||
|
||||
fn mock_lgdt(ptr: *const GdtPtr) void {
|
||||
expectEqual(TABLE_SIZE, ptr.limit);
|
||||
expectEqual(@intCast(u32, @ptrToInt(&gdt_entries[0])), ptr.base);
|
||||
}
|
||||
|
||||
test "GDT entries" {
|
||||
expectEqual(u32(1), @sizeOf(AccessBits));
|
||||
expectEqual(u32(1), @sizeOf(FlagBits));
|
||||
expectEqual(u32(8), @sizeOf(GdtEntry));
|
||||
expectEqual(u32(104), @sizeOf(TtsEntry));
|
||||
expectEqual(u32(6), @sizeOf(GdtPtr));
|
||||
|
||||
const null_entry = gdt_entries[NULL_INDEX];
|
||||
expectEqual(u64(0), @bitCast(u64, null_entry));
|
||||
|
||||
const kernel_code_entry = gdt_entries[KERNEL_CODE_INDEX];
|
||||
expectEqual(u64(0xCF9A000000FFFF), @bitCast(u64, kernel_code_entry));
|
||||
|
||||
const kernel_data_entry = gdt_entries[KERNEL_DATA_INDEX];
|
||||
expectEqual(u64(0xCF92000000FFFF), @bitCast(u64, kernel_data_entry));
|
||||
|
||||
const user_code_entry = gdt_entries[USER_CODE_INDEX];
|
||||
expectEqual(u64(0xCFFA000000FFFF), @bitCast(u64, user_code_entry));
|
||||
|
||||
const user_data_entry = gdt_entries[USER_DATA_INDEX];
|
||||
expectEqual(u64(0xCFF2000000FFFF), @bitCast(u64, user_data_entry));
|
||||
|
||||
const tss_entry = gdt_entries[TSS_INDEX];
|
||||
expectEqual(u64(0), @bitCast(u64, tss_entry));
|
||||
|
||||
expectEqual(TABLE_SIZE, gdt_ptr.limit);
|
||||
|
||||
expectEqual(u32(0), tss.prev_tss);
|
||||
expectEqual(u32(0), tss.esp0);
|
||||
expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0);
|
||||
expectEqual(u32(0), tss.esp1);
|
||||
expectEqual(u32(0), tss.ss1);
|
||||
expectEqual(u32(0), tss.esp2);
|
||||
expectEqual(u32(0), tss.ss2);
|
||||
expectEqual(u32(0), tss.cr3);
|
||||
expectEqual(u32(0), tss.eip);
|
||||
expectEqual(u32(0), tss.eflags);
|
||||
expectEqual(u32(0), tss.eax);
|
||||
expectEqual(u32(0), tss.ecx);
|
||||
expectEqual(u32(0), tss.edx);
|
||||
expectEqual(u32(0), tss.ebx);
|
||||
expectEqual(u32(0), tss.esp);
|
||||
expectEqual(u32(0), tss.ebp);
|
||||
expectEqual(u32(0), tss.esi);
|
||||
expectEqual(u32(0), tss.edi);
|
||||
expectEqual(u32(0), tss.es);
|
||||
expectEqual(u32(0), tss.cs);
|
||||
expectEqual(u32(0), tss.ss);
|
||||
expectEqual(u32(0), tss.ds);
|
||||
expectEqual(u32(0), tss.fs);
|
||||
expectEqual(u32(0), tss.gs);
|
||||
expectEqual(u32(0), tss.ldtr);
|
||||
expectEqual(u16(0), tss.trap);
|
||||
|
||||
// Size of TtsEntry will fit in a u16 as 104 < 65535 (2^16)
|
||||
expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset);
|
||||
}
|
||||
|
||||
test "makeEntry NULL" {
|
||||
const actual = makeEntry(u32(0), u32(0), NULL_SEGMENT, NULL_FLAGS);
|
||||
|
||||
const expected = u64(0);
|
||||
expectEqual(expected, @bitCast(u64, actual));
|
||||
}
|
||||
|
||||
test "makeEntry alternating bit pattern" {
|
||||
const alt_access = AccessBits{
|
||||
.accessed = 1,
|
||||
.read_write = 0,
|
||||
.direction_conforming = 1,
|
||||
.executable = 0,
|
||||
.descriptor = 1,
|
||||
.privilege = 0b10,
|
||||
.present = 0,
|
||||
};
|
||||
|
||||
expectEqual(u8(0b01010101), @bitCast(u8, alt_access));
|
||||
|
||||
const alt_flag = FlagBits{
|
||||
.reserved_zero = 1,
|
||||
.is_64_bit = 0,
|
||||
.is_32_bit = 1,
|
||||
.granularity = 0,
|
||||
};
|
||||
|
||||
expectEqual(u4(0b0101), @bitCast(u4, alt_flag));
|
||||
|
||||
const actual = makeEntry(u32(0b01010101010101010101010101010101), u20(0b01010101010101010101), alt_access, alt_flag);
|
||||
|
||||
const expected = u64(0b0101010101010101010101010101010101010101010101010101010101010101);
|
||||
expectEqual(expected, @bitCast(u64, actual));
|
||||
}
|
||||
|
||||
test "setTssStack" {
|
||||
// Pre-testing
|
||||
expectEqual(u32(0), tss.prev_tss);
|
||||
expectEqual(u32(0), tss.esp0);
|
||||
expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0);
|
||||
expectEqual(u32(0), tss.esp1);
|
||||
expectEqual(u32(0), tss.ss1);
|
||||
expectEqual(u32(0), tss.esp2);
|
||||
expectEqual(u32(0), tss.ss2);
|
||||
expectEqual(u32(0), tss.cr3);
|
||||
expectEqual(u32(0), tss.eip);
|
||||
expectEqual(u32(0), tss.eflags);
|
||||
expectEqual(u32(0), tss.eax);
|
||||
expectEqual(u32(0), tss.ecx);
|
||||
expectEqual(u32(0), tss.edx);
|
||||
expectEqual(u32(0), tss.ebx);
|
||||
expectEqual(u32(0), tss.esp);
|
||||
expectEqual(u32(0), tss.ebp);
|
||||
expectEqual(u32(0), tss.esi);
|
||||
expectEqual(u32(0), tss.edi);
|
||||
expectEqual(u32(0), tss.es);
|
||||
expectEqual(u32(0), tss.cs);
|
||||
expectEqual(u32(0), tss.ss);
|
||||
expectEqual(u32(0), tss.ds);
|
||||
expectEqual(u32(0), tss.fs);
|
||||
expectEqual(u32(0), tss.gs);
|
||||
expectEqual(u32(0), tss.ldtr);
|
||||
expectEqual(u16(0), tss.trap);
|
||||
expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset);
|
||||
|
||||
// Call function
|
||||
setTssStack(u32(100));
|
||||
|
||||
// Post-testing
|
||||
expectEqual(u32(0), tss.prev_tss);
|
||||
expectEqual(u32(100), tss.esp0);
|
||||
expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0);
|
||||
expectEqual(u32(0), tss.esp1);
|
||||
expectEqual(u32(0), tss.ss1);
|
||||
expectEqual(u32(0), tss.esp2);
|
||||
expectEqual(u32(0), tss.ss2);
|
||||
expectEqual(u32(0), tss.cr3);
|
||||
expectEqual(u32(0), tss.eip);
|
||||
expectEqual(u32(0), tss.eflags);
|
||||
expectEqual(u32(0), tss.eax);
|
||||
expectEqual(u32(0), tss.ecx);
|
||||
expectEqual(u32(0), tss.edx);
|
||||
expectEqual(u32(0), tss.ebx);
|
||||
expectEqual(u32(0), tss.esp);
|
||||
expectEqual(u32(0), tss.ebp);
|
||||
expectEqual(u32(0), tss.esi);
|
||||
expectEqual(u32(0), tss.edi);
|
||||
expectEqual(u32(0), tss.es);
|
||||
expectEqual(u32(0), tss.cs);
|
||||
expectEqual(u32(0), tss.ss);
|
||||
expectEqual(u32(0), tss.ds);
|
||||
expectEqual(u32(0), tss.fs);
|
||||
expectEqual(u32(0), tss.gs);
|
||||
expectEqual(u32(0), tss.ldtr);
|
||||
expectEqual(u16(0), tss.trap);
|
||||
expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset);
|
||||
|
||||
// Clean up
|
||||
setTssStack(u32(0));
|
||||
|
||||
expectEqual(u32(0), tss.prev_tss);
|
||||
expectEqual(u32(0), tss.esp0);
|
||||
expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0);
|
||||
expectEqual(u32(0), tss.esp1);
|
||||
expectEqual(u32(0), tss.ss1);
|
||||
expectEqual(u32(0), tss.esp2);
|
||||
expectEqual(u32(0), tss.ss2);
|
||||
expectEqual(u32(0), tss.cr3);
|
||||
expectEqual(u32(0), tss.eip);
|
||||
expectEqual(u32(0), tss.eflags);
|
||||
expectEqual(u32(0), tss.eax);
|
||||
expectEqual(u32(0), tss.ecx);
|
||||
expectEqual(u32(0), tss.edx);
|
||||
expectEqual(u32(0), tss.ebx);
|
||||
expectEqual(u32(0), tss.esp);
|
||||
expectEqual(u32(0), tss.ebp);
|
||||
expectEqual(u32(0), tss.esi);
|
||||
expectEqual(u32(0), tss.edi);
|
||||
expectEqual(u32(0), tss.es);
|
||||
expectEqual(u32(0), tss.cs);
|
||||
expectEqual(u32(0), tss.ss);
|
||||
expectEqual(u32(0), tss.ds);
|
||||
expectEqual(u32(0), tss.fs);
|
||||
expectEqual(u32(0), tss.gs);
|
||||
expectEqual(u32(0), tss.ldtr);
|
||||
expectEqual(u16(0), tss.trap);
|
||||
expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset);
|
||||
}
|
||||
|
||||
test "init" {
|
||||
// Set up
|
||||
arch.initTest();
|
||||
defer arch.freeTest();
|
||||
|
||||
arch.addTestParams("ltr", TSS_OFFSET);
|
||||
|
||||
arch.addConsumeFunction("lgdt", mock_lgdt);
|
||||
|
||||
// Call function
|
||||
init();
|
||||
|
||||
// Post testing
|
||||
const tss_entry = gdt_entries[TSS_INDEX];
|
||||
const tss_limit = @sizeOf(TtsEntry) - 1;
|
||||
const tss_addr = @ptrToInt(&tss);
|
||||
|
||||
var expected = u64(0);
|
||||
expected |= u64(@truncate(u16, tss_limit));
|
||||
expected |= u64(@truncate(u24, tss_addr)) << 16;
|
||||
expected |= u64(0x89) << (16 + 24);
|
||||
expected |= u64(@truncate(u4, tss_limit >> 16)) << (16 + 24 + 8);
|
||||
// Flags are zero
|
||||
expected |= u64(@truncate(u8, tss_addr >> 24)) << (16 + 24 + 8 + 4 + 4);
|
||||
|
||||
expectEqual(expected, @bitCast(u64, tss_entry));
|
||||
|
||||
// Reset
|
||||
gdt_ptr.base = 0;
|
||||
gdt_entries[TSS_INDEX] = makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS);
|
||||
}
|
||||
|
||||
///
|
||||
/// Check that the GDT table was loaded properly by getting the previously loaded table and
|
||||
/// compare the limit and base address.
|
||||
///
|
||||
fn rt_loadedGDTSuccess() void {
|
||||
const loaded_gdt = arch.sgdt();
|
||||
expect(gdt_ptr.limit == loaded_gdt.limit);
|
||||
expect(gdt_ptr.base == loaded_gdt.base);
|
||||
}
|
||||
|
||||
///
|
||||
/// Run all the runtime tests.
|
||||
///
|
||||
fn runtimeTests() void {
|
||||
rt_loadedGDTSuccess();
|
||||
log.logInfo("GDT: Tested loading GDT\n");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue