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 によるキャッシュコヒーレンシ転送の基礎知識を説明しました。

  * [「UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (基礎編)」 @Qiita](https://qiita.com/ikwzm/items/82f98bfa76922073e610)

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

お断り
-------------------------------------------------------------

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


Linux Kernel を Outer Share 対応にする
-------------------------------------------------------------

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

  * [「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」](https://qiita.com/ikwzm/items/d5417b39c983f8e56af9)


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

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

```diff
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_MASK`、`PTE_INNER_SHARED`、`PTE_OUTER_SHARED` を定義します。互換性のために `PTE_SHARED` も `PTE_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` フラグを追加します。

```diff
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_1` を `VM_OUTER_SHARED` として使います。

### mm/mmap.c

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

```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_READ`、`VM_WRITE`、`VM_EXEC`、`VM_SHARED` の４つのフラグの組み合わせ(全部で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 に定義されています。


```diff
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` の値を返します。


以上、３つのファイル(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_data` に `bool outer_shared` を追加します。

```diff
 /**
  * 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` をセットするコードを追加します。

```diff
 /**
  * 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` フラグを真にするコードを追加します。

```diff
     /*
      * 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](https://qiita.com/ikwzm/items/79170301e215b21b14aa) で用いたサンプルデザインとテストスクリプトを使います。詳細はそちらの記事を参照してください。

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](https://qiita.com/ikwzm/items/79170301e215b21b14aa) の「FPGA のコンフィギュレーション」参照。

FPGA のクロックの設定
-------------------------------------------------------------

[「UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (実践編1)」 @Qiita](https://qiita.com/ikwzm/items/79170301e215b21b14aa) の「FPGA のクロックの設定」参照。

Uio と Udmabuf の準備
-------------------------------------------------------------

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

```dts: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 が出来れば成功です。

```console
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 を使って合成した回路を動かす」](https://qiita.com/ikwzm/items/725ef2c4306e7bf021f0) で紹介した negative.py の udmabuf の sync_for_device()、sync_for_cpu() を使っているところをコメントアウトしたものです。negative.py の詳細な説明は記事を参照してください。

```python: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

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

```console
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 プロパティが追加されていないデバイスツリーです。
このデバイスツリーをオーバーレイして実行してみます。

```console
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 を実行すると次のように計算値と期待値が一致しません。

```console
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 のコミュニティは怖い雰囲気があります。


参考
-------------------------------------------------------------
 * [「Zynq UltraScale MPSoC Cache Coherency」@www.wiki.xilinx.com](http://www.wiki.xilinx.com/Zynq+UltraScale+MPSoC+Cache+Coherency)
 * [https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-3-UltraZed](https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-3-UltraZed)
 * [「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」 @Qiita](https://qiita.com/ikwzm/items/d5417b39c983f8e56af9)
 * [「UltraZed 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita](https://qiita.com/ikwzm/items/6e0506fc161d1da58e9c)
 * [「UltraZed 向け Debian GNU/Linux (v2018.2版) で Vivado-HLS を使って合成した回路を動かす」 @Qiita](https://qiita.com/ikwzm/items/725ef2c4306e7bf021f0)
 * [「UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (基礎編)」 @Qiita](https://qiita.com/ikwzm/items/82f98bfa76922073e610)
 * [「UltraZed 向け Debian GNU/Linux で AXI HPC port を使う (実践編1)」 @Qiita](https://qiita.com/ikwzm/items/79170301e215b21b14aa)