Skip to content

Commit b267e52

Browse files
committed
feat(10): Enable MMU support for Raspberry Pi 5
Enables virtual memory for the Raspberry Pi 5 target by implementing the required BSP-specific memory layout. This resolves a silent hang on boot that occurs immediately after the MMU is enabled. The root cause was a Translation Fault when the kernel tried to access peripheral drivers in high memory (0x1f...). The initial 4 GiB virtual address space was too small, so no page table entries were being generated for the RP1's MMIO region. This fix includes: 1. In 'memory.rs', the virtual address space is expanded to 128 GiB, the smallest power-of-two that encompasses the RP1's MMIO addresses. 2. A new 'memory/mmu.rs' file is added to define the 'KernelVirtualLayout'. This layout correctly identity-maps the RP1 peripheral range as Device memory and ports the tutorial's educational UART remapping feature. 3. The 'kernel.ld' linker script is updated to align the code section to a 64 KiB page boundary and export the symbols required by the MMU layout code.
1 parent d59aaca commit b267e52

File tree

3 files changed

+262
-0
lines changed

3 files changed

+262
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* SPDX-License-Identifier: MIT OR Apache-2.0
2+
*
3+
* Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
4+
*/
5+
6+
PAGE_SIZE = 64K;
7+
PAGE_MASK = PAGE_SIZE - 1;
8+
9+
__rpi_phys_dram_start_addr = 0;
10+
11+
/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */
12+
__rpi_phys_binary_load_addr = 0x80000;
13+
14+
15+
ENTRY(__rpi_phys_binary_load_addr)
16+
17+
/* Flags:
18+
* 4 == R
19+
* 5 == RX
20+
* 6 == RW
21+
*
22+
* Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses.
23+
* It doesn't mean all of them need actually be loaded.
24+
*/
25+
PHDRS
26+
{
27+
segment_boot_core_stack PT_LOAD FLAGS(6);
28+
segment_code PT_LOAD FLAGS(5);
29+
segment_data PT_LOAD FLAGS(6);
30+
}
31+
32+
SECTIONS
33+
{
34+
. = __rpi_phys_dram_start_addr;
35+
36+
/***********************************************************************************************
37+
* Boot Core Stack
38+
***********************************************************************************************/
39+
.boot_core_stack (NOLOAD) :
40+
{
41+
/* ^ */
42+
/* | stack */
43+
. += __rpi_phys_binary_load_addr; /* | growth */
44+
/* | direction */
45+
__boot_core_stack_end_exclusive = .; /* | */
46+
} :segment_boot_core_stack
47+
48+
ASSERT((. & PAGE_MASK) == 0, "End of boot core stack is not page aligned")
49+
50+
/***********************************************************************************************
51+
* Code + RO Data + Global Offset Table
52+
***********************************************************************************************/
53+
__code_start = .;
54+
.text :
55+
{
56+
KEEP(*(.text._start))
57+
*(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */
58+
*(.text._start_rust) /* The Rust entry point */
59+
*(.text*) /* Everything else */
60+
} :segment_code
61+
62+
.rodata : ALIGN(8) { *(.rodata*) } :segment_code
63+
64+
. = ALIGN(PAGE_SIZE);
65+
__code_end_exclusive = .;
66+
67+
/***********************************************************************************************
68+
* Data + BSS
69+
***********************************************************************************************/
70+
.data : { *(.data*) } :segment_data
71+
72+
/* Section is zeroed in pairs of u64. Align start and end to 16 bytes */
73+
.bss (NOLOAD) : ALIGN(16)
74+
{
75+
__bss_start = .;
76+
*(.bss*);
77+
. = ALIGN(16);
78+
__bss_end_exclusive = .;
79+
} :segment_data
80+
81+
/***********************************************************************************************
82+
* Misc
83+
***********************************************************************************************/
84+
.got : { *(.got*) }
85+
ASSERT(SIZEOF(.got) == 0, "Relocation support not expected")
86+
87+
/DISCARD/ : { *(.comment*) }
88+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
//
3+
// Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
4+
// Copyright (c) 2025 Devansh Lodha <devanshlodha12@gmail.com>
5+
6+
//! BSP Memory Management for the Raspberry Pi 5.
7+
//!
8+
//! The physical memory layout.
9+
//!
10+
//! The Raspberry's firmware copies the kernel binary to 0x8_0000. The preceding region will be used
11+
//! as the boot core's stack.
12+
13+
pub mod mmu;
14+
15+
use core::cell::UnsafeCell;
16+
17+
//--------------------------------------------------------------------------------------------------
18+
// Private Definitions
19+
//--------------------------------------------------------------------------------------------------
20+
21+
// Symbols from the linker script.
22+
extern "Rust" {
23+
static __code_start: UnsafeCell<()>;
24+
static __code_end_exclusive: UnsafeCell<()>;
25+
}
26+
27+
//--------------------------------------------------------------------------------------------------
28+
// Public Definitions
29+
//--------------------------------------------------------------------------------------------------
30+
31+
/// The board's physical memory map.
32+
#[rustfmt::skip]
33+
pub(super) mod map {
34+
/// The inclusive end address of the memory map.
35+
///
36+
/// End address + 1 must be a power of two.
37+
///
38+
/// We define the address space to be 128 GiB. This is the smallest power of two that
39+
/// encapsulates the RP1's MMIO addresses.
40+
pub const END_INCLUSIVE: usize = (128 * 1024 * 1024 * 1024) - 1;
41+
pub const RP1_PCI_ECAM_BASE: usize = 0x1f00000000;
42+
43+
/// Physical devices.
44+
pub mod mmio {
45+
use super::*;
46+
47+
// See `RP1 Peripherals` datasheet, table 5 and 22.
48+
pub const START: usize = RP1_PCI_ECAM_BASE;
49+
pub const GPIO_START: usize = START + 0xd0000;
50+
pub const PADS_BANK0_START: usize = START + 0xf0000;
51+
pub const PL011_UART_START: usize = START + 0x30000;
52+
53+
// Define an end for the MMIO region. The highest peripheral address in the datasheet
54+
// is around 0x1f00400000, so we'll use a slightly larger, rounded value.
55+
pub const END_INCLUSIVE: usize = 0x1f0fffffff;
56+
}
57+
}
58+
59+
//--------------------------------------------------------------------------------------------------
60+
// Private Code
61+
//--------------------------------------------------------------------------------------------------
62+
63+
/// Start page address of the code segment.
64+
///
65+
/// # Safety
66+
///
67+
/// - Value is provided by the linker script and must be trusted as-is.
68+
#[inline(always)]
69+
fn code_start() -> usize {
70+
unsafe { __code_start.get() as usize }
71+
}
72+
73+
/// Exclusive end page address of the code segment.
74+
/// # Safety
75+
///
76+
/// - Value is provided by the linker script and must be trusted as-is.
77+
#[inline(always)]
78+
fn code_end_exclusive() -> usize {
79+
unsafe { __code_end_exclusive.get() as usize }
80+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
//
3+
// Copyright (c) 2018-2025 Andre Richter <andre.o.richter@gmail.com>
4+
// Copyright (c) 2025 Devansh Lodha <devanshlodha12@gmail.com>
5+
6+
//! BSP Memory Management Unit for the Raspberry Pi 5.
7+
8+
use super::map as memory_map;
9+
use crate::memory::mmu::*;
10+
use core::ops::RangeInclusive;
11+
12+
//--------------------------------------------------------------------------------------------------
13+
// Public Definitions
14+
//--------------------------------------------------------------------------------------------------
15+
16+
/// The kernel's address space defined by this BSP.
17+
pub type KernelAddrSpace = AddressSpace<{ memory_map::END_INCLUSIVE + 1 }>;
18+
19+
const NUM_MEM_RANGES: usize = 3;
20+
21+
/// The virtual memory layout.
22+
///
23+
/// The layout must contain only special ranges, aka anything that is _not_ normal cacheable DRAM.
24+
/// It is agnostic of the paging granularity that the architecture's MMU will use.
25+
pub static LAYOUT: KernelVirtualLayout<NUM_MEM_RANGES> = KernelVirtualLayout::new(
26+
memory_map::END_INCLUSIVE,
27+
[
28+
TranslationDescriptor {
29+
name: "Kernel code and RO data",
30+
virtual_range: code_range_inclusive,
31+
physical_range_translation: Translation::Identity,
32+
attribute_fields: AttributeFields {
33+
mem_attributes: MemAttributes::CacheableDRAM,
34+
acc_perms: AccessPermissions::ReadOnly,
35+
execute_never: false,
36+
},
37+
},
38+
TranslationDescriptor {
39+
name: "Remapped Device MMIO (UART)",
40+
virtual_range: remapped_mmio_range_inclusive,
41+
// The chosen virtual address for the remapped UART is 0x1FFF_1000.
42+
// The virtual range starts at 0x1FFF_0000.
43+
// Therefore, the offset that the physical memory block must be based at is:
44+
// PL011_UART_START - 0x1000
45+
physical_range_translation: Translation::Offset(
46+
memory_map::mmio::PL011_UART_START - 0x1000,
47+
),
48+
attribute_fields: AttributeFields {
49+
mem_attributes: MemAttributes::Device,
50+
acc_perms: AccessPermissions::ReadWrite,
51+
execute_never: true,
52+
},
53+
},
54+
TranslationDescriptor {
55+
name: "Device MMIO",
56+
virtual_range: mmio_range_inclusive,
57+
physical_range_translation: Translation::Identity,
58+
attribute_fields: AttributeFields {
59+
mem_attributes: MemAttributes::Device,
60+
acc_perms: AccessPermissions::ReadWrite,
61+
execute_never: true,
62+
},
63+
},
64+
],
65+
);
66+
67+
//--------------------------------------------------------------------------------------------------
68+
// Private Code
69+
//--------------------------------------------------------------------------------------------------
70+
71+
fn code_range_inclusive() -> RangeInclusive<usize> {
72+
// Notice the subtraction to turn the exclusive end into an inclusive end.
73+
#[allow(clippy::range_minus_one)]
74+
RangeInclusive::new(super::code_start(), super::code_end_exclusive() - 1)
75+
}
76+
77+
fn remapped_mmio_range_inclusive() -> RangeInclusive<usize> {
78+
// The last 64 KiB slot in the first 512 MiB. Chosen for educational purposes
79+
// to match the original tutorial's concept.
80+
RangeInclusive::new(0x1FFF_0000, 0x1FFF_FFFF)
81+
}
82+
83+
fn mmio_range_inclusive() -> RangeInclusive<usize> {
84+
RangeInclusive::new(memory_map::mmio::START, memory_map::mmio::END_INCLUSIVE)
85+
}
86+
87+
//--------------------------------------------------------------------------------------------------
88+
// Public Code
89+
//--------------------------------------------------------------------------------------------------
90+
91+
/// Return a reference to the virtual memory layout.
92+
pub fn virt_mem_layout() -> &'static KernelVirtualLayout<NUM_MEM_RANGES> {
93+
&LAYOUT
94+
}

0 commit comments

Comments
 (0)