pluto/src/kernel/arch/x86/idt.zig

346 lines
10 KiB
Zig
Raw Normal View History

2019-09-17 18:24:27 +01:00
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const builtin = @import("builtin");
const is_test = builtin.is_test;
const panic = @import("../../panic.zig").panic;
2019-09-17 18:24:27 +01:00
const build_options = @import("build_options");
const mock_path = build_options.arch_mock_path;
const gdt = if (is_test) @import(mock_path ++ "gdt_mock.zig") else @import("gdt.zig");
const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig");
const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("../../log.zig");
/// The structure that contains all the information that each IDT entry needs.
pub const IdtEntry = packed struct {
/// The lower 16 bits of the base address of the interrupt handler offset.
base_low: u16,
/// The code segment in the GDT which the handlers will be held.
selector: u16,
/// Must be zero, unused.
zero: u8,
/// The IDT gate type.
gate_type: u4,
/// Must be 0 for interrupt and trap gates.
storage_segment: u1,
/// The minimum ring level that the calling code must have to run the handler. So user code may not be able to run some interrupts.
privilege: u2,
/// Whether the IDT entry is present.
present: u1,
/// The upper 16 bits of the base address of the interrupt handler offset.
base_high: u16,
};
/// The IDT pointer structure that contains the pointer to the beginning of the IDT and the number
/// of the table (minus 1). Used to load the IST with LIDT instruction.
pub const IdtPtr = packed struct {
/// The total size of the IDT (minus 1) in bytes.
limit: u16,
/// The base address where the IDT is located.
2019-09-17 18:24:27 +01:00
base: u32,
};
2020-01-07 13:30:54 +00:00
pub const InterruptHandler = fn () callconv(.Naked) void;
2019-09-17 18:24:27 +01:00
/// The error set for the IDT
pub const IdtError = error{
/// A IDT entry already exists for the provided index.
IdtEntryExists,
};
2019-09-17 18:24:27 +01:00
// ----------
// Task gates
// ----------
/// The base addresses aren't used, so set these to 0. When a interrupt happens, interrupts are not
/// automatically disabled. This is used for referencing the TSS descriptor in the GDT.
const TASK_GATE: u4 = 0x5;
/// Used to specify a interrupt service routine (ISR). When a interrupt happens, interrupts are
/// automatically disabled then enabled upon the IRET instruction which restores the saved EFLAGS.
const INTERRUPT_GATE: u4 = 0xE;
/// Used to specify a interrupt service routine (ISR). When a interrupt happens, interrupts are not
/// automatically disabled and doesn't restores the saved EFLAGS upon the IRET instruction.
const TRAP_GATE: u4 = 0xF;
// ----------
// Privilege levels
// ----------
/// Privilege level 0. Kernel land. The privilege level the calling descriptor minimum will have.
const PRIVILEGE_RING_0: u2 = 0x0;
/// Privilege level 1. The privilege level the calling descriptor minimum will have.
const PRIVILEGE_RING_1: u2 = 0x1;
/// Privilege level 2. The privilege level the calling descriptor minimum will have.
const PRIVILEGE_RING_2: u2 = 0x2;
/// Privilege level 3. User land. The privilege level the calling descriptor minimum will have.
const PRIVILEGE_RING_3: u2 = 0x3;
/// The total size of all the IDT entries (minus 1).
const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1;
/// The total number of entries the IDT can have (2^8).
pub const NUMBER_OF_ENTRIES: u16 = 256;
/// The IDT pointer that the CPU is loaded with that contains the base address of the IDT and the
/// size.
2019-09-17 18:24:27 +01:00
var idt_ptr: IdtPtr = IdtPtr{
.limit = TABLE_SIZE,
2019-09-17 18:24:27 +01:00
.base = 0,
};
/// The IDT entry table of NUMBER_OF_ENTRIES entries. Initially all zeroed.
2019-09-17 18:24:27 +01:00
var idt_entries: [NUMBER_OF_ENTRIES]IdtEntry = [_]IdtEntry{IdtEntry{
.base_low = 0,
.selector = 0,
.zero = 0,
.gate_type = 0,
.storage_segment = 0,
.privilege = 0,
.present = 0,
.base_high = 0,
}} ** NUMBER_OF_ENTRIES;
///
/// Make a IDT entry.
///
/// Arguments:
/// IN base: u32 - The pointer to the interrupt handler.
2019-09-17 18:24:27 +01:00
/// IN selector: u16 - The descriptor segment the interrupt is in. This will usually be the
/// kernels code segment.
2019-09-17 18:24:27 +01:00
/// IN gate_type: u4 - The type of interrupt. This will usually be the INTERRUPT_GATE.
/// IN privilege: u2 - What privilege to call the interrupt in. This will usually be
/// the kernel ring level 0.
///
2019-09-17 18:24:27 +01:00
/// Return: IdtEntry
/// A new IDT entry.
///
2019-09-17 18:24:27 +01:00
fn makeEntry(base: u32, selector: u16, gate_type: u4, privilege: u2) IdtEntry {
return IdtEntry{
.base_low = @truncate(u16, base),
.selector = selector,
.zero = 0,
.gate_type = gate_type,
.storage_segment = 0,
.privilege = privilege,
2019-09-17 18:24:27 +01:00
// Creating a new entry, so is now present.
.present = 1,
.base_high = @truncate(u16, base >> 16),
};
}
///
2019-09-17 18:24:27 +01:00
/// Check whether a IDT gate is open.
///
/// Arguments:
2019-09-17 18:24:27 +01:00
/// IN entry: IdtEntry - The IDT entry to check.
///
/// Return: bool
/// Whether the provided IDT entry is open or not.
///
pub fn isIdtOpen(entry: IdtEntry) bool {
2019-09-17 18:24:27 +01:00
return entry.present == 1;
}
///
2019-09-17 18:24:27 +01:00
/// Open a interrupt gate with a given index and a handler to call.
///
/// Arguments:
2019-09-17 18:24:27 +01:00
/// IN index: u8 - The interrupt number to open.
/// IN handler: InterruptHandler - The interrupt handler for the interrupt.
///
/// Errors:
/// IdtError.InvalidIdtEntry - If the interrupt number is invalid, see isValidInterruptNumber.
/// IdtError.IdtEntryExists - If the interrupt has already been registered.
///
2019-09-17 18:24:27 +01:00
pub fn openInterruptGate(index: u8, handler: InterruptHandler) IdtError!void {
// As the IDT is a u8, that maximum can only be 255 which is the maximum IDT entries.
// So there can't be a out of bounds.
if (isIdtOpen(idt_entries[index])) {
return IdtError.IdtEntryExists;
}
idt_entries[index] = makeEntry(@ptrToInt(handler), gdt.KERNEL_CODE_OFFSET, INTERRUPT_GATE, PRIVILEGE_RING_0);
}
///
/// Initialise the Interrupt descriptor table
///
pub fn init() void {
2020-01-01 19:12:36 +00:00
log.logInfo("Init idt\n", .{});
defer log.logInfo("Done idt\n", .{});
2019-09-17 18:24:27 +01:00
idt_ptr.base = @ptrToInt(&idt_entries);
2019-09-17 18:24:27 +01:00
arch.lidt(&idt_ptr);
2019-09-17 18:24:27 +01:00
switch (build_options.test_mode) {
.Initialisation => runtimeTests(),
else => {},
}
2019-09-17 18:24:27 +01:00
}
2020-01-07 13:30:54 +00:00
fn testHandler0() callconv(.Naked) void {}
fn testHandler1() callconv(.Naked) void {}
2019-09-17 18:24:27 +01:00
fn mock_lidt(ptr: *const IdtPtr) void {
expectEqual(TABLE_SIZE, ptr.limit);
expectEqual(@ptrToInt(&idt_entries[0]), ptr.base);
2019-09-17 18:24:27 +01:00
}
test "IDT entries" {
2019-11-10 12:35:08 +00:00
expectEqual(@as(u32, 8), @sizeOf(IdtEntry));
expectEqual(@as(u32, 6), @sizeOf(IdtPtr));
2019-09-17 18:24:27 +01:00
expectEqual(TABLE_SIZE, idt_ptr.limit);
2019-11-10 12:35:08 +00:00
expectEqual(@as(u32, 0), idt_ptr.base);
2019-09-17 18:24:27 +01:00
}
test "makeEntry alternating bit pattern" {
2019-11-10 12:35:08 +00:00
const actual = makeEntry(0b01010101010101010101010101010101, 0b0101010101010101, 0b0101, 0b01);
2019-09-17 18:24:27 +01:00
2019-11-10 12:35:08 +00:00
const expected: u64 = 0b0101010101010101101001010000000001010101010101010101010101010101;
2019-09-17 18:24:27 +01:00
expectEqual(expected, @bitCast(u64, actual));
}
test "isIdtOpen" {
const not_open = IdtEntry{
.base_low = 0,
.selector = 0,
.zero = 0,
.gate_type = 0,
.storage_segment = 0,
.privilege = 0,
.present = 0,
.base_high = 0,
};
const open = IdtEntry{
.base_low = 0,
.selector = 0,
.zero = 0,
.gate_type = 0,
.storage_segment = 0,
.privilege = 0,
.present = 1,
.base_high = 0,
};
expectEqual(false, isIdtOpen(not_open));
expectEqual(true, isIdtOpen(open));
}
test "openInterruptGate" {
2019-11-10 12:35:08 +00:00
const index: u8 = 100;
2019-09-17 18:24:27 +01:00
openInterruptGate(index, testHandler0) catch unreachable;
expectError(IdtError.IdtEntryExists, openInterruptGate(index, testHandler0));
const test_fn_0_addr = @ptrToInt(testHandler0);
const test_fn_1_addr = @ptrToInt(testHandler1);
2019-09-17 18:24:27 +01:00
const expected_entry0 = IdtEntry{
.base_low = @truncate(u16, test_fn_0_addr),
.selector = gdt.KERNEL_CODE_OFFSET,
.zero = 0,
.gate_type = INTERRUPT_GATE,
.storage_segment = 0,
.privilege = PRIVILEGE_RING_0,
.present = 1,
.base_high = @truncate(u16, test_fn_0_addr >> 16),
};
expectEqual(expected_entry0, idt_entries[index]);
// Reset
idt_entries[index] = IdtEntry{
.base_low = 0,
.selector = 0,
.zero = 0,
.gate_type = 0,
.storage_segment = 0,
.privilege = 0,
.present = 0,
.base_high = 0,
};
openInterruptGate(index, testHandler0) catch unreachable;
// With different handler
expectError(IdtError.IdtEntryExists, openInterruptGate(index, testHandler1));
const expected_entry1 = IdtEntry{
.base_low = @truncate(u16, test_fn_0_addr),
.selector = gdt.KERNEL_CODE_OFFSET,
.zero = 0,
.gate_type = INTERRUPT_GATE,
.storage_segment = 0,
.privilege = PRIVILEGE_RING_0,
.present = 1,
.base_high = @truncate(u16, test_fn_0_addr >> 16),
};
expectEqual(expected_entry1, idt_entries[index]);
// Reset
idt_entries[index] = IdtEntry{
.base_low = 0,
.selector = 0,
.zero = 0,
.gate_type = 0,
.storage_segment = 0,
.privilege = 0,
.present = 0,
.base_high = 0,
};
}
test "init" {
// Set up
arch.initTest();
defer arch.freeTest();
arch.addConsumeFunction("lidt", mock_lidt);
// Call function
init();
// Post testing
expectEqual(@ptrToInt(&idt_entries), idt_ptr.base);
2019-09-17 18:24:27 +01:00
// Reset
idt_ptr.base = 0;
}
///
/// Check that the IDT table was loaded properly by getting the previously loaded table and
/// compare the limit and base address.
///
fn rt_loadedIDTSuccess() void {
const loaded_idt = arch.sidt();
if (idt_ptr.limit != loaded_idt.limit) {
panic(@errorReturnTrace(), "FAILURE: IDT not loaded properly: 0x{X} != 0x{X}\n", .{ idt_ptr.limit, loaded_idt.limit });
}
if (idt_ptr.base != loaded_idt.base) {
panic(@errorReturnTrace(), "FAILURE: IDT not loaded properly: 0x{X} != {X}\n", .{ idt_ptr.base, loaded_idt.base });
}
2020-01-01 19:12:36 +00:00
log.logInfo("IDT: Tested loading IDT\n", .{});
2019-09-17 18:24:27 +01:00
}
///
/// Run all the runtime tests.
///
fn runtimeTests() void {
rt_loadedIDTSuccess();
2019-06-22 10:00:57 +01:00
}