ユーザ・ソフトウェアの作り方
5-1.はじめに
本章では、「特電PCI Express汎用デバイスドライバ」を用いて、PCI Expressバス上のFPGAを操作するユーザ・アプリケーションの作り方を説明します。
5-2.アプリケーションの開発環境
コンパイラの準備
アプリケーションソフトを開発するには、適当なCコンパイラが必要です。
本チュートリアルでは、VisualC++もしくはBorlandC++での作り方を明します。確認はしていませんが、GCCでも同様の方法で作成できるでしょう。
デバイスドライバの構成
特電PCI Expressドライバの本体は、tkdnpcie.sysとtkdnpcie.dllの2つのバイナリファイルで提供されており、お客様は、tkdnpcie.dllでエクスポートしているAPI関数を利用してアプリケーションを作成します。
ヘッダファイルとインポートライブラリ
お客様は、お客様が作成されたプログラムの中で、tkdnpcie.hをインクルードしてください。tkdnpcie.hにはtkdnpcie.dllでエクスポートされている関数がプロトタイプ宣言されています。
プログラムを作成する際には、お客様が作成したプログラムとDLLをリンクする必要があります。リンカにはインポートライブラリを指定します。インポートライブラリはVisualC用(tkdnpcie.lib)と、BorlandC用(tkdnpcie_bcc.lib)の2つのものが用意されています。
VisualCでコンパイル・リンクするには以下のように入力します。
cl your_program_source.cpp tkdnpcie.lib
BorlandCでコンパイル・リンクするには以下のように入力します。
bcc32 your_program_source.cpp tkdnpcie_bcc.lib
VisualStudioや、Borland C++ Builderなどの統合環境で使用する場合には、上記のライブラリファイルをプロジェクトに追加してください。
実行時の注意
プログラムの実行時には、tkdnpcie.dllをパスの通ったディレクトリに配置しておいてください。
また、特電PCI Expressデバイスドライバをインストールしておいてください。
5-3.デバイスをオープンする
ユーザ・アプリケーションで最初にやるべきことは、デバイスのオープンです。
デバイスをオープンするには、PcieOpen()関数を呼び出します。
この関数が TKPE_OPEN_SUCCESS を返せば、オープンは成功です。
そうでない場合は何らかのエラーが生じています。(デバイスが存在しない、ドライバがインストールされていない、など)
#include "tkdnpcie.h" #include <stdio.h> #include <wchar.h> #include <locale.h>
int main() { setlocale(LC_ALL, ""); // WCHARの文字列を表示するために必要な宣言
int status = PcieOpen(); // デバイスドライバをオープンする if(status != TKPE_OPEN_SUCCESS) // オープン失敗 { printf("Driver open failed. reason=%s\n",PcieGetOpenErrorString(status)); if(status == TKPE_ILLEGAL_VERSION) { // ドライバのバージョンが違う場合 unsigned long version = PcieGetDriverVersion(); unsigned long mj,mn,re,sp; mj = version >> 24; mn = (version >> 16) & 0xff; re = (version >> 8) & 0xff; sp = (version >> 0) & 0xff; printf("使用中のドライババージョン %x.%x.%x (特殊用途=%x)\n",mj,mn,re,sp); } return 0; }
|
5−4.各種情報の取得
ドライバのバージョン
次に、PcieGetDriverVersion()関数を使用してドライバのバージョンを調べます。
本稿執筆時点でのバージョンは、0x00070003です。(0.7.0.3の意味)
このバージョン番号は、今後のリリースを重ねるごとに更新されます。
ドライバの種類
特電PCI Express汎用ドライバは、評価ボードのデバイスドライバとして使用できるほか、汎用のデバイスドライバとしても使用できます。現在オープンされているドライバがどちらの動作をしているかを調べるには、PcieIsGenericDriver()関数を使用します。
この関数がFALSEを返せばPCI Express評価ボードのデバイスドライバとして動作しています。
TRUEを返せば、汎用ドライバとして動作しています。
情報文字列の取得
特電PCI Express汎用ドライバはWDMの形式に沿って作られたドライバです。WDMドライバは、親となるバスドライバから様々な情報を取得することができます。PcieGetDeviceInfo(ULONG id,WCHAR buffer[256]) 関数を使用すると、親ドライバから各種の情報を取得し、bufferで示されたバッファに格納します。
返される文字列は、WCHAR型で256ワードのバッファである必要があります。
id | 意味 | 意味 | 返される文字列の例 |
---|---|---|---|
0 |
BUS TYPE GUID |
|
|
1 |
CLASS GUID |
デバイスの種類を識別するためのGUID値 |
{E91FE2F3-117E-4A41-8508-5CD74881E1F5} |
2 |
CLASS NAME |
CLASS NAME |
|
3 |
DEVICE DESC |
DEVICE DESC |
NP1025A PCI Express 評価ボード |
4 |
DRIVER KEY NAME |
個々のデバイスを識別するためのGUID値 |
{E91FE2F3-117E-4A41-8508-5CD74881E1F5}¥0000 |
5 |
ENUMERATOR NAME |
親ドライバの種類 |
Root または PCI |
6 |
FRIENDRY NAME |
不明 |
|
7 |
LOCATION INFO |
デバイスの存在場所 |
PCI バス 1, デバイス 0, 機能 0 |
8 |
MANUFACTURE |
製造元 |
特殊電子回路株式会社 |
9 |
PDO NAME |
親ドライバの種類 |
¥Device¥00***** |
バス番号・デバイス番号の取得
現在開いているデバイスが、PCIのどのバスに接続されているかを調べるには、PcieGetBusNum()関数を使います。
この関数は
BOOLEAN WINAPI PcieGetBusNum(PULONG bus,PULONG dev,PULONG func);
という型で定義されています。
使用するには、
ULONG bus,dev,func; PcieGetBusNum(&bus,&dev,&func); |
のようにします。関数が成功すると、bus,dev,funcに値が設定されて戻ります。ただし、本ドライバはWDMドライバなので、デバイスの操作のためにバス番号やデバイス番号が必要になることはありません。
BAR空間の情報の取得
BAR0〜BAR5空間の情報を取得するには、PcieGetBar()関数を使います。
この関数は
BOOLEAN WINAPI PcieGetBar(ULONG barNum,PULONG startAddr,PULONG length);
という型で定義されています。
第一引数は知りたいBAR空間の番号で、0から5までの整数を指定します。
調べられた結果はポインタを用いて返されます。この関数を使用するには、
ULONG startAddr,length; PcieGetBar(0,&startAddr,&length); |
のようにします。関数が成功すると、startAddrにBAR0空間の開始する物理アドレスがセットされ、lengthにその長さ(バイト単位)がセットされて返ります。
5−5.コンフィギュレーション空間の読み書き
本ドライバはWDMドライバなので、ユーザプログラムからPCIデバイスのコンフィグ空間を読み書きする必要は通常はありませんが、コンフィグ空間にアクセスするための関数を備えています。
自デバイスに対するアクセス
以下の関数は、現在開いているPCIデバイスに対して、コンフィグ空間の読み書きを行います。
UCHAR WINAPI PcieReadConfigChar(ULONG offset); USHORT WINAPI PcieReadConfigShort(ULONG offset); ULONG WINAPI PcieReadConfigLong(ULONG offset); void WINAPI PcieWriteConfigChar(ULONG offset,UCHAR data); void WINAPI PcieWriteConfigShort(ULONG offset,USHORT data); void WINAPI PcieWriteConfigLong(ULONG offset,ULONG data); |
これらの関数のoffsetは、コンフィグ空間のアドレスを表します。関数名についたChar、Short、Longなどは、アクセスしたいサイズ(1バイト、2バイト、4バイト)を表します。これらの関数は、特電PCIデバイスドライバを使用するデバイスにしかアクセスできません。
任意のPCIデバイスに対するアクセス
次の関数を使うと、バス番号、デバイス番号、ファンクション番号を指定して、任意のPCIデバイスのコンフィグ空間にアクセスすることができます。非常に強力ですが使い方を間違えると大変危険です。
UCHAR WINAPI PcieReadConfigCharNT(ULONG bus,ULONG dev,ULONG func,ULONG offset); USHORT WINAPI PcieReadConfigShortNT(ULONG bus,ULONG dev,ULONG func,ULONG offset); ULONG WINAPI PcieReadConfigLongNT(ULONG bus,ULONG dev,ULONG func,ULONG offset); void WINAPI PcieWriteConfigCharNT(ULONG bus,ULONG dev,ULONG func, ULONG offset,UCHAR data); void WINAPI PcieWriteConfigShortNT(ULONG bus,ULONG dev,ULONG func, ULONG offset,USHORT data); void WINAPI PcieWriteConfigLongNT(ULONG bus,ULONG dev,ULONG func, ULONG offset,ULONG data); |
5−6.メモリ空間の読み書き
メモリ空間とは
PCIデバイスは、BAR0〜BAR5で表される6個のメモリ空間を持っています。
メモリ空間には、メモリ・マップド・レジスタや、メモリが配置され、PCIデバイスの各種機能を実現するために活用されます。
各空間のサイズや用途は、デバイスの設計者が任意に決めることができます。各空間の開始アドレスはパソコンの起動時(またはドライバが有効になったとき)に決定されます。アドレスは固定ではないので、メモリ空間を読み書きする関数を実行する前に、調べておく必要があります。
メモリ空間読み出し関数
以下の関数を使うと、メモリ空間の任意のアドレスのレジスタまたはメモリの値を読み出すことができます。
指定するアドレスは物理アドレスです。関数名についたChar、Short、Longなどは、アクセスしたいサイズ(1バイト、2バイト、4バイト)を表します。
UCHAR WINAPI PcieReadMemChar(ULONG addr); USHORT WINAPI PcieReadMemShort(ULONG addr); ULONG WINAPI PcieReadMemLong(ULONG addr);
|
addrは、読み出したいレジスタのアドレスです。PcieGetBar()関数を使ってBAR空間の先頭のアドレスを調べてから使用します。具体的には、メモリ空間0のオフセット+0x10番地の内容を読み出したい場合は、次のようにします。
ULONG startAddr,length; PcieGetBar(0,&startAddr,&length); // BAR0の先頭アドレスを得る printf("BAR0[0x10] = %x",PcieReadMemLong(startAddr + 0x10)); // PCIのメモリ空間を読み出す |
メモリ空間書き込み関数
以下の関数を使うと、メモリ空間の任意のアドレスのレジスタまたはメモリに任意の値を書き込むことができます。
ここで指定するアドレスは物理アドレスです。関数名についたChar、Short、Longなどは、アクセスしたいサイズ(1バイト、2バイト、4バイト)を表します。
BOOLEAN WINAPI PcieWriteMemChar(ULONG addr,UCHAR data); BOOLEAN WINAPI PcieWriteMemShort(ULONG addr,USHORT data); BOOLEAN WINAPI PcieWriteMemLong(ULONG addr,ULONG data);
|
addrは、読み出したいレジスタのアドレスです。PcieGetBar()関数を使ってBAR空間の先頭のアドレスを調べてから使用します。具体的には、メモリ空間1のオフセット+0x1c番地に値を書き込みたい場合は、次のようにします。
ULONG startAddr,length; PcieGetBar(1,&startAddr,&length); // BAR1の先頭アドレスを得る PcieWriteMemLong(startAddr + 0x1c)); // PCIのメモリ空間に書き込む |
ブロック転送関数
以下の関数を使うと、PCIのメモリ空間にまとまったデータをブロック転送することが出来ます。上記のメモリ空間読み書き関数を使うよりも高速です。
PUCHAR WINAPI PcieReadMemBlock(ULONG addr,PUCHAR data,ULONG bytes); BOOLEAN WINAPI PcieWriteMemBlock(ULONG addr,PUCHAR data,ULONG bytes); |
これらの関数を使うと、書き込み時は190MBytes/sec程度、読み出し時は4〜8MBytes/sec程度の速度で連続したデータを転送できます。第一引数のaddrは物理アドレスなので、本関数使用前にPcieGetBar関数を使ってあらかじめ調べておいてください。
第二引数のdataは、転送したいデータが格納されるバッファです。必ず転送するデータ量と同じか、より大きいサイズのバッファを確保しておいてください。
第三引数のbytesは転送したい長さです。
この関数は次のようにして使います。
ULONG startAddr,length; UCHAR bar1[8192]; PcieGetBar(1,&startAddr,&length); // BAR1の先頭アドレスを得る PcieWriteMemBlock(startAddr,data,8192); // BAR1空間に8192バイトのデータを書き込む |
DMA転送関数
以下の関数を使うと、DMA転送を行います。
書き込みは160MBytes/sec程度、読み出しは130MBytes/sec程度の速度が出ます。
BOOLEAN WINAPI PcieReadDMA(ULONG targetAddr,PUCHAR buf,ULONG length); BOOLEAN WINAPI PcieWriteDMA(ULONG targetAddr,PUCHAR buf,ULONG length); |
targetAddrは、転送先のFPGA内のローカルアドレスを示します。
bufには、mallocやnewで確保したバッファのアドレスを指定します。
lengthには、転送したいデータ長をバイト単位で指定します。ただし、4の倍数になっている必要があります。
割り込み制御関数
以下の関数を使うと、割り込みを受け取ることができます。
BOOLEAN WINAPI PcieRegistISR(CALLBACK_ISR isr); |
isrは、
typedef void (WINAPI *CALLBACK_ISR)(ULONG reason);
型のコールバック関数で、ユーザの割り込み処理ルーチンを指定します。
この関数は呼び出されるときに、16bitの割り込み発生要因を受け取ります。
具体的には、次のようなコードとなります。
void WINAPI myisr(ULONG reason) { printf("割り込み発生!割り込み要因=%04x¥n",reason); intcount++; }
int main () { .....
if(PcieRegistISR(myisr) == FALSE) { printf("割り込みセットアップ失敗¥n"); return 0; }
..... } |
main関数が終了する(ドライバがクローズされる)と、割り込みは自動的に解除されます。
5−7
.GPIOに値を出力する
特電PCI Express評価ボードのサンプルデザインでは、BAR0+2番地にあるレジスタを操作することで、LEDの点灯状態を変化させることができます。
これを操作するコードの例を以下に示します。
ULONG bar0,length;
PcieGetBar(0,&bar0,&length); // BAR0の先頭アドレスを得る PcieWriteMemChar(bar0 + 2, 0x00); // BAR0[2]に0x00を書き込み。LEDはTLP番号を表示 Sleep(1000); PcieWriteMemChar(bar0 + 2, 0x01); // BAR0[2]に0x01を書き込み。LEDはカウント値を表示 Sleep(1000); PcieWriteMemChar(bar0 + 2, 0x02); // BAR0[2]に0x02を書き込み。LEDは擬似乱数を表示 Sleep(1000); PcieWriteMemChar(bar0 + 2, 0x03); // BAR0[2]に0x03を書き込み。LEDはユーザ指定 PcieWriteMemChar(bar0 + 0, 0x55); // BAR0[0]に0x55を書き込み。LEDは○●○●○●○● Sleep(1000); PcieWriteMemChar(bar0 + 0, 0xaa); // BAR0[0]に0xAAを書き込み。LEDは●○●○●○●○ Sleep(1000); PcieWriteMemChar(bar0 + 0, 0x55); // BAR0[0]に0x55を書き込み。LEDは○●○●○●○● Sleep(1000); PcieWriteMemChar(bar0 + 0, 0xaa); // BAR0[0]に0xAAを書き込み。LEDは●○●○●○●○ Sleep(1000); PcieWriteMemChar(bar0 + 2, 0x00); // BAR0[2]に0x00を書き込み。LEDはTLP番号を表示
|
このようにして、PCI Expressの先にあるGPIOを簡単に制御することができます。
Copyright(C) 2009 TokushuDenshiKairo Inc. All rights reserved.
info@tokudenkairo.co.jp