PCI Expressソフトウェア開発方法
このページ内の目次
ダウンロード
コンパイル方法
プログラムの開発方法
ヘッダのインクルード
デバイスのオープン
BAR空間の情報の取得
メモリ空間読み出し
メモリ空間書き込み
DMA転送
割り込み制御
ライセンス
PCI Express汎用DMAデバイスドライバの実体はtkpe2.sysというカーネルモードのプログラムです。このプログラムはtkdnpcie2.dllを介して操作します。
tkdnpcie2.dllにはコンフィグ空間/メモリ空間の読み書き、DMA転送、割り込みの制御など、様々な機能を提供しています。お客様はtkdnpcie2.dllを使ってプログラミングすることで、簡単にPCI Expressデバイスを制御することができます。
ダウンロード
以下のリンクからDMA対応デバイスドライバ用DLLと、サンプルプログラムをダウンロードしてください。
- PCI Express汎用DMAデバイスドライバ用サンプルプログラム 2017/9/4
- Windows7~10 x64対応
コンパイル方法
Microsoft Visual C++を使用してコンパイルする方法を説明します。
お客様が作成したアプリ(tkpe2test.cppとする)をビルドするには、インポートライブラリtkdnpcie2.libをリンクしてコンパイルします。
コンソールからコンパイルするには、
cl tkpe2test.cpp tkdnpcie2.lib
で行います。
実際に行った結果を以下に示します。
VisualStudioの統合環境でビルドする場合はプロジェクトにtkdnpcie2.libを追加してください。
このプログラムを実行するには、exeと同じディレクトリにtkdnpcie2.dllを置いておいてください。
プログラムの開発方法
ヘッダのインクルード
プログラムの先頭で、tkdnpcie.hをインクルードしてください。
#include#include "tkdnpcie.h"
デバイスのオープン
ユーザ・アプリケーションで最初にやるべきことは、デバイスのオープンです。
デバイスをオープンするには、PcieOpen()関数を呼び出します。
この関数が TKPE_OPEN_SUCCESS を返せば、オープンは成功です。
そうでない場合は何らかのエラーが生じています。(デバイスが存在しない、ドライバがインストールされていない、など)
以下にサンプルコードを示します。
#include "tkdnpcie.h" #include#include #include 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; }
ドライバのバージョンを調べる
次に、PcieGetDriverVersion()関数を使用してドライバのバージョンを調べます。
本稿執筆時点でのバージョンは、0x02000004です。(2.0.0.4の意味)
このバージョン番号は、今後のリリースを重ねるごとに更新されます。
ドライバの種類
特電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}%MAIN_CONTENTS%00 |
| 5 |
ENUMERATOR NAME |
親ドライバの種類 |
Root または PCI |
| 6 |
FRIENDRY NAME |
不明 |
|
| 7 |
LOCATION INFO |
デバイスの存在場所 |
PCI バス 1, デバイス 0, 機能 0 |
| 8 |
MANUFACTURE |
製造元 |
特殊電子回路株式会社 |
| 9 |
PDO NAME |
親ドライバの種類 |
\Device%MAIN_CONTENTS%***** |
バス番号・デバイス番号の取得
現在開いているデバイスが、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にその長さ(バイト単位)がセットされて返ります。
コンフィギュレーション空間の読み書き
本ドライバは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バイト)を表します。
メモリ空間の読み書き
メモリ空間とは
32bitのPCIデバイスはBAR0~BAR5で表される6個のメモリ空間を、64bitのPCIデバイスはBAR0~BAR2で表される3個のメモリ空間を持っています。
メモリ空間には、メモリ・マップド・レジスタや、メモリが配置され、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のメモリ空間にまとまったデータをブロック転送することが出来ます。上記のメモリ空間読み書き関数を使うよりも高速です。とは言っても、Kintex-7のPCI Expressコアを使う場合には、書き込み時は100MBytes/sec程度、読み出し時は4~8MBytes/sec程度の速度しか出ません。より高速な転送を行うにはDMAを使います。
PUCHAR WINAPI PcieReadMemBlock(ULONG addr,PUCHAR data,ULONG bytes); BOOLEAN WINAPI PcieWriteMemBlock(ULONG addr,PUCHAR data,ULONG bytes);
第一引数のaddrは物理アドレスなので、本関数使用前にPcieGetBar関数を使ってあらかじめ調べておいてください。
第二引数のdataは、転送したいデータが格納されるバッファです。必ず転送するデータ量と同じか、より大きいサイズのバッファを確保しておいてください。
第三引数のbytesは転送したい長さです。
この関数は次のようにして使います。
ULONG startAddr,length; UCHAR bar1[8192]; PcieGetBar(1,&startAddr,&length); // BAR1の先頭アドレスを得る PcieWriteMemBlock(startAddr,data,8192); // BAR1空間に8192バイトのデータを書き込む
DMA転送関数
以下の関数を使うと、スキャッタギャザーDMA転送を行うことができます。
読み書きとも1600MBytes/sec程度の速度が出ます。
BOOLEAN WINAPI PcieReadDMA(ULONG targetAddr,PUCHAR buf,ULONG length); BOOLEAN WINAPI PcieWriteDMA(ULONG targetAddr,PUCHAR buf,ULONG length);
PCI ExpressのDMAはBAR0~BAR5のメモリ空間とは関係がないので、BARアドレスを調べる必要はありません。必要になるのは、転送先のFPGAのローカルアドレスと、ユーザプログラムが確保したメモリバッファの先頭アドレスのみです。
引数のtargetAddrは、転送先のFPGA内のローカルアドレスを示します。
bufには、mallocやnewで確保したバッファのアドレスを指定します。
lengthには、転送したいデータ長をバイト単位で指定します。ただし、4の倍数になっている必要があります。
割り込み制御関数
以下の関数を使うと、割り込みを受け取ることができます。
BOOLEAN WINAPI PcieRegistISR(CALLBACK_ISR isr);
引数のisrには、以下の書式で示されるコールバック関数を指定します。
typedef void (WINAPI *CALLBACK_ISR)(ULONG reason);
このコールバック関数は割り込みが発生したときに呼び出され、ユーザのFPGAが定義した16bitの割り込み発生要因を受け取ります。
具体的には、次のようなコードとなります。
void WINAPI myisr(ULONG reason)
{
printf("割り込み発生!割り込み要因=%04x\n",reason);
intcount++;
}
int main ()
{
...オープン処理..
if(PcieRegistISR(myisr) == FALSE)
{
printf("割り込みセットアップ失敗\n");
return 0;
}
.....
}
main関数が終了する(ドライバがクローズされる)と、割り込みは自動的に解除されます。
ライセンス
以下の条件のもと、どなたにでもこのドライバを使用することを許諾します。
- このソフトウェアは当社の著作物です。
- このソフトウェアは無保証です。特殊電子回路は実行結果に対して一切の責任を負いません。
- 当社Webサイトからダウンロードしたファイル(ソースコード、DLL、LIB、SYS)を再配布することはできません。
- お客様がtkdnpcie.libをリンクして生成したバイナリを再配布することは可能です。
- ソースコード、DLL、LIB、SYSは再配布できません。再配布先のエンドユーザに当社のWebサイトからダウンロードするように伝えてください。





