いよいよCortex-A53向けの環境を立ち上げていく。
ボードの選定
QEMUでサポートしているマシンリストは以下のコマンドで取得できる。
$ qemu-system-aarch64 -machine help
Supported machines are:
akita Sharp SL-C1000 (Akita) PDA (PXA270)
ast2500-evb Aspeed AST2500 EVB (ARM1176)
ast2600-evb Aspeed AST2600 EVB (Cortex-A7)
(省略)
raspi0 Raspberry Pi Zero (revision 1.2)
raspi1ap Raspberry Pi A+ (revision 1.1)
raspi2b Raspberry Pi 2B (revision 1.1)
raspi3ap Raspberry Pi 3A+ (revision 1.0)
raspi3b Raspberry Pi 3B (revision 1.2)
(省略)
A53をサポートしているボードが意外と少ない。当初virt-6.2というQEMUが提供する仮想マシンの利用を考えたが、UARTがあるのかどうかよくわからなかったので、結局ラズパイ3Bにした。
続いてラズパイの仕様確認。3BではBroadcom BCM2837というSoCを使用しているため、これの仕様書を調べる。
https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf
UART(PL011)は13章に記載があり、オフセットアドレスは0x7E2_0100…なのだが、結局これでは動かなかった。この仕様書表紙こそBCM2837って書いてあるけど2ページ目以降のヘッダがBCM2835になっている。
どうもBCM2837のデータシートは公開されていないらしく、上位アドレスを0x3Fに読み替える必要があるらしい。さらに、0x7E2_0100は多分誤植で正しくは0x7E20_1000と思われる。ということでラズパイ3BのUART(PL011)のオフセットアドレスは0x3F20_1000として、以降のポーティングを進める。
ソフトウェアのポーティング
上記で見たように、UARTDRレジスタのオフセットアドレスを書き換える。ほかはそのまま。
volatile unsigned int * const UART0DR = (unsigned int *)0x3F201000;
void print_uart0(const char *s) {
while(*s != '\0') { /* Loop until end of string */
*UART0DR = (unsigned int)(*s); /* Transmit char */
s++; /* Next char */
}
}
void c_entry() {
print_uart0("Hello world!\n");
}
つづいてstartup.sの書き換え。前回のようにスタックポインタに即値をロードできないらしく、汎用レジスタを介してスタックポインタの初期化を行う。
.global _Reset
_Reset:
LDR x0, =stack_top
MOV sp, x0
BL c_entry
B .
リンカはそのまま。これでビルド。
aarch64-none-elf-as -mcpu=cortex-a53 -g startup.s -o startup.o
aarch64-none-elf-gcc -c -mcpu=cortex-a53 -g test.c -o test.o
aarch64-none-elf-ld -T test.ld test.o startup.o -o test.elf
aarch64-none-elf-objcopy -O binary test.elf test.bin
動作確認
以下のコマンドで実行。マシンとしてラズパイ3Bを指定している。
$ qemu-system-aarch64 -M raspi3b -cpu cortex-a53 -nographic -kernel test.elf
Hello world!
HHHello world!
QEMU: Terminated
とりあえず動いたものの、表示が何やらおかしい。ラズパイ3BはCA53の4コア構成だが、これが関係しているのだろうか。
CPUIDが0でなければスリープさせるよう、startup.sを書き換えることにする。
肝になるのはMPIDR_EL1というシステムレジスタで、これの下位ビットを見て特定のCPU以外はwfe(wait for event)命令を実行する。
https://developer.arm.com/documentation/ddi0500/j/System-Control/AArch64-register-descriptions/Multiprocessor-Affinity-Register
以下が変更後のstartup.s。適宜コメントを入れている。
.global _Reset
_Reset:
// Initialization of stack pointer (sp)
LDR x0, =stack_top
MOV sp, x0
// sleep other than core0
mrs x0, mpidr_el1 /* Load MPIDR to x0 */
and x0, x0, #0xffff /* x0 = MPIDR[15:0] = MPIDR.CPU_ID */
cmp x0, #0x0 /* check CPU_ID (0x0: core0) */
b.ne sleep_core /* sleep core 1-3 */
// entry main func
BL c_entry
B .
// for core 1-3
sleep_core:
wfe
b .
追加したのは// sleep other than core0の部分で、CPUIDを取得し0でなければsleep_coreにブランチさせる。sleep_coreではwfe命令で省電力状態に移行後、無限ループで待機させている。
システムレジスタであるMPIDR_EL1には直接アクセスすることができず、専用のロード命令であるmrsでいったん汎用レジスタにロードする必要がある。
この状態で再度実行してみると、Hello world!は一回しか表示されなくなった。
$ qemu-system-aarch64 -M raspi3b -cpu cortex-a53 -nographic -kernel test.elf
Hello world!
QEMU: Terminated