x86/interrupts/idt.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5//! Interrupt descriptor table management
6//!
7//! ## Handler Stubs
8//!
9//! It is tempting to populate all IDT entries with a pointer to the same shared interrupt service
10//! routine. Unfortunately the x86 architecture does not define a mechanism for discovering the
11//! current interrupt number. Software is expected to program each IDT entry with a different,
12//! contextually appropriate ISR.
13//!
14//! Instead of writing 256 unique ISRs, this crate uses _build.rs_ to automatically generate 256
15//! tiny "handler stubs". Each stub is a short assembly routine which pushes the current interrupt
16//! number on the stack before jumping to the common ISR entrypoint in _handler_entry.rs_.
17//!
18//! The generated assembly also exports symbols for the first two stubs ([`handler_stub_0`] and
19//! [`handler_stub_1`]). Care is taken to ensure each stub is the exact same size, which allows us
20//! to use these symbols to compute the addresses of all the rest.
21
22use crate::registers::dtables::{self, DescriptorTablePointer};
23use crate::registers::irq::{BREAKPOINT_VECTOR, DEBUG_VECTOR, OVERFLOW_VECTOR};
24use crate::registers::ring::Ring;
25use crate::registers::segmentation::{
26 BuildDescriptor, Descriptor, DescriptorBuilder, GateDescriptorBuilder,
27};
28
29use kernel::static_init;
30
31use crate::segmentation::KERNEL_CODE;
32
33use super::{NUM_VECTORS, SYSCALL_VECTOR};
34
35extern "C" {
36 /// First byte of handler stub 0
37 static handler_stub_0: u8;
38
39 /// First byte of handler stub 1
40 static handler_stub_1: u8;
41}
42
43/// List of interrupt numbers which can be invoked from user mode.
44///
45/// When initializing the IDT, the interrupts listed here will have DPL set to 3. All other
46/// interrupts will have DPL of 0.
47const USER_ACCESSIBLE_INTERRUPTS: &[u8] = &[
48 DEBUG_VECTOR,
49 BREAKPOINT_VECTOR,
50 OVERFLOW_VECTOR,
51 SYSCALL_VECTOR,
52];
53
54/// Performs global initialization of interrupt handlers.
55///
56/// This function is primarily responsible for allocating and initializing the IDT. It creates an
57/// entry for every possible interrupt vector (see [`NUM_VECTORS`]) referencing the corresponding
58/// handler stub.
59///
60/// ## Safety
61///
62/// Must not be called more than once, or the IDT created by this function may become corrupted.
63///
64/// The kernel's segmentation must already be initialized (via [`segmentation::init`][crate::segmentation::init])
65/// prior to calling this function, and it must never be changed afterwards.
66///
67/// After this function returns, it is safe to enable interrupts. However, interrupts below number
68/// 32 must **never** be generated except by the CPU itself (i.e. exceptions), as doing so would
69/// interfere with the internal handler stubs. This means that before enabling interrupts, the
70/// caller must ensure that any hardware delivering external interrupts (such as the PIC/APIC) is
71/// configured to use interrupt number 32 or above.
72pub(super) unsafe fn init() {
73 let idt = static_init!([Descriptor; NUM_VECTORS], [Descriptor::NULL; NUM_VECTORS]);
74
75 // Handler stubs are laid out as an array in memory. We use the addresses of the first two stubs
76 // to compute the size of each stub. This allows us to compute the address of any arbitrary stub
77 // "i" using the expression "base + (size * i)".
78 //
79 // Safety: These symbols come from an assembly file which is generated by build.rs. We assume
80 // they have been generated correctly.
81 let stub_base = unsafe { core::ptr::from_ref::<u8>(&handler_stub_0) as u32 };
82 let stub_1_addr = unsafe { core::ptr::from_ref::<u8>(&handler_stub_1) as u32 };
83 let stub_size = stub_1_addr - stub_base;
84
85 for (i, idt_item) in idt.iter_mut().enumerate() {
86 let stub_addr = stub_base + (stub_size * i as u32);
87
88 // Certain interrupts need DPL of 3 so they can be invoked from user mode.
89 let dpl = if USER_ACCESSIBLE_INTERRUPTS.contains(&(i as u8)) {
90 Ring::Ring3
91 } else {
92 Ring::Ring0
93 };
94
95 let desc = DescriptorBuilder::interrupt_descriptor(KERNEL_CODE, stub_addr)
96 .present()
97 .dpl(dpl)
98 .finish();
99 *idt_item = desc;
100 }
101
102 unsafe {
103 dtables::lidt(&DescriptorTablePointer::new_from_slice(idt));
104 }
105}