diff --git a/boards/qemu_i486_q35/src/main.rs b/boards/qemu_i486_q35/src/main.rs index a42d7669e0..fffbc346fe 100644 --- a/boards/qemu_i486_q35/src/main.rs +++ b/boards/qemu_i486_q35/src/main.rs @@ -27,10 +27,13 @@ use kernel::process::ProcessArray; use kernel::scheduler::cooperative::CooperativeSched; use kernel::syscall::SyscallDriver; use kernel::{create_capability, static_init}; +use x86::mpu::PagingMPU; use x86::registers::bits32::paging::{PDEntry, PTEntry, PD, PT}; use x86::registers::irq; use x86_q35::pit::{Pit, RELOAD_1KHZ}; -use x86_q35::{Pc, PcComponent}; +use x86_q35::serial::{SerialPort, COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE}; +use x86_q35::vga_uart_driver::VgaText; +use x86_q35::{vga_early_clear, x86_low_level_init, Pc}; mod multiboot; use multiboot::MultibootV1Header; @@ -148,12 +151,53 @@ impl KernelResources for QemuI386Q35Platform { unsafe extern "cdecl" fn main() { // ---------- BASIC INITIALIZATION ----------- - // Basic setup of the i486 platform - let chip = PcComponent::new( - &mut *ptr::addr_of_mut!(PAGE_DIR), - &mut *ptr::addr_of_mut!(PAGE_TABLE), - ) - .finalize(x86_q35::x86_q35_component_static!()); + // Low-level CPU + PIC (no allocations) + unsafe { + x86_low_level_init( + &mut *ptr::addr_of_mut!(PAGE_DIR), + &mut *ptr::addr_of_mut!(PAGE_TABLE), + ); + // Optional: blank BIOS banner / enable text mode before first prints + vga_early_clear(&mut *ptr::addr_of_mut!(PAGE_DIR)); + } + + // Hardware peripherals (board-owned) + let com1 = static_init!(SerialPort<'static>, unsafe { SerialPort::new(COM1_BASE) }); + kernel::deferred_call::DeferredCallClient::register(com1); + + let com2 = static_init!(SerialPort<'static>, unsafe { SerialPort::new(COM2_BASE) }); + kernel::deferred_call::DeferredCallClient::register(com2); + + let com3 = static_init!(SerialPort<'static>, unsafe { SerialPort::new(COM3_BASE) }); + kernel::deferred_call::DeferredCallClient::register(com3); + + let com4 = static_init!(SerialPort<'static>, unsafe { SerialPort::new(COM4_BASE) }); + kernel::deferred_call::DeferredCallClient::register(com4); + + let pit = unsafe { Pit::new() }; + + let vga = static_init!(VgaText, VgaText::new()); + kernel::deferred_call::DeferredCallClient::register(vga); + + // MPU / paging + let paging = unsafe { + let pd_addr = ptr::addr_of!(PAGE_DIR) as usize; + let pt_addr = ptr::addr_of!(PAGE_TABLE) as usize; + PagingMPU::new( + &mut *ptr::addr_of_mut!(PAGE_DIR), + pd_addr, + &mut *ptr::addr_of_mut!(PAGE_TABLE), + pt_addr, + ) + }; + paging.init(); + + // Build the chip + let chip = static_init!( + Pc<'static>, + Pc::new(com1, com2, com3, com4, pit, vga, paging) + ); + CHIP = Some(chip); // Acquire required capabilities let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); diff --git a/chips/x86_q35/src/chip.rs b/chips/x86_q35/src/chip.rs index 16c5f24e3a..8140aac8e0 100644 --- a/chips/x86_q35/src/chip.rs +++ b/chips/x86_q35/src/chip.rs @@ -3,18 +3,15 @@ // Copyright Tock Contributors 2024. use core::fmt::Write; -use core::mem::MaybeUninit; -use kernel::component::Component; use kernel::platform::chip::Chip; -use kernel::static_init; use x86::mpu::PagingMPU; use x86::registers::bits32::paging::{PD, PT}; use x86::support; use x86::{Boundary, InterruptPoller}; use crate::pit::{Pit, RELOAD_1KHZ}; -use crate::serial::{SerialPort, SerialPortComponent, COM1_BASE, COM2_BASE, COM3_BASE, COM4_BASE}; +use crate::serial::SerialPort; use crate::vga_uart_driver::VgaText; /// Interrupt constants for legacy PC peripherals @@ -31,6 +28,24 @@ mod interrupt { pub(super) const COM1_COM3: u32 = (PIC1_OFFSET as u32) + 4; } +/// Low-level CPU + PIC init. No allocations; safe to call once during boot. +pub unsafe fn x86_low_level_init(_pd: &mut PD, _pt: &mut PT) { + unsafe { + // CPU, IDT, segmentation, etc. + x86::init(); + // Remap/unmask legacy PIC + crate::pic::init(); + } + + // (VGA clear stays in the board; call x86_q35::vga::new_text_console(pd) there) +} + +/// Optional convenience: expose VGA early clear via chip (still no allocations). +/// Call this from the BOARD if you want the blank screen before first prints. +pub fn vga_early_clear(pd: &mut PD) { + crate::vga::new_text_console(pd); // ensure new_text_console is `pub` in vga.rs +} + /// Representation of a generic PC platform. /// /// This struct serves as an implementation of Tock's [`Chip`] trait for the x86 PC platform. The @@ -159,108 +174,25 @@ impl<'a, const PR: u16> Chip for Pc<'a, PR> { } } -/// Component helper for constructing a [`Pc`] chip instance. -/// -/// During the call to `finalize()`, this helper will perform low-level initialization of the PC -/// hardware to ensure a consistent CPU state. This includes initializing memory segmentation and -/// interrupt handling. See [`x86::init`] for further details. -pub struct PcComponent<'a> { - pd: &'a mut PD, - pt: &'a mut PT, -} - -impl<'a> PcComponent<'a> { - /// Creates a new `PcComponent` instance. - /// - /// ## Safety - /// - /// It is unsafe to construct more than a single `PcComponent` during the entire lifetime of the - /// kernel. - /// - /// Before calling, memory must be identity-mapped. Otherwise, introduction of flat segmentation - /// will cause the kernel's code/data to move unexpectedly. - /// - /// See [`x86::init`] for further details. - pub unsafe fn new(pd: &'a mut PD, pt: &'a mut PT) -> Self { - Self { pd, pt } - } -} - -impl Component for PcComponent<'static> { - type StaticInput = ( - ::StaticInput, - ::StaticInput, - ::StaticInput, - ::StaticInput, - &'static mut MaybeUninit>, - ); - type Output = &'static Pc<'static>; - - fn finalize(self, s: Self::StaticInput) -> Self::Output { - // Low-level hardware initialization. We do this first to guarantee the CPU is in a - // predictable state before initializing the chip object. - unsafe { - x86::init(); - crate::pic::init(); - // Enable the VGA path by building or running with the feature flag, e.g.: - // `cargo run -- -display none` - // A plain `make run` / `cargo run` keeps everything on COM1. - // - // Initialise VGA and clear BIOS text if VGA is enabled - // Clear BIOS banner: the real-mode BIOS leaves its text (and the cursor off-screen) in - // 0xB8000. Wiping the full 80×25 buffer gives us a clean screen and a visible cursor - // before the kernel prints its first message. - // SAFETY: PAGE_DIR is identity-mapped, aligned, and unique - let pd: &mut PD = &mut *core::ptr::from_mut(self.pd); - crate::vga::new_text_console(pd); - } - - let com1 = unsafe { SerialPortComponent::new(COM1_BASE).finalize(s.0) }; - let com2 = unsafe { SerialPortComponent::new(COM2_BASE).finalize(s.1) }; - let com3 = unsafe { SerialPortComponent::new(COM3_BASE).finalize(s.2) }; - let com4 = unsafe { SerialPortComponent::new(COM4_BASE).finalize(s.3) }; - - let pit = unsafe { Pit::new() }; - - let vga = unsafe { static_init!(VgaText, VgaText::new()) }; - - kernel::deferred_call::DeferredCallClient::register(vga); - - let paging = unsafe { - let pd_addr = core::ptr::from_ref(self.pd) as usize; - let pt_addr = core::ptr::from_ref(self.pt) as usize; - PagingMPU::new(self.pd, pd_addr, self.pt, pt_addr) - }; - - paging.init(); - - let syscall = Boundary::new(); - - let pc = s.4.write(Pc { +impl<'a, const PR: u16> Pc<'a, PR> { + pub fn new( + com1: &'a SerialPort<'a>, + com2: &'a SerialPort<'a>, + com3: &'a SerialPort<'a>, + com4: &'a SerialPort<'a>, + pit: Pit<'a, PR>, + vga: &'a VgaText<'a>, + paging: PagingMPU<'a>, + ) -> Self { + Self { com1, com2, com3, com4, pit, vga, - syscall, + syscall: Boundary::new(), paging, - }); - - pc + } } } - -/// Provides static buffers needed for `PcComponent::finalize()`. -#[macro_export] -macro_rules! x86_q35_component_static { - () => {{ - ( - $crate::serial_port_component_static!(), - $crate::serial_port_component_static!(), - $crate::serial_port_component_static!(), - $crate::serial_port_component_static!(), - kernel::static_buf!($crate::Pc<'static>), - ) - };}; -} diff --git a/chips/x86_q35/src/lib.rs b/chips/x86_q35/src/lib.rs index 457c582a4f..9aeef04c41 100644 --- a/chips/x86_q35/src/lib.rs +++ b/chips/x86_q35/src/lib.rs @@ -1,6 +1,6 @@ // Licensed under the Apache License, Version 2.0 or the MIT License. // SPDX-License-Identifier: Apache-2.0 OR MIT -// Copyright Tock Contributors 2024. +// Copyright Tock Contributors 2025. //! Support for traditional x86 PC hardware. //! @@ -15,15 +15,13 @@ #![no_std] mod chip; -pub use chip::{Pc, PcComponent}; +pub use chip::Pc; +pub use chip::{vga_early_clear, x86_low_level_init}; mod interrupts; - mod pic; pub mod pit; - pub mod serial; - pub mod vga; pub mod vga_uart_driver; diff --git a/chips/x86_q35/src/serial.rs b/chips/x86_q35/src/serial.rs index 174c4bf5fa..e6e1ae9a8d 100644 --- a/chips/x86_q35/src/serial.rs +++ b/chips/x86_q35/src/serial.rs @@ -15,11 +15,9 @@ use core::cell::Cell; use core::fmt::{self, Write}; -use core::mem::MaybeUninit; use x86::registers::io; -use kernel::component::Component; use kernel::debug::IoWrite; use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil::uart::{ @@ -161,6 +159,26 @@ pub struct SerialPort<'a> { } impl SerialPort<'_> { + /// # Safety + /// - There must be an 8250-compatible UART at `base`. + /// - Only one SerialPort may access that base. + pub unsafe fn new(base: u16) -> Self { + Self { + base, + tx_client: OptionalCell::empty(), + tx_buffer: TakeCell::empty(), + tx_len: Cell::new(0), + tx_index: Cell::new(0), + tx_abort: Cell::new(false), + rx_client: OptionalCell::empty(), + rx_buffer: TakeCell::empty(), + rx_len: Cell::new(0), + rx_index: Cell::new(0), + rx_abort: Cell::new(false), + dc: DeferredCall::new(), + } + } + /// Finishes out a long-running TX operation. fn finish_tx(&self, res: Result<(), ErrorCode>) { if let Some(b) = self.tx_buffer.take() { @@ -424,61 +442,6 @@ impl DeferredCallClient for SerialPort<'_> { } } -/// Component interface used to instantiate a [`SerialPort`] -pub struct SerialPortComponent { - base: u16, -} - -impl SerialPortComponent { - /// Constructs and returns a new instance of `SerialPortComponent`. - /// - /// ## Safety - /// - /// An 8250-compatible serial port must exist at the specified address. Otherwise we could end - /// up spamming some unknown device with I/O operations. - /// - /// The specified serial port must not be in use by any other instance of `SerialPort` or any - /// other code. - pub unsafe fn new(base: u16) -> Self { - Self { base } - } -} - -impl Component for SerialPortComponent { - type StaticInput = (&'static mut MaybeUninit>,); - type Output = &'static SerialPort<'static>; - - fn finalize(self, s: Self::StaticInput) -> Self::Output { - let serial = s.0.write(SerialPort { - base: self.base, - tx_client: OptionalCell::empty(), - tx_buffer: TakeCell::empty(), - tx_len: Cell::new(0), - tx_index: Cell::new(0), - tx_abort: Cell::new(false), - rx_client: OptionalCell::empty(), - rx_buffer: TakeCell::empty(), - rx_len: Cell::new(0), - rx_index: Cell::new(0), - rx_abort: Cell::new(false), - dc: DeferredCall::new(), - }); - - // Deferred call registration - serial.register(); - - serial - } -} - -/// Statically allocates the storage needed to finalize a [`SerialPortComponent`]. -#[macro_export] -macro_rules! serial_port_component_static { - () => {{ - (kernel::static_buf!($crate::serial::SerialPort<'static>),) - }}; -} - /// Serial port handle for blocking I/O /// /// This struct is a lightweight version of [`SerialPort`] that can be used to perform blocking diff --git a/chips/x86_q35/src/vga.rs b/chips/x86_q35/src/vga.rs index 88f7ff39ab..61c123c507 100644 --- a/chips/x86_q35/src/vga.rs +++ b/chips/x86_q35/src/vga.rs @@ -457,7 +457,7 @@ pub fn framebuffer() -> Option<(*mut u8, usize)> { } /// Initialise 80×25 text mode and start with a clean screen. -pub(crate) fn new_text_console(_page_dir_ptr: &mut x86::registers::bits32::paging::PD) { +pub fn new_text_console(_page_dir_ptr: &mut x86::registers::bits32::paging::PD) { // Program 80×25 text mode VgaDevice::set_mode(VgaMode::Text80x25);