From 9c515cfab08d44132bda4ea129b68ae2f9836d6a Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 25 Feb 2025 19:13:46 -0800 Subject: [PATCH] Added IDT --- tetros/Cargo.lock | 3 +- tetros/Cargo.toml | 1 + tetros/src/idt/entry.rs | 127 +++++++++ tetros/src/idt/handler.rs | 117 +++++++++ tetros/src/idt/mod.rs | 22 ++ tetros/src/idt/stackframe.rs | 142 +++++++++++ tetros/src/idt/table.rs | 482 +++++++++++++++++++++++++++++++++++ tetros/src/idt/util.rs | 10 + tetros/src/idt/virtaddr.rs | 96 +++++++ tetros/src/lib.rs | 44 ++-- 10 files changed, 1028 insertions(+), 16 deletions(-) create mode 100644 tetros/src/idt/entry.rs create mode 100644 tetros/src/idt/handler.rs create mode 100644 tetros/src/idt/mod.rs create mode 100644 tetros/src/idt/stackframe.rs create mode 100644 tetros/src/idt/table.rs create mode 100644 tetros/src/idt/util.rs create mode 100644 tetros/src/idt/virtaddr.rs diff --git a/tetros/Cargo.lock b/tetros/Cargo.lock index cda49db..46cf528 100644 --- a/tetros/Cargo.lock +++ b/tetros/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "autocfg" @@ -79,6 +79,7 @@ dependencies = [ name = "tetros" version = "1.0.0" dependencies = [ + "bitflags 2.8.0", "lazy_static", "spin", "uart_16550", diff --git a/tetros/Cargo.toml b/tetros/Cargo.toml index 7589804..7741742 100644 --- a/tetros/Cargo.toml +++ b/tetros/Cargo.toml @@ -64,6 +64,7 @@ identity_op = "allow" [dependencies] +bitflags = "2.8.0" spin = "0.9.8" uart_16550 = "0.3.2" diff --git a/tetros/src/idt/entry.rs b/tetros/src/idt/entry.rs new file mode 100644 index 0000000..a8056d5 --- /dev/null +++ b/tetros/src/idt/entry.rs @@ -0,0 +1,127 @@ +use core::{ + fmt::{self}, + marker::PhantomData, +}; + +use super::{HandlerFuncType, VirtAddr}; + +/// An Interrupt Descriptor Table entry. +/// +/// The generic parameter is some [`HandlerFuncType`], depending on the interrupt vector. +/// +/// For reference, see https://wiki.osdev.org/Interrupt_Descriptor_Table#Gate_Descriptor +#[derive(Clone, Copy)] +#[repr(C)] +pub struct Entry { + phantom: PhantomData, + + /// Low 16 bits of the 32-bit ISR pointer + pointer_low: u16, + + /// A segment selector that points to a valid code segment in the GDT + segment: u16, + + /// Always zero + reserved: u8, + + /// Interrupt options. + /// + /// Bits 0-3: gate type. One of: + /// - 0101: Task Gate, note that in this case, the Offset value is unused and should be set to zero. + /// - 0110: 16-bit Interrupt Gate + /// - 0111: 16-bit Trap Gate + /// - 1110: 32-bit Interrupt Gate + /// - 1111: 32-bit Trap Gate + /// + /// Bit 4: always zero + /// + /// Bits 5-6: DPL, defines cpu privilege level require to INT this interrupt. + /// For our purposes, always zero. + /// + /// Bit 7: 1 if present, 0 if not. + /// + /// Note that these appear in reverse order in Rust. + /// For example, 0b1000_1110 sets `present = 1` and `type = 1110`. + options: u8, + + /// High 16 bits of the 32-bit ISR pointer + pointer_high: u16, +} + +impl fmt::Debug for Entry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Entry") + .field("preset", &self.is_present()) + .field( + "handler_addr", + &format_args!("{:#x}", self.handler_addr().unwrap_or(VirtAddr::new(0))), + ) + .field("options", &self.options) + .finish() + } +} + +impl Entry { + /// Create a valid non-present IDT entry. + #[inline] + pub const fn missing() -> Self { + Entry { + pointer_low: 0, + segment: 0, + reserved: 0, + options: 0, + pointer_high: 0, + phantom: PhantomData, + } + } + + /// Sets the handler address for the IDT entry and sets the following defaults: + /// - The code selector is the code segment currently active in the CPU + /// - The present bit is set + /// - Interrupts are disabled on handler invocation + /// - The privilege level (DPL) is [`PrivilegeLevel::Ring0`] + /// - No IST is configured (existing stack will be used) + /// - This is a 32-bit interrupt gate + /// + /// # Safety + /// + /// The caller must ensure that `addr` is the address of a valid interrupt handler function, + /// and the signature of such a function is correct for the entry type. + #[inline] + pub unsafe fn set_handler_addr(&mut self, addr: VirtAddr) { + let ptr = addr.as_u32(); + + self.pointer_low = ptr as u16; + self.pointer_high = (ptr >> 16) as u16; + self.options = 0b1000_1110; // Present 32-bit interrupt gate + + // SAFETY: The current CS is a valid, long-mode code segment. + self.segment = super::util::get_cs(); + } + + /// True if the "present" bit is set, false otherwise. + pub fn is_present(&self) -> bool { + return self.options & 0b1000_0000 == 0b1000_0000; + } + + /// Get the address of this entry's handler. + pub fn handler_addr(&self) -> Option { + self.is_present().then_some(VirtAddr::new( + self.pointer_low as u32 + ((self.pointer_high as u32) << 16), + )) + } +} + +impl Entry { + /// Sets the handler address for the IDT entry and sets the following defaults: + /// - The code selector is the code segment currently active in the CPU + /// - The present bit is set + /// - Interrupts are disabled on handler invocation + /// - The privilege level (DPL) is [`PrivilegeLevel::Ring0`] + /// - No IST is configured (existing stack will be used) + /// - This is a 32-bit interrupt gate + #[inline] + pub fn set_handler_fn(&mut self, handler: F) { + unsafe { self.set_handler_addr(handler.to_virt_addr()) } + } +} diff --git a/tetros/src/idt/handler.rs b/tetros/src/idt/handler.rs new file mode 100644 index 0000000..66fe96c --- /dev/null +++ b/tetros/src/idt/handler.rs @@ -0,0 +1,117 @@ +use bitflags::bitflags; + +use super::{InterruptStackFrame, VirtAddr}; + +bitflags! { + /// Describes an page fault error code. + /// + /// This structure is defined by the following manual sections: + /// * AMD Volume 2: 8.4.2 + /// * Intel Volume 3A: 4.7 + #[repr(transparent)] + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] + pub struct PageFaultErrorCode: u64 { + /// If this flag is set, the page fault was caused by a page-protection violation, + /// else the page fault was caused by a not-present page. + const PROTECTION_VIOLATION = 1; + + /// If this flag is set, the memory access that caused the page fault was a write. + /// Else the access that caused the page fault is a memory read. This bit does not + /// necessarily indicate the cause of the page fault was a read or write violation. + const CAUSED_BY_WRITE = 1 << 1; + + /// If this flag is set, an access in user mode (CPL=3) caused the page fault. Else + /// an access in supervisor mode (CPL=0, 1, or 2) caused the page fault. This bit + /// does not necessarily indicate the cause of the page fault was a privilege violation. + const USER_MODE = 1 << 2; + + /// If this flag is set, the page fault is a result of the processor reading a 1 from + /// a reserved field within a page-translation-table entry. + const MALFORMED_TABLE = 1 << 3; + + /// If this flag is set, it indicates that the access that caused the page fault was an + /// instruction fetch. + const INSTRUCTION_FETCH = 1 << 4; + + /// If this flag is set, it indicates that the page fault was caused by a protection key. + const PROTECTION_KEY = 1 << 5; + + /// If this flag is set, it indicates that the page fault was caused by a shadow stack + /// access. + const SHADOW_STACK = 1 << 6; + + /// If this flag is set, it indicates that the page fault was caused by SGX access-control + /// requirements (Intel-only). + const SGX = 1 << 15; + + /// If this flag is set, it indicates that the page fault is a result of the processor + /// encountering an RMP violation (AMD-only). + const RMP = 1 << 31; + } +} + +/// +/// MARK: types +/// + +/// A handler function for an interrupt or an exception without error code. +pub type HandlerFunc = extern "x86-interrupt" fn(InterruptStackFrame); + +/// A handler function for an exception that pushes an error code. +pub type HandlerFuncWithErrCode = extern "x86-interrupt" fn(InterruptStackFrame, error_code: u64); + +/// A handler function that must not return, e.g. for a machine check exception. +pub type DivergingHandlerFunc = extern "x86-interrupt" fn(InterruptStackFrame) -> !; + +/// A handler function with an error code that must not return, e.g. for a double fault exception. +pub type DivergingHandlerFuncWithErrCode = + extern "x86-interrupt" fn(InterruptStackFrame, error_code: u64) -> !; + +/// A page fault handler function that pushes a page fault error code. +pub type PageFaultHandlerFunc = + extern "x86-interrupt" fn(InterruptStackFrame, error_code: PageFaultErrorCode); + +/// A common trait for all handler functions usable in [`Entry`]. +/// +/// # Safety +/// +/// Implementors have to ensure that `to_virt_addr` returns a valid address. +pub unsafe trait HandlerFuncType { + /// Get the virtual address of the handler function. + fn to_virt_addr(self) -> VirtAddr; +} + +unsafe impl HandlerFuncType for HandlerFunc { + #[inline] + fn to_virt_addr(self) -> VirtAddr { + VirtAddr::new(self as u32) + } +} + +unsafe impl HandlerFuncType for HandlerFuncWithErrCode { + #[inline] + fn to_virt_addr(self) -> VirtAddr { + VirtAddr::new(self as u32) + } +} + +unsafe impl HandlerFuncType for DivergingHandlerFunc { + #[inline] + fn to_virt_addr(self) -> VirtAddr { + VirtAddr::new(self as u32) + } +} + +unsafe impl HandlerFuncType for DivergingHandlerFuncWithErrCode { + #[inline] + fn to_virt_addr(self) -> VirtAddr { + VirtAddr::new(self as u32) + } +} + +unsafe impl HandlerFuncType for PageFaultHandlerFunc { + #[inline] + fn to_virt_addr(self) -> VirtAddr { + VirtAddr::new(self as u32) + } +} diff --git a/tetros/src/idt/mod.rs b/tetros/src/idt/mod.rs new file mode 100644 index 0000000..809be38 --- /dev/null +++ b/tetros/src/idt/mod.rs @@ -0,0 +1,22 @@ +//! IDT structures and routines for 32-bit x86. +//! +//! Based on code from the `x86_64` crate. +//! Many comments are copied verbatim. +//! (most notably, in `table.rs`) + +mod virtaddr; +pub use virtaddr::*; + +mod entry; +pub use entry::*; + +mod table; +pub use table::*; + +mod handler; +pub use handler::*; + +mod stackframe; +pub use stackframe::*; + +mod util; diff --git a/tetros/src/idt/stackframe.rs b/tetros/src/idt/stackframe.rs new file mode 100644 index 0000000..e0c82e1 --- /dev/null +++ b/tetros/src/idt/stackframe.rs @@ -0,0 +1,142 @@ +use core::{fmt, ops::Deref}; + +use super::VirtAddr; + +use bitflags::bitflags; + +bitflags! { + /// The EFLAGS register. All bit patterns are valid representations for this type. + /// + /// See https://wiki.osdev.org/CPU_Registers_x86#EFLAGS_Register + #[repr(transparent)] + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] + pub struct EFlags: u32 { + /// Processor feature identification flag. + /// + /// If this flag is modifiable, the CPU supports CPUID. + const ID = 1 << 21; + + /// Indicates that an external, maskable interrupt is pending. + /// + /// Used when virtual-8086 mode extensions (CR4.VME) or protected-mode virtual + /// interrupts (CR4.PVI) are activated. + const VIRTUAL_INTERRUPT_PENDING = 1 << 20; + + /// Virtual image of the INTERRUPT_FLAG bit. + /// + /// Used when virtual-8086 mode extensions (CR4.VME) or protected-mode virtual + /// interrupts (CR4.PVI) are activated. + const VIRTUAL_INTERRUPT = 1 << 19; + + /// Enable automatic alignment checking if CR0.AM is set. Only works if CPL is 3. + const ALIGNMENT_CHECK = 1 << 18; + + /// Enable the virtual-8086 mode. + const VIRTUAL_8086_MODE = 1 << 17; + + /// Allows to restart an instruction following an instruction breakpoint. + const RESUME_FLAG = 1 << 16; + + /// Used by `iret` in hardware task switch mode to determine if current task is nested. + const NESTED_TASK = 1 << 14; + + /// The high bit of the I/O Privilege Level field. + /// + /// Specifies the privilege level required for executing I/O address-space instructions. + const IOPL_HIGH = 1 << 13; + + /// The low bit of the I/O Privilege Level field. + /// + /// Specifies the privilege level required for executing I/O address-space instructions. + const IOPL_LOW = 1 << 12; + + /// Set by hardware to indicate that the sign bit of the result of the last signed integer + /// operation differs from the source operands. + const OVERFLOW_FLAG = 1 << 11; + + /// Determines the order in which strings are processed. + const DIRECTION_FLAG = 1 << 10; + + /// Enable interrupts. + const INTERRUPT_FLAG = 1 << 9; + + /// Enable single-step mode for debugging. + const TRAP_FLAG = 1 << 8; + + /// Set by hardware if last arithmetic operation resulted in a negative value. + const SIGN_FLAG = 1 << 7; + + /// Set by hardware if last arithmetic operation resulted in a zero value. + const ZERO_FLAG = 1 << 6; + + /// Set by hardware if last arithmetic operation generated a carry ouf of bit 3 of the + /// result. + const AUXILIARY_CARRY_FLAG = 1 << 4; + + /// Set by hardware if last result has an even number of 1 bits (only for some operations). + const PARITY_FLAG = 1 << 2; + + /// Set by hardware if last arithmetic operation generated a carry out of the + /// most-significant bit of the result. + const CARRY_FLAG = 1; + } +} + +/// Wrapper type for the interrupt stack frame pushed by the CPU. +/// +/// This type derefs to an [`InterruptStackFrameValue`], which allows reading the actual values. +/// +/// This wrapper type ensures that no accidental modification of the interrupt stack frame +/// occurs, which can cause undefined behavior (see the [`as_mut`](InterruptStackFrame::as_mut) +/// method for more information). +#[repr(transparent)] +pub struct InterruptStackFrame(InterruptStackFrameValue); + +impl Deref for InterruptStackFrame { + type Target = InterruptStackFrameValue; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Debug for InterruptStackFrame { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Represents the interrupt stack frame pushed by the CPU on interrupt or exception entry. +/// +/// See https://wiki.osdev.org/Interrupt_Service_Routines#x86 +#[derive(Clone, Copy)] +#[repr(C)] +pub struct InterruptStackFrameValue { + /// This value points to the instruction that should be executed when the interrupt + /// handler returns. For most interrupts, this value points to the instruction immediately + /// following the last executed instruction. However, for some exceptions (e.g., page faults), + /// this value points to the faulting instruction, so that the instruction is restarted on + /// return. + pub eip: VirtAddr, + + /// The code segment selector at the time of the interrupt. + pub cs: u16, + + /// Padding for CS + _reserved1: [u8; 2], + + /// The EFLAGS register before the interrupt handler was invoked. + pub cpu_flags: EFlags, +} + +impl fmt::Debug for InterruptStackFrameValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("InterruptStackFrame"); + s.field("instruction_pointer", &self.eip); + s.field("code_segment", &self.cs); + s.field("cpu_flags", &self.cpu_flags); + s.finish() + } +} diff --git a/tetros/src/idt/table.rs b/tetros/src/idt/table.rs new file mode 100644 index 0000000..92b7478 --- /dev/null +++ b/tetros/src/idt/table.rs @@ -0,0 +1,482 @@ +use core::{arch::asm, mem::size_of}; + +use super::{ + handler::HandlerFunc, DivergingHandlerFunc, DivergingHandlerFuncWithErrCode, Entry, + HandlerFuncWithErrCode, PageFaultHandlerFunc, +}; + +// TODO: comments +#[repr(C, packed(2))] +struct IDTR { + size: u16, + offset: u32, +} + +// +// MARK: idt +// + +#[derive(Clone, Debug)] +#[repr(C)] +#[repr(align(8))] +pub struct InterruptDescriptorTable { + /// A divide error (`#DE`) occurs when the denominator of a DIV instruction or + /// an IDIV instruction is 0. A `#DE` also occurs if the result is too large to be + /// represented in the destination. + /// + /// The saved instruction pointer points to the instruction that caused the `#DE`. + /// + /// The vector number of the `#DE` exception is 0. + pub divide_error: Entry, + + /// When the debug-exception mechanism is enabled, a `#DB` exception can occur under any + /// of the following circumstances: + /// + ///
+ /// + /// - Instruction execution. + /// - Instruction single stepping. + /// - Data read. + /// - Data write. + /// - I/O read. + /// - I/O write. + /// - Task switch. + /// - Debug-register access, or general detect fault (debug register access when DR7.GD=1). + /// - Executing the INT1 instruction (opcode 0F1h). + /// + ///
+ /// + /// `#DB` conditions are enabled and disabled using the debug-control register, `DR7` + /// and `RFLAGS.TF`. + /// + /// In the following cases, the saved instruction pointer points to the instruction that + /// caused the `#DB`: + /// + /// - Instruction execution. + /// - Invalid debug-register access, or general detect. + /// + /// In all other cases, the instruction that caused the `#DB` is completed, and the saved + /// instruction pointer points to the instruction after the one that caused the `#DB`. + /// + /// The vector number of the `#DB` exception is 1. + pub debug: Entry, + + /// An non maskable interrupt exception (NMI) occurs as a result of system logic + /// signaling a non-maskable interrupt to the processor. + /// + /// The processor recognizes an NMI at an instruction boundary. + /// The saved instruction pointer points to the instruction immediately following the + /// boundary where the NMI was recognized. + /// + /// The vector number of the NMI exception is 2. + pub non_maskable_interrupt: Entry, + + /// A breakpoint (`#BP`) exception occurs when an `INT3` instruction is executed. The + /// `INT3` is normally used by debug software to set instruction breakpoints by replacing + /// + /// The saved instruction pointer points to the byte after the `INT3` instruction. + /// + /// The vector number of the `#BP` exception is 3. + pub breakpoint: Entry, + + /// An overflow exception (`#OF`) occurs as a result of executing an `INTO` instruction + /// while the overflow bit in `RFLAGS` is set to 1. + /// + /// The saved instruction pointer points to the instruction following the `INTO` + /// instruction that caused the `#OF`. + /// + /// The vector number of the `#OF` exception is 4. + pub overflow: Entry, + + /// A bound-range exception (`#BR`) exception can occur as a result of executing + /// the `BOUND` instruction. The `BOUND` instruction compares an array index (first + /// operand) with the lower bounds and upper bounds of an array (second operand). + /// If the array index is not within the array boundary, the `#BR` occurs. + /// + /// The saved instruction pointer points to the `BOUND` instruction that caused the `#BR`. + /// + /// The vector number of the `#BR` exception is 5. + pub bound_range_exceeded: Entry, + + /// An invalid opcode exception (`#UD`) occurs when an attempt is made to execute an + /// invalid or undefined opcode. The validity of an opcode often depends on the + /// processor operating mode. + /// + ///
A `#UD` occurs under the following conditions: + /// + /// - Execution of any reserved or undefined opcode in any mode. + /// - Execution of the `UD2` instruction. + /// - Use of the `LOCK` prefix on an instruction that cannot be locked. + /// - Use of the `LOCK` prefix on a lockable instruction with a non-memory target location. + /// - Execution of an instruction with an invalid-operand type. + /// - Execution of the `SYSENTER` or `SYSEXIT` instructions in long mode. + /// - Execution of any of the following instructions in 64-bit mode: `AAA`, `AAD`, + /// `AAM`, `AAS`, `BOUND`, `CALL` (opcode 9A), `DAA`, `DAS`, `DEC`, `INC`, `INTO`, + /// `JMP` (opcode EA), `LDS`, `LES`, `POP` (`DS`, `ES`, `SS`), `POPA`, `PUSH` (`CS`, + /// `DS`, `ES`, `SS`), `PUSHA`, `SALC`. + /// - Execution of the `ARPL`, `LAR`, `LLDT`, `LSL`, `LTR`, `SLDT`, `STR`, `VERR`, or + /// `VERW` instructions when protected mode is not enabled, or when virtual-8086 mode + /// is enabled. + /// - Execution of any legacy SSE instruction when `CR4.OSFXSR` is cleared to 0. + /// - Execution of any SSE instruction (uses `YMM`/`XMM` registers), or 64-bit media + /// instruction (uses `MMXTM` registers) when `CR0.EM` = 1. + /// - Execution of any SSE floating-point instruction (uses `YMM`/`XMM` registers) that + /// causes a numeric exception when `CR4.OSXMMEXCPT` = 0. + /// - Use of the `DR4` or `DR5` debug registers when `CR4.DE` = 1. + /// - Execution of `RSM` when not in `SMM` mode. + /// + ///
+ /// + /// The saved instruction pointer points to the instruction that caused the `#UD`. + /// + /// The vector number of the `#UD` exception is 6. + pub invalid_opcode: Entry, + + /// A device not available exception (`#NM`) occurs under any of the following conditions: + /// + ///
+ /// + /// - An `FWAIT`/`WAIT` instruction is executed when `CR0.MP=1` and `CR0.TS=1`. + /// - Any x87 instruction other than `FWAIT` is executed when `CR0.EM=1`. + /// - Any x87 instruction is executed when `CR0.TS=1`. The `CR0.MP` bit controls whether the + /// `FWAIT`/`WAIT` instruction causes an `#NM` exception when `TS=1`. + /// - Any 128-bit or 64-bit media instruction when `CR0.TS=1`. + /// + ///
+ /// + /// The saved instruction pointer points to the instruction that caused the `#NM`. + /// + /// The vector number of the `#NM` exception is 7. + pub device_not_available: Entry, + + /// A double fault (`#DF`) exception can occur when a second exception occurs during + /// the handling of a prior (first) exception or interrupt handler. + /// + ///
+ /// + /// Usually, the first and second exceptions can be handled sequentially without + /// resulting in a `#DF`. In this case, the first exception is considered _benign_, as + /// it does not harm the ability of the processor to handle the second exception. In some + /// cases, however, the first exception adversely affects the ability of the processor to + /// handle the second exception. These exceptions contribute to the occurrence of a `#DF`, + /// and are called _contributory exceptions_. The following exceptions are contributory: + /// + /// - Invalid-TSS Exception + /// - Segment-Not-Present Exception + /// - Stack Exception + /// - General-Protection Exception + /// + /// A double-fault exception occurs in the following cases: + /// + /// - If a contributory exception is followed by another contributory exception. + /// - If a divide-by-zero exception is followed by a contributory exception. + /// - If a page fault is followed by another page fault or a contributory exception. + /// + /// If a third interrupting event occurs while transferring control to the `#DF` handler, + /// the processor shuts down. + /// + ///
+ /// + /// The returned error code is always zero. The saved instruction pointer is undefined, + /// and the program cannot be restarted. + /// + /// The vector number of the `#DF` exception is 8. + pub double_fault: Entry, + + /// This interrupt vector is reserved. It is for a discontinued exception originally used + /// by processors that supported external x87-instruction coprocessors. On those processors, + /// the exception condition is caused by an invalid-segment or invalid-page access on an + /// x87-instruction coprocessor-instruction operand. On current processors, this condition + /// causes a general-protection exception to occur. + coprocessor_segment_overrun: Entry, + + /// An invalid TSS exception (`#TS`) occurs only as a result of a control transfer through + /// a gate descriptor that results in an invalid stack-segment reference using an `SS` + /// selector in the TSS. + /// + /// The returned error code is the `SS` segment selector. The saved instruction pointer + /// points to the control-transfer instruction that caused the `#TS`. + /// + /// The vector number of the `#TS` exception is 10. + pub invalid_tss: Entry, + + /// An segment-not-present exception (`#NP`) occurs when an attempt is made to load a + /// segment or gate with a clear present bit. + /// + /// The returned error code is the segment-selector index of the segment descriptor + /// causing the `#NP` exception. The saved instruction pointer points to the instruction + /// that loaded the segment selector resulting in the `#NP`. + /// + /// The vector number of the `#NP` exception is 11. + pub segment_not_present: Entry, + + /// An stack segment exception (`#SS`) can occur in the following situations: + /// + /// - Implied stack references in which the stack address is not in canonical + /// form. Implied stack references include all push and pop instructions, and any + /// instruction using `RSP` or `RBP` as a base register. + /// - Attempting to load a stack-segment selector that references a segment descriptor + /// containing a clear present bit. + /// - Any stack access that fails the stack-limit check. + /// + /// The returned error code depends on the cause of the `#SS`. If the cause is a cleared + /// present bit, the error code is the corresponding segment selector. Otherwise, the + /// error code is zero. The saved instruction pointer points to the instruction that + /// caused the `#SS`. + /// + /// The vector number of the `#NP` exception is 12. + pub stack_segment_fault: Entry, + + /// A general protection fault (`#GP`) can occur in various situations. Common causes include: + /// + /// - Executing a privileged instruction while `CPL > 0`. + /// - Writing a 1 into any register field that is reserved, must be zero (MBZ). + /// - Attempting to execute an SSE instruction specifying an unaligned memory operand. + /// - Loading a non-canonical base address into the `GDTR` or `IDTR`. + /// - Using WRMSR to write a read-only MSR. + /// - Any long-mode consistency-check violation. + /// + /// The returned error code is a segment selector, if the cause of the `#GP` is + /// segment-related, and zero otherwise. The saved instruction pointer points to + /// the instruction that caused the `#GP`. + /// + /// The vector number of the `#GP` exception is 13. + pub general_protection_fault: Entry, + + /// A page fault (`#PF`) can occur during a memory access in any of the following situations: + /// + /// - A page-translation-table entry or physical page involved in translating the memory + /// access is not present in physical memory. This is indicated by a cleared present + /// bit in the translation-table entry. + /// - An attempt is made by the processor to load the instruction TLB with a translation + /// for a non-executable page. + /// - The memory access fails the paging-protection checks (user/supervisor, read/write, + /// or both). + /// - A reserved bit in one of the page-translation-table entries is set to 1. A `#PF` + /// occurs for this reason only when `CR4.PSE=1` or `CR4.PAE=1`. + /// + /// The virtual (linear) address that caused the `#PF` is stored in the `CR2` register. + /// The saved instruction pointer points to the instruction that caused the `#PF`. + /// + /// The page-fault error code is described by the + /// [`PageFaultErrorCode`](struct.PageFaultErrorCode.html) struct. + /// + /// The vector number of the `#PF` exception is 14. + pub page_fault: Entry, + + /// vector nr. 15 + reserved_1: Entry, + + /// The x87 Floating-Point Exception-Pending exception (`#MF`) is used to handle unmasked x87 + /// floating-point exceptions. In 64-bit mode, the x87 floating point unit is not used + /// anymore, so this exception is only relevant when executing programs in the 32-bit + /// compatibility mode. + /// + /// The vector number of the `#MF` exception is 16. + pub x87_floating_point: Entry, + + /// An alignment check exception (`#AC`) occurs when an unaligned-memory data reference + /// is performed while alignment checking is enabled. An `#AC` can occur only when CPL=3. + /// + /// The returned error code is always zero. The saved instruction pointer points to the + /// instruction that caused the `#AC`. + /// + /// The vector number of the `#AC` exception is 17. + pub alignment_check: Entry, + + /// The machine check exception (`#MC`) is model specific. Processor implementations + /// are not required to support the `#MC` exception, and those implementations that do + /// support `#MC` can vary in how the `#MC` exception mechanism works. + /// + /// There is no reliable way to restart the program. + /// + /// The vector number of the `#MC` exception is 18. + pub machine_check: Entry, + + /// The SIMD Floating-Point Exception (`#XF`) is used to handle unmasked SSE + /// floating-point exceptions. The SSE floating-point exceptions reported by + /// the `#XF` exception are (including mnemonics): + /// + /// - IE: Invalid-operation exception (also called #I). + /// - DE: Denormalized-operand exception (also called #D). + /// - ZE: Zero-divide exception (also called #Z). + /// - OE: Overflow exception (also called #O). + /// - UE: Underflow exception (also called #U). + /// - PE: Precision exception (also called #P or inexact-result exception). + /// + /// The saved instruction pointer points to the instruction that caused the `#XF`. + /// + /// The vector number of the `#XF` exception is 19. + pub simd_floating_point: Entry, + + /// vector nr. 20 + pub virtualization: Entry, + + /// A #CP exception is generated when shadow stacks are enabled and mismatch + /// scenarios are detected (possible error code cases below). + /// + /// The error code is the #CP error code, for each of the following situations: + /// - A RET (near) instruction encountered a return address mismatch. + /// - A RET (far) instruction encountered a return address mismatch. + /// - A RSTORSSP instruction encountered an invalid shadow stack restore token. + /// - A SETSSBY instruction encountered an invalid supervisor shadow stack token. + /// - A missing ENDBRANCH instruction if indirect branch tracking is enabled. + /// + /// vector nr. 21 + pub cp_protection_exception: Entry, + + /// vector nr. 22-27 + reserved_2: [Entry; 6], + + /// The Hypervisor Injection Exception (`#HV`) is injected by a hypervisor + /// as a doorbell to inform an `SEV-SNP` enabled guest running with the + /// `Restricted Injection` feature of events to be processed. + /// + /// `SEV-SNP` stands for the _"Secure Nested Paging"_ feature of the _"AMD + /// Secure Encrypted Virtualization"_ technology. The `Restricted + /// Injection` feature disables all hypervisor-based interrupt queuing + /// and event injection of all vectors except #HV. + /// + /// The `#HV` exception is a benign exception and can only be injected as + /// an exception and without an error code. `SEV-SNP` enabled guests are + /// expected to communicate with the hypervisor about events via a + /// software-managed para-virtualization interface. + /// + /// The vector number of the ``#HV`` exception is 28. + pub hv_injection_exception: Entry, + + /// The VMM Communication Exception (`#VC`) is always generated by hardware when an `SEV-ES` + /// enabled guest is running and an `NAE` event occurs. + /// + /// `SEV-ES` stands for the _"Encrypted State"_ feature of the _"AMD Secure Encrypted Virtualization"_ + /// technology. `NAE` stands for an _"Non-Automatic Exit"_, which is an `VMEXIT` event that requires + /// hypervisor emulation. See + /// [this whitepaper](https://www.amd.com/system/files/TechDocs/Protecting%20VM%20Register%20State%20with%20SEV-ES.pdf) + /// for an overview of the `SEV-ES` feature. + /// + /// The `#VC` exception is a precise, contributory, fault-type exception utilizing exception vector 29. + /// This exception cannot be masked. The error code of the `#VC` exception is equal + /// to the `#VMEXIT` code of the event that caused the `NAE`. + /// + /// In response to a `#VC` exception, a typical flow would involve the guest handler inspecting the error + /// code to determine the cause of the exception and deciding what register state must be copied to the + /// `GHCB` (_"Guest Hypervisor Communication Block"_) for the event to be handled. The handler + /// should then execute the `VMGEXIT` instruction to + /// create an `AE` and invoke the hypervisor. After a later `VMRUN`, guest execution will resume after the + /// `VMGEXIT` instruction where the handler can view the results from the hypervisor and copy state from + /// the `GHCB` back to its internal state as needed. + /// + /// Note that it is inadvisable for the hypervisor to set the `VMCB` (_"Virtual Machine Control Block"_) + /// intercept bit for the `#VC` exception as + /// this would prevent proper handling of `NAE`s by the guest. Similarly, the hypervisor should avoid + /// setting intercept bits for events that would occur in the `#VC` handler (such as `IRET`). + /// + /// The vector number of the ``#VC`` exception is 29. + pub vmm_communication_exception: Entry, + + /// The Security Exception (`#SX`) signals security-sensitive events that occur while + /// executing the VMM, in the form of an exception so that the VMM may take appropriate + /// action. (A VMM would typically intercept comparable sensitive events in the guest.) + /// In the current implementation, the only use of the `#SX` is to redirect external INITs + /// into an exception so that the VMM may — among other possibilities. + /// + /// The only error code currently defined is 1, and indicates redirection of INIT has occurred. + /// + /// The vector number of the ``#SX`` exception is 30. + pub security_exception: Entry, + + /// vector nr. 31 + reserved_3: Entry, + + /// User-defined interrupts can be initiated either by system logic or software. They occur + /// when: + /// + /// - System logic signals an external interrupt request to the processor. The signaling + /// mechanism and the method of communicating the interrupt vector to the processor are + /// implementation dependent. + /// - Software executes an `INTn` instruction. The `INTn` instruction operand provides + /// the interrupt vector number. + /// + /// Both methods can be used to initiate an interrupt into vectors 0 through 255. However, + /// because vectors 0 through 31 are defined or reserved by the AMD64 architecture, + /// software should not use vectors in this range for purposes other than their defined use. + /// + /// The saved instruction pointer depends on the interrupt source: + /// + /// - External interrupts are recognized on instruction boundaries. The saved instruction + /// pointer points to the instruction immediately following the boundary where the + /// external interrupt was recognized. + /// - If the interrupt occurs as a result of executing the INTn instruction, the saved + /// instruction pointer points to the instruction after the INTn. + interrupts: [Entry; 256 - 32], +} + +// +// MARK: impl +// + +impl InterruptDescriptorTable { + /// Creates a new IDT filled with non-present entries. + #[inline] + pub const fn new() -> InterruptDescriptorTable { + InterruptDescriptorTable { + divide_error: Entry::missing(), + debug: Entry::missing(), + non_maskable_interrupt: Entry::missing(), + breakpoint: Entry::missing(), + overflow: Entry::missing(), + bound_range_exceeded: Entry::missing(), + invalid_opcode: Entry::missing(), + device_not_available: Entry::missing(), + double_fault: Entry::missing(), + coprocessor_segment_overrun: Entry::missing(), + invalid_tss: Entry::missing(), + segment_not_present: Entry::missing(), + stack_segment_fault: Entry::missing(), + general_protection_fault: Entry::missing(), + page_fault: Entry::missing(), + reserved_1: Entry::missing(), + x87_floating_point: Entry::missing(), + alignment_check: Entry::missing(), + machine_check: Entry::missing(), + simd_floating_point: Entry::missing(), + virtualization: Entry::missing(), + cp_protection_exception: Entry::missing(), + reserved_2: [Entry::missing(); 6], + hv_injection_exception: Entry::missing(), + vmm_communication_exception: Entry::missing(), + security_exception: Entry::missing(), + reserved_3: Entry::missing(), + interrupts: [Entry::missing(); 256 - 32], + } + } + + /// Loads the IDT in the CPU using the `lidt` command. + #[inline] + pub fn load(&'static self) { + unsafe { self.load_unsafe() } + } + + /// Loads the IDT in the CPU using the `lidt` command. + /// + /// # Safety + /// + /// As long as it is the active IDT, you must ensure that: + /// + /// - `self` is never destroyed. + /// - `self` always stays at the same memory location. + /// It is recommended to wrap it in a `Box`. + #[inline] + pub unsafe fn load_unsafe(&self) { + let idtr = { + IDTR { + size: (size_of::() - 1) as u16, + offset: self as *const _ as u32, + } + }; + + unsafe { + asm!("lidt [{}]", in(reg) &idtr, options(readonly, nostack, preserves_flags)); + } + } +} diff --git a/tetros/src/idt/util.rs b/tetros/src/idt/util.rs new file mode 100644 index 0000000..13f5a94 --- /dev/null +++ b/tetros/src/idt/util.rs @@ -0,0 +1,10 @@ +use core::arch::asm; + +/// Get the current value of the CS register +pub(super) fn get_cs() -> u16 { + let segment: u16; + unsafe { + asm!("mov {0:x}, cs", out(reg) segment, options(nomem, nostack, preserves_flags)); + } + segment +} diff --git a/tetros/src/idt/virtaddr.rs b/tetros/src/idt/virtaddr.rs new file mode 100644 index 0000000..1cc4cd7 --- /dev/null +++ b/tetros/src/idt/virtaddr.rs @@ -0,0 +1,96 @@ +use core::{ + fmt::{self}, + ops::{Add, AddAssign, Sub, SubAssign}, +}; + +/// A canonical 32-bit virtual memory address. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct VirtAddr(u32); + +impl VirtAddr { + /// Create a new [`VirtAddr`] from a [`u32`]. + #[inline] + pub const fn new(addr: u32) -> Self { + Self(addr) + } + + /// Converts this address to a `u32`. + #[inline] + pub const fn as_u32(self) -> u32 { + self.0 + } +} + +impl fmt::Debug for VirtAddr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VirtAddr") + .field(&format_args!("{:#x}", self.0)) + .finish() + } +} + +impl fmt::Binary for VirtAddr { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Binary::fmt(&self.0, f) + } +} + +impl fmt::LowerHex for VirtAddr { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} + +impl fmt::Octal for VirtAddr { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Octal::fmt(&self.0, f) + } +} + +impl fmt::UpperHex for VirtAddr { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::UpperHex::fmt(&self.0, f) + } +} + +impl fmt::Pointer for VirtAddr { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&(self.0 as *const ()), f) + } +} + +impl Add for VirtAddr { + type Output = Self; + #[inline] + fn add(self, rhs: u32) -> Self::Output { + VirtAddr(self.0.checked_add(rhs).unwrap()) + } +} + +impl AddAssign for VirtAddr { + #[inline] + fn add_assign(&mut self, rhs: u32) { + *self = *self + rhs; + } +} + +impl Sub for VirtAddr { + type Output = Self; + #[inline] + fn sub(self, rhs: u32) -> Self::Output { + VirtAddr(self.0.checked_sub(rhs).unwrap()) + } +} + +impl SubAssign for VirtAddr { + #[inline] + fn sub_assign(&mut self, rhs: u32) { + *self = *self - rhs; + } +} diff --git a/tetros/src/lib.rs b/tetros/src/lib.rs index a26dba0..77c0007 100644 --- a/tetros/src/lib.rs +++ b/tetros/src/lib.rs @@ -1,13 +1,19 @@ #![no_std] #![feature(int_roundings)] #![feature(lang_items)] +#![feature(abi_x86_interrupt)] #![allow(internal_features)] -use drivers::vga::Vga13h; -use os::thunk::ThunkData; +use core::arch::asm; +use lazy_static::lazy_static; use spin::Mutex; + +use drivers::vga::Vga13h; +use idt::{InterruptDescriptorTable, InterruptStackFrame}; +use os::thunk::ThunkData; use tetrisboard::TetrisBoard; +mod idt; mod os; mod tetrisboard; @@ -16,12 +22,25 @@ mod drivers; pub(crate) static VGA: Mutex = Mutex::new(unsafe { Vga13h::new() }); +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.divide_error.set_handler_fn(breakpoint_handler); + + idt + }; +} + +extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + println!("EXCEPTION {:?}", stack_frame); +} + #[no_mangle] pub unsafe extern "C" fn start( _boot_disk: usize, thunk10: extern "C" fn(), _thunk13: extern "C" fn(), - thunk15: extern "C" fn(), + _thunk15: extern "C" fn(), _thunk16: extern "C" fn(), ) -> ! { println!("Entered Rust, serial ready."); @@ -41,23 +60,18 @@ pub unsafe extern "C" fn start( data.with(thunk10); } + { + // Initialize OS + IDT.load(); + } + // Clear screen let mut v = VGA.lock(); - /* - let fb = v.get_fb(); - let mut c: u8 = 0; - for x in 0..128 { - for y in 0..128 { - let idx = Vga13h::pix_idx(x, y); - fb[idx] = c; - c = c.wrapping_add(1); - } - } - */ - let t = TetrisBoard::new(); t.draw(&mut v); + asm!("int $0"); + panic!("kernel"); }