- 1回目: 開発環境の準備
- 2回目: Hello Worldプロジェクト
- 3回目: PSのGPIOでLチカ
- 4回目: PLのAXI GPIOでPSからLチカ
- 5回目: PLだけでLチカ
- 6回目: 自作IPでLチカ
- 7回目: ブートイメージを作る
- 8回目: Linux起動する
- 9回目: Linuxカーネルを少しカスタマイズする
- 10回目: LinuxのRootFSをカスタマイズする / PythonでHello World
- 11回目: LinuxユーザアプリケーションでLチカ
- 12回目: LinuxカーネルモジュールでLチカ
- 13回目: LAN(Ethernet 0)を使う
- 14回目: Linuxユーザアプリをデバッグする / RootFSに取り込む
- 15回目: Linux起動時にアプリケーションを自動実行させる
- 16回目: Linuxから自作IPをUIOで制御する <--- 今回の内容
- 17回目: Linuxで自作IPのデバイスドライバを作る
- 18回目: IoT化してスマホからLチカ
環境
- 開発用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チカの続きです。11回目では、PSのGPIOを、
- Linux提供のデバイスドライバ(SysFS)経由で制御
-
/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チカに作成したものを使います。ハードウェア構成は以下の通りです。
- PSを配置 (Ethernet0のMDIO接続を修正)
- 自作IP(myip)を配置
- レジスタの下位4-bitの値を出力するだけのIP
- 出力先はLED (M14、M15、G14、D18)
- レジスタアドレス =
0x43C00000
- 名前は
myip_0
IPを作るのが面倒なら、AXI GPIOで試しても問題ないです。
ビットストリーム付きのhdfファイルを出力して、Ubuntu側にコピーしておきます。(project_1.sdk/design_1_wrapper.hdf)
Linuxイメージを作る
プロジェクトを作る
いつも通りの手順で、プロジェクトを作り、作成したハードウェアでコンフィグします。(プロジェクト名はUIOTestとする)。デフォルトの状態での設定を確認したいので、ビルドしてイメージ作成までしてしまいます。
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
自動生成されたデバイスツリーを確認する (見るだけ)
ハードウェア情報を基にしたデバイスツリーが、components/plnx_workspace/device-tree/device-tree-generation/
に自動生成されています。いくつかdts/dtsiファイルがあるのですが、今回見るのはPLに接続されたデバイスなので、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
で確認できます。すると、以下のようになっていることが分かります。
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/
に自動生成された接続設定を上書きします。
/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
にマッピングされていることが分かります。
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に取り込むを参考にしてください。
メニューバー -> File -> New -> Application Projectで、以下のプロジェクトを作ります。
- ProjectName = blink_uio
- OS Platform = linux
- Processor Type = ps7_cortexa9
- Language = C
- Linux System Root / Linux Toolchain = 非チェック
11回目: LinuxユーザアプリケーションでLチカとほぼ同じコードになります。差分は、使用するデバイスファイルを/dev/mem
から/dev/uio0
に変更、IOの出力設定が不要(Output固定のIPとしたため)。
#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に取り込むを参考にしてください。
ノート
デバイスツリー定義ファイル上でのラベル
今回の自作IPは、dtsi上では、myip_0
というラベルがつけられていました。このラベルはデバイスツリー定義ファイル(dts/dtsi)でのみ使われます。このラベルは、Vivado上で付けた名前がそのまま使われているようです。
ラベルを使わないでデバイスツリーを編集
ラベルを使わない場合には、以下のようにツリー構造を含めて指定する必要があります。
/include/ "system-conf.dtsi"
/ {
chosen {
bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
};
};
/ {
amba_pl {
myip@43c00000 {
compatible = "generic-uio";
};
};
};
割り込みも使えるらしい
AXI GPIOの場合
AXI GPIOを使用した場合は、以下のようにすることで、AXI GPIOのレジスタにUIOでアクセスできるようになります。しかし、GPIOとして使うことが出来なくなるので、ご注意ください。(/sys/class/gpio
にも表示されません。)
/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