ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(DRM Lima 編)







# はじめに


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



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



この記事では DRM Lima を Linux Kernel に組み込む方法を説明します。



なお、この記事は以下の記事のアップデート版です。

* [『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRM Lima 編)』@Qiita]



旧記事では、DRM Lima を変更せずに Device Tree を変更する方法を説明していました。この記事では、旧記事とは異なり、Device Tree を変更するのではなく DRM Lima を修正することで Mali Driver 用の Device Tree にも対応する方法について説明します。




# DRM Lima とは


DRM Lima はMali-400/450 を制御するための Linux カーネルの DRM(Direct Rendering Manager) ドライバです。DRM ドライバは [DRI(Direct Rendering Infrastructure)] の Linuxカーネル部分のコンポーネントです。 


![Fig.1 DRM Lima](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/c10f5a19-3c4c-1f16-c92c-cfa122ed741a.jpeg "Fig.1 DRM Lima")

Fig.1 DRM Lima

<br />




# DRM Lima のビルド



## Kernel への組み込み


Linux Kernel 5.2 からはメインラインに DRM Lima が組み込まれています。ドライバのソースコードは drivers/gpu/drm/lima にあります。

DRM Lima をカーネルに組み込むためには、カーネルコンフィギュレーションファイルにCONFIG_DRM_LIMA=y または CONFIG_DRM_LIMA=m を追加するか、 make menuconfig で Device Drivers > Graphics support > LIMA (DRM support for ARM Mali 400/450 GPU) を選択しておく必要があります。


## Device Tree の設定


Xilinx が提供する Linux Kernel の zynqmp 用の Device Tree には次の記述があります。


```devicetree:arch/arm64/boot/dts/xilinx/zynqmp.dtsi
	:
	(中略)
	:
	gpu: gpu@fd4b0000 {
		status = "disabled";
		compatible = "arm,mali-400", "arm,mali-utgard";
		reg = <0x0 0xfd4b0000 0x0 0x10000>;
		interrupt-parent = <&gic>;
		interrupts = <0 132 4>, <0 132 4>, <0 132 4>, <0 132 4>, <0 132 4>, <0 132 4>;
		interrupt-names = "IRQGP", "IRQGPMMU", "IRQPP0", "IRQPPMMU0", "IRQPP1", "IRQPPMMU1";
		clock-names = "gpu", "gpu_pp0", "gpu_pp1";
		power-domains = <&zynqmp_firmware PD_GPU>;
	};
	:
	(後略)

```


また、Ultra96 用の Device Tree には次の記述があります。




```devicetree:arch/arm64/boot/dts/xilinx/zynqmp-zcu100-revC.dts
/dts-v1/;
#include "zynqmp.dtsi"
	:
	(中略)
	:
&gpu {
	status = "okay";
};
	:
	(後略)

```




このことから、Ultra96 では GPU は有効になっているはずです。しかし、このままでは DRM Lima は動きません。実は、上で紹介した Device Tree は、DRM Lima 用ではなく Mali Driver 用だからです。




# DRM Lima の修正点


前節で説明したように、Xilinx が提供する Device Tree では DRM Lima は動きません。そこで DRM Lima を Mali 用の Device Tree でも動作するように修正します。


## Device Tree ID の変更


まず、Device Tree をロードする際にトリガーとなる Device Tree 上の ID の名前を変更できるようにしておきます。この理由は、本来の Mali ドライバを組み込む必要が出てきた際に、Device Tree ID が同じだとコンフリクトするので、それを防ぐためです。ここでは、 Linux Kernel のビルド時に設定したり、Kernel Module Parameter で指定できるようにします。

具体的には次のように drivers/gpu/drm/lima/lima_drv.c を修正します。


```C:drivers/gpu/drm/lima/lima_drv.c
#ifdef CONFIG_DRM_LIMA_OF_ID_PREFIX
#define OF_ID_PREFIX CONFIG_DRM_LIMA_OF_ID_PREFIX
#else
#define OF_ID_PREFIX "arm,mali-"
#endif
#ifdef CONFIG_DRM_LIMA_OF_ID_PARAMETERIZE
static struct of_device_id dt_match[] = {
	{ .compatible = OF_ID_PREFIX "400", .data = (void *)lima_gpu_mali400 },
	{ .compatible = OF_ID_PREFIX "450", .data = (void *)lima_gpu_mali450 },
	{}
};
module_param_string(of_400_id, dt_match[0].compatible, sizeof(dt_match[0].compatible), 0);
MODULE_PARM_DESC(of_400_id, "Openfirmware id of mali-400 to be handled by lima");
module_param_string(of_450_id, dt_match[1].compatible, sizeof(dt_match[1].compatible), 0);
MODULE_PARM_DESC(of_450_id, "Openfirmware id of mali-450 to be handled by lima");
#else
static const struct of_device_id dt_match[] = {
	{ .compatible = OF_ID_PREFIX "400", .data = (void *)lima_gpu_mali400 },
	{ .compatible = OF_ID_PREFIX "450", .data = (void *)lima_gpu_mali450 },
	{}
};
#endif
MODULE_DEVICE_TABLE(of, dt_match);

```


さらに driver/gpu/drm/lima/Kconfig に DRM_LIMA_OF_ID_PREFIX と DRM_LIMA_OF_ID_PARAMETERIZE を追加します。




```Makefile:drivers/gpu/drm/lima/Kconfig
config DRM_LIMA
       tristate "LIMA (DRM support for ARM Mali 400/450 GPU)"
       depends on DRM
       depends on ARM || ARM64 || COMPILE_TEST
       depends on MMU
       depends on COMMON_CLK
       depends on OF
       select DRM_SCHED
       select DRM_GEM_SHMEM_HELPER
       select PM_DEVFREQ
       select DEVFREQ_GOV_SIMPLE_ONDEMAND
       help
           DRM driver for ARM Mali 400/450 GPUs.
config DRM_LIMA_OF_ID_PREFIX
       string "Device Tree ID prefix"
       default "arm,mali-"
       depends on DRM_LIMA
       help
           ID prefix specified in the compatible property of the device tree.
config DRM_LIMA_OF_ID_PARAMETERIZE
       bool "Parameterize Device Tree ID"
       default n
       depends on DRM_LIMA
       help
           Parameterize the device ID of Lima so that it can be changed at boot time.
           For example, specify lima.of_400_id=arm,mali-400 as a boot paramater.

```





## クロックの変更


オリジナルの DRM Lima ではクロックの名前が "bus" と "core" で固定されていました。しかもクロックの数もこの２つしかサポートされていません。そこで複数の任意の名前のクロックでも動作するように drivers/gpu/drm/lima/lima_device.c を修正します。



具体的には、drivers/gpu/drm/lima/lima_device.h で定義されている struct lima_device に num_clks と clks を追加します。num_clks はクロックの数、clks はクロックの配列です。ここでは linux が標準で提供している clk_bulk_data という構造体を使っています。


```C:drivers/gpu/drm/lima/lima_device.h
struct lima_device {
	:
	int num_clks;
	struct clk_bulk_data *clks;
	:
};
```


あとは drivers/gpu/drm/lima/lima_device.c の lima_clk_enable() と lima_clk_disable() と lima_clk_init() を変更します。


```C:drivers/gpu/drm/lima/lima_device.c
static int lima_clk_enable(struct lima_device *dev)
{
	int err;
	if (dev->clks) {
		err = clk_bulk_prepare_enable(dev->num_clks, dev->clks);
		if (err) {
			dev_err(dev->dev,
				"clock controller enable failed %d\n", err);
			return err;
		}
	}
	if (dev->reset) {
		err = reset_control_deassert(dev->reset);
		if (err) {
			dev_err(dev->dev,
				"reset controller deassert failed %d\n", err);
			goto error_out1;
		}
	}
	return 0;
error_out1:
	if (dev->clks)
		clk_bulk_disable_unprepare(dev->num_clks, dev->clks);
	return err;
}

```





```C:drivers/gpu/drm/lima/lima_device.c
static void lima_clk_disable(struct lima_device *dev)
{
	if (dev->reset)
		reset_control_assert(dev->reset);
	if (dev->clks)
		clk_bulk_disable_unprepare(dev->num_clks, dev->clks);
}

```





```C:drivers/gpu/drm/lima/lima_device.c
static int lima_clk_init(struct lima_device *dev)
{
	int err;
	int i;
	dev->clks = NULL;
	dev->num_clks = devm_clk_bulk_get_all(dev->dev, &dev->clks);
	if (dev->num_clks < 0) {
		err = dev->num_clks;
		dev->num_clks = 0;
		dev_err(dev->dev, "get clock controllser failed %d\n", err);
		return err;
	} 
	dev->clk_gpu = NULL;
	for (i = 0 ; i < dev->num_clks ; i++) {
		if ((strcmp(dev->clks[i].id, "core") == 0) ||
		    (strcmp(dev->clks[i].id, "gpu" ) == 0)) {
			dev->clk_gpu = dev->clks[i].clk;
			break;
		}
	}
	dev->reset = devm_reset_control_array_get_optional_shared(dev->dev);
	if (IS_ERR(dev->reset)) {
		err = PTR_ERR(dev->reset);
		if (err != -EPROBE_DEFER)
			dev_err(dev->dev, "get reset controller failed %d\n",
				err);
		dev->reset = NULL;
		return err;
	}
	return lima_clk_enable(dev);
}

```



## 割り込み名の追加


オリジナルの DRM Lima では割り込みの名前が "gp", "gpmmu", "pp0", "ppmmu0", "pp1", "ppmmu1" 等と決まっていましたが、Mali 用の Device Tree の割り込みの名前でも動作するように変更します。

具体的には、drivers/gpu/drm/lima/lima_device.c の  struct lima_ip_desc の irq_name を配列にして２種類の名前を受け付けるようにします。


```C:drivers/gpu/drm/lima/lima_device.c
struct lima_ip_desc {
	char *name;
	char *irq_name[2];
	bool must_have[lima_gpu_num];
	int offset[lima_gpu_num];
	int (*init)(struct lima_ip *ip);
	void (*fini)(struct lima_ip *ip);
	int (*resume)(struct lima_ip *ip);
	void (*suspend)(struct lima_ip *ip);
};
#define LIMA_IP_DESC(ipname, mst0, mst1, off0, off1, func, irq0, irq1)	\
	[lima_ip_##ipname] = { \
		.name = #ipname, \
		.irq_name[0] = irq0, \
		.irq_name[1] = irq1, \
		.must_have = { \
			[lima_gpu_mali400] = mst0, \
			[lima_gpu_mali450] = mst1, \
		}, \
		.offset = { \
			[lima_gpu_mali400] = off0, \
			[lima_gpu_mali450] = off1, \
		}, \
		.init = lima_##func##_init, \
		.fini = lima_##func##_fini, \
		.resume = lima_##func##_resume, \
		.suspend = lima_##func##_suspend, \
	}

static struct lima_ip_desc lima_ip_desc[lima_ip_num] = {
	LIMA_IP_DESC(pmu,         false, false, 0x02000, 0x02000, pmu,      "pmu"  , "IRQPMU"   ),
	LIMA_IP_DESC(l2_cache0,   true,  true,  0x01000, 0x10000, l2_cache, NULL   , NULL       ),
	LIMA_IP_DESC(l2_cache1,   false, true,  -1,      0x01000, l2_cache, NULL   , NULL       ),
	LIMA_IP_DESC(l2_cache2,   false, false, -1,      0x11000, l2_cache, NULL   , NULL       ),
	LIMA_IP_DESC(gp,          true,  true,  0x00000, 0x00000, gp,       "gp"   , "IRQGP"    ),
	LIMA_IP_DESC(pp0,         true,  true,  0x08000, 0x08000, pp,       "pp0"  , "IRQPP0"   ),
	LIMA_IP_DESC(pp1,         false, false, 0x0A000, 0x0A000, pp,       "pp1"  , "IRQPP1"   ),
	LIMA_IP_DESC(pp2,         false, false, 0x0C000, 0x0C000, pp,       "pp2"  , "IRQPP2"   ),
	LIMA_IP_DESC(pp3,         false, false, 0x0E000, 0x0E000, pp,       "pp3"  , "IRQPP3"   ),
	LIMA_IP_DESC(pp4,         false, false, -1,      0x28000, pp,       "pp4"  , "IRQPP4"   ),
	LIMA_IP_DESC(pp5,         false, false, -1,      0x2A000, pp,       "pp5"  , "IRQPP5"   ),
	LIMA_IP_DESC(pp6,         false, false, -1,      0x2C000, pp,       "pp6"  , "IRQPP6"   ),
	LIMA_IP_DESC(pp7,         false, false, -1,      0x2E000, pp,       "pp7"  , "IRQPP7"   ),
	LIMA_IP_DESC(gpmmu,       true,  true,  0x03000, 0x03000, mmu,      "gpmmu", "IRQGPMMU" ),
	LIMA_IP_DESC(ppmmu0,      true,  true,  0x04000, 0x04000, mmu,      "ppmmu0","IRQPPMMU0"),
	LIMA_IP_DESC(ppmmu1,      false, false, 0x05000, 0x05000, mmu,      "ppmmu1","IRQPPMMU1"),
	LIMA_IP_DESC(ppmmu2,      false, false, 0x06000, 0x06000, mmu,      "ppmmu2","IRQPPMMU2"),
	LIMA_IP_DESC(ppmmu3,      false, false, 0x07000, 0x07000, mmu,      "ppmmu3","IRQPPMMU3"),
	LIMA_IP_DESC(ppmmu4,      false, false, -1,      0x1C000, mmu,      "ppmmu4","IRQPPMMU4"),
	LIMA_IP_DESC(ppmmu5,      false, false, -1,      0x1D000, mmu,      "ppmmu5","IRQPPMMU5"),
	LIMA_IP_DESC(ppmmu6,      false, false, -1,      0x1E000, mmu,      "ppmmu6","IRQPPMMU6"),
	LIMA_IP_DESC(ppmmu7,      false, false, -1,      0x1F000, mmu,      "ppmmu7","IRQPPMMU7"),
	LIMA_IP_DESC(dlbu,        false, true,  -1,      0x14000, dlbu,     NULL    , NULL      ),
	LIMA_IP_DESC(bcast,       false, true,  -1,      0x13000, bcast,    NULL    , NULL      ),
	LIMA_IP_DESC(pp_bcast,    false, true,  -1,      0x16000, pp_bcast, "pp"    , "IRQPP"   ),
	LIMA_IP_DESC(ppmmu_bcast, false, true,  -1,      0x15000, mmu,      NULL    , NULL      ),
};

```


そして lima_init_ip() を修正して、2種類の名前のうちどちらかが有効であれば、初期化を行うようにします。


```C:drivers/gpu/drm/lima/lima_device.c
static int lima_init_ip(struct lima_device *dev, int index)
{
	struct platform_device *pdev = to_platform_device(dev->dev);
	struct lima_ip_desc *desc = lima_ip_desc + index;
	struct lima_ip *ip = dev->ip + index;
	const char *irq_name0 = desc->irq_name[0];
	const char *irq_name1 = desc->irq_name[1];
	int offset = desc->offset[dev->id];
	bool must = desc->must_have[dev->id];
	int err;
	if (offset < 0)
		return 0;
	ip->dev = dev;
	ip->id = index;
	ip->iomem = dev->iomem + offset;
	err = -1;
	if (irq_name1) {
		if (must && !irq_name0)
			err = platform_get_irq_byname(pdev, irq_name1);
		else
			err = platform_get_irq_byname_optional(pdev, irq_name1);
	}
	if (irq_name0 && err < 0) {
		if (must)
			err = platform_get_irq_byname(pdev, irq_name0);
		else
			err = platform_get_irq_byname_optional(pdev, irq_name0);
	}
	if (irq_name0 || irq_name1) {
		if (err < 0)
			goto out;
		ip->irq = err;
	}
	err = desc->init(ip);
	if (!err) {
		ip->present = true;
		return 0;
	}
out:
	return must ? err : 0;
}

```



# DRM Lima の起動


DRM Lima を組み込んだ Linux Kernel を起動すると次のようなログがコンソール(or dmesg)にでます。


```Shell
shell$ dmesg
	:
	(中略)
	:
[    5.584001] lima fd4b0000.gpu: gpu(gpu_ref) rate = 499999995, enable = 1
[    5.590733] lima fd4b0000.gpu: gpu_pp0(gpu_pp0_ref) rate = 499999995, enable = 1
[    5.598146] lima fd4b0000.gpu: gpu_pp1(gpu_pp1_ref) rate = 499999995, enable = 1
[    5.605688] lima fd4b0000.gpu: gp - mali400 version major 1 minor 1
[    5.611968] lima fd4b0000.gpu: pp0 - mali400 version major 1 minor 1
[    5.618332] lima fd4b0000.gpu: pp1 - mali400 version major 1 minor 1
[    5.624691] lima fd4b0000.gpu: l2 cache 64K, 4-way, 64byte cache line, 128bit external bus
[    5.633409] [drm] Initialized lima 1.1.0 20191231 for fd4b0000.gpu on minor 1
	:
	(後略)

```




また、/dev/dri の下に renderD128 が出来ます。


```Shell
shell$ ls -la /dev/dri
total 0
drwxr-xr-x   3 root root        120 Sep 10 03:47 .
drwxr-xr-x  14 root root      14840 Sep 10 03:47 ..
drwxr-xr-x   2 root root        100 Sep 10 03:47 by-path
crw-rw----+  1 root video  226,   0 Sep 10 03:47 card0
crw-rw----+  1 root video  226,   1 Sep 10 03:47 card1
crw-rw----+  1 root render 226, 128 Sep 10 03:47 renderD128
```





# 参考



## ブートイメージ


以下の URL にLima に対応した Ultra96/Ultra96-V2/KV260 向け Ubuntu22.04 のブートイメージを提供しています。使ってみたい人は注意事項を確認のうえインストールしてみてください。



* https://github.com/ikwzm/ZynqMP-FPGA-Ubuntu22.04-Desktop




## Linux Kernel


以下の URL に、この記事で紹介した DRM Lima の修正を施した Linux Kernel のイメージとDebian Packageを提供しています。また修正用のパッチファイルも提供しているので参考にしてください。



* https://github.com/ikwzm/ZynqMP-FPGA-Linux-5.10-Trial/tree/5.10.120-zynqmp-fpga-trial-16


## 旧記事


この記事は次の記事を加筆修正したものです。

* [『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRM Lima 編)』@Qiita]


## その他


* 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/KV260 向け Debian GNU/Linux および Ubuntu20.04 で XRT(Xilinx Runtime) を動かす(概要編)』@Qiita]: https://qiita.com/ikwzm/items/0d3a8adfe725cd17f932 "『Ultra96/Ultra96-V2/KV260 向け Debian GNU/Linux および Ubuntu20.04 で XRT(Xilinx Runtime) を動かす(概要編)』@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"