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}