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

更新


公開メモ

概要

Zynq で簡単な DMA 処理をするための手順をまとめました。

Web上で探すといろんなドライバに関する情報が交錯していて、 正解にたどり着くまでに非常に苦労します。

  • Xilinx のベアメタルドライバ
    • Linux とはコンパチビリティのないドライバ
  • Xilinx の Linux ドライバ
    • Linux 上で他のデバイスドライバから使われることを想定したドライバ
    • ユーザーアプリケーションから使うものではない
  • サードパーティーの bperez77/xilinx_axidma
    • Xilinx の Linux ドライバの上に構築された汎用ドライバ
    • /dev/axidma 経由で簡単に DMA 転送が行える

のようにいろいろあるのですが、お手軽に使うには bperez77/xilinx_axidma が良さそうです。

サードパーティー製のドライバは他にもいくるかあるようですが、 少なくとも上記のものは固定サイズパケットの単純転送で割り込み通知を使うだけなら 非常に簡単に使えるのでおすすめです。

Scatter Gather 機能を使いたければもう少し苦労が必要なのかもしれませんが、 割り込みハンドラのスレッドを分けてしまえば、 次のパケットを CPU で受け取るというのも、 そんなに悪くない設計に思えます。

パケットサイズを可変にしたいときにどうするべきか、 についてはまだ調査中です。

bperez77/xilinx_axidma の使い方を調べる

これが使えるとかなり楽ができそう。

https://github.com/bperez77/xilinx_axidma

カーネルオプションを確認

ドライバに書かれている条件を満たしているか、 z-turn 付属のカーネル設定を調べます。

  • CONFIG_CMA=y
  • CONFIG_DMA_CMA=y
  • CONFIG_XILINX_DMAENGINES=y
  • CONFIG_XILINX_AXIDMA=y
  • CONFIG_XILINX_AXIVDMA=y
  • CONFIG_DMA_SHARED_BUFFER=y

のうち、CONFIG_XILINX_DMAENGINES が無かったが、代わりに

  • CONFIG_XILINX_DMA_ENGINES=y

があったので、Xilinx 製のドライバは初めから入っているみたい。

Device Tree

電気回路/HDL/VivadoでAXIバスを利用#sf57016a で作ったデバイスは、

  • address = 0x40400000
    • tx は +0x0000
    • rx は +0x0030
  • irq = GIC の F2P[4]
  • s2mm のみ
  • データ幅 32

DTS における割り込み番号の設定方法は、 http://qiita.com/ikwzm/items/b22592c31cdbb9ab6cf7 によれば、

interrupts=<0x0 0x1d 0x4> の2番目のパラメータの0x1d(十進数で29)が割り込み番号を指定しています。DMAコントローラーの割り込みはPS部のIRQ_F2P[0]に接続しています。ZYNQのテクニカルリファレンスマニュアルによれば、IRQ_F2P[0]は汎用割り込みコントローラー(GIC)内の共有ペリフェラル割り込み(SPI)の61番を通じて割り込みがかかります。このパラメータには61から32引いた値を設定するようです。

interrupts=<0x0 0x1d 0x4>の1番目のパラメータはGIC内部の割り込みの種類を指定します。GIC内部には共有ペリフェラル割り込み(SPI)の他にCPUプライベートペリフェラル割り込み(PPI)とソフトウェア生成割り込み(SGI)があります。共有ペリフェラル割り込み(SPI)を使う場合は0を指定します。

interrupts=<0x0 0x1d 0x4>の3番目のパラメータは割り込み信号がエッジトリガーかレベルトリガーかを指定します。1を指定するとエッジトリガー、4を指定するとレベルトリガーになります。

とのことなので、今の場合は

  • 0 = 共有ペリフェラル割り込み(SPI)
  • 33 = 61 + 4 - 32 = 33
  • 4 = レベルトリガー

インクルードファイルを使わない場合 &clkc = 1, &intc = 3 として、

axidma_chrdev: axidma_chrdev@0 {
    compatible = "xlnx,axidma-chrdev";
    dmas = <&axi_dma_0 0>;
    dma-names = "rx_channel";
};

axidma0: axidma0@40400000 {
    compatible = "xlnx,axi-dma", "xlnx,axi-dma-6.03.a", "xlnx,axi-dma-1.00.a";
    reg = <0x40400000 0x10000>;
    xlnx,include-sg;
    #dma-cells = <1>;

    dma-s2mm-channel@40400030 {
        compatible = "xlnx,axi-dma-s2mm-channel";
        dma-channels = <1>;
        xlnx,datawidth = <32>;
        xlnx,device-id = <0x0>;
        clocks = <1 15>;
        clock-names = "axis";
        interrupt-parent = <3>;
        interrupts = <0 33 4>;
    };
};

となりそうです?

http://qiita.com/ikwzm/items/aae3dab578e28c13bc65 によると clocks = 15 は fclk0 のことのようなのですが、どこで調べられるのかよくわかっていません。。。

始め dma-s2mm-channel のアドレスを 40400000 としていて うまく行かずはまりました。40400000 は mm2s 用のレジスタのために確保されているので、 mm2s を実装しているかどうかにかかわらず、s2mm のレジスタは 40400030 から始まります。

axidma デバイスが登録された

上記 devicetree をインストールしたところ、起動メッセージに、

xilinx-dma 40400000.axidma0: Probing xilinx axi dma engine...Successful

が現れ、

LANG:console
$ ls /sys/class/dma/dma1chan0/device/driver/
 40400000.axidma0  bind  uevent  unbind

となっているので、まずは xilinx の axidma が登録されたことになります。 これ自体はユーザーアプリケーションから直接使えるものではないので、 この上に構築された汎用ドライバである bperez77/xilinx_axidma をかぶせて使うことになります。

bperez77/xilinx_axidma ドライバのビルド

LANG:console
$ git clone https://github.com/bperez77/xilinx_axidma.git
$ cd xilinx_axidma
$ make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm KBUILD_DIR=/home/osamu/linux-zturn
 make -C /home/osamu/linux-zturn M=/home/osamu/xilinx_axidma/driver modules
 make[1]: Entering directory '/home/osamu/linux-zturn'
   CC [M]  /home/osamu/xilinx_axidma/driver/axi_dma.o
   CC [M]  /home/osamu/xilinx_axidma/driver/axidma_dma.o
 In file included from /home/osamu/xilinx_axidma/driver/axidma_dma.c:25:0:
 /home/osamu/xilinx_axidma/driver/version_portability.h: In function ‘axidma_setup_vdma_config’:
 /home/osamu/xilinx_axidma/driver/version_portability.h:133:15: error: ‘struct xilinx_vdma_config’ has no member named ‘vsize’
      dma_config->vsize = height;         // Height of the image (in lines)
                ^
 /home/osamu/xilinx_axidma/driver/version_portability.h:134:15: error: ‘struct xilinx_vdma_config’ has no member named ‘hsize’
      dma_config->hsize = width * depth;  // Width of the image (in bytes)
                ^
 /home/osamu/xilinx_axidma/driver/version_portability.h:135:15: error: ‘struct xilinx_vdma_config’ has no member named ‘stride’
      dma_config->stride = width * depth; // Number of bytes to process per line
                ^
 /home/osamu/xilinx_axidma/driver/version_portability.h: In function ‘axidma_to_xilinx_type’:
 /home/osamu/xilinx_axidma/driver/version_portability.h:154:59: error: ‘XILINX_DMA_IP_VDMA’ undeclared (first use in this function)
      return (dma_type == AXIDMA_DMA) ? XILINX_DMA_IP_DMA : XILINX_DMA_IP_VDMA;
                                                            ^
 /home/osamu/xilinx_axidma/driver/version_portability.h:154:59: note: each undeclared identifier is reported only once for each function it appears in
 /home/osamu/xilinx_axidma/driver/axidma_dma.c: In function ‘axidma_prep_transfer’:
 /home/osamu/xilinx_axidma/driver/axidma_dma.c:198:32: error: ‘DMA_COMPL_SKIP_DEST_UNMAP’ undeclared (first use in this function)
      dma_flags = DMA_CTRL_ACK | DMA_COMPL_SKIP_DEST_UNMAP | DMA_PREP_INTERRUPT;
                                 ^
 /home/osamu/xilinx_axidma/driver/axidma_dma.c: In function ‘axidma_start_transfer’:
 /home/osamu/xilinx_axidma/driver/axidma_dma.c:271:30: error: ‘DMA_SUCCESS’ undeclared (first use in this function)
          } else if (status != DMA_SUCCESS) {
                               ^
 In file included from /home/osamu/xilinx_axidma/driver/axidma_dma.c:25:0:
 /home/osamu/xilinx_axidma/driver/version_portability.h: In function ‘axidma_to_xilinx_type’:
/home/osamu/xilinx_axidma/driver/version_portability.h:155:1: error: control reaches end of non-void function [-Werror=return-type]
  }
  ^
 cc1: all warnings being treated as errors
 scripts/Makefile.build:318: recipe for target '/home/osamu/xilinx_axidma/driver/axidma_dma.o' failed
 make[2]: *** [/home/osamu/xilinx_axidma/driver/axidma_dma.o] Error 1
 Makefile:1310: recipe for target '_module_/home/osamu/xilinx_axidma/driver' failed
 make[1]: *** [_module_/home/osamu/xilinx_axidma/driver] Error 2
 make[1]: Leaving directory '/home/osamu/linux-zturn'
 driver/Makefile:83: recipe for target 'driver/axidma.ko' failed
 make: *** [driver/axidma.ko] Error 2
$ grep -A11 "struct xilinx_vdma_config {" ../linux-zturn/include/linux/amba/xilinx_dma.h
 struct xilinx_vdma_config {
         int frm_dly;
         int gen_lock;
         int master;
         int frm_cnt_en;
         int park;
         int park_frm;
         int coalesc;
         int delay;
         int reset;
         int ext_fsync;
 };
$ head -3 ../linux-zturn/Makefile
 VERSION = 3
 PATCHLEVEL = 15
 SUBLEVEL = 0
$ # https://github.com/bperez77/xilinx_axidma/issues/14
$ jed driver/version_portability.h
 + #if LINUX_VERSION_CODE < KERNEL_VERSION(3,15,0)
 +     dma_config->vsize = height;         // Height of the image (in lines)
 +     dma_config->hsize = width * depth;  // Width of the image (in bytes)
 +     dma_config->stride = width * depth; // Number of bytes to process per line
 + #endif
   ...
 
 + #ifdef XILINX_DMA_IP_VDMA
 +     BUG_ON(dma_type != AXIDMA_DMA && dma_type != AXIDMA_VDMA);
 +     return (dma_type == AXIDMA_DMA) ? XILINX_DMA_IP_DMA : XILINX_DMA_IP_VDMA;
 + #else
 +     BUG_ON(dma_type != AXIDMA_DMA);
 +     return XILINX_DMA_IP_DMA;
 + #endif
   ...
 
 + #ifndef DMA_SUCCESS
 + // DMA_SUCCESS was renamed to DMA_COMPLETE (indicates a DMA transaction is done)
 + #define DMA_SUCCESS                 DMA_COMPLETE
 + #endif
 + 
 + #ifndef DMA_COMPL_SKIP_DEST_UNMAP
 + // The skip destination unmap DMA control option was removed in between 3.0 and 3.15 ?
 + #define DMA_COMPL_SKIP_DEST_UNMAP   0
 + #endif
$ make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm KBUILD_DIR=/home/osamu/linux-zturn
$ ls outputs/
 axidma.ko  axidma_benchmark  axidma_display_image  axidma_transfer  libaxidma.so

4.x 向けに対策されている内容の一部が先取りして入っているようで、 3.15 に対して同様の対策が必要でした。

上記のように無理矢理書き換えたところ、コンパイルが通りました。

デバイスのインストール

axidma.ko と libaxidma.so を z-turn 上の Linux へコピーして、

LANG:console
$ sudo chown root:root axidma.ko libaxidma.so
$ sudo cp libaxidma.so /usr/local/lib/
$ sudo insmod axidma.ko
$ dmesg | tail
 [  469.438795] axidma: axidma_dma.c: axidma_dma_init: 674: DMA: Found 0 transmit channels and 1 receive channels.
 [  469.447790] axidma: axidma_dma.c: axidma_dma_init: 676: VDMA: Found 0 transmit channels and 0 receive channels.
$ ls /dev/axidma
 /dev/axidma

正しく入ったみたい。

bootargs の設定

ドライバの説明書きには

uEnv.txt の bootargs 行に cma=25M などと加えれば良いように書かれていましたが、

設定しないときのブート時のメッセージを見ると、

cma: CMA: reserved 128 MiB at 27800000

となっていて、始めから十分な大きさが確保されているようでした。

使ってみる

ドライバには、2種類の API があるようで、それぞれの使い方は 対応するヘッダファイルに詳しく書かれています。

後者はとても簡単に使えるようになっているので、こちらを使うことにします。

どちらを使った場合にも、Scatter Gather 機能のサポートは無いようです。

LANG:c
#include <stdio.h>
#include <stdbool.h>
#include "libaxidma.h"

#define MYCHAN 0
#define BUFFER_SIZE 1024
#define TRANSFER_LEN 16

void dma_int_handler(int channel_id, void *data)
{
  if(channel_id != MYCHAN)
    printf("ERROR: unexpected channel id %d\n", channel_id);

  (*(int*)data) = 1; // done
}

int array_contains(int target, int *array, int length)
{
  int i;
  for (i=0; i<length; i++)
    if ( array[i] == target ) return 1;
  return 0;
}


void display_result(void *buffer)
{
  int i, *p = (int*)buffer;
  for(i=0; i<TRANSFER_LEN; i++) {
    printf("%08x ", *(p++));
    if (i % 8 == 7)
      printf("\n");
  }
}

void main()
{
  // デバイスハンドルを得る
  axidma_dev_t hdma = axidma_init();

  // 使用するチャンネルが rx 機能を持っていることを確認
  int nrxchan, *rxchans = axidma_get_dma_rx(hdma, &nrxchan);
  if(!array_contains(MYCHAN, rxchans, nrxchan)) {
    printf("チャンネル %d は読み込みをサポートしません", MYCHAN);
    goto exit;
  }

  // バッファーを確保
  void *buffer = axidma_malloc(hdma, BUFFER_SIZE);

  // 割り込みハンドラを設定
  volatile int done = 0;
  axidma_set_callback(hdma, MYCHAN, dma_int_handler, (void*)&done);

  // 非同期転送開始
  printf("転送開始\n");
  int ret = axidma_oneway_transfer(hdma, AXIDMA_READ, MYCHAN, buffer, TRANSFER_LEN, false);

  // 終了待ち
  while (!done) {
    ;
    // 途中で止める場合
    // axidma_stop_transfer(hdma, MYCHAN, enum axidma_dir dir);
  }

  display_result(buffer);

  // バッファーを解放
  axidma_free(hdma, buffer, BUFFER_SIZE);

exit:

  // デバイスハンドルを解放する
  axidma_destroy(hdma);
}

リンクできない? → 解決

ごく普通にコンパイルしようとしたところ、

LANG:console
$ gcc main.c -L /usr/local/lib -l libaxidma
 /usr/bin/ld: -llibaxidma が見つかりません
$ file /usr/local/lib/libaxidma.so
 /usr/local/lib/libaxidma.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=b533a8f1de3cf2fd0ca9faf9b7e1424b5dd8c776, not stripped

so ファイルはあるにもかかわらずリンクできないという症状に悩まされました。

理由がわからずかなり悩みましたが、 xilinx_axidma の make は exmples のコードもリンクできているのだから、 それをまねすればいいはず!と気づいて(実はデバイスドライバの説明にも「まねしろ」と書かれてました orz)、まずはどうしているか確認。

LANG:console
$ make -n CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm KBUILD_DIR=/home/osamu/linux-zturn
 ...

 arm-linux-gnueabihf-gcc -Wall -Wextra -Werror -std=gnu99 -O3 -I include \
    examples/axidma_transfer.c examples/dma_util.c examples/util.c \
   -o examples/axidma_transfer -L outputs -l axidma -Wl,-rpath,'$ORIGIN'
 ...

怪しいオプションがあるのでこれをまねすると、

LANG:console
$ gcc main.c -L /usr/local/lib -l axidma -Wl,-rpath,'$ORIGIN'
$ ls a.out
 a.out

リンクできました。

http://www.kotha.net/ghcguide_ja/7.0.4/using-shared-libs.html によれば、 この指定はアプリケーションプログラムと同じディレクトリに .so ファイルを 置くという宣言になるので、ちょっと変更して

LANG:console
$ cat > Makefile
 axidmatest: main.c
         gcc -o axidmatest main.c -L /usr/local/lib -l axidma -Wl,-rpath,'/usr/local/lib'
$ make
 gcc -o axidmatest main.c -L /usr/local/lib -l axidma -Wl,-rpath,'/usr/local/lib'

としました。

エラーが出る

LANG:console
$ cat /sys/class/dma/dma1chan0/memcpy_count
 0
$ cat /sys/class/dma/dma1chan0/bytes_transferred
 0
$ sudo ./axidmatest
 転送開始
 xilinx_dma_start_transfer::simple DMA mode
 xilinx-dma 40400000.axidma0: Channel e6f61950 has errors 11, cdr 0 tdr 0
 00000004 00000005 00000006 00000007 00000000 00000000 00000000 00000000
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
$ cat /sys/class/dma/dma1chan0/memcpy_count
 0
$ cat /sys/class/dma/dma1chan0/bytes_transferred
 0
$ sudo ./axidmatest
 転送開始
 xilinx_dma_start_transfer::simple DMA mode
 xilinx-dma 40400000.axidma0: Channel e6f61950 has errors 11, cdr 0 tdr 0
 00000033 00000034 00000035 00000036 00000000 00000000 00000000 00000000
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
$ sudo ./axidmatest
 転送開始
 xilinx_dma_start_transfer::simple DMA mode
 xilinx-dma 40400000.axidma0: Channel e6f61950 has errors 11, cdr 0 tdr 0
 00000062 00000063 00000064 00000065 00000000 00000000 00000000 00000000
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
$ sudo ./axidmatest
 転送開始
 xilinx_dma_start_transfer::simple DMA mode
 xilinx-dma 40400000.axidma0: Channel e6f61950 has errors 11, cdr 0 tdr 0
 00000091 00000092 00000093 00000094 00000000 00000000 00000000 00000000
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
$ sudo ./axidmatest
 転送開始
 xilinx_dma_start_transfer::simple DMA mode
 xilinx-dma 40400000.axidma0: Channel e6f61950 has errors 11, cdr 0 tdr 0
 000000c0 000000c1 000000c2 000000c3 00000000 00000000 00000000 00000000
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
$ sudo ./axidmatest
 転送開始
 xilinx_dma_start_transfer::simple DMA mode
 xilinx-dma 40400000.axidma0: Channel e6f61950 has errors 11, cdr 0 tdr 0
 000000ef 000000f0 000000f1 000000f2 00000000 00000000 00000000 00000000
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

おかしいところ:

  • エラーが出ている xilinx-dma 40400000.axidma0: Channel e6f61950 has errors 11, cdr 0 tdr 0
  • 4ワードしか更新されていない
  • ゼロから始まっていない
  • 等間隔 (0x2f ごと) に値が増えている

エラーを出しているのは xilinx の xilinx_dma.c のここで、
https://github.com/Xilinx/linux-xlnx/blob/f6b354241c0b0eebd8aa9bff1411c24301e1e50f/drivers/dma/xilinx/xilinx_dma.c#L1474

axi_dma のステータスレジスタのエラービットをそのまま表示したのが 0x11 ということになる。

axi_dma のリファレンスによれば、
https://www.xilinx.com/support/documentation/ip_documentation/axi_dma/v7_1/pg021_axi_dma.pdf

dmasr_register.png

0x11 = DMAIntErr + Halted なので、問題は DMAIntErr だ。

このエラーが出る理由は、Stream から送られたデータ量が読み込み側が期待するより大きいとき、 とのことで、、、マジカッカカカ。

読み取り時に指定するパケットサイズを送信パケットサイズに合わせなければならないのだそうです。

受信できました

パケットサイズをハードウェアに合わせて 256ワード = 1024バイト にする。

あと、TRANSFER_LEN をワード単位と勘違いしていたので、 display_result を修正して、

LANG:c
#define BUFFER_SIZE 2048
#define TRANSFER_LEN 1024
...
void display_result(void *buffer)
{
  int i, *p = (int*)buffer;
  for(i=0; i<TRANSFER_LEN/4; i++) {
...

としたところ、正しく動作しました。

LANG:console
$ sudo ./axidmatest
 転送開始
 xilinx_dma_start_transfer::simple DMA mode
 00000800 00000801 00000802 00000803 00000804 00000805 00000806 00000807
 00000808 00000809 0000080a 0000080b 0000080c 0000080d 0000080e 0000080f
 ...
 000008f8 000008f9 000008fa 000008fb 000008fc 000008fd 000008fe 000008ff
$ cat /sys/class/dma/dma1chan0/memcpy_count
 0 
$ cat /sys/class/dma/dma1chan0/bytes_transferred
 0

/sys/class/dma/dma1chan0/ 以下の値が変化しないのはそういうものなのかどうか。

事前にパケットサイズがわかっていないと受け取れない?

このインタフェースを用いる限り、 パケットサイズが不明なデータを DMA を使って受け取るのは難しいみたいですね。

axi_dma 自体の説明によればタイムアウトで割り込みを送出する 機能もあるようなので、どうにかうまく使えないか、後で調べたいと思います。

以下、もっと突っ込んだ話を勉強しなければならなくなった時用の資料

というか、上記の簡単な解にたどり着くまでに苦労した経緯だったりもします。

後で読むリスト

Linux DMA from User Space
https://forums.xilinx.com/xlnx/attachments/xlnx/ELINUX/10693/1/Linux%20DMA%20from%20User%20Space-public.pdf

Linux DMA in Device Drivers
https://forums.xilinx.com/xlnx/attachments/xlnx/ELINUX/10658/1/drivers-session4-dma-4public.pdf

Introduction to Linux Device Drivers
https://forums.xilinx.com/xlnx/attachments/xlnx/ELINUX/10669/1/drivers-sessions1-2-public.pdf

axi_dma のドキュメント

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

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

など。

キャッシュについて

ACP について

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

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

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);

Xilinx の XAxiDma ドライバのサンプルを読む

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

割り込みについて 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_intr.c

  1. XAxiDma_LookupConfig(DeviceId) で DeviceId に対するハードウェア情報を得る (xaxidma_sinit.c)
  2. XAxiDma_CfgInitialize(&AxiDma, CfgPtr); によりハードウェア情報を元に AxiDma を設定する
    • 内部で呼ばれる XAxiDma_Reset がタイムアウトすると失敗する (AxiDma.Initialized == 0 となる)
  3. XAxiDma_HasSg(&AxiDma) で SG モードかどうかを判別 (AxiDma.HasSg, CfgPtr->HasSg に対応)
    • SG モード対応だと Simple モードでは動作しない???
  4. SetupIntrSystem 内で割り込みを設定
    1. XScuGic_LookupConfig(INTC_DEVICE_ID); でハードウェア情報を得る
    2. XScuGic_CfgInitialize(IntcPtr,IntcConfig,IntcConfig->CpuBaseAddress); で Intc を設定
    3. XScuGic_SetPriorityTriggerType で Priority = 0xA0, TriggerType = 0x3 にする
      • Priority は小さいほど高い。8 の倍数が有効。
      • TriggerType = 3 は Rising edge sensitive
    4. XScuGic_Connect(IntcPtr, XxIntrId, XxIntrHandler, AxiDmaPtr); で割り込みハンドラを設定
    5. XScuGic_Enable(IntcPtr, XxIntrId); で割り込みを許可
    6. Xil_ExceptionInit(); これ、実際には何もしない。 (lib\bsp\standalone_vX.X\src\arm\common\xil_exception.c)
    7. Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, INTC_HANDLER, IntcPtr); 割り込みハンドラを登録
      • INTC_HANDLER は xscugic_intr.c の XScuGic_InterruptHandler を指しており、 そこから GIC へ登録したハンドラが呼ばれる
    8. Xil_ExceptionEnable(); で cpsr の XIL_EXCEPTION_IRQ で指定したビットをクリアする
  5. XAxiDma_IntrDisable で一旦 DMA からの割り込み信号送出を禁止する
  6. XAxiDma_IntrEnable で DMA からの割り込み信号送出を許可する
  7. Xil_DCacheFlushRange で送信バッファのキャッシュをメモリに書き出す
  8. XAxiDma_SimpleTransfer で送受信の要求
  9. 割り込みハンドラで
    1. 引数から AxiDma へのポインタを得る
    2. XAxiDma_IntrGetIrq により対応する IRQ 番号を得る
    3. IrqStatus & XAXIDMA_IRQ_ALL_MASK で割り込みが生じたか確かめる
      • いくつかの割り込み信号線をまとめている場合にも対応できる
    4. XAxiDma_IntrAckIrq により対応する IRQ に Acknowledge を送る
    5. IrqStatus & XAXIDMA_IRQ_ERROR_MASK ならエラーで割り込みが生じた
      • XAxiDma_Reset して HALT 状態から戻す
      • アプリケーションにエラーを通知する
    6. IrqStatus & XAXIDMA_IRQ_IOC_MASK なら正常終了
  10. Xil_DCacheInvalidateRange で受信バッファのキャッシュ内容を無効化
  11. DisableIntrSystem 内で割り込みハンドラを解除する
    1. XScuGic_Disconnect で GIC から割り込みハンドラを解除する
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);
}

Xilinx の XAxiDma ドライバ から直接叩いてみる

ベアメタルで試します


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