/* * SPDX-License-Identifier: BlueOak-1.0.0 * Copyright (c) Berkus Decker */ //! Vesper single-address-space exokernel. //! //! This crate implements the kernel binary proper. #![no_std] #![no_main] #![feature(asm)] #![feature(global_asm)] #![feature(decl_macro)] #![feature(allocator_api)] #![feature(ptr_internals)] #![feature(format_args_nl)] #![feature(result_contains_err)] #![feature(nonnull_slice_from_raw_parts)] #![feature(custom_test_frameworks)] #![test_runner(crate::tests::test_runner)] #![reexport_test_harness_main = "test_main"] #![deny(missing_docs)] #![deny(warnings)] #![allow(dead_code)] // While working on features, some code may remain unused // repr128 is not yet stable #![feature(repr128)] #![feature(const_generics)] #![feature(const_evaluatable_checked)] // see https://github.com/rust-lang/rust/issues/68436 #![allow(incomplete_features)] #[cfg(not(target_arch = "aarch64"))] use architecture_not_supported_sorry; /// Architecture-specific code. #[macro_use] pub mod arch; pub use arch::*; mod caps; mod devices; mod macros; mod mm; mod objects; mod panic; mod platform; #[cfg(feature = "qemu")] mod qemu; mod sync; #[cfg(test)] mod tests; mod write_to; use { crate::platform::rpi3::{ display::{Color, DrawError}, mailbox::{channel, Mailbox, MailboxOps}, vc::VC, }, cfg_if::cfg_if, }; entry!(kmain); /// The global console. Output of the kernel print! and println! macros goes here. static CONSOLE: sync::NullLock = sync::NullLock::new(devices::Console::new()); /// The global allocator for DMA-able memory. That is, memory which is tagged /// non-cacheable in the page tables. static DMA_ALLOCATOR: sync::NullLock = sync::NullLock::new(mm::BumpAllocator::new( // @todo Init this after we loaded boot memory map memory::map::virt::DMA_HEAP_START as usize, memory::map::virt::DMA_HEAP_END as usize, "Global DMA Allocator", // Try the following arguments instead to see all mailbox operations // fail. It will cause the allocator to use memory that are marked // cacheable and therefore not DMA-safe. The answer from the VideoCore // won't be received by the CPU because it reads an old cached value // that resembles an error case instead. // 0x00600000 as usize, // 0x007FFFFF as usize, // "Global Non-DMA Allocator", )); fn print_mmu_state_and_features() { memory::mmu::print_features(); } fn init_mmu() { unsafe { memory::mmu::init().unwrap(); } println!("[!] MMU initialised"); print_mmu_state_and_features(); } fn init_exception_traps() { extern "C" { static __exception_vectors_start: u64; } unsafe { let exception_vectors_start: u64 = &__exception_vectors_start as *const _ as u64; arch::traps::set_vbar_el1_checked(exception_vectors_start) .expect("Vector table properly aligned!"); } println!("[!] Exception traps set up"); } #[cfg(not(feature = "noserial"))] fn init_uart_serial() { use crate::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart, pl011_uart::PL011Uart}; let gpio = GPIO::default(); let uart = MiniUart::default(); let uart = uart.prepare(&gpio); CONSOLE.lock(|c| { // Move uart into the global CONSOLE. c.replace_with(uart.into()); }); println!("[0] MiniUART is live!"); // Then immediately switch to PL011 (just as an example) let uart = PL011Uart::default(); let mbox = Mailbox::default(); // uart.init() will reconfigure the GPIO, which causes a race against // the MiniUart that is still putting out characters on the physical // line that are already buffered in its TX FIFO. // // To ensure the CPU doesn't rewire the GPIO before the MiniUart has put // its last character, explicitly flush it before rewiring. // // If you switch to an output that happens to not use the same pair of // physical wires (e.g. the Framebuffer), you don't need to do this, // because flush() is anyways called implicitly by replace_with(). This // is just a special case. use crate::devices::console::ConsoleOps; CONSOLE.lock(|c| c.flush()); match uart.prepare(mbox, &gpio) { Ok(uart) => { CONSOLE.lock(|c| { // Move uart into the global CONSOLE. c.replace_with(uart.into()); }); println!("[0] UART0 is live!"); } Err(_) => println!("[0] Error switching to PL011 UART, continue with MiniUART"), } } /// Kernel entry point. /// `arch` crate is responsible for calling it. #[inline] pub fn kmain() -> ! { #[cfg(feature = "jtag")] jtag::wait_debugger(); init_mmu(); init_exception_traps(); #[cfg(not(feature = "noserial"))] init_uart_serial(); #[cfg(test)] test_main(); /* try_init_kernel().expect("Failed to init kernel");*/ // schedule(); // activate_thread(); command_prompt(); reboot() } //------------------------------------------------------------ // Start a command prompt //------------------------------------------------------------ fn command_prompt() { 'cmd_loop: loop { let mut buf = [0u8; 64]; match CONSOLE.lock(|c| c.command_prompt(&mut buf)) { b"mmu" => init_mmu(), b"feats" => print_mmu_state_and_features(), #[cfg(not(feature = "noserial"))] b"uart" => init_uart_serial(), b"disp" => check_display_init(), b"trap" => check_data_abort_trap(), b"map" => arch::memory::print_layout(), b"led on" => set_led(true), b"led off" => set_led(false), b"help" => print_help(), b"end" => break 'cmd_loop, x => println!("[!] Unknown command {:?}, try 'help'", x), } } } fn print_help() { println!("Supported console commands:"); println!(" mmu - initialize MMU"); println!(" feats - print MMU state and supported features"); #[cfg(not(feature = "noserial"))] println!(" uart - try to reinitialize UART serial"); println!(" disp - try to init VC framebuffer and draw some text"); println!(" trap - trigger and recover from a data abort exception"); println!(" map - show kernel memory layout"); println!(" led [on|off] - change RPi LED status"); println!(" end - leave console and reset board"); } fn set_led(enable: bool) { let mut mbox = Mailbox::default(); let index = mbox.request(); let index = mbox.set_led_on(index, enable); let mbox = mbox.end(index); mbox.call(channel::PropertyTagsArmToVc) .map_err(|e| { println!("Mailbox call returned error {}", e); println!("Mailbox contents: {:?}", mbox); }) .ok(); } fn reboot() -> ! { cfg_if! { if #[cfg(feature = "qemu")] { println!("Bye, shutting down QEMU"); qemu::semihosting::exit_success() } else { use crate::platform::rpi3::power::Power; println!("Bye, going to reset now"); Power::new().reset() } } } #[derive(Debug)] enum KernelInitError { CapabilityCreationFailed, } // #[link_section = ".text.boot"] // fn try_init_kernel() -> Result<(), KernelInitError> { // let root_capnode_cap = create_root_capnode(); // if root_capnode_cap.is_err() { // return Err(KernelInitError::CapabilityCreationFailed); // } // Ok(()) // } const CONFIG_ROOT_CAPNODE_SIZE_BITS: usize = 12; const WORD_BITS: usize = 64; enum BootInfoCaps { InitThreadCapNode = 1, } impl From for usize { fn from(val: BootInfoCaps) -> usize { val as usize } } /* #[link_section = ".text.boot"] fn create_root_capnode() -> Result { // write the number of root CNode slots to global state BOOT_INFO.lock(|bi| bi.max_slot_pos = 1 << CONFIG_ROOT_CAPNODE_SIZE_BITS); // 12 bits => 4096 slots // seL4_SlotBits = 32 bytes per entry, 4096 entries => // create an empty root CapNode // this goes into the kernel startup/heap memory (one of the few items that kernel DOES allocate). let region_size = core::mem::size_of::() * BOOT_INFO.lock(|bi| bi.max_slot_pos); // 12 + 5 => 131072 (128Kb) let pptr = BOOT_INFO.lock(|bi| bi.alloc_region(region_size)); // GlobalAllocator::alloc_zeroed instead? let pptr = match pptr { Ok(pptr) => pptr, Err(_) => { println!("Kernel init failed: could not create root capnode"); return Err(caps::CapError::CannotCreate); } }; // @todo lifetime of slice -- slice here only to zero out memory let slice = unsafe { core::slice::from_raw_parts_mut(pptr.as_u64() as *mut u8, region_size as usize) }; for byte in slice.iter_mut() { *byte = 0; } // @todo wtf // transmute into a type? (you can use ptr.write() to just write a type into memory location) let cap = CapNodeCapability::new_root(pptr.as_u64()); use core::convert::TryFrom; let mut cap_node = CapNodeCapability::try_from(cap.as_u128()).unwrap(); // this cnode contains a cap to itself... /* write the root CNode cap into the root CNode */ cap_node.write_slot(usize::from(BootInfoCaps::InitThreadCapNode), &cap); Ok(cap_node) // reference to pptr is here }*/ fn check_display_init() { display_graphics() .map_err(|e| { println!("Error in display: {}", e); }) .ok(); } fn display_graphics() -> Result<(), DrawError> { if let Ok(mut display) = VC::init_fb(800, 600, 32) { println!("Display created"); display.clear(Color::black()); println!("Display cleared"); display.rect(10, 10, 250, 250, Color::rgb(32, 96, 64)); display.draw_text(50, 50, "Hello there!", Color::rgb(128, 192, 255))?; let mut buf = [0u8; 64]; let s = write_to::show(&mut buf, format_args!("Display width {}", display.width)); if s.is_err() { display.draw_text(50, 150, "Error displaying", Color::red())? } else { display.draw_text(50, 150, s.unwrap(), Color::white())? } display.draw_text(150, 50, "RED", Color::red())?; display.draw_text(160, 60, "GREEN", Color::green())?; display.draw_text(170, 70, "BLUE", Color::blue())?; } Ok(()) } fn check_data_abort_trap() { // Cause an exception by accessing a virtual address for which no // address translations have been set up. // // This line of code accesses the address 3 GiB, but page tables are // only set up for the range [0..1) GiB. let big_addr: u64 = 3 * 1024 * 1024 * 1024; unsafe { core::ptr::read_volatile(big_addr as *mut u64) }; println!("[i] Whoa! We recovered from an exception."); } #[cfg(test)] mod main_tests { use super::*; #[test_case] fn test_data_abort_trap() { check_data_abort_trap() } }