Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 51 additions & 7 deletions boards/qemu_i486_q35/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -148,12 +151,53 @@ impl<C: Chip> KernelResources<C> 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);
Expand Down
132 changes: 32 additions & 100 deletions chips/x86_q35/src/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 = (
<SerialPortComponent as Component>::StaticInput,
<SerialPortComponent as Component>::StaticInput,
<SerialPortComponent as Component>::StaticInput,
<SerialPortComponent as Component>::StaticInput,
&'static mut MaybeUninit<Pc<'static>>,
);
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>),
)
};};
}
8 changes: 3 additions & 5 deletions chips/x86_q35/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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.
//!
Expand All @@ -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;
77 changes: 20 additions & 57 deletions chips/x86_q35/src/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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<SerialPort<'static>>,);
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
Expand Down
2 changes: 1 addition & 1 deletion chips/x86_q35/src/vga.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down