Skip to content

Commit b69fca7

Browse files
committed
feat(11): Add initial BSP structure for Raspberry Pi 5
This commit ports the foundational Raspberry Pi 5 support from Tutorial 10 to Tutorial 11. It includes: - The 'bsp_rpi5' feature in Cargo.toml and a version bump to 0.11.0. - The Pi 5 BSP directory structure with all necessary files. - The RP1 GPIO driver and conditional compilation logic. - Makefile adaptations for the Pi 5 JTAG debugging workflow. - The newline translation fix in the shared PL011 UART driver.
1 parent b267e52 commit b69fca7

File tree

13 files changed

+646
-75
lines changed

13 files changed

+646
-75
lines changed

11_exceptions_part1_groundwork/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ lto = true
1111
default = []
1212
bsp_rpi3 = ["tock-registers"]
1313
bsp_rpi4 = ["tock-registers"]
14+
bsp_rpi5 = ["tock-registers"]
1415

1516
[[bin]]
1617
name = "kernel"

11_exceptions_part1_groundwork/Makefile

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## SPDX-License-Identifier: MIT OR Apache-2.0
22
##
3-
## Copyright (c) 2018-2023 Andre Richter <andre.o.richter@gmail.com>
3+
## Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
4+
## Copyright (c) 2025 Devansh Lodha <devanshlodha12@gmail.com>
45

56
include ../common/docker.mk
67
include ../common/format.mk
@@ -10,8 +11,8 @@ include ../common/operating_system.mk
1011
## Optional, user-provided configuration values
1112
##--------------------------------------------------------------------------------------------------
1213

13-
# Default to the RPi3.
14-
BSP ?= rpi3
14+
# Default to the RPi5.
15+
BSP ?= rpi5
1516

1617
# Default to a serial device name that is common in Linux.
1718
DEV_SERIAL ?= /dev/ttyUSB0
@@ -49,6 +50,20 @@ else ifeq ($(BSP),rpi4)
4950
JTAG_BOOT_IMAGE = ../X1_JTAG_boot/jtag_boot_rpi4.img
5051
LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi
5152
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
53+
else ifeq ($(BSP),rpi5)
54+
TARGET = aarch64-unknown-none-softfloat
55+
KERNEL_BIN = kernel8.img
56+
QEMU_BINARY = qemu-system-aarch64
57+
QEMU_MACHINE_TYPE =
58+
QEMU_RELEASE_ARGS = -serial stdio -display none
59+
OBJDUMP_BINARY = aarch64-none-elf-objdump
60+
NM_BINARY = aarch64-none-elf-nm
61+
READELF_BINARY = aarch64-none-elf-readelf
62+
GDB_BINARY = aarch64-elf-gdb
63+
OPENOCD_ARG = -f ../debug/pi5/cmsis-dap.cfg -f ../debug/pi5/raspberrypi5.cfg
64+
GDB_INIT_FILE = ../debug/pi5/gdb-init.txt
65+
LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi5
66+
RUSTC_MISC_ARGS = -C target-cpu=cortex-a76
5267
endif
5368

5469
# Export for build.rs.
@@ -66,7 +81,7 @@ LAST_BUILD_CONFIG = target/$(BSP).build_config
6681
KERNEL_ELF = target/$(TARGET)/release/kernel
6782
# This parses cargo's dep-info file.
6883
# https://doc.rust-lang.org/cargo/guide/build-cache.html#dep-info-files
69-
KERNEL_ELF_DEPS = $(filter-out %: ,$(file < $(KERNEL_ELF).d)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG)
84+
KERNEL_ELF_DEPS = $(filter-out %: ,$(shell cat $(KERNEL_ELF).d 2>/dev/null)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG)
7085

7186

7287

@@ -86,10 +101,10 @@ COMPILER_ARGS = --target=$(TARGET) \
86101
$(FEATURES) \
87102
--release
88103

89-
RUSTC_CMD = cargo rustc $(COMPILER_ARGS)
90-
DOC_CMD = cargo doc $(COMPILER_ARGS)
91-
CLIPPY_CMD = cargo clippy $(COMPILER_ARGS)
92-
OBJCOPY_CMD = rust-objcopy \
104+
BASE_RUSTC_CMD = cargo rustc $(COMPILER_ARGS)
105+
BASE_DOC_CMD = cargo doc $(COMPILER_ARGS)
106+
BASE_CLIPPY_CMD = cargo clippy $(COMPILER_ARGS)
107+
BASE_OBJCOPY_CMD = rust-objcopy \
93108
--strip-all \
94109
-O binary
95110

@@ -98,38 +113,38 @@ EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb
98113
EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
99114

100115
##------------------------------------------------------------------------------
101-
## Dockerization
116+
## OS-dependent commands and Dockerization
102117
##------------------------------------------------------------------------------
103-
DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial
104-
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i
105-
DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common
106-
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot
107-
DOCKER_ARG_DEV = --privileged -v /dev:/dev
108-
DOCKER_ARG_NET = --network host
109-
110-
# DOCKER_IMAGE defined in include file (see top of this file).
111-
DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
112-
DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE)
113-
DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE)
114-
DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
115-
116-
# Dockerize commands, which require USB device passthrough, only on Linux.
117-
ifeq ($(shell uname -s),Linux)
118-
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV)
118+
DOCKER_CMD_PREFIX = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial
119+
DOCKER_CMD_INTERACT = $(DOCKER_CMD_PREFIX) -i
119120

120-
DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE)
121-
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_IMAGE)
122-
DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
123-
else
124-
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
125-
endif
121+
# DOCKER_IMAGE is defined in ../common/docker.mk
122+
DOCKER_TOOLS_WRAPPER = $(DOCKER_CMD_PREFIX) $(DOCKER_IMAGE)
126123

124+
ifeq ($(shell uname -s),Linux)
125+
OPENOCD_CMD = $(DOCKER_CMD_INTERACT) --privileged -v /dev:/dev --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) openocd
126+
GDB_CMD = $(DOCKER_CMD_INTERACT) --network host -v $(shell pwd)/../debug:/work/debug $(DOCKER_IMAGE) gdb-multiarch
127+
STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE)
128+
else ifeq ($(shell uname -s),Darwin) # macOS - Use local tools for hardware interaction
129+
OPENOCD_CMD = openocd # Assumes OpenOCD is installed locally
130+
GDB_CMD = $(GDB_BINARY) # Assumes aarch64-elf-gdb is in PATH
131+
STUB_MAKE_CMD = $(DOCKER_TOOLS_WRAPPER) -v $(shell pwd)/../X2_pi5_jtag_halt_stub:/work/X2_pi5_jtag_halt_stub $(MAKE)
132+
else # Fallback for other OSes
133+
OPENOCD_CMD = echo "OpenOCD on this OS is not supported by this Makefile."; false
134+
GDB_CMD = echo "GDB on this OS is not supported by this Makefile."; false
135+
endif
127136

137+
# These commands are always local, as per the repository's design
138+
RUSTC_CMD = $(BASE_RUSTC_CMD)
139+
DOC_CMD = $(BASE_DOC_CMD)
140+
CLIPPY_CMD = $(BASE_CLIPPY_CMD)
141+
OBJCOPY_CMD = $(BASE_OBJCOPY_CMD)
142+
CLEAN_CMD = cargo clean
128143

129144
##--------------------------------------------------------------------------------------------------
130145
## Targets
131146
##--------------------------------------------------------------------------------------------------
132-
.PHONY: all doc qemu chainboot clippy clean readelf objdump nm check
147+
.PHONY: all doc qemu clippy clean readelf objdump nm check
133148

134149
all: $(KERNEL_BIN)
135150

@@ -178,16 +193,10 @@ else # QEMU is supported.
178193

179194
qemu: $(KERNEL_BIN)
180195
$(call color_header, "Launching QEMU")
181-
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
196+
@$(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
182197

183198
endif
184199

185-
##------------------------------------------------------------------------------
186-
## Push the kernel to the real HW target
187-
##------------------------------------------------------------------------------
188-
chainboot: $(KERNEL_BIN)
189-
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
190-
191200
##------------------------------------------------------------------------------
192201
## Run clippy
193202
##------------------------------------------------------------------------------
@@ -198,21 +207,22 @@ clippy:
198207
## Clean
199208
##------------------------------------------------------------------------------
200209
clean:
201-
rm -rf target $(KERNEL_BIN)
210+
@$(CLEAN_CMD)
211+
@rm -f kernel8.img
202212

203213
##------------------------------------------------------------------------------
204214
## Run readelf
205215
##------------------------------------------------------------------------------
206216
readelf: $(KERNEL_ELF)
207217
$(call color_header, "Launching readelf")
208-
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
218+
@$(DOCKER_TOOLS_WRAPPER) $(READELF_BINARY) --headers $(KERNEL_ELF)
209219

210220
##------------------------------------------------------------------------------
211221
## Run objdump
212222
##------------------------------------------------------------------------------
213223
objdump: $(KERNEL_ELF)
214224
$(call color_header, "Launching objdump")
215-
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
225+
@$(DOCKER_TOOLS_WRAPPER) $(OBJDUMP_BINARY) --disassemble --demangle \
216226
--section .text \
217227
--section .rodata \
218228
$(KERNEL_ELF) | rustfilt
@@ -222,27 +232,31 @@ objdump: $(KERNEL_ELF)
222232
##------------------------------------------------------------------------------
223233
nm: $(KERNEL_ELF)
224234
$(call color_header, "Launching nm")
225-
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
235+
@$(DOCKER_TOOLS_WRAPPER) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
226236

227237

228238

229239
##--------------------------------------------------------------------------------------------------
230240
## Debugging targets
231241
##--------------------------------------------------------------------------------------------------
232-
.PHONY: jtagboot openocd gdb gdb-opt0
242+
.PHONY: sd_image openocd gdb gdb-opt0
233243

234244
##------------------------------------------------------------------------------
235-
## Push the JTAG boot image to the real HW target
245+
## Build the JTAG halt stub and copy it for SD card use
236246
##------------------------------------------------------------------------------
237-
jtagboot:
238-
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE)
247+
sd_image:
248+
$(call color_header, "Building JTAG halt stub for SD card")
249+
@$(STUB_MAKE_CMD) -C ../X2_pi5_jtag_halt_stub
250+
@cp ../X2_pi5_jtag_halt_stub/halt_stub.img ./kernel8.img
251+
$(call color_progress_prefix, "Name")
252+
@echo "kernel8.img (Halt Stub)"
239253

240254
##------------------------------------------------------------------------------
241255
## Start OpenOCD session
242256
##------------------------------------------------------------------------------
243257
openocd:
244258
$(call color_header, "Launching OpenOCD")
245-
@$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG)
259+
@$(OPENOCD_CMD) $(OPENOCD_ARG)
246260

247261
##------------------------------------------------------------------------------
248262
## Start GDB session
@@ -251,7 +265,11 @@ gdb: RUSTC_MISC_ARGS += -C debuginfo=2
251265
gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0
252266
gdb gdb-opt0: $(KERNEL_ELF)
253267
$(call color_header, "Launching GDB")
254-
@$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF)
268+
ifeq ($(BSP),rpi5)
269+
@$(GDB_CMD) -q -x $(GDB_INIT_FILE) $(KERNEL_ELF)
270+
else
271+
@$(GDB_CMD) -q $(KERNEL_ELF)
272+
endif
255273

256274

257275

11_exceptions_part1_groundwork/src/bsp.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22
//
3-
// Copyright (c) 2018-2023 Andre Richter <andre.o.richter@gmail.com>
3+
// Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
44

55
//! Conditional reexporting of Board Support Packages.
66
@@ -11,3 +11,9 @@ mod raspberrypi;
1111

1212
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
1313
pub use raspberrypi::*;
14+
15+
#[cfg(feature = "bsp_rpi5")]
16+
mod raspberrypi5;
17+
18+
#[cfg(feature = "bsp_rpi5")]
19+
pub use raspberrypi5::*;
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22
//
3-
// Copyright (c) 2018-2023 Andre Richter <andre.o.richter@gmail.com>
3+
// Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
44

55
//! Device driver.
66
7-
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
7+
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4", feature = "bsp_rpi5"))]
88
mod bcm;
9+
#[cfg(feature = "bsp_rpi5")]
10+
mod rp1_gpio;
11+
912
mod common;
1013

14+
// For RPi3 and RPi4, re-export everything from the `bcm` module, which includes
15+
// both the GPIO and UART drivers.
1116
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
1217
pub use bcm::*;
18+
19+
// For RPi5, re-export only the drivers it uses: the common PL011Uart from the bcm
20+
// module and its specific GPIO driver from the rp1_gpio module.
21+
#[cfg(feature = "bsp_rpi5")]
22+
pub use bcm::PL011Uart;
23+
#[cfg(feature = "bsp_rpi5")]
24+
pub use rp1_gpio::GPIO;
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22
//
3-
// Copyright (c) 2018-2023 Andre Richter <andre.o.richter@gmail.com>
3+
// Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
44

55
//! BCM driver top level.
66
7+
// The bcm2xxx_gpio driver is only for RPi3 and RPi4.
8+
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
79
mod bcm2xxx_gpio;
10+
11+
// The PL011 UART is used by all supported RPi versions.
812
mod bcm2xxx_pl011_uart;
913

14+
// Re-export the GPIO driver only for RPi3 and RPi4.
15+
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
1016
pub use bcm2xxx_gpio::*;
17+
18+
// Re-export the UART driver for all.
1119
pub use bcm2xxx_pl011_uart::*;

11_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22
//
3-
// Copyright (c) 2018-2023 Andre Richter <andre.o.richter@gmail.com>
3+
// Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
44

55
//! PL011 UART driver.
66
//!
@@ -201,22 +201,6 @@ impl PL011UartInner {
201201
}
202202

203203
/// Set up baud rate and characteristics.
204-
///
205-
/// This results in 8N1 and 921_600 baud.
206-
///
207-
/// The calculation for the BRD is (we set the clock to 48 MHz in config.txt):
208-
/// `(48_000_000 / 16) / 921_600 = 3.2552083`.
209-
///
210-
/// This means the integer part is `3` and goes into the `IBRD`.
211-
/// The fractional part is `0.2552083`.
212-
///
213-
/// `FBRD` calculation according to the PL011 Technical Reference Manual:
214-
/// `INTEGER((0.2552083 * 64) + 0.5) = 16`.
215-
///
216-
/// Therefore, the generated baud rate divider is: `3 + 16/64 = 3.25`. Which results in a
217-
/// genrated baud rate of `48_000_000 / (16 * 3.25) = 923_077`.
218-
///
219-
/// Error = `((923_077 - 921_600) / 921_600) * 100 = 0.16%`.
220204
pub fn init(&mut self) {
221205
// Execution can arrive here while there are still characters queued in the TX FIFO and
222206
// actively being sent out by the UART hardware. If the UART is turned off in this case,
@@ -240,9 +224,26 @@ impl PL011UartInner {
240224
// updated on a single write strobe generated by a LCR_H write. So, to internally update the
241225
// contents of IBRD or FBRD, a LCR_H write must always be performed at the end.
242226
//
243-
// Set the baud rate, 8N1 and FIFO enabled.
244-
self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3));
245-
self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16));
227+
// Set the baud rate.
228+
#[cfg(feature = "bsp_rpi5")]
229+
{
230+
// For Pi 5, use the known-good 115200 baud configuration.
231+
// This is proven to work with the default 48MHz UART clock.
232+
// BAUDDIV = 48,000,000 / (16 * 115200) = 26.0416...
233+
// IBRD = 26, FBRD = 3
234+
self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26));
235+
self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3));
236+
}
237+
#[cfg(not(feature = "bsp_rpi5"))]
238+
{
239+
// Original configuration for RPi3/4 at 921600 baud.
240+
// BAUDDIV = 48_000,000 / (16 * 921_600) = 3.2552...
241+
// IBRD = 3, FBRD = 16
242+
self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3));
243+
self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16));
244+
}
245+
246+
// Set 8N1 and FIFO enabled.
246247
self.registers
247248
.LCR_H
248249
.write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled);
@@ -255,7 +256,18 @@ impl PL011UartInner {
255256

256257
/// Send a character.
257258
fn write_char(&mut self, c: char) {
258-
// Spin while TX FIFO full is set, waiting for an empty slot.
259+
// If the character is a newline, prepend a carriage return.
260+
if c == '\n' {
261+
// Spin while TX FIFO full is set.
262+
while self.registers.FR.matches_all(FR::TXFF::SET) {
263+
cpu::nop();
264+
}
265+
// Write the carriage return character.
266+
self.registers.DR.set('\r' as u32);
267+
}
268+
269+
// Now, send the original character.
270+
// Spin while TX FIFO full is set.
259271
while self.registers.FR.matches_all(FR::TXFF::SET) {
260272
cpu::nop();
261273
}
@@ -292,7 +304,8 @@ impl PL011UartInner {
292304
// Read one character.
293305
let mut ret = self.registers.DR.get() as u8 as char;
294306

295-
// Convert carrige return to newline.
307+
// Convert carriage return to newline. This is a standard behavior
308+
// for console input.
296309
if ret == '\r' {
297310
ret = '\n'
298311
}
@@ -316,6 +329,7 @@ impl PL011UartInner {
316329
impl fmt::Write for PL011UartInner {
317330
fn write_str(&mut self, s: &str) -> fmt::Result {
318331
for c in s.chars() {
332+
// The `write_char` function now correctly handles `\n` -> `\r\n` conversion.
319333
self.write_char(c);
320334
}
321335

0 commit comments

Comments
 (0)