はじめに
Lima とは Mali-400/450 用のオープンソースなグラフィックドライバです。筆者は Lima を ZynqMP 向け Ubuntu 22.04 で試験的に動かしてみました。動かすのに少々苦労したので、その方法を何回かに分けて説明します。
- 概要編
- DRM Lima 編(この記事)
- DRI Lima 編
- 共有バッファ編
- ストライド問題編
- GEM キャッシュ編
- インストール編
- glmark2編
この記事では DRM Lima を Linux Kernel に組み込む方法を説明します。
なお、この記事は以下の記事のアップデート版です。
旧記事では、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
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 には次の記述があります。
:
(中略)
:
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 には次の記述があります。
/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 を修正します。
#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 を追加します。
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" で固定されていました。しかもクロックの数もこの2つしかサポートされていません。そこで複数の任意の名前のクロックでも動作するように 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 という構造体を使っています。
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() を変更します。
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;
}
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);
}
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 を配列にして2種類の名前を受け付けるようにします。
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種類の名前のうちどちらかが有効であれば、初期化を行うようにします。
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$ 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$ 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 のブートイメージを提供しています。使ってみたい人は注意事項を確認のうえインストールしてみてください。
Linux Kernel
以下の URL に、この記事で紹介した DRM Lima の修正を施した Linux Kernel のイメージとDebian Packageを提供しています。また修正用のパッチファイルも提供しているので参考にしてください。
旧記事
この記事は次の記事を加筆修正したものです。
その他
- Lima web (https://gitlab.freedesktop.org/lima/web)
- Mesa 3D and Direct Rendering Infrastructure wiki (https://dri.freedesktop.org/wiki)