x86_q35/chip.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
5use core::fmt::Write;
6
7use kernel::component::Component;
8use kernel::platform::chip::{Chip, InterruptService};
9use x86::mpu::PagingMPU;
10use x86::registers::bits32::paging::{PD, PT};
11use x86::support;
12use x86::{Boundary, InterruptPoller};
13
14use crate::pic::PIC1_OFFSET;
15use crate::pit::{Pit, RELOAD_1KHZ};
16use crate::serial::{SerialPort, SerialPortComponent, COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE};
17use crate::vga_uart_driver::VgaText;
18
19/// Interrupt constants for legacy PC peripherals
20mod interrupt {
21 use crate::pic::PIC1_OFFSET;
22
23 /// Interrupt number used by PIT
24 pub(super) const PIT: u32 = PIC1_OFFSET as u32;
25
26 /// Interrupt number shared by COM2 and COM4 serial devices
27 pub(super) const COM2_COM4: u32 = (PIC1_OFFSET as u32) + 3;
28
29 /// Interrupt number shared by COM1 and COM3 serial devices
30 pub(super) const COM1_COM3: u32 = (PIC1_OFFSET as u32) + 4;
31}
32
33/// Representation of a generic PC platform.
34///
35/// This struct serves as an implementation of Tock's [`Chip`] trait for the x86 PC platform. The
36/// behavior and set of peripherals available on PCs is very heavily standardized. As a result, this
37/// chip definition should be broadly compatible with most PC hardware.
38///
39/// Parameter `PR` is the PIT reload value. See [`Pit`] for more information.
40///
41/// # Interrupt Handling
42///
43/// This chip automatically handles interrupts for legacy PC devices which are known to be present
44/// on QEMU's Q35 machine type. This includes the PIT timer and four serial ports. Other devices
45/// which are conditionally present (e.g. Virtio devices specified on the QEMU command line) may be
46/// handled via a board-specific implementation of [`InterruptService`].
47///
48/// This chip uses the legacy 8259 PIC to manage interrupts. This is relatively simple compared with
49/// using the Local APIC or I/O APIC and avoids needing to interact with ACPI or MP tables.
50///
51/// Internally, this chip re-maps the PIC interrupt numbers to avoid conflicts with ISA-defined
52/// exceptions. This remapping is fully encapsulated within the chip. **N.B.** Implementors of
53/// [`InterruptService`] will be passed the physical interrupt line number, _not_ the remapped
54/// number used internally by the chip. This should match the interrupt line number reported by
55/// documentation or read from the PCI configuration space.
56pub struct Pc<'a, I1: InterruptService + 'a, I2: InterruptService + 'a, const PR: u16 = RELOAD_1KHZ>
57{
58 /// Legacy COM1 serial port
59 pub com1: &'a SerialPort<'a>,
60
61 /// Legacy COM2 serial port
62 pub com2: &'a SerialPort<'a>,
63
64 /// Legacy COM3 serial port
65 pub com3: &'a SerialPort<'a>,
66
67 /// Legacy COM4 serial port
68 pub com4: &'a SerialPort<'a>,
69
70 /// Legacy PIT timer
71 pub pit: &'a Pit<'a, PR>,
72
73 /// Vga
74 pub vga: &'a VgaText<'a>,
75
76 /// System call context
77 syscall: Boundary,
78 paging: PagingMPU<'a>,
79
80 /// Interrupt service used to dispatch IRQs to peripherals
81 default_peripherals: &'a I1,
82
83 /// Interrupt service used to dispatch IRQs to board-specific peripherals
84 board_peripherals: &'a I2,
85}
86
87impl<I2: InterruptService, const PR: u16> Pc<'static, PcDefaultPeripherals<PR>, I2, PR> {
88 /// Construct `Pc` using a standard set of peripherals plus page tables.
89 ///
90 /// ## Safety
91 /// - Must be called only once for the lifetime of the kernel.
92 /// - `pd` and `pt` must be identity-mapped and unique.
93 pub unsafe fn new(
94 default_peripherals: &'static PcDefaultPeripherals<PR>,
95 pd: &'static mut PD,
96 pt: &'static mut PT,
97 board_peripherals: &'static I2,
98 ) -> Self {
99 let paging = unsafe {
100 let pd_addr = core::ptr::from_ref(pd) as usize;
101 let pt_addr = core::ptr::from_ref(pt) as usize;
102 let mpu = PagingMPU::new(pd, pd_addr, pt, pt_addr);
103 mpu.init();
104 mpu
105 };
106
107 let syscall = Boundary::new();
108
109 Self {
110 com1: default_peripherals.com1,
111 com2: default_peripherals.com2,
112 com3: default_peripherals.com3,
113 com4: default_peripherals.com4,
114 pit: &default_peripherals.pit,
115 vga: default_peripherals.vga,
116 syscall,
117 paging,
118 default_peripherals,
119 board_peripherals,
120 }
121 }
122}
123
124impl<'a, I1: InterruptService, I2: InterruptService, const PR: u16> Chip for Pc<'a, I1, I2, PR> {
125 type ThreadIdProvider = x86::thread_id::X86ThreadIdProvider;
126
127 type MPU = PagingMPU<'a>;
128 fn mpu(&self) -> &Self::MPU {
129 &self.paging
130 }
131
132 type UserspaceKernelBoundary = Boundary;
133 fn userspace_kernel_boundary(&self) -> &Self::UserspaceKernelBoundary {
134 &self.syscall
135 }
136
137 fn service_pending_interrupts(&self) {
138 InterruptPoller::access(|poller| {
139 while let Some(num) = poller.next_pending() {
140 let mut handled = true;
141 match unsafe { self.default_peripherals.service_interrupt(num) } {
142 true => {}
143 false => {
144 // Convert back to physical interrupt line number before passing to
145 // board-specific handler
146 let phys_num = num - PIC1_OFFSET as u32;
147 handled = unsafe { self.board_peripherals.service_interrupt(phys_num) };
148 }
149 }
150 poller.clear_pending(num);
151
152 // Unmask the interrupt so it can fire again, but only if we know how to handle it
153 if handled {
154 unsafe {
155 crate::pic::unmask(num);
156 }
157 } else {
158 kernel::debug!("Unhandled external interrupt {} left masked", num);
159 }
160 }
161 })
162 }
163
164 fn has_pending_interrupts(&self) -> bool {
165 InterruptPoller::access(|poller| poller.next_pending().is_some())
166 }
167
168 #[cfg(target_arch = "x86")]
169 fn sleep(&self) {
170 use x86::registers::bits32::eflags::{self, EFLAGS};
171
172 // On conventional embedded architectures like ARM and RISC-V, interrupts must be disabled
173 // before going to sleep. But on x86 it is the opposite; we must ensure interrupts are
174 // enabled before issuing the HLT instruction. Otherwise we will never wake up.
175 let eflags = unsafe { eflags::read() };
176 let enabled = eflags.0.is_set(EFLAGS::FLAGS_IF);
177
178 if enabled {
179 // Interrupts are already enabled, so go ahead and HLT.
180 //
181 // Safety: Assume we are running in ring zero.
182 unsafe {
183 x86::halt();
184 }
185 } else {
186 // We need to re-enable interrupts before HLT-ing. We use inline assembly to guarantee
187 // these instructions are executed back-to-back.
188 //
189 // Safety:
190 //
191 // As above, assume we are running in ring zero.
192 //
193 // Strictly speaking, this could cause to a TOCTOU race condition if `sleep` is called
194 // within an `atomic` block, because interrupt handlers would be executed. Solving this
195 // properly would require deep changes to Tock's `Chip` trait and kernel logic.
196 //
197 // In practice this doesn't seem to be an issue. `sleep` is only ever called once at the
198 // end of the kernel's main loop, and that code does not appear to be vulnerable to the
199 // TOCTOU.
200 unsafe {
201 core::arch::asm!("sti; hlt; cli");
202 }
203 }
204 }
205
206 #[cfg(not(target_arch = "x86"))]
207 fn sleep(&self) {
208 unimplemented!()
209 }
210
211 unsafe fn with_interrupts_disabled<F, R>(&self, f: F) -> R
212 where
213 F: FnOnce() -> R,
214 {
215 support::with_interrupts_disabled(f)
216 }
217
218 unsafe fn print_state(_this: Option<&Self>, writer: &mut dyn Write) {
219 let _ = writeln!(writer);
220 let _ = writeln!(writer, "---| PC State |---");
221 let _ = writeln!(writer);
222
223 // todo: print out anything that might be useful
224
225 let _ = writeln!(writer, "(placeholder)");
226 }
227}
228
229/// Default x86 PC peripherals
230pub struct PcDefaultPeripherals<const PR: u16 = RELOAD_1KHZ> {
231 pub com1: &'static SerialPort<'static>,
232 pub com2: &'static SerialPort<'static>,
233 pub com3: &'static SerialPort<'static>,
234 pub com4: &'static SerialPort<'static>,
235 pub pit: Pit<'static, PR>,
236 pub vga: &'static VgaText<'static>,
237}
238
239impl<const PR: u16> PcDefaultPeripherals<PR> {
240 /// Create and initialize default peripherals.
241 ///
242 /// The caller must provide statics through `x86_q35_peripherals_static!()`.
243 ///
244 /// ## Safety
245 /// - Must be called only once per kernel lifetime.
246 pub unsafe fn new(
247 s: (
248 (&'static mut core::mem::MaybeUninit<SerialPort<'static>>,),
249 (&'static mut core::mem::MaybeUninit<SerialPort<'static>>,),
250 (&'static mut core::mem::MaybeUninit<SerialPort<'static>>,),
251 (&'static mut core::mem::MaybeUninit<SerialPort<'static>>,),
252 &'static mut core::mem::MaybeUninit<VgaText<'static>>,
253 ),
254 page_dir: &mut PD,
255 ) -> Self {
256 // CPU/interrupt controller/VGA baseline init
257 // SAFETY: PAGE_DIR is identity-mapped, aligned, and unique
258 unsafe {
259 x86::init();
260 crate::pic::init();
261 let pd_ref: &mut PD = &mut *core::ptr::from_mut(page_dir);
262 // Enable the VGA path by building or running with the feature flag, e.g.:
263 // `cargo run -- -display none`
264 // A plain `make run` / `cargo run` keeps everything on COM1.
265 //
266 // Initialise VGA and clear BIOS text if VGA is enabled
267 // Clear BIOS banner: the real-mode BIOS leaves its text (and the cursor off-screen) in
268 // 0xB8000. Wiping the full 80×25 buffer gives us a clean screen and a visible cursor
269 // before the kernel prints its first message.
270 crate::vga::new_text_console(pd_ref);
271 }
272
273 let com1 = unsafe { SerialPortComponent::new(COM1_BASE).finalize(s.0) };
274 let com2 = unsafe { SerialPortComponent::new(COM2_BASE).finalize(s.1) };
275 let com3 = unsafe { SerialPortComponent::new(COM3_BASE).finalize(s.2) };
276 let com4 = unsafe { SerialPortComponent::new(COM4_BASE).finalize(s.3) };
277
278 let pit = unsafe { Pit::new() };
279
280 let vga = s.4.write(VgaText::new());
281
282 Self {
283 com1,
284 com2,
285 com3,
286 com4,
287 pit,
288 vga,
289 }
290 }
291
292 /// Finalize deferred-call registrations and any circular deps.
293 pub fn setup_circular_deps(&self) {
294 kernel::deferred_call::DeferredCallClient::register(self.vga);
295 }
296}
297
298impl<const PR: u16> InterruptService for PcDefaultPeripherals<PR> {
299 unsafe fn service_interrupt(&self, num: u32) -> bool {
300 match num {
301 interrupt::PIT => {
302 self.pit.handle_interrupt();
303 true
304 }
305 interrupt::COM2_COM4 => {
306 self.com2.handle_interrupt();
307 self.com4.handle_interrupt();
308 true
309 }
310 interrupt::COM1_COM3 => {
311 self.com1.handle_interrupt();
312 self.com3.handle_interrupt();
313 true
314 }
315 _ => false,
316 }
317 }
318}