2017年8月
JESD204BのデータをPCIe-DMAで受け取る
2017.08.31
高速ADCコンバータから送られてくるJESD204Bの信号を、Kintex-7でデコードして、それをPCI Expressで受け取るサンプルデザインができました。うちのアルバイトさんが全部作ってくれました。
使っているADCボードは1Gsps 16bit 2chのHyperFADC 、PCI Experssボードは40Gbps光モジュールのついたCosmo-K+ です。
デザインの全体は下の図のようになっています。PCI ExpressのブロックとJESD204BのブロックがBlockMemoryを共有してデータをやり取りする構成になっています。
PCIe BlockにはXDMAコアが入っていて、ホストPCからの指令によってAXIのバースト転送を発生させるようになっています。
JESDのブロックには、JESD204BのPHY、JESD→AXI Streamの変換コア、MicroBlaze、AXI DMAなどが入っています。
XILINXのJESDのコアは有料版ならばAXI出力ができるらしいのですが、無料版はPHYのみしかないようです。そこで、JESDのPHYのみXILINXのコアを使って、AXI Streamへの変換は自作しました。
JESD PHYの設定はこのようになっています。HyperFADCは10Gbps×4本で信号を出力してきますが、信号の受信は2.5Gbpsなので、送信は2.5G、受信は10Gと非対称な構成になっています。
MicroBlazeが入っている理由は、JESD204BをAXI Streamに変換した後、それをAXI MemoryMapに変換する「AXI DMA」の設定のためです。
MicroBlazeを入れるのはオーバースペックではないかと思ったのですが、AXI DMAは意外と設定することが多いので、ハードウェアのロジックでAXI Liteのトランザクションを発行して設定するよりも、MicroBlazeを入れてしまったほうが楽だそうです。
それに、ヒストグラムが乱れたら高速ADCにリセットをかけるなどというインテリジェントな動作もできます。
AXI DMAは、AXI StreamをMemoryMapに変換します。設定を次の図に示します。
こうして、JESD204Bで受信したデータを、AXI Stream→AXI MM→BlockRAMへと格納し、PCI ExpressからXDMAを使って読み出すデザインが出来上がりました。
Windows10でWindows7用のデバイスドライバを開発する
2017.08.30
先日書いたWindows10時代のデバイスドライバ開発とデバッグの記事にしたがって作ったデバイスドライバは、Windows7や8で動かないようです。
おそらく、最新のWDKを入れているので、Windows8用のライブラリなどがないためでしょう。
試しに、VisualStudioのプロジェクトのプロパティでプラットフォームに8.1を選択してみたものの、wdm.hがないとか、いろいろなエラーが出ます。
やはり8.1対応のSDKやWDKをいれないといけないようです。
そこで、昔のマシンを使ってWindows7時代のWDKを使ってビルドしてみました。同じソースなのですが、なぜかCではなくCPPにしないとうまくコンパイルできないとか困ったことはありましたが、とりあえずビルドはできて、署名を施したらWindows8のマシンにインストールできました。
Windows10マシンでDMAの速度を測ってみると、Windows7用に作った速度は1600MB/s程度。
WDK10でWindows10用に作ったドライバも速度も1600MB/st程度で、全く差はありませんでした。
PCI Express 64bitでのDMA
2017.08.12
PCI ExpressのBARを64bitにして、デバイスドライバで64bitのDmaAdapterをgetすれば、マップレジスタがエミュレーションされないのでデータのコピーが行われず、超高速になるのではないかと思い、実験してみました。
結果は大正解でした。
Read/Writeともに1.6GB/sほど出ています。PCI ExpressのBARが32bitだったときには1.0GB/s程度だったので、約1.6倍に高速化されました。この差は大きいですね。
一つ一つの転送波形を見てみることにします。
まず、DMA WRの波形。
拡大してみてみると、256ns転送して32ns休むというサイクルになっています。
256/(256+32) * 2GB = 1.7GB
なので、XDMAコアが出すAXIの最高速度はこのくらいです。
また、DMA RD(C2H)の波形ですが、2.4usくらいのサイクルの間に2.1usくらいの転送が行われているので、やはり1.7GBくらいとなります。
PCI Expressの先にあるAXIの速度が1.7GBで、Windowsのアプリケーションで測った速度が1.6GBなので、ほぼフルの速度が出ていると考えてよいともいます。
結論をまとめると、
- PCI Expressを32bit BAR構成にすると、スキャッタギャザーDMAを実現するときにHALでデータコピーが発生するので遅くなる。約1.0GB/secしかでない。
- 64bit BAR構成にすると、データコピーが発生しない。約1.6GB/secと、ほぼフルの速度が出る
また、64bit対応したPCI Expressカードならば、ドライバが巨大なコモンバッファを確保してくれるようになります。コモンバッファというのは物理アドレスで連続したページされないメモリ領域です。DMA転送などの用途にも使えます。
32bitだと8MBくらいしか取れませんが、64bit版だと4GBまで取れます。これだけでも、64bitのPCI Expressにする意義があるといえるでしょう。
XILINXのXDMAコアを使って、PCI Express Gen2 x4でバスの帯域2.0GB/sのところ、上り下りとも1.6GB/sまでできました。
PCI ExpressのDMA転送を改善する
2017.08.10
前回の記事ではWindowsが用意するしくみでは、1MBや2MBといったサイズでDMAが制限されていることを書きました。
具体的には、ドライバのコードで以下のようなものを書きます。
DEVICE_DESCRIPTION DeviceDescription; RtlZeroMemory(&DeviceDescription,sizeof(DeviceDescription)); DeviceDescription.Version = DEVICE_DESCRIPTION_VERSION; DeviceDescription.Master = TRUE; DeviceDescription.ScatterGather = TRUE; DeviceDescription.Dma32BitAddresses = TRUE; DeviceDescription.Dma64BitAddresses = FALSE; DeviceDescription.InterfaceType = PCIBus; DeviceDescription.MaximumLength = 0x10000000; // 256MByte // DMAアダプタを作成 dx->dmaAdapter = IoGetDmaAdapter(dx->pdo, &DeviceDescription, &dx->NumOfMappedRegister); KdPrint(("DmaAdapter: Num of Mapped registers =%d \n",dx->NumOfMappedRegister));
このコードは、DMAアダプタというものを獲得するコードなのですが、32bitのPCIバスを使って最大256MByteのDMA転送をするようなDMAアダプタを作ってくれ、と要求するものです。
ポイントは上の赤い字で記した、Dma64BitAddress=FALSEというところにあります。
64bitAddressesがFALSEだと、マップレジスタが256個しかとられません。
64bitAddressesをTRUEにしてみると、あら不思議、マップレジスタが65537個取れました。
さくっと256MByte用のマップレジスタが取れたのでしょうか?
いや、PCI Expressも64bitでCPUも64bitなので、おそらくマップレジスタを使わずにDMA転送を行えるようになったのでしょう。
64bit版PCI Expressデバイスのすごいところはマップレジスタを使わないだけではなく、コモンバッファも巨大なサイズが取れるということです。
メインメモリ16GB積んだPCで、0xffffffffバイトのCommonBufferを要求したら、物理アドレス0x00000003-00000000から連続した4GBの長さのバッファが取れました。
32bit版のPCIeとドライバでは4MBや8MBが限度だったので、4GBのバッファというのはすごいことです。
64bit版ドライバと64bit版デバイスはすごいですね。
本当にPCI Expressを64bitにするには、64bit BARを有効にしなければなりません。XILINXのXDMAコアでは、以下のオプションを設定します。
これで64bit版のBARが有効になります。
そして論理合成してBitファイルを書き込んだら、一度再起動します。32bitBARから64bitBARに切り替えるには、デバイスマネージャからの無効→有効するだけではうまくいかないようです。
WindowsのDMAとMapRegister
2017.08.09
スキャッタギャザーDMAを発行しようとして試行錯誤していたのですが、どうしても1MByte以上の転送を行おうとするとブルースクリーンになってしまいました。
最初はMapTransferという関数を使っていたのですが、オブジェクトの開放の手順がよくわからなかったので、途中からGetScatterGatherListを使うようにしたのですが、1MByteの転送をしようとすると物理アドレス(論理アドレス)のリストを作るだけで、実際の転送をしなくてもブルースクリーンになってしまいます。
100回くらい青い画面を出して、ようやくわかってきました。
スキャッタギャザーDMAでは、ユーザがmallocで確保したバッファが実際に割り当てられている物理メモリのアドレスを調べて、そこをめがけてFPGAからDMA転送するので、原理的にはデータのコピーが発生しません。
しかし、この方法には問題があります。
PCI Expressのアドレスはデフォルトでは32bitなので、4GBまでの転送先アドレスしか指定できません。パソコンのCPUのアドレスは4GBでは収まらないメモリ空間を持っています。それゆえ、メモリRD/WRを発行しても、メインメモリの先頭の4GBまでしかアクセスできないことになってしまいます。
そこで、Windowsでは、マップレジスタというものが用意されています。マップレジスタというのは、ハードウェアデバイス用の仮想メモリのようなもので、PCI Expressから指定された転送先アドレスを実際のDIMMの物理アドレスに変換する機能です。
つまり、PCI ExpressからメモリRD/WRで指定してくるアドレスは、パソコンのメモリの物理アドレスではなく、ハードウェア版のアドレス変換機構を通す前のアドレスなのです。これを論理アドレスといいます。
そして、物理アドレスと論理アドレスを変換する機能がマップレジスタなのです。
しかし、マップレジスタというのは実際に存在するわけではなく、そういう機能があったらいいな、というものをHALがエミュレートしているのです。
わけがわからないとは思いますが、つまりこういうことです。
- MapTransferやGetScatterGatherListを実行すると、DMA転送先のアドレスとして、下位4GBを指すようなものが返される。
- この下位4GBのバッファには、実際にユーザ用のバッファが割り当てられているわけではなく、DMA転送の前後で、カーネルがこっそりメモリコピーを行う
Windowsはこんな仕組みでDMAを実行しているのです。
ブルースクリーンになってしまう原因はマップレジスタの数でした。私が新しく買ったWindows10 64bitマシンでは、デバイスドライバの中で「このデバイスは0x10000000(256MByte)のDMAを行う」と申告しても、マップレジスタを256個しか割り当ててくれませんでした。
マップレジスタ1個は1つの物理ページ(=4096バイト)のアドレス変換テーブルなので、1MByte分のアドレス変換しかしてくれないのです。これ以上の長さのDMAをしようとしてもマップレジスタが無いので、NULLを返してしまい、それを開放しようとしてブルースクリーンになっていたのですね。
家のPCでやってみたところ512個のマップレジスタが取れました。
これなら2MBまでのDMA転送ができます。
実際に連続してスキャッタギャザーDMAをやってみたところ、アプリケーションレベルで測って毎秒1GB/secの速度で転送できました。
しかし、1MBや2MBのサイズのDMA転送しかできないのですから、効率が悪いですね。
Windows7や8で32bit版ドライバを動かしていたときにはもっと確保できていたと思うのですが・・
重要なことは、スキャッタギャザーといってもユーザのバッファにDMA転送しているわけではなく、裏でカーネルがこっそりコピーしているということです。そのカーネルがこっそりコピーできるサイズが、1MBや2MBしかないのです。
Windows10におけるXDMAコア割り込みの発生方法
2017.08.08
DMAの完了を知るためには割り込みの使用が必須です。
XDMAコアはかなり複雑なのですが、割り込みを利用するには開始コマンドを発行する際に、以下のようなレジスタ設定をします。
// 割り込みマスクの設定。転送失敗も含め、あらゆる要因を受け付ける WRITE_REGISTER_ULONG(&DmaReg[0x0090 / 4], 0xfffffe); // DMA割り込みの許可 WRITE_REGISTER_ULONG(&DmaReg[0x2010 / 4], 0xffffffff); DbgPrint("DMA descriptor set at %08x-%08x\n", dx->combufPhysAddr.HighPart, dx->combufPhysAddr.LowPart); // DMAデスクリプタのアドレスを指定 WRITE_REGISTER_ULONG(&DmaReg[0x4080 / 4], dx->combufPhysAddr.LowPart); // デスクリプタのアドレスを書き込む WRITE_REGISTER_ULONG(&DmaReg[0x4084 / 4], dx->combufPhysAddr.HighPart); WRITE_REGISTER_ULONG(&DmaReg[0x4088 / 4], 0); // extra adjは0 WRITE_REGISTER_ULONG(&DmaReg[0x0004 / 4], 0xfffe7f); // DMA開始
これでDMAの完了時に割り込みが発生します。
さて、FPGAがPCI Expressに割り込みを発生させたら、ドライバできちんと扱わなければなりません。そうしないと、割り込みが発生しっぱなしになって、CPUが割り込み確認→戻る→確認→戻るを繰り返してしまいます。
なお、Windowsのデバイスドライバでは、割り込み発生時に呼び出されるルーチン(ISR)は極めて高いIRQLで呼び出されます。
IRQLが高いというのは、優先順位が高いということなのですが、マイコンのようなものをイメージしてはいけません。
優先順位が高いルーチンというのは非常にプログラミングがしにくいのです。まず、使用できるメモリが非常に限られてきます。ページドメモリ(スワップ対象のメモリ)が使えないとか、いろいろと制約が多く、カーネルAPIの多くも使用禁止です。
以下のTPInterruptHanderという関数は実際のISRの例ですが、割り込みサービスルーチンでは、割り込み要因の確認と、割り込みフラグのクリアを行い、DPC(遅延プロシージャコール)を行って戻します。
BOOLEAN TPInterruptHandler( IN PKINTERRUPT Interupt, IN PVOID ServiceContext ) { BOOLEAN interruptRecognized = FALSE; PTP_DEVICE_EXTENSION dx = (PTP_DEVICE_EXTENSION)ServiceContext; ULONG *DmaReg = (PULONG)dx->barVirtualAddress[1]; // BAR1 ULONG DmaReason = READ_REGISTER_ULONG(&DmaReg[0x2044 / 4]); if (DmaReason & 1) { // bit0 : H2C channel interrupt WRITE_REGISTER_ULONG(&DmaReg[0x2018 / 4], 1); // mask IRQ, W1Cなので書き込むとマスク dx->InterruptReason |= 0x80000000; // 要因を保存 interruptRecognized = TRUE; // このハンドラで処理した } if(interruptRecognized) { IoRequestDpc(dx->fdo, NULL, dx); // 遅延ルーチン呼び出し InterlockedIncrement(&dx->InterruptCount); // 割り込み回数をカウント } return interruptRecognized; }
XDMAではDMA(H2C)完了の割り込みが発生すると、BAR1のオフセット0x2044のbit0が'1'になります。そのため、ここではフラグを確認して、割り込みをマスクしてDPCの呼び出しをセットするだけです。
DPCルーチンは、IRQLが低いので比較的なんでもできます。DbgPrintも気兼ねなく使うことができます。
LONG tempReason; tempReason = InterlockedExchange((LONG *)&dx->InterruptReason,0); //バスマスタ完了割込み処理。 if (tempReason & 0x80000000) { KeSetEvent(&dx->DmaEvent, IO_NO_INCREMENT, FALSE); // DMAイベントをセット tempReason &= ~0x80000000; }
当ドライバでは上のようにして、カーネルのイベントをセットします。
DMA発生元のルーチンでは、
Timeout.QuadPart = -30000000; // 3秒待ち status = KeWaitForSingleObject(&dx->DmaEvent, Executive, KernelMode, FALSE, &Timeout); if (status == STATUS_TIMEOUT) { DbgPrint("NO DMA interrupt.\n"); *BytesReturned = 0; return STATUS_TIMEOUT; } ULONG *DmaReg = (PULONG)dx->barVirtualAddress[1]; ULONG DmaStatus; ULONG DescCount; WRITE_REGISTER_ULONG(&DmaReg[0x0004 / 4], 0); // DMA停止 DmaStatus = READ_REGISTER_ULONG(&DmaReg[0x0044 / 4]); DescCount = READ_REGISTER_ULONG(&DmaReg[0x0048 / 4]); DbgPrint("DMA done:DmaStatus=%08x, Complete descriptor count=%d\n", DmaStatus, DescCount); WRITE_REGISTER_ULONG(&DmaReg[0x2014 / 4], 0xf); // 割り込みマスクを許可する *BytesReturned = length; return STATUS_SUCCESS;
として、KeWaitForSingleObject割り込みを待ち受けます。上のコードでは3秒でタイムアウトするようになっています。
割り込みが発生したら、ISR→DPC→イベント発生→元のプログラムが再開という流れになります。
そして、BAR1の0x0004に0を書いてDMAを停止させて、その後、DMAのステータスと完了したDescriptorの数を調べています。
このように、割り込みを使ってDMAの完了を知ることができるようになります。
Windows10時代のデバイスドライバ開発とデバッグ
2017.08.07
DMAのデバイスドライバを開発するため、Windows10での開発環境を整える必要が出てきましたので調べてみました。
いくつかの参考になりそうな記事がありますので、リンクを貼っておきます。
「Windows 10 でサンプル ドライバーをビルドするまで」
https://blogs.msdn.microsoft.com/jpwdkblog/2015/08/21/windows-10/
「ユニバーサル Hello World ドライバー (KMDF) の作成」
https://msdn.microsoft.com/ja-jp/library/windows/hardware/hh439665(v=vs.85).aspx
最初の「Windows 10 でサンプル ドライバーをビルドするまで」に従ってインストールします。
Visual Studio 2015 Expressのインストール
まずは、VisualStudio2015のExpressをインストールしなければなりませんが、これが大変です。ExpressはCommunityに変わり、2015は2017に変わったので、2015 Expressをダウンロードしようとしても2017 Communityに飛ばされてしまいます。なかなかたどり着けません。
Visual Studio 2015 Expressのダウンロード先はこちらです。
日本語版 https://www.visualstudio.com/ja/post-download-vs/?sku=xdesk&clcid=0x411&telem=ga
英語版 https://www.visualstudio.com/ja/post-download-vs/?sku=xdesk&clcid=0x409&telem=ga
上のページを訪れると自動的にダウンロードの保存ダイアログが出るので、保存します。
私は日本語版をインストールしました。
Windows 10 SDKのインストール
Windows 10 SDKは難しくありません。下記のページからEXEをダウンロードする、を押せば手に入ります。
https://developer.microsoft.com/ja-jp/windows/downloads/windows-10-sdk
Windows 10 WDKのインストール
下記のページにあります。
https://developer.microsoft.com/en-us/windows/hardware/windows-driver-kit
ユニバーサル Hello World ドライバー (KMDF) の作成
参考リンクの「ユニバーサル Hello World ドライバー (KMDF) の作成」をしてみようと思っても、同じようにはいきません。まず、ソリューションエクスプローラで「追加」→「新しい項目」とやっても、Package Manifestしか出ないのです。
C++やHのファイルのテンプレートというものがないためなのですが、テンプレートは以下のフォルダの中にあります。
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\VCProjectItems
しかし、VCフォルダに存在するのはVCProjectItems_WDExpressです。そこで、。VCProjectItems_WDExpressをコピーしてVCProjectItemsに名前を変えます。
プロジェクトに新しいC++やCファイルを追加できるようになります。
これで、ユニバーサル Hello World ドライバー (KMDF) の作成が作れるようになります。
配置とデバッグ
ただ、その先にある配置やデバッガのところがうまくいきません。リモートデバッグの方法も試してはみましたが、参考リンクのようにはいきません。
そもそも2台のPCをつないでデバッグする・・というのは理想的ではありますが、やはり1台のPCでやりたいものです。
私の方針は以下のとおりです。
- セキュアブートを無効にする
- 開発用兼実験用PCで、カーネルモード署名の強制を無効にする
- Windowsをbcdeditでテストモードにする
カーネルモードでのデバッグはDbgPrintを使います。KdPrintは括弧を二重にしないといけなかったり、何かと使いにくいのです。DbgPrintは
DbgPrint("DmaAdapter address = %x",addr);
のようにprintf感覚で使えてとても便利です。DbgPrintの出力は、DbgView.exeで見ることができます。
DbgView.exeは以下のリンクから入手できます。
https://technet.microsoft.com/ja-jp/sysinternals/debugview.aspx
DbgView.exeを管理者モードで起動して、Captureのオプションを下記のように設定すれば、DbgPrintの出力を見ることができます。
●ドライバの日付について
基本的にビルドしてテストしての繰り返しなので、面倒なことはしたくありません。Visual Studioでビルドしたドライバには、自動的にinf2catやsigntoolで署名が施されます。
ただし、日付がUTCなので日本の午前0時を過ぎると翌日になってしまい、未来の日付は使えないとエラーになってしまいます。そこで、下記のオプションを設定する必要があります。
ドライバの署名について
Visual Studioでプロジェクトを作ると、オレオレ証明書ができているようです。
Debugフォルダの中に.cerというファイルがありますので、これを信頼されたルート証明機関にインストールします。
これで、自分のPCだけで使えるAuthenticode署名ができるようなのですが、
ドライバのインストールで失敗するようなので、私はWindowsをテストモードにして、署名の強制を無効にして使っています。
ざっと駆け足で説明しましたが、このような手順でWindowsのデバイスドライバを開発できるようになると思います。
XILINX XDMAの使い方と速度
2017.08.06
XILINXのXDMAコアの使い方を調べています。
XDMAはディスクリプタベースのスキャッタギャザーDMAをサポートしているということです。
これだけだと何のことかさっぱりわからないと思いますが、ディスクリプタというのはDMAの転送元アドレスや転送先アドレスを書いたリストのことで、スキャッタギャザーというのは細かいDMAを大量に発行することを意味します。
さらに詳しく説明しますと、ユーザ空間でmallocやnewで確保したメモリというのは、一般的には物理メモリ上では細かく断片化しています。
イメージ的には下の図のような感じになります。
断片化の大きさや数、長さは常に変わっているので上の図のアドレスやサイズはあくまでもイメージです。数十キロバイトサイズの大きな連続したブロックに割り当てられていることもありますし、64バイトしかないこともあります。
とにかく、ユーザのバッファは物理(論理)アドレス上では断片化しているので、何番地~何番地までのメモリの内容を一度にFPGAに転送することはできません。
そこで、この断片化されたリストを送って、細かいDMAをたくさん発行するのがスキャッタギャザーです。スキャッタギャザーとは、散乱したものを集めるという意味です。
この断片化したパソコン内の物理メモリのアドレスとFPGAの転送元/先アドレスの情報を記したものがディスクリプタです。XILINX XDMAのディスクリプタは8ワード長で、形式は以下のとおりです。
オフセット0の上位16bitはMagicワードで、ここには0xad4bを指定します。下位8bitに0x13を書いておくと、転送終了後にSTOP、COMPLETE、EOPというイベントを発行します。
Lenにはバイト単位で長さを指定します。
src_addrはDMA転送元のアドレスです。DMA WR(Host to Card)の場合はPC上のメモリの物理アドレス(論理アドレス)を書いておきます。
dst_addrはFPGAのAXIに発行されるメモリのアドレスです。
nxt_addrは次のディスクリプタが格納されたPC上のメモリの物理アドレス(論理アドレス)です。スキャッタギャザーなので、複数のディスクリプタをリストにして使えるのですが、今回は1個のDMAを発行するのでnextポインタには0を指定しておきます。
さて、ディスクリプタはパソコンのメモリの中に書いておく必要がありますが、どこに書けばよいのでしょうか?
基本的に、mallocなどで確保したメモリはページアウトされている可能性があるのと、不連続なので、ディスクリプタには不向きです。
そこで、断片化していない連続した非ページメモリが最適です。そういうメモリは、AllocateCommonBufferという関数で確保します。AllocateCommonBufferは単純な関数ではなく、DMAアダプタを作ったあとでその中に関数ポインタとして存在しています。つまり、DMAのバスのタイプによって実装が変わる関数なので、単純ではありません。
コモンバッファ(共有バッファ)というのは、仮想アドレスと、物理アドレス(論理アドレス)の両方を知ることができるバッファです。2種類のアドレスでアクセスできるのでコモンバッファといわれているようです。
そういうわけで、AllocateCommonBufferで確保したコモンバッファにディスクリプタを作ります。
ULONG *desc = (ULONG *)dx->combuf; // 作ったコモンバッファの仮想アドレス desc[0] = 0xad4b0013; desc[1] = 100; // bytes desc[2] = dx->combufPhysAddr.LowPart; // src addr low desc[3] = dx->combufPhysAddr.HighPart; // src addr high desc[4] = 0; // dest addr low desc[5] = 0; // dest addr high desc[6] = 0; // next addr low desc[7] = 0; // next addr high // BAR1にDMAコントロールレジスタがある ULONG *DmaReg = (PULONG)dx->barVirtualAddress[1]; // DMAデスクリプタのアドレスを指定 WRITE_REGISTER_ULONG(&DmaReg[0x4080/4], dx->combufPhysAddr.LowPart); // デスクリプタのアドレスを書き込む WRITE_REGISTER_ULONG(&DmaReg[0x4084/4], dx->combufPhysAddr.HighPart); WRITE_REGISTER_ULONG(&DmaReg[0x4088/4], 0); // extra WRITE_REGISTER_ULONG(&DmaReg[0x0004/4], 0xfffe7f); // DMA開始
そして、BAR1の0x4080,0x4084にディスクリプタのあるPC上の物理アドレスを指定し、BAR1の0x0004にDMA開始のコマンドを書き込みます。本当は0x000001でいいのですが、エラーやその他の停止条件もトリガするため0xfffe7fを書いています。
このようにするとXDMAのコアが動き出します。実際に100バイトの書き込みを行ってみた時の波形は以下のようになりました。
AXIの転送サイクルとしては2回発行されていて、最初に4クロック(16バイト×4)で64バイトが転送され、次に32バイトと4バイトが転送されています。
データバスは128bitなので、wstrbが0xffff→0x000fと変化することで4バイトの書き込みに対応します。
次の波形は16kByteの転送を行ったときのものです。
小さな転送がたくさん行われていて、全体が終わるまでに約9.5μ秒かかっています。ここだけみれば1700MB/secの速度で転送できていることになります。
なお、1つ1つの転送は256nsの時間で行われているので、32クロック=512バイトです。
偶然によっては、転送先アドレスが0で始まらない場合もあって、wstrbがFFFFとならず1クロック余計にかかることもあります。
これでDMAの発行ができるようになりました。
次はスキャッタギャザーに本格対応させるために、デバイスドライバの開発環境を整えることにします。
Kintex-7のPCI ExpressコアのWrite速度
2017.08.05
Kintex-7にAXIベースのPCI Expressのコアを入れて速度を測ってみました。
PCI ExpressはGen2のx8なので、毎秒2GByteが理論上の最高速度なのですが、書き込み速度でさえ、65MB/s程度と、わずか3%の速度しか出ていません。
この回路で使っているのはXILINX XDMAコアですが、今回はDMAポートではなくAXI BYPASSポートを使っています。
AXI BYPASSのポートにBRAMをつないでいます。BRAMはすぐに応答を返すのでそこがボトルネックになっているというわけではありません。
そこで、XMDAコアからBlockRAMの間のAXIの配線を全部引き出して、MITOUJTAGで波形がみられるようにRTLのコアを横にいれました。
これでAXIの信号を見ることにします。
すると、こんな波形が得られました。
開始部分を拡大してみます。
128bit幅で125MHzの転送なので、1回のバスサイクルで16バイトを並列に転送できます。ですが、この図を見てわかることは、WSTRBが000F 00F0 0F00 F000と、32bitずつ4回にわけて転送しています。
また、AWLENが00(=256)を指しているのに毎回の転送でLASTが送られてきてしまって、バースト転送が行われていません。
このため、32bitの転送で平均60nsくらいかかってしまいます。4MBytes/0.06us=66MBytes/secなので、計測結果とも一致します。
なぜこんなに遅いのかはわかりませんが、XILINXのAXIベースのPCI Expressコアは、PCI ExpressのWrite転送を小分けにしてしまうようです。
やっぱりDMAを使わないとだめでうね。
Windows10におけるデバイスドライバ
2017.08.02
ASUS製のWindows10のマシンで、OSを再インストールしたら、Kintex-7のPCI Expressボード「Cosmo-K」用のデバイスドライバが動かなくなってしまいました。
あれ・・・?買ったときには動いていたのに。なぜ??
ちゃんとSHA256でカーネルモードの署名をしているのに、署名が検証できないと出てしまいます。
署名や証明書に問題はないはずなのですが・・
どうやらWindows10では、署名だけではドライバは動かないようなのです。
詳しいことは下記のページにありました。
https://blogs.msdn.microsoft.com/jpwdkblog/2016/10/26/windows-10-anniversary-update-signing-policy/
Windows10のAnniversary Update以降では、自分で署名したドライバは動かず、Microsoftが署名したドライバでないと動かなくなったようです。
この仕様を自分で回避する方法は4つあって、
- Windows10がAnniversary Update以前のものからアップデートされている
- PCでセキュアブートを無効にしている
- 証明書が2015年7月29日以前に発行されている
- ドライバがOS起動時にロードされる(一時的な措置であり、将来無効になるかも)
のどれか1つが満たされていれば、自分で署名したドライバでも動くようです。
つまり、Microsoftの考えでは、昔からアップデートして使っていたユーザや昔から存在したドライバは使えるように温情をかけてやるけど、新しく作ったドライバはMicrosoftの管理下に置かせろ、というわけです。
我々開発者は、ドライバの開発の段階ではデバッグやビルドを繰り返すわけですから、いちいちMicrosoftの署名など申請するわけにはいきません。
自己防衛できることはセキュアブートの無効化くらいしかありません。
セキュアブートについて調べてみると、セキュアブートというのは署名のないOSは起動させないようにUEFI(BIOS)が制限するというもののようです。ウィルスとかマルウェアがOSの重要なファイルを書き換えて乗っ取るのを防ぐ仕組みのようで、UEFIレベルで証明書とか署名とかややこしいことをするようになったようです。
はぁ、なんとも生きづらい世の中になったものです。
私が自宅で使っているASUSのPC()は、UEFIにセキュアブートを無効にする、という項目はなく、セキュアブートの欄は灰色で常にEnabledになって変更できませんでした。
ですが、OSのタイプをWindowsではなく、Other OSにしたところ、セキュアブートが無効にできました。
セキュアブートが有効になっているかどうかは、Windowsの「ファイル名を指定して実行」のところで、msinfo32.exeと打つと出てくる以下のツールで見ることができます。
この状態ならば、無事にドライバの読み込みができました。
これで一件落着なのですが、あくまでも一時的な回避策です。
商品として売る以上、お客様に「セキュアブートを無効にしてください」とお願いするのもよくないので、最新のカーネルモードドライバの作り方を勉強して、Microsoftの署名をもらう方法を調べたほうがいいかもしれないと思います。