qemu_i486_q35/
main.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
5//! Board file for qemu-system-i486 "q35" machine type
6
7#![no_std]
8// Disable this attribute when documenting, as a workaround for
9// https://github.com/rust-lang/rust/issues/62184.
10#![cfg_attr(not(doc), no_main)]
11
12use capsules_core::alarm;
13use capsules_core::console::{self, Console};
14use capsules_core::rng::RngDriver;
15use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm};
16use components::console::ConsoleComponent;
17use components::debug_writer::DebugWriterComponent;
18use core::ptr;
19use kernel::capabilities;
20use kernel::component::Component;
21use kernel::debug;
22use kernel::deferred_call::DeferredCallClient;
23use kernel::hil;
24use kernel::ipc::IPC;
25use kernel::platform::chip::InterruptService;
26use kernel::platform::scheduler_timer::VirtualSchedulerTimer;
27use kernel::platform::{KernelResources, SyscallDriverLookup};
28use kernel::process::ProcessArray;
29use kernel::scheduler::cooperative::CooperativeSched;
30use kernel::syscall::SyscallDriver;
31use kernel::utilities::cells::OptionalCell;
32use kernel::{create_capability, static_init};
33use virtio::devices::virtio_rng::VirtIORng;
34use virtio::devices::VirtIODeviceType;
35use virtio_pci_x86::VirtIOPCIDevice;
36use x86::registers::bits32::paging::{PDEntry, PTEntry, PD, PT};
37use x86::registers::irq;
38use x86_q35::pit::{Pit, RELOAD_1KHZ};
39use x86_q35::{Pc, PcComponent};
40
41mod multiboot;
42use multiboot::MultibootV1Header;
43
44mod io;
45
46/// Multiboot V1 header, allowing this kernel to be booted directly by QEMU
47///
48/// When compiling for a macOS host, the `link_section` attribute is elided as
49/// it yields the following error: `mach-o section specifier requires a segment
50/// and section separated by a comma`.
51#[cfg_attr(not(target_os = "macos"), link_section = ".multiboot")]
52#[used]
53static MULTIBOOT_V1_HEADER: MultibootV1Header = MultibootV1Header::new(0);
54
55const NUM_PROCS: usize = 4;
56
57type ChipHw = Pc<'static, ()>;
58
59/// Static variables used by io.rs.
60static mut PROCESSES: Option<&'static ProcessArray<NUM_PROCS>> = None;
61
62// Reference to the chip for panic dumps
63static mut CHIP: Option<&'static ChipHw> = None;
64
65// Reference to the process printer for panic dumps.
66static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> =
67    None;
68
69// How should the kernel respond when a process faults.
70const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy =
71    capsules_system::process_policies::PanicFaultPolicy {};
72
73kernel::stack_size! {0x1000}
74
75// Static allocations used for page tables
76//
77// These are placed into custom sections so they can be properly aligned and padded in layout.ld
78#[no_mangle]
79#[cfg_attr(not(target_os = "macos"), link_section = ".pde")]
80pub static mut PAGE_DIR: PD = [PDEntry(0); 1024];
81#[no_mangle]
82#[cfg_attr(not(target_os = "macos"), link_section = ".pte")]
83pub static mut PAGE_TABLE: PT = [PTEntry(0); 1024];
84
85/// Initializes a Virtio transport driver for the given PCI device.
86///
87/// Disables MSI/MSI-X interrupts for the device since the x86_q35 chip uses the 8259 PIC for
88/// interrupt management.
89///
90/// On success, returns a tuple containing the interrupt line number assigned to this device as well
91/// as a fully initialized Virtio PCI transport driver.
92///
93/// Returns `None` if the device does not report an assigned interrupt line, or if the transport
94/// driver fails to initialize for some other reason. Either of these could be an indication that
95/// `dev` is not a valid Virtio device.
96fn init_virtio_dev(
97    dev: pci_x86::Device,
98    dev_type: VirtIODeviceType,
99) -> Option<(u8, VirtIOPCIDevice)> {
100    use pci_x86::cap::Cap;
101
102    let int_line = dev.int_line()?;
103
104    for cap in dev.capabilities() {
105        match cap {
106            Cap::Msi(cap) => {
107                cap.disable();
108            }
109            Cap::Msix(cap) => {
110                cap.disable();
111            }
112            _ => {}
113        }
114    }
115
116    let dev = VirtIOPCIDevice::from_pci_device(dev, dev_type)?;
117
118    Some((int_line, dev))
119}
120
121/// Provides interrupt servicing logic for Virtio devices which may or may not be present at
122/// runtime.
123struct VirtioDevices {
124    rng: OptionalCell<(u8, &'static VirtIOPCIDevice)>,
125}
126
127impl InterruptService for VirtioDevices {
128    unsafe fn service_interrupt(&self, interrupt: u32) -> bool {
129        let mut handled = false;
130
131        self.rng.map(|(int_line, dev)| {
132            if interrupt == (int_line as u32) {
133                dev.handle_interrupt();
134                handled = true;
135            }
136        });
137
138        handled
139    }
140}
141
142pub struct QemuI386Q35Platform {
143    pconsole: &'static capsules_core::process_console::ProcessConsole<
144        'static,
145        { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN },
146        VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>,
147        components::process_console::Capability,
148    >,
149    console: &'static Console<'static>,
150    lldb: &'static capsules_core::low_level_debug::LowLevelDebug<
151        'static,
152        capsules_core::virtualizers::virtual_uart::UartDevice<'static>,
153    >,
154    alarm: &'static capsules_core::alarm::AlarmDriver<
155        'static,
156        VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>,
157    >,
158    ipc: IPC<{ NUM_PROCS as u8 }>,
159    scheduler: &'static CooperativeSched<'static>,
160    scheduler_timer:
161        &'static VirtualSchedulerTimer<VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>>,
162    rng: Option<&'static RngDriver<'static, VirtIORng<'static, 'static>>>,
163}
164
165impl SyscallDriverLookup for QemuI386Q35Platform {
166    fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
167    where
168        F: FnOnce(Option<&dyn SyscallDriver>) -> R,
169    {
170        match driver_num {
171            console::DRIVER_NUM => f(Some(self.console)),
172            alarm::DRIVER_NUM => f(Some(self.alarm)),
173            capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)),
174            capsules_core::rng::DRIVER_NUM => {
175                if let Some(rng) = self.rng {
176                    f(Some(rng))
177                } else {
178                    f(None)
179                }
180            }
181            kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
182            _ => f(None),
183        }
184    }
185}
186
187impl<C: kernel::platform::chip::Chip> KernelResources<C> for QemuI386Q35Platform {
188    type SyscallDriverLookup = Self;
189    fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup {
190        self
191    }
192
193    type SyscallFilter = ();
194    fn syscall_filter(&self) -> &Self::SyscallFilter {
195        &()
196    }
197
198    type ProcessFault = ();
199    fn process_fault(&self) -> &Self::ProcessFault {
200        &()
201    }
202
203    type Scheduler = CooperativeSched<'static>;
204    fn scheduler(&self) -> &Self::Scheduler {
205        self.scheduler
206    }
207
208    type SchedulerTimer =
209        VirtualSchedulerTimer<VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>>;
210    fn scheduler_timer(&self) -> &Self::SchedulerTimer {
211        self.scheduler_timer
212    }
213
214    type WatchDog = ();
215    fn watchdog(&self) -> &Self::WatchDog {
216        &()
217    }
218
219    type ContextSwitchCallback = ();
220    fn context_switch_callback(&self) -> &Self::ContextSwitchCallback {
221        &()
222    }
223}
224#[no_mangle]
225unsafe extern "cdecl" fn main() {
226    // ---------- BASIC INITIALIZATION -----------
227
228    // Basic setup of the i486 platform
229    let virtio_devs = static_init!(
230        VirtioDevices,
231        VirtioDevices {
232            rng: OptionalCell::empty(),
233        }
234    );
235    let chip = PcComponent::new(
236        &mut *ptr::addr_of_mut!(PAGE_DIR),
237        &mut *ptr::addr_of_mut!(PAGE_TABLE),
238        virtio_devs,
239    )
240    .finalize(x86_q35::x86_q35_component_static!(VirtioDevices));
241
242    // Acquire required capabilities
243    let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability);
244    let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability);
245    let main_loop_cap = create_capability!(capabilities::MainLoopCapability);
246
247    // Create an array to hold process references.
248    let processes = components::process_array::ProcessArrayComponent::new()
249        .finalize(components::process_array_component_static!(NUM_PROCS));
250    PROCESSES = Some(processes);
251
252    // Setup space to store the core kernel data structure.
253    let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(processes.as_slice()));
254
255    // ---------- QEMU-SYSTEM-I386 "Q35" MACHINE PERIPHERALS ----------
256
257    // Create a shared UART channel for the console and for kernel
258    // debug over the provided 8250-compatible UART.
259    let uart_mux = components::console::UartMuxComponent::new(chip.com1, 115_200)
260        .finalize(components::uart_mux_component_static!());
261
262    // Alternative for VGA
263    let vga_uart_mux = components::console::UartMuxComponent::new(chip.vga, 115_200)
264        .finalize(components::uart_mux_component_static!());
265
266    // Debug output: default to the VGA mux is
267    // active.  If you prefer to keep debug on the serial port even with VGA
268    // enabled, comment the line below and uncomment the next one.
269
270    // Debug output uses VGA when available, otherwise COM1
271    let debug_uart_device = vga_uart_mux;
272
273    // let debug_uart_device  = com1_uart_mux;
274
275    // Create a shared virtualization mux layer on top of a single hardware
276    // alarm.
277    let mux_alarm = static_init!(
278        MuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>,
279        MuxAlarm::new(&chip.pit),
280    );
281    hil::time::Alarm::set_alarm_client(&chip.pit, mux_alarm);
282
283    // Virtual alarm for the scheduler
284    let systick_virtual_alarm = static_init!(
285        VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>,
286        VirtualMuxAlarm::new(mux_alarm)
287    );
288    systick_virtual_alarm.setup();
289
290    // Virtual alarm and driver for userspace
291    let virtual_alarm_user = static_init!(
292        VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>,
293        VirtualMuxAlarm::new(mux_alarm)
294    );
295    virtual_alarm_user.setup();
296
297    let alarm = static_init!(
298        capsules_core::alarm::AlarmDriver<
299            'static,
300            VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>,
301        >,
302        capsules_core::alarm::AlarmDriver::new(
303            virtual_alarm_user,
304            board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap)
305        )
306    );
307    hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm);
308
309    // ---------- VIRTIO PERIPHERAL DISCOVERY ----------
310    //
311    // On x86, PCI is used to discover and communicate with Virtio devices.
312    //
313    // Enumerate the PCI bus to find supported Virtio devices. If there are two instances of a
314    // supported peripheral, we use the first one we encounter.
315    let mut virtio_rng_dev = None;
316    for dev in pci_x86::iter() {
317        use virtio::devices::VirtIODeviceType;
318        use virtio_pci_x86::{DEVICE_ID_BASE, VENDOR_ID};
319
320        // Only consider Virtio devices
321        if dev.vendor_id() != VENDOR_ID {
322            continue;
323        }
324        let dev_id = dev.device_id();
325        if dev_id < DEVICE_ID_BASE {
326            continue;
327        }
328
329        // Decode device type
330        let dev_id = (dev_id - DEVICE_ID_BASE) as u32;
331        let Some(dev_type) = VirtIODeviceType::from_device_id(dev_id) else {
332            continue;
333        };
334
335        if dev_type == VirtIODeviceType::EntropySource {
336            // Only consider first entropy source found
337            if virtio_rng_dev.is_some() {
338                continue;
339            }
340
341            virtio_rng_dev = Some(dev);
342        }
343    }
344
345    // If there is a VirtIO EntropySource present, use the appropriate VirtIORng
346    // driver and expose it to userspace though the RngDriver
347    let virtio_rng: Option<&'static VirtIORng> = if let Some(rng_dev) = virtio_rng_dev {
348        use virtio::queues::split_queue::{
349            SplitVirtqueue, VirtqueueAvailableRing, VirtqueueDescriptors, VirtqueueUsedRing,
350        };
351        use virtio::queues::Virtqueue;
352        use virtio::transports::VirtIOTransport;
353
354        // Initialize PCI transport driver
355        let (int_line, transport) = init_virtio_dev(rng_dev, VirtIODeviceType::EntropySource)
356            .expect("virtio pci init failed");
357        let transport = static_init!(VirtIOPCIDevice, transport);
358
359        // EntropySource requires a single Virtqueue for retrieved entropy
360        let descriptors = static_init!(VirtqueueDescriptors<1>, VirtqueueDescriptors::default(),);
361        let available_ring =
362            static_init!(VirtqueueAvailableRing<1>, VirtqueueAvailableRing::default(),);
363        let used_ring = static_init!(VirtqueueUsedRing<1>, VirtqueueUsedRing::default(),);
364        let queue = static_init!(
365            SplitVirtqueue<1>,
366            SplitVirtqueue::new(descriptors, available_ring, used_ring),
367        );
368        queue.set_transport(transport);
369
370        // VirtIO EntropySource device driver instantiation
371        let rng = static_init!(VirtIORng, VirtIORng::new(queue));
372        DeferredCallClient::register(rng);
373        queue.set_client(rng);
374
375        // Register the queues and driver with the transport, so interrupts
376        // are routed properly
377        let queues = static_init!([&'static dyn Virtqueue; 1], [queue; 1]);
378        transport.initialize(rng, queues).unwrap();
379
380        // Provide an internal randomness buffer
381        let rng_buffer = static_init!([u8; 64], [0; 64]);
382        rng.provide_buffer(rng_buffer)
383            .expect("rng: providing initial buffer failed");
384
385        // Device is successfully initialized, register it with the VirtioDevices struct so that
386        // interrupts are routed properly
387        virtio_devs.rng.set((int_line, transport));
388
389        Some(rng)
390    } else {
391        None
392    };
393
394    // ---------- INITIALIZE CHIP, ENABLE INTERRUPTS ---------
395
396    // PIT interrupts need to be started manually
397    chip.pit.start();
398
399    // Enable interrupts after all drivers are initialized
400    irq::enable();
401
402    // ---------- FINAL SYSTEM INITIALIZATION ----------
403
404    // Create the process printer used in panic prints, etc.
405    let process_printer = components::process_printer::ProcessPrinterTextComponent::new()
406        .finalize(components::process_printer_text_component_static!());
407    PROCESS_PRINTER = Some(process_printer);
408
409    // ProcessConsole stays on COM1 because we have no keyboard input yet.
410    // As soon as keyboard support will be added, the process console
411    // may be used with the VGA and keyboard.
412    //
413    // let console_uart_device = vga_uart_mux;
414
415    // For now the ProcessConsole (interactive shell) is wired to COM1 so the user can
416    // type commands over the serial port.  Once keyboard input is implemented
417    // we can switch `console_uart_device` to `vga_uart_mux`.
418    let console_uart_device = uart_mux;
419
420    // Initialize the kernel's process console.
421    let pconsole = components::process_console::ProcessConsoleComponent::new(
422        board_kernel,
423        console_uart_device,
424        mux_alarm,
425        process_printer,
426        None,
427    )
428    .finalize(components::process_console_component_static!(
429        Pit<'static, RELOAD_1KHZ>
430    ));
431
432    // Setup the console.
433    let console = ConsoleComponent::new(board_kernel, console::DRIVER_NUM, console_uart_device)
434        .finalize(components::console_component_static!());
435
436    // Create the debugger object that handles calls to `debug!()`.
437    DebugWriterComponent::new::<<ChipHw as kernel::platform::chip::Chip>::ThreadIdProvider>(
438        debug_uart_device,
439        create_capability!(capabilities::SetDebugWriterCapability),
440    )
441    .finalize(components::debug_writer_component_static!());
442
443    let lldb = components::lldb::LowLevelDebugComponent::new(
444        board_kernel,
445        capsules_core::low_level_debug::DRIVER_NUM,
446        uart_mux,
447    )
448    .finalize(components::low_level_debug_component_static!());
449
450    // ---------- RNG ----------
451
452    // Userspace RNG driver over the VirtIO EntropySource
453    let rng_driver = virtio_rng.map(|rng| {
454        components::rng::RngRandomComponent::new(board_kernel, capsules_core::rng::DRIVER_NUM, rng)
455            .finalize(components::rng_random_component_static!(VirtIORng))
456    });
457
458    let scheduler = components::sched::cooperative::CooperativeComponent::new(processes)
459        .finalize(components::cooperative_component_static!(NUM_PROCS));
460
461    let scheduler_timer = static_init!(
462        VirtualSchedulerTimer<VirtualMuxAlarm<'static, Pit<'static, RELOAD_1KHZ>>>,
463        VirtualSchedulerTimer::new(systick_virtual_alarm)
464    );
465
466    let platform = QemuI386Q35Platform {
467        pconsole,
468        console,
469        alarm,
470        lldb,
471        scheduler,
472        scheduler_timer,
473        rng: rng_driver,
474        ipc: kernel::ipc::IPC::new(
475            board_kernel,
476            kernel::ipc::DRIVER_NUM,
477            &memory_allocation_cap,
478        ),
479    };
480
481    // Start the process console:
482    let _ = platform.pconsole.start();
483
484    debug!("QEMU i486 \"Q35\" machine, initialization complete.");
485    debug!("Entering main loop.");
486
487    // These symbols are defined in the linker script.
488    extern "C" {
489        /// Beginning of the ROM region containing app images.
490        static _sapps: u8;
491        /// End of the ROM region containing app images.
492        static _eapps: u8;
493        /// Beginning of the RAM region for app memory.
494        static mut _sappmem: u8;
495        /// End of the RAM region for app memory.
496        static _eappmem: u8;
497    }
498
499    // ---------- PROCESS LOADING, SCHEDULER LOOP ----------
500
501    kernel::process::load_processes(
502        board_kernel,
503        chip,
504        core::slice::from_raw_parts(
505            ptr::addr_of!(_sapps),
506            ptr::addr_of!(_eapps) as usize - ptr::addr_of!(_sapps) as usize,
507        ),
508        core::slice::from_raw_parts_mut(
509            ptr::addr_of_mut!(_sappmem),
510            ptr::addr_of!(_eappmem) as usize - ptr::addr_of!(_sappmem) as usize,
511        ),
512        &FAULT_RESPONSE,
513        &process_mgmt_cap,
514    )
515    .unwrap_or_else(|err| {
516        debug!("Error loading processes!");
517        debug!("{:?}", err);
518    });
519
520    board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_cap);
521}