1
0

Added IDT

This commit is contained in:
Mark 2025-02-25 19:13:46 -08:00
parent db859ddeb8
commit 9c515cfab0
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
10 changed files with 1028 additions and 16 deletions

3
tetros/Cargo.lock generated
View File

@ -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",

View File

@ -64,6 +64,7 @@ identity_op = "allow"
[dependencies]
bitflags = "2.8.0"
spin = "0.9.8"
uart_16550 = "0.3.2"

127
tetros/src/idt/entry.rs Normal file
View File

@ -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<F> {
phantom: PhantomData<F>,
/// 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<T> fmt::Debug for Entry<T> {
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<F> Entry<F> {
/// 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<VirtAddr> {
self.is_present().then_some(VirtAddr::new(
self.pointer_low as u32 + ((self.pointer_high as u32) << 16),
))
}
}
impl<F: HandlerFuncType> Entry<F> {
/// 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()) }
}
}

117
tetros/src/idt/handler.rs Normal file
View File

@ -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)
}
}

22
tetros/src/idt/mod.rs Normal file
View File

@ -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;

View File

@ -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()
}
}

482
tetros/src/idt/table.rs Normal file
View File

@ -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<HandlerFunc>,
/// When the debug-exception mechanism is enabled, a `#DB` exception can occur under any
/// of the following circumstances:
///
/// <details>
///
/// - 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).
///
/// </details>
///
/// `#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<HandlerFunc>,
/// 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<HandlerFunc>,
/// 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<HandlerFunc>,
/// 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<HandlerFunc>,
/// 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<HandlerFunc>,
/// 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.
///
/// <details><summary>A `#UD` occurs under the following conditions:</summary>
///
/// - 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.
///
/// </details>
///
/// 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<HandlerFunc>,
/// A device not available exception (`#NM`) occurs under any of the following conditions:
///
/// <details>
///
/// - 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`.
///
/// </details>
///
/// 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<HandlerFunc>,
/// A double fault (`#DF`) exception can occur when a second exception occurs during
/// the handling of a prior (first) exception or interrupt handler.
///
/// <details>
///
/// 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.
///
/// </details>
///
/// 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<DivergingHandlerFuncWithErrCode>,
/// 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<HandlerFunc>,
/// 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<HandlerFuncWithErrCode>,
/// 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<HandlerFuncWithErrCode>,
/// 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<HandlerFuncWithErrCode>,
/// 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<HandlerFuncWithErrCode>,
/// 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<PageFaultHandlerFunc>,
/// vector nr. 15
reserved_1: Entry<HandlerFunc>,
/// 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<HandlerFunc>,
/// 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<HandlerFuncWithErrCode>,
/// 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<DivergingHandlerFunc>,
/// 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<HandlerFunc>,
/// vector nr. 20
pub virtualization: Entry<HandlerFunc>,
/// 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<HandlerFuncWithErrCode>,
/// vector nr. 22-27
reserved_2: [Entry<HandlerFunc>; 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<HandlerFunc>,
/// 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<HandlerFuncWithErrCode>,
/// 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<HandlerFuncWithErrCode>,
/// vector nr. 31
reserved_3: Entry<HandlerFunc>,
/// 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<HandlerFunc>; 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::<InterruptDescriptorTable>() - 1) as u16,
offset: self as *const _ as u32,
}
};
unsafe {
asm!("lidt [{}]", in(reg) &idtr, options(readonly, nostack, preserves_flags));
}
}
}

10
tetros/src/idt/util.rs Normal file
View File

@ -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
}

View File

@ -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<u32> for VirtAddr {
type Output = Self;
#[inline]
fn add(self, rhs: u32) -> Self::Output {
VirtAddr(self.0.checked_add(rhs).unwrap())
}
}
impl AddAssign<u32> for VirtAddr {
#[inline]
fn add_assign(&mut self, rhs: u32) {
*self = *self + rhs;
}
}
impl Sub<u32> for VirtAddr {
type Output = Self;
#[inline]
fn sub(self, rhs: u32) -> Self::Output {
VirtAddr(self.0.checked_sub(rhs).unwrap())
}
}
impl SubAssign<u32> for VirtAddr {
#[inline]
fn sub_assign(&mut self, rhs: u32) {
*self = *self - rhs;
}
}

View File

@ -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<Vga13h> = 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");
}