- 1回目: とりあえずサンプルを動かす
- 2回目: 通信方法やメモリマップについて <--- 今回の内容
- 3回目: How to Debug Linux Application
- 4回目: How to Debug Baremetal Firmware
本記事について
前回、Linux + ベアメタル(or FreeRTOS)のマルチ環境を、OpenAMPを使用してZYBOで実現させました。
- CPU0: Linux
- CPU1: ベアメタル (or FreeRTOS)
前回はサンプルコードをそのまま使用しました。次回以降、Linux側アプリとベアメタルファームウェアの開発に取り掛かる予定です。その前に、通信方法やメモリ周りに関して、現時点で分かったことをまとめておこうと思います。毎回注意書きを書くのが面倒なので、本記事ではベアメタル(BM)とだけ記載しますが、FreeRTOSでも同じです。
環境
- 開発用PC: Windows 10 64-bit
- Vivado 2017.4 WebPACKライセンス
- Xilinx SDK 2017.4
- 開発用PC (Linux): Ubuntu 16.04 本家 (日本語版じゃない) (on VirtualBox 5.2.4)
- PetaLinux 2017.4
- ターゲットボード: ZYBO (Z7-20)
Linux側
ベアメタルファームウェアの用意
ベアメタル(BM)ファームウェアは、/lib/firmware/XXX.elf
にインストールするのが一般的なようです。インストール方法は1回目の記事をご参照ください。
OpenAMPの始め方
Linux起動後は、LinuxがCPU0とCPU1の両方を使用しているSMP動作になります。AMPモードに入るには、通信用モジュール(rpmsg_user_dev_driver
)のロード、BMファームウェアのファイル指定、AMPの開始指示、をする必要があります。具体的には、下記コマンドになります。
modprobe rpmsg_user_dev_driver
echo XXX.elf > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state
これで、指定されたBMファームウェアがロードされ、main関数から処理が実行されます。その後、必要に応じて、BMと協調動作するLinuxアプリケーションを走らせます。待ち合わせ処理などがないと、エラーが発生するため、BM実行後にLinuxアプリケーションを起動した方がいいです。
start後は、Linux側プロセスはCPU0でのみ実行され、CPU1は管轄外になります。
OpenAMPの終わり方
下記コマンドで、OpenAMP環境を終了します。モジュールのアンロードはなくてもいいです。
echo stop > /sys/class/remoteproc/remoteproc0/state
modprobe -r rpmsg_user_dev_driver
stop後は、Linuxは再度CPU0, CPU1のどちらも使用可能になります。
BMとの通信方法
BMとの通信方法はいくつかあるようですが、ここではrpmsg_user_dev_driverモジュールを使用した方法を記載します。
やり方は非常に簡単で、/dev/rpmsg0
へのwrite/readで実現できます。
例えば、"ABC"という文字列を送信して、その後受信するというシンプルなコードは以下になります。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd = open("/dev/rpmsg0", O_RDWR | O_NONBLOCK);
if (fd < 0) {
perror("open");
return -1;
}
char bufSend[] = "ABC";
char bufRecv[10];
if (write(fd, bufSend, sizeof(bufSend)) < 0) perror("write");
printf("send: %s\n", bufSend);
if (read(fd, bufRecv, sizeof(bufRecv)) < 0) perror("read");
printf("recv: %s\n", bufRecv);
close(fd);
return 0;
}
ちなみに、前回動作確認に使用したecho_testのソースはこちら
https://github.com/Xilinx/meta-openamp/blob/rel-v2017.4/recipes-openamp/rpmsg-examples/rpmsg-echo-test/echo_test.c
注意点
-
/dev/rpmsg0
は、BMファームウェアを開始して、BM側で初期化処理が完了してから、見えるようになります。 - write直後に直ぐにreadすると、デバイスがunavailable状態になってreadできなかった。
- 一度closeすると、
/dev/rpmsg0
を再open出来なくなった。この場合は、再度OpenAMPをstop/startする必要がある。恐らく、同様の理由で、シェルからechoやcatでの操作は出来なかった。
ベアメタル(BM)側
Linux側の処理は非常に簡単でした。複雑なことは全てドライバが吸収してくれています。BM側ではこれらをコードで実装する必要があります。
基本的には、「OpenAMP echo-test」のテンプレートを流用するのが間違いないです。そのうえで、重要な点だけを記載します。
リンカスクリプトの設定
BMファームウェア(コード+ヒープ+スタック)の展開先を指定します。ここでは、0x3E00_0000 ~ 0x3E40_0000を設定しています。メモリ周りの詳細は、後述します。
通信用リソースの設定
前回、軽く説明しましたが、Linux(CPU0)とベアメタル(CPU1)の通信は、1. 共有領域にデータを書いて、2. 割り込みをかける、ことによって実現されます。
割り込み番号 (platform_info.h)
割り込み番号を定義します。デフォルトでは14と15が使用されます。この番号と、Linuxのデバイスツリーで指定する番号は同じである必要があります。変更する場合は、どちらも変更する必要があります。基本的にはデフォルトのままでいいはずです。
#define VRING0_IPI_INTR_VECT 15
#define VRING1_IPI_INTR_VECT 14
共有メモリ (rsc_table.c)
コードから察するに、共有領域をリングバッファのように使用しているようです。各バッファの先頭アドレスを定義しています。この領域は、LinuxとBMのどちらからも使われない空き領域である必要があります。BMのロード先やサイズが変わる際には、変更する必要があります。メモリ周りの詳細は、後述します。
#define RING_TX 0x3e600000
#define RING_RX 0x3e604000
struct remote_resource_table __resource resources = {
- 省略 -
{RSC_RPROC_MEM, 0x3e600000, 0x3e600000, 0x100000, 0},
初期化方法
よく分からないので、サンプルコードをパクる
通信方法
- 受信時には、初期化時に登録したコールバック関数(
rpmsg_read_cb
)が呼ばれる - 送信は、
rpmsg_send
関数を使う。
「送信のみ」ということが出来なかった。サンプルと同様に、受信→送信、ならできた。rpmsg_channelの指定方法が良くないのかもしれない。なぜだかは不明。仕様かどうかも不明。
メモリマップについて
物理的なメモリマップ
ZYBOの物理的なメモリマップは上記のようになります。デバイス周りのアドレスは設定によって変わる可能性がありますが、大体こんな感じです。重要な点は、0x0000_0000 - 0x4000_0000の1GByteがDDRに割り当てられているという点です。シングル環境だと、この領域全体がLinuxやベアメタルファームウェアに割り当てられます。
OpenAMPでのメモリマップ
前回作成したOpenAMP環境のメモリマップは上記のようになります。絶対にこのようにしないといけないというのではなく、一例としてご覧ください。
- 0x3E00_0000-0x3EFF_FFFF以外のDDR領域は全て、Linuxに割り当てられています。
- これは、Linuxのデバイスツリー設定で、0x3E00_0000-0x3EFF_FFFFをreservedにしたためです。
- また、この領域内の0x3E00_0000-0x3E3F_FFFFを、BMファームウェア用に割り当てています。これも、デバイスツリーで設定した通りです。
- BMファームウェアのリンカスクリプトでも同じ領域になるようにする必要があります。
- 結果として、0x3E40_0000-0x3EFF_FFFFは空き領域となります。そのため、この領域を通信用の共有メモリとして使用しています。前述のコードでは、0x3e600000と0x3e604000
アドレスやサイズは変わってもいいと思うのですが、このように矛盾なく各領域が設定される必要があります。設定変更する場合は、この記事で示したBM側のリンカスクリプト、ソースコード、そして、Linux側のデバイスツリー設定を、整合性が取れるように編集してください。
物理メモリマップの確認方法
ちなみに、Linuxで物理的なメモリマップを確認するには、/proc/iomem
を確認します。
root@PjOpenAmp:~# cat /proc/iomem
00000000-3dffffff : System RAM
00008000-008fffff : Kernel code
00a00000-00a6b697 : Kernel data
3e000000-3e3fffff : 3e000000.ddr
3f000000-3fffffff : System RAM
e0001000-e0001fff : xuartps
e000a000-e000afff : /amba/gpio@e000a000
e000b000-e000bfff : /amba/ethernet@e000b000
e000d000-e000dfff : /amba/spi@e000d000
e0100000-e0100fff : /amba/sdhci@e0100000
f8003000-f8003fff : /amba/dmac@f8003000
f8003000-f8003fff : /amba/dmac@f8003000
f8005000-f8005fff : /amba/watchdog@f8005000
f8007000-f80070ff : /amba/devcfg@f8007000
f8007100-f800711f : /amba/adc@f8007100
f800c000-f800cfff : /amba/ocmc@f800c000
fffc0000-ffffffff : f800c000.ocmc
Linux デバイスツリー設定 (再掲)
前回も載せましたが、本記事の内容と密接にかかわるので、デバイスツリー設定を再度貼り付けておきます。
/include/ "system-conf.dtsi"
/ {
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
rproc_0_reserved: rproc@3e000000 {
no-map;
reg = <0x3e000000 0x01000000>;
};
};
amba {
elf_ddr_0: ddr@0 {
compatible = "mmio-sram";
reg = <0x3e000000 0x400000>;
};
};
remoteproc0: remoteproc@0 {
compatible = "xlnx,zynq_remoteproc";
firmware = "firmware";
vring0 = <15>;
vring1 = <14>;
srams = <&elf_ddr_0>;
};
};
よく分かっていない点
通信用の共有メモリのアドレスは、前述の通りBM側のソースコードで設定しています(RING_TX, RING_RX)。が、これをどうやってLinux側に教えているのかが不明。OpenAMPがよしなにやってくれているのだと思うが。。。OCMとか使ってるのかな。