1
1
# Tutorial 0C - Virtual Memory
2
2
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:
4
59
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
+ ```
6
72
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 `.
12
85
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 `.
14
88
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
19
90
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 ).
22
95
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 .
25
100
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 .
29
104
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.
31
106
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
34
125
35
126
! [4 KiB translation block diagram ](.. / doc / page_tables_4KiB. png)
36
127
128
+
37
129
## Zero - cost abstraction
38
130
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
40
132
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.
41
133
42
134
Take this piece of code for setting up the `MAIR_EL1 ` register using the
@@ -57,7 +149,7 @@ MAIR_EL1.write(
57
149
);
58
150
```
59
151
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
61
153
` types ` and ` constants ` to provide type-safe register manipulation.
62
154
63
155
In the end, this code sets the first four bytes of the register to certain
@@ -84,6 +176,5 @@ ferris@box:~$ make raspboot
84
176
[i] MMU: Up to 40 Bit physical address range supported!
85
177
[2] MMU online.
86
178
87
- Writing through the virtual mapping at 0x00000000001FF000.
88
-
179
+ Writing through the virtual mapping at base address 0x00000000001FF000.
89
180
```
0 commit comments