UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (実践編2)


はじめに

UltraZed に載っている Zynq UltraScale+ MPSoC は PL(Programmable Logic) から PS(Processing System) へのアクセスを行う AXI Port が複数あります。そのうち HPC0 と HPC1 はハードウェアで APU のキャッシュをスヌープすることによりコヒーレンシを維持して転送することが出来ます。

次の記事で AXI HPC port によるキャッシュコヒーレンシ転送の基礎知識を説明しました。

上の記事で「現在の Linux Kernel は Outer Share をサポートしていません」と書きましたが、この記事では Linux Kernel にパッチをあてることで Outer Share をサポートする方法と udmabuf (https://github.com/ikwzm/udmabuf) を Outer Share に対応して実際に実行してみた結果を示します。


お断り

この記事では Linux Kernel の MMU 処理にパッチをあてますが、これはあくまでも実験です。MMU 周りは Linux Kernel の根幹部分であり、ここに修正を加えて運用するには数多くのレビューやテストが必要です。今回は ちょっとやってみました的なトライアルの記事であることに注意してください。


Linux Kernel を Outer Share 対応にする

ここでは次の記事で紹介した Linux Kernel をもとに Outer Share 対応の修正をします。


arch/arm64/include/asm/pgtable-hwdef.h

まずは arch/arm64/include/asm/pgtable-hwdef.h に、PTE に設定する Outer Share の値などを定義します。

index eb0c2bd..1892e13 100644

--- a/arch/arm64/include/asm/pgtable-hwdef.h
+++ b/arch/arm64/include/asm/pgtable-hwdef.h
@@ -157,7 +157,6 @@
#define PTE_TABLE_BIT (_AT(pteval_t, 1) << 1)
#define PTE_USER (_AT(pteval_t, 1) << 6) /* AP[1] */
#define PTE_RDONLY (_AT(pteval_t, 1) << 7) /* AP[2] */
-#define PTE_SHARED (_AT(pteval_t, 3) << 8) /* SH[1:0], inner shareable */
#define PTE_AF (_AT(pteval_t, 1) << 10) /* Access Flag */
#define PTE_NG (_AT(pteval_t, 1) << 11) /* nG */
#define PTE_DBM (_AT(pteval_t, 1) << 51) /* Dirty Bit Management */
@@ -173,6 +172,14 @@
#define PTE_ATTRINDX_MASK (_AT(pteval_t, 7) << 2)

/*
+ * SH[1:0] encoding
+ */
+#define PTE_SHARED_MASK (_AT(pteval_t, 3) << 8)
+#define PTE_INNER_SHARED (_AT(pteval_t, 3) << 8) /* SH[1:0], inner shareable */
+#define PTE_OUTER_SHARED (_AT(pteval_t, 2) << 8) /* SH[1:0], outer shareable */
+#define PTE_SHARED PTE_INNER_SHARED
+
+/*
* 2nd stage PTE definitions
*/
#define PTE_S2_RDONLY (_AT(pteval_t, 1) << 6) /* HAP[2:1] */

具体的には PTE_SHARED_MASKPTE_INNER_SHAREDPTE_OUTER_SHARED を定義します。互換性のために PTE_SHAREDPTE_INNER_SHARED の値で再定義しておきます。


include/linux/mm.h

Linux Kernel では MMU の各ページの設定を vm_flags というアーキテクチャに依存しないフラグを使います。そして各アーキテクチャごとに、この vm_flags の値から MMU にアーキテクチャに依存した値を作ります。この vm_flags に設定するフラグの値が include/linux/mm.h に定義されています。

この include/linux/mm.h に メモリ領域が Outer Share であることを示す VM_OUTER_SHARED フラグを追加します。

index 43edf65..62996eb 100644

--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -218,6 +218,8 @@
#if defined(CONFIG_X86)
# define VM_PAT VM_ARCH_1 /* PAT reserves whole VMA at once (x86) */
#if defined (CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS)
# define VM_PKEY_SHIFT VM_HIGH_ARCH_BIT_0
# define VM_PKEY_BIT0 VM_HIGH_ARCH_0 /* A protection key is a 4-bit value */
# define VM_PKEY_BIT1 VM_HIGH_ARCH_1
# define VM_PKEY_BIT2 VM_HIGH_ARCH_2
# define VM_PKEY_BIT3 VM_HIGH_ARCH_3
#endif
#elif defined(CONFIG_PPC)
# define VM_SAO VM_ARCH_1 /* Strong Access Ordering (powerpc) */
#elif defined(CONFIG_PARISC)
# define VM_GROWSUP VM_ARCH_1
#elif defined(CONFIG_METAG)
# define VM_GROWSUP VM_ARCH_1
#elif defined(CONFIG_IA64)
# define VM_GROWSUP VM_ARCH_1
+#elif defined(CONFIG_ARM64)
+# define VM_OUTER_SHARED VM_ARCH_1
#elif !defined(CONFIG_MMU)
# define VM_MAPPED_COPY VM_ARCH_1 /* T if mapped copy of data (nommu mmap) */
#endif

include/linux/mm.h で定義されている vm_flags の値のうち、アーキテクチャ毎に自由に使って良いフラグとして VM_ARCH_1 が定義されています。ここでは この VM_ARCH_1VM_OUTER_SHARED として使います。


mm/mmap.c

vm_flags で指定したフラグをアーキテクチャに依存した MMU の PTE(Page Table Entry) の値に変換する関数が vm_get_page_prot() として mm/mmap.c に定義されています。


mm/mmap.c

      :

(前略)
:
/* description of effects of mapping type and prot in current implementation.
* this is due to the limited x86 page protection hardware. The expected
* behavior is in parens:
*
* map_type prot
* PROT_NONE PROT_READ PROT_WRITE PROT_EXEC
* MAP_SHARED r: (no) no r: (yes) yes r: (no) yes r: (no) yes
* w: (no) no w: (no) no w: (yes) yes w: (no) no
* x: (no) no x: (no) yes x: (no) yes x: (yes) yes
*
* MAP_PRIVATE r: (no) no r: (yes) yes r: (no) yes r: (no) yes
* w: (no) no w: (no) no w: (copy) copy w: (no) no
* x: (no) no x: (no) yes x: (no) yes x: (yes) yes
*
* On arm64, PROT_EXEC has the following behaviour for both MAP_SHARED and
* MAP_PRIVATE:
* r: (no) no
* w: (no) no
* x: (yes) yes
*/

pgprot_t protection_map[16] __ro_after_init = {
__P000, __P001, __P010, __P011, __P100, __P101, __P110, __P111,
__S000, __S001, __S010, __S011, __S100, __S101, __S110, __S111
};

pgprot_t vm_get_page_prot(unsigned long vm_flags)
{
return __pgprot(pgprot_val(protection_map[vm_flags &
(VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)]) |
pgprot_val(arch_vm_get_page_prot(vm_flags)));
}
EXPORT_SYMBOL(vm_get_page_prot);

:
(後略)
:


vm_get_page_prot() は、まず vm_flags のうち VM_READVM_WRITEVM_EXECVM_SHARED の4つのフラグの組み合わせ(全部で16パターン)からテーブル(protection_map[]) を引いて値を得ます。protection_map[] にはあらかじめアーキテクチャに依存した値 __P000__S111 が入っています。

さらに、arch_vm_get_page_prot(vm_flags) というアーキテクチャに依存した関数の戻り値を論理和しています。実はこの論理和しているというのがちょっとやっかいで、vm_flags に VM_OUTER_SHARED が設定されていれば Inner Sharerable(SH の値は '11') を Outer Sharerable(SH の値は '10') にしたいと思ってもビットのクリアが無いので出来ません。

mm/mmap.c は各アーキテクチャ共通のファイルなので、このファイルを修正するのは避けて、別の方法で修正します。


arch/arm64/include/asm/pgtable-prot.h

mm/mmap.c の vm_get_page_prot() で使っている protection_map[] の初期値 __P000__S111 は arm64 の場合、arch/arm64/include/asm/pgtable-prot.h に定義されています。

index 0a5635f..7c9723c 100644

--- a/arch/arm64/include/asm/pgtable-prot.h
+++ b/arch/arm64/include/asm/pgtable-prot.h
@@ -63,31 +63,40 @@
#define PAGE_S2 __pgprot(PROT_DEFAULT | PTE_S2_MEMATTR(MT_S2_NORMAL) | PTE_S2_RDONLY)
#define PAGE_S2_DEVICE __pgprot(PROT_DEFAULT | PTE_S2_MEMATTR(MT_S2_DEVICE_nGnRE) | PTE_S2_RDONLY | PTE_UXN)

-#define PAGE_NONE __pgprot(((_PAGE_DEFAULT) & ~PTE_VALID) | PTE_PROT_NONE | PTE_RDONLY | PTE_PXN | PTE_UXN)
-#define PAGE_SHARED __pgprot(_PAGE_DEFAULT | PTE_USER | PTE_NG | PTE_PXN | PTE_UXN | PTE_WRITE)
-#define PAGE_SHARED_EXEC __pgprot(_PAGE_DEFAULT | PTE_USER | PTE_NG | PTE_PXN | PTE_WRITE)
-#define PAGE_READONLY __pgprot(_PAGE_DEFAULT | PTE_USER | PTE_RDONLY | PTE_NG | PTE_PXN | PTE_UXN)
-#define PAGE_READONLY_EXEC __pgprot(_PAGE_DEFAULT | PTE_USER | PTE_RDONLY | PTE_NG | PTE_PXN)
-#define PAGE_EXECONLY __pgprot(_PAGE_DEFAULT | PTE_RDONLY | PTE_NG | PTE_PXN)
-
-#define __P000 PAGE_NONE
-#define __P001 PAGE_READONLY
-#define __P010 PAGE_READONLY
-#define __P011 PAGE_READONLY
-#define __P100 PAGE_EXECONLY
-#define __P101 PAGE_READONLY_EXEC
-#define __P110 PAGE_READONLY_EXEC
-#define __P111 PAGE_READONLY_EXEC
-
-#define __S000 PAGE_NONE
-#define __S001 PAGE_READONLY
-#define __S010 PAGE_SHARED
-#define __S011 PAGE_SHARED
-#define __S100 PAGE_EXECONLY
-#define __S101 PAGE_READONLY_EXEC
-#define __S110 PAGE_SHARED_EXEC
-#define __S111 PAGE_SHARED_EXEC
-
+#define _PAGE_NONE (((_PAGE_DEFAULT) & ~PTE_VALID) | PTE_PROT_NONE | PTE_RDONLY | PTE_PXN | PTE_UXN)
+#define _PAGE_SHARED (_PAGE_DEFAULT | PTE_USER | PTE_NG | PTE_PXN | PTE_UXN | PTE_WRITE)
+#define _PAGE_SHARED_EXEC (_PAGE_DEFAULT | PTE_USER | PTE_NG | PTE_PXN | PTE_WRITE)
+#define _PAGE_READONLY (_PAGE_DEFAULT | PTE_USER | PTE_RDONLY | PTE_NG | PTE_PXN | PTE_UXN)
+#define _PAGE_READONLY_EXEC (_PAGE_DEFAULT | PTE_USER | PTE_RDONLY | PTE_NG | PTE_PXN)
+#define _PAGE_EXECONLY (_PAGE_DEFAULT | PTE_RDONLY | PTE_NG | PTE_PXN)
+
+#define PAGE_NONE __pgprot(_PAGE_NONE)
+#define PAGE_SHARED __pgprot(_PAGE_SHARED)
+#define PAGE_SHARED_EXEC __pgprot(_PAGE_SHARED_EXEC)
+#define PAGE_READONLY __pgprot(_PAGE_READONLY)
+#define PAGE_READONLY_EXEC __pgprot(_PAGE_READONLY_EXEC)
+#define PAGE_EXECONLY __pgprot(_PAGE_EXECONLY)
:
+
+#define __P000 __pgprot(_PAGE_NONE & ~PTE_SHARED_MASK)
+#define __P001 __pgprot(_PAGE_READONLY & ~PTE_SHARED_MASK)
+#define __P010 __pgprot(_PAGE_READONLY & ~PTE_SHARED_MASK)
+#define __P011 __pgprot(_PAGE_READONLY & ~PTE_SHARED_MASK)
+#define __P100 __pgprot(_PAGE_EXECONLY & ~PTE_SHARED_MASK)
+#define __P101 __pgprot(_PAGE_READONLY_EXEC & ~PTE_SHARED_MASK)
+#define __P110 __pgprot(_PAGE_READONLY_EXEC & ~PTE_SHARED_MASK)
+#define __P111 __pgprot(_PAGE_READONLY_EXEC & ~PTE_SHARED_MASK)
+
+#define __S000 __pgprot(_PAGE_NONE & ~PTE_SHARED_MASK)
+#define __S001 __pgprot(_PAGE_READONLY & ~PTE_SHARED_MASK)
+#define __S010 __pgprot(_PAGE_SHARED & ~PTE_SHARED_MASK)
+#define __S011 __pgprot(_PAGE_SHARED & ~PTE_SHARED_MASK)
+#define __S100 __pgprot(_PAGE_EXECONLY & ~PTE_SHARED_MASK)
+#define __S101 __pgprot(_PAGE_READONLY_EXEC & ~PTE_SHARED_MASK)
+#define __S110 __pgprot(_PAGE_SHARED_EXEC & ~PTE_SHARED_MASK)
+#define __S111 __pgprot(_PAGE_SHARED_EXEC & ~PTE_SHARED_MASK)
+
+#define arch_vm_get_page_prot(vm_flags) __pgprot((vm_flags & VM_OUTER_SHARED) ? PTE_OUTER_SHARED : PTE_INNER_SHARED)
+
#endif /* __ASSEMBLY__ */

#endif /* __ASM_PGTABLE_PROT_H */

まず、この初期値 __P000__S111 には Sharable attribute のビットをクリアした値を設定します。ソースコードでは ~PTE_SHARED_MASK で論理積(&) しています。

次に arch_vm_get_page_prot(vm_flags) を定義します。arch_vm_get_page_prot(vm_flags) は vm_flags の VM_OUTER_SHARED フラグを調べて、真なら PTE_OUTER_SHARED の値を、偽なら PTE_INNER_SHARED の値を返します。

以上、3つのファイル(include/linux/mm.h、arch/arm64/include/asm/pgtable-hwdef.h、arch/arm64/include/asm/pgtable-prot.h)を修正して Linux Kernel を Outer Share に対応させます。


udmabuf を Outer Share 対応にする

ここでは udmabuf v1.3.1 をもとに Outer Share 対応の修正をします。


struct udmabuf_device_data

デバイスドライバの状態を維持する struct udmabuf_device_databool outer_shared を追加します。

 /**

* struct udmabuf_device_data - Udmabuf driver data structure
*/
struct udmabuf_device_data {
struct device* sys_dev;
struct device* dma_dev;
struct cdev cdev;
dev_t device_number;
struct mutex sem;
bool is_open;
int size;
size_t alloc_size;
void* virt_addr;
dma_addr_t phys_addr;
int sync_mode;
int sync_offset;
size_t sync_size;
int sync_direction;
bool sync_owner;
int sync_for_cpu;
int sync_for_device;
#if (USE_OF_RESERVED_MEM == 1)
bool of_reserved_mem;
#endif
+#if (defined(VM_OUTER_SHARED))
+ bool outer_shared;
+#endif
#if ((UDMABUF_DEBUG == 1) && (USE_VMA_FAULT == 1))
bool debug_vma;
#endif
};


udmabuf_device_file_mmap()

udmabuf_device_file_mmap() では kernel に渡す vma の vm_flags と vm_page_prot の値を設定しています。ここに、outer_shared フラグが真にセットされていた場合は vm_flags に VM_OUTER_SHARED をセットするコードを追加します。

 /**

* udmabuf_device_file_mmap() - udmabuf device file memory map operation.
* @file: Pointer to the file structure.
* @vma: Pointer to the vm area structure.
* Return: Success(=0) or error status(<0).
*/
static int udmabuf_device_file_mmap(struct file *file, struct vm_area_struct* vma)
{
struct udmabuf_device_data* this = file->private_data;

if ((file->f_flags & O_SYNC) | (this->sync_mode & SYNC_ALWAYS)) {
switch (this->sync_mode & SYNC_MODE_MASK) {
case SYNC_MODE_NONCACHED :
vma->vm_flags |= VM_IO;
vma->vm_page_prot = _PGPROT_NONCACHED(vma->vm_page_prot);
break;
case SYNC_MODE_WRITECOMBINE :
vma->vm_flags |= VM_IO;
vma->vm_page_prot = _PGPROT_WRITECOMBINE(vma->vm_page_prot);
break;
case SYNC_MODE_DMACOHERENT :
vma->vm_flags |= VM_IO;
vma->vm_page_prot = _PGPROT_DMACOHERENT(vma->vm_page_prot);
break;
default :
break;
}
}
+ else {
+#if (defined(VM_OUTER_SHARED))
+ if (this->outer_shared) {
+ vma->vm_flags |= VM_OUTER_SHARED;
+ }
+#endif
+ }
vma->vm_private_data = this;
vma->vm_pgoff = 0;


udmabuf_platform_driver_probe()

udmabuf_platform_driver_probe() は デバイスツリーのプロパティによって初期値を設定しています。この udmabuf_platform_driver_probe() に、デバイスツリーに outer-shared プロパティが定義されていた場合は outer_shared フラグを真にするコードを追加します。

     /*

* sync-size property
*/
if (of_property_read_u32(pdev->dev.of_node, "sync-size", &of_u32_value) == 0) {
if (device_data->sync_offset + of_u32_value > device_data->size) {
dev_err(&pdev->dev, "invalid sync-size property value=%d\n", of_u32_value);
goto failed;
}
device_data->sync_size = (size_t)of_u32_value;
} else {
device_data->sync_size = device_data->size;
}
+ /*
+ * outer shared property
+ */
+#if (defined(VM_OUTER_SHARED))
+ {
+ device_data->outer_shared = of_property_read_bool(pdev->dev.of_node, "outer-shared");
+ }
+#endif
/*
* udmabuf_device_setup()
*/
retval = udmabuf_device_setup(device_data);


準備

ここでは 「UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (実践編1)」 @Qiita で用いたサンプルデザインとテストスクリプトを使います。詳細はそちらの記事を参照してください。

Outer Share に対応した Linux Kernel をビルドして UltraZed にインストールします。その際、linux-image パッケージおよび linux-header パッケージのインストールも忘れずに。

Outer Share に対応した udmabuf をビルドして UltraZed にインストールします。

Linux Kernel を変更しているので、fclkcfg も再ビルドして UltraZed にインストールします。


FPGA のコンフィギュレーション

「UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (実践編1)」 @Qiita の「FPGA のコンフィギュレーション」参照。


FPGA のクロックの設定

「UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (実践編1)」 @Qiita の「FPGA のクロックの設定」参照。


Uio と Udmabuf の準備

FPGA にコンフィギュレーションした回路にアクセスするには uio を使います。また、データのやりとりは udmabuf を使います。そのために次のような Device Tree Overlay 用のソースファイルを用意します。ただし、negative-udmabuf4 と negative-udmabuf5 には outer-shared プロパティが追加されています。


negative2-outer-shared.dts

/dts-v1/;/plugin/;

/ {
fragment@0 {
target-path = "/amba_pl@0";
#address-cells = <2>;
#size-cells = <2>;

__overlay__ {
#address-cells = <2>;
#size-cells = <2>;

negative-uio {
compatible = "generic-uio";
reg = <0x0 0x80010000 0x0 0x10000>;
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
};

negative-udmabuf4 {
compatible = "ikwzm,udmabuf-0.10.a";
device-name = "udmabuf4";
size = <0x00100000>;
outer-shared;
};

negative-udmabuf5 {
compatible = "ikwzm,udmabuf-0.10.a";
device-name = "udmabuf5";
size = <0x00100000>;
outer-shared;
};
};
};
} ;


次の手順で Device Tree Overlay を使って uio と udmabuf を設定します。


  1. Device Tree Overlay 用ソースファイル(ここでは negative2-outer-shared.dts)を dtc(Device Tree Compiler) を使って dtb (ここでは negative2-outer-shared.dtb) に変換します。

  2. /config/device-tree/overlays 下に Device Tree Overlay 用のディレクトリ(ここでは negative2) を作ります。

  3. 2.で作ったディレクトリ下の dtbo に 1. で作った dtb を書き込みます。

これで次のように /dev/uio1、/dev/udmabuf4、/dev/udmabuf5 が出来れば成功です。

fpga@debian-fpga:~/examples/negative2$ dtc -I dts -O dtb -o negative2-outer-shared.dtb negative2-outer-shared.dts

fpga@debian-fpga:~/examples/negative2$ sudo mkdir /config/device-tree/overlays/negative2
fpga@debian-fpga:~/examples/negative2$ sudo cp negative2-outer-shared.dtb /config/device-tree/overlays/negative2/dtbo
[ 164.131871] udmabuf udmabuf4: driver installed
[ 164.136254] udmabuf udmabuf4: major number = 244
[ 164.141021] udmabuf udmabuf4: minor number = 0
[ 164.145619] udmabuf udmabuf4: phys address = 0x0000000070400000
[ 164.151689] udmabuf udmabuf4: buffer size = 1048576
[ 164.156811] udmabuf udmabuf4: dma coherent = 0
[ 164.161412] udmabuf amba_pl@0:negative-udmabuf4: driver installed.
[ 164.175758] udmabuf udmabuf5: driver installed
[ 164.180142] udmabuf udmabuf5: major number = 244
[ 164.184917] udmabuf udmabuf5: minor number = 1
[ 164.189505] udmabuf udmabuf5: phys address = 0x0000000070500000
[ 164.195576] udmabuf udmabuf5: buffer size = 1048576
[ 164.200698] udmabuf udmabuf5: dma coherent = 0
[ 164.205298] udmabuf amba_pl@0:negative-udmabuf5: driver installed.
fpga@debian-fpga:~/examples/negative2$ ls -la /dev/uio*
crw------- 1 root root 245, 0 Jan 8 18:07 /dev/uio0
crw------- 1 root root 245, 0 Jan 8 18:07 /dev/uio1
fpga@debian-fpga:~/examples/negative2$ ls -la /dev/udmabuf*
crw------- 1 root root 244, 0 Jan 8 18:07 /dev/udmabuf4
crw------- 1 root root 244, 1 Jan 8 18:07 /dev/udmabuf5


実際に走らせてみよう


negative2.py

FPGA にロードした回路を動かすために次のようなサンプルプログラムを用意しています。このプログラムは「UltraZed 向け Debian GNU/Linux (v2018.2版) で Vivado-HLS を使って合成した回路を動かす」 で紹介した negative.py の udmabuf の sync_for_device()、sync_for_cpu() を使っているところをコメントアウトしたものです。negative.py の詳細な説明は記事を参照してください。


negative2.py

from udmabuf import Udmabuf

from uio import Uio
import numpy as np
import time

if __name__ == '__main__':
uio1 = Uio('uio1')
regs = uio1.regs()
udmabuf4 = Udmabuf('udmabuf4')
udmabuf5 = Udmabuf('udmabuf5')
test_dtype = np.uint32
test_size = min(int(udmabuf4.buf_size/(np.dtype(test_dtype).itemsize)),
int(udmabuf5.buf_size/(np.dtype(test_dtype).itemsize)))

udmabuf4_array = udmabuf4.memmap(dtype=test_dtype, shape=(test_size))
udmabuf4_array[:] = np.random.randint(-21474836478,2147483647,(test_size))
udmabuf4.set_sync_to_device(0, test_size*(np.dtype(test_dtype).itemsize))

udmabuf5_array = udmabuf5.memmap(dtype=test_dtype, shape=(test_size))
udmabuf5_array[:] = np.random.randint(-21474836478,2147483647,(test_size))
udmabuf5.set_sync_to_cpu( 0, test_size*(np.dtype(test_dtype).itemsize))

total_setup_time = 0
total_cleanup_time = 0
total_xfer_time = 0
total_xfer_size = 0
count = 0

for i in range (0,9):

start_time = time.time()
# udmabuf4.sync_for_device()
# udmabuf5.sync_for_device()
regs.write_word(0x18, udmabuf4.phys_addr & 0xFFFFFFFF)
regs.write_word(0x20, udmabuf5.phys_addr & 0xFFFFFFFF)
regs.write_word(0x28, test_size)
regs.write_word(0x04, 0x000000001)
regs.write_word(0x08, 0x000000001)
regs.write_word(0x0C, 0x000000001)
uio1.irq_on()
phase0_time = time.time()
regs.write_word(0x00, 0x000000001)
uio1.wait_irq()

phase1_time = time.time()
regs.write_word(0x0C, 0x000000001)
# udmabuf4.sync_for_cpu()
# udmabuf5.sync_for_cpu()

end_time = time.time()
setup_time = phase0_time - start_time
xfer_time = phase1_time - phase0_time
cleanup_time = end_time - phase1_time
total_time = end_time - start_time

total_setup_time = total_setup_time + setup_time
total_cleanup_time = total_cleanup_time + cleanup_time
total_xfer_time = total_xfer_time + xfer_time
total_xfer_size = total_xfer_size + test_size
count = count + 1
print ("total:{0:.3f}[msec] setup:{1:.3f}[msec] xfer:{2:.3f}[msec] cleanup:{3:.3f}[msec]".format(round(total_time*1000.0,3), round(setup_time*1000.0,3), round(xfer_time*1000.0,3), round(cleanup_time*1000.0,3)))

print ("average_setup_time :{0:.3f}".format(round((total_setup_time /count)*1000.0,3)) + "[msec]")
print ("average_cleanup_time:{0:.3f}".format(round((total_cleanup_time/count)*1000.0,3)) + "[msec]")
print ("average_xfer_time :{0:.3f}".format(round((total_xfer_time /count)*1000.0,3)) + "[msec]")
print ("throughput :{0:.3f}".format(round(((total_xfer_size/total_xfer_time)/(1000*1000)),3)) + "[MByte/sec]")

udmabuf4_negative_array = np.negative(udmabuf4_array)
if np.array_equal(udmabuf4_negative_array, udmabuf5_array):
print("np.negative(udmabuf4) == udmabuf5 : OK")
else:
print("np.negative(udmabuf4) == udmabuf5 : NG")
count = 0
for i in range(test_size):
if udmabuf4_negative_array[i] != udmabuf5_array[i] :
count = count + 1
if count < 16:
print("udmabuf4_negative_array[0x{0:08X}] = 0x{1:08X} udmabuf5_array[0x{0:08X}] = 0x{2:08X}".format(i, udmabuf4_negative_array[i], udmabuf5_array[i]))
print("NG Count:{0}".format(count))



実行結果


negative2.py

うまくいくと次のような結果が得られます。

fpga@debian-fpga:~/examples/negative2$ python3 negative2.py

total:0.182[msec] setup:0.131[msec] xfer:0.039[msec] cleanup:0.012[msec]
total:0.106[msec] setup:0.077[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.105[msec] setup:0.076[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.103[msec] setup:0.074[msec] xfer:0.018[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
average_setup_time :0.081[msec]
average_cleanup_time:0.011[msec]
average_xfer_time :0.021[msec]
throughput :12416.066[MByte/sec]
np.negative(udmabuf4) == udmabuf5 : OK

udmabuf の sync_for_device()、sync_for_cpu() を使っていない(つまりソフトウェアでキャッシュのフラッシュと無効化をしていない)にも関わらず正常に動作していることがわかります。


おまけ

ダウンロードしたリポジトリには negative2.dts というデバイスツリーファイルがあります。これは negative-udmabuf4 と negative-udmabuf5 に outer-shared プロパティが追加されていないデバイスツリーです。

このデバイスツリーをオーバーレイして実行してみます。

fpga@debian-fpga:~/examples/negative2$ dtc -I dts -O dtb -o negative2.dtb negative2.dts

fpga@debian-fpga:~/examples/negative2$ sudo mkdir /config/device-tree/overlays/negative2
fpga@debian-fpga:~/examples/negative2$ sudo cp negative2.dtb /config/device-tree/overlays/negative2/dtbo

[ 411.302608] udmabuf udmabuf4: major number = 242
[ 411.308797] udmabuf udmabuf4: minor number = 0
[ 411.318939] udmabuf udmabuf4: phys address = 0x0000000070100000
[ 411.330561] udmabuf udmabuf4: buffer size = 1048576
[ 411.341235] udmabuf udmabuf4: dma coherent = 0
[ 411.361557] udmabuf amba_pl@0:negative-udmabuf4: driver installed.
[ 411.374819] udmabuf udmabuf5: major number = 242
[ 411.379563] udmabuf udmabuf5: minor number = 1
[ 411.384164] udmabuf udmabuf5: phys address = 0x0000000070200000
[ 411.390226] udmabuf udmabuf5: buffer size = 1048576
[ 411.395343] udmabuf udmabuf5: dma coherent = 0
[ 411.404544] udmabuf amba_pl@0:negative-udmabuf5: driver installed.

udmabuf に outer-shared プロパティを設定していない状態で negative2.py を実行すると次のように計算値と期待値が一致しません。

root@debian-fpga:/home/fpga/examples/negative2# python3 negative2.py

total:8.055[msec] setup:0.133[msec] xfer:7.909[msec] cleanup:0.013[msec]
total:0.108[msec] setup:0.078[msec] xfer:0.020[msec] cleanup:0.010[msec]
total:0.105[msec] setup:0.076[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.105[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.105[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.074[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.104[msec] setup:0.075[msec] xfer:0.019[msec] cleanup:0.010[msec]
total:0.103[msec] setup:0.074[msec] xfer:0.019[msec] cleanup:0.010[msec]
average_setup_time :0.082[msec]
average_cleanup_time:0.011[msec]
average_xfer_time :0.896[msec]
throughput :292.648[MByte/sec]
np.negative(udmabuf4) == udmabuf5 : NG
udmabuf4_negative_array[0x00000150] = 0x35F0A390 udmabuf5_array[0x00000150] = 0x2CAEB489
udmabuf4_negative_array[0x00000151] = 0xA282E20C udmabuf5_array[0x00000151] = 0x9C1B407D
udmabuf4_negative_array[0x00000152] = 0xE36A5ACE udmabuf5_array[0x00000152] = 0x1C8E0779
udmabuf4_negative_array[0x00000153] = 0x72D0E327 udmabuf5_array[0x00000153] = 0xEFFD13D9
udmabuf4_negative_array[0x00000154] = 0xD67B4723 udmabuf5_array[0x00000154] = 0x0397152D
udmabuf4_negative_array[0x00000155] = 0x91CAA97F udmabuf5_array[0x00000155] = 0x6AEEB430
udmabuf4_negative_array[0x00000156] = 0xECB19403 udmabuf5_array[0x00000156] = 0x5F7A10CD
udmabuf4_negative_array[0x00000157] = 0x14459554 udmabuf5_array[0x00000157] = 0x098BE308
udmabuf4_negative_array[0x00000158] = 0x5AD6963B udmabuf5_array[0x00000158] = 0xE8280CDE
udmabuf4_negative_array[0x00000159] = 0xAAF66E9C udmabuf5_array[0x00000159] = 0x343E66B2
udmabuf4_negative_array[0x0000015A] = 0x258FEE0C udmabuf5_array[0x0000015A] = 0x874D78C4
udmabuf4_negative_array[0x0000015B] = 0xA70D3B73 udmabuf5_array[0x0000015B] = 0xBA4D6D64
udmabuf4_negative_array[0x0000015C] = 0x8A12A8FD udmabuf5_array[0x0000015C] = 0x263185F8
udmabuf4_negative_array[0x0000015D] = 0x00B265CA udmabuf5_array[0x0000015D] = 0xC4C11AAD
udmabuf4_negative_array[0x0000015E] = 0xF4BAD740 udmabuf5_array[0x0000015E] = 0x7A815C84
NG Count:10496

これはキャッシュのコヒーレンシ維持が出来ていないために、PL 側の回路が間違った値を読んでいるか PL が書いた値を CPU が正常に読めていないために起ったものです。


所感

今回、Linux Kernel のソースコードを修正して Outer Share に対応して動作を確認してみたわけですが、ちょっと簡単に行き過ぎた感があります。すると次のような疑問がわきます。


はたしてこの修正方法で問題ないのか?

ぶっちゃけ簡単過ぎます。なにか見落としてないか不安です。


どうして Mainline では Outer Share に対応していなかったのか?

仮にこの修正方法で問題無かったとして、それならば何故 Mainline では Outer Share に対応していないのでしょう?

実は ARM64 の MMU あたりにバグがあって Outer Share を使うとそのバグが顕在化する(地雷を踏むとも言う)のかもしれません。

あるいは Outer Share の需要が無かったのでしょうか。需要が無いからあえて実装しなかったのかもしれません。しかし今回のような ZynqMP のケースでは Outer Share は大変有用でした。需要は十分あると思うのですが。


どうすれば Mainline で Outer Share に対応してもらえるのか?

ZynqMP のケースでは Outer Share は大変有用です。Mainline の方で対応してもらえると、とても嬉しい。しかし、筆者はあまり Linux Kernel のコミュニティとかに詳しく無いので、正直どうすれば良いのか判りません。特に Linux Kernel のコミュニティは怖い雰囲気があります。


参考