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;
10
11use x86::mpu::PagingMPU;
12use x86::registers::bits32::paging::{PD, PT};
13use x86::support;
14use x86::{Boundary, InterruptPoller};
15
16use crate::pit::{Pit, RELOAD_1KHZ};
17use crate::serial::{SerialPort, SerialPortComponent, COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE};
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.
40pub struct Pc<'a, const PR: u16 = RELOAD_1KHZ> {
41    /// Legacy COM1 serial port
42    pub com1: &'a SerialPort<'a>,
43
44    /// Legacy COM2 serial port
45    pub com2: &'a SerialPort<'a>,
46
47    /// Legacy COM3 serial port
48    pub com3: &'a SerialPort<'a>,
49
50    /// Legacy COM4 serial port
51    pub com4: &'a SerialPort<'a>,
52
53    /// Legacy PIT timer
54    pub pit: Pit<'a, PR>,
55
56    /// System call context
57    syscall: Boundary,
58    paging: PagingMPU<'a>,
59}
60
61impl<'a, const PR: u16> Chip for Pc<'a, PR> {
62    type MPU = PagingMPU<'a>;
63    fn mpu(&self) -> &Self::MPU {
64        &self.paging
65    }
66
67    type UserspaceKernelBoundary = Boundary;
68    fn userspace_kernel_boundary(&self) -> &Self::UserspaceKernelBoundary {
69        &self.syscall
70    }
71
72    fn service_pending_interrupts(&self) {
73        InterruptPoller::access(|poller| {
74            while let Some(num) = poller.next_pending() {
75                match num {
76                    interrupt::PIT => self.pit.handle_interrupt(),
77                    interrupt::COM2_COM4 => {
78                        self.com2.handle_interrupt();
79                        self.com4.handle_interrupt();
80                    }
81                    interrupt::COM1_COM3 => {
82                        self.com1.handle_interrupt();
83                        self.com3.handle_interrupt();
84                    }
85                    _ => unimplemented!("interrupt {num}"),
86                }
87
88                poller.clear_pending(num);
89            }
90        })
91    }
92
93    fn has_pending_interrupts(&self) -> bool {
94        InterruptPoller::access(|poller| poller.next_pending().is_some())
95    }
96
97    #[cfg(target_arch = "x86")]
98    fn sleep(&self) {
99        use x86::registers::bits32::eflags::{self, EFLAGS};
100
101        // On conventional embedded architectures like ARM and RISC-V, interrupts must be disabled
102        // before going to sleep. But on x86 it is the opposite; we must ensure interrupts are
103        // enabled before issuing the HLT instruction. Otherwise we will never wake up.
104        let eflags = unsafe { eflags::read() };
105        let enabled = eflags.0.is_set(EFLAGS::FLAGS_IF);
106
107        if enabled {
108            // Interrupts are already enabled, so go ahead and HLT.
109            //
110            // Safety: Assume we are running in ring zero.
111            unsafe {
112                x86::halt();
113            }
114        } else {
115            // We need to re-enable interrupts before HLT-ing. We use inline assembly to guarantee
116            // these instructions are executed back-to-back.
117            //
118            // Safety:
119            //
120            // As above, assume we are running in ring zero.
121            //
122            // Strictly speaking, this could cause to a TOCTOU race condition if `sleep` is called
123            // within an `atomic` block, because interrupt handlers would be executed. Solving this
124            // properly would require deep changes to Tock's `Chip` trait and kernel logic.
125            //
126            // In practice this doesn't seem to be an issue. `sleep` is only ever called once at the
127            // end of the kernel's main loop, and that code does not appear to be vulnerable to the
128            // TOCTOU.
129            unsafe {
130                core::arch::asm!("sti; hlt; cli");
131            }
132        }
133    }
134
135    #[cfg(not(target_arch = "x86"))]
136    fn sleep(&self) {
137        unimplemented!()
138    }
139
140    unsafe fn atomic<F, R>(&self, f: F) -> R
141    where
142        F: FnOnce() -> R,
143    {
144        support::atomic(f)
145    }
146
147    unsafe fn print_state(&self, writer: &mut dyn Write) {
148        let _ = writeln!(writer);
149        let _ = writeln!(writer, "---| PC State |---");
150        let _ = writeln!(writer);
151
152        // todo: print out anything that might be useful
153
154        let _ = writeln!(writer, "(placeholder)");
155    }
156}
157
158/// Component helper for constructing a [`Pc`] chip instance.
159///
160/// During the call to `finalize()`, this helper will perform low-level initialization of the PC
161/// hardware to ensure a consistent CPU state. This includes initializing memory segmentation and
162/// interrupt handling. See [`x86::init`] for further details.
163pub struct PcComponent<'a> {
164    pd: &'a mut PD,
165    pt: &'a mut PT,
166}
167
168impl<'a> PcComponent<'a> {
169    /// Creates a new `PcComponent` instance.
170    ///
171    /// ## Safety
172    ///
173    /// It is unsafe to construct more than a single `PcComponent` during the entire lifetime of the
174    /// kernel.
175    ///
176    /// Before calling, memory must be identity-mapped. Otherwise, introduction of flat segmentation
177    /// will cause the kernel's code/data to move unexpectedly.
178    ///
179    /// See [`x86::init`] for further details.
180    pub unsafe fn new(pd: &'a mut PD, pt: &'a mut PT) -> Self {
181        Self { pd, pt }
182    }
183}
184
185impl Component for PcComponent<'static> {
186    type StaticInput = (
187        <SerialPortComponent as Component>::StaticInput,
188        <SerialPortComponent as Component>::StaticInput,
189        <SerialPortComponent as Component>::StaticInput,
190        <SerialPortComponent as Component>::StaticInput,
191        &'static mut MaybeUninit<Pc<'static>>,
192    );
193    type Output = &'static Pc<'static>;
194
195    fn finalize(self, s: Self::StaticInput) -> Self::Output {
196        // Low-level hardware initialization. We do this first to guarantee the CPU is in a
197        // predictable state before initializing the chip object.
198        unsafe {
199            x86::init();
200            crate::pic::init();
201        }
202
203        let com1 = unsafe { SerialPortComponent::new(COM1_BASE).finalize(s.0) };
204        let com2 = unsafe { SerialPortComponent::new(COM2_BASE).finalize(s.1) };
205        let com3 = unsafe { SerialPortComponent::new(COM3_BASE).finalize(s.2) };
206        let com4 = unsafe { SerialPortComponent::new(COM4_BASE).finalize(s.3) };
207
208        let pit = unsafe { Pit::new() };
209
210        let paging = unsafe {
211            let pd_addr = core::ptr::from_ref(self.pd) as usize;
212            let pt_addr = core::ptr::from_ref(self.pt) as usize;
213            PagingMPU::new(self.pd, pd_addr, self.pt, pt_addr)
214        };
215
216        paging.init();
217
218        let syscall = Boundary::new();
219
220        let pc = s.4.write(Pc {
221            com1,
222            com2,
223            com3,
224            com4,
225            pit,
226            syscall,
227            paging,
228        });
229
230        pc
231    }
232}
233
234/// Provides static buffers needed for `PcComponent::finalize()`.
235#[macro_export]
236macro_rules! x86_q35_component_static {
237    () => {{
238        (
239            $crate::serial_port_component_static!(),
240            $crate::serial_port_component_static!(),
241            $crate::serial_port_component_static!(),
242            $crate::serial_port_component_static!(),
243            kernel::static_buf!($crate::Pc<'static>),
244        )
245    };};
246}