Booting Raspberry Pi 3: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
No edit summary
No edit summary
 
(15 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{Tone}}
{{Spelling}}
{{FirstPerson}}
{{You}}
{{Rating|1}}
This is a tutorial on bare-metal OS development on the Raspberry Pi 3 (RPi 3).


'''THIS IS NOT A PRACTICAL IMPLEMENTATION NOR A REFERENCE! It'S SIMPLY SOMETHING TO BE BUILT UP ON!'''
This is a tutorial on bare-metal OS development on the AArch64 architecture. This article oriented to the Raspberry Pi 3 (RPi 3), but is aimed to be as device agnostic as possible. Therefore, some sections will be specific to the RPi 3 and will be marked to represent that.

This is the author's very first ARM system and wiki page. I learn as I write and will continue to develop this page once I feel comfortable enough at the subjet to share.


== Preamble ==
== Preamble ==
Line 20: Line 24:
https://aur.archlinux.org/packages/aarch64-elf-newlib-linaro-bin/
https://aur.archlinux.org/packages/aarch64-elf-newlib-linaro-bin/


====RPi 3: Firmware====
====Firmware====


Firmware is required for the RPi 3 to boot properly. The firmware needed can be found at these link:
Firmware is required for the RPi 3 to boot properly. The firmware needed can be found at these link:
Line 30: Line 34:
There is also another file required to boot properly. A config.txt file must be supplied to provide configuration details for the device and the OS. Here are the only entries you need:
There is also another file required to boot properly. A config.txt file must be supplied to provide configuration details for the device and the OS. Here are the only entries you need:


<syntaxhighlight lang="ini">
<pre>
boot_delay=1
boot_delay=1
force_turbo=1
force_turbo=1
enable_uart=1
enable_uart=1
</syntaxhighlight>
</pre>


More details about booting and configuration can be found at thse links.
More details about booting and configuration can be found at thse links.
Line 63: Line 67:


'''The start address and alignment are for the RPi3. Use whatever is applicable to you.'''
'''The start address and alignment are for the RPi3. Use whatever is applicable to you.'''
<syntaxhighlight lang="c">
<pre>
SECTIONS
SECTIONS
{
{
. = 0x8000;
. = 0x80000;
.text : {
.text : {
Line 82: Line 86:
.bss (NOLOAD) : {
.bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .;
*(.bss*)
*(.bss*)
. = ALIGN(16);
__bss_nlwords = (. - __bss_start);
}
}
}
}
</syntaxhighlight>
</pre>


== Booting the Kernel ==
== Booting the Kernel ==
Line 95: Line 95:
For the RPi 3, after the start.elf finishes, the CPU and SRAM have been enabled and control is given to the kernel image. But there are a few things we must do in order to set up a basic C environment. This is known as a bootstrap stage which initializes our OS and programming environment on startup and hands control to the kernel. Keep in mind that this is for a minimalistic development environment on a single core.
For the RPi 3, after the start.elf finishes, the CPU and SRAM have been enabled and control is given to the kernel image. But there are a few things we must do in order to set up a basic C environment. This is known as a bootstrap stage which initializes our OS and programming environment on startup and hands control to the kernel. Keep in mind that this is for a minimalistic development environment on a single core.


<syntaxhighlight lang="asm">
When the CPU starts running the kernel, all cores will be executing the same code. This can be tricky for multiple reasons, which will be described later. For now, all we want is just one core running the kernel. To do that, we need get the core identification number and set all but one core to run an infinite loop.

<pre>
.section .boot
.section .boot


Line 103: Line 101:


_start:
_start:
mrs x0, mpidr_el1
mrs x4, mpidr_el1
and x0, x0, #3
and x4, x4, #3
cbz x0, _init
cbz x4, _init
0: wfe
0: wfe
b 0b
b 0b
</syntaxhighlight>
</pre>


The mrs instruction loads data from specialty registers into a standard register. This special register in question is called the MPIDR or the Multiprocessor Affinity Register. Caring only about the first two bits only we use a bitwise and to weed out any core ID's that aren't zero. Then we apply the cbz instruction, which is a shorthand instruction for comparing the x0 register with 0 and branching if they are equal, i.e. core 0.
The mrs instruction loads data from specialty registers into a standard register. This special register in question is called the MPIDR or the Multiprocessor Affinity Register. Caring only about the first two bits only we use a bitwise and to weed out any core ID's that aren't zero. Then we apply the cbz instruction, which is a shorthand instruction for comparing the x4 register with 0 and branching if they are equal, i.e. core 0.


If the core ID is 0, we branch to another section that will initialize the environment and will hand control over to the kernel. For now, the C function for our kernel will simply by kern_main.
If the core ID is 0, we branch to another section that will initialize the environment and will hand control over to the kernel. For now, the C function for our kernel will simply by kern_main.


<syntaxhighlight lang="asm">
<pre>
_init:
_init: ldr x4, =_start
mov sp, x4
b kern_main
b kern_main
</syntaxhighlight>
</pre>

Note that with the latest firmware, this is not necessary, as only the primary core runs. It is enough to set up stack and branch to kern_main.

<syntaxhighlight lang="asm">
// for latest firmware, after 2020-01-01
.section .boot

.global _start

_start: ldr x4, =_start
mov sp, x4
b kern_main
</syntaxhighlight>


== Writing the Kernel ==
== Writing the Kernel ==
Line 123: Line 135:
The kernel is as simple as creating a kern_main function for the bootstrap stage to transfer control to. You notice that in start.S we have the program branch into the function. All we need to do is provide one.
The kernel is as simple as creating a kern_main function for the bootstrap stage to transfer control to. You notice that in start.S we have the program branch into the function. All we need to do is provide one.


<syntaxhighlight lang="c">
<pre>


#include <stdbool.h>
#include <stdbool.h>
Line 132: Line 144:
while (true);
while (true);
}
}
</syntaxhighlight>
</pre>


== Conclusion ==
== Conclusion ==
Line 138: Line 150:
At the time of writing, QEMU does not yet emulate the RPi 3. The only way to test is to hook up the RPi 3 to an HDMI monitor. If start.elf was successfully able to run, then a square with interpolated colors will appear on the screen. That paird with the ACT (green LED) flashing even after the square shows means that it most likely has loaded the kernel and started executing. As this lacks a UART example, it will be difficult to get concrete proof that the kernel has booted.
At the time of writing, QEMU does not yet emulate the RPi 3. The only way to test is to hook up the RPi 3 to an HDMI monitor. If start.elf was successfully able to run, then a square with interpolated colors will appear on the screen. That paird with the ACT (green LED) flashing even after the square shows means that it most likely has loaded the kernel and started executing. As this lacks a UART example, it will be difficult to get concrete proof that the kernel has booted.


====How to Proceed====
== See Also ==

If you are new to operating system development, I encourage you to take the time and read '''Basic Information''' section of OsDev's [https://wiki.osdev.org/Main_Page Main Page]. You should know they developing an operating system, even a microkernel, is difficult. The lack of explicit documentation is enough to frustrate novices and even experienced developers (which is exactly the main purpose of this series). The goal is to be patient and understand that what may be ahead is fun but also challanging. Continuing forward also means that you must be accustomed to and aware of the functionalities an operating system must provide. I also recommend that you take the time to go through the resources in Extra Resources to make sure you are acquainted with operating systems and their engineering for beginners and those who are familiar.


=== Articles ===
That being said, if you have committed to the project then take note that from this point on I will be focusing on RPi 3 development.
* [[Raspberry Pi Bare Bones]] - a much more detailed introduction to booting on Raspberry Pi


[[Category:Raspberry Pi]]
====Extra Resources====
[[Category:Booting]]
* [https://pdos.csail.mit.edu/6.828/2017/xv6/book-rev10.pdf Short book on operating systems with reference implemenation]
* [https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2012/ MIT OpenCourseWare class on operating system engineering]

Latest revision as of 07:38, 17 June 2024

This article's tone or style may not reflect the encyclopedic tone used throughout the wiki.
See Wikipedia's article on tone for suggestions.
This article contains many spelling and grammar mistakes.
You could help out by fixing them.
This article refers to its readers or editors using I, my, we or us.
It should be edited to be in an encyclopedic tone.
This article refers to its readers using you in an unencyclopedic manner.
It should be edited to be in an encyclopedic tone.
Difficulty level

Beginner

This is a tutorial on bare-metal OS development on the Raspberry Pi 3 (RPi 3).

THIS IS NOT A PRACTICAL IMPLEMENTATION NOR A REFERENCE! It'S SIMPLY SOMETHING TO BE BUILT UP ON!

Preamble

This article assumes that you have all of the necessary materials needed to begin developing on your respective hardware. Other assumptions are that you are comfortable with low-level programming using C/C++, GNU Make buildsystem, and that you are comfortable enough about OS development and hardware to not be confused.. Such details are not in the scope of this article and must be done before proceeding to the next steps.

This article is also intended for beginner users who want a minimal solution for creating/starting an operating system, although some insight can be derived for the advanced audience.

Preperations

Cross Compiler

A bare-metal AArch64 toolchain is available on the AUR for installation. The links are provided here as well as a download link from the publisher for Windows users.

https://www.linaro.org/downloads/

https://aur.archlinux.org/packages/aarch64-elf-gcc-linaro-bin/

https://aur.archlinux.org/packages/aarch64-elf-newlib-linaro-bin/

Firmware

Firmware is required for the RPi 3 to boot properly. The firmware needed can be found at these link:

https://github.com/raspberrypi/firmware/blob/master/boot/start.elf

https://github.com/raspberrypi/firmware/blob/master/boot/bootcode.bin

There is also another file required to boot properly. A config.txt file must be supplied to provide configuration details for the device and the OS. Here are the only entries you need:

boot_delay=1
force_turbo=1
enable_uart=1

More details about booting and configuration can be found at thse links.

https://elinux.org/RPi_Software#Overview

https://raspberrypi.stackexchange.com/a/10595

https://www.raspberrypi.org/documentation/configuration/config-txt/

https://wiki.beyondlogic.org/index.php?title=Understanding_RaspberryPi_Boot_Process (although a bit old, is still useful)

When loading the OS onto an SD card, these file must be included in the boot partition.

Overview

By now you should have your cross compiler set up. The compiler binaries have the same name as they usually would with "aarch64-elf-" prefixing them (e.x. aarch64-elf-gcc). For this example, three files are used:

  • linker.ld - Linker script
  • start.S - Setup the environment and call the kernel
  • kernel.c - Kernel entry and use

This article is written so that segments of the code will be presented with an explaination, with the full code in the Conclusion section.

Linking the Kernel

Linker script allows us to segment, organize, and align our kernel image. More information about linker scripts can be found osdev's Linker Scripts.

The start address and alignment are for the RPi3. Use whatever is applicable to you.

SECTIONS
{
    . = 0x80000;
 
    .text : { 
        KEEP(*(.boot)); 
        *(.text*) 
    } 
 
    .rodata ALIGN(16) : { 
        *(.rodata*) 
    } 
 
    .data ALIGN(16) : { 
        *(.data*) 
    } 
 
    .bss (NOLOAD) : { 
        *(.bss*) 
    } 
}

Booting the Kernel

For the RPi 3, after the start.elf finishes, the CPU and SRAM have been enabled and control is given to the kernel image. But there are a few things we must do in order to set up a basic C environment. This is known as a bootstrap stage which initializes our OS and programming environment on startup and hands control to the kernel. Keep in mind that this is for a minimalistic development environment on a single core.

.section .boot

.global _start

_start:
        mrs        x4, mpidr_el1
        and        x4, x4, #3
        cbz        x4, _init
0:      wfe
        b            0b

The mrs instruction loads data from specialty registers into a standard register. This special register in question is called the MPIDR or the Multiprocessor Affinity Register. Caring only about the first two bits only we use a bitwise and to weed out any core ID's that aren't zero. Then we apply the cbz instruction, which is a shorthand instruction for comparing the x4 register with 0 and branching if they are equal, i.e. core 0.

If the core ID is 0, we branch to another section that will initialize the environment and will hand control over to the kernel. For now, the C function for our kernel will simply by kern_main.

_init:  ldr     x4, =_start
        mov     sp, x4
        b       kern_main

Note that with the latest firmware, this is not necessary, as only the primary core runs. It is enough to set up stack and branch to kern_main.

// for latest firmware, after 2020-01-01
.section .boot

.global _start

_start: ldr     x4, =_start
        mov     sp, x4
        b       kern_main

Writing the Kernel

The kernel is as simple as creating a kern_main function for the bootstrap stage to transfer control to. You notice that in start.S we have the program branch into the function. All we need to do is provide one.

#include <stdbool.h>

void
kern_main(void)
{
    while (true);
}

Conclusion

At the time of writing, QEMU does not yet emulate the RPi 3. The only way to test is to hook up the RPi 3 to an HDMI monitor. If start.elf was successfully able to run, then a square with interpolated colors will appear on the screen. That paird with the ACT (green LED) flashing even after the square shows means that it most likely has loaded the kernel and started executing. As this lacks a UART example, it will be difficult to get concrete proof that the kernel has booted.

See Also

Articles