1. Spróbujemy teraz uruchomić nasz (symulowany) procesor ARM bez systemu operacyjnego, inaczej mówiąc napiszemy program na ,,gołą'' maszynę. Zaczniemy od programu, który nic nie robi (hw-entry.c):
int entry () { return 0; }
2. Musimy go skompilować. Można to zrobić instalując cross-kompilator, łatwiej jednak uruchomić symulator ARMa z Linuxem i tam go skompilować
gcc -c hw-entry.c -o hw-entry.o3. To jednak nie koniec zabawy. Potrzebny będzie program w asemblerze, ustawiający wektory przerwań (hw-startup.s)
.section INTERRUPT_VECTOR, "x" .global _Reset _Reset: B Reset_Handler /* Reset */ B . /* Undefined */ B . /* SWI */ B . /* Prefetch Abort */ B . /* Data Abort */ B . /* reserved */ B . /* IRQ */ B . /* FIQ */ Reset_Handler: LDR sp, =stack_top BL entry B .
Tablica wektorów to tablica skoków do procedur obsługi. Jak widać, obsługujemy tylko reset, wszystko inne kręci się w kółko.
Obsługa reset to wywołanie naszej procedury.
4. Asemblujemy na ARMie przez
as hw-startup.s -o hw-startup.o
5. Trzeba to teraz zlinkować. Ponieważ nie budujemy normalnego programu, musimy w skrypcie powiedzieć linkerowi, co ma robić (hw-boot.ld)
ENTRY(_Reset) SECTIONS { . = 0x0; .text : { hw-startup.o (INTERRUPT_VECTOR) *(.text) } .data : { *(.data) } .bss : { *(.bss COMMON) } . = ALIGN(8); . = . + 0x1000; /* 4kB of stack memory */ stack_top = .; }
Na początku pamięci umieszczamy wektor przerwań i wszystkie sekcje z kodem. Potem sekcje z danymi statycznymi i rezerwacja 4 kB na stos. Linkujemy na ARMie
ld -T hw-boot.ld hw-entry.o hw-startup.o -o hw-boot.elf
5. Powstał plik hw-boot.elf. Można go obejrzeć
objdump -d hw-boot.elf
Plik ELF byłby świetny, gdybyśmy mieli system operacyjny z loaderem. Ale my go nie mamy, a plik ELF zawiera nagłówki, tablice itp. Trzeba go przerobić na czysty plik binarny
objcopy -O binary hw-boot.elf hw-boot.bin
6. I to już wszystko. Przerzucamy plik hw-boot.bin na komputer-matkę i uruchamiamy emulator (runmebare.sh)
qemu-system-arm -M versatilepb -kernel hw-boot.bin
Oczywiście nic się nie stanie, bo nie ma żadnych urządzeń wejścia/wyjścia, ale nie powinno wybuchnąć. Nasz program to najprostszy bootloader, zwykle wbudowany w sprzęt jako firmware w pamięci ROM.
Komplet plików źródłowych jest w katalogu hw-code.
1. Teraz pora na ,,prawdziwy'' program. Oczywiście przywitamy się ze światem. Niestety całkiem goły procesor zwykle nie ma żadnych urządzeń. Ale QEMU potrafi emulować różne systemy, np. Versatile/PB (Versatile Platform Baseboard, chyba już nie produkowany). Oprócz procesora opartego na ARM926EJ-S obejmuje on cztery porty szeregowe (UART). Pierwszy z nich jest pod adresem 0x0101f1000. Przy odpowiedniej opcji potrafi połączyć jego wyjście z terminalem uruchomieniowym.
Nie będziemy sami inicjować tego portu, bowiem skorzystamy z usług bootloadera QEMU. Oczywiście o bibliotece glibc i wywołaniach systemowych należy zapomnieć, więc nasz program wygląda tak (hw2-entry.c)
volatile unsigned char * const UART0_PTR = (unsigned char *)0x0101f1000; void print_uart0 (const char *string) { while (*string != '\0') { *UART0_PTR = *string; string++; } } int entry () { print_uart0("Hello, world!\n"); return 0; }
2. Kompilujemy na ARMie
gcc -c hw2-entry.c -o hw2-entry.o
3. Wbudowany w QEMU bootloader nie pozwoli nam ustawić wektorów przerwań, bowiem rezerwuje sobie początkową część pamięci (ale można ją potem przemapować). Po zakończeniu inicjowania systemu wywołuje kod pod adresem 0x10000. Dlatego nasz kod w asemblerze nie inicjuje wektorów (hw2-startup.s)
.global _MyApp _MyApp: LDR sp, =stack_top BL entry B .
4. Asemblujemy na ARMie jak przedtem
as hw2-startup.s -o hw2-startup.o
5. Trzeba jeszcze rozmieścić program w pamięci, więc pora na konfigurację linkera (qemuboot.ld)
ENTRY(_MyApp) SECTIONS { . = 0x10000; .startup . : { hw2-startup.o(.text) } .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss COMMON) } . = ALIGN(8); . = . + 0x1000; /* 4kB of stack memory */ stack_top = .; }
6. Linkujemy na ARMie
ld -T qemuboot.ld hw2-entry.o hw2-startup.o -o qemuboot.elfi binaryzujemy otrzymanego ELFa
objcopy -O binary qemuboot.elf qemuboot.bin
7. Uruchamiamy spod komputera-matki
qemu-system-arm -M versatilepb -nographic -kernel qemuboot.binNie będzie widać okna QEMU, ale na terminalu zobaczymy powitanie.
I to by było na tyle...
[Źródło: James A. Langbridge ,,Professional Embedded ARM Development'', John Wiley & Sons, Indianapolis 2014]