UART1 output on QEMU. Rework some Readmes.

pull/9/head
Andre Richter 6 years ago
parent b6fafa6191
commit c62b79f09b
No known key found for this signature in database
GPG Key ID: 2116C1AB102F615E

@ -4,8 +4,8 @@ Okay, we're not going to do much here, just test our toolchain. The resulting
kernel8.img should boot on the Raspberry Pi 3, and stop all CPU cores in an
infinite waiting loop. You can check that by running
```bash
$ make qemu
```console
ferris@box:~$ make qemu
... some output removed for clearity: ...
----------------
IN:

@ -34,15 +34,15 @@ cores][dist]. To do so, we read the [mpidr_el1][mpdir] system register. If it is
not zero, we enter the former infinite waiting loop, aka stopping the respective
CPU core.
If the result of the read from `mpidr_el1` is zero, which means we are
executing on core0, we set up the stack for that core, and afterwards call the
Rust `reset()` function of the boot code in `raspi3_boot/src/lib.rs`. In case
the Rust code returns (which it never should), we also jump to the same
infinite loop the other CPU cores are running.
The Rust `reset()`, in turn, will then zero-out the `bss section` (the next
section explains what that is) and finally call our `main()` function from
`main.rs`.
If the result of the read from `mpidr_el1` is zero, which means we are executing
on core0, we set up the stack for that core, and afterwards call the Rust
`reset()` function of the boot code in `raspi3_boot/src/lib.rs`. In case the
Rust code returns (which it never should), we also jump to the same infinite
loop the other CPU cores are running.
The Rust `reset()`, in turn, will then zero-out the `bss section` (the next
section explains what that is) and finally call our `main()` function from
`main.rs`.
[dist]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/CFHCIDCH.html
[mpdir]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500g/BABHBJCI.html

@ -51,7 +51,7 @@ kernel8.img: kernel8
$(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel8.img
qemu:
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial stdio
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial null -serial stdio
clippy:
cargo xclippy --target=$(TARGET)

@ -1,9 +1,7 @@
# Tutorial 03 - UART1, Auxilary mini UART
It is time for the famous Hello World example. We're going to write on the UART1
first, as it's easier to program as it has a fixed clocked frequency.
NOTE: qemu does not redirect UART1 to terminal by default, only UART0!
first, as it is easier to program, since it has a fixed clocked frequency.
## gpio.rs
@ -32,8 +30,22 @@ return character will also be sent (13 + 10).
## main.rs
First we have to call the uart initialization code. Then we wait for the first
First, we have to call the uart initialization code. Then we wait for the first
keypress from the user before we say "Hello Rustacean!". If you've purchased an
USB serial cable, you should see it on `screen`'s screen. After that, every
character typed in `screen` will be echoed back. If you haven't turned off local
echo, that means you'll see every pressed key twice.
## Simulation
We can also use `QEMU` to simulate the UART output of our bare-metal binary on
the host PC.
```console
ferris@box:~$ make qemu
<Press any key>
Hello Rustacean!
```
However, let it be said that it is more thrilling to see your first output from
the real hardware target, so don't be shy and grab a USB-serial.

@ -51,7 +51,7 @@ kernel8.img: kernel8
$(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel8.img
qemu:
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial stdio
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial null -serial stdio
clippy:
cargo xclippy --target=$(TARGET)

@ -1,10 +1,13 @@
# Tutorial 04 - Mailboxes
Before we could go on with UART0, we need mailboxes. So in this tutorial we
introduce the mailbox interface. We'll use it to query the board's serial
number and print that out on UART1.
The Raspberry Pi 3 also has a more powerful UART, `UART0`, that among other
features, supports programmable clock rates. Before we can go on with setting
up `UART0`, we need mailboxes. Mailboxes are an interface between the Pi's ARM
CPU cores and the GPU. They will be used as a means to request work from the
GPU, for example, requesting to program a certain clock for `UART0`.
NOTE: qemu does not redirect UART1 to terminal by default, only UART0!
In this tutorial, we'll start slowly and use it to query the Raspberry's serial
number and print that out on the already functional `UART1`.
## uart.rs
@ -74,4 +77,4 @@ instructions] to prevent this behavior would be needed.
## main.rs
We query the board's serial number and then we display it on the serial console.
We query the board's serial number and then we display it on the UART.

@ -1,22 +1,48 @@
# Tutorial 05 - UART0, PL011
This tutorial does the same as tutorial 04, but it prints the serial number on
UART0. As such, it can be used easily with qemu, like
```sh
$ make # To build the kernel
$ make qemu
<Press any key>
Hello Rustacean!
My serial number is: 0000000000000000
```
Finally, we can set up `UART0` thanks to the mailbox interface. This tutorial
produces the same output as tutorial 04, but it prints the serial number on
`UART0`.
## uart.rs
Before we could use a rate divisor value, we must establish a valid clock rate
for the PL011. It's done via mailboxes, with the same property channel we used
earlier. Otherwise this interface is identical to the UART1 one.
In the init function, we use the mailbox to set a base clock for the UART:
```rust
mbox.buffer[0] = 9 * 4;
mbox.buffer[1] = mbox::REQUEST;
mbox.buffer[2] = mbox::tag::SETCLKRATE;
mbox.buffer[3] = 12;
mbox.buffer[4] = 8;
mbox.buffer[5] = mbox::clock::UART; // UART clock
mbox.buffer[6] = 4_000_000; // 4Mhz
mbox.buffer[7] = 0; // skip turbo setting
mbox.buffer[8] = mbox::tag::LAST;
// Insert a compiler fence that ensures that all stores to the
// mbox buffer are finished before the GPU is signaled (which
// is done by a store operation as well).
compiler_fence(Ordering::Release);
if mbox.call(mbox::channel::PROP).is_err() {
return Err(UartError::MailboxError); // Abort if UART clocks couldn't be set
};
```
Afterwards, we can program the rate divisors:
```rust
self.IBRD.write(IBRD::IBRD.val(2)); // Results in 115200 baud
self.FBRD.write(FBRD::FBRD.val(0xB));
```
Baud rate calculation won't be covered in detail here. Please see [this
reference from ARM](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/I49493.html)
for details.
The API for using the UART is identical to the `UART1` API.
## main.rs
We query the board's serial number and then we display it on the serial console.
We query the board's serial number and display it on the serial console.

@ -18,16 +18,16 @@ runs on your PC, we will rely on the original [raspbootcom][bootcom] utility.
For convenience, it is already packaged in our `raspi3-utils` docker
container. So if you are running a Linux host, it will be as easy as calling
another Makefile target. It will be included starting with the next tutorial,
`07_abstraction`. You can invoke it with
`07_abstraction`. You can invoke it with:
```bash
```sh
make raspboot
```
If you want to use it with earlier versions of this tutorial, here is a bash
command to invoke it:
```bash
```sh
docker run -it --rm \
--privileged -v /dev/:/dev/ \
-v $PWD:/work -w /work \
@ -53,8 +53,8 @@ same position in memory, therefore the latter thinks it was loaded by the
firmware. To implement that, we use a different linking address this time (we
subtract `2048` from the original address). You can check that with:
```sh
$ cargo nm -- kernel8 | grep _boot_cores
```console
ferris@box:~$ cargo nm -- kernel8 | grep _boot_cores
000000000007f800 T _boot_cores
```

@ -92,8 +92,8 @@ inlined, so we fulfill this requirement. The compilation result of this function
should yield something like the following, where you can see that the stack
pointer is not used apart from ourselves setting it.
```bash
[andre:/work] $ cargo objdump --target aarch64-raspi3-none-elf.json -- -disassemble -print-imm-hex kernel8
```console
ferris@box:~$ cargo objdump --target aarch64-raspi3-none-elf.json -- -disassemble -print-imm-hex kernel8
[...] (Some output omitted)
@ -116,6 +116,6 @@ compiler.
Since this is the first tutorial after we've written our own bootloader over
serial, you can now for the first time test this convenient interface:
```bash
```sh
make raspboot
```

@ -4,38 +4,50 @@
## Introduction
This repository aims to provide easy reference code for programming bare metal on the Raspberry Pi 3
in the [Rust systems programming language]. Emphasis is on leveraging Rust's zero-overhead abstractions to compile
lean code that is readable, concise and safe (at least as safe as it gets on bare-metal hardware).
This repository aims to provide easy reference code for programming bare metal
on the Raspberry Pi 3 in the [Rust systems programming language]. Emphasis is on
leveraging Rust's zero-overhead abstractions to compile lean code that is
readable, concise and safe (at least as safe as it gets on bare-metal hardware).
[Rust systems programming language]: https://www.rust-lang.org
The target audience is **hobby OS developers** who are new to this hardware. It will give you examples on how to do common
Operating Systems tasks, like writing to the serial console, reading keystrokes from it or use various peripherals like
a hardware-backed random number generator.
The target audience is **hobby OS developers** who are new to this hardware. It
will give you examples on how to do common Operating Systems tasks, like writing
to the serial console, reading keystrokes from it or use various peripherals
like a hardware-backed random number generator.
However, it is *not* a tutorial on how to write a _complete_ OS. I won't cover topics like advanced memory management
and virtual file systems, or how to implement multi-tasking. Rather, it comprises a set of micro-tutorials that
introduce different topics one after the other. Maybe in the distant future, we might introduce a meta tutorial
that combines all the resources to a full-fleged kernel that can multi-task some simple userspace processes, but
don't take my word for it.
However, it is *not* a tutorial on how to write a _complete_ OS. I won't cover
topics like advanced memory management and virtual file systems, or how to
implement multi-tasking. Rather, it comprises a set of micro-tutorials that
introduce different topics one after the other. Maybe in the distant future, we
might introduce a meta tutorial that combines all the resources to a full-fleged
kernel that can multi-task some simple userspace processes, but don't take my
word for it.
## Environment
This repo tries to put a focus on user friendliness. Therefore, I made some efforts to eliminate the biggest painpoint in embedded development: _Toolchain hassles_.
This repo tries to put a focus on user friendliness. Therefore, I made some
efforts to eliminate the biggest painpoint in embedded development: _Toolchain
hassles_.
Users eager to try the code should not be bothered with complicated toolchain installation/compilation steps. This is achieved by trying to use the standard Rust toolchain as much as possible, and bridge existing gaps with Docker containers. Please [install Docker for your distro].
Users eager to try the code should not be bothered with complicated toolchain
installation/compilation steps. This is achieved by trying to use the standard
Rust toolchain as much as possible, and bridge existing gaps with Docker
containers. Please [install Docker for your distro].
The setup consists of the following components:
1. Compiler, linker and binutils are used from Rust nightly.
2. QEMU will be used for emulation, but RPi3 support in QEMU is very fresh and has not landed in most of the pre-packaged versions of popular distributions. [This] container will provide it ready to go.
Please notice that you won't need to download or prepare the containers upfront. As long as you have docker installed, they will be pulled automatically the first time the Makefile needs them.
Please notice that you won't need to download or prepare the containers
upfront. As long as you have docker installed, they will be pulled automatically
the first time the Makefile needs them.
[install Docker for your distro]: https://www.docker.com/community-edition#/download
[This]: https://github.com/andre-richter/docker-raspi3-qemu
For now, only a few basic tutorials are ready, but more will be ported over time.
For now, only a few basic tutorials are ready, but more will be ported over
time.
## Prerequisites
@ -47,20 +59,27 @@ cargo install cargo-xbuild cargo-binutils
rustup component add clippy-preview --toolchain=nightly
```
Additionally, a Micro SD card with [firmware files](https://github.com/raspberrypi/firmware/tree/master/boot) on a FAT filesystem.
Additionally, a Micro SD card with [firmware
files](https://github.com/raspberrypi/firmware/tree/master/boot) on a FAT
filesystem.
I recommend to get a [Micro SD card USB adapter](http://media.kingston.com/images/products/prodReader-FCR-MRG2-img.jpg)
(many manufacturers ship SD cards with such an adapter), so that you can connect the card to any desktop computer just
like an USB stick, no special card reader interface required (although many laptops have those these days).
I recommend to get a [Micro SD card USB
adapter](http://media.kingston.com/images/products/prodReader-FCR-MRG2-img.jpg)
(many manufacturers ship SD cards with such an adapter), so that you can connect
the card to any desktop computer just like an USB stick, no special card reader
interface required (although many laptops have those these days).
You can create an MBR partitioning scheme on the SD card with an LBA FAT32 (type 0x0C) partition, format it
and copy `bootcode.bin`, `start.elf` and `fixup.dat` onto it. **Delete all other files or booting might not work**. Or alternatively you can download a raspbian image,
`dd` it to the SD card, mount it and delete the unnecessary .img files. Whichever you prefer. What's important, you'll
create `kernel8.img` with these tutorials which must be copied to the root directory on the SD card, and no other `.img`
files should exists there.
You can create an MBR partitioning scheme on the SD card with an LBA FAT32 (type
0x0C) partition, format it and copy `bootcode.bin`, `start.elf` and `fixup.dat`
onto it. **Delete all other files or booting might not work**. Or alternatively
you can download a raspbian image, `dd` it to the SD card, mount it and delete
the unnecessary .img files. Whichever you prefer. What's important, you'll
create `kernel8.img` with these tutorials which must be copied to the root
directory on the SD card, and no other `.img` files should exists there.
I'd also recommend to get an [USB serial debug cable](https://www.adafruit.com/product/954). You connect it to the
GPIO pins 14/15.
I'd also recommend to get an [USB serial debug
cable](https://www.adafruit.com/product/954). You connect it to the GPIO pins
14/15.
![UART wiring diagram](doc/wiring.png)
@ -72,14 +91,6 @@ sudo screen /dev/ttyUSB0 115200
Exit screen again by pressing <kbd>ctrl-a</kbd> <kbd>ctrl-d</kbd>
## Emulation
QEMU currently only emulates UART0, so only the tutorials 05 and above will work, as UART1 is *not* redirected by default.
For that, you would have to add something like `-chardev socket,host=localhost,port=1111,id=aux -serial chardev:aux` (thanks
[@godmar](https://github.com/godmar) for the info).
**!!!WARNING!!!** Qemu emulation is rudimentary, only the most common peripherals are emulated! **!!!WARNING!!!**
## About this repository
The tutorial is basically a combination of two awesome resources.
@ -94,39 +105,50 @@ The tutorial is basically a combination of two awesome resources.
## About the hardware
There are lots of pages on the internet describing the Raspberry Pi 3 hardware in detail, so I'll be brief and
cover only the basics.
There are lots of pages on the internet describing the Raspberry Pi 3 hardware
in detail, so I'll be brief and cover only the basics.
The board is shipped with a [BCM2837 SoC](https://github.com/raspberrypi/documentation/tree/master/hardware/raspberrypi/bcm2837) chip.
That includes a
The board is shipped with a [BCM2837
SoC](https://github.com/raspberrypi/documentation/tree/master/hardware/raspberrypi/bcm2837)
chip. That includes a
- VideoCore GPU
- ARM-Cortex-A53 CPU (ARMv8)
- Some MMIO mapped pheripherals.
Interestingly the CPU is not the main processor on the board. When it's powered up, first GPU runs. When it's
finished with the initialization by executing the code in bootcode.bin, it will load and execute the start.elf executable.
That's not an ARM executable, but compiled for the GPU. What interests us is that start.elf looks for different
ARM executables, all starting with `kernel` and ending in `.img`. As we're going to program the CPU in AArch64 mode,
we'll need `kernel8.img` only, which is the last to look for. Once it's loaded, the GPU triggers the reset line on
the ARM processor, which starts executing code at address 0x80000 (or more precisely at 0, but the GPU puts an ARM
jump code there first).
The RAM (1G for the Raspberry Pi 3) is shared among the CPU and the GPU, meaning one can read what the other has
written into memory. To avoid confusion, a well defined, so called [mailbox interface](https://github.com/raspberrypi/firmware/wiki/Mailboxes)
is established. The CPU writes a message into the mailbox, and tells the GPU to read it. The GPU (knowing that the
message is entirely in memory) interprets it, and places a response message at the same address. The CPU has
to poll the memory to know when the GPU is finished, and then it can read the response.
Similarily, all peripherals communicates in memory with the CPU. Each has it's dedicated memory address starting from
0x3F000000, but it's not in real RAM (called Memory Mapped IO). Now there's no mailbox for peripherals, instead each
device has it's own protocol. What's common for these devices that their memory must be read and written in 32 bit
units at 4 bytes aligned addresses (so called words), and each has control/status and data words. Unfortunately
Broadcom (the manufacturer of the SoC chip) is legendary bad at documenting their products. The best we've got is the
BCM2835 documentation, which is close enough.
There's also a Memory Management Unit in the CPU which allows creating virtual address spaces. This can be programmed
by specific CPU registers, and care must be taken when you map these MMIO addresses into a virtual address space.
Interestingly the CPU is not the main processor on the board. When it's powered
up, first GPU runs. When it's finished with the initialization by executing the
code in bootcode.bin, it will load and execute the start.elf executable. That's
not an ARM executable, but compiled for the GPU. What interests us is that
start.elf looks for different ARM executables, all starting with `kernel` and
ending in `.img`. As we're going to program the CPU in AArch64 mode, we'll need
`kernel8.img` only, which is the last to look for. Once it's loaded, the GPU
triggers the reset line on the ARM processor, which starts executing code at
address 0x80000 (or more precisely at 0, but the GPU puts an ARM jump code there
first).
The RAM (1G for the Raspberry Pi 3) is shared among the CPU and the GPU, meaning
one can read what the other has written into memory. To avoid confusion, a well
defined, so called [mailbox
interface](https://github.com/raspberrypi/firmware/wiki/Mailboxes) is
established. The CPU writes a message into the mailbox, and tells the GPU to
read it. The GPU (knowing that the message is entirely in memory) interprets it,
and places a response message at the same address. The CPU has to poll the
memory to know when the GPU is finished, and then it can read the response.
Similarily, all peripherals communicates in memory with the CPU. Each has it's
dedicated memory address starting from 0x3F000000, but it's not in real RAM
(called Memory Mapped IO). Now there's no mailbox for peripherals, instead each
device has it's own protocol. What's common for these devices that their memory
must be read and written in 32 bit units at 4 bytes aligned addresses (so called
words), and each has control/status and data words. Unfortunately Broadcom (the
manufacturer of the SoC chip) is legendary bad at documenting their
products. The best we've got is the BCM2835 documentation, which is close
enough.
There's also a Memory Management Unit in the CPU which allows creating virtual
address spaces. This can be programmed by specific CPU registers, and care must
be taken when you map these MMIO addresses into a virtual address space.
Some of the more interesting MMIO addresses are:
```

Loading…
Cancel
Save