ZYBO (Zynq) 初心者ガイド (16) Linuxから自作IPをUIOで制御する

- [1回目: 開発環境の準備](https://qiita.com/take-iwiw/items/966f252f6ca954aff08b)
- [2回目: Hello Worldプロジェクト](https://qiita.com/take-iwiw/items/24a8a94741fdbb80f62a)
- [3回目: PSのGPIOでLチカ](https://qiita.com/take-iwiw/items/39d2ed00fa88a1204fce)
- [4回目: PLのAXI GPIOでPSからLチカ](https://qiita.com/take-iwiw/items/a3b935a4e4dbd41593e1)
- [5回目: PLだけでLチカ](https://qiita.com/take-iwiw/items/efcdfca1866b9941fd2a)
- [6回目: 自作IPでLチカ](https://qiita.com/take-iwiw/items/b0f2ce7b4653bc6f32b9)
- [7回目: ブートイメージを作る](https://qiita.com/take-iwiw/items/ece0cdf83e6e1908fad8)
- [8回目: Linux起動する](https://qiita.com/take-iwiw/items/6e6915f7318689818368)
- [9回目: Linuxカーネルを少しカスタマイズする](https://qiita.com/take-iwiw/items/6f2644d16cbfdb021d07)
- [10回目: LinuxのRootFSをカスタマイズする / PythonでHello World](https://qiita.com/take-iwiw/items/94a90047978664159681)
- [11回目: LinuxユーザアプリケーションでLチカ](https://qiita.com/take-iwiw/items/5e4ab9a106acfda2c866)
- [12回目: LinuxカーネルモジュールでLチカ](https://qiita.com/take-iwiw/items/e7efc48d92d7b979efdc)
- [13回目: LAN(Ethernet 0)を使う](https://qiita.com/take-iwiw/items/ac489acaca1ab54d7ce8)
- [14回目: Linuxユーザアプリをデバッグする / RootFSに取り込む](https://qiita.com/take-iwiw/items/8134ca7098622219b177)
- [15回目: Linux起動時にアプリケーションを自動実行させる](https://qiita.com/take-iwiw/items/50065df6cdd1ba8bcbd6)
- __[16回目: Linuxから自作IPをUIOで制御する](https://qiita.com/take-iwiw/items/da91ce4dc2a8a8df3c0a) <--- 今回の内容__
- [17回目: Linuxで自作IPのデバイスドライバを作る](https://qiita.com/take-iwiw/items/05eb1204cb51ce0a5c03)
- [18回目: IoT化してスマホからLチカ](https://qiita.com/take-iwiw/items/e9311292c5078d785699)

# 環境
- 開発用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から自作IPをUIOで制御する
ちょっと飛んでしまいますが、内容的には[11回目: LinuxユーザアプリケーションでLチカ](https://qiita.com/take-iwiw/items/5e4ab9a106acfda2c866)の続きです。11回目では、PSのGPIOを、

1. Linux提供のデバイスドライバ(SysFS)経由で制御
2. `/dev/mem`をmmapして、レジスタ直叩きで制御

しました。1.があるべき姿です。11回目で使用したデバイスはGPIOだったため、Linux標準(または、PetaLinuxが裏で生成した?)のGPIOデバイスドライバを使えました。自作IPの場合には、このデバイスドライバを自分で作る必要があります。2.の方法は使うべきではありません。が、実際には便利です。特にZynqの場合は制御対象のハードウェア(PL)が完全にオリジナルなため、いちいちデバイスドライバを作るのも面倒です。

Linuxには、UIO (User space IO) という仕組みが用意されています。UIOによって、ユーザ空間からでもデバイスのレジスタアクセスが可能となります。具体的には、以下のような手順になります。

- 制御したいデバイス(ここでは自作IP)を、UIOデバイスだと指定する (デバイスツリーで設定)
- `/dev/uio0`をmmapして、レジスタアクセスして制御する (Linuxユーザアプリケーション)

`/dev/mem`と比べると、安全性が上がることと、割り込みが使えることがメリットになります。

# ハードウェアを用意する
ハードウェアは、[6回目: 自作IPでLチカ](https://qiita.com/take-iwiw/items/b0f2ce7b4653bc6f32b9)に作成したものを使います。ハードウェア構成は以下の通りです。

- PSを配置 (Ethernet0のMDIO接続を修正)
- 自作IP(myip)を配置
	- レジスタの下位4-bitの値を出力するだけのIP
	- 出力先はLED (M14、M15、G14、D18)
	- レジスタアドレス = `0x43C00000`
	- 名前は`myip_0`

![01.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/8730b3f3-2649-fd4f-5f73-4e6833ed4f52.jpeg)
![02.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/65020c46-c52f-3519-fe6f-4deed5d1f83e.jpeg)

IPを作るのが面倒なら、AXI GPIOで試しても問題ないです。

ビットストリーム付きのhdfファイルを出力して、Ubuntu側にコピーしておきます。(project_1.sdk/design_1_wrapper.hdf)


# Linuxイメージを作る
## プロジェクトを作る
いつも通りの手順で、プロジェクトを作り、作成したハードウェアでコンフィグします。(プロジェクト名はUIOTestとする)。デフォルトの状態での設定を確認したいので、ビルドしてイメージ作成までしてしまいます。

```sh:開発PCのターミナル
cd ~/work/peta
petalinux-create --type project --template zynq --name UIOTest
cd UIOTest/
petalinux-config --get-hw-description=../project_1.sdk
	# プロジェクトの設定は何も変えずにExit
petalinux-build
petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/design_1_wrapper.bit --u-boot
```

### ノート
デフォルト設定でOKだが、念のため`petalinux-config -c kernel`で、Device Drivers → Userspace I/O driversの`Userspace I/O platform driver with generic IRQ handling`にチェックがついていることを確認する。M(モジュール)でもOK

![03.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/fc26979c-8f3b-3418-3cbb-4573bac42264.jpeg)

## 自動生成されたデバイスツリーを確認する (見るだけ)
ハードウェア情報を基にしたデバイスツリーが、`components/plnx_workspace/device-tree/device-tree-generation/`に自動生成されています。いくつかdts/dtsiファイルがあるのですが、今回見るのはPLに接続されたデバイスなので、`components/plnx_workspace/device-tree/device-tree-generation/pl.dtsi`を開いてみます。

```txt:components/plnx_workspace/device-tree/device-tree-generation/pl.dtsi
/ {
	amba_pl: amba_pl {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "simple-bus";
		ranges ;
		myip_0: myip@43c00000 {
			compatible = "xlnx,myip-1.0";
			reg = <0x43c00000 0x10000>;
			xlnx,s00-axi-addr-width = <0x4>;
			xlnx,s00-axi-data-width = <0x20>;
		};
	};
};
```

デバイスの接続情報がツリー構造で記載されています。
まず最初に、ルート`/`があります。その下に、`amba_pl`というノードがぶら下がっています。コロン(:)の左側はラベルになります。後で参照するときに使えます。`amba_pl`ノードのプロパティがいくつか書かれています。(`compatible = "simple-bus";`など)。
さらに、`amba_pl`ノードの下に、`myip_0: myip@43c00000`というノードがぶら下がっています。この行の意味は、「0x43c00000番地にmyipというデバイスが接続している。これに対して、myip_0というラベル名をつける。」です。そして、`myip_0: myip@43c00000`のプロパティがその下に書かれています。重要なのは以下2点です。

- compatible = "xlnx,myip-1.0";
	- ベンダー名 = xlnx。デバイス名 = myip-1.0。デバイスドライバが、自分が対応すべきデバイスかどうかを判断するのに使う
- reg = <0x43c00000 0x10000>;
	- このデバイスに割り当てられているレジスタアドレスの範囲

このファイルの編集は不要です。というか、`components/plnx_workspace/device-tree/device-tree-generation/`以下のファイルは自動生成されるので、編集すべきではありません。(`build/`の中にもdts/dtsiファイルが作られますが、こちらも編集すべきではありません。)

### デフォルトの接続状態を見てみる
とりあえず、何も設定を変更していない状態で作成したLinuxイメージで起動してみます。デバイスツリーの情報は`/proc/device-tree`で確認できます。すると、以下のようになっていることが分かります。

```sh:Zyboのターミナル
root@UIOTest:~# ls /proc/device-tree/amba_pl/
#address-cells  compatible      name
#size-cells     myip@43c00000   ranges

root@UIOTest:~# more /proc/device-tree/amba_pl/myip\@43c00000/compatible
xlnx,myip-1.0
```

## デバイスツリーを編集する
確認したように、現状、`myip_0: myip@43c00000`はxlnx社のmyip-1.0というデバイスに対応したデバイスドライバで制御する必要があります。当然、そんなものはこの世にはありません。今回は、デバイスドライバを作らずに、UIO経由で制御します。UIOデバイスとして扱うには、`compatible`のプロパティを`generic-uio`にする必要があります。これによって、UIOドライバが、「自分が処理すべきデバイスがつながっている」と判断して、このデバイス(myip)の制御をしてくれます。

開発者で編集可能なデバイスツリー定義ファイルは`project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi`になります。この中で、`components/plnx_workspace/device-tree/device-tree-generation/`に自動生成された接続設定を上書きします。

```txt:project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/include/ "system-conf.dtsi"
/ {
    chosen {
        bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
    };
};

&myip_0 {
    compatible = "generic-uio";
};
```

アンパサンド(&)によって、ラベルを参照することが出来ます。`myip_0`というデバイスの`compatible`プロパティを`generic-uio`で上書きします。
また、起動パラメータ(`bootargs`)に`uio_pdrv_genirq.of_id=generic-uio`を追加します。


編集が終わったら、再度`petalinux-build`して、出来上がったイメージで起動します。(`petalinux-build -x package`でも大丈夫です。コピーするのは`image.ub`だけでも大丈夫です。)

### 編集後の接続状態を見てみる
まず`/proc/device-tree`で接続状態を見てみます。`myip@43c00000`の接続位置は変わらずamba_plの下ですが、`compatible`プロパティが変わっています。また、`/dev/uio0`が出来ていることが分かります。`/sys/class/uio`で詳細を見ると、ちゃんと`amba_pl/myip@43c00000`にマッピングされていることが分かります。

```Zyboのターミナル
root@UIOTest:~# ls /proc/device-tree/amba_pl/
#address-cells  compatible      name
#size-cells     myip@43c00000   ranges

root@UIOTest:~# more /proc/device-tree/amba_pl/myip\@43c00000/compatible
generic-uio

root@UIOTest:~# ls /dev/uio0
/dev/uio0

root@UIOTest:~# more /sys/class/uio/uio0/maps/map0/name
/amba_pl/myip@43c00000
```

あとは、`/dev/uio0`にアクセスするコードを書けばOKです。

# UIO経由でLチカするソフトを作る
Windows上のXilinx SDKでLinux アプリケーション用のプロジェクトを作ります。詳細は、[14回目: Linuxユーザアプリをデバッグする / RootFSに取り込む](https://qiita.com/take-iwiw/items/8134ca7098622219b177)を参考にしてください。

メニューバー -> File -> New -> Application Projectで、以下のプロジェクトを作ります。

- ProjectName = blink_uio
- OS Platform = linux
- Processor Type = ps7_cortexa9
- Language = C
- Linux System Root / Linux Toolchain = 非チェック

[11回目: LinuxユーザアプリケーションでLチカ](https://qiita.com/take-iwiw/items/5e4ab9a106acfda2c866)とほぼ同じコードになります。差分は、使用するデバイスファイルを`/dev/mem`から`/dev/uio0`に変更、IOの出力設定が不要(Output固定のIPとしたため)。

```c:helloworld.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

#define REG(address) *(volatile unsigned int*)(address)

int main(int argc, char **argv)
{
    printf("Hello World!\n");

    int address;    /* GPIOレジスタへの仮想アドレス(ユーザ空間) */
    int fd;

    /* メモリアクセス用のデバイスファイルを開く */
    if ((fd = open("/dev/uio0", O_RDWR | O_SYNC)) < 0) {
        perror("open");
        return -1;
    }

    /* ARM(CPU)から見た物理アドレス → 仮想アドレスへのマッピング */
    address = (int)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (address == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    while(1) {
        /* Set LEDs(PL GPIO) as High */
        REG(address) = 0x0F;
        usleep(1*1000*1000);
        /* Set LEDs(PL GPIO) as Low */
        REG(address) = 0x00;
        usleep(1*1000*1000);
    }

    /* 使い終わったリソースを解放する */
    munmap((void*)address, 0x1000);
    close(fd);

    return 0;
}
```

これを実行すると、LED(LD0～LD3)がチカチカします。Xilinx SDKからの実行/デバッグ方法は[14回目: Linuxユーザアプリをデバッグする / RootFSに取り込む](https://qiita.com/take-iwiw/items/8134ca7098622219b177)を参考にしてください。


# ノート
## デバイスツリー定義ファイル上でのラベル
今回の自作IPは、dtsi上では、`myip_0`というラベルがつけられていました。このラベルはデバイスツリー定義ファイル(dts/dtsi)でのみ使われます。このラベルは、Vivado上で付けた名前がそのまま使われているようです。

## ラベルを使わないでデバイスツリーを編集
ラベルを使わない場合には、以下のようにツリー構造を含めて指定する必要があります。

```txt:project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/include/ "system-conf.dtsi"
/ {
    chosen {
        bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
    };
};

/ {
    amba_pl {
        myip@43c00000 {
            compatible = "generic-uio";
        };
    };
};
```

## 割り込みも使えるらしい
https://qiita.com/ikwzm/items/b22592c31cdbb9ab6cf7

## AXI GPIOの場合
AXI GPIOを使用した場合は、以下のようにすることで、AXI GPIOのレジスタにUIOでアクセスできるようになります。しかし、GPIOとして使うことが出来なくなるので、ご注意ください。(`/sys/class/gpio`にも表示されません。)

```txt:project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/include/ "system-conf.dtsi"
/ {
    chosen {
        bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
    };
};

&axi_gpio_0 {
    compatible = "generic-uio";
};
```

## dts/dtsi
見てきたように、開発者が変更できるデバイスツリー定義ファイルは`project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi`になります。一般に、dtsにはチップ固有の情報、dtsiにはボード固有の情報を記載します。PLに関する情報はdtsiで良いのだろうか？？

## UIO番号を変えたい
UIOのデバイスファイルのフォーマットは`/dev/uioX`です。最後の番号は接続された順のようです。デバイスが1つだけだったらいいですが、増えてくると管理が大変そうです。

~~この番号を自由に設定する方法を、どなたかご存知でしたら教えてください！！~~


@tetsu_koba さんから情報をいただきました。ありがとうございます。

>どのデバイスが /dev/uioX に対応するのかを調べるには
>/sys/class/uio/uioX/name
>を見るのが想定された使い方のようです。
>
>https://www.kernel.org/doc/html/v4.12/driver-api/uio-howto.html

_追記(2018/1/22)_
Vivado HLSで合成したIPの場合、linuxでUIOとして制御するためのライブラリ関数が自動で生成されました。そして、`X(IP名)_Initialize()`関数内で、対応する`/dev/uioX`を探して取得してくれるようです。

# 参考
https://github.com/Digilent/Petalinux-Zybo-Z7-20/blob/master/Zybo-Z7-20/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
https://forum.digilentinc.com/topic/2719-how-to-register-my-device-as-uio-on-petalinux/
https://qiita.com/7of9/items/a0f91a0c26e362852d67
http://arch.jpn.org/archives/292