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