interrupts

This commit is contained in:
janis 2026-07-19 22:51:35 +02:00
parent bbe0551ac5
commit eccb202d04
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
7 changed files with 737 additions and 8 deletions

14
kernel/Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.13.1" version = "2.13.1"
@ -12,5 +18,13 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
name = "kernel" name = "kernel"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bit_field",
"bitflags", "bitflags",
"seq-macro",
] ]
[[package]]
name = "seq-macro"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"

View file

@ -18,4 +18,6 @@ name = "simple"
harness = false harness = false
[dependencies] [dependencies]
bit_field = "0.10.3"
bitflags = "2.13.1" bitflags = "2.13.1"
seq-macro = "0.3.6"

View file

@ -72,7 +72,7 @@ qemu-system-x86_64 \
-drive file="$image",format=raw \ -drive file="$image",format=raw \
-machine q35,accel=kvm -enable-kvm \ -machine q35,accel=kvm -enable-kvm \
-drive if=pflash,format=raw,readonly=on,file="$OVMF_PATH/FV/OVMF_CODE.fd" \ -drive if=pflash,format=raw,readonly=on,file="$OVMF_PATH/FV/OVMF_CODE.fd" \
-chardev stdio,id=serial0,logfile=qemu.log,signal=off \ -chardev stdio,id=serial0,logfile=qemu.log,signal=on \
-serial chardev:serial0 \ -serial chardev:serial0 \
-vga std \ -vga std \
${QEMU_TEST_ARGS[@]} ${QEMU_TEST_ARGS[@]}

View file

@ -1,5 +1,5 @@
#![no_std] #![no_std]
#![feature(const_trait_impl, const_default)] #![feature(const_trait_impl, const_default, abi_x86_interrupt, never_type)]
#![cfg_attr(test, feature(custom_test_frameworks))] #![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, test_runner(crate::tests::test_runner))] #![cfg_attr(test, test_runner(crate::tests::test_runner))]
#![cfg_attr(test, no_main)] #![cfg_attr(test, no_main)]

View file

@ -1,11 +1,9 @@
#![cfg(target_arch = "x86_64")] #![cfg(target_arch = "x86_64")]
#[inline] pub mod idt;
pub fn hlt() { pub mod instructions;
unsafe {
core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); pub use instructions::hlt;
}
}
pub fn halt_loop() -> ! { pub fn halt_loop() -> ! {
loop { loop {
@ -47,3 +45,25 @@ impl PortU16 {
} }
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
// registers in the order they are pushed onto the stack:
pub struct Registers {
pub r15: u64,
pub r14: u64,
pub r13: u64,
pub r12: u64,
pub r11: u64,
pub r10: u64,
pub r9: u64,
pub r8: u64,
pub rdi: u64,
pub rsi: u64,
pub rbp: u64,
// rsp is not included here
pub rbx: u64,
pub rdx: u64,
pub rcx: u64,
pub rax: u64,
}

648
kernel/src/x86_64/idt.rs Normal file
View file

@ -0,0 +1,648 @@
use core::{arch::naked_asm, fmt::Debug, marker::PhantomData, ops::Deref, ptr::NonNull};
use bit_field::BitField;
use crate::{serial_println, x86_64::Registers};
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Entry {
pub offset_low: u16,
pub selector: u16,
pub options: EntryOptions,
pub offset_middle: u16,
pub offset_high: u32,
pub reserved: u32,
}
impl core::fmt::Debug for Entry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Entry")
.field("handler", &self.handler_address())
.field("selector", &self.selector)
.field("options", &self.options)
.finish()
}
}
impl Entry {
pub unsafe fn new<T: ToAddress>(handler: T, selector: u16, options: EntryOptions) -> Self {
let handler_addr = handler.to_address();
Self {
offset_low: handler_addr as u16,
selector,
options,
offset_middle: (handler_addr >> 16) as u16,
offset_high: (handler_addr >> 32) as u32,
reserved: 0,
}
}
pub unsafe fn new_default_interrupt<const IDX: u8>() -> Self {
let cs = unsafe { super::instructions::get_cs() };
let mut options = EntryOptions::empty_interrupt_gate();
options.set_present(true);
let handler = match IDX {
8 | 10 | 11 | 12 | 13 | 14 | 17 | 21 | 29 | 30 => {
interrupt_trampoline_with_err::<IDX> as *const () as u64
}
_ => interrupt_trampoline_no_err::<IDX> as *const () as u64,
};
unsafe { Self::new(handler, cs, options) }
}
pub const fn missing_interrupt() -> Self {
Self {
offset_low: 0,
selector: 0,
options: EntryOptions::empty_interrupt_gate(),
offset_middle: 0,
offset_high: 0,
reserved: 0,
}
}
pub fn handler_address(&self) -> u64 {
let low = self.offset_low as u64;
let middle = self.offset_middle as u64;
let high = self.offset_high as u64;
(high << 32) | (middle << 16) | low
}
pub fn set_handler<T>(&mut self, handler: T)
where
T: ToAddress,
{
let handler_addr = handler.to_address();
self.offset_low = handler_addr as u16;
self.offset_middle = (handler_addr >> 16) as u16;
self.offset_high = (handler_addr >> 32) as u32;
self.options = EntryOptions::empty_interrupt_gate();
unsafe {
self.selector = super::instructions::get_cs();
}
self.options.set_present(true);
}
}
pub trait ToAddress {
fn to_address(&self) -> u64;
}
impl ToAddress for u64 {
fn to_address(&self) -> u64 {
*self
}
}
impl<T> ToAddress for *const T {
fn to_address(&self) -> u64 {
*self as u64
}
}
impl<T> ToAddress for *mut T {
fn to_address(&self) -> u64 {
*self as u64
}
}
impl<T> ToAddress for NonNull<T> {
fn to_address(&self) -> u64 {
self.as_ptr() as u64
}
}
impl<T> ToAddress for &T {
fn to_address(&self) -> u64 {
*self as *const T as u64
}
}
impl<T> ToAddress for &mut T {
fn to_address(&self) -> u64 {
*self as *const T as u64
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct EntryOptions(u16);
impl Debug for EntryOptions {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EntryOptions")
.field("present", &self.present())
.field("privilege_level", &self.privilege_level())
.field(
"interrupt_stack_table_index",
&self.interrupt_stack_table_index(),
)
.field("kind", &self.kind())
.finish()
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum EntryType {
#[default]
InterruptGate = 0b1110,
TrapGate = 0b1111,
}
impl EntryType {
pub fn into_u8(self) -> u8 {
self as u8
}
/// # Safety
/// The caller must ensure that `value` is either `0b1110` or `0b1111`, or
/// the result of calling `into_u8` on an `EntryType`.
pub unsafe fn from_u8_unchecked(value: u8) -> Self {
unsafe { core::mem::transmute(value) }
}
}
impl EntryOptions {
pub const fn empty_interrupt_gate() -> Self {
Self(0b1110_0000_0000)
}
pub const fn empty_trap_gate() -> Self {
Self(0b1111_0000_0000)
}
pub fn present(&self) -> bool {
self.0.get_bit(15)
}
pub fn set_present(&mut self, present: bool) {
self.0.set_bit(15, present);
}
pub fn privilege_level(&self) -> u8 {
self.0.get_bits(13..15) as u8
}
pub fn set_privilege_level(&mut self, level: u8) {
self.0.set_bits(13..15, level as u16);
}
pub fn interrupt_stack_table_index(&self) -> Option<u8> {
let index = self.0.get_bits(0..3) as u8;
if index == 0 { None } else { Some(index - 1) }
}
pub fn set_interrupt_stack_table_index(&mut self, index: u8) {
self.0.set_bits(0..3, index as u16 + 1);
}
pub fn kind(&self) -> EntryType {
unsafe { EntryType::from_u8_unchecked(self.0.get_bits(8..12) as u8) }
}
pub fn set_kind(&mut self, kind: EntryType) {
self.0.set_bits(8..12, kind.into_u8() as u16);
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct InterruptStackFrameInner {
pub instruction_pointer: u64,
pub code_segment: u16,
_reserved: [u16; 3],
pub stack_pointer: u64,
pub stack_segment: u16,
_reserved2: [u16; 3],
}
#[derive(Debug)]
#[repr(transparent)]
pub struct InterruptStackFrame(InterruptStackFrameInner);
impl Deref for InterruptStackFrame {
type Target = InterruptStackFrameInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct PageFaultErrorCode: u64 {
const PRESENT = 1 << 0;
const WRITE = 1 << 1;
const USER = 1 << 2;
const RESERVED = 1 << 3;
const INSTRUCTION_FETCH = 1 << 4;
const PROTECTION_KEY = 1 << 5;
const SGX = 1 << 15;
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C, packed(2))]
pub struct InterruptDescriptorTablePointer<'idt> {
pub limit: u16,
pub base: u64,
_pd: PhantomData<&'idt ()>,
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct InterruptDescriptorTable {
pub divide_by_zero: Entry,
pub debug: Entry,
pub non_maskable_interrupt: Entry,
pub breakpoint: Entry,
pub overflow: Entry,
pub bound_range_exceeded: Entry,
pub invalid_opcode: Entry,
pub device_not_available: Entry,
pub double_fault: Entry,
pub coprocessor_segment_overrun: Entry,
pub invalid_tss: Entry,
pub segment_not_present: Entry,
pub stack_segment_fault: Entry,
pub general_protection_fault: Entry,
pub page_fault: Entry,
_reserved1: Entry,
pub x87_floating_point: Entry,
pub alignment_check: Entry,
pub machine_check: Entry,
pub simd_floating_point: Entry,
pub virtualization: Entry,
pub cp_protection: Entry,
_reserved2: [Entry; 6],
pub hypervisor: Entry,
pub vmm_communication_exception: Entry,
pub security_exception: Entry,
_reserved3: Entry,
pub interrupts: [Entry; 256 - 32],
}
const impl Default for InterruptDescriptorTable {
fn default() -> Self {
Self::new_empty()
}
}
impl InterruptDescriptorTable {
pub const fn new_empty() -> Self {
Self {
divide_by_zero: Entry::missing_interrupt(),
debug: Entry::missing_interrupt(),
non_maskable_interrupt: Entry::missing_interrupt(),
breakpoint: Entry::missing_interrupt(),
overflow: Entry::missing_interrupt(),
bound_range_exceeded: Entry::missing_interrupt(),
invalid_opcode: Entry::missing_interrupt(),
device_not_available: Entry::missing_interrupt(),
double_fault: Entry::missing_interrupt(),
coprocessor_segment_overrun: Entry::missing_interrupt(),
invalid_tss: Entry::missing_interrupt(),
segment_not_present: Entry::missing_interrupt(),
stack_segment_fault: Entry::missing_interrupt(),
general_protection_fault: Entry::missing_interrupt(),
page_fault: Entry::missing_interrupt(),
_reserved1: Entry::missing_interrupt(),
x87_floating_point: Entry::missing_interrupt(),
alignment_check: Entry::missing_interrupt(),
machine_check: Entry::missing_interrupt(),
simd_floating_point: Entry::missing_interrupt(),
virtualization: Entry::missing_interrupt(),
cp_protection: Entry::missing_interrupt(),
_reserved2: [Entry::missing_interrupt(); 6],
hypervisor: Entry::missing_interrupt(),
vmm_communication_exception: Entry::missing_interrupt(),
security_exception: Entry::missing_interrupt(),
_reserved3: Entry::missing_interrupt(),
interrupts: [Entry::missing_interrupt(); 256 - 32],
}
}
pub fn new_default() -> Self {
let mut idt = Self::new_empty();
let slice = unsafe { idt.as_mut_type_erased() };
seq_macro::seq!(
N in 0..=255 {
slice[N] = unsafe { Entry::new_default_interrupt::<N>() };
}
);
idt
}
pub unsafe fn pointer(&self) -> InterruptDescriptorTablePointer<'_> {
InterruptDescriptorTablePointer {
limit: (core::mem::size_of::<Self>() - 1) as u16,
base: self as *const _ as u64,
_pd: PhantomData,
}
}
pub unsafe fn load(&self) {
unsafe {
super::instructions::lidt(&self.pointer());
}
}
pub fn set_interrupt<const N: u8, T: Fn(InterruptStackFrame, u8, Option<u64>)>(
&mut self,
_handler: T,
) {
let wrapper = InterruptHandlerWrapper::<T>(PhantomData);
let handler_addr = handler_entry_by_index::<N, T>(wrapper);
unsafe {
let entry = self.as_mut_type_erased().get_unchecked_mut(N as usize);
entry.set_handler(handler_addr);
}
}
pub unsafe fn as_type_erased(&self) -> &[Entry; 256] {
unsafe { core::mem::transmute::<&Self, &[Entry; 256]>(self) }
}
pub unsafe fn as_mut_type_erased(&mut self) -> &mut [Entry; 256] {
unsafe { core::mem::transmute::<&mut Self, &mut [Entry; 256]>(self) }
}
}
#[unsafe(naked)]
extern "C" fn interrupt_trampoline_with_err<const IDX: u8>() {
naked_asm!(
"push {idx}",
"jmp {dispatcher}",
idx = const { IDX },
dispatcher = sym interrupt_dispatcher,
)
}
#[unsafe(naked)]
extern "C" fn interrupt_trampoline_no_err<const IDX: u8>() {
naked_asm!(
"push 0", // push a dummy error code of 0
"push {idx}",
"jmp {dispatcher}",
idx = const { IDX },
dispatcher = sym interrupt_dispatcher,
)
}
#[unsafe(naked)]
extern "C" fn interrupt_dispatcher() {
naked_asm!(
"push rax",
"push rcx",
"push rdx",
"push rbx",
"push rbp",
"push rsi",
"push rdi",
"push r8",
"push r9",
"push r10",
"push r11",
"push r12",
"push r13",
"push r14",
"push r15",
"lea rdi, [rsp + {registers_size} + 16]", // interrupt frame
"mov rdx, [rsp + {registers_size} + 8]", // error code
"mov rsi, [rsp + {registers_size}]", // interrupt index
"mov rcx, rsp", // pass the current stack pointer as the registers pointer
"call {global_handler}",
"pop r15",
"pop r14",
"pop r13",
"pop r12",
"pop r11",
"pop r10",
"pop r9",
"pop r8",
"pop rdi",
"pop rsi",
"pop rbp",
"pop rbx",
"pop rdx",
"pop rcx",
"pop rax",
"iretq",
registers_size = const { core::mem::size_of::<Registers>() },
global_handler = sym global_interrupt_handler,
)
}
extern "C" fn global_interrupt_handler(
frame: &InterruptStackFrame,
index: u8,
error_code: u64,
registers: &Registers,
) {
crate::serial_println!(
"Interrupt {} occurred! Error code: {:#x}, Frame: {:?}, Registers: {:?}",
index,
error_code,
frame,
registers
);
}
pub type InterruptHandler = extern "x86-interrupt" fn(InterruptStackFrame);
pub type InterruptWithErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64);
pub type PageFaultHandler = extern "x86-interrupt" fn(InterruptStackFrame, PageFaultErrorCode);
pub type DoubleFaultHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64) -> !;
pub type DivergingHandler = extern "x86-interrupt" fn(InterruptStackFrame) -> !;
pub type GeneralHandler = fn(InterruptStackFrame, index: u8, error_code: Option<u64>);
macro_rules! impl_handler_type {
($($handler_type:ty),*) => {
$(
impl ToAddress for $handler_type {
fn to_address(&self) -> u64 {
*self as *const () as u64
}
}
)*
};
}
impl_handler_type!(
InterruptHandler,
InterruptWithErrorCodeHandler,
PageFaultHandler,
DoubleFaultHandler,
DivergingHandler
);
pub trait HandleGeneratorTrait<const IDX: u8> {
fn get_handler_addr(&self) -> u64;
}
fn handler_entry_by_index<const IDX: u8, T>(handler: InterruptHandlerWrapper<T>) -> u64
where
T: Fn(InterruptStackFrame, u8, Option<u64>) + Sized,
{
if IDX == 8 {
// Double fault
DoubleFaultHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 10 {
// Invalid TSS
InterruptWithErrorCodeHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 11 {
// Segment not present
InterruptWithErrorCodeHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 12 {
// Stack segment fault
InterruptWithErrorCodeHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 13 {
// General protection fault
InterruptWithErrorCodeHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 14 {
// Page fault
PageFaultHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 17 {
// Alignment check
InterruptWithErrorCodeHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 18 {
// Machine check
DivergingHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 29 {
// VMM communication exception
InterruptWithErrorCodeHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else if IDX == 30 {
// Security exception
InterruptWithErrorCodeHandlerTrait::<IDX, T>::get_handler_addr(&handler)
} else {
// All other interrupts
InterruptHandlerTrait::<IDX, T>::get_handler_addr(&handler)
}
}
pub struct InterruptHandlerWrapper<T>(PhantomData<fn() -> T>);
pub trait InterruptErrorCode: Sized {
fn as_u64(&self) -> u64;
}
impl InterruptErrorCode for u64 {
fn as_u64(&self) -> u64 {
*self
}
}
impl InterruptErrorCode for PageFaultErrorCode {
fn as_u64(&self) -> u64 {
self.bits()
}
}
macro_rules! interrupt_error_param {
( $err:ty) => {
$err
};
() => {
()
};
($val:ident: $err:ty) => {
Some(InterruptErrorCode::as_u64(&$val))
};
(err:) => {
None
};
}
macro_rules! interrupt_return_expr {
( $expr:ty) => {
$crate::serial_println!("halt looping after unrecoverable interrupt");
$crate::x86_64::halt_loop()
};
() => {
()
};
}
macro_rules! impl_interrupt_handler_trait {
(trait $trait_name:ident (InterruptStackFrame $(, $err:ty)?) $(-> $ret:ty)?) => {
pub trait $trait_name<const IDX: u8, T>
where
T: Fn(InterruptStackFrame, u8, Option<u64>),
{
extern "x86-interrupt" fn handler(
frame: InterruptStackFrame,
$( err: interrupt_error_param!{$err})*)
$(-> $ret)* {
assert_eq!(
core::mem::size_of::<T>(),
0,
"Handler type must be zero-sized"
);
let f: T = unsafe { core::mem::transmute_copy::<(), T>(&()) };
$crate::serial_println!("calling interrupt handler {} with {:?}", IDX, core::any::type_name::<T>());
f(frame, IDX, interrupt_error_param!{err: $($err)*});
$crate::serial_println!("returning from interrupt handler {} with {:?}", IDX, core::any::type_name::<T>());
interrupt_return_expr!{$($ret)*}
}
fn get_handler_addr(&self) -> u64 {
Self::handler as *const () as u64
}
}
impl<const IDX: u8, T> $trait_name<IDX, T> for InterruptHandlerWrapper<T>
where
T: Fn(InterruptStackFrame, u8, Option<u64>),
{
}
};
}
impl_interrupt_handler_trait!(trait InterruptHandlerTrait(InterruptStackFrame));
impl_interrupt_handler_trait!(trait DivergingHandlerTrait(InterruptStackFrame) -> !);
impl_interrupt_handler_trait!(trait InterruptWithErrorCodeHandlerTrait(InterruptStackFrame, u64));
impl_interrupt_handler_trait!(trait PageFaultHandlerTrait(InterruptStackFrame, PageFaultErrorCode));
impl_interrupt_handler_trait!(trait DoubleFaultHandlerTrait(InterruptStackFrame, u64) -> !);
fn my_handler(frame: InterruptStackFrame, index: u8) {
// Handle the interrupt here
crate::serial_println!("Interrupt {} occurred!", index);
}
// fn asdf() {
// let wrapper = InterruptHandlerWrapper::<>(PhantomData);
// let handler = InterruptHandlerTrait::<32, _>::get_handler(&wrapper);
// }
#[cfg_attr(test, test_case)]
fn test_interrupt_handler_trait() {
let mut idt = InterruptDescriptorTable::new_empty();
idt.set_interrupt::<3, _>(my_handler_a);
let addr_a = unsafe { idt.as_type_erased() }[3].handler_address();
serial_println!("idt: {:#?}", idt);
unsafe {
idt.load();
}
super::instructions::int3();
serial_println!("back from int3");
idt.set_interrupt::<3, _>(my_handler_b);
let addr_b = unsafe { idt.as_type_erased() }[3].handler_address();
assert_ne!(addr_a, addr_b, "Handler addresses should be different");
unsafe {
idt.load();
}
super::instructions::int3();
idt.set_interrupt::<3, _>(|frame, index, error_code| {
crate::serial_println!("handler c!");
});
unsafe {
idt.load();
}
super::instructions::int3();
}

View file

@ -0,0 +1,45 @@
#[inline]
pub fn hlt() {
unsafe {
core::arch::asm!("hlt", options(nomem, nostack, preserves_flags));
}
}
#[inline]
pub unsafe fn lidt(idt: &super::idt::InterruptDescriptorTablePointer) {
unsafe {
core::arch::asm!(
"lidt [{}]",
in(reg) idt,
options(readonly, nostack, preserves_flags)
);
}
}
#[inline]
pub fn int3() {
unsafe {
core::arch::asm!("int3", options(nomem, nostack, preserves_flags));
}
}
macro_rules! read_segment {
($segment:literal) => {
{
let value: u16;
unsafe {
core::arch::asm!(
concat!("mov {0:x}, ", $segment),
out(reg) value,
options(nomem, nostack, preserves_flags)
);
}
value
}
}
}
#[inline]
pub unsafe fn get_cs() -> u16 {
read_segment!("cs")
}