From 9818779ff222c779e39a137f90d7f0349f268f71 Mon Sep 17 00:00:00 2001 From: Andre Richter Date: Thu, 28 Mar 2019 23:10:04 +0100 Subject: [PATCH] Add README for tutorial 10_DMA_memory --- 10_DMA_memory/README.md | 244 ++++++++++++++++++++++- 10_DMA_memory/src/main.rs | 7 +- doc/dma_0.png | Bin 0 -> 10166 bytes doc/dma_0.svg | 402 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 643 insertions(+), 10 deletions(-) create mode 100644 doc/dma_0.png create mode 100644 doc/dma_0.svg diff --git a/10_DMA_memory/README.md b/10_DMA_memory/README.md index 9e6c48ae..d9b9bdf9 100644 --- a/10_DMA_memory/README.md +++ b/10_DMA_memory/README.md @@ -1,13 +1,245 @@ # Tutorial 10 - DMA Memory -Coming soon! +There's a secret I haven't told you! A certain part of our code doesn't work +anymore since the [virtual memory](../0D_virtual_memory) tutorial. There is a +regression that manifests in the `Videocore Mailbox` driver. It will only work +until **paging and caching** is switched on. Afterwards, the `call()` method +will fail. Why is that? -This lesson will teach about: -- Simple bump memory allocators and non-cacheable memory. -- Using MiniUart for early boot messages and dynamically switching to the PL011 - Uart later (which now needs the memory allocator that theoretically could fail - - which the MiniUart could then print). +The reason is that in our code, the RPi's processor is sharing a `DRAM buffer` +with the `Videocore` device. In other words, the concept of **shared memory** is +used. Let's recall a simplified version of the protocol: +1. RPi `CPU` checks the `STATUS` MMIO register of the `Videcore` if a message can + be written. +2. If so, `CPU` writes the address of the `DRAM buffer` in which the actual + message is stored into the `Videocore`'s `WRITE` MMIO register. +3. `CPU` checks the `STATUS` and `READ` MMIO registers if the Videocore has + answered. +4. If so, `CPU` checks the first `u32` word of the earlier provided `DRAM buffer` + if the response is valid (the `Videocore` puts its answer into the same buffer + in which the original request was stored. This is what is commonly called + a `DMA` transaction). + +At step **4**, things break. The reason is that code and **page tables** were +set up in a way that the `DRAM buffer` used for message exchange between `CPU` +and Videcore is attributed as _cacheable_. + +So when the `CPU` is writing to the buffer, the contents might not get written +back to `DRAM` in time before the notification of a new message is signaled to +the Videocore via the `WRITE` MMIO register (which is correctly attributed as +device memory in the page tables and hence not cached). + +Even if the contents would land in `DRAM` in time, the `Videocore`'s answer +which overwrites the same buffer would not be reflected in the `CPU`'s cache, +since there is no coherency mechanism in place between the two. The RPi `CPU` +would read back the same values it put into the buffer itself when setting up +the message, and not the `DRAM` content that contains the answer. + +![DMA block diagram](../doc/dma_0.png) + +The regression did not manifest yet because the Mailbox is only used before the +paging caching is switched on, and never afterwards. However, now is a good time +to fix this. + +## An Allocator for DMA Memory + +The first step is to introduce a region of _non-cacheable DRAM_ in the +`KERNEL_VIRTUAL_LAYOUT` in `memory.rs`: + +```rust +Descriptor { + name: "DMA heap pool", + virtual_range: || RangeInclusive::new(map::virt::DMA_HEAP_START, map::virt::DMA_HEAP_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::NonCacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, +}, +``` + +When you saw the inferior performance of non-cacheable mapped DRAM compared to +cacheable DRAM in the [cache performance tutorial](../0E_cache_performance) +earlier and asked yourself why anybody would ever want this: Exactly for the +use-case at hand! + +Theoretically, some linker hacks could be used to ensure that the `Videcore` is +using a buffer that is statically linked to the DMA heap pool once paging and +caching is turned on. However, in real-world kernels, it is common to frequently +map/allocate and unmap/free chunks of `DMA` memory at runtime, for example in +device drivers for DMA-capable devices. + +Hence, let's introduce an `allocator`. + +### Bump Allocation + +As always in the tutorials, a simple implementation is used for getting started +with basic concepts of a topic, and upgrades are introduced when they are +needed. + +In a `bump allocator`, when a requests comes in, it always returns the next +possible aligned region of its heap until it runs out of memory. What makes it +really simple is that it doesn't provide means for freeing memory again. When no +more memory is left, game is over. + +Conveniently enough, [Rust already provides memory allocation APIs](https://doc.rust-lang.org/alloc/alloc/index.html). There is an +[Alloc](https://doc.rust-lang.org/alloc/alloc/trait.Alloc.html) and a +[GlobalAlloc](https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html) +trait. The latter is intended for realizing a _default allocator_, meaning it +would be the allocator used for any standard language construtcs that +automatically allocate something on the heap, for example a +[Box](https://doc.rust-lang.org/alloc/boxed/index.html). There can only be one +global allocator, so the tutorials will make use of it for cacheable DRAM later. + +Hence, for the DMA bump allocator, +[Alloc](https://doc.rust-lang.org/alloc/alloc/trait.Alloc.html) will be +used. What is also really nice is that for both traits, only the `alloc()` +method needs to be implemented. If this is done, you automatically get a bunch +of additional default methods for free, e.g. `alloc_zeroed()`. + +Here is the implementation in `memory/bump_allocator.rs`: + +```rust +pub struct BumpAllocator { + next: usize, + pool_end: usize, + name: &'static str, +} + +unsafe impl Alloc for BumpAllocator { + unsafe fn alloc(&mut self, layout: Layout) -> Result, AllocErr> { + let start = crate::memory::aligned_addr_unchecked(self.next, layout.align()); + let end = start + layout.size(); + + if end <= self.pool_end { + self.next = end; + + println!( + "[i] {}:\n Allocated Addr {:#010X} Size {:#X}", + self.name, + start, + layout.size() + ); + + Ok(NonNull::new_unchecked(start as *mut u8)) + } else { + Err(AllocErr) + } + } + + // A bump allocator doesn't care + unsafe fn dealloc(&mut self, _ptr: NonNull, _layout: Layout) {} +} +``` + +The `alloc()` method returns a pointer to memory. However, it is safer to +operate with [slices](https://doc.rust-lang.org/alloc/slice/index.html), since +they are intrinsically bounds-checked. Therefore, the `BumpAllocator` gets an +additional method called `alloc_slice_zeroed()`, which wraps around +`alloc_zeroed()` provided by the `Alloc` trait and on success returns a `&'a mut +[T]`. + +### Global Instance + +A global instance of the allocator is needed, and since its methods demand +_mutable references_ to `self`, it is wrapped into a `NullLock`, which was +introduced in the [last tutorial](../0F_globals_synchronization_println): + +```rust +/// The global allocator for DMA-able memory. That is, memory which is tagged +/// non-cacheable in the page tables. +static DMA_ALLOCATOR: sync::NullLock = + sync::NullLock::new(memory::BumpAllocator::new( + memory::map::virt::DMA_HEAP_START as usize, + memory::map::virt::DMA_HEAP_END as usize, + "Global DMA Allocator", + )); + +``` + +## Videocore Driver + +The `Videocore` driver has to be changed to use the allocator during +instantiation, and in contrast to earlier, this could fail now: + +```rust +let ret = crate::DMA_ALLOCATOR.lock(|d| d.alloc_slice_zeroed(MBOX_SIZE, MBOX_ALIGNMENT)); + +if ret.is_err() { + return Err(()); +} +``` + +## Reorg of the Kernel Init + +Since the `Videcore` now depends on the `DMA Allocator`, its initialization must +now happen _after_ the `MMU init`, which turns on **paging and caching**. This, +in turn, means that the `PL011 UART`, which is used for printing and needs the +`Videcore` for its setup, has to shift its init as well. So there is a lot of +shuffling happening. + +In summary, the new init procedure would be: + +1. GPIO +2. MMU +3. Videcore +4. PL011 UART + +That is a bit unfortunate, because if anything goes wrong at `MMU` or +`Videocore` init, we can not print any fault info on the console. For this +reason, the `MiniUart` from the earlier tutorials is revived, because it only +needs the `GPIO` driver to set itself up. So here is the revamped init: + +1. GPIO +2. MiniUart +3. MMU +4. Videcore +5. PL011 UART + +Using this procedure, the `MiniUart` can report faults for any of the subsequent +stages like`MMU` or `Videocore` init. If all is successful and the more capable +`PL011 UART` comes online, we can let it conveniently replace the `MiniUart` +through the `CONSOLE.replace_with()` scheme introduced in the [last tutorial](../0F_globals_synchronization_println). + +### Make it Fault + +If you feel curious and want to put all the theory to action, take a look at the +code in `main.rs` for the DMA allocator instantiation and try the changes in the +comments: + +```rust +/// The global allocator for DMA-able memory. That is, memory which is tagged +/// non-cacheable in the page tables. +static DMA_ALLOCATOR: sync::NullLock = + sync::NullLock::new(memory::BumpAllocator::new( + memory::map::virt::DMA_HEAP_START as usize, + memory::map::virt::DMA_HEAP_END as usize, + "Global DMA Allocator", + // Try the following arguments instead to see the PL011 UART init + // fail. It will cause the allocator to use memory that is marked + // cacheable and therefore not DMA-safe. The communication with the + // Videocore will therefore fail. + + // 0x00600000 as usize, + // 0x007FFFFF as usize, + // "Global Non-DMA Allocator", + )); +``` + +This might only work on the real HW and not in QEMU. + +## QEMU + +On the actual HW it is possible to reprogram the same `GPIO` pins at runtime to +either use the `MiniUart` or the `PL011`, and as a result the console output of +both is sent through the same USB-serial dongle. This is transparent to the +user. + +On QEMU, unfortunately, two different virtual terminals must be used and this +multiplexing is not possible. As a result, you'll see that the QEMU output has +changed in optics a bit and now provides separate views for the two `UARTs`. ## Output diff --git a/10_DMA_memory/src/main.rs b/10_DMA_memory/src/main.rs index 4f4b2405..f5410979 100644 --- a/10_DMA_memory/src/main.rs +++ b/10_DMA_memory/src/main.rs @@ -49,10 +49,9 @@ static DMA_ALLOCATOR: sync::NullLock = memory::map::virt::DMA_HEAP_END as usize, "Global DMA Allocator", // Try the following arguments instead to see the PL011 UART init - // fail. It will cause the allocator to use memory that are marked - // cacheable and therefore not DMA-safe. The answer from the Videocore - // won't be received by the CPU because it reads an old cached value - // that resembles an error case instead. + // fail. It will cause the allocator to use memory that is marked + // cacheable and therefore not DMA-safe. The communication with the + // Videocore will therefore fail. // 0x00600000 as usize, // 0x007FFFFF as usize, diff --git a/doc/dma_0.png b/doc/dma_0.png new file mode 100644 index 0000000000000000000000000000000000000000..65c8c6192657bd6613b2d95d4d7074bd16b454c7 GIT binary patch literal 10166 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 VAG6}0H^{Hao>e@Pe(J)F{{Y;+ca8u6 literal 0 HcmV?d00001 diff --git a/doc/dma_0.svg b/doc/dma_0.svg new file mode 100644 index 00000000..8ea0f46a --- /dev/null +++ b/doc/dma_0.svg @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + core0 + + core1 + + core2 + + core3 + + + Cache + + + CPU + + DRAM + + Videocore + + + +