Raspberry Pi Bare Bones: Difference between revisions

m
Use {{main}}
[unchecked revision][unchecked revision]
m (Use {{main}})
 
(20 intermediate revisions by 6 users not shown)
Line 14:
 
== Building a Cross-Compiler ==
:''Main article: [[{{main|GCC Cross-Compiler]], [[|Why do I need a Cross Compiler?]]}}
 
The first thing you should do is set up a [[GCC Cross-Compiler]] for '''arm-none-eabi'''. You have not yet modified your compiler to know about the existence of your operating system, so we use a generic target called arm-none-eabi, which provides you with a toolchain targeting the [[System V ABI]].
Line 38:
 
The environment is set up and execution to _start transferred from [https://github.com/raspberrypi/tools/blob/master/armstubs/armstub.S#L35 armstub.s].
<sourcesyntaxhighlight lang="text">
// AArch32 mode
 
Line 51:
// r15 -> should begin execution at 0x8000.
// r0 -> 0x00000000
// r1 -> 0x00000C42 - machine id
// r2 -> 0x00000100 - start of ATAGS
// preserve these registers as argument for kernel_main
Line 84:
wfe
b halt
</syntaxhighlight>
</source>
 
The section ".text.boot" will be used in the linker script to place the boot.S as the very first thing in our kernel image. The code initializes a minimum C environment, which means having a stack and zeroing the BSS segment, before calling the kernel_main function. Note that the code avoids using r0-r2 so the remain valid for the kernel_main call.
Line 90:
You can then assemble boot.S using:
 
<sourcesyntaxhighlight lang="bash">
arm-none-eabi-gcc -mcpu=arm1176jzf-s -fpic -ffreestanding -c boot.S -o boot.o
</syntaxhighlight>
</source>
 
===Pi 2===
Line 99:
 
The environment is set up and execution to _start transferred from [https://github.com/raspberrypi/tools/blob/master/armstubs/armstub7.S#L167 armstub7.s].
<sourcesyntaxhighlight lang="text">
// AArch32 mode
 
Line 152:
wfe
b halt
</syntaxhighlight>
</source>
 
You can assemble boot.S using:
 
<sourcesyntaxhighlight lang="bash">
arm-none-eabi-gcc -mcpu=cortex-a7 -fpic -ffreestanding -c boot.S -o boot.o
</syntaxhighlight>
</source>
 
===Pi 3, 4===
Line 164:
It worth mentioning that Pi 3 and 4 normally boots kernel8.img into 64 bit mode, but you can still use AArch32 with kernel7.img for backward compatibility. Note that in 64 bit mode, the boot code is loaded at 0x80000 and not 0x8000. The boot code in AArch64 is exactly the same for Pi 3 and 4, however Pi 4 has a different peripheral base address (see below the C example code).
 
With the latest firmware, only the primary core runs (core 0), and the secondary cores are awaiting in a spin loop. To wake them up, write a function's address at 0xE0 (core 1), 0xE8 (core 2) or 0xF0 (core 3) and they will start to execute that function.
<source lang="text">
 
The environment is set up and execution to _start transferred from [https://github.com/raspberrypi/tools/blob/master/armstubs/armstub8.S#L154 armstub8.s].
<syntaxhighlight lang="text">
// AArch64 mode
 
Line 188 ⟶ 191:
ldr x5, =__bss_start
ldr w6, =__bss_size
31: cbz w6, 4f2f
str xzr, [x5], #8
sub w6, w6, #1
cbnz w6, 3b1b
 
// jump to C code, should not return
42: bl kernel_main
// for failsafe, halt this core too
halt:
b 1b
wfe
</source>
b halt
 
</syntaxhighlight>
 
Compile your code with:
 
<sourcesyntaxhighlight lang="bash">
aarch64-elf-as -c boot.S -o boot.o
</syntaxhighlight>
</source>
 
== Implementing the Kernel ==
Line 211 ⟶ 217:
=== Freestanding and Hosted Environments ===
 
If you have done C or C++ programming in user-space, you have used a so-called Hosted Environment. Hosted means that there is a C standard library and other useful runtime features. Alternatively, there is the Freestanding version, which is what we are using here. Freestanding means that there is no C standard library, only what we provide ourselves. However, some header files are actually not part of the C standard library, but rather the compiler. These remain available even in freestanding C source code. In this case we use <stddef.h> to get size_t & NULL and <stdint.h> to get the intx_t and uintx_t datatypes which are invaluable for operating systems development, where you need to make sure that the variable is of an exact size (if we used a short instead of uint16_t and the size of short changed, our VGA driver herecode would break!). Additionally you can access the <float.h>, <iso646.h>, <limits.h>, and <stdarg.h> headers, as they are also freestanding. GCC actually ships a few more headers, but these are special purpose.
 
=== Writing a kernel in C ===
 
The following shows how to create a simple kernel in C. Please take a few moments to understand the code. To set the value for "int raspi" in run-time, see [[Detecting_Raspberry_Pi_Board|detecting the board type]].
 
<sourcesyntaxhighlight lang="cpp">
#include <stddef.h>
#include <stdint.h>
 
static uint32_t MMIO_BASE;
// board type, raspi2
 
int raspi = 2;
// The MMIO area base address, depends on board type
static inline void mmio_init(int raspi)
{
switch (raspi) {
case 2:
case 3: MMIO_BASE = 0x3F000000; break; // for raspi2 & 3
case 4: MMIO_BASE = 0xFE000000; break; // for raspi4
default: MMIO_BASE = 0x20000000; break; // for raspi1, raspi zero etc.
}
}
 
// Memory-Mapped I/O output
static inline void mmio_write(uint32_t reg, uint32_t data)
{
*(volatile uint32_t*)(MMIO_BASE + reg) = data;
}
 
Line 233 ⟶ 249:
static inline uint32_t mmio_read(uint32_t reg)
{
return *(volatile uint32_t*)(MMIO_BASE + reg);
}
 
Line 245 ⟶ 261:
enum
{
// The GPIO registers base address.
switch (raspi) {
case 2:
case 3: GPIO_BASE = 0x3F200000; break; // for raspi2 & 3
case 4: GPIO_BASE = 0xFE200000; break; // for raspi4
default: GPIO_BASE = 0x20200000; break; // for raspi1, raspi zero etc.
}
 
// The offsets for reach register.
GPIO_BASE = 0x200000,
 
// Controls actuation of pull up/down to ALL GPIO pins.
Line 262 ⟶ 271:
 
// The base address for UART.
UART0_BASE = 0x3F201000(GPIO_BASE + 0x1000), // for raspi4 0xFE201000, raspi2 & 3 0x3F201000, and 0x20201000 for raspi1
 
// The offsets for reach register for the UART.
Line 283 ⟶ 292:
UART0_ITOP = (UART0_BASE + 0x88),
UART0_TDR = (UART0_BASE + 0x8C),
 
// The offsets for Mailbox registers
MBOX_BASE = 0xB880,
MBOX_READ = (MBOX_BASE + 0x00),
MBOX_STATUS = (MBOX_BASE + 0x18),
MBOX_WRITE = (MBOX_BASE + 0x20)
};
 
// A Mailbox message with set clock rate of PL011 to 3MHz tag
void uart_init()
volatile unsigned int __attribute__((aligned(16))) mbox[9] = {
9*4, 0, 0x38002, 12, 8, 2, 3000000, 0 ,0
};
 
void uart_init(int raspi)
{
mmio_init(raspi);
 
// Disable UART0.
mmio_write(UART0_CR, 0x00000000);
Line 308 ⟶ 330:
// Divider = UART_CLOCK/(16 * Baud)
// Fraction part register = (Fractional part * 64) + 0.5
// UART_CLOCK = 3000000; Baud = 115200.
 
// For Raspi3 and 4 the UART_CLOCK is system-clock dependent by default.
// Set it to 3Mhz so that we can consistently set the baud rate
if (raspi >= 3) {
// UART_CLOCK = 30000000;
unsigned int r = (((unsigned int)(&mbox) & ~0xF) | 8);
// wait until we can talk to the VC
while ( mmio_read(MBOX_STATUS) & 0x80000000 ) { }
// send our message to property channel and wait for the response
mmio_write(MBOX_WRITE, r);
while ( (mmio_read(MBOX_STATUS) & 0x40000000) || mmio_read(MBOX_READ) != r ) { }
}
 
// Divider = 3000000 / (16 * 115200) = 1.627 = ~1.
Line 351 ⟶ 385:
 
#ifdef AARCH64
// arguments for AArch64
void kernel_main(uint64_t dtb_ptr32, uint64_t x1, uint64_t x2, uint64_t x3)
{
// Declare as unused
(void) dtb_ptr32;
(void) x1;
(void) x2;
(void) x3;
#else
// arguments for AArch32
void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
#endif
{
// initialize UART for Raspi2
// Declare as unused
uart_init(2);
(void) r0;
(void) r1;
(void) atags;
#endif
 
uart_init();
uart_puts("Hello, kernel World!\r\n");
 
Line 373 ⟶ 399:
uart_putc(uart_getc());
}
</syntaxhighlight>
</source>
 
The GPU bootloader passes arguments to the AArch32 kernel via r0-r2 and the boot.S makes sure to preserve those 3 registers. They are the first 3 arguments in a C function call. The argument r0 contains a code for the device the RPi was booted from. This is generally 0 but its actual value depends on the firmware of the board. r1 contains the 'ARM Linux Machine Type' which for the RPi is 3138 (0xc42) identifying the BCM2708 CPU. A full list of ARM Machine Types is available from [http://www.arm.linux.org.uk/developer/machines/ here]. r2 contains the address of the ATAGs.
 
For AArch64, the registers are a little bit different, but also passed as arguments to the C function. The first, x0 is the 32 bit address of the DTB (that is, [https://elinux.org/Device_Tree_Reference Device Tree Blob] in memory). Watch out, it is a 32 bit address, the upper bits may not be cleared. The other arguments, x1-x3 are cleared to zero for now, but reserved for future use. Your boot.S should preserve them.
The GPU bootloader passes arguments to the kernel via r0-r2 and the boot.S makes sure to preserve those 3 registers. They are the first 3 arguments in a C function call. The argument r0 contains a code for the device the RPi was booted from. This is generally 0 but its actual value depends on the firmware of the board. r1 contains the 'ARM Linux Machine Type' which for the RPi is 3138 (0xc42) identifying the BCM2708 CPU. A full list of ARM Machine Types is available from [http://www.arm.linux.org.uk/developer/machines/ here]. r2 contains the address of the ATAGs.
 
Notice how we wish to use the common C function strlen, but this function is part of the C standard library that we don't have available. Instead, we rely on the freestanding header <stddef.h> to provide size_t and we simply declare our own implementation of strlen. You will have to do this for every function you wish to use (as the freestanding headers only provide macros and data types).
Line 383 ⟶ 411:
Compile using:
 
<sourcesyntaxhighlight lang="bash">
arm-none-eabi-gcc -mcpu=arm1176jzf-s -fpic -ffreestanding -std=gnu99 -c kernel.c -o kernel.o -O2 -Wall -Wextra
</syntaxhighlight>
</source>
 
or for 64 bit:
 
<sourcesyntaxhighlight lang="bash">
aarch64-elf-gcc -ffreestanding -c kernel.c -o kernel.o -O2 -Wall -Wextra
</syntaxhighlight>
</source>
 
Note that the above code uses a few extensions and hence we build as the GNU version of C99.
Line 401 ⟶ 429:
The linker script for 64 bit mode looks exactly the same, except for the starting address.
 
<sourcesyntaxhighlight lang="text">
ENTRY(_start)
 
Line 446 ⟶ 474:
__end = .;
}
</syntaxhighlight>
</source>
 
There is a lot of text here but don't despair. The script is rather simple if you look at it bit by bit.
Line 454 ⟶ 482:
SECTIONS declares sections. It decides where the bits and pieces of our code and data go and also sets a few symbols that help us track the size of each section.
 
<sourcesyntaxhighlight lang="text">
. = 0x8000;
__start = .;
</syntaxhighlight>
</source>
 
The "." denotes the current address so the first line tells the linker to set the current address to 0x8000 (or 0x80000), where the kernel starts. The current address is automatically incremented when the linker adds data. The second line then creates a symbol "__start" and sets it to the current address.
Line 463 ⟶ 491:
After that sections are defined for text (code), read-only data, read-write data and BSS (0 initialized memory). Other than the name the sections are identical so lets just look at one of them:
 
<sourcesyntaxhighlight lang="text">
__text_start = .;
.text : {
Line 471 ⟶ 499:
. = ALIGN(4096); /* align to page size */
__text_end = .;
</syntaxhighlight>
</source>
 
The first line creates a __text_start symbol for the section. The second line opens a .text section for the output file which gets closed in the fifth line. Lines 3 and 4 declare what sections from the input files will be placed inside the output .text section. In our case ".text.boot" is to be placed first followed by the more general ".text". ".text.boot" is only used in boot.S and ensures that it ends up at the beginning of the kernel image. ".text" then contains all the remaining code. Any data added by the linker automatically increments the current address ("."). In line 6 we explicitly increment it so that it is aligned to a 4096 byte boundary (which is the page size for the RPi). And last line 7 creates a __text_end symbol so we know where the section ends.
Line 477 ⟶ 505:
What are the __text_start and __text_end for and why use page alignment? The 2 symbols can be used in the kernel source and the linker will then place the correct addresses into the binary. As an example the __bss_start and __bss_end are used in boot.S. But you can also use the symbols from C by declaring them extern first. While not required I made all sections aligned to page size. This later allows mapping them in the page tables with executable, read-only and read-write permissions without having to handle overlaps (2 sections in one page).
 
<sourcesyntaxhighlight lang="text">
__end = .;
</syntaxhighlight>
</source>
 
After all sections are declared the __end symbol is created. If you ever want to know how large your kernel is at runtime you can use __start and __end to find out.
Line 487 ⟶ 515:
You can then link your kernel using:
 
<sourcesyntaxhighlight lang="bash">
arm-none-eabi-gcc -T linker.ld -o myos.elf -ffreestanding -O2 -nostdlib boot.o kernel.o -lgcc
arm-none-eabi-objcopy myos.elf -O binary kernel7.img
</syntaxhighlight>
</source>
 
or for 64 bit:
 
<sourcesyntaxhighlight lang="bash">
aarch64-elf-gcc -T linker.ld -o myos.elf -ffreestanding -O2 -nostdlib boot.o kernel.o -lgcc
aarch64-elf-objcopy myos.elf -O binary kernel8.img
</syntaxhighlight>
</source>
 
== Booting the Kernel ==
Line 509 ⟶ 537:
Now mount the first partition from the SD card and look at it:
 
<sourcesyntaxhighlight lang="text">
bootcode.bin fixup.dat kernel.img start.elf
cmdline.txt fixup_cd.dat kernel_cutdown.img start_cd.elf
config.txt issue.txt kernel_emergency.img
</syntaxhighlight>
</source>
 
If you don't have a raspbian image, you can create a FAT32 partition, and [https://github.com/raspberrypi/firmware/tree/master/boot download the firmware files] from the official repository. You'll need only three files:
 
* bootcode.bin: this is the one that's loaded first, executed on the GPU (not needed on RPi4 as that model has bootcode.bin in a ROM)
* fixup.dat: this data file contains important hardware-related information, a must have
* start.elf: this is the RPi firmware (same as BIOS on IBM PC). This also runs on the GPU.
 
Simplified when the RPi powers up the ARM CPU is halted and the GPU runs. The GPU loads the bootloader from ROM and executes it. That then finds the SD card and loads the bootcode.bin (except RPi4 which has a big enough ROM to include bootcode.bin as well). The bootcode loads the firmware, start.elf which handles the config.txt and cmdline.txt. The start.elf loads the kernel*.img and at last the ARM CPU is started running that kernel image.
 
To switch among ARM modes, you have to rename your kernel.img file. If you rename it to '''kernel7.img''', that will be executed in AArch32 mode (ARMv7). For AArch64 mode (ARMv8) you'll have to rename it to '''kernel8.img'''.
Line 528 ⟶ 556:
Your Minicom should then show the following:
 
<sourcesyntaxhighlight lang="text">
Hello, kernel World!
</syntaxhighlight>
</source>
 
=== Testing your operating system (QEMU) ===
Line 540 ⟶ 568:
With QEMU you do not need to objcopy the kernel into a plain binary; QEMU also supports ELF kernels:
 
<sourcesyntaxhighlight lang="text">
$YOURINSTALLLOCATION/bin/qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel kernel.elf
</syntaxhighlight>
</source>
 
==== Updated Support for AArch64 (raspi2, raspi3) ====
{{stub}}
As of QEMU 2.12 (April 2018), emulation for 64-bit ARM, ''qemu-system-aarch64'', now supports direct emulation of both Raspberry Pi 2 and 3 using the machine types '''raspi2''' and '''raspi3''', respectively. This should allow for testing of 64-bit system code.
 
<sourcesyntaxhighlight lang="bash">
qemu-system-aarch64 -M raspi3 -serial stdio -kernel kernel8.img
</syntaxhighlight>
</source>
 
Note that in most cases, there will be few if any differences between 32-bit ARM code and 64-bit ARM, but there can be difference in the way the code behaves, in particular regarding kernel memory management. Also, some AArch64 implementations may support features not found on any of their 32-bit counterparts (e.g., cryptographic extensions, enhanced NEON SIMD support).
Line 570 ⟶ 597:
 
[[Category:ARM]]
[[Category:ARM_RaspberryPiRaspberry Pi]]
[[Category:Bare bones tutorials]]
[[Category:C]]