rust-raspberrypi-OS-tutorials/06_raspbootin64
Andre Richter 1ca70bcd56
Check-in kernel8
Useful for assembly inspection via objdump
2018-08-26 20:34:37 +02:00
..
.cargo Correct linker issues 2018-08-20 17:07:18 +02:00
raspi3_boot rust-fmt all 2018-08-26 14:02:07 +02:00
src rust-fmt all 2018-08-26 14:02:07 +02:00
Cargo.lock Remove unused cortex-a imports. 2018-08-24 08:29:13 +02:00
Cargo.toml Bump register to v0.2.0 2018-08-23 23:53:55 +02:00
kernel8 Check-in kernel8 2018-08-26 20:34:37 +02:00
kernel8.img Bump cortex-a to v1.1.0 2018-08-24 00:13:53 +02:00
link.ld Correct linker issues 2018-08-20 17:07:18 +02:00
Makefile Use cargo-xbuild 2018-08-21 21:22:50 +02:00
README.md Correct linker issues 2018-08-20 17:07:18 +02:00

Tutorial 06 - Raspbootin64

We are now at a point where we have a running serial connection, but for each new feature we want to try, we still have to write and exchange the SD card every time.

As this tends to get very annoying and also to avoid potential SD card damage, we create a kernel8.img that will load the real kernel8.img over serial.

This tutorial is a rewrite of the well known serial boot loader raspbootin in 64-bit. We only provide one part of the loader, the kernel receiver, which runs on the RPi. For the other part, the sender, which runs on your PC, we will rely on the original raspbootcom 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

make raspboot

If you want to use it with earlier versions of this tutorial, here is a bash command to invoke it:

docker run -it --rm \
           --privileged -v /dev/:/dev/ \
           -v $PWD:/work -w /work \
           raspi3-utils \
           raspbootcom /dev/ttyUSB0 kernel8.img

In any case, if your USB device is enumerated differently, adapt accordingly.

If you want to send kernels from a Windows machine, I suggest to take a look at John Cronin's rewrite, raspbootin-server which can be compiled for the Win32 API. Even more, @milanvidakovic was so kind to share a Java version of the kernel sender with you.

Chain Loading

In order to load the new kernel to the same address, we have to move ourself out of the way. It's called chain loading: One code loads the next code to the 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:

$ cargo nm -- kernel8 | grep _boot_cores
000000000007f800 T _boot_cores

However, since the GPU loads us to 0x80_000 regardless, as a first action in our binary, we have to copy our code to that link address. This is added to boot_cores.S:

    // relocate our code from load address to link address
    ldr     x1, =0x80000
    ldr     x2, =_boot_cores //<- actual link addr (0x80000 - 2048) from link.ld
    ldr     w3, =__loader_size
3:  ldr     x4, [x1], #8
    str     x4, [x2], #8
    sub     w3, w3, #1
    cbnz    w3, 3b

When we're done, the memory at 0x80_000 is free to use.

We also should minimize the size of the loader, since it will be overwritten by the newly loaded code anyway. By removing Uart::puts() and other functions, we've managed to shrink the loader's size by some bytes.

Position Independent Code (PIC)

For reasons stated above, our code will initially execute from address 0x80_000 despite the binary being actually linked to 0x7f_800. In order to ensure that our binary will not reference hardcoded addresses that actually contain no or wrong data, we need to make this binary position independent. This means that all addresses will always be runtime-computable as an offset to the current Program Counter, and not hardcoded.

To enable PIC for our loader, we add the following line to the compiler flags in the.cargo/config:

[target.aarch64-unknown-none]
rustflags = [
  "-C", "link-arg=-Tlink.ld",
  "-C", "target-feature=-fp-armv8",
  "-C", "target-cpu=cortex-a53",
  "-C", "relocation-model=pic", # <-- New
]

boot_cores.S

In addition to the relocation copying, we also need to adjust the branch instruction that jumps to the reset handler, because we want to jump to the relocated reset handler, not the original one.

Since rustc generates RIP-relative jumps now due to the position independence, we can leverage this feature and add the same offset to the reset address that we implicitly used for the relocation copying (2048). This ensures that we jump to the reset handler in the relocated loader code.

Linker and Boot Code

We use a different linking address this time. We calculate our code's size to know how many bytes we have to copy.

Additionally, we can remove the bss section entirely, since our loader does not use any static variables.

main.rs

We print 'RBIN64', receive the new kernel over serial and save it at the memory address where the start.elf would have been loaded it. When finished, we restore the arguments and jump to the new kernel using an absolute address.