1. Wstęp
Raspbbery Pi jako platforma edukacyjna dla linuksa okazała się wielkim sukcesem . Dalszy rozwój platformy zaowocował zastosowaniem rdzeni cortex-A53 o architekturze 64bit oraz 4 rdzeniach. Zachęcam jak najbardziej do używania linuksa i jego nauki. Z drugiej strony czy jest możliwe programowanie maliny, niczym mikrokontrolera bez systemu operacyjnego?
Oczywiście, że jest to możliwe, chociaż jest to trochę bardziej złożone, gdyż sercem maliny nie jest cortex-A, tylko VC (VideoCore) od którego wszystko się zaczyna.
2. Proces bootup'u
Na początku wstaje VC, który uruchamia pierwszy bootloader zawarty w pamięci nieulotnej samego VC. Jego zadaniem jest skonfigurować układ aby mógł odczytać kolejny bootloader: bootcode.bin. Ten kawałek kodu zostaje przekopiowany do pamięci L2 i uruchomiony przez VC. Kontynuuje konfigurację (konfiguracja SDRAM) i wczytuje trzeci już bootloader: start.elf. Zostaje zaczytany plik konfiguracyjny config.txt, który jest substytutem BIOS, zostają uruchomione rdzenie cortex-A oraz kernel linuksa zostaje skopiowany do pamięci SDRAM. Kernel linuksa zaczyna być wykonywany przez rdzenie cortex-A53. Piszę tutaj o kernelu linuksa a artykuł ma być na temat bare metal na raspberry Pi 3 A+, tak więc gdzie ten bare metal?
Już odpowiadam. Podmienimy plik kernel7.img naszym własnym kodem.
Więcej o pliku config.txt można przeczytać tutaj:
https://elinux.org/RPiconfig
Oczywiście można również zmodyfikować sam bootcode.bin aby wyświetlić informacje z procesu bootup’u na mini UART:
https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/README.md
3. Narzędzia
Zanim przejdziemy do pisania kodu, musimy uzbroić się w odpowiednie narzędzia:
- Kompilator dla architektury AArch64:
https://developer.arm.com/open-source/gnu-toolchain/gnu-a/downloads
lub
https://www.linaro.org/downloads/
- Emulator terminala (na przykład Termite)
- cygwin (z make), mingw lub inny substytut make na windows (nie dotyczy linuksa)
- Konwerter UART/USB
- quemu
4. Podłączenie konwertera USB/UART
GND konwertera do pinu 6 (GND)
RXD konwertera do pinu 8 (GPIO14 – TXD0)
TXD konwertera do pinu 10 (GPIO15 – RXD0)
5. startup.S
start.S
Kod: text
W przeciwieństwie do cortex-M, cortex-A nie startuje z predefiniowanej sekwencji, a po inicjalizacji przez GPU od razu zaczyna wykonywać kod. Warto zwrócić uwagę na to, że wszystkie 4 rdzenie startują jednocześnie i w aktualnym przykładzie nie wszystkie nam będą potrzebne, dlatego zatrzymujemy je, wchodząc w nieskończoną pętle. Kolejną różnica między cortex-M a cortex-A jest adres stosu. W tym przypadku musimy go samodzielnie ustawić.
Jako, że kernel linuksa zostaje skopiowany do pamięci SRAM, nie mamy potrzeby samodzielnego przenoszenia sekcji .data do RAM, gdyż robi to za nas bootloader. Nadal jednak mamy sekcję .bss, którą należy wyzerować.
6. skrypt linkera
link.ld
Kod: text
Skrypt linkera rozpoczyna wrzucanie kodu pod adres 0x8000 wynikający z konwencji kernela linuksa. W naszym wypadku nie jest wymagane, ale jako że raspberry pi jest tworem dedykowanym dla linuksa, warto się jej trzymać:
http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html
Ważne by sekcja .text.boot była pierwszą sekcją pod adresem 0x8000 oraz aby nie została zoptymalizowana przez AS z powodu braku referencji do tej sekcji, aby temu zapobiec używamy KEEP().
Sekcja .data nie wymaga przenoszenia, gdyż robi to za nas bootloader.
7. main
main.c
Kod: C / C++
W naszym main nie dzieje się za wiele. Najpierw inicjalizujemy mini uart, piszemy słowo „Hello” i wchodzimy w nieskończoną pętle, która będzie odsyłała każdy znak jaki prześlemy po mini uart.
8. Obsługa mini uart
uart.h
Kod: text
uart.c
Kod: C / C++
Bez dokumentacji tym razem się już nie obejdzie. Niestety dokumentacja jest dość skromna i zawiera bardzo wiele błędów to można z niej wygrzebać interesujące nas informacje. Jedno ostrzeżenie, broadcom nie podołał jakże trudnemu zadaniu zmiany rysunków i tyczą się one SoC BCM2835, chociaż tekście są już poprawne adresy.
https://github.com/raspberrypi/documentation/files/1888662/
Co nas interesuje? Adres pod którym zaczynają się rejestry peryferiów:
„Physical addresses range from 0x3F000000 to 0x3FFFFFFF for peripherals.”
Najstarszy bajt się nie zmienia, więc zdefiniujmy go jako:
#define MMIO_BASE 0x3F000000
Dzięki temu pozostałe adresy będą pozbawione ciągle powtarzającego się wspólnego początku.
W rozdziale 2.2.1 mamy opis działania mini uart. Chociaż w dokumentacji mamy adres 0x7E21 5040, ale odnosi się do video core CPU Bus, ale poprzez VC/ARM MMU (memory menagment unit) jest zmapowany jako adres fizyczny ARM 0x3F21 5040.
Reszta kodu jest już bardzo łatwa do analizy i pozostawiam ją Tobie, jako forma ćwiczenia.
9. Makefile, qemu i termite
Kod: text
Tak jak i w poprzednim przykładzie dla cortex-M:
https://www.elektroda.pl/rtvforum/topic3520355.html#17597051
Budujemy nasz kod bez użycia standardowych bibliotek. Nowym elementem jest nowa opcja run. Ta opcja uruchamia emulator qemu, dzięki któremu możemy testować nasz kod bez ciągłego uciążliwego przekładania karty SD.
Po zainstalowaniu qemu i dodaniu go zmiennej środowiskowej PATH możemy wywołać opcję run i przetestować działanie
Aby uruchomić nasz program na malinie musimy przygotować naszą kartę SD według polecenia raspberry pi fundation:
https://www.raspberrypi.org/documentation/installation/sdxc_formatting.md
Następnie kopiujemy na kartę następujące elementy:
- bootcode.bin
- fixup.dat
- start.elf
- kernel8.img
Pierwsze trzy są dostępne tutaj:
https://github.com/raspberrypi/firmware/tree/master/boot
a kernel8.img jest wynikiem kompilacji naszego projektu. Uruchamiamy termite i konfigurujemy go jak na obrazku (com7 będzie mieć inną wartość):
Wsuwamy kartę SD do maliny podpinamy konwerter USB/UART i zasilamy układ. Naszym oczom powinien się pokazać tekst wysłany z maliny:
10. podsumowanie
Pisanie kodu bare metal na raspberry pi jest możliwe i otwiera bardzo wiele możliwości, to niestety sama platforma nie jest przyjazna do tego typu pracy. Jednocześnie jest to stosunkowo tania i popularna platforma by zaznajomić się z rdzeniami cortex-A, które są znacznie bardziej złożone od cortex-M.
Jeśli jednak chcemy zapoznać się w programowaniu układów 64bit, zbudować własny bardziej zaawansowany system operacyjny, potrzeba nam większej mocy obliczeniowej, zacząć zabawę z układami wielordzeniowymi, zapoznać się z cortex-A czy wszystko jednocześnie to myślę że jest to układ dla Ciebie.
W sieci jest wiele dostępnych tutoriali, które pozwolą na lepszym zapoznaniu się z platformą niż mój dość skromny artykuł, który ma tylko zwrócić uwagę na to że tego typu programowanie jest możliwe na raspberry pi i nie jest to żaden „rocket science”.
Linki warte uwagi:
https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/README.md
http://www.valvers.com/open-software/raspberry-pi/step01-bare-metal-programming-in-cpt1/
https://wiki.beyondlogic.org/index.php?title=Understanding_RaspberryPi_Boot_Process
https://github.com/bztsrc/raspi3-tutorial
https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/
https://github.com/s-matyukevich/raspberry-pi-os
