OpenAMP on ZYBO (Zynq) 1回目: とりあえずサンプルを動かす

- __[1回目: とりあえずサンプルを動かす](https://qiita.com/take-iwiw/items/ff46792eb107d978cd0b) <--- 今回の内容__
- [2回目: 通信方法やメモリマップについて](https://qiita.com/take-iwiw/items/3922ec5cae6aa35c5e81)
- [3回目: How to Debug Linux Application](https://qiita.com/take-iwiw/items/8b589650c15a6f705f35)
- [4回目: How to Debug Baremetal Firmware](https://qiita.com/take-iwiw/items/5f7c24bd0475274ded23)

# 本記事について
Linux + ベアメタル(or FreeRTOS)のマルチ環境を、OpenAMPを使用してZYBOで実現させます。

- CPU0: Linux
- CPU1: ベアメタル (or FreeRTOS)

基本的には、[ug1186-zynq-openamp-gsg_2018.pdf](https://www.xilinx.com/support/documentation/sw_manuals/xilinx2017_3/ug1186-zynq-openamp-gsg.pdf)の内容と同じです。ただし、このドキュメントはZCU102ボードをターゲットにしていることと、あらかじめBSPが用意されている前提で書かれています。本記事では、ZYBOを対象に、作業内容が一から(ハードウェア作成から)分かるように記載しました。

まずは、サンプルプログラムである、`echo_test`を動かせるようにします。

# 環境
- 開発用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) 

手順は一から記載します。ただし、VivadoとPetaLinuxの基本的な説明は省略します。必要に応じて、[こちら](https://qiita.com/take-iwiw/items/6e6915f7318689818368)の記事を参照してください。

# 事前知識
まず最初に事前知識に説明です。具体的なやり方を先に知りたい方は、[ここ](https://qiita.com/take-iwiw/items/ff46792eb107d978cd0b#openamp%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9Famp%E7%92%B0%E5%A2%83%E3%81%AE%E6%A7%8B%E7%AF%89)に飛んでください。

## マルチプロセッサのコンフィギュレーション (AMP/SMP)
ZYBOにはCortex-A9のコアが2つ搭載されています(CPU0, CPU1)。
_なお、本記事内でCPU, コア, プロセッサといった単語は全て同じ意味とします(本来は区別すべきですが、元ドキュメントや図に合わせました)。_

### AMP (ベアメタル/FreeRTOS)
XSDKで、ベアメタル(OS無し)またはFreeRTOSとしてプロジェクトを作成した場合は、シングルコア動作となります。CPU0とCPU1のどちらで動かすかはプロジェクト作成時に指定できます。また、XSDK上で2つプロジェクトを作り、FSBLやローダの設定をちゃんとすることで、CPU0, CPU1でそれぞれ別々のファームウェアを動かすことが出来ます。この場合は、AMP(Asymmetrical multi processing)動作となり、各ファームウェアは決められたCPUでのみ動作します。このとき、L1キャッシュのコヒーレンシも保たれません。ちなみに、L2キャッシュがどうなるかは不明。

![01.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/4dfa3a3b-b22c-07d7-1bda-088bcef59508.jpeg)

### SMP (Linux)
PetaLinuxで、通常通りにLinuxイメージを作成した場合は、SMP(Symmetrical multi processing)動作となります。通常のPCと同じ動きになります。Linux上の各プロセスは、CPUの空き状況や優先度に応じて、CPU0,CPU1に動的にスケジューリングされます。同じプロセスでも、ある時はCPU0で動き、ある時はCPU1で動くというようになります。このとき、動作するCPUが変わってもL1キャッシュのコヒーレンシは保たれます。

### AMP (Linuxとベアメタル(or FreeRTOS))
開発するプロダクトの性質に応じて、Linuxとベアメタルを動かしたいというケースが出てきます。例えば、以下のような処理が必要な場合が考えられます。

- Linux: USBやネットワーク周りの処理。UI関係の処理
- ベアメタル: リアルタイム性の高いデバイス制御

このとき、例えば、CPU0でLinuxを動かし、CPU1でベアメタルを動かしたい、となります。また通常は、各CPUで動作する処理は協調して動く必要があります。そのため、CPU間通信機能も必要になります。
これらを実現するために、__OpenAMP__ を使用します。

![02.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/c246d4c4-2972-9718-8bc5-90381ac8eb05.jpeg)

### その他
巷には、Linux上の一つのプロセスとしてベアメタルプログラムやRTOSタスクを走らせたり、逆にRTOS上でLinuxを動かすといった方法もあります。

また、ハイパーバイザを使うという方法もあるそうです。


## OpenAMP
OpenAMPとは、[The Multicore Association](https://www.multicore-association.org/workgroup/oamp.php)によって管理されている、マルチプロセッサ用のオープンなフレームワークです。
OpenAMPによって、以下のことが可能になります(他にも色々あると思うが、今回使うものだけ)。

- Linuxからベアメタル(or FreeRTOS)用ファームウェア(elfバイナリ)のロードを行う
- 通信機能の提供

重要な点として、OpenAMPを使うときは、ベースとなるOSはLinuxになります。まずCPU0(ホストプロセッサ)でLinuxを起動して、CPU1(リモートプロセッサ)にベアメタル側のファームウェア(リモートアプリケーション)をロードします。

### OpenAMP コンポーネント
OpenAMPは下記3つのコンポーネントを持ちます。

- virtIO
	- 共有メモリの管理
- remoteproc
	- リモートプロセッサのライフサイクル管理。ファームウェアをロードしたりする
- RPMsg
	- 異なるコアで動作するプロセス間の通信機能(IPC: Inter-Process Communication)

### Libmetalライブラリ
LibmetalはOpenAMPによって提供されるライブラリです。LibmetalはLinux, FreeRTOS, ベアメタル環境で使用可能です。
Libmetalライブラリが、デバイスアクセス、割り込み、メモリアクセスといったハードウェアを抽象化したAPIを提供します。
OpenAMPの各コンポーネントがLibmetalライブラリを使用します。

![05_2.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/cc80ab70-cc26-bde4-8a0d-cc7154f76c0a.jpeg)

### プロセッサ間のメッセージ通信の仕組み
![03.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/d698a2cf-4448-bd8c-7fe4-fadc4e1cde69.jpeg)

基本的なプロセッサ間通信の流れとしては、以下のような感じです。

1. 送信側プロセッサが、共有メモリ(Shared Memory)にデータを書く
2. 送信側プロセッサが、プロセッサ間割り込み(IPI: Inter Processor Interrupt)を発生させ、受信側プロセッサに通知する


# OpenAMPを使用したAMP環境の構築
ここから、実際にLinux+ベアメタルのAMP環境を作っていきます。作業の流れは以下のようになります。

1. ハードウェアの作成 (Vivado)
	- 特別なことは不要
2. ベアメタル (or FreeRTOS) 用ファームウェアの作成 (XSDK)
3. Linuxイメージの作成 (PetaLinux)
	- OpenAMPを有効にする
	- 2で作成したファームウェアをインストールする
	- デバイスツリーの編集   

# ハードウェアの用意 (on Vivado)
まずVivadoで、PSだけを配置したハードウェアを作ります。デザインの名前はデフォルトのままで、`design_1`にします。また、必要に応じてEthernet 0の設定を修正しておいてください。[こちらを参照](https://qiita.com/take-iwiw/items/ac489acaca1ab54d7ce8)。

![04.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/ed7984da-7cfc-3301-fbf9-27742af91c55.jpeg)

hdfをエクスポートして(ビットストリーム込みで)、Ubuntu側にコピーしておきます。コピー先は`~/work/peta/design_1_wrapper.hdf`とします。
また、次の作業のために、メニューバー -> File -> Launch SDKで、XSDKを起動します。

# ベアメタルファームウェアの作成 (on XSDK)
## echo_test用サンプルプロジェクトの作成
メニューバー -> File -> New -> Application Projectで、新しいプロジェクトを作成します。重要な設定は、CPU1(ps7_cortexa9_1)で動作させる点です。

- Project Name: my_echo_test (何でもいい)
- OS Platform: standalone
- Hardware Platform: design_1_wrapper_hw_platform_0 (Vivadoで作成したもの)
- Processor: ps7_cortexa9_1  ⇐ __重要__
- Language: C
- Templates: OpenAMP echo-test

## bspをカスタマイズする
Project Explorer -> my_echo_test_bspで右クリック -> Board Support Package Settingsをクリック。

![05.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/890aa5f6-4cf1-44a1-0028-f42ad7590cad.jpeg)
![06.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/d4ec25f8-5bdf-a070-aa29-a0c291c7d3a9.jpeg)

ps7_cortexa9_1を選択して、extra_compiler_flagsのValueに` -DUSE_AMP=1`を追加します。

また、openampを選択して、WITH_PROXYをfalseに設定します。これは、Remote Procedure Call (RPC)機能を使う時にはtrueにする必要があります。今回は使用しないので、falseにします。(が、別にデフォルトのままでも大丈夫なようです。コードサイズが増えるだけっぽい)

## ファームウェアの作成
ソースコードは何も変えずに、ビルドします。(デフォルトでオートビルドになっている)

作成された`my_echo_test.elf`をUbuntu側にコピーしておきます。


# Linuxイメージを作る (on PetaLinux in Ubuntu)
## PetaLinuxプロジェクトを作る
下記コマンドで、PjOpenAmpというプロジェクトを作成します。そして、先ほどVivadoで作成したハードウェアで設定をします。コンフィグ設定画面では何もせずにExitします。

```sh:コマンド
cd ~/work/peta
petalinux-create --type project --template zynq --name PjOpenAmp
cd PjOpenAmp
petalinux-config --get-hw-description=~/work/peta
```

## Kernelコンフィグ
Kernelのコンフィギュレーションをします。ただ、これはデフォルト設定でOKなはずなので、確認だけ行います。

```sh:コマンド
petalinux-config -c kernel
```

下記設定が有効になっていることを確認してください。

- Enable loadable module supportが、`[*]`
- Device Drivers > Generic Driver Options > Userspace firmware loading supportが、`-*-`
- Device Drivers > Remoteproc drivers > Support ZYNQ remoteprocが、`<M>`

![07.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/7e74e173-7de6-91d7-6c9a-f676647004b7.jpeg)


## rootfsコンフィグ
rootfsのコンフィギュレーションをします。

```sh:コマンド
petalinux-config -c rootfs
```

下記のように設定してください。libmetalとOpenAMPを有効にします。また、テスト用にテストアプリをインストールします(これはなくてもOK)。もしかしたら、packagegroup-petalinux-openampだけで大丈夫かもしれません。

- Filesystem Packages > misc > sysfsutils > libsysfsを、`[*]`
- Filesystem Packages > libs > libmetal > libmetalを、`[*]`
- Filesystem Packages > misc > packagegroup-petalinux-openamp > packagegroup-petalinux-openampを、`[*]`
- Filesystem Packages > misc > openamp-fw-echo-testd > openamp-fw-echo-testdを、`[*]`
- Filesystem Packages > misc > openamp-fw-mat-muld > openamp-fw-mat-muldを、`[*]`
- Filesystem Packages > misc > openamp-fw-rpc-demo > openamp-fw-rpc-demoを、`[*]`

![08.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/74aa89cf-5dc0-4548-ab61-6653addf72b6.jpeg)


## リモートアプリケーション(ベアメタルファームウェア)を含める
先ほどXSDKで作成したベアメタルファームウェアである、my_echo_test.elfを取り込みます。
まず、MyRemoteAppEchoTestという名前の、ファイル取り込みだけをするアプリケーションを作成します。そこに、バイナリファイルをコピーします。最後に、レシピ(MyRemoteAppEchoTest.bb)を編集します。

これによって、my_echo_test.elfが`/lib/firmware/my_echo_test.elf`にインストールされます。

```sh:コマンド
petalinux-create -t apps --template install -n MyRemoteAppEchoTest --enable
cp ~/work/peta/my_echo_test.elf project-spec/meta-user/recipes-apps/MyRemoteAppEchoTest/files/.
code project-spec/meta-user/recipes-apps/MyRemoteAppEchoTest/MyRemoteAppEchoTest.bb &
```

```txt:MyRemoteAppEchoTest.bb
SUMMARY = "Simple MyRemoteAppEchoTest application"
SECTION = "PETALINUX/apps"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://my_echo_test.elf \
	"

S = "${WORKDIR}"
INSANE_SKIP_${PN} = "arch"

do_install() {
	install -d ${D}/lib/firmware
	install -m 0644 ${S}/my_echo_test.elf ${D}/lib/firmware/my_echo_test.elf
}
FILES_${PN} = "/lib/firmware/my_echo_test.elf"
```

## デバイスツリーを編集する
割り込み番号や、リモートアプリケーション用メモリ領域の設定のため、デバイスツリーを編集します。これは、ug1186-zynq-openamp-gsg_2018.pdfに記載されている内容そのままです。

```sh:コマンド
code project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi &
```

```txt:project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/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>;
	};
};
```

## ビルドしてイメージを完成させる
```sh:コマンド
petalinux-build
petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/design_1_wrapper.bit --u-boot
```

上記コマンドで生成された、`images/linux/BOOT.BIN`と`images/linux/image.ub`を、SDカード(FAT32)にコピーします。

# 自分で作成したエコーテストを試す
先ほど作成したSDカードをZYBOにさして、ZYBOのJP5をSDカード起動に設定します。そして、電源ONします。
UARTターミナルから、以下コマンドを実行します。

```txt:ZYBOのUARTターミナル(開始)
modprobe rpmsg_user_dev_driver
echo my_echo_test.elf > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state
echo_test
```

すると、このように動いていることが分かります。

```txt:echo_test実行結果
root@PjOpenAmp:~# echo_test
 Echo test start
~省略~
 1 - Send data to remote core, retrieve the echo and validate its integrity ..
 2 - Quit this application ..
 CMD>1
 sending payload number 0 of size 9
echo test: sent : 9
 received payload number 0 of size 9
~省略~
 echo test: sent : 478
 received payload number 470 of size 478

 sending payload number 471 of size 479
echo test: sent : 479
 received payload number 471 of size 479
 **************************************
 Test Results: Error count = 0
```

Linux側のアプリケーションとベアメタル側のファームウェアが通信しています。
確認できたらファームウェアを停止します。これによって、OpenAMP動作も停止します。

```txt:ZYBOのUARTターミナル(停止)
echo stop > /sys/class/remoteproc/remoteproc0/state
```

## 備考
おそらく不要ですが、場合によっては `modprobe zynq_remoteproc` が最初に必要かもしれません。
OpenAMPのメッセージ通信用のモジュールをアンロードする場合は、 `modprobe -r rpmsg_user_dev_driver` を実行してください。


## AMPになっていることを確認してみる
起動直後に、/proc/cpuinfoを見ると、CPU0とCPU1があります。しかし、`echo start > /sys/class/remoteproc/remoteproc0/state`後に見ると、CPU0しかないことが分かります。

```sh
root@PjOpenAmp:~# cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 0 (v7l)
-省略-

processor       : 1
model name      : ARMv7 Processor rev 0 (v7l)
-省略-

Hardware        : Xilinx Zynq Platform

-///////////////////////////////////-
-echo start > /sys/class/remoteproc/remoteproc0/state　実行後-
-///////////////////////////////////-

root@PjOpenAmp:~# cat /proc/cpuinfo
processor       : 0
-省略-

Hardware        : Xilinx Zynq Platform
```

# デモアプリを試す
順番が逆になってしまいましたが、自分でファームウェアを作らないでも、デモアプリを使ってOpenAMPを試すことができます。それを実行してみます。

rootfsでopenamp-fw-XXXを有効にしたことで、以下のバイナリ(ファームウェア)が/lib/firmwareにインストールされています。ちなみに、自作のmy_echo_test.elfも同じ場所にインストールされています。

- /lib/firmware/image_echo_test
- /lib/firmware/image_matrix_multiply
- /lib/firmware/image_rpc_demo

## image_echo_testを試す
image_echo_testはmy_echo_test.elfと同じ動作をします。自作のmy_echo_test.elfがうまく動かない場合は、image_echo_testを試すことで、問題の切り分けが出来るかもしれません。

```sh:ZYBOのUARTターミナル
modprobe rpmsg_user_dev_driver
echo image_echo_test > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state
echo_test
echo stop > /sys/class/remoteproc/remoteproc0/state
```

## image_matrix_multiplyを試す
```sh:ZYBOのUARTターミナル
modprobe rpmsg_user_dev_driver
echo image_matrix_multiply > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state
mat_mul_demo
echo stop > /sys/class/remoteproc/remoteproc0/state
```

## image_rpc_demoを試す
`proxy_app`を実行するだけで、ファームウェアのロードなどを行ってくれるようです。そのため、事前に他のファームウェアをロードしていた場合には、終了させておく必要があります。

```sh:ZYBOのUARTターミナル
echo stop > /sys/class/remoteproc/remoteproc0/state
modprobe -r rpmsg_user_dev_driver
proxy_app
```

# おわりに
ひとまずコードの中身や仕組みは置いといて、手順としてOpenAMP環境を作れるようにしました。次回は実際にコードをいじってみようと思います。

# 参考
- [ug1186-zynq-openamp-gsg_2018.pdf](https://www.xilinx.com/support/documentation/sw_manuals/xilinx2017_3/ug1186-zynq-openamp-gsg.pdf)
- http://www.wiki.xilinx.com/Multi-OS+Support+%28AMP+%26+Hypervisor%29

# そういえば
msysやmingwのbinにパスが通っていると、XSDKでOpenAMPプロジェクトを作成したときにフリーズする可能性があるのでご注意ください。