### SD Card Setup for Raspberry Pi Boot Source: https://context7.com/rust-embedded/rust-raspberrypi-os-tutorials/llms.txt Provides a step-by-step guide for preparing an SD card to boot a custom kernel on Raspberry Pi 3 or Pi 4 hardware. This includes partitioning, copying firmware files, building the kernel, and connecting a serial console. ```bash # 1. Create FAT32 partition named 'boot' on SD card # 2. Create config.txt on SD card with: arm_64bit=1 init_uart_clock=48000000 # 3. For RPi3, copy from Raspberry Pi firmware repo: # - bootcode.bin # - fixup.dat # - start.elf # 4. For RPi4, copy from Raspberry Pi firmware repo: # - fixup4.dat # - start4.elf # - bcm2711-rpi-4-b.dtb # 5. Build and copy kernel make # or: BSP=rpi4 make cp kernel8.img /path/to/sdcard/ # 6. Connect USB-serial cable to GPIO pins 14/15 and GND # 7. Start miniterm and power on the Pi make miniterm ``` -------------------------------- ### Install Rust Toolchain and Dependencies (Linux) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/README.md Installs the Rust programming language toolchain using `rustup` and then installs `cargo-binutils` and `rustfilt` for embedded development. This script ensures the environment is set up for building Rust projects, including those for embedded systems. ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env cargo install cargo-binutils rustfilt ``` -------------------------------- ### Install Cargo Binutils and Rustfilt Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/README.md Installs necessary Rust command-line tools for embedded development, specifically `cargo-binutils` for working with binary files and `rustfilt` for demangling symbol names. These are essential for debugging and inspecting compiled Rust code in embedded environments. ```bash cargo install cargo-binutils rustfilt ``` -------------------------------- ### Get Start Address of MMIODescriptor in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md The `start_addr` method returns the starting physical address of the `MMIODescriptor`. ```rust /// Return the start address. pub const fn start_addr(&self) -> Address { self.start_addr } ``` -------------------------------- ### Initiate EL2 to EL1 Transition in Rust (_start_rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/09_privilege_level/README.md This Rust function, `_start_rust`, is the entry point for the Rust code. It calls `prepare_el2_to_el1_transition` and then uses the `eret` instruction to return to EL1, effectively starting the kernel execution in the Kernel privilege level. ```rust #[no_mangle] pub unsafe extern "C" fn _start_rust(phys_boot_core_stack_end_exclusive_addr: u64) -> ! { prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr); // Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1. asm::eret() } ``` -------------------------------- ### Get Virtual Heap Start Address (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/19_kernel_heap/README.md Retrieves the virtual start address of the heap segment. This function relies on a linker-provided symbol `__heap_start` and is marked `inline(always)` for performance. It returns a `PageAddress` type. ```rust /// Start page address of the heap segment. #[inline(always)] fn virt_heap_start() -> PageAddress { PageAddress::from(unsafe { __heap_start.get() as usize }) } ``` -------------------------------- ### Get Kernel Symbols Section Virtual Start Address (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/17_kernel_symbols/README.md A function that retrieves the virtual start address of the kernel symbols section. It dereferences the linker-provided `__kernel_symbols_start` symbol, converting it to a `Virtual` address type. ```rust fn kernel_symbol_section_virt_start_addr() -> Address { Address::new(unsafe { __kernel_symbols_start.get() as usize }) } ``` -------------------------------- ### Initialize Kernel and Print 'Hello' in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/03_hacky_hello_world/README.md Sets up the kernel initialization process, enabling necessary Rust features and calling `println!` to display 'Hello from Rust!' before intentionally panicking. This demonstrates basic output and controlled program termination. ```rust #![feature(asm_const)] #![feature(format_args_nl)] #![feature(panic_info_message)] #![no_main] #![no_std] mod bsp; mod console; mod cpu; mod panic_wait; mod print; /// Early init code. /// /// This function is called by the architecture specific code after memory has been initialized. /// /// - Only a single core must be active and running this function. unsafe fn kernel_init() -> ! { println!("Hello from Rust!"); panic!("Stopping here.") } ``` -------------------------------- ### Create and Prepare Test Kernel Runner Script Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/12_integrated_testing/README.md This Makefile snippet defines a bash script that prepares the test kernel by converting the ELF binary to a raw image using `objcopy` and then executes it within QEMU via a Docker container. It ensures the script runs from the project root and correctly paths the kernel image. ```makefile define KERNEL_TEST_RUNNER #!/usr/bin/env bash # The cargo test runner seems to change into the crate under test's directory. Therefore, ensure # this script executes from the root. cd $(shell pwd) TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY endef export KERNEL_TEST_RUNNER define test_prepare @mkdir -p target @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh @chmod +x target/kernel_test_runner.sh endef ##------------------------------------------------------------------------------ ## Run unit test(s) ##------------------------------------------------------------------------------ test_unit: $(call color_header, "Compiling unit test(s) - $(BSP)") $(call test_prepare) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib ``` -------------------------------- ### Get Virtual MMIO Remap Reservation Info (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md Provides functions to get the virtual start address and size of the MMIO remap reservation area. This is important for managing memory-mapped I/O regions within the virtual memory space. Relies on linker script values and `PageAddress`. ```Rust fn virt_mmio_remap_start() -> PageAddress { PageAddress::from(unsafe { __mmio_remap_start.get() as usize }) } fn mmio_remap_size() -> usize { unsafe { (__mmio_remap_end_exclusive.get() as usize) - (__mmio_remap_start.get() as usize) } } ``` -------------------------------- ### Get Virtual Data Segment Information (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md Offers functions to obtain the virtual start address and size of the data segment. Like the code segment, these are linker-script defined and critical for initializing and accessing program data. Uses `PageAddress` for virtual address representation. ```Rust fn virt_data_start() -> PageAddress { PageAddress::from(unsafe { __data_start.get() as usize }) } fn data_size() -> usize { unsafe { (__data_end_exclusive.get() as usize) - (__data_start.get() as usize) } } ``` -------------------------------- ### Build and Run Kernel with Make Source: https://context7.com/rust-embedded/rust-raspberrypi-os-tutorials/llms.txt Standard make commands for building the kernel for Raspberry Pi 3 or 4, running it in the QEMU emulator, generating documentation, and cleaning build artifacts. ```bash # Build kernel for RPi3 (default) make # Build kernel for RPi4 BSP=rpi4 make # Run in QEMU emulator make qemu # Generate documentation make doc # Clean build artifacts make clean ``` -------------------------------- ### Get Boot Core Stack Information (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md Functions to retrieve the virtual start address and size of the boot core's stack. Essential for managing the initial execution stack of the primary processor core. Uses `PageAddress` for virtual address handling and linker-defined values. ```Rust fn virt_boot_core_stack_start() -> PageAddress { PageAddress::from(unsafe { __boot_core_stack_start.get() as usize }) } fn boot_core_stack_size() -> usize { unsafe { (__boot_core_stack_end_exclusive.get() as usize) - (__boot_core_stack_start.get() as usize) } } ``` -------------------------------- ### Prepare for Rust Execution (AArch64 Assembly) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/07_timestamps/README.md Sets up the stack pointer and reads the CPU's timer counter frequency. It stores the frequency in `ARCH_TIMER_COUNTER_FREQUENCY` and aborts if it's zero. Finally, it jumps to the Rust entry point `_start_rust`. ```assembly // Prepare the jump to Rust code. .L_prepare_rust: // Set the stack pointer. ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 // Read the CPU's timer counter frequency and store it in ARCH_TIMER_COUNTER_FREQUENCY. // Abort if the frequency read back as 0. ADR_REL x1, ARCH_TIMER_COUNTER_FREQUENCY // provided by aarch64/time.rs mrs x2, CNTFRQ_EL0 cmp x2, xzr b.eq .L_parking_loop str w2, [x1] // Jump to Rust code. b _start_rust ``` -------------------------------- ### Get Virtual Code Segment Information (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md Provides functions to retrieve the virtual start address and size of the code segment. These values are determined by the linker script and are essential for memory management and execution flow control. The `PageAddress` type ensures correct virtual address handling. ```Rust fn virt_code_start() -> PageAddress { PageAddress::from(unsafe { __code_start.get() as usize }) } fn code_size() -> usize { unsafe { (__code_end_exclusive.get() as usize) - (__code_start.get() as usize) } } ``` -------------------------------- ### Initialize Virtual Memory with kernel_map_binary and enable_mmu_and_caching Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md Sets up virtual memory by mapping the kernel binary and enabling the MMU with caching. It handles potential errors during these critical initialization steps. ```rust let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() { Err(string) => panic!("Error mapping kernel binary: {}", string), Ok(addr) => addr, }; if let Err(e) = memory::mmu::enable_mmu_and_caching(phys_kernel_tables_base_addr) { panic!("Enabling MMU failed: {}", e); } ``` -------------------------------- ### Execute ERET Instruction in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/09_privilege_level/README.md This snippet shows the `_start_rust` function which prepares the EL2 to EL1 transition by calling `prepare_el2_to_el1_transition` and then executes the `eret` instruction to perform the privilege level change. ```rust #[no_mangle] pub unsafe extern "C" fn _start_rust(phys_boot_core_stack_end_exclusive_addr: u64) -> ! { prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr); // Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1. asm::eret() } ``` -------------------------------- ### Install Ruby Gems for macOS Development Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/README.md Installs Ruby gems required for development on macOS, specifically for managing dependencies using Bundler. It configures Bundler to install gems locally within the project and excludes development-specific gems. ```ruby bundle config set --local path '.vendor/bundle' bundle config set --local without 'development' bundle install ``` -------------------------------- ### Compare Tutorial Diffs with Meld Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/12_integrated_testing/README.md This command-line snippet demonstrates how to use the `meld` diff tool to compare the previous tutorial's directory with the `kernel` folder of the current tutorial. This is useful for understanding changes in folder structure when automatic diffs are unreadable. ```console meld 11_exceptions_part1_groundwork 12_integrated_testing/kernel ``` -------------------------------- ### Rust: Kernel Initialization and Driver Management Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/05_drivers_gpio_uart/README.md This Rust code snippet shows the kernel initialization process, including the initialization of the BSP driver subsystem and all registered device drivers. It then transitions to the safe `kernel_main` function, which prints system information, enumerates loaded drivers, and enters an input echoing loop. ```rust #![allow(clippy::upper_case_acronyms)] #![feature(asm_const)] #![feature(format_args_nl)] #![feature(panic_info_message)] mod bsp; mod console; mod cpu; mod driver; mod panic_wait; mod print; mod synchronization; /// # Safety /// /// - Only a single core must be active and running this function. /// - The init calls in this function must appear in the correct order. unsafe fn kernel_init() -> ! { // Initialize the BSP driver subsystem. if let Err(x) = bsp::driver::init() { panic!("Error initializing BSP driver subsystem: {}", x); } // Initialize all device drivers. driver::driver_manager().init_drivers(); // println! is usable from here on. // Transition from unsafe to safe. kernel_main() } /// The main function running after the early init. fn kernel_main() -> ! { use console::console; println!( "[0] {} version {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION") ); println!("[1] Booting on: {}", bsp::board_name()); println!("[2] Drivers loaded:"); driver::driver_manager().enumerate(); println!("[3] Chars written: {}", console().chars_written()); println!("[4] Echoing input now"); // Discard any spurious received characters before going into echo mode. console().clear_rx(); loop { let c = console().read_char(); console().write_char(c); } } ``` -------------------------------- ### Kernel Initialization with MMU Setup in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md This Rust code snippet demonstrates the kernel initialization process, including setting up exception handling and configuring the Memory Management Unit (MMU). It maps the kernel binary, enables MMU and caching, and initializes BSP drivers. Error handling is included for MMU operations. ```rust use memory::mmu::interface::MMU; #[no_mangle] unsafe fn kernel_init() -> ! { exception::handling_init(); let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() { Err(string) => panic!("Error mapping kernel binary: {}", string), Ok(addr) => addr, }; if let Err(e) = memory::mmu::enable_mmu_and_caching(phys_kernel_tables_base_addr) { panic!("Enabling MMU failed: {}", e); } memory::mmu::post_enable_init(); // Initialize the BSP driver subsystem. if let Err(x) = bsp::driver::init() { panic!("Error initializing BSP driver subsystem: {}", x); } info!("{}", libkernel::version()); info!("Booting on: {}", bsp::board_name()); info!("MMU online:"); memory::mmu::kernel_print_mappings(); let (_, privilege_level) = exception::current_privilege_level(); info!("Current privilege level: {}", privilege_level); // ... rest of the kernel initialization ... loop {} } ``` -------------------------------- ### x86_64 PIC Disassembly Example Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/15_virtual_mem_part3_precomputed_tables/README.md This snippet shows the disassembled output of PIC code compiled for x86_64, demonstrating indirect access through the Global Offset Table (GOT). This is presented as an example of how PIC code functions, similar to AArch64 code. ```text Disassembly of section .text: ffff000000000070 get_address_of_global: ffff000000000070: 55 push rbp ffff000000000071: 48 89 e5 mov rbp, rsp ffff000000000074: 48 8b 05 2d 00 00 00 mov rax, qword ptr [rip + 0x2d] ffff00000000007b: 5d pop rbp ffff00000000007c: c3 ret ffff00000000007d: 0f 1f 00 nop dword ptr [rax] Disassembly of section .got: ffff0000000000a8 .got: ffff0000000000a8: 08 00 01 00 ffff0000000000ac: 00 00 ff ff Disassembly of section .data: ffff000000010008 global_data_word: ffff000000010008: 44 33 22 11 ffff00000001000c: 00 00 00 00 ``` -------------------------------- ### Kernel Panic Backtrace Example Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/18_backtrace/README.md This console output shows an example of a kernel panic with a detailed backtrace. It includes the panic location (file, line, column) and a list of addresses and the functions they correspond to, which is crucial for debugging. ```console [ 0.002782] Writing to bottom of address space to address 1 GiB... [ 0.004623] Kernel panic! Panic location: File 'kernel/src/_arch/aarch64/exception.rs', line 59, column 5 [...] Backtrace: ---------------------------------------------------------------------------------------------- Address Function containing address ---------------------------------------------------------------------------------------------- 1. ffffffffc0005560 | libkernel::panic_wait::_panic_print 2. ffffffffc00054a0 | rust_begin_unwind 3. ffffffffc0002950 | core::panicking::panic_fmt 4. ffffffffc0004898 | current_elx_synchronous 5. ffffffffc0000a74 | __vector_current_elx_synchronous 6. ffffffffc000111c | kernel_init ---------------------------------------------------------------------------------------------- ``` -------------------------------- ### Execute Rust Tests for Raspberry Pi OS Source: https://context7.com/rust-embedded/rust-raspberrypi-os-tutorials/llms.txt Provides commands to run various types of tests for the Raspberry Pi OS project using `make`. This includes commands to run all tests (boot, unit, and integration), only unit tests, and only integration tests. These tests are typically executed using QEMU emulation. ```bash # Run all tests (boot + unit + integration) make test # Run only unit tests make test_unit # Run only integration tests make test_integration ``` -------------------------------- ### Initialize BSP Driver Subsystem and Device Drivers in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/05_drivers_gpio_uart/README.md This Rust code snippet demonstrates the initialization sequence for the BSP driver subsystem and the global driver manager within a kernel. It first initializes the BSP drivers and then proceeds to initialize all registered device drivers. Error handling is included for the BSP driver initialization. ```rust unsafe fn kernel_init() -> ! { // Initialize the BSP driver subsystem. if let Err(x) = bsp::driver::init() { panic!("Error initializing BSP driver subsystem: {}", x); } // Initialize all device drivers. driver::driver_manager().init_drivers(); // println! is usable from here on. } ``` -------------------------------- ### Define Kernel Virtual Start Address in Linker Script Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/16_virtual_mem_part4_higher_half_kernel/README.md Defines the `__kernel_virt_start_addr` symbol in the `kernel.ld` linker script, calculating the start of the kernel's virtual address space. It then sets the location counter to this address, ensuring alignment. ```ld.s SECTIONS { . = __kernel_virt_start_addr; ASSERT((. & PAGE_MASK) == 0, "Start of address space is not page aligned") /*********************************************************************************************** * Code + RO Data + Global Offset Table ***********************************************************************************************/ ``` -------------------------------- ### Ruby Chainboot Test Setup Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/07_timestamps/README.md This Ruby code extends the BootTest class to facilitate testing with a MiniPush instance connected to a QEMU instance. It sets up pseudo-terminals (PTY) for inter-process communication between Ruby scripts and QEMU, allowing for controlled input and output redirection. ```ruby class ChainbootTest < BootTest MINIPUSH = '../common/serial/minipush.rb' def initialize(qemu_cmd, payload_path) super(qemu_cmd, EXPECTED_PRINT) @test_name = 'Boot test using Minipush' @payload_path = payload_path end private # override def setup pty_main, pty_secondary = PTY.open mp_out, _mp_in = PTY.spawn("ruby #{MINIPUSH} #{pty_secondary.path} #{@payload_path}") # The subtests (from this class and the parents) listen on @qemu_out_wrapped. Hence, point # it to MiniPush's output. @qemu_out_wrapped = PTYLoggerWrapper.new(mp_out, "\r\n") # Important: Run this subtest before the one in the parent class. @console_subtests.prepend(PowerTargetRequestTest.new(@qemu_cmd, pty_main)) end # override def finish super() @test_output.map! { |x| x.gsub(/.*\r/, ' ') } end end ##-------------------------------------------------------------------------------------------------- ## Execution starts here ##-------------------------------------------------------------------------------------------------- payload_path = ARGV.pop qemu_cmd = ARGV.join(' ') ChainbootTest.new(qemu_cmd, payload_path).run ``` -------------------------------- ### Initialize EL1 Transition and Enable MMU in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/15_virtual_mem_part3_precomputed_tables/README.md This Rust function `_start_rust` is the entry point after the assembly boot code. It prepares the transition from EL2 to EL1, enables the MMU for EL1 using the provided physical table base address, and then uses an `eret` instruction to return to EL1, starting the execution of `kernel_init()`. ```rust @no_mangle pub unsafe extern "C" fn _start_rust( phys_kernel_tables_base_addr: u64, phys_boot_core_stack_end_exclusive_addr: u64, ) -> ! { prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr); // Turn on the MMU for EL1. let addr = Address::new(phys_kernel_tables_base_addr as usize); memory::mmu::enable_mmu_and_caching(addr).unwrap(); // Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1. asm::eret() } ``` -------------------------------- ### Define Raspberry Pi Peripheral Memory Addresses (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/13_exceptions_part2_peripheral_IRQs/README.md Defines constants for the physical memory start addresses of various peripherals on the Raspberry Pi. These addresses are crucial for memory-mapped I/O operations in embedded systems. The constants cover general peripheral start, interrupt controllers (IC), GPIO, and UART. ```rust pub mod peripheral { pub const START: usize = 0x3F00_0000; pub const PERIPHERAL_IC_START: usize = START + 0x0000_B200; pub const GPIO_START: usize = START + GPIO_OFFSET; pub const PL011_UART_START: usize = START + UART_OFFSET; pub const END_INCLUSIVE: usize = 0x4000_FFFF; } /// Physical devices. pub mod physical_device { pub const START: usize = 0xFE00_0000; pub const GPIO_START: usize = START + GPIO_OFFSET; pub const PL011_UART_START: usize = START + UART_OFFSET; pub const GICD_START: usize = 0xFF84_1000; pub const GICC_START: usize = 0xFF84_2000; pub const END_INCLUSIVE: usize = 0xFF84_FFFF; } ``` -------------------------------- ### Streamline MMU Initialization for IRQ Sanity Tests in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/15_virtual_mem_part3_precomputed_tables/README.md This Rust code snippet streamlines the MMU initialization for interrupt request (IRQ) sanity tests. It replaces the previous manual MMU setup with a single `memory::init()` call, followed by console bring-up and exception handling initialization. This simplification makes the test setup more efficient. ```rust memory::init(); bsp::driver::qemu_bring_up_console(); exception::handling_init(); exception::asynchronous::local_irq_unmask(); test_main(); ``` -------------------------------- ### Prepare EL2 to EL1 Transition in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/09_privilege_level/README.md This Rust code prepares the system for a transition from EL2 to EL1. It configures timer counter registers, sets the EL1 execution state to AArch64, and prepares a simulated exception return to the `kernel_init` function in EL1. The function is unsafe due to direct hardware manipulation and assumptions about the BSS section. ```rust use aarch64_cpu::{asm, registers::*}; use core::arch::global_asm; use tock_registers::interfaces::Writeable; // Assembly counterpart to this file. global_asm!( include_str!("boot.s"), CONST_CURRENTEL_EL2 = const 0x8, CONST_CORE_ID_MASK = const 0b11 ); //-------------------------------------------------------------------------------------------------- // Private Code //-------------------------------------------------------------------------------------------------- /// Prepares the transition from EL2 to EL1. /// /// # Safety /// /// - The `bss` section is not initialized yet. The code must not use or reference it in any way. /// - The HW state of EL1 must be prepared in a sound way. #[inline(always)] unsafe fn prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr: u64) { // Enable timer counter registers for EL1. CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET); // No offset for reading the counters. CNTVOFF_EL2.set(0); // Set EL1 execution state to AArch64. HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64); // Set up a simulated exception return. // // First, fake a saved program status where all interrupts were masked and SP_EL1 was used as a // stack pointer. SPSR_EL2.write( SPSR_EL2::D::Masked + SPSR_EL2::A::Masked + SPSR_EL2::I::Masked + SPSR_EL2::F::Masked + SPSR_EL2::M::EL1h, ); // Second, let the link register point to kernel_init(). ELR_EL2.set(crate::kernel_init as *const () as u64); // Set up SP_EL1 (stack pointer), which will be used by EL1 once we "return" to it. Since there // are no plans to ever return to EL2, just re-use the same stack. SP_EL1.set(phys_boot_core_stack_end_exclusive_addr); } //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- /// The Rust entry of the `kernel` binary. /// /// The function is called from the assembly `_start` function. /// /// # Safety /// /// - Exception return from EL2 must must continue execution in EL1 with `kernel_init()`. #[no_mangle] pub unsafe extern "C" fn _start_rust(phys_boot_core_stack_end_exclusive_addr: u64) -> ! { prepare_el2_to_el1_transition(phys_boot_core_stack_end_exclusive_addr); // Use `eret` to "return" to EL1. This results in execution of kernel_init() in EL1. asm::eret() } ``` -------------------------------- ### Rust Entry Point and Assembly Integration (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/02_runtime_init/README.md This Rust code snippet defines the `_start_rust` function, which serves as the entry point for the kernel after the initial assembly setup. It also demonstrates how to include assembly code using `global_asm!` and pass constants to it, like `CONST_CORE_ID_MASK`. ```rust use core::arch::global_asm; // Assembly counterpart to this file. global_asm!( include_str!("boot.s"), CONST_CORE_ID_MASK = const 0b11 ); //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- /// The Rust entry of the `kernel` binary. /// /// The function is called from the assembly `_start` function. #[no_mangle] pub unsafe fn _start_rust() -> ! { crate::kernel_init() } ``` -------------------------------- ### Refactor MMU Initialization for Exception Restore Tests in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/15_virtual_mem_part3_precomputed_tables/README.md This Rust code snippet refactors the MMU initialization for exception restore sanity tests. It replaces the verbose manual MMU setup with a concise `memory::init()` call, followed by console bring-up and exception handling initialization. This change simplifies the test setup and improves readability. ```rust unsafe { exception::handling_init(); memory::init(); bsp::driver::qemu_bring_up_console(); // This line will be printed as the test header. println!("Testing exception restore"); info!("Making a dummy system call"); // Calling this inside a function indirectly tests if the link register is restored properly. } ``` -------------------------------- ### Assembly Boot Code with Runtime Initialization (Assembly) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/02_runtime_init/README.md This assembly code sets up the initial environment for the Rust kernel. It includes a macro `ADR_REL` for PC-relative addressing, checks for the boot core, initializes the BSS section by zeroing it out, sets up the stack pointer, and finally jumps to the Rust entry point `_start_rust`. ```assembly // Load the address of a symbol into a register, PC-relative. // // The symbol must lie within +/- 4 GiB of the Program Counter. // // # Resources // // - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html .macro ADR_REL register, symbol adrp \register, \symbol add \register, \register, #:lo12:\symbol .endm //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- .section .text._start // fn _start() //------------------------------------------------------------------------------ _start: // Only proceed on the boot core. Park it otherwise. mrs x0, MPIDR_EL1 and x0, x0, {CONST_CORE_ID_MASK} ldr x1, BOOT_CORE_ID // provided by bsp/__board_name__/cpu.rs cmp x0, x1 b.ne .L_parking_loop // If execution reaches here, it is the boot core. // Initialize DRAM. ADR_REL x0, __bss_start ADR_REL x1, __bss_end_exclusive .L_bss_init_loop: cmp x0, x1 b.eq .L_prepare_rust stp xzr, xzr, [x0], #16 b .L_bss_init_loop // Prepare the jump to Rust code. .L_prepare_rust: // Set the stack pointer. ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0 // Jump to Rust code. b _start_rust // Infinitely wait for events (aka "park the core"). .L_parking_loop: wfe ``` -------------------------------- ### Verify KERNEL_TABLES Placement in .bss Section in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md This Rust test function checks if the `KERNEL_TABLES` symbol is located within the .bss (Block Started by Symbol) section of the kernel. It defines the start and end addresses of the .bss section using external symbols and then asserts that the address of `KERNEL_TABLES` falls within this range. This is important for ensuring uninitialized data is correctly placed. ```rust extern "Rust" { static __bss_start: UnsafeCell; static __bss_end_exclusive: UnsafeCell; } let bss_range = unsafe { Range { start: __bss_start.get(), end: __bss_end_exclusive.get(), } }; let kernel_tables_addr = &KERNEL_TABLES as *const _ as usize as *mut u64; assert!(bss_range.contains(&kernel_tables_addr)); } ``` -------------------------------- ### Validate Kernel Virtual Memory Layout Alignment in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md This Rust test function verifies that the kernel's virtual memory layout sections (boot core stack, code, and data) are aligned to page boundaries. It iterates through the defined regions, retrieves their start and end addresses, and asserts that both are page-aligned. It also ensures that the end address is not less than the start address. ```rust for i in [ virt_boot_core_stack_region, virt_code_region, virt_data_region, ] .iter() { let start = i().start_page_addr().into_inner(); let end_exclusive = i().end_exclusive_page_addr().into_inner(); assert!(start.is_page_aligned()); assert!(end_exclusive.is_page_aligned()); assert!(end_exclusive >= start); } } ``` -------------------------------- ### Set Up Exception Handling in Rust Source: https://context7.com/rust-embedded/rust-raspberrypi-os-tutorials/llms.txt Initializes exception handling mechanisms, including synchronous exceptions, interrupts (IRQs), and page faults. It demonstrates how to query the current privilege level, print exception handling states, unmask local interrupts, and register custom IRQ handlers using `libkernel::exception`. The code also shows how to enable and print registered handlers. ```rust use libkernel::exception; // Initialize exception handling (called early in kernel_init) exception::handling_init(); // Query current privilege level let (level, privilege_level) = exception::current_privilege_level(); println!("Current privilege level: {}", privilege_level); // Output: "Current privilege level: EL1" // Print exception handling state exception::asynchronous::print_state(); // Output: // Debug: Masked // SError: Masked // IRQ: Masked // FIQ: Masked // Unmask interrupts exception::asynchronous::local_irq_unmask(); // IRQ handler registration use exception::asynchronous::{irq_manager, IRQHandlerDescriptor}; let descriptor = IRQHandlerDescriptor::new( irq_number, "Timer IRQ", handler_instance ); irq_manager().register_handler(descriptor)?; irq_manager().enable(&irq_number); // Print registered handlers irq_manager().print_handler(); ``` -------------------------------- ### Update Memory Layout Documentation (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/16_virtual_mem_part4_higher_half_kernel/README.md This Rust code snippet updates the documentation comments describing the kernel's virtual memory layout. It removes the explicit mention of the boot-core stack starting at address `0x0` and instead indicates that the code starts at `__kernel_virt_start_addr`, reflecting the move to a higher-half kernel. ```rust //+---------------------------------------+ //! | | code_start @ __kernel_virt_start_addr //! | //! | .text | //! | .rodata | //! | .got | //! | | //! +---------------------------------------+ //! | | mmio_remap_end_exclusive //! | Unmapped guard page | //! | | //! +---------------------------------------+ //! | | boot_core_stack_start //! | //! | Boot-core Stack | //! | //! | //! +---------------------------------------+ //! | | boot_core_stack_end_exclusive //! | ``` -------------------------------- ### Generate Documentation for Raspberry Pi 4 using Makefile Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/05_drivers_gpio_uart/README.md This console command illustrates how to generate documentation for the project when targeting the Raspberry Pi 4. By using `BSP=rpi4 make doc`, the documentation generation process will incorporate configurations and potentially driver specifics relevant to the RPi 4. ```bash $ BSP=rpi4 make doc ``` -------------------------------- ### Update Linker Script for Raspberry Pi Kernel Load Address Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/06_uart_chainloader/README.md This linker script modification removes the explicit DRAM start address and sets the kernel binary load address to 0x2000000 (32 MiB). It also defines symbols for the start and end of non-zero binary sections, ensuring proper alignment for relocation. ```linker script /* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ __rpi_phys_binary_load_addr = 0x80000; SECTIONS { /* Set the link address to 32 MiB */ . = 0x2000000; /*********************************************************************************************** * Boot Core Stack ***********************************************************************************************/ . = ALIGN(16); __boot_stack_start = .; /*********************************************************************************************** * Code + RO Data + Global Offset Table ***********************************************************************************************/ __binary_nonzero_start = .; .text : { KEEP(*(.text._start)) *(.text*) *(.rodata*) } :segment_text /*********************************************************************************************** * Initialized Data ***********************************************************************************************/ .data : { *(.data*) } :segment_data /* Fill up to 8 byte, b/c relocating the binary is done in u64 chunks */ . = ALIGN(8); __binary_nonzero_end_exclusive = .; /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ .bss (NOLOAD) : ALIGN(16) { *(.bss*) } :segment_bss } ``` -------------------------------- ### Unified Memory Initialization (Rust) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/15_virtual_mem_part3_precomputed_tables/README.md This Rust code snippet demonstrates the consolidation of memory initialization procedures within the `kernel_init` function. It replaces detailed steps for mapping the kernel binary and enabling the MMU with a single call to `memory::init()`, streamlining the setup process for virtual memory and caching. ```rust unsafe fn kernel_init() -> ! { exception::handling_init(); memory::init(); // Initialize the BSP driver subsystem. if let Err(x) = bsp::driver::init() { panic!("BSP driver init failed: {}", x); } // Initialize all device drivers. driver::driver_manager().init_drivers_and_irqs(); bsp::memory::mmu::kernel_add_mapping_records_for_precomputed(); // Unmask interrupts on the boot CPU core. exception::asynchronous::local_irq_unmask(); } ``` -------------------------------- ### Get Exclusive End Address of MMIODescriptor in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md The `end_addr_exclusive` method returns the exclusive end physical address of the `MMIODescriptor`. ```rust /// Return the exclusive end address. pub fn end_addr_exclusive(&self) -> Address { self.end_addr_exclusive } ``` -------------------------------- ### Define Kernel Virtual Address Space (Linker Script) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/16_virtual_mem_part4_higher_half_kernel/README.md This linker script snippet defines the start address for the kernel's virtual address space. It calculates the starting address based on `u64::MAX` and a predefined kernel virtual address space size, ensuring that the kernel occupies the higher half of the virtual address space. ```linker-script __kernel_virt_start_addr = ((0xffffffffffffffff - __kernel_virt_addr_space_size) + 1); ``` -------------------------------- ### Initialize Exception Handling and MMU in Rust Kernel Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/11_exceptions_part1_groundwork/README.md Demonstrates the initialization sequence for the Rust kernel. It first calls `exception::handling_init()` to set up exception handlers, then enables the MMU and caching, panicking if MMU enablement fails. ```rust unsafe fn kernel_init() -> ! { use memory::mmu::interface::MMU; exception::handling_init(); if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { panic!("MMU: {}", string); } // ... rest of the kernel initialization } ``` -------------------------------- ### Get MMU Reference Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/10_virtual_mem_part1_identity_mapping/README.md Provides a public function to return a static reference to the MMU instance. This allows other parts of the system to access the MMU driver. ```rust /// Return a reference to the MMU instance. pub fn mmu() -> &'static impl memory::mmu::interface::MMU { &MMU } ``` -------------------------------- ### Kernel ELF and BSP Initialization (Ruby) Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/15_virtual_mem_part3_precomputed_tables/README.md Initializes the KernelELF instance for handling ELF files, determines the Board Support Package (BSP) type, and sets up the translation tables based on the kernel's architecture. It uses a case statement for conditional logic. ```ruby KERNEL_ELF = KernelELF.new(kernel_elf_path) BSP = case BSP_TYPE when :rpi3, :rpi4 RaspberryPi.new else raise end TRANSLATION_TABLES = case KERNEL_ELF.machine when :AArch64 Arch::ARMv8::TranslationTable.new else raise end kernel_map_binary ``` -------------------------------- ### Makefile for Kernel Symbol Generation and Management Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/17_kernel_symbols/README.md This Makefile defines the build process for kernel symbols. It includes checks for required environment variables, defines paths for source and output files, and sets up build commands using Docker. The primary target `symbols` copies the input ELF, generates symbols using a Ruby script, demangles them with `rustfilt`, compiles a Rust crate with these symbols, strips the resulting ELF, and patches the data. ```makefile ## SPDX-License-Identifier: MIT OR Apache-2.0 ## ## Copyright (c) 2018-2023 Andre Richter include ../common/format.mk include ../common/docker.mk ##-------------------------------------------------------------------------------------------------- ## Check for input variables that need be exported by the calling Makefile ##-------------------------------------------------------------------------------------------------- ifndef KERNEL_SYMBOLS_TOOL_PATH $(error KERNEL_SYMBOLS_TOOL_PATH is not set) endif ifndef TARGET $(error TARGET is not set) endif ifndef KERNEL_SYMBOLS_INPUT_ELF $(error KERNEL_SYMBOLS_INPUT_ELF is not set) endif ifndef KERNEL_SYMBOLS_OUTPUT_ELF $(error KERNEL_SYMBOLS_OUTPUT_ELF is not set) endif ##-------------------------------------------------------------------------------------------------- ## Targets and Prerequisites ##-------------------------------------------------------------------------------------------------- KERNEL_SYMBOLS_MANIFEST = kernel_symbols/Cargo.toml KERNEL_SYMBOLS_LINKER_SCRIPT = kernel_symbols/kernel_symbols.ld KERNEL_SYMBOLS_RS = $(KERNEL_SYMBOLS_INPUT_ELF)_symbols.rs KERNEL_SYMBOLS_DEMANGLED_RS = $(shell pwd)/$(KERNEL_SYMBOLS_INPUT_ELF)_symbols_demangled.rs KERNEL_SYMBOLS_ELF = target/$(TARGET)/release/kernel_symbols KERNEL_SYMBOLS_STRIPPED = target/$(TARGET)/release/kernel_symbols_stripped # Export for build.rs of kernel_symbols crate. export KERNEL_SYMBOLS_DEMANGLED_RS ##-------------------------------------------------------------------------------------------------- ## Command building blocks ##-------------------------------------------------------------------------------------------------- GET_SYMBOLS_SECTION_VIRT_ADDR = $(DOCKER_TOOLS) $(EXEC_SYMBOLS_TOOL) \ --get_symbols_section_virt_addr $(KERNEL_SYMBOLS_OUTPUT_ELF) RUSTFLAGS = -C link-arg=--script=$(KERNEL_SYMBOLS_LINKER_SCRIPT) \ -C link-arg=--section-start=.rodata=$$($(GET_SYMBOLS_SECTION_VIRT_ADDR)) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) \ -D warnings \ -D missing_docs COMPILER_ARGS = --target=$(TARGET) \ --release RUSTC_CMD = cargo rustc $(COMPILER_ARGS) --manifest-path $(KERNEL_SYMBOLS_MANIFEST) OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary EXEC_SYMBOLS_TOOL = ruby $(KERNEL_SYMBOLS_TOOL_PATH)/main.rb ##------------------------------------------------------------------------------ ## Dockerization ##------------------------------------------------------------------------------ DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial # DOCKER_IMAGE defined in include file (see top of this file). DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) ##-------------------------------------------------------------------------------------------------- ## Targets ##-------------------------------------------------------------------------------------------------- .PHONY: all symbols measure_time_start measure_time_finish all: measure_time_start symbols measure_time_finish symbols: @cp $(KERNEL_SYMBOLS_INPUT_ELF) $(KERNEL_SYMBOLS_OUTPUT_ELF) @$(DOCKER_TOOLS) $(EXEC_SYMBOLS_TOOL) --gen_symbols $(KERNEL_SYMBOLS_OUTPUT_ELF) \ $(KERNEL_SYMBOLS_RS) $(call color_progress_prefix, "Demangling") @echo Symbol names @cat $(KERNEL_SYMBOLS_RS) | rustfilt > $(KERNEL_SYMBOLS_DEMANGLED_RS) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) $(call color_progress_prefix, "Stripping") @echo Symbols ELF file @$(OBJCOPY_CMD) $(KERNEL_SYMBOLS_ELF) $(KERNEL_SYMBOLS_STRIPPED) @$(DOCKER_TOOLS) $(EXEC_SYMBOLS_TOOL) --patch_data $(KERNEL_SYMBOLS_OUTPUT_ELF) \ $(KERNEL_SYMBOLS_STRIPPED) # Note: The following is the only _trivial_ way I could think of that works out of the box on both # Linux and macOS. Since macOS does not have the moduloN nanosecond format string option, the # resolution is restricted to whole seconds. measure_time_start: @date +modulos > /tmp/kernel_symbols_start.date measure_time_finish: @date +modulos > /tmp/kernel_symbols_end.date $(call color_progress_prefix, "Finished") @echo "in $$((`cat /tmp/kernel_symbols_end.date` - `cat /tmp/kernel_symbols_start.date`)).0s" @rm /tmp/kernel_symbols_end.date /tmp/kernel_symbols_start.date ``` -------------------------------- ### From MMIODescriptor to MemoryRegion in Rust Source: https://github.com/rust-embedded/rust-raspberrypi-os-tutorials/blob/master/14_virtual_mem_part2_mmio_remap/README.md This `From` trait implementation converts an `MMIODescriptor` into a `MemoryRegion`. It calculates the start and end addresses of the region by aligning the descriptor's addresses to page boundaries. ```rust impl From for MemoryRegion { fn from(desc: MMIODescriptor) -> Self { let start = PageAddress::from(desc.start_addr.align_down_page()); let end_exclusive = PageAddress::from(desc.end_addr_exclusive().align_up_page()); Self { start, end_exclusive, } } } ```