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