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}