diff --git a/09_privilege_level/Cargo.toml b/09_privilege_level/Cargo.toml index 480508b53..d36481a4f 100644 --- a/09_privilege_level/Cargo.toml +++ b/09_privilege_level/Cargo.toml @@ -11,6 +11,7 @@ lto = true default = [] bsp_rpi3 = ["tock-registers"] bsp_rpi4 = ["tock-registers"] +bsp_rpi5 = ["tock-registers"] [[bin]] name = "kernel" diff --git a/09_privilege_level/Makefile b/09_privilege_level/Makefile index 9549f0920..2e60010c4 100644 --- a/09_privilege_level/Makefile +++ b/09_privilege_level/Makefile @@ -1,6 +1,7 @@ ## SPDX-License-Identifier: MIT OR Apache-2.0 ## -## Copyright (c) 2018-2023 Andre Richter +## Copyright (c) 2018-2025 Andre Richter +## Copyright (c) 2025 Devansh Lodha include ../common/docker.mk include ../common/format.mk @@ -49,6 +50,20 @@ else ifeq ($(BSP),rpi4) JTAG_BOOT_IMAGE = ../X1_JTAG_boot/jtag_boot_rpi4.img LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +else ifeq ($(BSP),rpi5) + TARGET = aarch64-unknown-none-softfloat + KERNEL_BIN = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = + QEMU_RELEASE_ARGS = -serial stdio -display none + OBJDUMP_BINARY = aarch64-none-elf-objdump + NM_BINARY = aarch64-none-elf-nm + READELF_BINARY = aarch64-none-elf-readelf + GDB_BINARY = aarch64-elf-gdb + OPENOCD_ARG = -f ../debug/pi5/cmsis-dap.cfg -f ../debug/pi5/raspberrypi5.cfg + GDB_INIT_FILE = ../debug/pi5/gdb-init.txt + LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi5 + RUSTC_MISC_ARGS = -C target-cpu=cortex-a76 endif # Export for build.rs. @@ -66,7 +81,7 @@ LAST_BUILD_CONFIG = target/$(BSP).build_config KERNEL_ELF = target/$(TARGET)/release/kernel # This parses cargo's dep-info file. # https://doc.rust-lang.org/cargo/guide/build-cache.html#dep-info-files -KERNEL_ELF_DEPS = $(filter-out %: ,$(file < $(KERNEL_ELF).d)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) +KERNEL_ELF_DEPS = $(filter-out %: ,$(shell cat $(KERNEL_ELF).d 2>/dev/null)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) @@ -86,10 +101,10 @@ COMPILER_ARGS = --target=$(TARGET) \ $(FEATURES) \ --release -RUSTC_CMD = cargo rustc $(COMPILER_ARGS) -DOC_CMD = cargo doc $(COMPILER_ARGS) -CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) -OBJCOPY_CMD = rust-objcopy \ +BASE_RUSTC_CMD = cargo rustc $(COMPILER_ARGS) +BASE_DOC_CMD = cargo doc $(COMPILER_ARGS) +BASE_CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) +BASE_OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary @@ -98,38 +113,38 @@ EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb EXEC_MINIPUSH = ruby ../common/serial/minipush.rb ##------------------------------------------------------------------------------ -## Dockerization +## OS-dependent commands and Dockerization ##------------------------------------------------------------------------------ -DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common -DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot -DOCKER_ARG_DEV = --privileged -v /dev:/dev -DOCKER_ARG_NET = --network host - -# DOCKER_IMAGE defined in include file (see top of this file). -DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) -DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) - -# Dockerize commands, which require USB device passthrough, only on Linux. -ifeq ($(shell uname -s),Linux) - DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) +DOCKER_CMD_PREFIX = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD_PREFIX) -i - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) - DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_IMAGE) - DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -else - DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# -endif +# DOCKER_IMAGE is defined in ../common/docker.mk +DOCKER_TOOLS_WRAPPER = $(DOCKER_CMD_PREFIX) $(DOCKER_IMAGE) +ifeq ($(shell uname -s),Linux) + OPENOCD_CMD = $(DOCKER_CMD_INTERACT) --privileged -v /dev:/dev --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) openocd + GDB_CMD = $(DOCKER_CMD_INTERACT) --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) gdb-multiarch + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else ifeq ($(shell uname -s),Darwin) # macOS - Use local tools for hardware interaction + OPENOCD_CMD = openocd # Assumes OpenOCD is installed locally + GDB_CMD = $(GDB_BINARY) # Assumes aarch64-elf-gdb is in PATH + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else # Fallback for other OSes + OPENOCD_CMD = echo "OpenOCD on this OS is not supported by this Makefile."; false + GDB_CMD = echo "GDB on this OS is not supported by this Makefile."; false +endif +# These commands are always local, as per the repository's design +RUSTC_CMD = $(BASE_RUSTC_CMD) +DOC_CMD = $(BASE_DOC_CMD) +CLIPPY_CMD = $(BASE_CLIPPY_CMD) +OBJCOPY_CMD = $(BASE_OBJCOPY_CMD) +CLEAN_CMD = cargo clean ##-------------------------------------------------------------------------------------------------- ## Targets ##-------------------------------------------------------------------------------------------------- -.PHONY: all doc qemu chainboot clippy clean readelf objdump nm check +.PHONY: all doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) @@ -178,16 +193,10 @@ else # QEMU is supported. qemu: $(KERNEL_BIN) $(call color_header, "Launching QEMU") - @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + @$(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif -##------------------------------------------------------------------------------ -## Push the kernel to the real HW target -##------------------------------------------------------------------------------ -chainboot: $(KERNEL_BIN) - @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) - ##------------------------------------------------------------------------------ ## Run clippy ##------------------------------------------------------------------------------ @@ -198,21 +207,22 @@ clippy: ## Clean ##------------------------------------------------------------------------------ clean: - rm -rf target $(KERNEL_BIN) + @$(CLEAN_CMD) + @rm -f kernel8.img ##------------------------------------------------------------------------------ ## Run readelf ##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call color_header, "Launching readelf") - @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) + @$(DOCKER_TOOLS_WRAPPER) $(READELF_BINARY) --headers $(KERNEL_ELF) ##------------------------------------------------------------------------------ ## Run objdump ##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call color_header, "Launching objdump") - @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ + @$(DOCKER_TOOLS_WRAPPER) $(OBJDUMP_BINARY) --disassemble --demangle \ --section .text \ --section .rodata \ $(KERNEL_ELF) | rustfilt @@ -222,27 +232,31 @@ objdump: $(KERNEL_ELF) ##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call color_header, "Launching nm") - @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt + @$(DOCKER_TOOLS_WRAPPER) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt ##-------------------------------------------------------------------------------------------------- ## Debugging targets ##-------------------------------------------------------------------------------------------------- -.PHONY: jtagboot openocd gdb gdb-opt0 +.PHONY: sd_image openocd gdb gdb-opt0 ##------------------------------------------------------------------------------ -## Push the JTAG boot image to the real HW target +## Build the JTAG halt stub and copy it for SD card use ##------------------------------------------------------------------------------ -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) +sd_image: + $(call color_header, "Building JTAG halt stub for SD card") + @$(STUB_MAKE_CMD) -C ../X2_pi5_jtag_halt_stub + @cp ../X2_pi5_jtag_halt_stub/halt_stub.img ./kernel8.img + $(call color_progress_prefix, "Name") + @echo "kernel8.img (Halt Stub)" ##------------------------------------------------------------------------------ ## Start OpenOCD session ##------------------------------------------------------------------------------ openocd: $(call color_header, "Launching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + @$(OPENOCD_CMD) $(OPENOCD_ARG) ##------------------------------------------------------------------------------ ## Start GDB session @@ -251,7 +265,11 @@ gdb: RUSTC_MISC_ARGS += -C debuginfo=2 gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 gdb gdb-opt0: $(KERNEL_ELF) $(call color_header, "Launching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) +ifeq ($(BSP),rpi5) + @$(GDB_CMD) -q -x $(GDB_INIT_FILE) $(KERNEL_ELF) +else + @$(GDB_CMD) -q $(KERNEL_ELF) +endif diff --git a/09_privilege_level/src/bsp.rs b/09_privilege_level/src/bsp.rs index 246973bc0..285b208f8 100644 --- a/09_privilege_level/src/bsp.rs +++ b/09_privilege_level/src/bsp.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! Conditional reexporting of Board Support Packages. @@ -11,3 +11,9 @@ mod raspberrypi; #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use raspberrypi::*; + +#[cfg(feature = "bsp_rpi5")] +mod raspberrypi5; + +#[cfg(feature = "bsp_rpi5")] +pub use raspberrypi5::*; diff --git a/09_privilege_level/src/bsp/device_driver.rs b/09_privilege_level/src/bsp/device_driver.rs index 64049a4cf..caf807b92 100644 --- a/09_privilege_level/src/bsp/device_driver.rs +++ b/09_privilege_level/src/bsp/device_driver.rs @@ -1,12 +1,24 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! Device driver. -#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4", feature = "bsp_rpi5"))] mod bcm; +#[cfg(feature = "bsp_rpi5")] +mod rp1_gpio; + mod common; +// For RPi3 and RPi4, re-export everything from the `bcm` module, which includes +// both the GPIO and UART drivers. #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm::*; + +// For RPi5, re-export only the drivers it uses: the common PL011Uart from the bcm +// module and its specific GPIO driver from the rp1_gpio module. +#[cfg(feature = "bsp_rpi5")] +pub use bcm::PL011Uart; +#[cfg(feature = "bsp_rpi5")] +pub use rp1_gpio::GPIO; diff --git a/09_privilege_level/src/bsp/device_driver/bcm.rs b/09_privilege_level/src/bsp/device_driver/bcm.rs index 1c343d1d7..29812e9a9 100644 --- a/09_privilege_level/src/bsp/device_driver/bcm.rs +++ b/09_privilege_level/src/bsp/device_driver/bcm.rs @@ -1,11 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! BCM driver top level. +// The bcm2xxx_gpio driver is only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] mod bcm2xxx_gpio; + +// The PL011 UART is used by all supported RPi versions. mod bcm2xxx_pl011_uart; +// Re-export the GPIO driver only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm2xxx_gpio::*; + +// Re-export the UART driver for all. pub use bcm2xxx_pl011_uart::*; diff --git a/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs b/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs index d92612ea8..e7b2200a9 100644 --- a/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs +++ b/09_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! PL011 UART driver. //! @@ -201,22 +201,6 @@ impl PL011UartInner { } /// Set up baud rate and characteristics. - /// - /// This results in 8N1 and 921_600 baud. - /// - /// The calculation for the BRD is (we set the clock to 48 MHz in config.txt): - /// `(48_000_000 / 16) / 921_600 = 3.2552083`. - /// - /// This means the integer part is `3` and goes into the `IBRD`. - /// The fractional part is `0.2552083`. - /// - /// `FBRD` calculation according to the PL011 Technical Reference Manual: - /// `INTEGER((0.2552083 * 64) + 0.5) = 16`. - /// - /// Therefore, the generated baud rate divider is: `3 + 16/64 = 3.25`. Which results in a - /// genrated baud rate of `48_000_000 / (16 * 3.25) = 923_077`. - /// - /// Error = `((923_077 - 921_600) / 921_600) * 100 = 0.16%`. pub fn init(&mut self) { // Execution can arrive here while there are still characters queued in the TX FIFO and // actively being sent out by the UART hardware. If the UART is turned off in this case, @@ -240,9 +224,26 @@ impl PL011UartInner { // updated on a single write strobe generated by a LCR_H write. So, to internally update the // contents of IBRD or FBRD, a LCR_H write must always be performed at the end. // - // Set the baud rate, 8N1 and FIFO enabled. - self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); - self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + // Set the baud rate. + #[cfg(feature = "bsp_rpi5")] + { + // For Pi 5, use the known-good 115200 baud configuration. + // This is proven to work with the default 48MHz UART clock. + // BAUDDIV = 48,000,000 / (16 * 115200) = 26.0416... + // IBRD = 26, FBRD = 3 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3)); + } + #[cfg(not(feature = "bsp_rpi5"))] + { + // Original configuration for RPi3/4 at 921600 baud. + // BAUDDIV = 48_000,000 / (16 * 921_600) = 3.2552... + // IBRD = 3, FBRD = 16 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + } + + // Set 8N1 and FIFO enabled. self.registers .LCR_H .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled); @@ -255,7 +256,18 @@ impl PL011UartInner { /// Send a character. fn write_char(&mut self, c: char) { - // Spin while TX FIFO full is set, waiting for an empty slot. + // If the character is a newline, prepend a carriage return. + if c == '\n' { + // Spin while TX FIFO full is set. + while self.registers.FR.matches_all(FR::TXFF::SET) { + cpu::nop(); + } + // Write the carriage return character. + self.registers.DR.set('\r' as u32); + } + + // Now, send the original character. + // Spin while TX FIFO full is set. while self.registers.FR.matches_all(FR::TXFF::SET) { cpu::nop(); } @@ -292,7 +304,8 @@ impl PL011UartInner { // Read one character. let mut ret = self.registers.DR.get() as u8 as char; - // Convert carrige return to newline. + // Convert carriage return to newline. This is a standard behavior + // for console input. if ret == '\r' { ret = '\n' } @@ -316,6 +329,7 @@ impl PL011UartInner { impl fmt::Write for PL011UartInner { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { + // The `write_char` function now correctly handles `\n` -> `\r\n` conversion. self.write_char(c); } diff --git a/09_privilege_level/src/bsp/device_driver/rp1_gpio.rs b/09_privilege_level/src/bsp/device_driver/rp1_gpio.rs new file mode 100644 index 000000000..006310550 --- /dev/null +++ b/09_privilege_level/src/bsp/device_driver/rp1_gpio.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2025 Devansh Lodha +// Copyright (c) 2018-2025 Andre Richter + +//! RP1 GPIO Driver for Raspberry Pi 5. + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, driver, synchronization, + synchronization::NullLock, +}; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::ReadWrite, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// Describes the layout of the IO_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 6. +register_structs! { + #[allow(non_snake_case)] + IoBank0RegisterBlock { + (0x000 => _reserved1), + (0x074 => GPIO14_CTRL: ReadWrite), // GPIO 14 Control Register + (0x078 => _reserved2), + (0x07c => GPIO15_CTRL: ReadWrite), // GPIO 15 Control Register + (0x080 => @END), + } +} + +// Describes the layout of the PADS_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 19. +register_structs! { + #[allow(non_snake_case)] + PadsBank0RegisterBlock { + (0x000 => _reserved1), + (0x03c => GPIO14: ReadWrite), // Pad control for GPIO 14 + (0x040 => GPIO15: ReadWrite), // Pad control for GPIO 15 + (0x044 => @END), + } +} + +/// Abstraction for the IO_BANK0 registers. +type IoBank0Registers = MMIODerefWrapper; + +/// Abstraction for the PADS_BANK0 registers. +type PadsBank0Registers = MMIODerefWrapper; + +// The inner state of the GPIO driver. +struct GPIOInner { + io_bank0: IoBank0Registers, + pads_bank0: PadsBank0Registers, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the GPIO peripheral. +pub struct GPIO { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Implementation +//-------------------------------------------------------------------------------------------------- + +impl GPIOInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + io_bank0: IoBank0Registers::new(io_bank0_mmio_start_addr), + pads_bank0: PadsBank0Registers::new(pads_bank0_mmio_start_addr), + } + } + + /// Map PL011 UART to GPIO pins 14 and 15. + pub fn map_pl011_uart(&mut self) { + // From RP1 Peripherals datasheet, Table 21: PADS_BANK0 GPIO0 Register + const BIT_PADS_OD: u32 = 1 << 7; // Output Disable + const BIT_PADS_IE: u32 = 1 << 6; // Input Enable + const BIT_PADS_PUE: u32 = 1 << 3; // Pull-Up Enable + + // From RP1 Peripherals datasheet, Table 4: GPIO function selection + const FUNCSEL_FIELD_LSB: u32 = 0; + const FUNCSEL_FIELD_MASK: u32 = 0x1F << FUNCSEL_FIELD_LSB; + const FUNC_UART0: u32 = 4; // UART0 is alternate function 4 + + // --- Configure GPIO 14 as UART0 TX --- + + // 1. Set pad electrical properties in PADS_BANK0. + // - Clear Output Disable (OD) bit to enable output. + let mut pads14_val = self.pads_bank0.GPIO14.get(); + pads14_val &= !BIT_PADS_OD; + self.pads_bank0.GPIO14.set(pads14_val); + + // 2. Set pin function in IO_BANK0. + let mut io14_val = self.io_bank0.GPIO14_CTRL.get(); + io14_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io14_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO14_CTRL.set(io14_val); + + // --- Configure GPIO 15 as UART0 RX --- + + // 1. Set pad electrical properties. + // - Set Input Enable (IE) bit. + // - Set Pull-Up Enable (PUE) as per datasheet recommendation. + let mut pads15_val = self.pads_bank0.GPIO15.get(); + pads15_val |= BIT_PADS_IE | BIT_PADS_PUE; + self.pads_bank0.GPIO15.set(pads15_val); + + // 2. Set pin function. + let mut io15_val = self.io_bank0.GPIO15_CTRL.get(); + io15_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io15_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO15_CTRL.set(io15_val); + } +} + +impl GPIO { + pub const COMPATIBLE: &'static str = "RP1 GPIO"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses for both register blocks. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + inner: NullLock::new(GPIOInner::new( + io_bank0_mmio_start_addr, + pads_bank0_mmio_start_addr, + )), + } + } + + /// Concurrency-safe version of `GPIOInner.map_pl011_uart()`. + pub fn map_pl011_uart(&self) { + self.inner.lock(|inner| inner.map_pl011_uart()) + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; + +impl driver::interface::DeviceDriver for GPIO { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } +} diff --git a/09_privilege_level/src/bsp/raspberrypi5.rs b/09_privilege_level/src/bsp/raspberrypi5.rs new file mode 100644 index 000000000..56b37261b --- /dev/null +++ b/09_privilege_level/src/bsp/raspberrypi5.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! Top-level BSP file for the Raspberry Pi 5. + +pub mod cpu; +pub mod driver; +pub mod memory; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Board identification. +pub fn board_name() -> &'static str { + "Raspberry Pi 5" +} \ No newline at end of file diff --git a/09_privilege_level/src/bsp/raspberrypi5/cpu.rs b/09_privilege_level/src/bsp/raspberrypi5/cpu.rs new file mode 100644 index 000000000..1a22eafad --- /dev/null +++ b/09_privilege_level/src/bsp/raspberrypi5/cpu.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter + +//! BSP Processor code for the Raspberry Pi 5. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Used by `arch` code to find the early boot core. +#[no_mangle] +#[link_section = ".text._start_arguments"] +pub static BOOT_CORE_ID: u64 = 0; \ No newline at end of file diff --git a/09_privilege_level/src/bsp/raspberrypi5/driver.rs b/09_privilege_level/src/bsp/raspberrypi5/driver.rs new file mode 100644 index 000000000..a14918388 --- /dev/null +++ b/09_privilege_level/src/bsp/raspberrypi5/driver.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP driver support for the Raspberry Pi 5. + +use super::memory::map::mmio; +use crate::{bsp::device_driver, console, driver as generic_driver}; +use core::sync::atomic::{AtomicBool, Ordering}; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static PL011_UART: device_driver::PL011Uart = + unsafe { device_driver::PL011Uart::new(mmio::PL011_UART_START) }; + +static GPIO: device_driver::GPIO = + unsafe { device_driver::GPIO::new(mmio::GPIO_START, mmio::PADS_BANK0_START) }; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// This must be called only after successful init of the UART driver. +fn post_init_uart() -> Result<(), &'static str> { + console::register_console(&PL011_UART); + Ok(()) +} + +/// This must be called only after successful init of the GPIO driver. +fn post_init_gpio() -> Result<(), &'static str> { + GPIO.map_pl011_uart(); + Ok(()) +} + +fn driver_uart() -> Result<(), &'static str> { + let uart_descriptor = + generic_driver::DeviceDriverDescriptor::new(&PL011_UART, Some(post_init_uart)); + generic_driver::driver_manager().register_driver(uart_descriptor); + Ok(()) +} + +fn driver_gpio() -> Result<(), &'static str> { + let gpio_descriptor = generic_driver::DeviceDriverDescriptor::new(&GPIO, Some(post_init_gpio)); + generic_driver::driver_manager().register_driver(gpio_descriptor); + Ok(()) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Initialize the driver subsystem. +/// +/// # Safety +/// +/// See child function calls. +pub unsafe fn init() -> Result<(), &'static str> { + static INIT_DONE: AtomicBool = AtomicBool::new(false); + if INIT_DONE.load(Ordering::Relaxed) { + return Err("Init already done"); + } + + // On the Raspberry Pi 5, GPIO pins must be configured for their peripheral + // function (e.g., UART) before the peripheral itself is initialized. + driver_gpio()?; + driver_uart()?; + + INIT_DONE.store(true, Ordering::Relaxed); + Ok(()) +} \ No newline at end of file diff --git a/09_privilege_level/src/bsp/raspberrypi5/kernel.ld b/09_privilege_level/src/bsp/raspberrypi5/kernel.ld new file mode 100644 index 000000000..ee51fcdfd --- /dev/null +++ b/09_privilege_level/src/bsp/raspberrypi5/kernel.ld @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2018-2025 Andre Richter + */ + +__rpi_phys_dram_start_addr = 0; + +/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ +__rpi_phys_binary_load_addr = 0x80000; + + +ENTRY(__rpi_phys_binary_load_addr) + +/* Flags: + * 4 == R + * 5 == RX + * 6 == RW + * + * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. + * It doesn't mean all of them need actually be loaded. + */ +PHDRS +{ + segment_boot_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + . = __rpi_phys_dram_start_addr; + + /*********************************************************************************************** + * Boot Core Stack + ***********************************************************************************************/ + .boot_core_stack (NOLOAD) : + { + /* ^ */ + /* | stack */ + . += __rpi_phys_binary_load_addr; /* | growth */ + /* | direction */ + __boot_core_stack_end_exclusive = .; /* | */ + } :segment_boot_core_stack + + /*********************************************************************************************** + * Code + RO Data + Global Offset Table + ***********************************************************************************************/ + .text : + { + KEEP(*(.text._start)) + *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ + *(.text._start_rust) /* The Rust entry point */ + *(.text*) /* Everything else */ + } :segment_code + + .rodata : ALIGN(8) { *(.rodata*) } :segment_code + + /*********************************************************************************************** + * Data + BSS + ***********************************************************************************************/ + .data : { *(.data*) } :segment_data + + /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end_exclusive = .; + } :segment_data + + /*********************************************************************************************** + * Misc + ***********************************************************************************************/ + .got : { *(.got*) } + ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") + + /DISCARD/ : { *(.comment*) } +} \ No newline at end of file diff --git a/09_privilege_level/src/bsp/raspberrypi5/memory.rs b/09_privilege_level/src/bsp/raspberrypi5/memory.rs new file mode 100644 index 000000000..661b5370c --- /dev/null +++ b/09_privilege_level/src/bsp/raspberrypi5/memory.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP Memory Management. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The board's physical memory map. +#[rustfmt::skip] +pub(super) mod map { + pub const RP1_PCI_ECAM_BASE: usize = 0x1f00000000; + + /// Physical devices. + pub mod mmio { + use super::*; + + // See `RP1 Peripherals` datasheet, table 5 and 22. + pub const GPIO_START: usize = RP1_PCI_ECAM_BASE + 0xd0000; + pub const PADS_BANK0_START: usize = RP1_PCI_ECAM_BASE + 0xf0000; + pub const PL011_UART_START: usize = RP1_PCI_ECAM_BASE + 0x30000; + } +} \ No newline at end of file diff --git a/10_virtual_mem_part1_identity_mapping/Cargo.toml b/10_virtual_mem_part1_identity_mapping/Cargo.toml index 6f12f98e8..5104c320f 100644 --- a/10_virtual_mem_part1_identity_mapping/Cargo.toml +++ b/10_virtual_mem_part1_identity_mapping/Cargo.toml @@ -11,6 +11,7 @@ lto = true default = [] bsp_rpi3 = ["tock-registers"] bsp_rpi4 = ["tock-registers"] +bsp_rpi5 = ["tock-registers"] [[bin]] name = "kernel" diff --git a/10_virtual_mem_part1_identity_mapping/Makefile b/10_virtual_mem_part1_identity_mapping/Makefile index 9549f0920..2e60010c4 100644 --- a/10_virtual_mem_part1_identity_mapping/Makefile +++ b/10_virtual_mem_part1_identity_mapping/Makefile @@ -1,6 +1,7 @@ ## SPDX-License-Identifier: MIT OR Apache-2.0 ## -## Copyright (c) 2018-2023 Andre Richter +## Copyright (c) 2018-2025 Andre Richter +## Copyright (c) 2025 Devansh Lodha include ../common/docker.mk include ../common/format.mk @@ -49,6 +50,20 @@ else ifeq ($(BSP),rpi4) JTAG_BOOT_IMAGE = ../X1_JTAG_boot/jtag_boot_rpi4.img LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +else ifeq ($(BSP),rpi5) + TARGET = aarch64-unknown-none-softfloat + KERNEL_BIN = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = + QEMU_RELEASE_ARGS = -serial stdio -display none + OBJDUMP_BINARY = aarch64-none-elf-objdump + NM_BINARY = aarch64-none-elf-nm + READELF_BINARY = aarch64-none-elf-readelf + GDB_BINARY = aarch64-elf-gdb + OPENOCD_ARG = -f ../debug/pi5/cmsis-dap.cfg -f ../debug/pi5/raspberrypi5.cfg + GDB_INIT_FILE = ../debug/pi5/gdb-init.txt + LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi5 + RUSTC_MISC_ARGS = -C target-cpu=cortex-a76 endif # Export for build.rs. @@ -66,7 +81,7 @@ LAST_BUILD_CONFIG = target/$(BSP).build_config KERNEL_ELF = target/$(TARGET)/release/kernel # This parses cargo's dep-info file. # https://doc.rust-lang.org/cargo/guide/build-cache.html#dep-info-files -KERNEL_ELF_DEPS = $(filter-out %: ,$(file < $(KERNEL_ELF).d)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) +KERNEL_ELF_DEPS = $(filter-out %: ,$(shell cat $(KERNEL_ELF).d 2>/dev/null)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) @@ -86,10 +101,10 @@ COMPILER_ARGS = --target=$(TARGET) \ $(FEATURES) \ --release -RUSTC_CMD = cargo rustc $(COMPILER_ARGS) -DOC_CMD = cargo doc $(COMPILER_ARGS) -CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) -OBJCOPY_CMD = rust-objcopy \ +BASE_RUSTC_CMD = cargo rustc $(COMPILER_ARGS) +BASE_DOC_CMD = cargo doc $(COMPILER_ARGS) +BASE_CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) +BASE_OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary @@ -98,38 +113,38 @@ EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb EXEC_MINIPUSH = ruby ../common/serial/minipush.rb ##------------------------------------------------------------------------------ -## Dockerization +## OS-dependent commands and Dockerization ##------------------------------------------------------------------------------ -DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common -DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot -DOCKER_ARG_DEV = --privileged -v /dev:/dev -DOCKER_ARG_NET = --network host - -# DOCKER_IMAGE defined in include file (see top of this file). -DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) -DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) - -# Dockerize commands, which require USB device passthrough, only on Linux. -ifeq ($(shell uname -s),Linux) - DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) +DOCKER_CMD_PREFIX = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD_PREFIX) -i - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) - DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_IMAGE) - DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -else - DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# -endif +# DOCKER_IMAGE is defined in ../common/docker.mk +DOCKER_TOOLS_WRAPPER = $(DOCKER_CMD_PREFIX) $(DOCKER_IMAGE) +ifeq ($(shell uname -s),Linux) + OPENOCD_CMD = $(DOCKER_CMD_INTERACT) --privileged -v /dev:/dev --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) openocd + GDB_CMD = $(DOCKER_CMD_INTERACT) --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) gdb-multiarch + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else ifeq ($(shell uname -s),Darwin) # macOS - Use local tools for hardware interaction + OPENOCD_CMD = openocd # Assumes OpenOCD is installed locally + GDB_CMD = $(GDB_BINARY) # Assumes aarch64-elf-gdb is in PATH + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else # Fallback for other OSes + OPENOCD_CMD = echo "OpenOCD on this OS is not supported by this Makefile."; false + GDB_CMD = echo "GDB on this OS is not supported by this Makefile."; false +endif +# These commands are always local, as per the repository's design +RUSTC_CMD = $(BASE_RUSTC_CMD) +DOC_CMD = $(BASE_DOC_CMD) +CLIPPY_CMD = $(BASE_CLIPPY_CMD) +OBJCOPY_CMD = $(BASE_OBJCOPY_CMD) +CLEAN_CMD = cargo clean ##-------------------------------------------------------------------------------------------------- ## Targets ##-------------------------------------------------------------------------------------------------- -.PHONY: all doc qemu chainboot clippy clean readelf objdump nm check +.PHONY: all doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) @@ -178,16 +193,10 @@ else # QEMU is supported. qemu: $(KERNEL_BIN) $(call color_header, "Launching QEMU") - @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + @$(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif -##------------------------------------------------------------------------------ -## Push the kernel to the real HW target -##------------------------------------------------------------------------------ -chainboot: $(KERNEL_BIN) - @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) - ##------------------------------------------------------------------------------ ## Run clippy ##------------------------------------------------------------------------------ @@ -198,21 +207,22 @@ clippy: ## Clean ##------------------------------------------------------------------------------ clean: - rm -rf target $(KERNEL_BIN) + @$(CLEAN_CMD) + @rm -f kernel8.img ##------------------------------------------------------------------------------ ## Run readelf ##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call color_header, "Launching readelf") - @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) + @$(DOCKER_TOOLS_WRAPPER) $(READELF_BINARY) --headers $(KERNEL_ELF) ##------------------------------------------------------------------------------ ## Run objdump ##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call color_header, "Launching objdump") - @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ + @$(DOCKER_TOOLS_WRAPPER) $(OBJDUMP_BINARY) --disassemble --demangle \ --section .text \ --section .rodata \ $(KERNEL_ELF) | rustfilt @@ -222,27 +232,31 @@ objdump: $(KERNEL_ELF) ##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call color_header, "Launching nm") - @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt + @$(DOCKER_TOOLS_WRAPPER) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt ##-------------------------------------------------------------------------------------------------- ## Debugging targets ##-------------------------------------------------------------------------------------------------- -.PHONY: jtagboot openocd gdb gdb-opt0 +.PHONY: sd_image openocd gdb gdb-opt0 ##------------------------------------------------------------------------------ -## Push the JTAG boot image to the real HW target +## Build the JTAG halt stub and copy it for SD card use ##------------------------------------------------------------------------------ -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) +sd_image: + $(call color_header, "Building JTAG halt stub for SD card") + @$(STUB_MAKE_CMD) -C ../X2_pi5_jtag_halt_stub + @cp ../X2_pi5_jtag_halt_stub/halt_stub.img ./kernel8.img + $(call color_progress_prefix, "Name") + @echo "kernel8.img (Halt Stub)" ##------------------------------------------------------------------------------ ## Start OpenOCD session ##------------------------------------------------------------------------------ openocd: $(call color_header, "Launching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + @$(OPENOCD_CMD) $(OPENOCD_ARG) ##------------------------------------------------------------------------------ ## Start GDB session @@ -251,7 +265,11 @@ gdb: RUSTC_MISC_ARGS += -C debuginfo=2 gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 gdb gdb-opt0: $(KERNEL_ELF) $(call color_header, "Launching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) +ifeq ($(BSP),rpi5) + @$(GDB_CMD) -q -x $(GDB_INIT_FILE) $(KERNEL_ELF) +else + @$(GDB_CMD) -q $(KERNEL_ELF) +endif diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp.rs b/10_virtual_mem_part1_identity_mapping/src/bsp.rs index 7a3c804bd..d5ed872f7 100644 --- a/10_virtual_mem_part1_identity_mapping/src/bsp.rs +++ b/10_virtual_mem_part1_identity_mapping/src/bsp.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! Conditional reexporting of Board Support Packages. @@ -11,3 +11,9 @@ mod raspberrypi; #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use raspberrypi::*; + +#[cfg(feature = "bsp_rpi5")] +mod raspberrypi5; + +#[cfg(feature = "bsp_rpi5")] +pub use raspberrypi5::*; \ No newline at end of file diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver.rs index 64049a4cf..caf807b92 100644 --- a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver.rs +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver.rs @@ -1,12 +1,24 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! Device driver. -#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4", feature = "bsp_rpi5"))] mod bcm; +#[cfg(feature = "bsp_rpi5")] +mod rp1_gpio; + mod common; +// For RPi3 and RPi4, re-export everything from the `bcm` module, which includes +// both the GPIO and UART drivers. #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm::*; + +// For RPi5, re-export only the drivers it uses: the common PL011Uart from the bcm +// module and its specific GPIO driver from the rp1_gpio module. +#[cfg(feature = "bsp_rpi5")] +pub use bcm::PL011Uart; +#[cfg(feature = "bsp_rpi5")] +pub use rp1_gpio::GPIO; diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm.rs index 1c343d1d7..29812e9a9 100644 --- a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm.rs +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm.rs @@ -1,11 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! BCM driver top level. +// The bcm2xxx_gpio driver is only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] mod bcm2xxx_gpio; + +// The PL011 UART is used by all supported RPi versions. mod bcm2xxx_pl011_uart; +// Re-export the GPIO driver only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm2xxx_gpio::*; + +// Re-export the UART driver for all. pub use bcm2xxx_pl011_uart::*; diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs index d92612ea8..e7b2200a9 100644 --- a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! PL011 UART driver. //! @@ -201,22 +201,6 @@ impl PL011UartInner { } /// Set up baud rate and characteristics. - /// - /// This results in 8N1 and 921_600 baud. - /// - /// The calculation for the BRD is (we set the clock to 48 MHz in config.txt): - /// `(48_000_000 / 16) / 921_600 = 3.2552083`. - /// - /// This means the integer part is `3` and goes into the `IBRD`. - /// The fractional part is `0.2552083`. - /// - /// `FBRD` calculation according to the PL011 Technical Reference Manual: - /// `INTEGER((0.2552083 * 64) + 0.5) = 16`. - /// - /// Therefore, the generated baud rate divider is: `3 + 16/64 = 3.25`. Which results in a - /// genrated baud rate of `48_000_000 / (16 * 3.25) = 923_077`. - /// - /// Error = `((923_077 - 921_600) / 921_600) * 100 = 0.16%`. pub fn init(&mut self) { // Execution can arrive here while there are still characters queued in the TX FIFO and // actively being sent out by the UART hardware. If the UART is turned off in this case, @@ -240,9 +224,26 @@ impl PL011UartInner { // updated on a single write strobe generated by a LCR_H write. So, to internally update the // contents of IBRD or FBRD, a LCR_H write must always be performed at the end. // - // Set the baud rate, 8N1 and FIFO enabled. - self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); - self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + // Set the baud rate. + #[cfg(feature = "bsp_rpi5")] + { + // For Pi 5, use the known-good 115200 baud configuration. + // This is proven to work with the default 48MHz UART clock. + // BAUDDIV = 48,000,000 / (16 * 115200) = 26.0416... + // IBRD = 26, FBRD = 3 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3)); + } + #[cfg(not(feature = "bsp_rpi5"))] + { + // Original configuration for RPi3/4 at 921600 baud. + // BAUDDIV = 48_000,000 / (16 * 921_600) = 3.2552... + // IBRD = 3, FBRD = 16 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + } + + // Set 8N1 and FIFO enabled. self.registers .LCR_H .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled); @@ -255,7 +256,18 @@ impl PL011UartInner { /// Send a character. fn write_char(&mut self, c: char) { - // Spin while TX FIFO full is set, waiting for an empty slot. + // If the character is a newline, prepend a carriage return. + if c == '\n' { + // Spin while TX FIFO full is set. + while self.registers.FR.matches_all(FR::TXFF::SET) { + cpu::nop(); + } + // Write the carriage return character. + self.registers.DR.set('\r' as u32); + } + + // Now, send the original character. + // Spin while TX FIFO full is set. while self.registers.FR.matches_all(FR::TXFF::SET) { cpu::nop(); } @@ -292,7 +304,8 @@ impl PL011UartInner { // Read one character. let mut ret = self.registers.DR.get() as u8 as char; - // Convert carrige return to newline. + // Convert carriage return to newline. This is a standard behavior + // for console input. if ret == '\r' { ret = '\n' } @@ -316,6 +329,7 @@ impl PL011UartInner { impl fmt::Write for PL011UartInner { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { + // The `write_char` function now correctly handles `\n` -> `\r\n` conversion. self.write_char(c); } diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/rp1_gpio.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/rp1_gpio.rs new file mode 100644 index 000000000..006310550 --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/device_driver/rp1_gpio.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2025 Devansh Lodha +// Copyright (c) 2018-2025 Andre Richter + +//! RP1 GPIO Driver for Raspberry Pi 5. + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, driver, synchronization, + synchronization::NullLock, +}; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::ReadWrite, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// Describes the layout of the IO_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 6. +register_structs! { + #[allow(non_snake_case)] + IoBank0RegisterBlock { + (0x000 => _reserved1), + (0x074 => GPIO14_CTRL: ReadWrite), // GPIO 14 Control Register + (0x078 => _reserved2), + (0x07c => GPIO15_CTRL: ReadWrite), // GPIO 15 Control Register + (0x080 => @END), + } +} + +// Describes the layout of the PADS_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 19. +register_structs! { + #[allow(non_snake_case)] + PadsBank0RegisterBlock { + (0x000 => _reserved1), + (0x03c => GPIO14: ReadWrite), // Pad control for GPIO 14 + (0x040 => GPIO15: ReadWrite), // Pad control for GPIO 15 + (0x044 => @END), + } +} + +/// Abstraction for the IO_BANK0 registers. +type IoBank0Registers = MMIODerefWrapper; + +/// Abstraction for the PADS_BANK0 registers. +type PadsBank0Registers = MMIODerefWrapper; + +// The inner state of the GPIO driver. +struct GPIOInner { + io_bank0: IoBank0Registers, + pads_bank0: PadsBank0Registers, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the GPIO peripheral. +pub struct GPIO { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Implementation +//-------------------------------------------------------------------------------------------------- + +impl GPIOInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + io_bank0: IoBank0Registers::new(io_bank0_mmio_start_addr), + pads_bank0: PadsBank0Registers::new(pads_bank0_mmio_start_addr), + } + } + + /// Map PL011 UART to GPIO pins 14 and 15. + pub fn map_pl011_uart(&mut self) { + // From RP1 Peripherals datasheet, Table 21: PADS_BANK0 GPIO0 Register + const BIT_PADS_OD: u32 = 1 << 7; // Output Disable + const BIT_PADS_IE: u32 = 1 << 6; // Input Enable + const BIT_PADS_PUE: u32 = 1 << 3; // Pull-Up Enable + + // From RP1 Peripherals datasheet, Table 4: GPIO function selection + const FUNCSEL_FIELD_LSB: u32 = 0; + const FUNCSEL_FIELD_MASK: u32 = 0x1F << FUNCSEL_FIELD_LSB; + const FUNC_UART0: u32 = 4; // UART0 is alternate function 4 + + // --- Configure GPIO 14 as UART0 TX --- + + // 1. Set pad electrical properties in PADS_BANK0. + // - Clear Output Disable (OD) bit to enable output. + let mut pads14_val = self.pads_bank0.GPIO14.get(); + pads14_val &= !BIT_PADS_OD; + self.pads_bank0.GPIO14.set(pads14_val); + + // 2. Set pin function in IO_BANK0. + let mut io14_val = self.io_bank0.GPIO14_CTRL.get(); + io14_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io14_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO14_CTRL.set(io14_val); + + // --- Configure GPIO 15 as UART0 RX --- + + // 1. Set pad electrical properties. + // - Set Input Enable (IE) bit. + // - Set Pull-Up Enable (PUE) as per datasheet recommendation. + let mut pads15_val = self.pads_bank0.GPIO15.get(); + pads15_val |= BIT_PADS_IE | BIT_PADS_PUE; + self.pads_bank0.GPIO15.set(pads15_val); + + // 2. Set pin function. + let mut io15_val = self.io_bank0.GPIO15_CTRL.get(); + io15_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io15_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO15_CTRL.set(io15_val); + } +} + +impl GPIO { + pub const COMPATIBLE: &'static str = "RP1 GPIO"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses for both register blocks. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + inner: NullLock::new(GPIOInner::new( + io_bank0_mmio_start_addr, + pads_bank0_mmio_start_addr, + )), + } + } + + /// Concurrency-safe version of `GPIOInner.map_pl011_uart()`. + pub fn map_pl011_uart(&self) { + self.inner.lock(|inner| inner.map_pl011_uart()) + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; + +impl driver::interface::DeviceDriver for GPIO { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } +} diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5.rs new file mode 100644 index 000000000..56b37261b --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! Top-level BSP file for the Raspberry Pi 5. + +pub mod cpu; +pub mod driver; +pub mod memory; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Board identification. +pub fn board_name() -> &'static str { + "Raspberry Pi 5" +} \ No newline at end of file diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/cpu.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/cpu.rs new file mode 100644 index 000000000..1a22eafad --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/cpu.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter + +//! BSP Processor code for the Raspberry Pi 5. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Used by `arch` code to find the early boot core. +#[no_mangle] +#[link_section = ".text._start_arguments"] +pub static BOOT_CORE_ID: u64 = 0; \ No newline at end of file diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/driver.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/driver.rs new file mode 100644 index 000000000..a14918388 --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/driver.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP driver support for the Raspberry Pi 5. + +use super::memory::map::mmio; +use crate::{bsp::device_driver, console, driver as generic_driver}; +use core::sync::atomic::{AtomicBool, Ordering}; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static PL011_UART: device_driver::PL011Uart = + unsafe { device_driver::PL011Uart::new(mmio::PL011_UART_START) }; + +static GPIO: device_driver::GPIO = + unsafe { device_driver::GPIO::new(mmio::GPIO_START, mmio::PADS_BANK0_START) }; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// This must be called only after successful init of the UART driver. +fn post_init_uart() -> Result<(), &'static str> { + console::register_console(&PL011_UART); + Ok(()) +} + +/// This must be called only after successful init of the GPIO driver. +fn post_init_gpio() -> Result<(), &'static str> { + GPIO.map_pl011_uart(); + Ok(()) +} + +fn driver_uart() -> Result<(), &'static str> { + let uart_descriptor = + generic_driver::DeviceDriverDescriptor::new(&PL011_UART, Some(post_init_uart)); + generic_driver::driver_manager().register_driver(uart_descriptor); + Ok(()) +} + +fn driver_gpio() -> Result<(), &'static str> { + let gpio_descriptor = generic_driver::DeviceDriverDescriptor::new(&GPIO, Some(post_init_gpio)); + generic_driver::driver_manager().register_driver(gpio_descriptor); + Ok(()) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Initialize the driver subsystem. +/// +/// # Safety +/// +/// See child function calls. +pub unsafe fn init() -> Result<(), &'static str> { + static INIT_DONE: AtomicBool = AtomicBool::new(false); + if INIT_DONE.load(Ordering::Relaxed) { + return Err("Init already done"); + } + + // On the Raspberry Pi 5, GPIO pins must be configured for their peripheral + // function (e.g., UART) before the peripheral itself is initialized. + driver_gpio()?; + driver_uart()?; + + INIT_DONE.store(true, Ordering::Relaxed); + Ok(()) +} \ No newline at end of file diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/kernel.ld b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/kernel.ld new file mode 100644 index 000000000..b967219b0 --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/kernel.ld @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2018-2025 Andre Richter + */ + +PAGE_SIZE = 64K; +PAGE_MASK = PAGE_SIZE - 1; + +__rpi_phys_dram_start_addr = 0; + +/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ +__rpi_phys_binary_load_addr = 0x80000; + + +ENTRY(__rpi_phys_binary_load_addr) + +/* Flags: + * 4 == R + * 5 == RX + * 6 == RW + * + * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. + * It doesn't mean all of them need actually be loaded. + */ +PHDRS +{ + segment_boot_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + . = __rpi_phys_dram_start_addr; + + /*********************************************************************************************** + * Boot Core Stack + ***********************************************************************************************/ + .boot_core_stack (NOLOAD) : + { + /* ^ */ + /* | stack */ + . += __rpi_phys_binary_load_addr; /* | growth */ + /* | direction */ + __boot_core_stack_end_exclusive = .; /* | */ + } :segment_boot_core_stack + + ASSERT((. & PAGE_MASK) == 0, "End of boot core stack is not page aligned") + + /*********************************************************************************************** + * Code + RO Data + Global Offset Table + ***********************************************************************************************/ + __code_start = .; + .text : + { + KEEP(*(.text._start)) + *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ + *(.text._start_rust) /* The Rust entry point */ + *(.text*) /* Everything else */ + } :segment_code + + .rodata : ALIGN(8) { *(.rodata*) } :segment_code + + . = ALIGN(PAGE_SIZE); + __code_end_exclusive = .; + + /*********************************************************************************************** + * Data + BSS + ***********************************************************************************************/ + .data : { *(.data*) } :segment_data + + /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end_exclusive = .; + } :segment_data + + /*********************************************************************************************** + * Misc + ***********************************************************************************************/ + .got : { *(.got*) } + ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") + + /DISCARD/ : { *(.comment*) } +} diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/memory.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/memory.rs new file mode 100644 index 000000000..a022e23f5 --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/memory.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP Memory Management for the Raspberry Pi 5. +//! +//! The physical memory layout. +//! +//! The Raspberry's firmware copies the kernel binary to 0x8_0000. The preceding region will be used +//! as the boot core's stack. + +pub mod mmu; + +use core::cell::UnsafeCell; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// Symbols from the linker script. +extern "Rust" { + static __code_start: UnsafeCell<()>; + static __code_end_exclusive: UnsafeCell<()>; +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The board's physical memory map. +#[rustfmt::skip] +pub(super) mod map { + /// The inclusive end address of the memory map. + /// + /// End address + 1 must be a power of two. + /// + /// We define the address space to be 128 GiB. This is the smallest power of two that + /// encapsulates the RP1's MMIO addresses. + pub const END_INCLUSIVE: usize = (128 * 1024 * 1024 * 1024) - 1; + pub const RP1_PCI_ECAM_BASE: usize = 0x1f00000000; + + /// Physical devices. + pub mod mmio { + use super::*; + + // See `RP1 Peripherals` datasheet, table 5 and 22. + pub const START: usize = RP1_PCI_ECAM_BASE; + pub const GPIO_START: usize = START + 0xd0000; + pub const PADS_BANK0_START: usize = START + 0xf0000; + pub const PL011_UART_START: usize = START + 0x30000; + + // Define an end for the MMIO region. The highest peripheral address in the datasheet + // is around 0x1f00400000, so we'll use a slightly larger, rounded value. + pub const END_INCLUSIVE: usize = 0x1f0fffffff; + } +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// Start page address of the code segment. +/// +/// # Safety +/// +/// - Value is provided by the linker script and must be trusted as-is. +#[inline(always)] +fn code_start() -> usize { + unsafe { __code_start.get() as usize } +} + +/// Exclusive end page address of the code segment. +/// # Safety +/// +/// - Value is provided by the linker script and must be trusted as-is. +#[inline(always)] +fn code_end_exclusive() -> usize { + unsafe { __code_end_exclusive.get() as usize } +} diff --git a/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/memory/mmu.rs b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/memory/mmu.rs new file mode 100644 index 000000000..50f09b24f --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/src/bsp/raspberrypi5/memory/mmu.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP Memory Management Unit for the Raspberry Pi 5. + +use super::map as memory_map; +use crate::memory::mmu::*; +use core::ops::RangeInclusive; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The kernel's address space defined by this BSP. +pub type KernelAddrSpace = AddressSpace<{ memory_map::END_INCLUSIVE + 1 }>; + +const NUM_MEM_RANGES: usize = 3; + +/// The virtual memory layout. +/// +/// The layout must contain only special ranges, aka anything that is _not_ normal cacheable DRAM. +/// It is agnostic of the paging granularity that the architecture's MMU will use. +pub static LAYOUT: KernelVirtualLayout = KernelVirtualLayout::new( + memory_map::END_INCLUSIVE, + [ + TranslationDescriptor { + name: "Kernel code and RO data", + virtual_range: code_range_inclusive, + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + TranslationDescriptor { + name: "Remapped Device MMIO (UART)", + virtual_range: remapped_mmio_range_inclusive, + // The chosen virtual address for the remapped UART is 0x1FFF_1000. + // The virtual range starts at 0x1FFF_0000. + // Therefore, the offset that the physical memory block must be based at is: + // PL011_UART_START - 0x1000 + physical_range_translation: Translation::Offset( + memory_map::mmio::PL011_UART_START - 0x1000, + ), + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + TranslationDescriptor { + name: "Device MMIO", + virtual_range: mmio_range_inclusive, + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + ], +); + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +fn code_range_inclusive() -> RangeInclusive { + // Notice the subtraction to turn the exclusive end into an inclusive end. + #[allow(clippy::range_minus_one)] + RangeInclusive::new(super::code_start(), super::code_end_exclusive() - 1) +} + +fn remapped_mmio_range_inclusive() -> RangeInclusive { + // The last 64 KiB slot in the first 512 MiB. Chosen for educational purposes + // to match the original tutorial's concept. + RangeInclusive::new(0x1FFF_0000, 0x1FFF_FFFF) +} + +fn mmio_range_inclusive() -> RangeInclusive { + RangeInclusive::new(memory_map::mmio::START, memory_map::mmio::END_INCLUSIVE) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the virtual memory layout. +pub fn virt_mem_layout() -> &'static KernelVirtualLayout { + &LAYOUT +} \ No newline at end of file diff --git a/11_exceptions_part1_groundwork/Cargo.toml b/11_exceptions_part1_groundwork/Cargo.toml index 22343d4c5..a37666dd2 100644 --- a/11_exceptions_part1_groundwork/Cargo.toml +++ b/11_exceptions_part1_groundwork/Cargo.toml @@ -11,6 +11,7 @@ lto = true default = [] bsp_rpi3 = ["tock-registers"] bsp_rpi4 = ["tock-registers"] +bsp_rpi5 = ["tock-registers"] [[bin]] name = "kernel" diff --git a/11_exceptions_part1_groundwork/Makefile b/11_exceptions_part1_groundwork/Makefile index 9549f0920..d6b2bd92e 100644 --- a/11_exceptions_part1_groundwork/Makefile +++ b/11_exceptions_part1_groundwork/Makefile @@ -1,6 +1,7 @@ ## SPDX-License-Identifier: MIT OR Apache-2.0 ## -## Copyright (c) 2018-2023 Andre Richter +## Copyright (c) 2018-2025 Andre Richter +## Copyright (c) 2025 Devansh Lodha include ../common/docker.mk include ../common/format.mk @@ -10,8 +11,8 @@ include ../common/operating_system.mk ## Optional, user-provided configuration values ##-------------------------------------------------------------------------------------------------- -# Default to the RPi3. -BSP ?= rpi3 +# Default to the RPi5. +BSP ?= rpi5 # Default to a serial device name that is common in Linux. DEV_SERIAL ?= /dev/ttyUSB0 @@ -49,6 +50,20 @@ else ifeq ($(BSP),rpi4) JTAG_BOOT_IMAGE = ../X1_JTAG_boot/jtag_boot_rpi4.img LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +else ifeq ($(BSP),rpi5) + TARGET = aarch64-unknown-none-softfloat + KERNEL_BIN = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = + QEMU_RELEASE_ARGS = -serial stdio -display none + OBJDUMP_BINARY = aarch64-none-elf-objdump + NM_BINARY = aarch64-none-elf-nm + READELF_BINARY = aarch64-none-elf-readelf + GDB_BINARY = aarch64-elf-gdb + OPENOCD_ARG = -f ../debug/pi5/cmsis-dap.cfg -f ../debug/pi5/raspberrypi5.cfg + GDB_INIT_FILE = ../debug/pi5/gdb-init.txt + LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi5 + RUSTC_MISC_ARGS = -C target-cpu=cortex-a76 endif # Export for build.rs. @@ -66,7 +81,7 @@ LAST_BUILD_CONFIG = target/$(BSP).build_config KERNEL_ELF = target/$(TARGET)/release/kernel # This parses cargo's dep-info file. # https://doc.rust-lang.org/cargo/guide/build-cache.html#dep-info-files -KERNEL_ELF_DEPS = $(filter-out %: ,$(file < $(KERNEL_ELF).d)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) +KERNEL_ELF_DEPS = $(filter-out %: ,$(shell cat $(KERNEL_ELF).d 2>/dev/null)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) @@ -86,10 +101,10 @@ COMPILER_ARGS = --target=$(TARGET) \ $(FEATURES) \ --release -RUSTC_CMD = cargo rustc $(COMPILER_ARGS) -DOC_CMD = cargo doc $(COMPILER_ARGS) -CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) -OBJCOPY_CMD = rust-objcopy \ +BASE_RUSTC_CMD = cargo rustc $(COMPILER_ARGS) +BASE_DOC_CMD = cargo doc $(COMPILER_ARGS) +BASE_CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) +BASE_OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary @@ -98,38 +113,38 @@ EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb EXEC_MINIPUSH = ruby ../common/serial/minipush.rb ##------------------------------------------------------------------------------ -## Dockerization +## OS-dependent commands and Dockerization ##------------------------------------------------------------------------------ -DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common -DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot -DOCKER_ARG_DEV = --privileged -v /dev:/dev -DOCKER_ARG_NET = --network host - -# DOCKER_IMAGE defined in include file (see top of this file). -DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) -DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) - -# Dockerize commands, which require USB device passthrough, only on Linux. -ifeq ($(shell uname -s),Linux) - DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) +DOCKER_CMD_PREFIX = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD_PREFIX) -i - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) - DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_IMAGE) - DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -else - DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# -endif +# DOCKER_IMAGE is defined in ../common/docker.mk +DOCKER_TOOLS_WRAPPER = $(DOCKER_CMD_PREFIX) $(DOCKER_IMAGE) +ifeq ($(shell uname -s),Linux) + OPENOCD_CMD = $(DOCKER_CMD_INTERACT) --privileged -v /dev:/dev --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) openocd + GDB_CMD = $(DOCKER_CMD_INTERACT) --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) gdb-multiarch + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else ifeq ($(shell uname -s),Darwin) # macOS - Use local tools for hardware interaction + OPENOCD_CMD = openocd # Assumes OpenOCD is installed locally + GDB_CMD = $(GDB_BINARY) # Assumes aarch64-elf-gdb is in PATH + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else # Fallback for other OSes + OPENOCD_CMD = echo "OpenOCD on this OS is not supported by this Makefile."; false + GDB_CMD = echo "GDB on this OS is not supported by this Makefile."; false +endif +# These commands are always local, as per the repository's design +RUSTC_CMD = $(BASE_RUSTC_CMD) +DOC_CMD = $(BASE_DOC_CMD) +CLIPPY_CMD = $(BASE_CLIPPY_CMD) +OBJCOPY_CMD = $(BASE_OBJCOPY_CMD) +CLEAN_CMD = cargo clean ##-------------------------------------------------------------------------------------------------- ## Targets ##-------------------------------------------------------------------------------------------------- -.PHONY: all doc qemu chainboot clippy clean readelf objdump nm check +.PHONY: all doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) @@ -178,16 +193,10 @@ else # QEMU is supported. qemu: $(KERNEL_BIN) $(call color_header, "Launching QEMU") - @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + @$(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif -##------------------------------------------------------------------------------ -## Push the kernel to the real HW target -##------------------------------------------------------------------------------ -chainboot: $(KERNEL_BIN) - @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) - ##------------------------------------------------------------------------------ ## Run clippy ##------------------------------------------------------------------------------ @@ -198,21 +207,22 @@ clippy: ## Clean ##------------------------------------------------------------------------------ clean: - rm -rf target $(KERNEL_BIN) + @$(CLEAN_CMD) + @rm -f kernel8.img ##------------------------------------------------------------------------------ ## Run readelf ##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call color_header, "Launching readelf") - @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) + @$(DOCKER_TOOLS_WRAPPER) $(READELF_BINARY) --headers $(KERNEL_ELF) ##------------------------------------------------------------------------------ ## Run objdump ##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call color_header, "Launching objdump") - @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ + @$(DOCKER_TOOLS_WRAPPER) $(OBJDUMP_BINARY) --disassemble --demangle \ --section .text \ --section .rodata \ $(KERNEL_ELF) | rustfilt @@ -222,27 +232,31 @@ objdump: $(KERNEL_ELF) ##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call color_header, "Launching nm") - @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt + @$(DOCKER_TOOLS_WRAPPER) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt ##-------------------------------------------------------------------------------------------------- ## Debugging targets ##-------------------------------------------------------------------------------------------------- -.PHONY: jtagboot openocd gdb gdb-opt0 +.PHONY: sd_image openocd gdb gdb-opt0 ##------------------------------------------------------------------------------ -## Push the JTAG boot image to the real HW target +## Build the JTAG halt stub and copy it for SD card use ##------------------------------------------------------------------------------ -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) +sd_image: + $(call color_header, "Building JTAG halt stub for SD card") + @$(STUB_MAKE_CMD) -C ../X2_pi5_jtag_halt_stub + @cp ../X2_pi5_jtag_halt_stub/halt_stub.img ./kernel8.img + $(call color_progress_prefix, "Name") + @echo "kernel8.img (Halt Stub)" ##------------------------------------------------------------------------------ ## Start OpenOCD session ##------------------------------------------------------------------------------ openocd: $(call color_header, "Launching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + @$(OPENOCD_CMD) $(OPENOCD_ARG) ##------------------------------------------------------------------------------ ## Start GDB session @@ -251,7 +265,11 @@ gdb: RUSTC_MISC_ARGS += -C debuginfo=2 gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 gdb gdb-opt0: $(KERNEL_ELF) $(call color_header, "Launching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) +ifeq ($(BSP),rpi5) + @$(GDB_CMD) -q -x $(GDB_INIT_FILE) $(KERNEL_ELF) +else + @$(GDB_CMD) -q $(KERNEL_ELF) +endif diff --git a/11_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs b/11_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs index 9d2ed5b76..ae5d6b732 100644 --- a/11_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs +++ b/11_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs @@ -90,9 +90,10 @@ extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) { if e.fault_address_valid() { let far_el1 = FAR_EL1.get(); - // This catches the demo case for this tutorial. If the fault address happens to be 8 GiB, - // advance the exception link register for one instruction, so that execution can continue. - if far_el1 == 8 * 1024 * 1024 * 1024 { + // This catches the demo case for this tutorial. If the fault address happens to be + // 129 GiB, advance the exception link register for one instruction, so that execution + // can continue. + if far_el1 == 129 * 1024 * 1024 * 1024 { e.elr_el1 += 4; return; diff --git a/11_exceptions_part1_groundwork/src/bsp.rs b/11_exceptions_part1_groundwork/src/bsp.rs index 246973bc0..c72e2c13b 100644 --- a/11_exceptions_part1_groundwork/src/bsp.rs +++ b/11_exceptions_part1_groundwork/src/bsp.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! Conditional reexporting of Board Support Packages. @@ -11,3 +11,9 @@ mod raspberrypi; #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use raspberrypi::*; + +#[cfg(feature = "bsp_rpi5")] +mod raspberrypi5; + +#[cfg(feature = "bsp_rpi5")] +pub use raspberrypi5::*; \ No newline at end of file diff --git a/11_exceptions_part1_groundwork/src/bsp/device_driver.rs b/11_exceptions_part1_groundwork/src/bsp/device_driver.rs index 64049a4cf..caf807b92 100644 --- a/11_exceptions_part1_groundwork/src/bsp/device_driver.rs +++ b/11_exceptions_part1_groundwork/src/bsp/device_driver.rs @@ -1,12 +1,24 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! Device driver. -#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4", feature = "bsp_rpi5"))] mod bcm; +#[cfg(feature = "bsp_rpi5")] +mod rp1_gpio; + mod common; +// For RPi3 and RPi4, re-export everything from the `bcm` module, which includes +// both the GPIO and UART drivers. #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm::*; + +// For RPi5, re-export only the drivers it uses: the common PL011Uart from the bcm +// module and its specific GPIO driver from the rp1_gpio module. +#[cfg(feature = "bsp_rpi5")] +pub use bcm::PL011Uart; +#[cfg(feature = "bsp_rpi5")] +pub use rp1_gpio::GPIO; diff --git a/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm.rs b/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm.rs index 1c343d1d7..29812e9a9 100644 --- a/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm.rs +++ b/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm.rs @@ -1,11 +1,19 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! BCM driver top level. +// The bcm2xxx_gpio driver is only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] mod bcm2xxx_gpio; + +// The PL011 UART is used by all supported RPi versions. mod bcm2xxx_pl011_uart; +// Re-export the GPIO driver only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm2xxx_gpio::*; + +// Re-export the UART driver for all. pub use bcm2xxx_pl011_uart::*; diff --git a/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs b/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs index d92612ea8..e7b2200a9 100644 --- a/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs +++ b/11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter //! PL011 UART driver. //! @@ -201,22 +201,6 @@ impl PL011UartInner { } /// Set up baud rate and characteristics. - /// - /// This results in 8N1 and 921_600 baud. - /// - /// The calculation for the BRD is (we set the clock to 48 MHz in config.txt): - /// `(48_000_000 / 16) / 921_600 = 3.2552083`. - /// - /// This means the integer part is `3` and goes into the `IBRD`. - /// The fractional part is `0.2552083`. - /// - /// `FBRD` calculation according to the PL011 Technical Reference Manual: - /// `INTEGER((0.2552083 * 64) + 0.5) = 16`. - /// - /// Therefore, the generated baud rate divider is: `3 + 16/64 = 3.25`. Which results in a - /// genrated baud rate of `48_000_000 / (16 * 3.25) = 923_077`. - /// - /// Error = `((923_077 - 921_600) / 921_600) * 100 = 0.16%`. pub fn init(&mut self) { // Execution can arrive here while there are still characters queued in the TX FIFO and // actively being sent out by the UART hardware. If the UART is turned off in this case, @@ -240,9 +224,26 @@ impl PL011UartInner { // updated on a single write strobe generated by a LCR_H write. So, to internally update the // contents of IBRD or FBRD, a LCR_H write must always be performed at the end. // - // Set the baud rate, 8N1 and FIFO enabled. - self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); - self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + // Set the baud rate. + #[cfg(feature = "bsp_rpi5")] + { + // For Pi 5, use the known-good 115200 baud configuration. + // This is proven to work with the default 48MHz UART clock. + // BAUDDIV = 48,000,000 / (16 * 115200) = 26.0416... + // IBRD = 26, FBRD = 3 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3)); + } + #[cfg(not(feature = "bsp_rpi5"))] + { + // Original configuration for RPi3/4 at 921600 baud. + // BAUDDIV = 48_000,000 / (16 * 921_600) = 3.2552... + // IBRD = 3, FBRD = 16 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + } + + // Set 8N1 and FIFO enabled. self.registers .LCR_H .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled); @@ -255,7 +256,18 @@ impl PL011UartInner { /// Send a character. fn write_char(&mut self, c: char) { - // Spin while TX FIFO full is set, waiting for an empty slot. + // If the character is a newline, prepend a carriage return. + if c == '\n' { + // Spin while TX FIFO full is set. + while self.registers.FR.matches_all(FR::TXFF::SET) { + cpu::nop(); + } + // Write the carriage return character. + self.registers.DR.set('\r' as u32); + } + + // Now, send the original character. + // Spin while TX FIFO full is set. while self.registers.FR.matches_all(FR::TXFF::SET) { cpu::nop(); } @@ -292,7 +304,8 @@ impl PL011UartInner { // Read one character. let mut ret = self.registers.DR.get() as u8 as char; - // Convert carrige return to newline. + // Convert carriage return to newline. This is a standard behavior + // for console input. if ret == '\r' { ret = '\n' } @@ -316,6 +329,7 @@ impl PL011UartInner { impl fmt::Write for PL011UartInner { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { + // The `write_char` function now correctly handles `\n` -> `\r\n` conversion. self.write_char(c); } diff --git a/11_exceptions_part1_groundwork/src/bsp/device_driver/rp1_gpio.rs b/11_exceptions_part1_groundwork/src/bsp/device_driver/rp1_gpio.rs new file mode 100644 index 000000000..006310550 --- /dev/null +++ b/11_exceptions_part1_groundwork/src/bsp/device_driver/rp1_gpio.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2025 Devansh Lodha +// Copyright (c) 2018-2025 Andre Richter + +//! RP1 GPIO Driver for Raspberry Pi 5. + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, driver, synchronization, + synchronization::NullLock, +}; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::ReadWrite, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// Describes the layout of the IO_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 6. +register_structs! { + #[allow(non_snake_case)] + IoBank0RegisterBlock { + (0x000 => _reserved1), + (0x074 => GPIO14_CTRL: ReadWrite), // GPIO 14 Control Register + (0x078 => _reserved2), + (0x07c => GPIO15_CTRL: ReadWrite), // GPIO 15 Control Register + (0x080 => @END), + } +} + +// Describes the layout of the PADS_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 19. +register_structs! { + #[allow(non_snake_case)] + PadsBank0RegisterBlock { + (0x000 => _reserved1), + (0x03c => GPIO14: ReadWrite), // Pad control for GPIO 14 + (0x040 => GPIO15: ReadWrite), // Pad control for GPIO 15 + (0x044 => @END), + } +} + +/// Abstraction for the IO_BANK0 registers. +type IoBank0Registers = MMIODerefWrapper; + +/// Abstraction for the PADS_BANK0 registers. +type PadsBank0Registers = MMIODerefWrapper; + +// The inner state of the GPIO driver. +struct GPIOInner { + io_bank0: IoBank0Registers, + pads_bank0: PadsBank0Registers, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the GPIO peripheral. +pub struct GPIO { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Implementation +//-------------------------------------------------------------------------------------------------- + +impl GPIOInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + io_bank0: IoBank0Registers::new(io_bank0_mmio_start_addr), + pads_bank0: PadsBank0Registers::new(pads_bank0_mmio_start_addr), + } + } + + /// Map PL011 UART to GPIO pins 14 and 15. + pub fn map_pl011_uart(&mut self) { + // From RP1 Peripherals datasheet, Table 21: PADS_BANK0 GPIO0 Register + const BIT_PADS_OD: u32 = 1 << 7; // Output Disable + const BIT_PADS_IE: u32 = 1 << 6; // Input Enable + const BIT_PADS_PUE: u32 = 1 << 3; // Pull-Up Enable + + // From RP1 Peripherals datasheet, Table 4: GPIO function selection + const FUNCSEL_FIELD_LSB: u32 = 0; + const FUNCSEL_FIELD_MASK: u32 = 0x1F << FUNCSEL_FIELD_LSB; + const FUNC_UART0: u32 = 4; // UART0 is alternate function 4 + + // --- Configure GPIO 14 as UART0 TX --- + + // 1. Set pad electrical properties in PADS_BANK0. + // - Clear Output Disable (OD) bit to enable output. + let mut pads14_val = self.pads_bank0.GPIO14.get(); + pads14_val &= !BIT_PADS_OD; + self.pads_bank0.GPIO14.set(pads14_val); + + // 2. Set pin function in IO_BANK0. + let mut io14_val = self.io_bank0.GPIO14_CTRL.get(); + io14_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io14_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO14_CTRL.set(io14_val); + + // --- Configure GPIO 15 as UART0 RX --- + + // 1. Set pad electrical properties. + // - Set Input Enable (IE) bit. + // - Set Pull-Up Enable (PUE) as per datasheet recommendation. + let mut pads15_val = self.pads_bank0.GPIO15.get(); + pads15_val |= BIT_PADS_IE | BIT_PADS_PUE; + self.pads_bank0.GPIO15.set(pads15_val); + + // 2. Set pin function. + let mut io15_val = self.io_bank0.GPIO15_CTRL.get(); + io15_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io15_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO15_CTRL.set(io15_val); + } +} + +impl GPIO { + pub const COMPATIBLE: &'static str = "RP1 GPIO"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses for both register blocks. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + inner: NullLock::new(GPIOInner::new( + io_bank0_mmio_start_addr, + pads_bank0_mmio_start_addr, + )), + } + } + + /// Concurrency-safe version of `GPIOInner.map_pl011_uart()`. + pub fn map_pl011_uart(&self) { + self.inner.lock(|inner| inner.map_pl011_uart()) + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; + +impl driver::interface::DeviceDriver for GPIO { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } +} diff --git a/11_exceptions_part1_groundwork/src/bsp/raspberrypi5.rs b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5.rs new file mode 100644 index 000000000..56b37261b --- /dev/null +++ b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! Top-level BSP file for the Raspberry Pi 5. + +pub mod cpu; +pub mod driver; +pub mod memory; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Board identification. +pub fn board_name() -> &'static str { + "Raspberry Pi 5" +} \ No newline at end of file diff --git a/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/cpu.rs b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/cpu.rs new file mode 100644 index 000000000..1a22eafad --- /dev/null +++ b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/cpu.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter + +//! BSP Processor code for the Raspberry Pi 5. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Used by `arch` code to find the early boot core. +#[no_mangle] +#[link_section = ".text._start_arguments"] +pub static BOOT_CORE_ID: u64 = 0; \ No newline at end of file diff --git a/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/driver.rs b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/driver.rs new file mode 100644 index 000000000..a14918388 --- /dev/null +++ b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/driver.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP driver support for the Raspberry Pi 5. + +use super::memory::map::mmio; +use crate::{bsp::device_driver, console, driver as generic_driver}; +use core::sync::atomic::{AtomicBool, Ordering}; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static PL011_UART: device_driver::PL011Uart = + unsafe { device_driver::PL011Uart::new(mmio::PL011_UART_START) }; + +static GPIO: device_driver::GPIO = + unsafe { device_driver::GPIO::new(mmio::GPIO_START, mmio::PADS_BANK0_START) }; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// This must be called only after successful init of the UART driver. +fn post_init_uart() -> Result<(), &'static str> { + console::register_console(&PL011_UART); + Ok(()) +} + +/// This must be called only after successful init of the GPIO driver. +fn post_init_gpio() -> Result<(), &'static str> { + GPIO.map_pl011_uart(); + Ok(()) +} + +fn driver_uart() -> Result<(), &'static str> { + let uart_descriptor = + generic_driver::DeviceDriverDescriptor::new(&PL011_UART, Some(post_init_uart)); + generic_driver::driver_manager().register_driver(uart_descriptor); + Ok(()) +} + +fn driver_gpio() -> Result<(), &'static str> { + let gpio_descriptor = generic_driver::DeviceDriverDescriptor::new(&GPIO, Some(post_init_gpio)); + generic_driver::driver_manager().register_driver(gpio_descriptor); + Ok(()) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Initialize the driver subsystem. +/// +/// # Safety +/// +/// See child function calls. +pub unsafe fn init() -> Result<(), &'static str> { + static INIT_DONE: AtomicBool = AtomicBool::new(false); + if INIT_DONE.load(Ordering::Relaxed) { + return Err("Init already done"); + } + + // On the Raspberry Pi 5, GPIO pins must be configured for their peripheral + // function (e.g., UART) before the peripheral itself is initialized. + driver_gpio()?; + driver_uart()?; + + INIT_DONE.store(true, Ordering::Relaxed); + Ok(()) +} \ No newline at end of file diff --git a/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/kernel.ld b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/kernel.ld new file mode 100644 index 000000000..b967219b0 --- /dev/null +++ b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/kernel.ld @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2018-2025 Andre Richter + */ + +PAGE_SIZE = 64K; +PAGE_MASK = PAGE_SIZE - 1; + +__rpi_phys_dram_start_addr = 0; + +/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ +__rpi_phys_binary_load_addr = 0x80000; + + +ENTRY(__rpi_phys_binary_load_addr) + +/* Flags: + * 4 == R + * 5 == RX + * 6 == RW + * + * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. + * It doesn't mean all of them need actually be loaded. + */ +PHDRS +{ + segment_boot_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + . = __rpi_phys_dram_start_addr; + + /*********************************************************************************************** + * Boot Core Stack + ***********************************************************************************************/ + .boot_core_stack (NOLOAD) : + { + /* ^ */ + /* | stack */ + . += __rpi_phys_binary_load_addr; /* | growth */ + /* | direction */ + __boot_core_stack_end_exclusive = .; /* | */ + } :segment_boot_core_stack + + ASSERT((. & PAGE_MASK) == 0, "End of boot core stack is not page aligned") + + /*********************************************************************************************** + * Code + RO Data + Global Offset Table + ***********************************************************************************************/ + __code_start = .; + .text : + { + KEEP(*(.text._start)) + *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ + *(.text._start_rust) /* The Rust entry point */ + *(.text*) /* Everything else */ + } :segment_code + + .rodata : ALIGN(8) { *(.rodata*) } :segment_code + + . = ALIGN(PAGE_SIZE); + __code_end_exclusive = .; + + /*********************************************************************************************** + * Data + BSS + ***********************************************************************************************/ + .data : { *(.data*) } :segment_data + + /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end_exclusive = .; + } :segment_data + + /*********************************************************************************************** + * Misc + ***********************************************************************************************/ + .got : { *(.got*) } + ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") + + /DISCARD/ : { *(.comment*) } +} diff --git a/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/memory.rs b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/memory.rs new file mode 100644 index 000000000..a022e23f5 --- /dev/null +++ b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/memory.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP Memory Management for the Raspberry Pi 5. +//! +//! The physical memory layout. +//! +//! The Raspberry's firmware copies the kernel binary to 0x8_0000. The preceding region will be used +//! as the boot core's stack. + +pub mod mmu; + +use core::cell::UnsafeCell; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// Symbols from the linker script. +extern "Rust" { + static __code_start: UnsafeCell<()>; + static __code_end_exclusive: UnsafeCell<()>; +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The board's physical memory map. +#[rustfmt::skip] +pub(super) mod map { + /// The inclusive end address of the memory map. + /// + /// End address + 1 must be a power of two. + /// + /// We define the address space to be 128 GiB. This is the smallest power of two that + /// encapsulates the RP1's MMIO addresses. + pub const END_INCLUSIVE: usize = (128 * 1024 * 1024 * 1024) - 1; + pub const RP1_PCI_ECAM_BASE: usize = 0x1f00000000; + + /// Physical devices. + pub mod mmio { + use super::*; + + // See `RP1 Peripherals` datasheet, table 5 and 22. + pub const START: usize = RP1_PCI_ECAM_BASE; + pub const GPIO_START: usize = START + 0xd0000; + pub const PADS_BANK0_START: usize = START + 0xf0000; + pub const PL011_UART_START: usize = START + 0x30000; + + // Define an end for the MMIO region. The highest peripheral address in the datasheet + // is around 0x1f00400000, so we'll use a slightly larger, rounded value. + pub const END_INCLUSIVE: usize = 0x1f0fffffff; + } +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// Start page address of the code segment. +/// +/// # Safety +/// +/// - Value is provided by the linker script and must be trusted as-is. +#[inline(always)] +fn code_start() -> usize { + unsafe { __code_start.get() as usize } +} + +/// Exclusive end page address of the code segment. +/// # Safety +/// +/// - Value is provided by the linker script and must be trusted as-is. +#[inline(always)] +fn code_end_exclusive() -> usize { + unsafe { __code_end_exclusive.get() as usize } +} diff --git a/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/memory/mmu.rs b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/memory/mmu.rs new file mode 100644 index 000000000..d991147e8 --- /dev/null +++ b/11_exceptions_part1_groundwork/src/bsp/raspberrypi5/memory/mmu.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP Memory Management Unit for the Raspberry Pi 5. + +use super::map as memory_map; +use crate::memory::mmu::*; +use core::ops::RangeInclusive; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The kernel's address space defined by this BSP. +pub type KernelAddrSpace = AddressSpace<{ memory_map::END_INCLUSIVE + 1 }>; + +const NUM_MEM_RANGES: usize = 2; + +/// The virtual memory layout. +/// +/// The layout must contain only special ranges, aka anything that is _not_ normal cacheable DRAM. +/// It is agnostic of the paging granularity that the architecture's MMU will use. +pub static LAYOUT: KernelVirtualLayout = KernelVirtualLayout::new( + memory_map::END_INCLUSIVE, + [ + TranslationDescriptor { + name: "Kernel code and RO data", + virtual_range: code_range_inclusive, + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + TranslationDescriptor { + name: "Device MMIO", + virtual_range: mmio_range_inclusive, + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + ], +); + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +fn code_range_inclusive() -> RangeInclusive { + // Notice the subtraction to turn the exclusive end into an inclusive end. + #[allow(clippy::range_minus_one)] + RangeInclusive::new(super::code_start(), super::code_end_exclusive() - 1) +} + +fn mmio_range_inclusive() -> RangeInclusive { + RangeInclusive::new(memory_map::mmio::START, memory_map::mmio::END_INCLUSIVE) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the virtual memory layout. +pub fn virt_mem_layout() -> &'static KernelVirtualLayout { + &LAYOUT +} diff --git a/11_exceptions_part1_groundwork/src/main.rs b/11_exceptions_part1_groundwork/src/main.rs index fc01bb3b9..98c8c2d73 100644 --- a/11_exceptions_part1_groundwork/src/main.rs +++ b/11_exceptions_part1_groundwork/src/main.rs @@ -195,14 +195,13 @@ fn kernel_main() -> ! { info!("Timer test, spinning for 1 second"); time::time_manager().spin_for(Duration::from_secs(1)); - // Cause an exception by accessing a virtual address for which no translation was set up. This - // code accesses the address 8 GiB, which is outside the mapped address space. + // Cause an exception by accessing a virtual address for which no translation was set up. // - // For demo purposes, the exception handler will catch the faulting 8 GiB address and allow - // execution to continue. + // For RPi5, the address space is 128 GiB. We must pick an address outside this range + // to guarantee a translation fault. Let's use 129 GiB. info!(""); - info!("Trying to read from address 8 GiB..."); - let mut big_addr: u64 = 8 * 1024 * 1024 * 1024; + info!("Trying to read from address 129 GiB..."); + let mut big_addr: u64 = 129 * 1024 * 1024 * 1024; unsafe { core::ptr::read_volatile(big_addr as *mut u64) }; info!("************************************************"); @@ -211,9 +210,9 @@ fn kernel_main() -> ! { info!(""); info!("Let's try again"); - // Now use address 9 GiB. The exception handler won't forgive us this time. - info!("Trying to read from address 9 GiB..."); - big_addr = 9 * 1024 * 1024 * 1024; + // Now use address 130 GiB. The exception handler won't forgive us this time. + info!("Trying to read from address 130 GiB..."); + big_addr = 130 * 1024 * 1024 * 1024; unsafe { core::ptr::read_volatile(big_addr as *mut u64) }; // Will never reach here in this tutorial. @@ -226,3 +225,4 @@ fn kernel_main() -> ! { console().write_char(c); } } + diff --git a/12_integrated_testing/Makefile b/12_integrated_testing/Makefile index 4e2efeff3..b570999f0 100644 --- a/12_integrated_testing/Makefile +++ b/12_integrated_testing/Makefile @@ -1,6 +1,7 @@ ## SPDX-License-Identifier: MIT OR Apache-2.0 ## ## Copyright (c) 2018-2023 Andre Richter +## Copyright (c) 2025 Devansh Lodha include ../common/docker.mk include ../common/format.mk @@ -58,6 +59,21 @@ else ifeq ($(BSP),rpi4) JTAG_BOOT_IMAGE = ../X1_JTAG_boot/jtag_boot_rpi4.img LD_SCRIPT_PATH = $(shell pwd)/kernel/src/bsp/raspberrypi RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +else ifeq ($(BSP),rpi5) + TARGET = aarch64-unknown-none-softfloat + KERNEL_BIN = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = + QEMU_RELEASE_ARGS = -serial stdio -display none + QEMU_TEST_ARGS = $(QEMU_RELEASE_ARGS) -semihosting + OBJDUMP_BINARY = aarch64-none-elf-objdump + NM_BINARY = aarch64-none-elf-nm + READELF_BINARY = aarch64-none-elf-readelf + GDB_BINARY = aarch64-elf-gdb + OPENOCD_ARG = -f ../debug/pi5/cmsis-dap.cfg -f ../debug/pi5/raspberrypi5.cfg + GDB_INIT_FILE = ../debug/pi5/gdb-init.txt + LD_SCRIPT_PATH = $(shell pwd)/kernel/src/bsp/raspberrypi5 + RUSTC_MISC_ARGS = -C target-cpu=cortex-a76 endif # Export for build.rs. @@ -72,17 +88,17 @@ KERNEL_MANIFEST = kernel/Cargo.toml KERNEL_LINKER_SCRIPT = kernel.ld LAST_BUILD_CONFIG = target/$(BSP).build_config -KERNEL_ELF = target/$(TARGET)/release/kernel +KERNEL_ELF = target/$(TARGET)/release/kernel # This parses cargo's dep-info file. # https://doc.rust-lang.org/cargo/guide/build-cache.html#dep-info-files -KERNEL_ELF_DEPS = $(filter-out %: ,$(file < $(KERNEL_ELF).d)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) +KERNEL_ELF_DEPS = $(filter-out %: ,$(shell cat $(KERNEL_ELF).d 2>/dev/null)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) ##-------------------------------------------------------------------------------------------------- ## Command building blocks ##-------------------------------------------------------------------------------------------------- -RUSTFLAGS = $(RUSTC_MISC_ARGS) \ +RUSTFLAGS = $(RUSTC_MISC_ARGS) \ -C link-arg=--library-path=$(LD_SCRIPT_PATH) \ -C link-arg=--script=$(KERNEL_LINKER_SCRIPT) @@ -90,17 +106,17 @@ RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) \ -D warnings \ -D missing_docs -FEATURES = --features bsp_$(BSP) +FEATURES = --features bsp_$(BSP) COMPILER_ARGS = --target=$(TARGET) \ - $(FEATURES) \ + $(FEATURES) \ --release -RUSTC_CMD = cargo rustc $(COMPILER_ARGS) --manifest-path $(KERNEL_MANIFEST) -DOC_CMD = cargo doc $(COMPILER_ARGS) -CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) -TEST_CMD = cargo test $(COMPILER_ARGS) --manifest-path $(KERNEL_MANIFEST) -OBJCOPY_CMD = rust-objcopy \ - --strip-all \ +BASE_RUSTC_CMD = cargo rustc $(COMPILER_ARGS) --manifest-path $(KERNEL_MANIFEST) +BASE_DOC_CMD = cargo doc $(COMPILER_ARGS) +BASE_CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) +BASE_TEST_CMD = cargo test $(COMPILER_ARGS) --manifest-path $(KERNEL_MANIFEST) +BASE_OBJCOPY_CMD = rust-objcopy \ + --strip-all \ -O binary EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) @@ -108,38 +124,39 @@ EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb EXEC_MINIPUSH = ruby ../common/serial/minipush.rb ##------------------------------------------------------------------------------ -## Dockerization +## OS-dependent commands and Dockerization ##------------------------------------------------------------------------------ -DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common -DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot -DOCKER_ARG_DEV = --privileged -v /dev:/dev -DOCKER_ARG_NET = --network host +DOCKER_CMD_PREFIX = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD_PREFIX) -i -# DOCKER_IMAGE defined in include file (see top of this file). -DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) -DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) +# DOCKER_IMAGE is defined in ../common/docker.mk +DOCKER_TOOLS_WRAPPER = $(DOCKER_CMD_PREFIX) $(DOCKER_IMAGE) -# Dockerize commands, which require USB device passthrough, only on Linux. ifeq ($(shell uname -s),Linux) - DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) - - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) - DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_IMAGE) - DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -else - DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# + OPENOCD_CMD = $(DOCKER_CMD_INTERACT) --privileged -v /dev:/dev --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) openocd + GDB_CMD = $(DOCKER_CMD_INTERACT) --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) gdb-multiarch + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else ifeq ($(shell uname -s),Darwin) # macOS - Use local tools for hardware interaction + OPENOCD_CMD = openocd # Assumes OpenOCD is installed locally + GDB_CMD = $(GDB_BINARY) # Assumes aarch64-elf-gdb is in PATH + STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE) +else # Fallback for other OSes + OPENOCD_CMD = echo "OpenOCD on this OS is not supported by this Makefile."; false + GDB_CMD = echo "GDB on this OS is not supported by this Makefile."; false endif - +# These commands are always local, as per the repository's design +RUSTC_CMD = $(BASE_RUSTC_CMD) +DOC_CMD = $(BASE_DOC_CMD) +CLIPPY_CMD = $(BASE_CLIPPY_CMD) +TEST_CMD = $(BASE_TEST_CMD) +OBJCOPY_CMD = $(BASE_OBJCOPY_CMD) +CLEAN_CMD = cargo clean ##-------------------------------------------------------------------------------------------------- ## Targets ##-------------------------------------------------------------------------------------------------- -.PHONY: all doc qemu chainboot clippy clean readelf objdump nm check +.PHONY: all doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) @@ -188,73 +205,72 @@ else # QEMU is supported. qemu: $(KERNEL_BIN) $(call color_header, "Launching QEMU") - @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + @$(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif -##------------------------------------------------------------------------------ -## Push the kernel to the real HW target -##------------------------------------------------------------------------------ -chainboot: $(KERNEL_BIN) - @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) - ##------------------------------------------------------------------------------ ## Run clippy ##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) --features test_build --tests \ - --manifest-path $(KERNEL_MANIFEST) + --manifest-path $(KERNEL_MANIFEST) ##------------------------------------------------------------------------------ ## Clean ##------------------------------------------------------------------------------ clean: - rm -rf target $(KERNEL_BIN) + @$(CLEAN_CMD) + @rm -f kernel8.img ##------------------------------------------------------------------------------ ## Run readelf ##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call color_header, "Launching readelf") - @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) + @$(DOCKER_TOOLS_WRAPPER) $(READELF_BINARY) --headers $(KERNEL_ELF) ##------------------------------------------------------------------------------ ## Run objdump ##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call color_header, "Launching objdump") - @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ - --section .text \ - --section .rodata \ - $(KERNEL_ELF) | rustfilt + @$(DOCKER_TOOLS_WRAPPER) $(OBJDUMP_BINARY) --disassemble --demangle \ + --section .text \ + --section .rodata \ + $(KERNEL_ELF) | rustfilt ##------------------------------------------------------------------------------ ## Run nm ##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call color_header, "Launching nm") - @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt + @$(DOCKER_TOOLS_WRAPPER) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt ##-------------------------------------------------------------------------------------------------- ## Debugging targets ##-------------------------------------------------------------------------------------------------- -.PHONY: jtagboot openocd gdb gdb-opt0 +.PHONY: sd_image openocd gdb gdb-opt0 ##------------------------------------------------------------------------------ -## Push the JTAG boot image to the real HW target +## Build the JTAG halt stub and copy it for SD card use ##------------------------------------------------------------------------------ -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) +sd_image: + $(call color_header, "Building JTAG halt stub for SD card") + @$(STUB_MAKE_CMD) -C ../X2_pi5_jtag_halt_stub + @cp ../X2_pi5_jtag_halt_stub/halt_stub.img ./kernel8.img + $(call color_progress_prefix, "Name") + @echo "kernel8.img (Halt Stub)" ##------------------------------------------------------------------------------ ## Start OpenOCD session ##------------------------------------------------------------------------------ openocd: $(call color_header, "Launching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + @$(OPENOCD_CMD) $(OPENOCD_ARG) ##------------------------------------------------------------------------------ ## Start GDB session @@ -263,7 +279,11 @@ gdb: RUSTC_MISC_ARGS += -C debuginfo=2 gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 gdb gdb-opt0: $(KERNEL_ELF) $(call color_header, "Launching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) +ifeq ($(BSP),rpi5) + @$(GDB_CMD) -q -x $(GDB_INIT_FILE) $(KERNEL_ELF) +else + @$(GDB_CMD) -q $(KERNEL_ELF) +endif @@ -274,19 +294,18 @@ gdb gdb-opt0: $(KERNEL_ELF) test_unit test_integration: FEATURES += --features test_build -ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. - -test_boot test_unit test_integration test: - $(call color_header, "$(QEMU_MISSING_STRING)") - -else # QEMU is supported. - ##------------------------------------------------------------------------------ ## Run boot test ##------------------------------------------------------------------------------ test_boot: $(KERNEL_BIN) - $(call color_header, "Boot test - $(BSP)") - @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) +ifeq ($(BSP),rpi5) + @$(call color_header, "Skipping boot test for $(BSP) (no QEMU support)") +else ifeq ($(QEMU_MACHINE_TYPE),) + @$(call color_header, "$(QEMU_MISSING_STRING)") +else + @$(call color_header, "Boot test - $(BSP)") + @$(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) +endif ##------------------------------------------------------------------------------ ## Helpers for unit and integration test targets @@ -302,7 +321,7 @@ define KERNEL_TEST_RUNNER 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 + $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY endef export KERNEL_TEST_RUNNER @@ -317,18 +336,30 @@ endef ## Run unit test(s) ##------------------------------------------------------------------------------ test_unit: - $(call color_header, "Compiling unit test(s) - $(BSP)") - $(call test_prepare) +ifeq ($(BSP),rpi5) + @echo "## Compiling unit test(s) for $(BSP), but not running." + @echo "Due to no QEMU support, tests are built but not executed." + @echo "Load and run the test binaries on-target manually for verification." + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --no-run --lib +else + @$(call color_header, "Compiling unit test(s) - $(BSP)") + @$(call test_prepare) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib +endif ##------------------------------------------------------------------------------ ## Run integration test(s) ##------------------------------------------------------------------------------ test_integration: - $(call color_header, "Compiling integration test(s) - $(BSP)") - $(call test_prepare) +ifeq ($(BSP),rpi5) + @echo "## Compiling integration test(s) for $(BSP), but not running." + @echo "Due to no QEMU support, tests are built but not executed." + @echo "Load and run the test binaries on-target manually for verification." + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --no-run $(TEST_ARG) +else + @$(call color_header, "Compiling integration test(s) - $(BSP)") + @$(call test_prepare) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) +endif test: test_boot test_unit test_integration - -endif diff --git a/12_integrated_testing/kernel/Cargo.toml b/12_integrated_testing/kernel/Cargo.toml index 97dd972e0..fb69bf16e 100644 --- a/12_integrated_testing/kernel/Cargo.toml +++ b/12_integrated_testing/kernel/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "mingo" version = "0.12.0" -authors = ["Andre Richter "] +authors = ["Andre Richter ", "Devansh Lodha "] edition = "2021" [features] default = [] bsp_rpi3 = ["tock-registers"] bsp_rpi4 = ["tock-registers"] +bsp_rpi5 = ["tock-registers"] test_build = ["qemu-exit"] ##-------------------------------------------------------------------------------------------------- diff --git a/12_integrated_testing/kernel/src/bsp.rs b/12_integrated_testing/kernel/src/bsp.rs index 246973bc0..fc3dd4c1d 100644 --- a/12_integrated_testing/kernel/src/bsp.rs +++ b/12_integrated_testing/kernel/src/bsp.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // // Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2025 Devansh Lodha //! Conditional reexporting of Board Support Packages. @@ -11,3 +12,9 @@ mod raspberrypi; #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use raspberrypi::*; + +#[cfg(feature = "bsp_rpi5")] +mod raspberrypi5; + +#[cfg(feature = "bsp_rpi5")] +pub use raspberrypi5::*; diff --git a/12_integrated_testing/kernel/src/bsp/device_driver.rs b/12_integrated_testing/kernel/src/bsp/device_driver.rs index 64049a4cf..634e64386 100644 --- a/12_integrated_testing/kernel/src/bsp/device_driver.rs +++ b/12_integrated_testing/kernel/src/bsp/device_driver.rs @@ -1,12 +1,25 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha //! Device driver. -#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4", feature = "bsp_rpi5"))] mod bcm; +#[cfg(feature = "bsp_rpi5")] +mod rp1_gpio; + mod common; +// For RPi3 and RPi4, re-export everything from the `bcm` module, which includes +// both the GPIO and UART drivers. #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm::*; + +// For RPi5, re-export only the drivers it uses: the common PL011Uart from the bcm +// module and its specific GPIO driver from the rp1_gpio module. +#[cfg(feature = "bsp_rpi5")] +pub use bcm::PL011Uart; +#[cfg(feature = "bsp_rpi5")] +pub use rp1_gpio::GPIO; diff --git a/12_integrated_testing/kernel/src/bsp/device_driver/bcm.rs b/12_integrated_testing/kernel/src/bsp/device_driver/bcm.rs index 1c343d1d7..d699f4f34 100644 --- a/12_integrated_testing/kernel/src/bsp/device_driver/bcm.rs +++ b/12_integrated_testing/kernel/src/bsp/device_driver/bcm.rs @@ -1,11 +1,20 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha //! BCM driver top level. +// The bcm2xxx_gpio driver is only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] mod bcm2xxx_gpio; + +// The PL011 UART is used by all supported RPi versions. mod bcm2xxx_pl011_uart; +// Re-export the GPIO driver only for RPi3 and RPi4. +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] pub use bcm2xxx_gpio::*; + +// Re-export the UART driver for all. pub use bcm2xxx_pl011_uart::*; diff --git a/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs b/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs index d92612ea8..4317d1a65 100644 --- a/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs +++ b/12_integrated_testing/kernel/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2023 Andre Richter +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha //! PL011 UART driver. //! @@ -201,22 +202,6 @@ impl PL011UartInner { } /// Set up baud rate and characteristics. - /// - /// This results in 8N1 and 921_600 baud. - /// - /// The calculation for the BRD is (we set the clock to 48 MHz in config.txt): - /// `(48_000_000 / 16) / 921_600 = 3.2552083`. - /// - /// This means the integer part is `3` and goes into the `IBRD`. - /// The fractional part is `0.2552083`. - /// - /// `FBRD` calculation according to the PL011 Technical Reference Manual: - /// `INTEGER((0.2552083 * 64) + 0.5) = 16`. - /// - /// Therefore, the generated baud rate divider is: `3 + 16/64 = 3.25`. Which results in a - /// genrated baud rate of `48_000_000 / (16 * 3.25) = 923_077`. - /// - /// Error = `((923_077 - 921_600) / 921_600) * 100 = 0.16%`. pub fn init(&mut self) { // Execution can arrive here while there are still characters queued in the TX FIFO and // actively being sent out by the UART hardware. If the UART is turned off in this case, @@ -240,9 +225,26 @@ impl PL011UartInner { // updated on a single write strobe generated by a LCR_H write. So, to internally update the // contents of IBRD or FBRD, a LCR_H write must always be performed at the end. // - // Set the baud rate, 8N1 and FIFO enabled. - self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); - self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + // Set the baud rate. + #[cfg(feature = "bsp_rpi5")] + { + // For Pi 5, use the known-good 115200 baud configuration. + // This is proven to work with the default 48MHz UART clock. + // BAUDDIV = 48,000,000 / (16 * 115200) = 26.0416... + // IBRD = 26, FBRD = 3 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3)); + } + #[cfg(not(feature = "bsp_rpi5"))] + { + // Original configuration for RPi3/4 at 921600 baud. + // BAUDDIV = 48_000,000 / (16 * 921_600) = 3.2552... + // IBRD = 3, FBRD = 16 + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + } + + // Set 8N1 and FIFO enabled. self.registers .LCR_H .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled); @@ -255,7 +257,18 @@ impl PL011UartInner { /// Send a character. fn write_char(&mut self, c: char) { - // Spin while TX FIFO full is set, waiting for an empty slot. + // If the character is a newline, prepend a carriage return. + if c == '\n' { + // Spin while TX FIFO full is set. + while self.registers.FR.matches_all(FR::TXFF::SET) { + cpu::nop(); + } + // Write the carriage return character. + self.registers.DR.set('\r' as u32); + } + + // Now, send the original character. + // Spin while TX FIFO full is set. while self.registers.FR.matches_all(FR::TXFF::SET) { cpu::nop(); } @@ -292,7 +305,8 @@ impl PL011UartInner { // Read one character. let mut ret = self.registers.DR.get() as u8 as char; - // Convert carrige return to newline. + // Convert carriage return to newline. This is a standard behavior + // for console input. if ret == '\r' { ret = '\n' } @@ -316,6 +330,7 @@ impl PL011UartInner { impl fmt::Write for PL011UartInner { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.chars() { + // The `write_char` function now correctly handles `\n` -> `\r\n` conversion. self.write_char(c); } diff --git a/12_integrated_testing/kernel/src/bsp/device_driver/rp1_gpio.rs b/12_integrated_testing/kernel/src/bsp/device_driver/rp1_gpio.rs new file mode 100644 index 000000000..006310550 --- /dev/null +++ b/12_integrated_testing/kernel/src/bsp/device_driver/rp1_gpio.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2025 Devansh Lodha +// Copyright (c) 2018-2025 Andre Richter + +//! RP1 GPIO Driver for Raspberry Pi 5. + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, driver, synchronization, + synchronization::NullLock, +}; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::ReadWrite, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// Describes the layout of the IO_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 6. +register_structs! { + #[allow(non_snake_case)] + IoBank0RegisterBlock { + (0x000 => _reserved1), + (0x074 => GPIO14_CTRL: ReadWrite), // GPIO 14 Control Register + (0x078 => _reserved2), + (0x07c => GPIO15_CTRL: ReadWrite), // GPIO 15 Control Register + (0x080 => @END), + } +} + +// Describes the layout of the PADS_BANK0 registers. +// Found in the "RP1 Peripherals" datasheet, section 3.1.4, Table 19. +register_structs! { + #[allow(non_snake_case)] + PadsBank0RegisterBlock { + (0x000 => _reserved1), + (0x03c => GPIO14: ReadWrite), // Pad control for GPIO 14 + (0x040 => GPIO15: ReadWrite), // Pad control for GPIO 15 + (0x044 => @END), + } +} + +/// Abstraction for the IO_BANK0 registers. +type IoBank0Registers = MMIODerefWrapper; + +/// Abstraction for the PADS_BANK0 registers. +type PadsBank0Registers = MMIODerefWrapper; + +// The inner state of the GPIO driver. +struct GPIOInner { + io_bank0: IoBank0Registers, + pads_bank0: PadsBank0Registers, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the GPIO peripheral. +pub struct GPIO { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Implementation +//-------------------------------------------------------------------------------------------------- + +impl GPIOInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + io_bank0: IoBank0Registers::new(io_bank0_mmio_start_addr), + pads_bank0: PadsBank0Registers::new(pads_bank0_mmio_start_addr), + } + } + + /// Map PL011 UART to GPIO pins 14 and 15. + pub fn map_pl011_uart(&mut self) { + // From RP1 Peripherals datasheet, Table 21: PADS_BANK0 GPIO0 Register + const BIT_PADS_OD: u32 = 1 << 7; // Output Disable + const BIT_PADS_IE: u32 = 1 << 6; // Input Enable + const BIT_PADS_PUE: u32 = 1 << 3; // Pull-Up Enable + + // From RP1 Peripherals datasheet, Table 4: GPIO function selection + const FUNCSEL_FIELD_LSB: u32 = 0; + const FUNCSEL_FIELD_MASK: u32 = 0x1F << FUNCSEL_FIELD_LSB; + const FUNC_UART0: u32 = 4; // UART0 is alternate function 4 + + // --- Configure GPIO 14 as UART0 TX --- + + // 1. Set pad electrical properties in PADS_BANK0. + // - Clear Output Disable (OD) bit to enable output. + let mut pads14_val = self.pads_bank0.GPIO14.get(); + pads14_val &= !BIT_PADS_OD; + self.pads_bank0.GPIO14.set(pads14_val); + + // 2. Set pin function in IO_BANK0. + let mut io14_val = self.io_bank0.GPIO14_CTRL.get(); + io14_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io14_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO14_CTRL.set(io14_val); + + // --- Configure GPIO 15 as UART0 RX --- + + // 1. Set pad electrical properties. + // - Set Input Enable (IE) bit. + // - Set Pull-Up Enable (PUE) as per datasheet recommendation. + let mut pads15_val = self.pads_bank0.GPIO15.get(); + pads15_val |= BIT_PADS_IE | BIT_PADS_PUE; + self.pads_bank0.GPIO15.set(pads15_val); + + // 2. Set pin function. + let mut io15_val = self.io_bank0.GPIO15_CTRL.get(); + io15_val &= !FUNCSEL_FIELD_MASK; // Clear the function field. + io15_val |= FUNC_UART0 << FUNCSEL_FIELD_LSB; // Set to UART0. + self.io_bank0.GPIO15_CTRL.set(io15_val); + } +} + +impl GPIO { + pub const COMPATIBLE: &'static str = "RP1 GPIO"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide correct MMIO start addresses for both register blocks. + pub const unsafe fn new( + io_bank0_mmio_start_addr: usize, + pads_bank0_mmio_start_addr: usize, + ) -> Self { + Self { + inner: NullLock::new(GPIOInner::new( + io_bank0_mmio_start_addr, + pads_bank0_mmio_start_addr, + )), + } + } + + /// Concurrency-safe version of `GPIOInner.map_pl011_uart()`. + pub fn map_pl011_uart(&self) { + self.inner.lock(|inner| inner.map_pl011_uart()) + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; + +impl driver::interface::DeviceDriver for GPIO { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } +} diff --git a/12_integrated_testing/kernel/src/bsp/raspberrypi5.rs b/12_integrated_testing/kernel/src/bsp/raspberrypi5.rs new file mode 100644 index 000000000..a4b2d3369 --- /dev/null +++ b/12_integrated_testing/kernel/src/bsp/raspberrypi5.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! Top-level BSP file for the Raspberry Pi 5. + +pub mod cpu; +pub mod driver; +pub mod memory; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Board identification. +pub fn board_name() -> &'static str { + "Raspberry Pi 5" +} diff --git a/12_integrated_testing/kernel/src/bsp/raspberrypi5/cpu.rs b/12_integrated_testing/kernel/src/bsp/raspberrypi5/cpu.rs new file mode 100644 index 000000000..1a22eafad --- /dev/null +++ b/12_integrated_testing/kernel/src/bsp/raspberrypi5/cpu.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter + +//! BSP Processor code for the Raspberry Pi 5. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Used by `arch` code to find the early boot core. +#[no_mangle] +#[link_section = ".text._start_arguments"] +pub static BOOT_CORE_ID: u64 = 0; \ No newline at end of file diff --git a/12_integrated_testing/kernel/src/bsp/raspberrypi5/driver.rs b/12_integrated_testing/kernel/src/bsp/raspberrypi5/driver.rs new file mode 100644 index 000000000..2bd7ef4f2 --- /dev/null +++ b/12_integrated_testing/kernel/src/bsp/raspberrypi5/driver.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP driver support for the Raspberry Pi 5. + +use super::memory::map::mmio; +use crate::{bsp::device_driver, console, driver as generic_driver}; +use core::sync::atomic::{AtomicBool, Ordering}; + +// Needed to call the .init() method on the UART driver. +#[cfg(feature = "test_build")] +use crate::driver::interface::DeviceDriver; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static PL011_UART: device_driver::PL011Uart = + unsafe { device_driver::PL011Uart::new(mmio::PL011_UART_START) }; + +static GPIO: device_driver::GPIO = + unsafe { device_driver::GPIO::new(mmio::GPIO_START, mmio::PADS_BANK0_START) }; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// This must be called only after successful init of the UART driver. +fn post_init_uart() -> Result<(), &'static str> { + console::register_console(&PL011_UART); + Ok(()) +} + +/// This must be called only after successful init of the GPIO driver. +fn post_init_gpio() -> Result<(), &'static str> { + GPIO.map_pl011_uart(); + Ok(()) +} + +fn driver_uart() -> Result<(), &'static str> { + let uart_descriptor = + generic_driver::DeviceDriverDescriptor::new(&PL011_UART, Some(post_init_uart)); + generic_driver::driver_manager().register_driver(uart_descriptor); + Ok(()) +} + +fn driver_gpio() -> Result<(), &'static str> { + let gpio_descriptor = generic_driver::DeviceDriverDescriptor::new(&GPIO, Some(post_init_gpio)); + generic_driver::driver_manager().register_driver(gpio_descriptor); + Ok(()) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Initialize the driver subsystem. +/// +/// # Safety +/// +/// See child function calls. +pub unsafe fn init() -> Result<(), &'static str> { + static INIT_DONE: AtomicBool = AtomicBool::new(false); + if INIT_DONE.load(Ordering::Relaxed) { + return Err("Init already done"); + } + + // On the Raspberry Pi 5, GPIO pins must be configured for their peripheral + // function (e.g., UART) before the peripheral itself is initialized. + driver_gpio()?; + driver_uart()?; + + INIT_DONE.store(true, Ordering::Relaxed); + Ok(()) +} + +/// Bring up the console for testing purposes. +/// +/// # Note +/// +/// This is named `qemu_bring_up_console` in the original RPi3/4 code because for QEMU, no real +/// hardware setup is needed. For the Pi 5, we are running on hardware, so this function +/// must perform the actual, minimal hardware initialization. +#[cfg(feature = "test_build")] +pub unsafe fn qemu_bring_up_console() { + // This sequence is critical for the RPi5. + // 1. Initialize GPIO and map UART pins. + GPIO.map_pl011_uart(); + + // 2. Initialize the UART driver. + PL011_UART.init().unwrap(); + + // 3. Register the console. + console::register_console(&PL011_UART); +} diff --git a/12_integrated_testing/kernel/src/bsp/raspberrypi5/kernel.ld b/12_integrated_testing/kernel/src/bsp/raspberrypi5/kernel.ld new file mode 100644 index 000000000..b967219b0 --- /dev/null +++ b/12_integrated_testing/kernel/src/bsp/raspberrypi5/kernel.ld @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2018-2025 Andre Richter + */ + +PAGE_SIZE = 64K; +PAGE_MASK = PAGE_SIZE - 1; + +__rpi_phys_dram_start_addr = 0; + +/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ +__rpi_phys_binary_load_addr = 0x80000; + + +ENTRY(__rpi_phys_binary_load_addr) + +/* Flags: + * 4 == R + * 5 == RX + * 6 == RW + * + * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. + * It doesn't mean all of them need actually be loaded. + */ +PHDRS +{ + segment_boot_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + . = __rpi_phys_dram_start_addr; + + /*********************************************************************************************** + * Boot Core Stack + ***********************************************************************************************/ + .boot_core_stack (NOLOAD) : + { + /* ^ */ + /* | stack */ + . += __rpi_phys_binary_load_addr; /* | growth */ + /* | direction */ + __boot_core_stack_end_exclusive = .; /* | */ + } :segment_boot_core_stack + + ASSERT((. & PAGE_MASK) == 0, "End of boot core stack is not page aligned") + + /*********************************************************************************************** + * Code + RO Data + Global Offset Table + ***********************************************************************************************/ + __code_start = .; + .text : + { + KEEP(*(.text._start)) + *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ + *(.text._start_rust) /* The Rust entry point */ + *(.text*) /* Everything else */ + } :segment_code + + .rodata : ALIGN(8) { *(.rodata*) } :segment_code + + . = ALIGN(PAGE_SIZE); + __code_end_exclusive = .; + + /*********************************************************************************************** + * Data + BSS + ***********************************************************************************************/ + .data : { *(.data*) } :segment_data + + /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end_exclusive = .; + } :segment_data + + /*********************************************************************************************** + * Misc + ***********************************************************************************************/ + .got : { *(.got*) } + ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") + + /DISCARD/ : { *(.comment*) } +} diff --git a/12_integrated_testing/kernel/src/bsp/raspberrypi5/memory.rs b/12_integrated_testing/kernel/src/bsp/raspberrypi5/memory.rs new file mode 100644 index 000000000..a022e23f5 --- /dev/null +++ b/12_integrated_testing/kernel/src/bsp/raspberrypi5/memory.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP Memory Management for the Raspberry Pi 5. +//! +//! The physical memory layout. +//! +//! The Raspberry's firmware copies the kernel binary to 0x8_0000. The preceding region will be used +//! as the boot core's stack. + +pub mod mmu; + +use core::cell::UnsafeCell; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// Symbols from the linker script. +extern "Rust" { + static __code_start: UnsafeCell<()>; + static __code_end_exclusive: UnsafeCell<()>; +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The board's physical memory map. +#[rustfmt::skip] +pub(super) mod map { + /// The inclusive end address of the memory map. + /// + /// End address + 1 must be a power of two. + /// + /// We define the address space to be 128 GiB. This is the smallest power of two that + /// encapsulates the RP1's MMIO addresses. + pub const END_INCLUSIVE: usize = (128 * 1024 * 1024 * 1024) - 1; + pub const RP1_PCI_ECAM_BASE: usize = 0x1f00000000; + + /// Physical devices. + pub mod mmio { + use super::*; + + // See `RP1 Peripherals` datasheet, table 5 and 22. + pub const START: usize = RP1_PCI_ECAM_BASE; + pub const GPIO_START: usize = START + 0xd0000; + pub const PADS_BANK0_START: usize = START + 0xf0000; + pub const PL011_UART_START: usize = START + 0x30000; + + // Define an end for the MMIO region. The highest peripheral address in the datasheet + // is around 0x1f00400000, so we'll use a slightly larger, rounded value. + pub const END_INCLUSIVE: usize = 0x1f0fffffff; + } +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// Start page address of the code segment. +/// +/// # Safety +/// +/// - Value is provided by the linker script and must be trusted as-is. +#[inline(always)] +fn code_start() -> usize { + unsafe { __code_start.get() as usize } +} + +/// Exclusive end page address of the code segment. +/// # Safety +/// +/// - Value is provided by the linker script and must be trusted as-is. +#[inline(always)] +fn code_end_exclusive() -> usize { + unsafe { __code_end_exclusive.get() as usize } +} diff --git a/12_integrated_testing/kernel/src/bsp/raspberrypi5/memory/mmu.rs b/12_integrated_testing/kernel/src/bsp/raspberrypi5/memory/mmu.rs new file mode 100644 index 000000000..d991147e8 --- /dev/null +++ b/12_integrated_testing/kernel/src/bsp/raspberrypi5/memory/mmu.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2025 Andre Richter +// Copyright (c) 2025 Devansh Lodha + +//! BSP Memory Management Unit for the Raspberry Pi 5. + +use super::map as memory_map; +use crate::memory::mmu::*; +use core::ops::RangeInclusive; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The kernel's address space defined by this BSP. +pub type KernelAddrSpace = AddressSpace<{ memory_map::END_INCLUSIVE + 1 }>; + +const NUM_MEM_RANGES: usize = 2; + +/// The virtual memory layout. +/// +/// The layout must contain only special ranges, aka anything that is _not_ normal cacheable DRAM. +/// It is agnostic of the paging granularity that the architecture's MMU will use. +pub static LAYOUT: KernelVirtualLayout = KernelVirtualLayout::new( + memory_map::END_INCLUSIVE, + [ + TranslationDescriptor { + name: "Kernel code and RO data", + virtual_range: code_range_inclusive, + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + TranslationDescriptor { + name: "Device MMIO", + virtual_range: mmio_range_inclusive, + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + ], +); + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +fn code_range_inclusive() -> RangeInclusive { + // Notice the subtraction to turn the exclusive end into an inclusive end. + #[allow(clippy::range_minus_one)] + RangeInclusive::new(super::code_start(), super::code_end_exclusive() - 1) +} + +fn mmio_range_inclusive() -> RangeInclusive { + RangeInclusive::new(memory_map::mmio::START, memory_map::mmio::END_INCLUSIVE) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the virtual memory layout. +pub fn virt_mem_layout() -> &'static KernelVirtualLayout { + &LAYOUT +} diff --git a/12_integrated_testing/kernel/tests/02_exception_sync_page_fault.rs b/12_integrated_testing/kernel/tests/02_exception_sync_page_fault.rs index bf5b7d711..48b951f79 100644 --- a/12_integrated_testing/kernel/tests/02_exception_sync_page_fault.rs +++ b/12_integrated_testing/kernel/tests/02_exception_sync_page_fault.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // // Copyright (c) 2019-2023 Andre Richter +// Copyright (c) 2025 Devansh Lodha //! Page faults must result in synchronous exceptions. @@ -34,9 +35,21 @@ unsafe fn kernel_init() -> ! { cpu::qemu_exit_failure() } - info!("Writing beyond mapped area to address 9 GiB..."); - let big_addr: u64 = 9 * 1024 * 1024 * 1024; - core::ptr::read_volatile(big_addr as *mut u64); + #[cfg(not(feature = "bsp_rpi5"))] + { + info!("Writing beyond mapped area to address 9 GiB..."); + let big_addr: u64 = 9 * 1024 * 1024 * 1024; + core::ptr::read_volatile(big_addr as *mut u64); + } + + #[cfg(feature = "bsp_rpi5")] + { + // For RPi5, the address space is 128 GiB. We must pick an address outside this range + // to guarantee a translation fault. Let's use 129 GiB. + info!("Writing beyond mapped area to address 129 GiB..."); + let big_addr: u64 = 129 * 1024 * 1024 * 1024; + core::ptr::read_volatile(big_addr as *mut u64); + } // If execution reaches here, the memory access above did not cause a page fault exception. cpu::qemu_exit_failure() diff --git a/X2_pi5_jtag_halt_stub/Makefile b/X2_pi5_jtag_halt_stub/Makefile new file mode 100644 index 000000000..2329f0d28 --- /dev/null +++ b/X2_pi5_jtag_halt_stub/Makefile @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2025 Devansh Lodha + +# This Makefile is designed to be called from the parent directory's +# Docker environment, which provides the aarch64-elf toolchain. + +CROSS_COMPILE ?= aarch64-elf- +OBJCOPY = $(CROSS_COMPILE)objcopy +AS = $(CROSS_COMPILE)as +LD = $(CROSS_COMPILE)ld + +IMG_NAME = halt_stub.img +ELF_NAME = halt_stub.elf +OBJ_NAME = start.o + +.PHONY: all clean + +all: $(IMG_NAME) + +$(IMG_NAME): $(ELF_NAME) + @echo "--- Building Halt Stub Image ---" + @$(OBJCOPY) -O binary $(ELF_NAME) $(IMG_NAME) + +$(ELF_NAME): $(OBJ_NAME) + @$(LD) -T linker.ld -o $(ELF_NAME) $(OBJ_NAME) + +$(OBJ_NAME): start.s + @$(AS) -o $(OBJ_NAME) start.s + +clean: + @rm -f $(IMG_NAME) $(ELF_NAME) $(OBJ_NAME) \ No newline at end of file diff --git a/X2_pi5_jtag_halt_stub/halt_stub.img b/X2_pi5_jtag_halt_stub/halt_stub.img new file mode 100755 index 000000000..2276dd4d5 --- /dev/null +++ b/X2_pi5_jtag_halt_stub/halt_stub.img @@ -0,0 +1 @@ +_ Õÿÿÿ \ No newline at end of file diff --git a/X2_pi5_jtag_halt_stub/linker.ld b/X2_pi5_jtag_halt_stub/linker.ld new file mode 100644 index 000000000..8eee75306 --- /dev/null +++ b/X2_pi5_jtag_halt_stub/linker.ld @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2025 Devansh Lodha + */ + +ENTRY(_start) +SECTIONS +{ + . = 0x80000; + .text : { *(.text.boot) } +} \ No newline at end of file diff --git a/X2_pi5_jtag_halt_stub/start.s b/X2_pi5_jtag_halt_stub/start.s new file mode 100644 index 000000000..e1a325277 --- /dev/null +++ b/X2_pi5_jtag_halt_stub/start.s @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2025 Devansh Lodha + */ + +.section ".text.boot" +.global _start +_start: + wfe // Wait for event (low-power idle) + b _start \ No newline at end of file diff --git a/debug/pi5/cmsis-dap.cfg b/debug/pi5/cmsis-dap.cfg new file mode 100644 index 000000000..1dc3d5151 --- /dev/null +++ b/debug/pi5/cmsis-dap.cfg @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2025 Devansh Lodha + +adapter driver cmsis-dap \ No newline at end of file diff --git a/debug/pi5/gdb-init.txt b/debug/pi5/gdb-init.txt new file mode 100644 index 000000000..0e22f76da --- /dev/null +++ b/debug/pi5/gdb-init.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2025 Devansh Lodha + +# 1. Connect to the OpenOCD server. +target remote localhost:3333 + +# 2. Reset the Pi and halt it at the very beginning. +monitor reset init + +# 3. Load your program's symbols and code into the Pi's RAM. +load + +# 4. Set the Program Counter to the start of our code. +set $pc = 0x80000 \ No newline at end of file diff --git a/debug/pi5/raspberrypi5.cfg b/debug/pi5/raspberrypi5.cfg new file mode 100644 index 000000000..6f9170fea --- /dev/null +++ b/debug/pi5/raspberrypi5.cfg @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2025 Devansh Lodha + +transport select swd +adapter speed 4000 +reset_config srst_push_pull srst_only +set _CHIPNAME bcm2712 +set _DAP_TAPID 0x4ba00477 +set _CHIPCORES 4 +swd newdap $_CHIPNAME cpu -expected-id $_DAP_TAPID +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu +set _DBGBASE {0x80010000 0x80110000 0x80210000 0x80310000} +set _CTIBASE {0x80020000 0x80120000 0x80220000 0x80320000} +for { set _core 0 } { $_core < $_CHIPCORES } { incr _core } { + set _CTINAME $_CHIPNAME.cti$_core + set _TARGETNAME $_CHIPNAME.cpu$_core + cti create $_CTINAME -dap $_CHIPNAME.dap -ap-num 0 -baseaddr [lindex $_CTIBASE $_core] + target create $_TARGETNAME aarch64 -dap $_CHIPNAME.dap -coreid $_core -dbgbase [lindex $_DBGBASE $_core] -cti $_CTINAME + $_TARGETNAME configure -event gdb-attach { halt } +} +targets $_CHIPNAME.cpu0 +init +$_CHIPNAME.cpu0 configure -event reset-init { halt } \ No newline at end of file