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