kernel/src/interrupts.rs

694 lines
27 KiB
Rust

use crate::{
bootinfo::BOOTINFO,
print, println,
serial::SECOND_PORT,
tasking::SleepReason,
virtual_memory::{ASpaceMutex, AddressSpace, ACTIVE_SPACE, KERNEL_SPACE},
TASKING,
};
use alloc::{boxed::Box, vec::Vec};
use az::WrappingCast;
use cast::{u64, usize};
use core::{arch::asm, ffi::CStr, ptr, slice};
use hashbrown::HashMap;
use pic8259::ChainedPics;
use saturating_cast::SaturatingCast;
use spin::{Lazy, Mutex, RwLock};
use tap::Tap;
use x86_64::{
registers::control::Cr2,
set_general_handler,
structures::{
idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode},
paging::{mapper::TranslateResult, Page, PageTableFlags, PhysFrame, Translate},
},
PhysAddr, PrivilegeLevel, VirtAddr,
};
const IRQ_BASE: u8 = 32;
static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
let mut idt = InterruptDescriptorTable::new();
set_general_handler!(&mut idt, general_handler);
set_general_handler!(&mut idt, exc_handler, 0..32);
set_general_handler!(&mut idt, irq_handler, 32..48);
idt[0x80].set_handler_fn(syscall_handler_header).set_privilege_level(PrivilegeLevel::Ring3);
idt.page_fault.set_handler_fn(page_fault_handler);
idt
});
static PICS: Mutex<ChainedPics> = Mutex::new(unsafe { ChainedPics::new(IRQ_BASE, IRQ_BASE + 8) });
static IRQ_HANDLERS: RwLock<[Option<IrqHandler>; 16]> = RwLock::new([None; 16]);
pub type IrqHandler = fn(irq_num: u8, eoi_guard: EoiGuard);
#[derive(Debug)]
pub struct InvalidIrq;
pub fn init() {
IDT.load();
unsafe { PICS.lock().initialize() };
unsafe { PICS.lock().write_masks(0, 0) };
x86_64::instructions::interrupts::enable();
}
extern "x86-interrupt" fn page_fault_handler(
stack_frame: InterruptStackFrame,
error_code: PageFaultErrorCode,
) {
#[warn(clippy::expect_used, reason = "FIXME")]
let faulting_addr =
Cr2::read().expect("Cannot handle page faults caused by non-canonical addresses");
if let Some(current_pid) = TASKING.current_pid() {
print!("PID {current_pid} ");
} else {
print!("Kernel init ");
}
if error_code.contains(PageFaultErrorCode::PROTECTION_VIOLATION) {
println!(
"page faulted {error_code:#?} at {:#x}\nEntry flags: {:#?}\n{stack_frame:#?}",
faulting_addr,
match ACTIVE_SPACE.lock().translate(faulting_addr) {
TranslateResult::Mapped { flags, .. } => flags,
_ =>
{
#![allow(
clippy::panic,
reason = "A protection violation only happends on mapped addresses. If we get here, something has gone VERY wrong."
)]
panic!();
}
},
);
} else {
println!("page faulted {error_code:#?} at {:#x}\n{stack_frame:#?}", faulting_addr);
}
TASKING.exit();
}
#[expect(clippy::needless_pass_by_value, reason = "Signature dictated by external crate")]
fn general_handler(stack_frame: InterruptStackFrame, index: u8, _error_code: Option<u64>) {
println!("Other interrupt {index}\n{stack_frame:#?}");
}
#[expect(clippy::needless_pass_by_value, reason = "Signature dictated by external crate")]
fn exc_handler(_stack_frame: InterruptStackFrame, _index: u8, _error_code: Option<u64>) {
if let Some(current_pid) = TASKING.current_pid() {
print!("PID {current_pid} ");
} else {
print!("Kernel init ");
}
println!("had exception, exiting");
TASKING.exit();
}
pub struct EoiGuard(u8);
impl Drop for EoiGuard {
fn drop(&mut self) {
unsafe {
PICS.lock().notify_end_of_interrupt(self.0);
}
}
}
#[expect(clippy::needless_pass_by_value, reason = "Signature dictated by external crate")]
fn irq_handler(_stack_frame: InterruptStackFrame, index: u8, _error_code: Option<u64>) {
#[expect(
clippy::arithmetic_side_effects,
reason = "This function is only called for irqs, which are always above the base index."
)]
let irq_num = index - IRQ_BASE;
let eoi_guard = EoiGuard(index);
#[expect(
clippy::indexing_slicing,
reason = "This function is only called for 16 irqs, which are all valid indexes"
)]
if let Some(handler) = IRQ_HANDLERS.read()[usize(irq_num)] {
handler(irq_num, eoi_guard);
};
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct SyscallRegs {
rax: u64,
rbx: u64,
rcx: u64,
rdx: u64,
rsi: u64,
rdi: u64,
rsp: u64,
rbp: u64,
r8: u64,
r9: u64,
r10: u64,
r11: u64,
r12: u64,
r13: u64,
r14: u64,
r15: u64,
}
#[no_mangle]
static mut SYSCALL_REGS: SyscallRegs = SyscallRegs {
rax: 0,
rbx: 0,
rcx: 0,
rdx: 0,
rsi: 0,
rdi: 0,
rsp: 0,
rbp: 0,
r8: 0,
r9: 0,
r10: 0,
r11: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
};
#[naked]
extern "x86-interrupt" fn syscall_handler_header(stack_frame: InterruptStackFrame) {
unsafe {
asm!(
"mov [rip+SYSCALL_REGS], rax",
"mov [rip+SYSCALL_REGS+8], rbx",
"mov [rip+SYSCALL_REGS+16], rcx",
"mov [rip+SYSCALL_REGS+24], rdx",
"mov [rip+SYSCALL_REGS+32], rsi",
"mov [rip+SYSCALL_REGS+40], rdi",
"mov [rip+SYSCALL_REGS+48], rsp",
"mov [rip+SYSCALL_REGS+56], rbp",
"mov [rip+SYSCALL_REGS+64], r8",
"mov [rip+SYSCALL_REGS+72], r9",
"mov [rip+SYSCALL_REGS+80], r10",
"mov [rip+SYSCALL_REGS+88], r11",
"mov [rip+SYSCALL_REGS+96], r12",
"mov [rip+SYSCALL_REGS+104], r13",
"mov [rip+SYSCALL_REGS+112], r14",
"mov [rip+SYSCALL_REGS+120], r15",
"jmp syscall_handler",
options(noreturn)
)
}
}
fn get_buffer(id: u64) -> Option<Box<[u8], &'static ASpaceMutex>> {
TASKING.data_buffers_mut(|x| {
x.try_remove(usize(id)).map(|buf| unsafe { Box::from_raw_in(buf, &*ACTIVE_SPACE) })
})
}
static REGISTERD_PIDS: Lazy<RwLock<HashMap<u64, u64>>> = Lazy::new(|| RwLock::new(HashMap::new()));
static INITRD_BUF: Lazy<&'static [u8]> = Lazy::new(|| {
let initrd = unsafe {
#[warn(clippy::expect_used, reason = "FIXME")]
let ramdisk_start = BOOTINFO.ramdisk_addr.into_option().expect("initrd not present");
let ramdisk_len = BOOTINFO.ramdisk_len;
slice::from_raw_parts(
ptr::with_exposed_provenance::<u8>(usize(ramdisk_start)),
usize(ramdisk_len),
)
};
KERNEL_SPACE.lock().alloc_force_user = true;
let initrd = Box::leak(
Vec::with_capacity_in(initrd.len(), &*KERNEL_SPACE)
.tap_mut(|v| v.extend_from_slice(initrd))
.into_boxed_slice(),
);
KERNEL_SPACE.lock().alloc_force_user = false;
initrd
});
#[no_mangle]
extern "C" fn syscall_handler() {
let regs = unsafe { SYSCALL_REGS };
let mut retval = 0;
let mut retval2 = regs.rcx;
let mut retval3 = regs.rdx;
match regs.rax {
0 => {
let rval = if let Some(chr) = char::from_u32(regs.rcx.wrapping_cast()) {
if chr == '\n' {
print!("\r\n");
} else {
print!("{}", chr);
}
0
} else {
1
};
retval = rval;
}
1 => TASKING.exit(),
2 => {
retval = if regs.rcx == 0 {
ACTIVE_SPACE
.lock()
.map_free(usize(regs.rdx), PageTableFlags::from_bits_truncate(regs.rsi))
.map_or(0, |x| u64(x.expose_provenance()))
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
space
.map_free(usize(regs.rdx), PageTableFlags::from_bits_truncate(regs.rsi))
.map_or(0, |x| u64(x.expose_provenance()))
} else {
0
}
})
};
}
3 => {
retval = u64(INITRD_BUF.as_ptr().expose_provenance());
retval2 = u64(INITRD_BUF.len());
}
4 => {
#[warn(clippy::expect_used, reason = "FIXME")]
#[expect(
clippy::arithmetic_side_effects,
reason = "usize::MAX will never be returned as an index, and so incrementing can never overflow."
)]
let address_space = u64(TASKING.address_spaces_mut(|x| {
x.insert(AddressSpace::new().expect("Failed to create address space")) + 1
}));
retval = address_space;
}
5 => {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
TASKING.address_spaces_mut(|x| x.remove(usize(regs.rcx - 1)));
}
6 => 'call6: {
let Ok(page) = Page::from_start_address(VirtAddr::new(regs.rdx)) else {
retval = 1;
break 'call6;
};
let num_pages = usize(regs.rsi);
let flags = PageTableFlags::from_bits_truncate(regs.rdi);
let failed = if regs.rcx == 0 {
ACTIVE_SPACE.lock().map_assert_unused(page, num_pages, flags).is_err()
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
space.map_assert_unused(page, num_pages, flags).is_err()
} else {
true
}
})
};
retval = failed.into();
}
7 => {
if let Some(mut buffer) = get_buffer(regs.rdx) {
let len = usize(regs.rdi);
assert!(len <= buffer.len());
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
let buffer_num_pages = buffer.len() / 4096;
let buffer_raw = Box::into_raw(buffer);
let page = ACTIVE_SPACE
.lock()
.move_mappings_free(
Page::from_start_address(VirtAddr::new(u64(
buffer_raw.expose_provenance()
)))
.unwrap(),
buffer_num_pages,
space,
)
.unwrap();
space.run(|| unsafe {
(ptr::with_exposed_provenance_mut::<u8>(usize(regs.rsi)))
.copy_from(page.start_address().as_mut_ptr::<u8>(), len);
});
space.unmap_and_free(page, buffer_num_pages).unwrap();
retval = 0;
} else {
retval = 1;
}
});
} else {
retval = 1;
}
}
8 => {
let args = unsafe {
let argc = usize(regs.rsi);
let argv: &[&[u8]] =
slice::from_raw_parts(ptr::with_exposed_provenance(usize(regs.rdi)), argc);
argv.iter().map(|arg| CStr::from_bytes_with_nul_unchecked(arg)).collect::<Vec<_>>()
};
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
let space = TASKING.address_spaces_mut(|x| x.remove(usize(regs.rdx - 1)));
let res = TASKING.new_process(
ptr::with_exposed_provenance(usize(regs.rcx)),
space,
args.as_slice(),
);
if let Ok(pid) = res {
retval = 0;
retval2 = u64(pid);
} else {
retval = 1;
}
}
9 => {
#[expect(
clippy::unwrap_used,
reason = "Syscalls cannot be called during early boot, the only time when there is no current PID"
)]
REGISTERD_PIDS.write().insert(regs.rcx, u64(TASKING.current_pid().unwrap()));
}
10 => {
let id = REGISTERD_PIDS.read().get(&regs.rcx).copied();
if let Some(id) = id {
retval = 0;
retval2 = id;
} else {
retval = 1;
}
}
11 => {
let pid = usize(regs.rcx);
if let Some(buffer) = get_buffer(regs.rdx) {
let len = usize(regs.rsi);
assert!(len <= buffer.len());
if TASKING.message_queue_mut(pid, |_| ()).is_ok() {
#[expect(
clippy::unwrap_used,
reason = "The min call guarantees that the value is in the range of a u32 before the cast"
)]
let trunc_len: u32 = usize::min(len, 4096).try_into().unwrap();
#[expect(
clippy::arithmetic_side_effects,
reason = "Can't underflow, as x % 4 < 4 no matter the x"
)]
let padding = if (trunc_len % 4) != 0 { 4 - (trunc_len % 4) } else { 0 };
#[expect(
clippy::arithmetic_side_effects,
reason = "Can't overflow, as padding is no more than 4 and trunc_len is no more than 4096."
)]
let padded_len = trunc_len + padding;
#[expect(
clippy::arithmetic_side_effects,
reason = "Can't overflow, as padded_len is no more than 4096 and 4096+24 < u32::MAX"
)]
let total_len = padded_len + 8 + (4 * 4);
SECOND_PORT.write_u32s(&[
0x3, // SPB type
total_len, // Total block length
len.saturating_cast::<u32>().saturating_add(8), // Packet length
]);
SECOND_PORT.write_bytes(&pid.to_ne_bytes());
#[expect(
clippy::indexing_slicing,
reason = "The truncated length is always <= the buffer's length"
)]
SECOND_PORT.write_bytes(&buffer[0..usize(trunc_len)]);
for _ in 0..padding {
SECOND_PORT.write_bytes(&[0]);
}
SECOND_PORT.write_u32s(&[
total_len, // Total block length
]);
let buf_num_pages = buffer.len() / 4096;
let buffer = Box::into_raw(buffer);
let buf_start_page =
Page::from_start_address(VirtAddr::new(u64(buffer.expose_provenance())))
.unwrap();
let dest_buffer = TASKING
.address_space_mut(pid, |aspace| {
// This is None only if the destiniation is the current process. If so,
// no remapping is necessary so just retyurn the old buffer.
let Some(aspace) = aspace else {
return buffer;
};
let page = ACTIVE_SPACE
.lock()
.move_mappings_free(buf_start_page, buf_num_pages, aspace)
.unwrap();
ptr::slice_from_raw_parts_mut::<u8>(
page.start_address().as_mut_ptr(),
buffer.len(),
)
})
.unwrap();
#[expect(
clippy::unwrap_used,
reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition"
)]
let new_buffer_key =
TASKING.proc_data_buffers_mut(pid, |x| x.insert(dest_buffer)).unwrap();
#[expect(
clippy::unwrap_used,
reason = "The option was already checked at the start of the if-let"
)]
TASKING.message_queue_mut(pid, |x| x.push((new_buffer_key, len))).unwrap();
#[expect(
clippy::unwrap_used,
reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition"
)]
let sleep_status = TASKING.proc_sleeping(pid).unwrap();
if sleep_status == Some(SleepReason::WaitingForIPC) {
#[expect(
clippy::unwrap_used,
reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition"
)]
TASKING.wake(pid).unwrap();
}
retval = 0;
} else {
println!("ipc_send: Bad PID ({})", pid);
retval = 1;
}
} else {
println!("ipc_send: Bad buffer ({})", regs.rdx);
retval = 1;
}
}
12 => {
if let Some(msg) = TASKING.current_message_queue_mut(|x| x.pop()) {
#[expect(
clippy::unwrap_used,
reason = "The message queue only contains valid buffer IDs"
)]
let buffer_addr =
u64(TASKING.data_buffers_mut(|x| *x.get(msg.0).unwrap()).expose_provenance());
retval2 = u64(msg.1);
retval = buffer_addr;
retval3 = u64(msg.0);
} else {
retval = 0;
}
}
13 => {
#[expect(
clippy::unwrap_used,
reason = "Syscalls cannot be called during early boot, the only time when there is no current PID"
)]
let pid = u64(TASKING.current_pid().unwrap());
retval = pid;
}
14 => 'call14: {
let Ok(page) = Page::from_start_address(VirtAddr::new(regs.rdx)) else {
retval = 1;
break 'call14;
};
let num_pages = usize(regs.rsi);
let flags = PageTableFlags::from_bits_truncate(regs.rdi);
let failed = if regs.rcx == 0 {
ACTIVE_SPACE.lock().map_only_unused(page, num_pages, flags).is_err()
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
space.map_only_unused(page, num_pages, flags).is_err()
} else {
true
}
})
};
retval = failed.into();
}
15 => {
get_buffer(regs.rcx);
}
16 => {
let size = usize(regs.rcx);
let rounded_size = size.next_multiple_of(4096);
let mut buffer = Vec::with_capacity_in(rounded_size, &*ACTIVE_SPACE);
buffer.resize(rounded_size, 0);
let buffer = buffer.into_boxed_slice();
let buffer = Box::into_raw(buffer);
retval = u64(TASKING.data_buffers_mut(|x| x.insert(buffer)));
retval2 = u64(buffer.cast::<u8>().expose_provenance());
retval3 = u64(rounded_size);
}
17 => {
#[warn(clippy::expect_used, reason = "FIXME")]
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
TASKING.address_spaces_mut(|x| {
let space = x.get_mut(usize(regs.rcx - 1)).expect("Invalid address space");
let slice_start: *mut u8 = ptr::with_exposed_provenance_mut(usize(regs.rdx));
space.run(|| unsafe {
slice::from_raw_parts_mut(slice_start, usize(regs.rsi)).fill(0);
});
});
retval = 0;
}
18 => {
if TASKING.current_message_queue_mut(|x| x.is_empty()) {
TASKING.sleep(SleepReason::WaitingForIPC);
}
}
19 => {
let args = TASKING.arguments();
retval = u64(args.0.expose_provenance());
retval2 = u64(args.1);
}
20 => {
retval = if regs.rcx == 0 {
unsafe {
ACTIVE_SPACE
.lock()
.map_free_to(
PhysFrame::from_start_address(PhysAddr::new(regs.rdx)).unwrap(),
usize(regs.rsi),
PageTableFlags::from_bits_truncate(regs.rdi),
)
.map_or(0, |x| u64(x.expose_provenance()))
}
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
unsafe {
space
.map_free_to(
PhysFrame::from_start_address(PhysAddr::new(regs.rdx)).unwrap(),
usize(regs.rsi),
PageTableFlags::from_bits_truncate(regs.rdi),
)
.map_or(0, |x| u64(x.expose_provenance()))
}
} else {
0
}
})
}
}
21 => {
retval = if regs.rcx == 0 {
let (start_virt, start_phys) = ACTIVE_SPACE
.lock()
.map_free_cont_phys(
usize(regs.rdx),
PageTableFlags::from_bits_truncate(regs.rsi),
)
.map_or((0, 0), |(ptr, start_phys)| (u64(ptr.expose_provenance()), start_phys));
retval2 = start_phys;
start_virt
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
let (start_virt, start_phys) = space
.map_free_cont_phys(
usize(regs.rdx),
PageTableFlags::from_bits_truncate(regs.rsi),
)
.map_or((0, 0), |(ptr, start_phys)| {
(u64(ptr.expose_provenance()), start_phys)
});
retval2 = start_phys;
start_virt
} else {
0
}
})
};
}
22 => 'call22: {
let Ok(page) = Page::from_start_address(VirtAddr::new(regs.rdx)) else {
retval = 1;
break 'call22;
};
retval = if regs.rcx == 0 {
u64::from(ACTIVE_SPACE.lock().unmap_and_free(page, usize(regs.rsi)).is_err())
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
u64::from(space.unmap_and_free(page, usize(regs.rsi)).is_err())
} else {
1
}
})
}
}
_ => (),
};
unsafe { SYSCALL_REGS = regs };
unsafe {
asm!(
"mov rbx, [rip+SYSCALL_REGS+8]",
"mov rsi, [rip+SYSCALL_REGS+32]",
"mov rdi, [rip+SYSCALL_REGS+40]",
"mov rsp, [rip+SYSCALL_REGS+48]",
"mov rbp, [rip+SYSCALL_REGS+56]",
"mov r8, [rip+SYSCALL_REGS+64]",
"mov r9, [rip+SYSCALL_REGS+72]",
"mov r10, [rip+SYSCALL_REGS+80]",
"mov r11, [rip+SYSCALL_REGS+88]",
"mov r12, [rip+SYSCALL_REGS+96]",
"mov r13, [rip+SYSCALL_REGS+104]",
"mov r14, [rip+SYSCALL_REGS+112]",
"mov r15, [rip+SYSCALL_REGS+120]",
"iretq",
in("rax") retval, in("rcx") retval2, in("rdx") retval3, options(noreturn)
)
}
}
pub fn register_handler(irq: u8, handler: IrqHandler) -> Result<(), InvalidIrq> {
*(IRQ_HANDLERS.write().get_mut(usize(irq)).ok_or(InvalidIrq)?) = Some(handler);
Ok(())
}