ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(GEM キャッシュ編)







# はじめに


[Lima] とは Mali-400/450 用のオープンソースなグラフィックドライバです。筆者は Lima を ZynqMP 向け Ubuntu 22.04 で試験的に動かしてみました。動かすのに少々苦労したので、その方法を何回かに分けて説明します。



  * [概要編]
  * [DRM Lima 編]
  * [DRI Lima 編]
  * [共有バッファ編]
  * [ストライド問題編]
  *  __GEM キャッシュ編(この記事)__  
  * [インストール編]
  * [glmark2編]



この記事では、Lima で3Dレンダリングした際に性能の低下の原因だった GEM のキャッシュ制御に関して説明します。




# 背景


Lima を動かすだけならば、この前の記事までで良かったのですが、残念ながら動いたというだけで、glmark2 のベンチマークの結果は芳しくありませんでした。具体的には次のようになりました。詳細は [glmark2編] を見てください。





<table border="2">
  <tr>
    <td align="center" rowspan="2">Machine</td>
    <td align="center" rowspan="2">Lima</td>
    <td align="center" colspan="2">glmark2 </td>
  </tr>
  <tr>
    <td align="center">Surface Size</td>
    <td align="center">Score</td>
  </tr>
  <tr>
    <td align="center" rowspan="2">Ultra96-V2</td>
    <td align="center">無</td>
    <td align="center" rowspan="2">800x600</td>
    <td align="right">21</td>
  </tr>
  <tr>
    <td align="center">有</td>
    <td align="right">21</td>
  </tr>
</table>



見ての通り、Lima を使ってレンダリングした場合(下段) と Lima を使わずに CPU でレンダリングした場合(上段)とで、スコアが同じくらいにしかなりませんでした。さすがにこれはおかしいと思って調べてみました。




# 原因


結果から先に述べると、速度低下の原因は Lima がレンダリングに使用する共有バッファの CPU キャッシュがオフになっていたためでした。この章では、そのメカニズムについて説明します。


## 共有バッファの仕組み


[DRI(Direct Rendering Infrastructure)] は単一のソフトウェアではなく、いくつもの DRI コンポーネントで構成されています。これらの DRI コンポーネント間で画像データをやりとりするための手段として、共有バッファが用いられます。しかもディスプレイドライバやレンダリングエンジンのようなハードウェアでも使うため、DRM(irect Rendering Manager - Linux カーネル部分の DRI コンポーネント) でも使える共有バッファが必要です。

GEM(Graphics Excution Manager) は DRM の一部であり、複数のアプリケーションの間でバッファを共有するための API を提供しています。


![Fig.1 GEM(Graphics Excution Manager)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/0eea0337-5093-b877-c22a-816da7d9e928.jpeg "Fig.1 GEM(Graphics Excution Manager)")

Fig.1 GEM(Graphics Excution Manager)

<br />




## DRI Lima のバッファは DRM Xlnx が用意する


X-server で libGL(Mesa Graphics Library) の DRI Lima がレンダリングを行う場合、GPU がレンダリングするためのバッファは表示側の DRM ドライバが用意するようです。具体的には表示側の DRM ドライバに対して ioctl() で DRM_IOCTL_MODE_CREATE_DUMB を指定することで Dumb バッファを確保します。したがって ZynqMP の場合、 GPU がレンダリングするためのバッファは DRM Xlnx が用意します。




## DRM Xlnx の Dumb バッファは CPU キャッシュがオフになっている


DRM Xlnx の GEM は Linux kernel が提供している drm_gem_cma_helper というメカニズムを使って GEM の管理をしています。実は、この drm_gem_cma_helper でバッファを確保したり mmap() すると、CPU がこのバッファにアクセスする際、 write combine でアクセスされます。

write combine とは、CPU がメモリに対して短時間に連続した書き込みを行った場合に、それらの複数の書き込みを一つにまとめて(combineして)しまうメカニズムです。したがって memcpy() とか memset() とかでシーケンシャルに連続して書き込みを行う場合は高速です。しかしながら、読み出しは CPU キャッシュを通さずに行われるため、とても低速です。

DRM Xlnx はもともと表示用のデバイスドライバです。表示用なのでフレームバッファに書き込まれたデータは DMA によって表示用コントローラーに転送されるのみで、CPU がフレームバッファを読み出すことはあまり想定されていないようです。ですから、write combine でも問題なかったのでしょう。

しかし、GPU がレンダリングした内容をディスプレイに表示するためには、バッファの内容を読み出して表示用のフレームバッファに転送する必要があります。この転送は CPU が行いますが、バッファが CPU キャッシュオフの状態なので、この転送がとても低速になるわけです。




# 解決方法


Lima の使用時に、より高速に描画するには Lima が描画するバッファの CPU キャッシュを有効にする必要があります。



Lima が描画するバッファの CPU キャッシュを有効にするには

  * DRM Xlnx の GEM を CPU キャッシュ対応にする
  * DDX Xlnx でDRM Xlnx の設定をする

ように修正することによって行います。この章では CPU キャッシュの変更方法と、具体的な修正内容を説明します。




## CPU キャッシュの変更方法


CPU キャッシュを有効にするか無効にするかは、DRM Xlnx GEM が Dumb バッファを生成する際に渡されるフラグによって判断します。したがって、Dumb バッファ毎に個別に設定することが出来ます。

ただし、DRI Lima が Dumb バッファを生成する場合は CPU キャッシュがオンになるように、DDX Xlnx が Dumb バッファを生成する場合は CPU キャッシュがオフになるようにしなければなりません。

そこで、Dumb バッファ生成時の引数フラグ args.flag とグローバル変数 xlnx_dumb_cache_default_mode の組み合わせて CPU キャッシュの有効/無効を決定します。なお、グローバル変数 xlnx_dumb_cache_default_mode の値は、Linux Kernel のビルド時の config 、DRM Xlnx のロード時のカーネルパラメーター、ioctl() による設定の３種類の方法で変更することができます。



<table border="2">
  <tr>
    <td align="center" colspan="2">args.flags</td>
    <td rowspan="2">xlnx_dumb_cache_default_mode</td>
    <td align="center" rowspan="2">Data Cache</td>
  </tr>
  <tr>
    <td align="center">[2]</td>
    <td align="center">[1]</td>
  <tr>
    <td align="center" rowspan="2">0</td>
    <td align="center" rowspan="2">0</td>
    <td align="center">0</td>
    <td align="center">Off</td>
  </tr>
  <tr>
    <td align="center">1</td>
    <td align="center">On</td>
  </tr>
  <tr>
    <td align="center">1</td>
    <td align="center">0</td>
    <td align="center">x</td>
    <td align="center">Off</td>
  </tr>
  <tr>
    <td align="center">1</td>
    <td align="center">1</td>
    <td align="center">x</td>
    <td align="center">On</td>
  </tr>
</table>

DRI Lima がDumb バッファを生成する時は args.flags=00 なので、この時に CPU キャッシュがオンになるためには、xlnx_dumb_cache_default_mode が1である必要があります。


## DRM Xlnx の GEM を CPU キャッシュ対応にする



### 　リポジトリ


この章で説明する内容は以下の github リポジトリに用意しています。ソースコード自体はこのリポジトリにはありませんが、パッチ用のファイルを以下 の URL に用意しています。



* https://github.com/ikwzm/ZynqMP-FPGA-Linux-5.10-Trial/tree/5.10.120-zynqmp-fpga-trial-16
  * files/linux-5.10.120-zynqmp-fpga-drm-xlnx-gem.diff




### 　driver/gpu/drm/xlnx/xlnx_gem.c


GEM を管理する xlnx_gem.c を drm_gem_cma_helper を使っていた簡便なものから、CPU キャッシュを有効にした新しいものに置き換えます。具体的には以下の関数と構造体を用意します。

* gem object の構造体
  * struct xlnx_gem_object
* gem object を生成する関数
  * xlnx_gem_dumb_create()
  * xlnx_gem_create_object()
  * xlnx_gem_create_with_handle()
  * \_\_xlnx_gem_create()
* gem object を削除する関数
  * xlnx_gem_free_object()
* mmap 関数
  * xlnx_gem_file_mmap()
  * \_\_xlnx_gem_mmap()
* PRIME DMA-BUFs サポート関数
  * xlnx_gem_prime_import_sg_table()
  * xlnx_gem_prime_import_sg_table_vmap()
  * xlnx_gem_prime_mmap()
  * xlnx_gem_prime_get_sg_table()
  * xlnx_gem_prime_vmap()
  * xlnx_gem_prime_vunmap()



上記の関数のうち、xlnx_gem_prime\_ がついている関数は、PRIME DMA-BUFs で使用する関数です。PRIME DMA-BUFs というのは Linux カーネル内部の DMA バッファの共有 API の略称のことです。PRIME DMA-BUFs は異なる種類のデバイスドライバが管理している複数のデバイス間で DMA バッファを共有するための一般的なメカニズムを提供します。


#### 　インクルードファイルたち



```C:driver/gpu/drm/xlnx/xlnx_gem.c
#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <drm/drm.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/xlnx_drm.h>
#include <drm/drm_vma_manager.h>
#include <drm/drm_file.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_gem.h>
#include "xlnx_drv.h"
#include "xlnx_gem.h"

```



#### 　struct xlnx_gem_object


struct xlnx_gem_object はDRM Xlnx GEMオブジェクト用の構造体です。

paddr は Linux Kernel が CMA 上に確保したバッファの物理アドレスを示します。

vaddr は Linux Kernel 上の仮想アドレスを示します。

size はCMA 上に確保したバッファの大きさを示します。アライメントの関係上、必ずしも要求されたサイズと同じサイズを示すわけではありません。

sgt はスキャッターギャザーテーブルです。これは DRM Xlnx GEM オブジェクトが xlnx_gem_prime_import_sg_table() によって外部に確保された PRIME DMA-BUF sオブジェクトを取り込んだ際に設定されます。

最後の cached フラグが、バッファを mmap() する際に CPU キャッシュを有効にするか無効にするかを指定します。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * struct xlnx_gem_object - Xilinx GEM object backed by CMA memory allocations
 * @base:      base GEM object
 * @paddr:     physical address of the backing memory
 * @vaddr:     kernel virtual address of the backing memory
 * @size:      size of the backing memory
 * @sgt:       scatter/gather table for imported PRIME buffers. 
 *             The table can have more than one entry but they are guaranteed to 
 *             have contiguous DMA addresses.
 * @cached:    map object cached (instead of using writecombine).
 */
struct xlnx_gem_object {
	struct drm_gem_object   base;
	dma_addr_t              paddr;
	void*                   vaddr;
	size_t                  size;
	struct sg_table*        sgt;
	bool                    cached;
};
#define to_xlnx_gem_object(gem_obj) \
	container_of(gem_obj, struct xlnx_gem_object, base)

```



#### 　xlnx_gem_dumb_create()


xlnx_gem_dumb_create() は Dumb バッファを生成する関数です。この関数はユーザー空間から ioctl() で DRM_IOCTL_MODE_CREATE_DUMB が指定された時に呼び出されます。この関数でパラメータをいろいろとチェックして、後述のxlnx_gem_create_with_handle() を呼んで DRM Xlnx GEM オブジェクトを生成してバッファを確保します。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/*
 * xlnx_gem_dumb_create - (struct drm_driver)->dumb_create callback
 * @file_priv: drm_file object
 * @drm: DRM object
 * @args: info for dumb buffer creation
 *
 * This function is for dumb_create callback of drm_driver struct. Simply
 * it wraps around drm_gem_cma_dumb_create() and sets the pitch value
 * by retrieving the value from the device.
 *
 * Return: The return value from drm_gem_cacma_create_with_handle()
 */

int
xlnx_gem_dumb_create(struct drm_file *file_priv,
                     struct drm_device *drm,
                     struct drm_mode_create_dumb *args)
{
	bool scanout = (((args->flags) & DRM_XLNX_GEM_DUMB_SCANOUT_MASK) == DRM_XLNX_GEM_DUMB_SCANOUT);
	bool cache;
	int  align     = xlnx_get_align(drm, scanout);
	int  min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
	struct drm_gem_object* gem_obj;
	if (!args->pitch || !IS_ALIGNED(args->pitch, align))
		args->pitch = ALIGN(min_pitch, align);
	if (args->pitch < min_pitch)
		args->pitch = min_pitch;
	if (args->size < args->pitch * args->height)
		args->size = args->pitch * args->height;

	switch ((args->flags) & DRM_XLNX_GEM_DUMB_CACHE_MASK) {
	case DRM_XLNX_GEM_DUMB_CACHE_OFF:
		cache = false;
		break;
	case DRM_XLNX_GEM_DUMB_CACHE_ON:
		cache = true;
		break;
	case DRM_XLNX_GEM_DUMB_CACHE_DEFAULT:
		cache = (xlnx_get_dumb_cache_default_mode(drm) != 0);
		break;
	default:
		cache = false;
		break;
	}
	DRM_DEBUG("width=%d, height=%d, bpp=%d, pitch=%d, align=%d, cache=%d\n",
                  args->width, args->height, args->bpp, args->pitch, align, cache);
	gem_obj = xlnx_gem_create_with_handle(file_priv, drm, args->size, cache, &args->handle);
	return PTR_ERR_OR_ZERO(gem_obj);
}
```


CPU キャッシュを有効にするか否かは、args で指定されたフラグによって指定します。CPU キャッシュに関するフラグは include/uapi/drm/xlnx_drm.h にて指定されています。

DRM_XLNX_GEM_DUMB_CACHE_DEFAULT(00) の場合は xlnx_get_dumb_cache_default_mode() で指定されたキャッシュモードに設定されます。xlnx_get_dumb_cache_default_mode() は、xlnx_drv.c で定義されているカーネルパラメータ xlnx_dumb_cache_default_mode の値を返します。

DRM_XLNX_GEM_DUMB_CACHE_OFF(10)の場合は、強制的にCPU キャッシュをオフにします。

DRM_XLNX_GEM_DUMB_CACHE_ON(11) の場合は、強制的に CPU キャッシュをオンにします。


#### 　xlnx_gem_create_object()


xlnx_gem_create_object() は DRM Xlnx GEM オブジェクトを生成して、バッファを確保します。バッファを確保する際、cache フラグによって dma_alloc_coherent() か dma_alloc_wc() かを使い分けています。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_create_object - Create a Xilinx GEM object with allocating memory
 * @drm:       DRM device
 * @size:      size of the object to allocate
 * @cache:     cache mode
 *
 * This function creates a Xilinx GEM object and allocates a contiguous chunk of
 * memory as backing store. The backing memory has the writecombine attribute
 * set.
 *
 * Returns:
 * A struct drm_gem_object * on success or an ERR_PTR()-encoded negative
 * error code on failure.
 */
struct drm_gem_object*
xlnx_gem_create_object(struct drm_device *drm, size_t size, bool cache)
{
	struct xlnx_gem_object* xlnx_obj;
	struct drm_gem_object*  gem_obj;
	void*                   vaddr;
	dma_addr_t              paddr;
	int                     ret;
	size = round_up(size, PAGE_SIZE);
	xlnx_obj = __xlnx_gem_create(drm, size);
	if (IS_ERR(xlnx_obj))
		return ERR_CAST(xlnx_obj);
	gem_obj = &xlnx_obj->base;
	if (cache)
		vaddr = dma_alloc_coherent(drm->dev, size, &paddr, GFP_KERNEL | __GFP_NOWARN);
	else
		vaddr = dma_alloc_wc(      drm->dev, size, &paddr, GFP_KERNEL | __GFP_NOWARN);
	
	if (IS_ERR_OR_NULL(vaddr)) {
		ret = (IS_ERR(vaddr)) ? PTR_ERR(vaddr) : -ENOMEM;
		DRM_ERROR("failed to allocate buffer with size=%zu, return=%d", size, ret);
		goto error;
	}
	xlnx_obj->paddr  = paddr;
	xlnx_obj->vaddr  = vaddr;
	xlnx_obj->size   = size;
	xlnx_obj->cached = cache;
	return gem_obj;
error:
	drm_gem_object_put(gem_obj);
	return ERR_PTR(ret);
}

```



#### 　xlnx_gem_create_with_handle()


xlnx_gem_create_with_handle() は DRM Xlnx GEM オブジェクトを生成して、バッファを確保します。そして同時にDRM ハンドルを生成して GEM オブジェクトと紐づけします。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_create_with_handle - allocate an object with the given size and
 *     return a GEM handle to it
 * @file_priv: DRM file-private structure to register the handle for
 * @drm:       DRM device
 * @size:      size of the object to allocate
 * @cache:     cache mode
 * @handle:    return location for the GEM handle
 *
 * This function creates a GEM object, allocating a physically contiguous
 * chunk of memory as backing store. The GEM object is then added to the list
 * of object associated with the given file and a handle to it is returned.
 *
 * Returns:
 * A struct drm_gem_object * on success or an ERR_PTR()-encoded negative
 * error code on failure.
 */
struct drm_gem_object*
xlnx_gem_create_with_handle(struct drm_file *file_priv,
			    struct drm_device *drm, size_t size, bool cache, 
			    uint32_t *handle)
{
	struct drm_gem_object*  gem_obj;
	int ret;
	gem_obj = xlnx_gem_create_object(drm, size, cache);
	if (IS_ERR(gem_obj))
		return ERR_CAST(gem_obj);
	/*
	 * allocate a id of idr table where the obj is registered
	 * and handle has the id what user can see.
	 */
	ret = drm_gem_handle_create(file_priv, gem_obj, handle);
	/* drop reference from allocate - handle holds it now. */
	drm_gem_object_put(gem_obj);
	if (ret) {
		DRM_ERROR("failed to create gem handle, return=%d", ret);
		return ERR_PTR(ret);
	}
	return gem_obj;
}
```



#### 　\_\_xlnx_gem_create()


\_\_xlnx_gem_create() は、struct xlnx_gem_object を生成する関数です。ただし、struct xlnx_gem_object のインスタンスを生成するだけで、バッファの確保は行いません。

\_\_xlnx_gem_create() は、 xlnx_gem_create_object()、xlnx_gem_create_with_handle()、 xlnx_gem_prime_import_sg_table()、xlnx_gem_prime_import_sg_table_vmap() で使用します。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * __xlnx_gem_create() - Create a Xilinx GEM object without allocating memory
 * @drm:       DRM device
 * @size:      size of the object to allocate
 *
 * This function creates and initializes a Xilinx GEM object of the given size,
 * but doesn't allocate any memory to back the object.
 *
 * Returns:
 * A struct xlnx_gem_object * on success or an ERR_PTR()-encoded negative
 * error code on failure.
 */

static
struct xlnx_gem_object*
__xlnx_gem_create(struct drm_device *drm, size_t size)
{
	struct xlnx_gem_object* xlnx_obj;
	struct drm_gem_object*  gem_obj;
	int ret;
	if (drm->driver->gem_create_object)
		gem_obj = drm->driver->gem_create_object(drm, size);
	else
		gem_obj = kzalloc(sizeof(*xlnx_obj), GFP_KERNEL);
	if (!gem_obj) {
		ret = -ENOMEM;
		DRM_DEV_ERROR(drm->dev, "failed to allocate gem object, return=%d", ret);
		goto error;
	}
	ret = drm_gem_object_init(drm, gem_obj, size);
	if (ret) {
		DRM_DEV_ERROR(drm->dev, "failed to initialize gem object, return=%d", ret);
		goto error;
	}
	ret = drm_gem_create_mmap_offset(gem_obj);
	if (ret) {
		DRM_DEV_ERROR(drm->dev, "failed to create mmap offset, return=%d", ret);
		drm_gem_object_release(gem_obj);
		goto error;
	}
	xlnx_obj = to_xlnx_gem_object(gem_obj);
	xlnx_obj->paddr  = (dma_addr_t)NULL;
	xlnx_obj->vaddr  = NULL;
	xlnx_obj->size   = 0;
	xlnx_obj->cached = 0;
	
	return xlnx_obj;
error:
	if (gem_obj)
		kfree(gem_obj);
	return ERR_PTR(ret);
}
```



#### 　xlnx_gem_free_object()


確保したバッファを開放して DRM Xlnx GEM オブジェクトを削除します。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_free_object() - free resources associated with a Xilinx GEM object
 * @gem_obj:   GEM object to free
 *
 * This function frees the backing memory of the Xilinx GEM object, cleans up 
 * the GEM object state and frees the memory used to store the object itself.
 * If the buffer is imported and the virtual address is set, it is released.
 */
void xlnx_gem_free_object(struct drm_gem_object *gem_obj)
{
	struct xlnx_gem_object* xlnx_obj;
	xlnx_obj = to_xlnx_gem_object(gem_obj);
	if (gem_obj->import_attach) {
		if (xlnx_obj->vaddr) {
			dma_buf_vunmap(gem_obj->import_attach->dmabuf, xlnx_obj->vaddr);
			xlnx_obj->vaddr = NULL;
		}
		drm_prime_gem_destroy(gem_obj, xlnx_obj->sgt);
	} else {
		if (xlnx_obj->vaddr) {
			struct device* dev   = gem_obj->dev->dev;
			size_t         size  = xlnx_obj->size;
			void*          vaddr = xlnx_obj->vaddr;
			dma_addr_t     paddr = xlnx_obj->paddr;
			if (xlnx_obj->cached)
				dma_free_coherent(dev, size, vaddr, paddr);
			else
				dma_free_wc(      dev, size, vaddr, paddr);
			xlnx_obj->vaddr = NULL;
		}
	}
	drm_gem_object_release(gem_obj);
	kfree(xlnx_obj);
}

```



#### 　xlnx_gem_file_mmap()


xlnx_gem_file_mmap() は DRM のファイルハンドルに対して mmap() がコールされた時にコールされる関数です。一つの DRM ファイルハンドルに対して複数の GEM オブジェクトが生成され、それら GEM オブジェクトの切り分けは mmap() に渡されるオフセットによって行われます。xlnx_gem_file_mmap() では、まず drm_gem_mmap() を実行することよってどの GEM オブジェクトに対して行われる mmap() なのかを判別してから、\_\_xlnx_gem_mmap() を実行しています。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_file_mmap() - memory-map a Xilinx GEM object by file operation.
 * @filp:      file object
 * @vma:       VMA for the area to be mapped
 *
 * Returns:    0 on success or a negative error code on failure.
 */
int xlnx_gem_file_mmap(struct file *filp, struct vm_area_struct *vma)
{
	struct drm_gem_object  *gem_obj;
	int ret;
	ret = drm_gem_mmap(filp, vma);
	if (ret)
		return ret;
	gem_obj  = vma->vm_private_data;
	return __xlnx_gem_mmap(gem_obj, vma);
}
```



#### 　\_\_xlnx_gem_mmap()


\_\_xlnx_gem_mmap() は前述した xlnx_gem_file_mmap() と xlnx_gem_prime_mmap() の共通部分です。この \_\_xlnx_gem_mmap() が CPU キャッシュを有効にしている部分です。



ここではオンデマンドページングという(裏技的な？)技法を使って CPU キャッシュを有効にしています。オンデマンドページングとは、mmap() した直後は仮想アドレスと物理アドレスを紐づけせずに、CPU がその仮想アドレスにアクセスした際にページフォルトを起こさせて改めて物理アドレスを割り当てる方法です。



オンデマンドページングを行うには、まず、MMU がページフォルトした際に物理アドレスと仮想アドレスを結びつけるためのコールバック関数 xlnx_gem_vm_fault() を用意します。この関数は、各種情報からページフレーム番号を算出して、vmf_insert_pfn() を呼び出します。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_vm_fault() - Xilinx GEM object vm area fault operation.
 * @vfm:       Pointer to the vm fault structure.
 * Return:     VM_FAULT_RETURN_TYPE (Success(=0) or error status(!=0)).
 */
static
vm_fault_t xlnx_gem_vm_fault(struct vm_fault *vmf)
{
	struct vm_area_struct*  vma      = vmf->vma;
	struct drm_gem_object*  gem_obj  = vma->vm_private_data;
	struct xlnx_gem_object* xlnx_obj = to_xlnx_gem_object(gem_obj);
	unsigned long offset             = vmf->address - vma->vm_start;
	unsigned long virt_addr          = vmf->address;
	unsigned long phys_addr          = xlnx_obj->paddr + offset;
	unsigned long page_frame_num     = phys_addr  >> PAGE_SHIFT;
	unsigned long request_size       = 1          << PAGE_SHIFT;
	unsigned long available_size     = xlnx_obj->size - offset;
	if (request_size > available_size)
	        return VM_FAULT_SIGBUS;
	if (!pfn_valid(page_frame_num))
	        return VM_FAULT_SIGBUS;
	return vmf_insert_pfn(vma, virt_addr, page_frame_num);
}
```


次に mmap() の際に設定する vm operation table を定義します。この fault フィールドには前出の xlnx_gem_vm_fault() を設定します。open フィールドと close フィールドには DRM 共通で定義されている drm_gem_vm_open() と drm_gem_vm_close() を設定します。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * Xilinx GEM object vm operation table.
 */
const struct vm_operations_struct xlnx_gem_vm_ops = {
	.fault = xlnx_gem_vm_fault,
	.open  = drm_gem_vm_open,
	.close = drm_gem_vm_close,
};
```


\_\_xlnx_gem_mmap() で vma の vm_ops に前述の xlnx_gem_vm_ops を設定して戻ります。また、CPU キャッシュを有効にしない場合(!xlnx_obj->cached) は vm_page_prot に write combine を設定します。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * __xlnx_gem_mmap() - memory-map a Xilinx GEM object
 * @gem_obj:   GEM object
 * @vma:       VMA for the area to be mapped
 *
 * Returns:    0 on success.
 */
static
int __xlnx_gem_mmap(struct drm_gem_object *gem_obj, struct vm_area_struct *vma)
{
	struct xlnx_gem_object *xlnx_obj = to_xlnx_gem_object(gem_obj);
	if (gem_obj->import_attach) {
		/* Drop the reference drm_gem_mmap_obj() acquired.*/
		drm_gem_object_put(gem_obj);
		vma->vm_private_data = NULL;
		return dma_buf_mmap(gem_obj->dma_buf, vma, 0);
	}
	
	vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
	if (!xlnx_obj->cached)
 		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
	vma->vm_ops = &xlnx_gem_vm_ops;
	return 0;
}
```



#### 　xlnx_gem_prime_import_sg_table()


xlnx_gem_prime_import_sg_table() は、PRIME DMA-BUFs で外部から指定された DMA バッファを自分の GEM オブジェクトとして生成する際に呼ばれる関数です。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_prime_import_sg_table() - produce a GEM object from another
 *     driver's scatter/gather table of pinned pages
 * @dev:       device to import into
 * @attach:    DMA-BUF attachment
 * @sgt:       scatter/gather table of pinned pages
 *
 * This function imports a scatter/gather table exported via DMA-BUF by
 * another driver. Imported buffers must be physically contiguous in memory
 * (i.e. the scatter/gather table must contain a single entry). Drivers that
 * use the CMA helpers should set this as their
 * &drm_driver.gem_prime_import_sg_table callback.
 *
 * Returns:
 * A pointer to a newly created GEM object or an ERR_PTR-encoded negative
 * error code on failure.
 */
struct drm_gem_object*
xlnx_gem_prime_import_sg_table(struct drm_device *dev,
			       struct dma_buf_attachment *attach,
			       struct sg_table *sgt)
{
	struct xlnx_gem_object *xlnx_obj;
	/* check if the entries in the sg_table are contiguous */
	if (drm_prime_get_contiguous_size(sgt) < attach->dmabuf->size) {
		DRM_ERROR("buffer chunks must be mapped contiguously");
		return ERR_PTR(-EINVAL);
	}
	/* Create a Xilinx GEM Object */
	xlnx_obj = __xlnx_gem_create(dev, attach->dmabuf->size);
	if (IS_ERR(xlnx_obj))
		return ERR_CAST(xlnx_obj);
	xlnx_obj->paddr = sg_dma_address(sgt->sgl);
	xlnx_obj->sgt   = sgt;
	return &xlnx_obj->base;
}

```



#### 　xlnx_gem_prime_import_sg_table_vmap()


xlnx_gem_prime_import_sg_table_vmap() は、PRIME DMA-BUFs で外部から指定された DMA バッファを自分の GEM オブジェクトとして生成する際に呼ばれる関数です。この関数では同時に vmap() を実行して仮想アドレスの割り当ても行います。


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_prime_import_sg_table_vmap() - PRIME import another driver's
 *	scatter/gather table and get the virtual address of the buffer
 * @dev:       device to import into
 * @attach:    DMA-BUF attachment
 * @sgt:       Scatter/gather table of pinned pages
 *
 * This function imports a scatter/gather table using
 * xlnx_gem_prime_import_sg_table() and uses dma_buf_vmap() to get the kernel
 * virtual address. This ensures that a GEM object always has its virtual
 * address set. This address is released when the object is freed.
 *
 * Returns:
 * A pointer to a newly created GEM object or an ERR_PTR-encoded negative
 * error code on failure.
 */
struct drm_gem_object*
xlnx_gem_prime_import_sg_table_vmap(struct drm_device *dev,
				    struct dma_buf_attachment *attach,
				    struct sg_table *sgt)
{
	struct xlnx_gem_object* xlnx_obj;
	struct drm_gem_object*  gem_obj;
	void *vaddr;
	vaddr = dma_buf_vmap(attach->dmabuf);
	if (!vaddr) {
		DRM_ERROR("Failed to vmap PRIME buffer");
		return ERR_PTR(-ENOMEM);
	}
	gem_obj = xlnx_gem_prime_import_sg_table(dev, attach, sgt);
	if (IS_ERR(gem_obj)) {
		dma_buf_vunmap(attach->dmabuf, vaddr);
		return gem_obj;
	}
	xlnx_obj = to_xlnx_gem_object(gem_obj);
	xlnx_obj->vaddr = vaddr;
	return gem_obj;
}
```



#### 　xlnx_gem_prime_mmap()


xlnx_gem_prime_mmap() は、PRIME DMA-BUFs で mmap() する際に呼ばれる関数です。 


```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_prime_mmap() - memory-map a Xilinx GEM object by prime operation.
 * @gem_obj:   GEM object
 * @vma:       VMA for the area to be mapped
 *
 * Returns:    0 on success or a negative error code on failure.
 */
int xlnx_gem_prime_mmap(struct drm_gem_object *gem_obj, struct vm_area_struct *vma)
{
	int ret;
	ret = drm_gem_mmap_obj(gem_obj, gem_obj->size, vma);
	if (ret < 0)
		return ret;
	return __xlnx_gem_mmap(gem_obj, vma);
}

```



#### 　xlnx_gem_prime_get_sg_table()



```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_prime_get_sg_table() - provide a scatter/gather table of pinned
 *     pages for a Xilinx GEM object
 * @gem_obj:   GEM object
 *
 * This function exports a scatter/gather table suitable for PRIME usage by
 * calling the standard DMA mapping API. 
 *
 * Returns:
 * A pointer to the scatter/gather table of pinned pages or NULL on failure.
 */
struct sg_table *xlnx_gem_prime_get_sg_table(struct drm_gem_object *gem_obj)
{
	struct xlnx_gem_object* xlnx_obj = to_xlnx_gem_object(gem_obj);
	struct sg_table*        sgt;
	int ret;
	sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
	if (!sgt)
		return ERR_PTR(-ENOMEM);
	ret = dma_get_sgtable(
		gem_obj->dev->dev, /* struct device*   dev      */
		sgt              , /* struct sg_table* sgt      */
		xlnx_obj->vaddr  , /* void*            cpu_addr */
		xlnx_obj->paddr  , /* dma_addr_t       dma_addr */
		gem_obj->size      /* size_t           size     */
	      );
	if (ret < 0) {
		DRM_ERROR("failed to get sgtable, return=%d", ret);
		goto out;
	}
	return sgt;
out:
	kfree(sgt);
	return ERR_PTR(ret);
}

```



#### 　xlnx_gem_prime_vmap()



```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_prime_vmap() - map a Xilinx GEM object into the kernel's virtual
 *     address space
 * @gem_obj:   GEM object
 *
 * This function maps a buffer exported via DRM PRIME into the kernel's
 * virtual address space. Since the CMA buffers are already mapped into the
 * kernel virtual address space this simply returns the cached virtual
 * address. 
 *
 * Returns:
 * The kernel virtual address of the Xilinx GEM object's backing store.
 */
void* xlnx_gem_prime_vmap(struct drm_gem_object *gem_obj)
{
	struct xlnx_gem_object* xlnx_obj = to_xlnx_gem_object(gem_obj);
	if ((xlnx_obj->vaddr == NULL) && (gem_obj->import_attach)) {
		void* vaddr = dma_buf_vmap(gem_obj->import_attach->dmabuf);
 		if (!vaddr) 
 			DRM_ERROR("Failed to vmap PRIME buffer");
		xlnx_obj->vaddr = vaddr;
	}
	return xlnx_obj->vaddr;
}
```



#### 　xlnx_gem_prime_vunmap()



```C:driver/gpu/drm/xlnx/xlnx_gem.c
/**
 * xlnx_gem_prime_vunmap() - unmap a Xilinx GEM object from the kernel's virtual
 *     address space
 * @gem_obj:   GEM object
 * @vaddr:     kernel virtual address where the GEM object was mapped
 *
 * This function removes a buffer exported via DRM PRIME from the kernel's
 * virtual address space. This is a no-op because CMA buffers cannot be
 * unmapped from kernel space. 
 */
void xlnx_gem_prime_vunmap(struct drm_gem_object *gem_obj, void *vaddr)
{
	/* Nothing to do */
}
```



### 　drivers/gpu/drm/xlnx/xlnx_gem.h


このヘッダファイルは前述の xlnx_gem.c で定義したグローバルな関数のプロトタイプを宣言しています。


```C:driver/gpu/drm/xlnx/xlnx_gem.h
#ifndef _XLNX_GEM_H_
#define _XLNX_GEM_H_
struct dma_buf_attachment;
struct drm_printer;
struct sg_table;
int   xlnx_gem_dumb_create(struct drm_file *file_priv,
                           struct drm_device *drm,
                           struct drm_mode_create_dumb *args);
struct drm_gem_object* xlnx_gem_create_object(struct drm_device *drm, size_t size, bool cache);
struct drm_gem_object* xlnx_gem_create_with_handle(struct drm_file *file_priv,
                                              struct drm_device *drm, size_t size, bool cache,
                                              uint32_t *handle);
struct drm_gem_object* xlnx_gem_prime_import_sg_table(struct drm_device *dev,
                                              struct dma_buf_attachment *attach,
                                              struct sg_table *sgt);
struct drm_gem_object* xlnx_gem_prime_import_sg_table_vmap(struct drm_device *dev,
                                              struct dma_buf_attachment *attach,
                                              struct sg_table *sgt);
void  xlnx_gem_free_object(struct drm_gem_object *gem_obj);
struct sg_table* xlnx_gem_prime_get_sg_table(struct drm_gem_object *gem_obj);
int   xlnx_gem_file_mmap(struct file *filp, struct vm_area_struct *vma);
int   xlnx_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);
void* xlnx_gem_prime_vmap(struct drm_gem_object *gem_obj);
void  xlnx_gem_prime_vunmap(struct drm_gem_object *gem_obj, void *vaddr);
void  xlnx_gem_print_info(struct drm_printer *p,
                          unsigned int indent,
                          const struct drm_gem_object *obj);
#endif /* _XLNX_GEM_H_ */

```



### 　include/uapi/drm/xlnx_drm.h


このヘッダファイルは、ioctl() によって指定するパラメータ等の値を定義しています。


```C:include/uapi/drm/xlnx_drm.h
/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
/* xrt_drm.h 
 *
 * Xilinx DRM KMS Driver Public Header
 *
 * Copyright (c) 2022 ikwzm <ichiro_k@ca2.so-net.ne.jp> 
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */
#ifndef __XLNX_DRM_H__
#define __XLNX_DRM_H__
#include "drm.h"
#if defined(__cplusplus)
extern "C" {
#endif

/**
 * Parameter identifier of various information
 */	
enum drm_xlnx_param {
	DRM_XLNX_PARAM_DRIVER_IDENTIFIER       = 0,
	DRM_XLNX_PARAM_SCANOUT_ALIGNMENT_SIZE  = 1,
	DRM_XLNX_PARAM_DUMB_ALIGNMENT_SIZE     = 2,
	DRM_XLNX_PARAM_DUMB_CACHE_AVALABLE     = 3,
	DRM_XLNX_PARAM_DUMB_CACHE_DEFAULT_MODE = 4,
};

/**
 * Get various information of the Xilinx DRM KMS Driver
 */
struct drm_xlnx_get_param {
	__u32 param; /* in , value in enum drm_xlnx_param */
	__u32 pad;   /* pad, must be zero */
	__u64 value; /* out, parameter value */
};

/**
 * Set various information of the Xilinx DRM KMS Driver
 */
struct drm_xlnx_set_param {
	__u32 param; /* in , value in enum drm_xlnx_param */
	__u32 pad;   /* pad, must be zero */
	__u64 value; /* in , parameter value */
};

/**
 * Xilinx DRM KMS Driver specific ioctls.
 */
#define DRM_XLNX_GET_PARAM   0x00
#define DRM_XLNX_SET_PARAM   0x01
#define DRM_IOCTL_XLNX_GET_PARAM DRM_IOWR(DRM_COMMAND_BASE+DRM_XLNX_GET_PARAM, struct drm_xlnx_get_param)
#define DRM_IOCTL_XLNX_SET_PARAM DRM_IOWR(DRM_COMMAND_BASE+DRM_XLNX_SET_PARAM, struct drm_xlnx_set_param)

/**
 * Xilinx DRM KMS Driver Identifier
 */
#define DRM_XLNX_DRIVER_IDENTIFIER      (0x53620C75)

/**
 * Xilinx DRM KMS Driver Create Dumb Flags
 */
#define DRM_XLNX_GEM_DUMB_SCANOUT_BIT   (0)
#define DRM_XLNX_GEM_DUMB_SCANOUT_MASK  (1 << DRM_XLNX_GEM_DUMB_SCANOUT_BIT)
#define DRM_XLNX_GEM_DUMB_SCANOUT       (1 << DRM_XLNX_GEM_DUMB_SCANOUT_BIT)
#define DRM_XLNX_GEM_DUMB_NON_SCANOUT   (0 << DRM_XLNX_GEM_DUMB_SCANOUT_BIT)
	
#define DRM_XLNX_GEM_DUMB_CACHE_BIT     (1)
#define DRM_XLNX_GEM_DUMB_CACHE_MASK    (3 << DRM_XLNX_GEM_DUMB_CACHE_BIT)
#define DRM_XLNX_GEM_DUMB_CACHE_DEFAULT (0 << DRM_XLNX_GEM_DUMB_CACHE_BIT)
#define DRM_XLNX_GEM_DUMB_CACHE_OFF     (2 << DRM_XLNX_GEM_DUMB_CACHE_BIT)
#define DRM_XLNX_GEM_DUMB_CACHE_ON      (3 << DRM_XLNX_GEM_DUMB_CACHE_BIT)
	
#if defined(__cplusplus)
}
#endif
#endif /* __XLNX_DRM_H__ */

```





### 　drivers/gpu/drm/xlnx/xlnx_drv.c


xlnx_drv.c には、グローバル変数 xlnx_dumb_cache_default_mode を追加すると同時に、 drm_gem_cma_helper の代わりに xlnx_gem 独自関数を使うようにします。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
@@ -23,9 +23,11 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_fb_helper.h>
-#include <drm/drm_gem_cma_helper.h>
 #include <drm/drm_of.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_file.h>
+#include <drm/drm_gem.h>
 #include <drm/xlnx_drm.h>
 
 #include <linux/component.h>

```


グローバル変数 xlnx_dumb_alignment_size を追加します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
+#ifdef CONFIG_DRM_XLNX_DUMB_ALIGNMENT_DEFAULT_SIZE
+static uint       xlnx_dumb_alignment_size = CONFIG_DRM_XLNX_DUMB_ALIGNMENT_DEFAULT_SIZE;
+#else
+static uint       xlnx_dumb_alignment_size = 0;
+#endif
+module_param_named(dumb_alignment_size, xlnx_dumb_alignment_size, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(  dumb_alignment_size, "xlnx dumb buffer alignment size");
+

```


グローバル変数 xlnx_dumb_cache_default_mode を追加します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
+#ifdef CONFIG_DRM_XLNX_DUMB_CACHE_DEFAULT_MODE
+static uint       xlnx_dumb_cache_default_mode = CONFIG_DRM_XLNX_DUMB_CACHE_DEFAULT_MODE;
+#else
+static uint       xlnx_dumb_cache_default_mode = 0;
+#endif
+module_param_named(dumb_cache_default_mode, xlnx_dumb_cache_default_mode, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(  dumb_cache_default_mode, "xlnx dumb buffer cache default mode on=1/off=0");
+
```


xlnx_gem.c の xlnx_gem_dumb_create() から呼ばれる xlnx_get_align() を xlnx_dumb_alignment_size を見るように変更します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
 /**
  * xlnx_get_align - Return the align requirement through CRTC helper
  * @drm: DRM device
+ * @scanout: SCANOUT 
  *
  * Return: the alignment requirement
  */
-unsigned int xlnx_get_align(struct drm_device *drm)
+unsigned int xlnx_get_align(struct drm_device *drm, bool scanout)
 {
 	struct xlnx_drm *xlnx_drm = drm->dev_private;
 
-	return xlnx_crtc_helper_get_align(xlnx_drm->crtc);
+	if (scanout || xlnx_dumb_alignment_size == 0)
+		return xlnx_crtc_helper_get_align(xlnx_drm->crtc);
+	else
+		return xlnx_dumb_alignment_size;
+}
```


xlnx_gem.c の xlnx_gem_dumb_create() から呼ばれる xlnx_get_dumb_cache_default_mode() を追加します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
+/**
+ * xlnx_get_dumb_cache_default_mode - Return the xlnx_dumb_cache_default_mode
+ * @drm: DRM device
+ * @scanout: SCANOUT 
+ *
+ * Return: the alignment requirement
+ */
+unsigned int xlnx_get_dumb_cache_default_mode(struct drm_device *drm)
+{
+	return xlnx_dumb_cache_default_mode;
+}

```


ioctl() で各種設定を得ることが出来るように xlnx_ioctl_get_param() を追加します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
+static int xlnx_ioctl_get_param(struct drm_device *drm, void *data, struct drm_file *file)
+{
+	struct xlnx_drm *xlnx_drm = drm->dev_private;
+	struct drm_xlnx_get_param *args = data;
+
+	if (args->pad)
+		return -EINVAL;
+
+	switch (args->param) {
+	case DRM_XLNX_PARAM_DRIVER_IDENTIFIER:
+		args->value = (__u64)DRM_XLNX_DRIVER_IDENTIFIER;
+		break;
+	case DRM_XLNX_PARAM_SCANOUT_ALIGNMENT_SIZE:
+		args->value = (__u64)xlnx_crtc_helper_get_align(xlnx_drm->crtc);
+		break;
+	case DRM_XLNX_PARAM_DUMB_ALIGNMENT_SIZE:
+		args->value = (__u64)xlnx_dumb_alignment_size;
+		break;
+	case DRM_XLNX_PARAM_DUMB_CACHE_AVALABLE:
+		args->value = 1;
+		break;
+	case DRM_XLNX_PARAM_DUMB_CACHE_DEFAULT_MODE:
+		args->value = (__u64)xlnx_dumb_cache_default_mode;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
```


ioctl() で各種パラメータを設定できるように xlnx_ioctl_set_param() を追加します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
+static int xlnx_ioctl_set_param(struct drm_device *drm, void *data, struct drm_file *file)
+{
+	struct drm_xlnx_set_param *args = data;
+
+	if (args->pad)
+		return -EINVAL;
+
+	switch (args->param) {
+	case DRM_XLNX_PARAM_DUMB_ALIGNMENT_SIZE:
+		xlnx_dumb_alignment_size = (uint)args->value;
+		break;
+	case DRM_XLNX_PARAM_DUMB_CACHE_DEFAULT_MODE:
+		xlnx_dumb_cache_default_mode = (uint)args->value;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
```


ioctl() に DRM_XLNX_GET_PARAM  が指定された場合は xlnx_ioctl_get_param() が呼び出されるように、また、 DRM_XLNX_SET_PARAM が指定された場合は xlnx_ioctl_set_param() が呼び出されるように xlnx_drm_driver_ioctls という関数テーブルを用意します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
+static const struct drm_ioctl_desc xlnx_drm_driver_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(XLNX_GET_PARAM, xlnx_ioctl_get_param, DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(XLNX_SET_PARAM, xlnx_ioctl_set_param, DRM_RENDER_ALLOW),
+};
```


file_operations の mmap フィールドを drm_gem_cma_mmap() から xlnx_gem_file_mmap() に変更します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
@@ -183,7 +275,7 @@ static const struct file_operations xlnx_fops = {
 	.open		= drm_open,
 	.release	= xlnx_drm_release,
 	.unlocked_ioctl	= drm_ioctl,
-	.mmap		= drm_gem_cma_mmap,
+	.mmap		= xlnx_gem_file_mmap,
 	.poll		= drm_poll,
 	.read		= drm_read,
 #ifdef CONFIG_COMPAT

```


xlnx_drm_driver の各種フィールドを drm_gem_cma_helper のものから xlnx_gem 独自のものに変更します。また、ioctls フィールドには xlnx_drm_drivers_ioctls を指定します。


```diff:drivers/gpu/drm/xlnx/xlnx_drv.c
@@ -202,15 +296,17 @@ static struct drm_driver xlnx_drm_driver = {
 	.prime_fd_to_handle		= drm_gem_prime_fd_to_handle,
 	.gem_prime_export		= drm_gem_prime_export,
 	.gem_prime_import		= drm_gem_prime_import,
-	.gem_prime_get_sg_table		= drm_gem_cma_prime_get_sg_table,
-	.gem_prime_import_sg_table	= drm_gem_cma_prime_import_sg_table,
-	.gem_prime_vmap			= drm_gem_cma_prime_vmap,
-	.gem_prime_vunmap		= drm_gem_cma_prime_vunmap,
-	.gem_prime_mmap			= drm_gem_cma_prime_mmap,
-	.gem_vm_ops			= &drm_gem_cma_vm_ops,
-	.gem_free_object_unlocked	= drm_gem_cma_free_object,
-	.dumb_create			= xlnx_gem_cma_dumb_create,
+	.gem_prime_get_sg_table		= xlnx_gem_prime_get_sg_table,
+	.gem_prime_import_sg_table	= xlnx_gem_prime_import_sg_table_vmap,
+	.gem_prime_vmap			= xlnx_gem_prime_vmap,
+	.gem_prime_vunmap		= xlnx_gem_prime_vunmap,
+	.gem_prime_mmap			= xlnx_gem_prime_mmap,
+	.gem_vm_ops			= &xlnx_gem_vm_ops,
+	.gem_free_object_unlocked	= xlnx_gem_free_object,
+	.dumb_create			= xlnx_gem_dumb_create,
 	.dumb_destroy			= drm_gem_dumb_destroy,
+	.ioctls	                        = xlnx_drm_driver_ioctls,
+	.num_ioctls                     = ARRAY_SIZE(xlnx_drm_driver_ioctls),
 
 	.fops				= &xlnx_fops,

```



### 　drivers/gpu/drm/xlnx/Kconfig


DRM_GEM_CMA_HELPER は使わないので select から削除します。


```diff:drivers/gpu/drm/xlnx/Kconfig
@@ -16,7 +16,6 @@ config DRM_XLNX
 	depends on DRM && OF
 	select DRM_KMS_HELPER
 	select DRM_KMS_CMA_HELPER
-	select DRM_GEM_CMA_HELPER
 	help
 	  Xilinx DRM KMS driver. Choose this option if you have
 	  a Xilinx SoCs with hardened display pipeline or soft

```


DRM_XLNX_DUMB_CACHE_DEFAULT_MODE を追加します。


```diff:drivers/gpu/drm/xlnx/Kconfig
+config DRM_XLNX_DUMB_CACHE_DEFAULT_MODE
+	int "Xilinx DRM Dumb Buffer Cache Default Mode"
+	range 0 1
+	default 0
+	depends on DRM_XLNX
+	help
+	  Thie specifies the data cache state of dumb buffer of the
+	  Xilinx DRM Driver. The dumb buffer data cache mode can also
+	  bo set in the module parameter. If not set by module parameter,
+	  this value will be set to dumb buffer data cache state.
+	  If 0 is specified, the dumb buffer data cache mode is set to
+	  write combine and read cache off.
+	  If 1 is specified, the dumb buffer data cache mode is set to
+	  write back and read cache on.
+	  

```



## DDX Xlnx で DRM Xlnx の設定をする



### 　リポジトリ


この章で説明する内容は以下の github リポジトリにあります。



* https://github.com/ikwzm/xf86-video-armsoc-xilinx/tree/v1.5-trial-13



2022年9月現在、まだ開発中のため DRI3 に対応したソースコードは xilinx-dri3-develop ブランチにあります。master ブランチにはまだ反映されていないことに注意してください。master ブランチには修正前のソースコードがあります。




### 　src/drmmode_xilinx/drmmode_xilinx.c



#### 　インクルードファイルたち



```C:src/drmmode_xilinx/drmmode_xilinx.c
#include <stdlib.h>
#include <drm.h>
#include <xf86drm.h>
#include "../drmmode_driver.h"
#define DIV_ROUND_UP(val,d)	(((val) + (d    ) - 1) / (d))
#define ALIGN(val, align)	(((val) + (align) - 1) & ~((align) - 1))
#include "xlnx_drm.h"

```



#### 　ローカル変数



```C:src/drmmode_xilinx/drmmode_xilinx.c
static int  xlnx_drmmode_initialized    = 0;
static int  xlnx_drm_scanout_align_size = 256;
static int  xlnx_drm_dumb_align_size    = 8;
static int  xlnx_drm_dumb_cache_avalable= 0;
static int  xlnx_drm_dumb_cache_default = 1;

```



#### 　ログ出力関数



```C:src/drmmode_xilinx/drmmode_xilinx.c
extern _X_EXPORT Bool armsocDebug;
#define XLNX_DRM_WARNING(fmt, ...) \
	do {	xf86Msg(X_WARNING, "WARNING: drmmode_xilinx " fmt "\n",\
			##__VA_ARGS__); \
	} while (0)
#define XLNX_DRM_INFO(fmt, ...) \
	do {	xf86Msg(X_INFO, "drmmode_xilinx " fmt "\n",\
			##__VA_ARGS__);\
	} while (0)
#define XLNX_DRM_DEBUG(fmt, ...) \
	do { if (armsocDebug) \
		xf86Msg(X_INFO, "drmmode_xilinx " fmt "\n",\
			##__VA_ARGS__);\
	} while (0)

```



#### 　xilinx_drmmode_init()


xilinx_drmmode_initialized が 0 の時に呼び出される初期化関数です。ここで ioctl() を使って DRM Xlnx のパラメータを設定します。


```C:src/drmmode_xilinx/drmmode_xilinx.c
static void xlnx_drmmode_init(int fd)
{
	int ret;
	struct drm_xlnx_get_param get_param_arg;
	struct drm_xlnx_set_param set_param_arg;
		
	memset(&get_param_arg, 0, sizeof(get_param_arg));
	get_param_arg.param = DRM_XLNX_PARAM_DRIVER_IDENTIFIER;
	ret = drmIoctl(fd, DRM_IOCTL_XLNX_GET_PARAM, &get_param_arg);
	if (ret) {
		XLNX_DRM_WARNING("drmIoctl("
				 "DRM_IOCTL_XLNX_GET_PARAM,"
				 "DRM_XLNX_PARAM_DRIVER_IDENTIFIER"
				 ") failed(%d)", ret);
		goto init_done;
	}
	if (get_param_arg.value != DRM_XLNX_DRIVER_IDENTIFIER) {
		XLNX_DRM_WARNING("invalid identifier(0x%08X)\n", (int)get_param_arg.value);
		goto init_done;
	}

```


drmIoctl() で xlnx_drm_scanout_align_size の値を得ます。


```C:src/drmmode_xilinx/drmmode_xilinx.c
	memset(&get_param_arg, 0, sizeof(get_param_arg));
	get_param_arg.param = DRM_XLNX_PARAM_SCANOUT_ALIGNMENT_SIZE;
	ret = drmIoctl(fd, DRM_IOCTL_XLNX_GET_PARAM, &get_param_arg);
	if (ret) {
		XLNX_DRM_WARNING("drmIoctl("
				 "DRM_IOCTL_XLNX_GET_PARAM,"
				 "DRM_XLNX_PARAM_SCANOUT_ALIGNMENT_SIZE"
				 ") failed(%d)", ret);
	} else {
		xlnx_drm_scanout_align_size = get_param_arg.value;
	}

```


drmIoctl() で xlnx_dumb_alignment_size を 8 に設定します。


```C:src/drmmode_xilinx/drmmode_xilinx.c
	memset(&set_param_arg, 0, sizeof(set_param_arg));
	set_param_arg.param = DRM_XLNX_PARAM_DUMB_ALIGNMENT_SIZE;
	set_param_arg.value = xlnx_drm_dumb_align_size;
	ret = drmIoctl(fd, DRM_IOCTL_XLNX_SET_PARAM, &set_param_arg);
	if (ret) {
		XLNX_DRM_WARNING("drmIoctl("
				 "DRM_IOCTL_XLNX_SET_PARAM,"
				 "DRM_XLNX_PARAM_DUMB_ALIGNMENT_SIZE,"
				 "%d"
				 ") failed(%d)", 
				 (int)set_param_arg.value, ret);
	}

```


Dumb バッファの CPU キャッシュを有効にできるか否かを得ます。


```C:src/drmmode_xilinx/drmmode_xilinx.c(4)
	memset(&get_param_arg, 0, sizeof(get_param_arg));
	get_param_arg.param = DRM_XLNX_PARAM_DUMB_CACHE_AVALABLE;
	ret = drmIoctl(fd, DRM_IOCTL_XLNX_GET_PARAM, &get_param_arg);
	if (ret) {
		XLNX_DRM_WARNING("drmIoctl("
				 "DRM_IOCTL_XLNX_GET_PARAM,"
				 "DRM_XLNX_PARAM_DUMB_CACHE_AVALABLE"
				 ") failed(%d)", ret);
	} else {
		xlnx_drm_dumb_cache_avalable = get_param_arg.value;
	}

```


xlnx_dumb_cache_default_mode を 1 に設定します。1 に設定することにより、Lima が DRM Xlnx GEM に対して Dumb バッファを確保する際には CPU キャッシュをオンにします。


```C:src/drmmode_xilinx/drmmode_xilinx.c
	memset(&get_param_arg, 0, sizeof(set_param_arg));
	set_param_arg.param = DRM_XLNX_PARAM_DUMB_CACHE_DEFAULT_MODE;
	set_param_arg.value = xlnx_drm_dumb_cache_default;
	ret = drmIoctl(fd, DRM_IOCTL_XLNX_SET_PARAM, &set_param_arg);
	if (ret) {
		XLNX_DRM_WARNING("drmIoctl("
				 "DRM_IOCTL_XLNX_SET_PARAM,"
				 "DRM_XLNX_PARAM_DUMB_CACHE_DEFAULT_MODE,"
				 "%d"
				 ") failed(%d)", 
				 (int)set_param_arg.value, ret);
	}

```


最後に初期化の結果をログに出力します。


```C:src/drmmode_xilinx/drmmode_xilinx.c
    init_done:
	XLNX_DRM_DEBUG("scanout  alignment size = %d", xlnx_drm_scanout_align_size );
	XLNX_DRM_DEBUG("dumb buf alignment size = %d", xlnx_drm_dumb_align_size    );
	XLNX_DRM_DEBUG("dumb buf cache avalable = %d", xlnx_drm_dumb_cache_avalable);
	XLNX_DRM_INFO("initialized");
}

```



#### 　create_custom_gem()



```C:src/drmmode_xilinx/drmmode_xilinx.c
static int create_custom_gem(int fd, struct armsoc_create_gem *create_gem)
{
	struct drm_mode_create_dumb arg;
	int ret;

```


create_custom_gem() が最初に呼び出された時の１回だけ、xlnx_drmmode_init() によって初期化を行います。


```C:src/drmmode_xilinx/drmmode_xilinx.c
	if (xlnx_drmmode_initialized != 1) {
		xlnx_drmmode_init(fd);
		xlnx_drmmode_initialized = 1;
	}
	memset(&arg, 0, sizeof(arg));

```


バッファタイプが ARMSOC_BO_SCANOUT だった場合は、ストライドを xlnx_drm_scanout_align_size の値でアライメントされた値に設定します。また、CPU キャッシュをオフにします。


```C:src/drmmode_xilinx/drmmode_xilinx.c
	if (create_gem->buf_type == ARMSOC_BO_SCANOUT) {
		arg.height = create_gem->height;
		arg.width  = create_gem->width;
		arg.bpp    = create_gem->bpp;
		/* For Xilinx DPDMA needs pitch scanout alignment */
		arg.pitch  = ALIGN(create_gem->width * DIV_ROUND_UP(create_gem->bpp,8), xlnx_drm_scanout_align_size);
		arg.flags  = DRM_XLNX_GEM_DUMB_SCANOUT;
		if (xlnx_drm_dumb_cache_avalable != 0)
			arg.flags |= DRM_XLNX_GEM_DUMB_CACHE_OFF;

```


バッファタイプが ARMSOC_BO_SCANOUT でなかった場合は、ストライドを Lima が要求するアライメントに合わせます。また、CPU キャッシュをオフにします。ここで CPU キャッシュをオフにする理由は、オンにしておくとカーソルなどがちゃんと表示されなくなるためです。


```C:src/drmmode_xilinx/drmmode_xilinx.c
	} else {
		/* For Lima need height and width 16 pixel alignment */
		arg.height = ALIGN(create_gem->height, 16);
		arg.width  = ALIGN(create_gem->width , 16);
		arg.bpp    = create_gem->bpp;
		arg.pitch  = arg.width * DIV_ROUND_UP(create_gem->bpp, 8);
		arg.flags  = DRM_XLNX_GEM_DUMB_NON_SCANOUT;
		if (xlnx_drm_dumb_cache_avalable != 0)
			arg.flags |= DRM_XLNX_GEM_DUMB_CACHE_OFF;
	}

```


用意したパラメータを使って Dumb バッファを生成します。返ってきたハンドル、ストライド、バッファのサイズを保存して戻ります。


```C:src/drmmode_xilinx/drmmode_xilinx.c
	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &arg);
	if (ret)
		return ret;
	create_gem->handle = arg.handle;
	create_gem->pitch  = arg.pitch;
	create_gem->size   = arg.size;
	return 0;
}

```



#### 　xilinx_interface



```C:src/drmmode_xilinx/drmmode_xilinx.c
struct drmmode_interface xilinx_interface = {
	"xlnx"                /* name of drm driver */,
	1                     /* use_page_flip_events */,
	1                     /* use_early_display */,
	0                     /* cursor width */,
	0                     /* cursor_height */,
	0                     /* cursor padding */,
	HWCURSOR_API_NONE     /* cursor_api */,
	NULL                  /* init_plane_for_cursor */,
	0                     /* vblank_query_supported */,
	create_custom_gem     /* create_custom_gem */,
};

```



### 　src/drmmode_xilinx/xlnx_drm.h


このファイルは DRM Xlnx の include/uapi/drm/xlnx_drm.h の内容と同じです。




# 効果


CPU キャッシュを有効にした DRM Xlnx の場合、glmark2 のスコアが 21 から 85 まで向上しました。



<table border="2">
  <tr>
    <td align="center" rowspan="2">Machine</td>
    <td align="center" rowspan="2">Lima</td>
    <td align="center" rowspan="2">Cache</td>
    <td align="center" colspan="2">glmark2 </td>
  </tr>
  <tr>
    <td align="center">Surface Size</td>
    <td align="center">Score</td>
  </tr>
  <tr>
    <td align="center" rowspan="3">Ultra96-V2</td>
    <td align="center">無</td>
    <td align="center">-</td>
    <td align="center" rowspan="3">800x600</td>
    <td align="right">21</td>
  </tr>
  <tr>
    <td align="center" rowspan="2">有</td>
    <td align="center">無</td>
    <td align="right">21</td>
  </tr>
  <tr>
    <td align="center">有</td>
    <td align="right">85</td>
  </tr>
</table>



具体的な効果は[glmark2編]参照してください。




# 参考



## リポジトリ


この記事で説明した DRM Xlnx の変更内容は以下の github リポジトリにあります。ソースコード自体はこのリポジトリにはありませんが、パッチ用のファイルを用意しています。



* https://github.com/ikwzm/ZynqMP-FPGA-Linux-5.10-Trial/tree/5.10.120-zynqmp-fpga-trial-16
  * files/linux-5.10.120-zynqmp-fpga-drm-xlnx-gem.diff



この記事で説明した DDX Xlnx は以下の github リポジトリにあります。



* https://github.com/ikwzm/xf86-video-armsoc-xilinx/tree/v1.5-trial-13


## その他


* Lima web (https://gitlab.freedesktop.org/lima/web)
* Mesa 3D and Direct Rendering Infrastructure wiki  (https://dri.freedesktop.org/wiki)



[概要編]: https://qiita.com/ikwzm/items/e8fa7540efdf580e902f "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(概要編)』@Qiita"
[DRM Lima 編]: https://qiita.com/ikwzm/items/465bcb51ab5a6ed8dbfb "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(DRM Lima 編)』@Qiita"
[DRI Lima 編]: https://qiita.com/ikwzm/items/a5681608932a97bf2037 "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(DRI Lima 編)』@Qiita"
[共有バッファ編]: https://qiita.com/ikwzm/items/aaa7c7a321cfc29518df "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(共有バッファ編)』@Qiita"
[ストライド問題編]: https://qiita.com/ikwzm/items/0c49289e4e4db7a673ac "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(ストライド問題編)』@Qiita"
[GEM キャッシュ編]: https://qiita.com/ikwzm/items/23165500ca2a0363de6e "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(GEM キャッシュ編)』@Qiita"
[glmark2編]: https://qiita.com/ikwzm/items/5e8694aafb811f24a549 "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(glmark2編)』@Qiita"
[インストール編]: https://qiita.com/ikwzm/items/9d096104a129255823a8 "『Ultra96/Ultra96-V2/KV260/KR260 向け Ubuntu 22.04(Desktop版) ブートイメージの提供』@Qiita"
[『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(概要編)』@Qiita]: https://qiita.com/ikwzm/items/642df8c14fd3a9e782b6 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(概要編)』@Qiita"
[『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRM Lima 編)』@Qiita]: https://qiita.com/ikwzm/items/91adfcf05e767d33be70 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRM Lima 編)』@Qiita"
[『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRI Lima 編)』@Qiita]: https://qiita.com/ikwzm/items/ec29ae456ead67438733 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRI Lima 編)』@Qiita"
[『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(共有バッファ編)』@Qiita]: https://qiita.com/ikwzm/items/ed2ce2d8bb90391d7c65 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(共有バッファ編)』@Qiita"
[『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(ストライド問題編)』@Qiita]: https://qiita.com/ikwzm/items/5c4c0114c15cfba8e95b "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(ストライド問題編)』@Qiita"
[Lima]: https://gitlab.freedesktop.org/lima/web "Lima Website"
[DRI]: https://dri.freedesktop.org/wiki "Mesa 3D and Direct Rendering Infrastructure wiki"
[DRI(Direct Rendering Infrastructure)]: https://dri.freedesktop.org/wiki "Mesa 3D and Direct Rendering Infrastructure wiki"