Zedboardで、最小構成のPSでubuntuを動かす - kernel, device tree, u-bootのビルドからHello worldまで

Zed boardのプリセット設定のPSのみをインプリメントしたZynq上でubuntuを動かします。
勉強のために、リファレンスデザインの改造などでなく、一からハードウェアの設計（PSをzed boardプリセット設定にするだけですが笑）、Linuxカーネル、デバイスツリー、u-bootのビルドをやってみました。
映像出力用のIPなど、PS以外のあらゆるハードウェアは含めないので、キーボードやディスプレイは使用できません。シリアルコンソールで操作します。

ディスプレイ、キーボードを使用できるリファレンスデザインについては　http://qiita.com/yuichiroTCY/items/3b792feedb8f55aaef43 を書きました。
そちらのADV7511リファレンスデザインなどを見ながら改造すると良いと思います。
また、この内容がかぶる部分が結構あります。
合わせて読むと良いかもしれません。

# 環境

以下の環境で試しました。

- xubuntu 14.04
- vivado 2015.2

# ハードウェア
vivadoでハードウェアを設計、ビットストリームを作成します。
といっても、普通にZedboardプリセット設定のPSのみのデザインを作成するだけです。特別なことはありません。
一応手順は書いておきますが、本当に普通です。

1. 新規プロジェクトを作成。プロジェクト設定ダイアログのDefault PartページでZed boardを選んでおく。
![dialog.png](https://qiita-image-store.s3.amazonaws.com/0/65141/ef26bd03-3f46-91ab-3f16-4183b9749b40.png)

2. Create Block Design. Design nameは`system`とした。
3. Add IP -> ZYNQ7 Processing System
4. 追加されたZynqコアをダブルクリックして設定ダイアログを表示
5. PresetsからZed boardを選択、OK
6. Run Block Automationを実行
7. FCLK_CLK0とM_AXI_GP0_ACLKを接続
8. Tool -> Validate Designを実行し、エラーが無いことを確認
9. Sourcesペインでsystemを右クリック -> Generate Output Products
10. Sourcesペインでsystemを右クリック -> Create HDL Wrapper
11. Generate Bitstream

ちなみに最終的なデザインはこんな感じです。本当に最小限。
![design.png](https://qiita-image-store.s3.amazonaws.com/0/65141/3ebd0998-a56a-55a1-2ec0-5911efb10e2d.png)

# 必要なソースのダウンロード
ここから一旦Vivadoを離れてシェルで作業します。
適当な作業ディレクトリに移動し、以下のようにXilinxのリポジトリから必要なソースを落としてきます。

```bash
$ git clone git@github.com:Xilinx/linux-xlnx.git
$ git clone git@github.com:Xilinx/u-boot-xlnx.git
$ git clone git@github.com:Xilinx/device-tree-xlnx.git
```

# u-bootのビルド
u-boot-xlnxディレクトリに移動し、適当なリビジョンをチェックアウトします。

```bash
$ cd u-boot-xlnx
$ git checkout xilinx-v2015.2
```

## zynq_zed.hの編集
`include/configs/zynq_zed.h`を編集します。
`include/configs/zynq-common.h`の内容が基本的な設定で、これが`zynq_zed.h`にインクルードされ、さらにその中でオーバーライドされます。
`zynq_zed.h`を編集してさらに必要な設定をオーバーライドします。
今回、ext4パーティション上にRoot file systemを用意してLinuxを立ち上げるので、そのための設定のようです。

1. `include/configs/zynq-common.h`から`CONFIG_EXTRA_ENV_SETTINGS`の定義（複数行の長い#define）をコピーして `include/configs/zynq_zed.h`の末尾に貼り付け。
2. `sdboot`でgrepし、当該部分を書き換え

    ```diff
    "sdboot=if mmcinfo; then " \
    			"run uenvboot; " \
    -			"echo Copying Linux from SD to RAM... && " \
    +			"echo Copying Linux from SD to RAM... RFS in ext4 && " \
    			"load mmc 0 ${kernel_load_address} ${kernel_image} && " \
    			"load mmc 0 ${devicetree_load_address} ${devicetree_image} && " \
    -			"load mmc 0 ${ramdisk_load_address} ${ramdisk_image} && " \
    -			"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}; " \
    +			"bootm ${kernel_load_address} - ${devicetree_load_address}; " \
    ```

3. `#define CONFIG_EXTRA_ENV_SETTINGS`の前に

    ```c
    #ifdef CONFIG_EXTRA_ENV_SETTINGS 
    #undef CONFIG_EXTRA_ENV_SETTINGS 
    #endif
    ```
を追記

4. (Optional) 自分の好みに応じて、同様に`#define CONFIG_SYS_PROMPT`や`#define CONFIG_BOOTDELAY`などをオーバーライドしてもよい

## ビルド
```bash
$ export CROSS_COMPILE=arm-xilinx-linux-gnueabi-
$ make distclean
$ make zynq_zed_config
$ make
```

ビルドが完了すると、`u-boot-xlnx`ディレクトリ直下に`u-boot`が生成されます。

また、`tools/mkimage`というファイルもできています。あとで使うので、パスを通しておきます。

```bash
$ export PATH=/path/to/dir/u-boot-xlnx/tools/:${PATH}
```

# Linuxカーネルのビルド
`linux-xlnx`ディレクトリに移動し、適当なリビジョンをチェックアウトします。
その後、ビルドします。

```bash
$ cd linux-xlnx
$ git checkout xilinx-v2015.2
$ make ARCH=arm xilinx_zynq_defconfig
$ make ARCH=arm UIMAGE_LOADADDR=0x8000 uImage
```

前章で設定した`CROSS_COMPILE`環境変数がこのビルドでも必要です。
また、`mkimage`へパスが通っていることも必要です。
エラーが出る場合はこの辺を確認してください。

# SDKでの作業
Vivadoに戻り、Export -> Export Hardwareのあと、Launch SDKでSDKを立ち上げます。

## FSBL作成
FSBLを作成します。特に変わったことはありません。
File -> New -> Application Projectを選択します。

New Projectダイアログで、Project nameは適当に`zynq_fsbl_0`とかにしておきます。
Finishは押さずにNext。
2ページ目でZynq FSBLを選び、Finish。

## BOOT.bin作成
5. ツリービューで`zynq_fsbl_0`を選択した状態で、メニューからXilinx Tools -> Create Zynq Boot Imageを選びます。
6. Output BIF file pathを適宜設定します。今回はファイル名を`ubuntu.bif`に変えました。
7. ダイアログ下部のBoot image partitionsにはすでにbootloaderとビットストリームが読み込まれているはずです。無ければ、適宜Addします。
8. 上で作ったu-bootを`u-boot.elf`とリネームして適当な場所にコピーしておきます。Boot image partitionsにAddします。Partition typeはdatafileです。
9. 適宜Output pathを設定します。
10. Create Imageを押します。Output pathで指定した場所に`BOOT.bin`ができます。

## Device tree作成
まず、Device tree generatorをSDKに追加します。

1. Xilinx Tools -> Repositories を選択
2. Local RepositoriesでNewを押し、上でダウンロードした`device-tree-xlnx`ディレクトリを選択し追加
3. OKを押してダイアログを抜ける。

次に、Device tree BSPを作成します。

1. File -> New -> Board Support Package を選択
2. Board Support Package OSの欄で`device_tree`を選択
3. Finish
4. 次に開く設定ダイアログで、bootargs行のValue列に`console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlyprintk rootfstype=ext4 rootwait devtmpfs.mount=1 `と入力
5. OK

以上で`system.dts`ファイルが作成されます。
ちなみに、上記bootargsなどを変えたいときは、`system.mss`をダブルクリックして表示される画面で`Modify this BSP's Settings`を押して設定を変更したあと、`Re-generate BSP Sources`でファイルを更新します。

最後に、.dtsファイルをコンパイラに食わせて.dtbファイルを作成します。
コンパイラは`linux-xlnx/scripts/dtc/dtc`にあります。
シェルで作業します。

```bash
$ cd path/to/sdk/device_tree_bsp_0
$ path/to/linux-xlnx/scripts/dtc/dtc -I dts -O dtb -o devicetree.dtb system.dts
```
これで`devicetree.dtb`ができました。

# SDカードの準備

fdiskやGPartedでFAT32とext4のパーティションを１つずつ作ります。
FAT32の方を`ZED_BOOT`、ext4の方を`rootfs`と名づけました。

# Root file system作成

```bash
$ wget http://releases.linaro.org/15.04/ubuntu/utopic-images/nano/linaro-utopic-nano-20150421-702.tar.gz
```

他のイメージを使いたい場合、
http://www.linaro.org/downloads/historic/
から好きなのを探してください。
あまり古いとサポートが切れていてaptのリポジトリがなくなっていたりするのでおすすめしません。設定をいじる場所が増えます。

## SDカードへ展開
SDカードをPCに挿し、マウントした状態で以下を実行します。
`/media/foo/rootfs`はSDカードの`rootfs`パーティションがマウントされた場所です。自分の環境に合わせて適宜変えます。

```bash
$ sudo tar --strip-components=1 -C /media/foo/rootfs -xzpf linaro-saucy-nano-20140410-652.tar.gz
```

わりと時間がかかります。
また、展開後は

```bash
$ sudo sync
```
を実行します。（メモリ上のキャッシュをすべてSDに書き込んでいる？）結構時間がかかります。
もしくは、GUIでSDカードのマウントを解除してもよいです。多分内部で`sync`しているので、それでも時間はかかります。

# BOOT.bin, uImage, devicetree.dtbをSDカードにコピー
`linux-xlnx/arch/arm/boot/uImage`, `path/to/sdk/zynq_fsbl_0/bootimage/BOOT.bin`, `path/to/sdk/device_tree_bsp_0/devicetree.dtb`をSDカードのFATフォーマットのパーティションにコピーします。

# Ubuntu起動
作成したSDカードをZed boardに挿し、電源を入れるとubuntuが起動します。
シリアルコンソールから確認します。

u-bootの環境変数がうまく設定されていないと、うまく起動しなかったり、u-bootが入力受付状態で止まったりします。
u-bootが入力受付状態になればそのまま、
そうならなければZed boardのPS-RSTボタンを押してブートをやり直し、u-bootがlinuxのブートを始める前に何らかのキー入力を与えて中断し、u-bootを入力受付状態にします。
その後、u-bootのコンソールで

```
env default –a –f
printenv sdboot
saveenv
```
を入力します。
`printenv sdboot`で、上記で`zynq_zed.h`に対して行った変更が反映されていることを確認してください。
その後、`reset`コマンドを入力するとu-bootが再ブートされます。

ネットワークの設定など
http://qiita.com/yuichiroTCY/items/3b792feedb8f55aaef43
も読んでください。

# AXIバスにIPを追加してみる
IPを追加してLinuxに認識させてみます。

## PLデザイン
![design2.png](https://qiita-image-store.s3.amazonaws.com/0/65141/9961a69d-9639-9754-c8e3-b89361b1f9b8.png)
こんな感じで、AXI GPIOを2つ追加し、それぞれLEDとスイッチにつながります。
![address.png](https://qiita-image-store.s3.amazonaws.com/0/65141/67a68897-f22f-6cc5-91b1-bdd6a9b326be.png)
アドレスマッピングはこんな感じ。

ハードウェアは以上。ビットストリームを作成し、SDKにエクスポートします。

## BOOT.bin, Device tree再生成
ビットストリームが新しくなったので、BOOT.binを再生成します。
上記のBOOT.bon作成をもう一度やればOKです。

また、Device treeも再生成します。
Device tree BSPプロジェクト（デフォルトなら`device_tree_bsp_0`）の下にある`system.mss`をダブルクリックします。
開いたペインで、`Re-generate BSP Sources`をクリックします。
Device treeが再生成されます。
ここで、`pl.dtsi`を確認してみると、

```
/ {
	amba_pl: amba_pl {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "simple-bus";
		ranges ;
		axi_gpio_0: gpio@41200000 {
			#gpio-cells = <2>;
			compatible = "xlnx,xps-gpio-1.00.a";
			gpio-controller ;
			reg = <0x41200000 0x10000>;
			xlnx,all-inputs = <0x0>;
			xlnx,all-inputs-2 = <0x0>;
			xlnx,all-outputs = <0x1>;
			xlnx,all-outputs-2 = <0x0>;
			xlnx,dout-default = <0x00000000>;
			xlnx,dout-default-2 = <0x00000000>;
			xlnx,gpio-width = <0x8>;
			xlnx,gpio2-width = <0x20>;
			xlnx,interrupt-present = <0x0>;
			xlnx,is-dual = <0x0>;
			xlnx,tri-default = <0xFFFFFFFF>;
			xlnx,tri-default-2 = <0xFFFFFFFF>;
		};
		axi_gpio_1: gpio@41210000 {
			#gpio-cells = <2>;
			compatible = "xlnx,xps-gpio-1.00.a";
			gpio-controller ;
			reg = <0x41210000 0x10000>;
			xlnx,all-inputs = <0x1>;
			xlnx,all-inputs-2 = <0x0>;
			xlnx,all-outputs = <0x0>;
			xlnx,all-outputs-2 = <0x0>;
			xlnx,dout-default = <0x00000000>;
			xlnx,dout-default-2 = <0x00000000>;
			xlnx,gpio-width = <0x8>;
			xlnx,gpio2-width = <0x20>;
			xlnx,interrupt-present = <0x0>;
			xlnx,is-dual = <0x0>;
			xlnx,tri-default = <0xFFFFFFFF>;
			xlnx,tri-default-2 = <0xFFFFFFFF>;
		};
	};
};
```

となっていて、追加したIPが正しく反映されていることがわかります。

Device treeも、上記の手順を再度繰り返してコンパイルし、`devicetree.dtb`を再生成します。

## SDカードへコピー
生成したファイルをSDカードにコピーします。
最初に作成したSDカードを再び使うのであれば、更新された`BOOT.bin`と`devicetree.dtb`だけ上書きすればOKです。

## テスト
SDブートし、ubuntuを起動します。
`devmem2`をインストールします。（ネットワーク設定などは完了していて、apt-getが使えるものとします）

```bash
$ apt-get install devmem2
```

`/dev/mem`という特殊なデバイスファイルを使うと直接アドレス空間にアクセスできるため、
デバイスドライバ不要でIPのレジスタに書き込み・読み出しができます。
`devmem2`はこれをやってくれるコマンドです。

今回は簡単のため`/dev/mem`を使ってしまいますが、
本来は独自ハードウェアはそれに対応したデバイスドライバを作成する必要があります。
本番では`/dev/mem`は使わないようにしましょう。

また、`/dev/mem`を利用するにはroot権限が必要です。

```bash
$ devmem2 0x41200000 b 0xff
```
とすると、LEDが点灯します。アドレス`0x41200000`に、8bit幅(b)で、値0xffを書き込みました。

また、適当にスイッチを設定して、

```bash
$ devmem2 0x41210000 b
```
とすると、スイッチの状態に対応した値が読み出されます。

以上で、AXIバスに追加したハードウェアが正しくLinuxのメモリ空間にマップされ、読み書きできることが確認できました。

# Hello worldをやってみる
母艦PCでアプリケーションをビルドし、ssh経由でzynq上のubuntuに送り、実行してみます。
Xilinxの公式チュートリアルにあるようなやつのUbuntu(Linaro)版です。

## SSHの設定
zynqのubuntuでSSHサービスを起動しておきます。

```bash
$ apt-get install ssh
$ /etc/init.d/ssh restart
```

さらに、作業用にユーザーを作ったり、SSHでのrootログインを禁止したりするとよいかもしれません。

## プロジェクトの作成
母艦PCで作業します。
SDKで File -> New -> Application Project で新規プロジェクトを作成します。
OS Platformをlinuxにし、Nextを押した後の2ページ目でLinux Hello Worldを選びます。

プロジェクトが作成されたら、実行します。
上記で作ったプロジェクトを選択した状態で
Run -> Run Configurations
を選択します。
ダイアログでは、左ペインから`Remote ARM Linux Application`をダブルクリックします。
（`Remote ARM Linux Application`が見つからない場合、左ペイン上部のメニューで`Filter Configuration Types`などにチェックが入っているかもしれません。チェックを外してみます。）

ConnectionのNewをクリックします。
SSH Onlyを選んでNext, 次のページでZynqのIPアドレスを入れてFinish。

次にRemote Absolute File Path for C/C++ ApplicationでBrowseを押します。
最初はssh接続のための設定ダイアログが出るので、適宜設定します。
ここでZynqのファイルシステムが見えます。
適当な場所にで右クリック -> New -> Fileでファイルを作り、OKします。
今回は`My Home/apps/hello_world_linux.elf`としました。

以上で設定は完了です。
Applyし、Runします。

## エラー…
```bash
-bash: ./hello_world_linux: No such file or directory
```
とか出てうまく行きません。

http://zedboard.com/content/cant-start-my-binary-under-linaro
のとおり、rootで

```bash
$ cd /lib
$ ln -s arm-linux-gnueabihf/ld-linux-armhf.so.3 ld-linux.so.3
```
とします。

これで、

```bash
Hello World
```
ただしく表示されました！

## Connect to xxx.xxx.xxx.xxx was canceled
SDKから実行しようとすると、
`Connect to xxx.xxx.xxx.xxx was canceled`（xxx.xxx.xxx.xxxはzynqのIPアドレス）と出て実行できないことがあります。
実行ファイルはZynqの上記で設定したパスにコピーされているので、
直接SSHして実行すればOKです。

これはSSHのバグ？なんですかね
http://stackoverflow.com/questions/33408763/cross-debugging-rseg1058-connect-to-192-168-xxx-xxx-cancelled