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