« Windows10時代のデバイスドライバ開発とデバッグ | トップページ | 特電の商品発送箱が変わります! »

2017.08.08

Windows10におけるXDMAコア割り込みの発生方法

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時代のデバイスドライバ開発とデバッグ | トップページ | 特電の商品発送箱が変わります! »

コメント

コメントを書く



(ウェブ上には掲載しません)




« Windows10時代のデバイスドライバ開発とデバッグ | トップページ | 特電の商品発送箱が変わります! »