Skip to content

Commit 6f89d7e

Browse files
committed
Finalize README for 0C - Virtual Memory
1 parent e4438e4 commit 6f89d7e

9 files changed

+1451
-43
lines changed

0C_virtual_memory/Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0C_virtual_memory/README.md

+117-26
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,134 @@
11
# Tutorial 0C - Virtual Memory
22

3-
**This is a stub**
3+
Virtual memory is an immensely complex, but exciting topic. In this first
4+
lesson, we start slow and switch on the MMU and use static page tables. We will
5+
only be concerned about the first `1 GiB` of address space. That is the amount
6+
of `DRAM` the usual Raspberry Pi 3 has. As we already know, the upper `16 MiB`
7+
of this gigabyte-window are occupied by the Raspberry's peripherals such as the
8+
UART.
9+
10+
## MMU and paging theory
11+
12+
At this point, we will not reinvent the wheel again and go into detailed
13+
descriptions of how paging in modern application processors works. The internet
14+
is full of great resources regarding this topic, and we encourage you to read
15+
some of it to get a high-level understanding of the topic.
16+
17+
To follow the rest of this `AArch64` specific tutorial, we strongly recommend
18+
that you stop right here and first read `Chapter 12` of the [ARM Cortex-A Series
19+
Programmer's Guide for
20+
ARMv8-A](http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf)
21+
before you continue. This will set you up with all the `AArch64`-specific
22+
knowledge needed to follow along.
23+
24+
Back from reading `Chapter 12` already? Good job :+1:!
25+
26+
## Approach
27+
28+
The following partitioning will be used for the static page tables:
29+
- The first `2 MiB` will be mapped using a Level 3 table with `4 KiB` granule.
30+
- This aperture includes, among others, the kernel's code, read-only data, and
31+
mutable data. All of which will be `identity mapped` to make our life easy
32+
for now.
33+
- In the past, we already made sure that the linker script aligns the
34+
respective regions to `4 KiB` boundaries.
35+
- This way, we can conveniently flag corresponding regions in distinct page
36+
table entries. E.g. marking the code regions executable, while the mutable
37+
data regions are not.
38+
- All the rest will be mapped using `2 MiB` granule.
39+
40+
The actual code is divided into two files: `memory.rs` and `memory/mmu.rs`.
41+
42+
### memory.rs
43+
44+
This file is used to describe our kernel's memory layout in a high-level
45+
abstraction using our own descriptor format. We can define ranges of arbitrary
46+
length and set respective attributes, for example if the bits and bytes in this
47+
range should be executable or not.
48+
49+
The descriptors we use here are agnostic of the hardware `MMU`'s actual
50+
descriptors, and we are also agnostic of the paging granule the `MMU` will use.
51+
Having this distinction is less of a technical need and more a convenience
52+
feature for us in order to easily describe the kernels memory layout, and
53+
hopefully it makes the whole concept a bit more graspable for the reader.
54+
55+
The file contains a global `static KERNEL_VIRTUAL_LAYOUT` array which
56+
stores these descriptors. The policy is to only store regions that are **not**
57+
ordinary, normal chacheable DRAM. However, nothing prevents you from defining
58+
those too if you wish to. Here is an example for the device MMIO region:
459

5-
TODO: Write rest of tutorial.
60+
```rust
61+
// Device MMIO
62+
Descriptor {
63+
virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END),
64+
translation: Translation::Identity,
65+
attribute_fields: AttributeFields {
66+
mem_attributes: MemAttributes::Device,
67+
acc_perms: AccessPermissions::ReadWrite,
68+
execute_never: true,
69+
},
70+
},
71+
```
672

7-
Virtual memory is an immensely complex, but exciting topic. In this first
8-
lesson, we start slow and switch on the MMU using handcrafted page tables for
9-
the first `1 GiB` of memory. That is the amount of `DRAM` the usual Raspberry Pi
10-
3 has. As we already know, the upper `16 MiB` of this gigabyte-window are
11-
occupied by the Raspberry's peripherals such as the UART.
73+
Finally, the file contains the following function:
74+
75+
```rust
76+
fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str>
77+
```
78+
79+
It will be used by code in `mmu.rs` to request attributes for a virtual address
80+
and the translation of the address. The function scans `KERNEL_VIRTUAL_LAYOUT`
81+
for a descriptor that contains the queried address, and returns the respective
82+
findings for the first entry that is a hit. If no entry is found, it returns
83+
default attributes for normal chacheable DRAM and the input address, hence
84+
telling the `MMU` code that the requested address should be `identity mapped`.
1285

13-
The page tables we install alternate between `2 MiB` blocks and `4 KiB` blocks.
86+
Due to this default return, it is not needed to define normal cacheable DRAM
87+
regions in `KERNEL_VIRTUAL_LAYOUT`.
1488

15-
The first `2 MiB` of memory are identity mapped, and therefore contain our code
16-
and the stack. We use a single table with a `4 KiB` granule to differentiate
17-
between code, RO-data and RW-data. The linker script was adapted to adhere to
18-
the pagetable sizes.
89+
### mmu.rs
1990

20-
Next, we map the UART into the second `2 MiB` block to show the effects of
21-
virtual memory.
91+
This file contains the `AArch64` specific code. It is a driver, if you like, and
92+
the split in paging granule mentioned before is hardcoded here (`4 KiB` page
93+
descriptors for the first `2 MiB` and `2 MiB` block descriptors for everything
94+
else).
2295

23-
Everyting else is, for reasons of convenience, again identity mapped using `2
24-
MiB` blocks.
96+
Two static page table arrays are instantiated, `LVL2_TABLE` and `LVL3_TABLE`,
97+
and they are populated using `get_virt_addr_properties()` and a bunch of utility
98+
functions that convert our own descriptors to the actual `64 bit` descriptor
99+
entries needed by the MMU hardware for the page table arrays.
25100

26-
Hopefully, in a later tutorial, we will write or use (e.g. from the `cortex-a`
27-
crate) proper modules for page table handling, that, among others, cover topics
28-
such as using recursive mapping for maintenace.
101+
Afterwards, the [Translation Table Base Register 0 - EL1](https://docs.rs/crate/cortex-a/2.4.0/source/src/regs/ttbr0_el1.rs) is set up with the base address of the `LVL3_TABLE` and
102+
the [Translation Control Register - EL1](https://docs.rs/crate/cortex-a/2.4.0/source/src/regs/tcr_el1.rs) is
103+
configured.
29104

30-
## Adress translation with the 4 KiB LVL3 table
105+
Finally, the MMU is turned on through the [System Control Register - EL1](https://docs.rs/crate/cortex-a/2.4.0/source/src/regs/sctlr_el1.rs). The last step also enables caching for data and instructions.
31106

32-
The following block diagram shows address translation by example of the UART's
33-
Control Register (CR).
107+
## Address translation examples
108+
109+
For educational purposes, in `memory.rs`, a layout is defined which allows to
110+
access the `UART` via two different virtual addresses:
111+
- Since we identity map the whole `Device MMIO` region, it is accessible by
112+
asserting its physical base address (`0x3F20_1000`) after the `MMU` is turned
113+
on.
114+
- Additionally, it is also mapped into the last `4 KiB` entry of the `LVL3`
115+
table, making it accessible through base address `0x001F_F000`.
116+
117+
The following two block diagrams visualize the underlying translations for the
118+
two mappings, accessing the UART's Control Register (`CR`, offset `0x30`).
119+
120+
### Adress translation using a 2 MiB block descriptor
121+
122+
![2 MiB translation block diagram](../doc/page_tables_2MiB.png)
123+
124+
### Adress translation using a 4 KiB page descriptor
34125

35126
![4 KiB translation block diagram](../doc/page_tables_4KiB.png)
36127

128+
37129
## Zero-cost abstraction
38130

39-
The MMU init code is a good example to see the great potential of Rust's
131+
The MMU init code is also a good example to see the great potential of Rust's
40132
zero-cost abstractions[[1]](https://blog.rust-lang.org/2015/05/11/traits.html)[[2]](https://ruudvanasseldonk.com/2016/11/30/zero-cost-abstractions) for embedded programming.
41133

42134
Take this piece of code for setting up the `MAIR_EL1` register using the
@@ -57,7 +149,7 @@ MAIR_EL1.write(
57149
);
58150
```
59151

60-
This piece of code is super expressive, and it makes us of `traits`, different
152+
This piece of code is super expressive, and it makes use of `traits`, different
61153
`types` and `constants` to provide type-safe register manipulation.
62154

63155
In the end, this code sets the first four bytes of the register to certain
@@ -84,6 +176,5 @@ ferris@box:~$ make raspboot
84176
[i] MMU: Up to 40 Bit physical address range supported!
85177
[2] MMU online.
86178

87-
Writing through the virtual mapping at 0x00000000001FF000.
88-
179+
Writing through the virtual mapping at base address 0x00000000001FF000.
89180
```

0C_virtual_memory/kernel8

192 Bytes
Binary file not shown.

0C_virtual_memory/kernel8.img

74 Bytes
Binary file not shown.

0C_virtual_memory/src/main.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ fn kernel_entry() -> ! {
6060
uart.puts(s);
6161
uart.puts("\n");
6262
}
63+
// The following write is already using the identity mapped
64+
// translation in the LVL2 table.
6365
Ok(()) => uart.puts("[2] MMU online.\n"),
6466
}
6567
} // After this closure, the UART instance is not valid anymore.
@@ -68,7 +70,7 @@ fn kernel_entry() -> ! {
6870
// again, though.
6971
let uart = uart::Uart::new(memory::map::virt::REMAPPED_UART_BASE);
7072

71-
uart.puts("\nWriting through the virtual mapping at 0x");
73+
uart.puts("\nWriting through the virtual mapping at base address 0x");
7274
uart.hex(memory::map::virt::REMAPPED_UART_BASE as u64);
7375
uart.puts(".\n");
7476

doc/page_tables_2MiB.png

69.7 KB
Loading

0 commit comments

Comments
 (0)