電気回路/zynq/DMA処理 のバックアップ(No.3)

更新


公開メモ

概要

Zynq で DMA 処理するために勉強中の内容です。

まだ動かせていないので間違ったことも多いと思います。

axi_dma のドキュメント

デザインダイアグラム上で axi_dma IP を右クリックして [IP Documentations] から各種ドキュメントが出る。

https://www.xilinx.com/products/intellectual-property/axi_dma.html#overview

など。

後で読む

Zynq DMA 用 Linux ドライバ:
https://github.com/bmartini/zynq-xdma

[stackoverflow] Linux DMAドライバのインスタンス化および利用方法
http://qiita.com/wstone/items/ab4d47a3690bb1a9bf4c

AXI DMA with Zynq Running Linux Accepted Solution [Solved]
https://forums.xilinx.com/t5/Embedded-Linux/AXI-DMA-with-Zynq-Running-Linux/td-p/522755

キャッシュについて

ACP について

accelerator coherency port を使うとキャッシュコヒーレンシーを アプリケーション側で制御しなくても良くなるみたい?

http://marsee101.blog19.fc2.com/blog-entry-2297.html

udmabuf

DMA バッファとして使うメモリ領域に Linux のユーザーアプリケーションから アクセスするには udmabuf を使うのが良いらしい?

http://qiita.com/ikwzm/items/cc1bb33ff43a491440ea

Xilinx の XAxiDma ドライバ

自分で Linux ドライバを作るときやベアメタルでは Xilinx の低レベルドライバを使うことになる。

C:\Xilinx\SDK\2016.4\data\embeddedsw\XilinxProcessorIPLib\drivers\axidma_v9_3\src

というようなディレクトリにソースコードが、

C:\Xilinx\SDK\2016.4\data\embeddedsw\XilinxProcessorIPLib\drivers\axidma_v9_3\doc/html/api/index.html

にドキュメントがある。[File List] から各ヘッダファイル *.h をクリックして関数一覧を見るのがわかりやすそう

ドキュメントトップの日本語訳

以下は AXI DMA エンジンドライバの API です。

DMA 機能の全詳細はハードウェアのスペックを参照してください。 このドライバでは次の機能を利用できます。

  • Scatter-Gather DMA (SGDMA)
  • Simple DMA
  • Interrupts
  • Programmable interrupt coalescing for SGDMA
  • APIs to manage Buffer Descriptors (BD) movement to and from the SGDMA engine

Simple DMA

Simple DMA API を使うと DMA とデバイスとの間の単一の転送処理を設定できます。 2つのチャンネルを利用可能で、1つは DMA からデバイスへ、もう1つはデバイスから DMA へのチャンネルになります。転送処理を開始するには対応するチャンネルに対応する バッファーアドレスとバッファー長さの2つのフィールドを設定する必要があります。

転送処理

転送処理を記述するのに使われる構造体は Buffer Descriptor (BD) と呼ばれます。 ユーザーアプリケーションは BD を malloc し、転送処理に用いるバッファーアドレス、 転送長、制御情報を設定します。制御情報は SOF や EOF を含みます。 これらの定数は xaxidma_hw.h に定義されています。

Scatter-Gather DMA (SGDMA)

(訳注:axi_dma では IP のカスタマイズで [Enable Scatter Gather Engine] をオンにしないとこの機能は使えない)

SGDMA ではユーザーアプリケーションがメモリ上に次善に準備した一連の転送処理設定を、 ハードウェアが順に処理します。この処理中にユーザーアプリケーションが介在する 必要はありません。また、処理中に新たな処理内容を追加することも可能であり、 ハードウェアを常に働かせておくことができます。

ユーザーはハードウェアをポーリングする、あるいは割り込みを使うことで 転送処理の完了を知ることができます。

SGDMA はすべてのパケットを処理します。パケットは一続きのバイトデータで、1つの メッセージを表します。SGDMA を使えば1つのパケットを複数の転送処理に分割できます。 例えば、14バイトのヘッダーと、それに続く1バイト以上のペイロードからなるIP パケットを思い浮かべてください。SGDMA を使えばアプリケーションはヘッダーに対応する BD と、ペイロードに対応する BD とを指定して、それらを1つのメッセージとして転送処理 することができます。この方法を使うと TCP/IP スタックをより効率的に作成できます。 なぜならパケットヘッダーとデータを異なるメモリ領域においたまま処理できるからです。 そうでなければパケットを一つの連続するメモリブロック上に組み立て直さなければなりません。

BD リングの管理

BD リングはソフトとハードとで共用されます。

ハードウェアは BD が連結リストの形で提供されることを期待します。DMA ハードウェアは 1つの BD の処理が終わるごとにその next pointer フィールドの値をたどることでリストを 次々に処理します。そして、ハードウェアの Tail Ptr レジスタに指定された BD を処理したら 停止します。

BD リングでは最終 BD は最初の BD へリンクされています(ので、処理は自動では停止しません)。

BD の管理はすべてドライバ内で行われます。ユーザーアプリケーションは BD のフィールドを 直接変更すべきではありません。BD フィールドの変更は常に対応する API 関数を通じて 行うようにしてください。

BD リング中ではドライバーは4つの BD グループを管理します。それぞれのグループは 0 個以上の連続する BD から構成されます。

Free
アプリケーションが XAxiDma_BdRingAlloc() で確保可能な(現在使われていない)BD
Pre-process
XAxiDma_BdRingAlloc() で確保済みの BD。 これらの BD はアプリケーションの管理下にあります。 アプリケーションはドライバ API を用いて BD の内容を編集し、DMA 転送の準備をします。
Hardware
XAxiDma_BdRingToHw() でハードウェアに渡され、処理待ちになっている BD。 これらの BD はハードウェアの管理下にあり、処理を待ち、処理中、処理済み、 のいずれかの状態にあります。 この状態の BD をアプリケーションが変更することはできません。 変更した場合にはデータが壊れたり、システムが不安定になったりしかねません。
Post-process
XAxiDma_BdRingFromHw() により Hardware グループから取り出された処理済みの BD。 これらの BD はアプリケーションの管理下にあります。アプリケーションは BD の転送処理結果を確認することができます。アプリケーションは XAxiDma_BdRingFree() することで BD を Free グループに戻せます。

連続して転送処理を行うには、BD は下記のような状態変化をすることが期待されています。

&uml( [*]-r->Free Free: under Driver control Free: to be alloc for use Free-r->Preprocess : XAxiDma_BdRingAlloc() state "Pre-process" as Preprocess Preprocess: under Application control Preprocess: setup for Transaction Preprocess-d->Free :  XAxiDma_BdRingUnAlloc() Preprocess-d->Hardware :  XAxiDma_BdRingToHw() state Hardware { [*]-r->Queuing Queuing-->InProcess InProcess-->Processed Processed-l->[*] } Hardware-l->PostProcess :  XAxiDma_BdRingFromHw() state "Post-process" as PostProcess PostProcess: under Application control PostProcess: Transaction status can be checked PostProcess-u->Free :  XAxiDma_BdRingFree() );

Pre-process 段階で、DMA 転送をハードウェアのキューに入れる前にキャンセルするには、 アプリケーションは XAxiDma_BdRingUnAlloc() を呼んで BD を Free に戻すことができます。

API は BD リストを渡り歩くため、以下の関数を提供しています。

  • XAxiDma_BdRingNext()
  • XAxiDma_BdRingPrev()

これらの関数はどこでグループが終了し、次のグループが始まるかを認識しないため、 使う際には注意が必要です。

SGDMA デスクリプタリングの作成

BD リングは XAxiDma_BdRingCreate() で作成されます。BD リングのためのメモリは アプリケーションが割り当てます。この領域は連続していなければなりません。 BD リングの設定には物理アドレスが必要となります。

アプリケーションは XAxiDma_BdRingMemCalc() で所望の数の BD を用意するのに必要な メモリの大きさを計算できます。逆に、XAxiDma_BdRingCntCalc() により指定のメモリサイズに 何個の BD を格納可能か計算できます。

XAxiDma_BdRingClone() というヘルパ関数を使うことで、同じタイプの指示、例えば SOF や EOF を持つ、複数の BD を含む BD リングを高速に準備できます。 XAxiDma_BdRingClone() した後、アプリケーションはバッファアドレスと転送長のみ 設定すれば良いことになります。1つのパケットのうちいくつかの BD、例えば 最初と最後のものは、特別な制御情報を設定する必要があるかもしれません。

デスクリプタリングのステートマシン

BD リングには2つの状態があります。

  • HALTED (H) : ハードウェアは停止中です。
  • NOT HALTED (NH) : ハードウェアは動作中です。

DMA エンジンの状態遷移は次のようになります。

&uml( [*]-->HALTED HALTED-r->NOT_HALTED : StartBdRingHw() or BdRingStart() or Resume() HALTED: 停止中 NOT_HALTED-l->HALTED : Pause() or Reset() NOT_HALTED: 動作中 );

割り込みの併合

SGDMA は割り込み頻度を制御するために割り込みの併合機能を提供します。 DMA エンジンは割り込み併合を最適化するために2種類の方法を提供しています。

  • パケット臨界値カウンタ: エンジンにより指定された数のパケットが処理されるごとに1回割り込みを発生させます。
  • パケット遅延時間カウンタ: 最後のパケットが処理された後、新しいパケットが処理されないまま指定の時間が 経過したら1回割り込みを発生させます。最低1つのパケットが処理されないと 割り込みは発生しないことに注意してください。

割り込み

割り込みはユーザーアプリケーションで処理されます。 DMA チャンネルごとに割り込みIDを持っています。 ドライバは割り込みを許可・拒否し、また、パケット処理の頻度に応じて割り込み頻度を 最適化するための API を提供します。

ソフトウェア初期化

Simple mode DMA エンジンを使い転送を行うには、次の設定手順に従います。

  1. XAxiDma_CfgInitialize() により DMA を初期化します。 これにより指定された DMA エンジンに対するドライバインスタンスを初期化し、 エンジンをリセットします。
  2. 割り込みモードを使う場合、割り込みを許可します。 割り込みシステムを適切に設定するのはアプリケーションの責任です。 少なくとも、エンジンが実際に割り込みを発行する前に割り込みハンドラを用意し、 適切に登録することが必要です。
  3. 該当のチャンネルのバッファアドレスと転送長フィールドを設定し、DMA 転送を開始します。

SG モードで DMA エンジンを使い転送処理を行うには、次の手順に従います。

  • XAxiDma_CfgInitialize() にて DMA を初期化します。
  • BD リングを作成します。DMA チャンネルごとに XAxiDma_BdRingCreate(). により BD リングを作る必要があります。
  • 割り込みモードを使うのであれば割り込みを許可します。
  • DMA 転送を開始します: 一番始め、あるいはリセット後には XAxiDma_BdRingStart() により転送を開始します。チャンネルがすでに転送を開始しているなら、 XAxiDma_BdRingToHw() を使います。DMA チャンネルが動作していないときに XAxiDma_BdRingToHw() しても BD はハードウェアに送られません。かわりに、 後に DMA チャンネルが XAxiDma_BdRingStart() で開始されてから処理されます。

DMA 転送処理の開始方法

ユーザーアプリケーションは XAxiDma_BdRingToHw() により BD をハードウェアに送り、 DMA 転送を開始します。

どちら向きのチャンネルにおいても、DMA エンジンが(XAxiDma_Pause() により) 現在停止中であれば、新たに追加された BD は受理されるものの、 XAxiDma_BdRingStart() により DMA エンジンが転送を開始する、あるいは XAxiDma_Resume() により転送を再開するまで、処理されることはありません。

DMA 転送が終了した後の処理

割り込みが設定され、許可されたなら、DMA チャンネルは 転送終了をソフトウェアへ通知するのために割り込みを行います。 割り込みを使わない場合、ユーザーアプリケーションは BD の完了を XAxiDma_BdRingFromHw() あるいは XAxiDma_BdHwCompleted() によりポーリングすることもできます。

  • BD が DMA チャンネルにより処理済みとなったら、 アプリケーションはまずそれらを XAxiDma_BdRingFromHw() により回収する必要があります。
  • TX 側では、その時点でアプリケーションはその BD に関連づけられていた データバッファーを解放することができます。なぜならバッファー中のデータは すでに転送されたからです。
  • RX 側では、その時点でアプリケーションは BD に関連づけられたデータ バッファーのデータを利用可能になります。
  • どちらのチャンネルにおいても、処理済みの BD は XAxiDma_BdRingFree() により Free 状態に戻してやる必要があります。そうすることで以降の 転送処理で再利用することが可能になります。
  • RX 側では、アプリケーションは常にいずれかの DB がデータ受け入れ 可能な状態にあるよう手配する責任があります。そうでないと、その RX チャンネルはデータを受け入れられなくなってしまいます。

使用例

ドライバ API の使用方法を示すため、5つの例が挙げられています。

  • SG 割り込みモード (xaxidma_example_sg_intr.c)
    複数の BD/パケットを転送します
  • SB ポーリングモード (xaxidma_example_sg_poll.c)
    単一 の BD を転送をします
  • SB ポーリングモード (xaxidma_poll_multi_pkts.c)
    複数の BD/パケットを転送します
  • Simple ポーリングモード (xaxidma_example_simple_poll.c)
  • Simple 割り込みモード (xaxidma_example_simple_intr.c)

アドレス変換

ハードウェアに与えるすべてのバッファーアドレスおよび BD アドレスは物理アドレスです。

キャッシュの整合性

このドライバは BD で指定されるすべてのアプリケーションバッファーは キャッシュと整合性の取れたメモリ空間にあると仮定しています。 システムでキャッシュが使われている場合、データの送信元として 用いるバッファメモリはドライバに BD を渡す前にキャッシュを フラッシュ(メモリへの書き戻し)する必要があります。 また、データの受信先として用いるバッファメモリは、受信された データにアクセスする前にキャッシュを無効化しなければなりません。

データアラインメント

BD に対して:
アラインメントの最小単位は XAXIDMA_BD_MINIMUM_ALIGNMENT 定数で 定義されています。

これはハードウェアとソフトウェアの両方が正しく動作するために 必要な最小アラインメントです。

デスクリプタリングがキャッシュメモリに置かれる場合、 アラインメントは最低限プロセッサーのキャッシュライン サイズでなければなりません。 キャッシュサイズより大きいアラインメントを使う場合、 キャッシュラインの整数倍のアラインメントが必要です。 (訳注:XAXIDMA_BD_MINIMUM_ALIGNMENT とキャッシュライン サイズの両方の倍数となるように取れということ)

デスクリプタリングを最初に作るとき(XAxiDma_BdRingCreate() を参照)以外、BD のアラインメントが正しいかどうかランタイムの チェックは働きません。

アプリケーションデータバッファについて:
ハードウェアに DRE が実装されている限り、アプリケーション データバッファは任意のアラインメントに基づくもので構いません。 そうでなければアプリケーションデータバッファはワードアラインメント でなければなりません。ワードサイズは送信用には XPAR_AXIDMA_0_M_AXIS_MM2S_TDATA_WIDTH で、受信用には XPAR_AXIDMA_0_S_AXIS_S2MM_TDATA_WIDTH で定義されています。

複数の BD からなる BD チェインに対する SG 転送では、 それぞれの BD 転送長もワード単位でなければなりません。 さもないとハードウェア内で内部エラーが生じます。

エラーの取り扱い

DMA エンジンは任意のエラーに対して停止します。 ソフトウェアは新しい転送要求を開始できるようにするために リセットをかける必要があります。

停止後の再開

DMA エンジンがリセットや、エラー後にリセットにより停止状態に あるとき、ソフトウェアはリセットがかかった時点の DB ポインタを確認し、 XAxiDma_BdRingStart() により BD の処理を再開することができます。

制限事項

このドライバは同時利用の排他処理機能を持っていません。 この種の保護はアプリケーション側で行う必要があります。

ハードウェアの初期設定と独占的使用

初期化後、あるいはリセット後には DMA エンジンは以下のモードに初期設定されます。

  • すべての割り込みは禁止されます
  • 割り込み併合カウンタは1になります
  • DMA エンジンは停止状態になります。 それぞれの DMA チャンネルは独立して処理を開始します。 それには、転送に BD が設定されていない状態であれば XAxiDma_StartBdRingHw() を使って、そうでなければ XAxiDma_BdRingStart() を使います。

ドライバはレジスタや BD を独占的に使用します。 レジスタや BD へのすべてのアクセスはドライバインターフェースを 介して行われるべきです。

デバッグ表示

ドライバに対するデバッグ用メッセージを表示するには、 コンパイル時に -DDEBUG フラグを付けて下さい。 さらに、xdebug.h の中の "#undef DEBUG" という行をコメントアウトして下さい。

API メモ

xaxidma.h

  • enum Direction { XAXIDMA_DMA_TO_DEVICE, XAXIDMA_DEVICE_TO_DMA };
  • XAxiDma_BdRing * XAxiDma_GetTxRing(XAxiDma * InstancePtr);
  • XAxiDma_BdRing * XAxiDma_GetRxRing(XAxiDma * InstancePtr);
  • XAxiDma_BdRing * XAxiDma_GetRxIndexRing(XAxiDma * InstancePtr, int RingIndex);
  • bool XAxiDma_HasSg(XAxiDma * InstancePtr);
  • void XAxiDma_IntrEnable(XAxiDma * InstancePtr, uint Mask, int Direction);
  • uint XAxiDma_IntrGetEnabled(XAxiDma * InstancePtr, int Direction);
  • void XAxiDma_IntrDisable(XAxiDma * InstancePtr, uint Mask, int Direction);
  • uint XAxiDma_IntrGetIrq(XAxiDma * InstancePtr, int Direction);
  • void XAxiDma_IntrAckIrq(XAxiDma * InstancePtr, uint Mask, int Direction);
  • XAxiDma_Config *XAxiDma_LookupConfig(u32 DeviceId);
  • int XAxiDma_CfgInitialize(XAxiDma * InstancePtr, XAxiDma_Config *Config);
  • void XAxiDma_Reset(XAxiDma * InstancePtr);
  • int XAxiDma_ResetIsDone(XAxiDma * InstancePtr);
  • int XAxiDma_Pause(XAxiDma * InstancePtr);
  • int XAxiDma_Resume(XAxiDma * InstancePtr);
  • u32 XAxiDma_Busy(XAxiDma *InstancePtr,int Direction);
  • u32 XAxiDma_SimpleTransfer(XAxiDma *InstancePtr, UINTPTR BuffAddr, u32 Length,int Direction);
  • int XAxiDma_SelectKeyHole(XAxiDma *InstancePtr, int Direction, int Select);
  • int XAxiDma_SelectCyclicMode(XAxiDma *InstancePtr, int Direction, int Select);
  • int XAxiDma_Selftest(XAxiDma * InstancePtr);

xaxidma_bdring.h

  • int XAxiDma_BdRingCntCalc(u32 Alignment, u32 Bytes);
  • int XAxiDma_BdRingMemCalc(u32 Alignment, u32 NumBd)
  • int XAxiDma_BdRingGetCnt(XAxiDma_BdRing* RingPtr)
  • int XAxiDma_BdRingGetFreeCnt(XAxiDma_BdRing* RingPtr)
  • void XAxiDma_BdRingSnapShotCurrBd(XAxiDma_BdRing* RingPtr)
    • RingPtr->IsRxChannel, RingPtr->ChanBase, RingPtr->RingIndex を設定してから呼び出す
    • ハードウェアから現在値を読み出した値により RingPtr->BdaRestart, RingPtr->ChanBase がセットされる
  • XAxiDma_Bd * XAxiDma_BdRingGetCurrBd(XAxiDma_BdRing* RingPtr)
  • XAxiDma_Bd *XAxiDma_BdRingNext(XAxiDma_BdRing* RingPtr, XAxiDma_Bd *BdPtr)
    • 配列上で次の XAxiDma_Bd を返す。連結リストの Next ではない
    • Ring なので最後の要素の Next は最初の要素になる
  • XAxiDma_Bd *XAxiDma_BdRingPrev(XAxiDma_BdRing* RingPtr, XAxiDma_Bd *BdPtr)
    • 配列上で前の XAxiDma_Bd を返す。連結リストの Prev ではない
    • Ring なので最初の要素の Prev は最後の要素になる
  • u32 XAxiDma_BdRingGetSr(XAxiDma_BdRing* RingPtr)
    • Sr はステータスレジスタ
  • u32 XAxiDma_BdRingGetError(XAxiDma_BdRing* RingPtr)
    • ステータスレジスタにエラービットのマスクをかけて返す
    • 内容を見るには XAXIDMA_ERR_*_MASK と & を取る (xaxidma_hw.h)
  • int XAxiDma_BdRingHwIsStarted(XAxiDma_BdRing* RingPtr)
    • Halt か Not Halt かを見る
  • int XAxiDma_BdRingBusy(XAxiDma_BdRing* RingPtr)
    • 実際に転送中かを見る
  • void XAxiDma_BdRingIntEnable(XAxiDma_BdRing* RingPtr, u32 Mask)
    • XAXIDMA_CR_OFFSET レジスタを設定する
  • u32 XAxiDma_BdRingIntGetEnabled(XAxiDma_BdRing* RingPtr)
    • XAXIDMA_CR_OFFSET レジスタを読む
  • void XAxiDma_BdRingIntDisable(XAxiDma_BdRing* RingPtr, u32 Mask)
    • XAXIDMA_CR_OFFSET レジスタを設定する
  • u32 XAxiDma_BdRingGetIrq(XAxiDma_BdRing* RingPtr)
    • IRQ_OFFSET レジスタを読む
    • アプリケーションは XAXIDMA_IRQ_*** 定数と & を取って解釈する
  • void XAxiDma_BdRingAckIrq(XAxiDma_BdRing* RingPtr)
  • int XAxiDma_StartBdRingHw(XAxiDma_BdRing* RingPtr);
  • int XAxiDma_UpdateBdRingCDesc(XAxiDma_BdRing* RingPtr);
  • u32 XAxiDma_BdRingCreate(XAxiDma_BdRing * RingPtr, UINTPTR PhysAddr, UINTPTR VirtAddr, u32 Alignment, int BdCount);
  • int XAxiDma_BdRingClone(XAxiDma_BdRing * RingPtr, XAxiDma_Bd * SrcBdPtr);
  • int XAxiDma_BdRingAlloc(XAxiDma_BdRing * RingPtr, int NumBd, XAxiDma_Bd ** BdSetPtr);
  • int XAxiDma_BdRingUnAlloc(XAxiDma_BdRing * RingPtr, int NumBd, XAxiDma_Bd * BdSetPtr);
  • int XAxiDma_BdRingToHw(XAxiDma_BdRing * RingPtr, int NumBd, XAxiDma_Bd * BdSetPtr);
  • int XAxiDma_BdRingFromHw(XAxiDma_BdRing * RingPtr, int BdLimit, XAxiDma_Bd ** BdSetPtr);
  • int XAxiDma_BdRingFree(XAxiDma_BdRing * RingPtr, int NumBd, XAxiDma_Bd * BdSetPtr);
  • int XAxiDma_BdRingStart(XAxiDma_BdRing * RingPtr);
  • int XAxiDma_BdRingSetCoalesce(XAxiDma_BdRing * RingPtr, u32 Counter, u32 Timer);
  • void XAxiDma_BdRingGetCoalesce(XAxiDma_BdRing * RingPtr, u32 *CounterPtr, u32 *TimerPtr);

xaxidma_bd.h

BD は

typedef u32 XAxiDma_Bd[XAXIDMA_BD_NUM_WORDS];

のように構造体じゃなく u32 の配列として定義されているのだけれど、

LANG:c
typedef struct {
  XAxiDma_Bd *Next;           // 64bit  次の BD
  void *Buffer;               // 64bit  バッファーアドレス
  struct {
    u8 TDest;                 // 8bit 
    u8 TReserved1;            // 8bit 
    u8 TId;                   // 8bit 
    u8 TReserved2;            // 8bit 
    u8 TUser;                 // 8bit 
    u8 TReserved3;            // 8bit 
    u8 ARCache;               // 8bit 
    u8 ARUser;                // 8bit 
  } MultiChannelControl;      // 32bit  MCCTL
  u16  Stride;                // 16bit
  u3   Reserved0;             //  3bit
  u13  VSize;                 // 13bit
  u16  Control;               // 16bit
  u16  Length;                // 16bit  転送長
  struct {
    u26 ;
    struct {
      u1 RXEOF;               //  1bit Last rx packet
      u1 RXSOF;               //  1bit First rx packet
      struct {
        u1 InternalError;     //  1bit
        u1 SlaveError;        //  1bit
        u1 DecodeError;       //  1bit
      } StatusErrorBits;      //  4bit
      u1 Completed;           //  1bit
    } StatusBits;             //  6bit
  } Status;                   // 32bit  STS
  u32  User0;                 // 32bit
  u32  User1;                 // 32bit
  u32  User2;                 // 32bit
  u32  User3;                 // 32bit
  u32  User4;                 // 32bit
  u32  Id;                    // 32bit
  u32  HasSTSCNTRL;           // 32bit
  u32  HasDRE;                // 32bit
} XAxiDma_Bd;

と考えればいいみたい。

個々のフィールドにアクセスするために

  • XAxiDma_BdClear(BdPtr) // ゼロフィルする
  • XAxiDma_BdGetCtrl(BdPtr) // Control
  • XAxiDma_BdSetCtrl(BdPtr, Data); // Control
  • XAxiDma_BdGetSts(BdPtr) // Status
  • XAxiDma_BdGetLength(BdPtr, LengthMask) // 設定した転送長
  • XAxiDma_BdSetLength(BdPtr, LenBytes, LengthMask); // 転送長
  • XAxiDma_BdSetId(BdPtr, u32 Id) // アプリケーションが付ける任意の ID
  • XAxiDma_BdGetId(BdPtr) // アプリケーションが付けた任意の ID
  • XAxiDma_BdGetBufAddr(BdPtr)
  • XAxiDma_BdSetBufAddr(BdPtr, Addr);
  • XAxiDma_BdSetBufAddrMicroMode(BdPtr, Addr);
  • XAxiDma_BdHwCompleted(BdPtr) // Status の Completed ビットを読む
  • XAxiDma_BdGetActualLength(BdPtr, LengthMask) // Status の転送されたデータ長を取り出す
  • XAxiDma_BdSetTId(BdPtr, TId) // MCCTL の TId フィールド
  • XAxiDma_BdGetTId(BdPtr) // MCCTL の TId フィールド
  • XAxiDma_BdSetTDest(BdPtr, TDest) // MCCTL の TDest フィールド
  • XAxiDma_BdGetTDest(BdPtr) // MCCTL の TDest フィールド
  • XAxiDma_BdSetTUser(BdPtr, TUser) // MCCTL の TUser フィールド
  • XAxiDma_BdGetTUser(BdPtr) // MCCTL の TUser フィールド
  • XAxiDma_BdSetARCache(BdPtr, ARCache) // MCCTL の ARCache フィールド
  • XAxiDma_BdGetARCache(BdPtr) // MCCTL の ARCache フィールド
  • XAxiDma_BdSetARUser(BdPtr, ARUser) // MCCTL の ARUser フィールド
  • XAxiDma_BdGetARUser(BdPtr) // MCCTL の ARUser フィールド
  • XAxiDma_BdSetStride(BdPtr, Stride)
  • XAxiDma_BdGetStride(BdPtr)
  • XAxiDma_BdSetVSize(BdPtr, VSize)
  • XAxiDma_BdGetVSize(BdPtr)
  • XAxiDma_BdGetAppWord(BdPtr, Offset, int *Valid);
  • XAxiDma_BdSetAppWord(BdPtr, Offset, Word);

サンプルを読む

エラー処理を飛ばして読んでみます。

割り込みについて XPAR_INTC_0_DEVICE_ID による #ifdef があるけれど、 Zynq は GIC を持っているので、XPAR_INTC_0_DEVICE_ID をセットしない、 xscugic.h ベースのコードを追えばいいはず。

Zynq での割り込み使用例:
https://japan.xilinx.com/support/answers/50572.html

xaxidma_example_simple_poll.c

LANG:c
void main()
{
    /* Run the poll example for simple transfer */
    XAxiDma_SimplePollExample(DMA_DEV_ID);
}

int XAxiDma_SimplePollExample(u16 DeviceId)
{
    XAxiDma_Config *CfgPtr;
    int Status;
    int Tries = NUMBER_OF_TRANSFERS;
    int Index;
    u8 *TxBufferPtr;
    u8 *RxBufferPtr;
    u8 Value;

    TxBufferPtr = (u8 *)TX_BUFFER_BASE;
    RxBufferPtr = (u8 *)RX_BUFFER_BASE;

    // Initialize the XAxiDma device.
    CfgPtr = XAxiDma_LookupConfig(DeviceId);
    XAxiDma_CfgInitialize(&AxiDma, CfgPtr);
    assert(! XAxiDma_HasSg(&AxiDma)); // SG mode でないことを確認

    // Disable interrupts, we use polling mode
    XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
    XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);

    InitializeTxBuffer(TxBufferPtr);

    // キャッシュのフラッシュ
    Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);

    // 受信要求
    XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
                MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

    // 送信要求
    XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
                MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

    // 終わるのを待つ
    while ( (XAxiDma_Busy(&AxiDma,XAXIDMA_DEVICE_TO_DMA)) ||
            (XAxiDma_Busy(&AxiDma,XAXIDMA_DMA_TO_DEVICE)) ) {
            /* Wait */
    }

    // キャッシュの Invalidate
    Xil_DCacheInvalidateRange((UINTPTR)RxPacket, MAX_PKT_LEN);
    return XST_SUCCESS;
}

xaxidma_example_simple_intr.c

LANG:c
#include "xscugic.h"

void main(void)
{
   int Status;
   XAxiDma_Config *Config;
   int Tries = NUMBER_OF_TRANSFERS;
   int Index;
   u8 *TxBufferPtr;
   u8 *RxBufferPtr;
   u8 Value;

   TxBufferPtr = (u8 *)TX_BUFFER_BASE ;
   RxBufferPtr = (u8 *)RX_BUFFER_BASE;

   Config = XAxiDma_LookupConfig(DMA_DEV_ID);
   XAxiDma_CfgInitialize(&AxiDma, Config);

   assert(!XAxiDma_HasSg(&AxiDma));

   // Set up Interrupt system
   SetupIntrSystem(&Intc, &AxiDma, TX_INTR_ID, RX_INTR_ID);

   // Disable all interrupts before setup
   XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
   XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

   // Enable all interrupts
   XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
   XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

   /* Initialize flags before start transfer test  */
   TxDone = 0;
   RxDone = 0;
   Error = 0;

   InitializeTxBuffer(TxBuffer);

   // Flush the SrcBuffer before the DMA transfer, in case the Data Cache is enabled
   Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);

   // Send a packet
   for(Index = 0; Index < Tries; Index ++) {

       XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
                   MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
       XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
                   MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

       // Wait TX done and RX done
       while (!TxDone && !RxDone && !Error) {
               // NOP
       }
       assert(!Error);

       // Transfer finished, check data
	Xil_DCacheInvalidateRange((u32)RxPacket, Length);
       Status = CheckData(MAX_PKT_LEN, 0xC);
       assert(Status == XST_SUCCESS);
   }

   // Disable TX and RX Ring interrupts and return success
   DisableIntrSystem(&Intc, TX_INTR_ID, RX_INTR_ID);
}

static void TxIntrHandler(void *Callback)
{
   u32 IrqStatus;
   int TimeOut;
   XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

   // Read and acknowledge pending interrupts
   IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);
   XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);

   // If no interrupt is asserted, we do not do anything
   if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK))
       return;

   // If error interrupt is asserted, raise error flag, reset the
   // hardware to recover from the error, and return with no further
   // processing.
   if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
       Error = 1;

       // Reset should never fail for transmit channel
       XAxiDma_Reset(AxiDmaInst);

       TimeOut = RESET_TIMEOUT_COUNTER;
       while (TimeOut) {
           if (XAxiDma_ResetIsDone(AxiDmaInst))
               break;
           TimeOut -= 1;
       }
       return;
   }

   // If Completion interrupt is asserted, then set the TxDone flag
   if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK))
       TxDone = 1;
}

static void RxIntrHandler(void *Callback)
{
   u32 IrqStatus;
   int TimeOut;
   XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

   // Read and acknowledge pending interrupts
   IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
   XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

   // If no interrupt is asserted, we do not do anything
   if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK))
       return;

   // If error interrupt is asserted, raise error flag, reset the
   // hardware to recover from the error, and return with no further
   // processing.
   if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
       Error = 1;

       // Reset could fail and hang
       // NEED a way to handle this or do not call it??
       XAxiDma_Reset(AxiDmaInst);

       TimeOut = RESET_TIMEOUT_COUNTER;
       while (TimeOut) {
           if(XAxiDma_ResetIsDone(AxiDmaInst))
               break;
           TimeOut -= 1;
       }
       return;
   }

   // If completion interrupt is asserted, then set RxDone flag
   if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK))
       RxDone = 1;
}

static int SetupIntrSystem(INTC * IntcInstancePtr,
              XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId)
{
   int Status;

   XScuGic_Config *IntcConfig;

   // Initialize the interrupt controller driver so that it is ready to use.
   XScuGic_LookupConfig(INTC_DEVICE_ID);
   XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
                   IntcConfig->CpuBaseAddress);

   XScuGic_SetPriorityTriggerType(IntcInstancePtr, TxIntrId, 0xA0, 0x3);
   XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3);

    // Connect the device driver handler that will be called when an
    // interrupt for the device occurs, the handler defined above performs
    // the specific interrupt processing for the device.
   XScuGic_Connect(IntcInstancePtr, TxIntrId,
               (Xil_InterruptHandler)TxIntrHandler, AxiDmaPtr);

   XScuGic_Connect(IntcInstancePtr, RxIntrId,
               (Xil_InterruptHandler)RxIntrHandler, AxiDmaPtr);

   XScuGic_Enable(IntcInstancePtr, TxIntrId);
   XScuGic_Enable(IntcInstancePtr, RxIntrId);

   // Enable interrupts from the hardware
   Xil_ExceptionInit();
   Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
           (Xil_ExceptionHandler)INTC_HANDLER,
           (void *)IntcInstancePtr);

   Xil_ExceptionEnable();

   return XST_SUCCESS;
}

static void DisableIntrSystem(INTC * IntcInstancePtr,
                   u16 TxIntrId, u16 RxIntrId)
{
   XScuGic_Disconnect(IntcInstancePtr, TxIntrId);
   XScuGic_Disconnect(IntcInstancePtr, RxIntrId);
}

Counter: 17132 (from 2010/06/03), today: 1, yesterday: 0