Finalize README for 0C - Virtual Memory

pull/15/head
Andre Richter 5 years ago
parent e4438e4220
commit 6f89d7e4a9
No known key found for this signature in database
GPG Key ID: 2116C1AB102F615E

@ -1,3 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "cortex-a"
version = "2.3.1"

@ -1,42 +1,134 @@
# Tutorial 0C - Virtual Memory
**This is a stub**
Virtual memory is an immensely complex, but exciting topic. In this first
lesson, we start slow and switch on the MMU and use static page tables. We will
only be concerned about the first `1 GiB` of address space. That is the amount
of `DRAM` the usual Raspberry Pi 3 has. As we already know, the upper `16 MiB`
of this gigabyte-window are occupied by the Raspberry's peripherals such as the
UART.
## MMU and paging theory
At this point, we will not reinvent the wheel again and go into detailed
descriptions of how paging in modern application processors works. The internet
is full of great resources regarding this topic, and we encourage you to read
some of it to get a high-level understanding of the topic.
To follow the rest of this `AArch64` specific tutorial, we strongly recommend
that you stop right here and first read `Chapter 12` of the [ARM Cortex-A Series
Programmer's Guide for
ARMv8-A](http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf)
before you continue. This will set you up with all the `AArch64`-specific
knowledge needed to follow along.
Back from reading `Chapter 12` already? Good job :+1:!
## Approach
The following partitioning will be used for the static page tables:
- The first `2 MiB` will be mapped using a Level 3 table with `4 KiB` granule.
- This aperture includes, among others, the kernel's code, read-only data, and
mutable data. All of which will be `identity mapped` to make our life easy
for now.
- In the past, we already made sure that the linker script aligns the
respective regions to `4 KiB` boundaries.
- This way, we can conveniently flag corresponding regions in distinct page
table entries. E.g. marking the code regions executable, while the mutable
data regions are not.
- All the rest will be mapped using `2 MiB` granule.
The actual code is divided into two files: `memory.rs` and `memory/mmu.rs`.
### memory.rs
This file is used to describe our kernel's memory layout in a high-level
abstraction using our own descriptor format. We can define ranges of arbitrary
length and set respective attributes, for example if the bits and bytes in this
range should be executable or not.
The descriptors we use here are agnostic of the hardware `MMU`'s actual
descriptors, and we are also agnostic of the paging granule the `MMU` will use.
Having this distinction is less of a technical need and more a convenience
feature for us in order to easily describe the kernels memory layout, and
hopefully it makes the whole concept a bit more graspable for the reader.
The file contains a global `static KERNEL_VIRTUAL_LAYOUT` array which
stores these descriptors. The policy is to only store regions that are **not**
ordinary, normal chacheable DRAM. However, nothing prevents you from defining
those too if you wish to. Here is an example for the device MMIO region:
TODO: Write rest of tutorial.
```rust
// Device MMIO
Descriptor {
virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END),
translation: Translation::Identity,
attribute_fields: AttributeFields {
mem_attributes: MemAttributes::Device,
acc_perms: AccessPermissions::ReadWrite,
execute_never: true,
},
},
```
Virtual memory is an immensely complex, but exciting topic. In this first
lesson, we start slow and switch on the MMU using handcrafted page tables for
the first `1 GiB` of memory. That is the amount of `DRAM` the usual Raspberry Pi
3 has. As we already know, the upper `16 MiB` of this gigabyte-window are
occupied by the Raspberry's peripherals such as the UART.
Finally, the file contains the following function:
```rust
fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str>
```
It will be used by code in `mmu.rs` to request attributes for a virtual address
and the translation of the address. The function scans `KERNEL_VIRTUAL_LAYOUT`
for a descriptor that contains the queried address, and returns the respective
findings for the first entry that is a hit. If no entry is found, it returns
default attributes for normal chacheable DRAM and the input address, hence
telling the `MMU` code that the requested address should be `identity mapped`.
The page tables we install alternate between `2 MiB` blocks and `4 KiB` blocks.
Due to this default return, it is not needed to define normal cacheable DRAM
regions in `KERNEL_VIRTUAL_LAYOUT`.
The first `2 MiB` of memory are identity mapped, and therefore contain our code
and the stack. We use a single table with a `4 KiB` granule to differentiate
between code, RO-data and RW-data. The linker script was adapted to adhere to
the pagetable sizes.
### mmu.rs
Next, we map the UART into the second `2 MiB` block to show the effects of
virtual memory.
This file contains the `AArch64` specific code. It is a driver, if you like, and
the split in paging granule mentioned before is hardcoded here (`4 KiB` page
descriptors for the first `2 MiB` and `2 MiB` block descriptors for everything
else).
Everyting else is, for reasons of convenience, again identity mapped using `2
MiB` blocks.
Two static page table arrays are instantiated, `LVL2_TABLE` and `LVL3_TABLE`,
and they are populated using `get_virt_addr_properties()` and a bunch of utility
functions that convert our own descriptors to the actual `64 bit` descriptor
entries needed by the MMU hardware for the page table arrays.
Hopefully, in a later tutorial, we will write or use (e.g. from the `cortex-a`
crate) proper modules for page table handling, that, among others, cover topics
such as using recursive mapping for maintenace.
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
the [Translation Control Register - EL1](https://docs.rs/crate/cortex-a/2.4.0/source/src/regs/tcr_el1.rs) is
configured.
## Adress translation with the 4 KiB LVL3 table
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.
The following block diagram shows address translation by example of the UART's
Control Register (CR).
## Address translation examples
For educational purposes, in `memory.rs`, a layout is defined which allows to
access the `UART` via two different virtual addresses:
- Since we identity map the whole `Device MMIO` region, it is accessible by
asserting its physical base address (`0x3F20_1000`) after the `MMU` is turned
on.
- Additionally, it is also mapped into the last `4 KiB` entry of the `LVL3`
table, making it accessible through base address `0x001F_F000`.
The following two block diagrams visualize the underlying translations for the
two mappings, accessing the UART's Control Register (`CR`, offset `0x30`).
### Adress translation using a 2 MiB block descriptor
![2 MiB translation block diagram](../doc/page_tables_2MiB.png)
### Adress translation using a 4 KiB page descriptor
![4 KiB translation block diagram](../doc/page_tables_4KiB.png)
## Zero-cost abstraction
The MMU init code is a good example to see the great potential of Rust's
The MMU init code is also a good example to see the great potential of Rust's
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.
Take this piece of code for setting up the `MAIR_EL1` register using the
@ -57,7 +149,7 @@ MAIR_EL1.write(
);
```
This piece of code is super expressive, and it makes us of `traits`, different
This piece of code is super expressive, and it makes use of `traits`, different
`types` and `constants` to provide type-safe register manipulation.
In the end, this code sets the first four bytes of the register to certain
@ -84,6 +176,5 @@ ferris@box:~$ make raspboot
[i] MMU: Up to 40 Bit physical address range supported!
[2] MMU online.
Writing through the virtual mapping at 0x00000000001FF000.
Writing through the virtual mapping at base address 0x00000000001FF000.
```

Binary file not shown.

Binary file not shown.

@ -60,6 +60,8 @@ fn kernel_entry() -> ! {
uart.puts(s);
uart.puts("\n");
}
// The following write is already using the identity mapped
// translation in the LVL2 table.
Ok(()) => uart.puts("[2] MMU online.\n"),
}
} // After this closure, the UART instance is not valid anymore.
@ -68,7 +70,7 @@ fn kernel_entry() -> ! {
// again, though.
let uart = uart::Uart::new(memory::map::virt::REMAPPED_UART_BASE);
uart.puts("\nWriting through the virtual mapping at 0x");
uart.puts("\nWriting through the virtual mapping at base address 0x");
uart.hex(memory::map::virt::REMAPPED_UART_BASE as u64);
uart.puts(".\n");

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

@ -16,7 +16,6 @@
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="page_tables_4KiB.svg"
inkscape:export-filename="/home/arichter/repos/rust-raspi3-tutorial/doc/page_tables.svg.png"
inkscape:export-xdpi="95.999992"
inkscape:export-ydpi="95.999992">
<defs
@ -115,9 +114,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="372.0084"
inkscape:cy="529.49707"
inkscape:zoom="2.8284271"
inkscape:cx="768.68074"
inkscape:cy="297.78832"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
@ -125,10 +124,10 @@
showguides="true"
inkscape:snap-grids="true"
inkscape:snap-text-baseline="true"
inkscape:window-width="1853"
inkscape:window-height="1145"
inkscape:window-x="1987"
inkscape:window-y="27"
inkscape:window-width="1920"
inkscape:window-height="1172"
inkscape:window-x="3840"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
@ -1460,17 +1459,17 @@
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:4px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="89.958336"
y="279.81244"
x="92.604164"
y="279.80206"
id="text4524-6-0-4-2-1-02-1-0-8-2-0-69-7-5-0-1-2-5"><tspan
sodipodi:role="line"
x="89.958336"
y="279.81244"
x="92.604164"
y="279.80206"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:4px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';text-align:center;text-anchor:middle;stroke-width:0.26458332"
id="tspan2057-2-5-6-0-9-6">47</tspan><tspan
sodipodi:role="line"
x="89.958336"
y="283.81244"
x="92.604164"
y="283.80206"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;line-height:4px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';text-align:center;text-anchor:middle;stroke-width:0.26458332"
id="tspan2059-6-8-0-2-7-4" /></text>
<text
@ -1523,14 +1522,14 @@
id="tspan1417-0">The LVL3 PT entry points</tspan><tspan
sodipodi:role="line"
x="182.5625"
y="66.371506"
y="66.371513"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';fill:#cc0000;stroke-width:0.26458332"
id="tspan1447">to the start address of</tspan><tspan
sodipodi:role="line"
x="182.5625"
y="71.222206"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777767px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';fill:#cc0000;stroke-width:0.26458332"
id="tspan1449">a 4KiB frame. The last</tspan><tspan
id="tspan1449">a 4 KiB frame. The last</tspan><tspan
sodipodi:role="line"
x="182.5625"
y="76.072899"
@ -1552,5 +1551,16 @@
y="242.76041"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:3px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';text-align:center;text-anchor:middle;stroke-width:0.26458332"
id="tspan2597-6">output address</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="208.68695"
y="233.3885"
id="text4524-6-7-8-5-0-3"><tspan
sodipodi:role="line"
id="tspan4522-0-5-1-97-2-2"
x="208.68695"
y="233.3885"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.88055563px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono';stroke-width:0.26458332">1</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Loading…
Cancel
Save