Raspberry Piベアメタルプログラムへのパラメータの渡し方

Raspberry PiのベアメタルプログラムをQEMUで動作させる場合には、シリアルポートはMini-UARTではなく通常のUARTを使う必要があります。
これについて以前書いたときは、コンパイル時に#ifでどちらのUARTを使うかを指定するようにしていました。

これを、コードは同一にして、main関数に与えるパラメータで分岐させられないか? と考えたのが今回の記事です。
結果的には、全く同じバイナリだとちょっと難しそう・・・ということなのですが、調査・実験した結果をまとめておきます。

(1)Raspberry PiおよびQEMUでのパラメータの記述方法

Raspberry Piでは、起動用SDカードに「cmdline.txt」というファイルを置くと、その内容がパラメータとして実行ファイルに渡されます。

The Kernel Command Line – Raspberry Pi Documentation

QEMUでは、-append “string” というオプションを付加することにより、stringが実行ファイルに渡されます。
-appendを使う場合は、実行ファイルは-kernelで指定します。
または、-semihosting-config arg=string というより新しい形式もあります。

QEMU version 2.11.90 User Documentation

実行ファイルにパラメータを渡したい場合、実行ファイルはrawバイナリでなければなりません。ELFは不可で、objcopyでバイナリに変換する必要があります。
これについては、以下の記事に書かれています。

So I downloaded the QEMU source code and found the relevant code in hw/arm_boot.c. The code there is pretty straightforward, and it turns out that QEMU ARM will only install a tag list if it determines kernel to be Linux. This is fine in itself, but the way it figures out if a given kernel is Linux is really stupid – as a comment in the file reads:

/* Assume that raw images are linux kernels, and ELF images are not. */
QEMU ARM boot tags – Season of Code

(2)パラメータを渡すインタフェース(ARM Boot Tags)

上記で記述されたパラメータは、シェルからコマンドを起動する場合のようにargc, argvで渡されるのではありません。
ARMバイナリへパラメータを渡すには、「boot tag」というデータ形式を使用します。

boot tagはTag-Length-Value形式(実際にはLength-Tag-Value)の配列です。
Tagはあらかじめ値が決められており、文字列パラメータを渡すTagは0x54410009です。
また、末尾を表すTagは0と決められています。

先頭のTagのアドレスが、バイナリが実行される際にレジスタR2に渡されます。
ただ、伝統的にこのアドレスは0x100(0xffまではジャンプテーブルなので)に決まっているようです。

Booting ARM Linux

なお、(1)で書いたとおり、QEMUではboot tagは実行ファイルがバイナリ形式のときのみ与えられます。

(3)プログラム開始アドレス

開始アドレスは、実機では0x8000ですが、QEMUでは0x10000になります。QEMUのオプション等では、このアドレスは指定できないようです。
フォーラムの以下の書き込みでは「-kernelと-initrdで同一のバイナリを指定するとアドレスが0x8000になる」というQEMUが存在するように書かれていますが、そのQEMUのリンク先は既に消失していました。

Debugging with qemu? – Raspberry Pi Forums

指定できないのであれば、開始アドレスを0x10000にしたQEMU用のバイナリを作らざるを得ません。
objcopyコマンドにはアドレスのオフセットを与えるオプションがありますが、試してみた限りでは開始アドレスが0x8000のELFから開始アドレスが0x10000のバイナリを作ることはできないようでした。

開始アドレスはリンカスクリプトに書かれています。
これは

SECTIONS {
. = 0x8000;

のように定数になっていますが、これを

SECTIONS {
. = DEFINED(_load_addr) ? _load_addr : 0x8000;

のようにすれば、_load_addrが定義されていればその値を、定義されていなければ0x8000を使うように指定できます。
変数の値はldコマンドのオプションで

--defsym=_load_addr=0x10000

のように与えます。

変数の値をどう与えるかは、Makefileで制御します。
QEMU用と実機用とでMakefileのターゲットを分け、ターゲットによってldコマンドのオプションを変えて、開始アドレスの異なるバイナリを生成するようにしました。

以上で、(やや苦しいですが)QEMU用と実機用のコードをかなり共有することができました。
Raspberry PiベアメタルMicroPythonに適用してみたものが以下のパッチです。

feat: use UART for QEMU when qemu runs img file with -append "qemu" · boochow/micropython-raspberrypi@994849b

cmdlineオプションが「qemu」であればシリアルポートをQEMU用に初期化し、そうでなければ普通にMini-UARTを初期化します。

コメント