今回は、Linux上のアプリケーションからUIO経由で自作のハードウェア(IP)を制御する方法についてです。
Linux UIO
いつもの参考サイトの通りに進めます。
ZYBO (Zynq) 初心者ガイド (16) Linuxから自作IPをUIOで制御する - Qiita
Liunx上のアプリケーションからのハードウェア制御自体は前もやりましたが(https://nokixa.hatenablog.com/entry/2019/07/30/045129)、今回はLinuxで用意されているUIOというものを使って制御するやり方をやってみます。
UIOについての解説は他にも色々とありました。
https://elinux.org/images/b/b0/Uio080417celfelc08.pdf
LinuxのUIO(User space I/O) その1 - Qiita
UIO(User space IO)の割り込みの使い方の例 - Qiita
総合してみると、
- UIOは、ユーザスペースからのデバイスへのレジスタアクセスを可能にする
- 通常ハードウェアデバイスの制御を行う際は、デバイスドライバを作ってカーネルに組み込むが、 以下のケースではUIOが有用
- 汎用的なハードウェアデバイスでない
- ユーザアプリケーション間でのハードウェア共有が不要
ということで、FPGAを使用した組込みシステムを作る際には有用なものと思われます。
今回制御する対象
参考サイトでは、自作のIPを制御していますが、今回はDigilentのサンプルデザインを使用しているので、この中に入っているIPを制御します。
PL部のLEDに接続されているAXI GPIO IPを制御することとします。
ZYBOを進める - 7. Vivado環境を見てみる - 勉強しないとな~blog
PealinuxプロジェクトでのUIO設定
参考サイトの通りです。確認するだけです。
petalinux-config -c kernel
でカーネル設定メニューを表示- Device Drivers -> Userspace I/O drivers に進む
- Userspace I/O platform driver with generic IRQ handling にチェックまたはMが入っていることを確認
デバイスツリーの編集
まず、自動生成されたデバイスツリーを確認します。
components/plnx_workspace/device-tree/device-tree-generation/
にデバイスツリー等のファイルがあります。
noki@ubuntu:~/work/Petalinux/Zybo-base-linux-peta$ ls components/plnx_workspace/device-tree/device-tree-generation/ board plnx_arm-system.pp skeleton.dtsi device-tree.mss ps7_init.c system-conf.dtsi hardware_description.hdf ps7_init_gpl.c system-top.dts pcw.dtsi ps7_init_gpl.h system_wrapper.bit pl.dtsi ps7_init.h zynq-7000.dtsi plnx_arm-system.dtb ps7_init.html plnx_arm-system.dts ps7_init.tcl noki@ubuntu:~/work/Petalinux/Zybo-base-linux-peta$
このうち、pl.dtsi
がPL部のデバイスツリーになります。
今回使用しているPL部では、参考サイトのものに比べて接続されているモジュールが多いので、デバイスツリーも長くなります。
ここではLED制御のAXI GPIOの部分を見てみます。
/* * CAUTION: This file is automatically generated by Xilinx. * Version: * Today is: Sat Dec 7 23:55:25 2019 */ / { amba_pl: amba_pl { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; ranges ; ... axi_gpio_led: gpio@41200000 { #gpio-cells = <2>; compatible = "xlnx,xps-gpio-1.00.a"; gpio-controller ; reg = <0x41200000 0x10000>; xlnx,all-inputs = <0x0>; ... }; ...
このような形で、IPのバス上でのアドレス、パラメータ設定などが記述されています。
compatible = "xlnx,xps-gpio-1.00.a";
の記述は、xlnx
社のxps-gpio-1.00.a
という名前のモジュールが存在している、ということを示しています。これをもとに適切なデバイスドライバが割り当てられるようです。
自動生成デバイスツリーの上書き
ユーザ編集用のデバイスツリーとして、project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
が用意されています。
ここに、自動生成のデバイスツリーの記述を上書きする記述をして、デバイスドライバとしてUIOが割り当てられるようにします。
/include/ "system-conf.dtsi" / { chosen { bootargs = "console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait uio_pdrv_genirq.of_id=generic-uio"; }; }; &axi_gpio_led { compatible = "generic-uio"; };
bootargs
に、uio_pdrv_genriq.of_id=generic-uio
を追加- axi_gpio_ledの
compatible
をgeneric-uio
に変更
※元のbootargs
は、Petalinux設定で確認できます。
最初はcomponents/plnx_workspace/device-tree/device-tree-generation/system-conf.dtsi
を見ればいいかとも思ったのですが、こちらのbootargsにはroot=
の設定がなく、起動時に正しくルートファイルシステムが設定できずにうまくいきませんでした。
/* * CAUTION: This file is automatically generated by PetaLinux SDK. * DO NOT modify this file */ / { chosen { bootargs = "console=ttyPS0,115200 earlyprintk"; stdout-path = "serial0:115200n8"; }; }; ...
以下も参考になります。
UIOデバイスドライバを組み込む方法: なひたふJTAG日記
アプリケーションの作成
※アプリケーション作成の前に、Vivadoプロジェクトのほうで、制約ファイルの変更が必要でした。
LED制御ピンの記述が必要です。
##LEDs ##IO_L23P_T3_35 set_property PACKAGE_PIN M14 [get_ports {leds_4bits_tri_io[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {leds_4bits_tri_io[0]}] ##IO_L23N_T3_35 set_property PACKAGE_PIN M15 [get_ports {leds_4bits_tri_io[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {leds_4bits_tri_io[1]}] ##IO_0_35 set_property PACKAGE_PIN G14 [get_ports {leds_4bits_tri_io[2]}] set_property IOSTANDARD LVCMOS33 [get_ports {leds_4bits_tri_io[2]}] ##IO_L3N_T0_DQS_AD1N_35 set_property PACKAGE_PIN D18 [get_ports {leds_4bits_tri_io[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {leds_4bits_tri_io[3]}]
Windows PC側で、Xilinx SDKで新しいアプリケーションプロジェクトを作成します。(https://nokixa.hatenablog.com/entry/2019/08/16/004820)
プロジェクト名は、zybo_uio_testとしました。
アプリケーションのコードは、参考サイトのものをほぼそのまま使わせていただきます。
実際に動かしてみたところ、GPIO_TRIレジスタへの設定(出力設定)をしておかないとLEDが点滅しなかったので、それだけ追加します。
#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; } REG(address + 0x04) = 0x0; // GPIO_TRI register 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; }
- AXI GPIOでは、ベースアドレス + 0x0 の読み書きで、IOポートへの出力/IOポートレベルの読み出しを行うことができます。レジスタデータの32bitの各ビットが、GPIOの各ポートに対応しています。
今回のデザインでは、ZYBO上の4つのLEDが、GPIOの下位4bitのところに接続されているので、0xFを書けば全点灯、0x0を書けば全消灯になります。 - ベースアドレス + 0x4がGPIO_TRIレジスタとなっていて、これもレジスタデータの32bitとGPIOの各ポートが対応しています。各ビットで1を設定すると入力、0を設定すると出力になります。
uio0
のところは、UIOで制御するデバイスが増えた場合、適切な番号のuioを選ぶ必要があります。
アプリケーションの実行
出来上がった生成物(今回はzybo_uio_test.elf)をLinuxルートファイルシステムに取り込んで実行すると、ZYBOでPL部に接続されたLED4つが点滅しました!
まとめ
ひとまずチュートリアルでやりたい部分は一通りやってみました。
次からはいよいよオリジナルのデザインを入れていこうと思います。