Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRI Lima 編)







# 注意(2022年10月12日追記)


この記事は2021年4月に投稿したものであり、古い内容が含まれています。2022年10月に以下の記事を投稿しましたので参照してください。

・[『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(DRI Lima 編)』@Qiita]


# はじめに


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



  * [概要編]
  * [DRM Lima 編]
  *  __DRI Lima 編 (この記事)__  
  * [共有バッファ編]
  * [ストライド問題編]
  * [インストール編]
  * [Ubuntu Desktop ビルド編]



この記事では Mesa が提供している DRI Lima ドライバを xlnx に対応するための修正点や、Debian パッケージのビルド方法を説明します。



  __なお、現時点(2021年4月)では、Ubuntu 20.04 の gnome-shell が動作するところまで確認していますが、すべてのアプリケーションで動作を確認したわけでは無いことに注意してください。__  




# DRI Lima とは


DRI Lima はMesa(オープンソースな OpenGL 実装)の DRI(Direct Rendering Infrastructure)ドライバです。DRI Lima は、OpenGL コマンドシーケンスを Lima のコマンドに変換し、DRM Lima を使用してコマンドをハードウェアに送信します。DRI Lima は、DRM Lima がカーネル空間で必要なことをすべて実行する間、ユーザー空間で可能な限り多くの処理を行います。


![Fig.1 DRI Lima](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24981/1f31298f-80d8-788c-203b-0793e7b4663b.jpeg "Fig.1 DRI Lima")

Fig.1 DRI Lima

<br />




# DRI Lima を xlnx 対応にする


Ubuntu 20.04 に使われている libGL の DRI ドライバは libgl1-mesa-dri という Debian パッケージで提供されています。そして、このパッケージには Lima に対応した DRI ドライバが含まれています。しかし、残念ながらこのパッケージに含まれている DRI  Limaは  DDX Xlnx および DRM Xlnx に対応していません。そのため、xlnx に対応した DRI Lima を別途ビルドする必要があります。この章ではその変更点を説明します。


## ソースコード


Ubuntu 20.04 に使われている libgl1-mesa-dri のバージョンは 20.2.6-0ubuntu0.20.04.1 です。ソースコードは apt-get の source コマンドによってダウンロードします。


```console
shell$ apt-get source mesa=20.2.6-0ubuntu0.20.04.1
shell$ cd mesa-20.2.6
```



## ライブラリの追加


DRI Lima が xlnx に対応するためには、/usr/lib/aarch64-linux-gnu/dri/xlnx_dri.so という動的ライブラリが必要です。

実は libgl1-mesa-dri が提供している DRI ドライバは、ファイルこそ多数ありますが、一つのライブラリファイルをハードリンクして別名を与えているにすぎません。したがって、xlnx_dri.so という名前の動的ライブラリを作るだけならば、src/gallium/targets/dri/meson.build に次のように 'xlnx_dri.so' を追加するだけで、ビルド時に xlnx_dri.so が生成されます。ただしこのままだと xlnx に対応したエントリポイントが無いので動的ライブラリとのリンクに失敗します。


```diff:src/gallium/targets/dri/meson.build
index 7cd8666..c85dc74 100644
--- a/src/gallium/targets/dri/meson.build
+++ b/src/gallium/targets/dri/meson.build
@@ -84,6 +84,7 @@ foreach d : [[with_gallium_kmsro, [
                'st7735r_dri.so',
                'stm_dri.so',
 	       'sun4i-drm_dri.so',
+	       'xlnx_dri.so',
              ]],
              [with_gallium_radeonsi, 'radeonsi_dri.so'],
              [with_gallium_nouveau, 'nouveau_dri.so'],

```





## エントリポイントの追加


DRI Lima が xlnx に対応するためには、xlnx_dri.so という名前の 動的ライブラリがあるだけでは不十分です。それに加えて、動的ライブラリに \_\_driDriverGetExtensions_xlnx() という関数がエントリポイントとして定義されていなければなりません。そこで、src/gallium/targes/dri/target.c に xlnx のエントリポイントを追加します。


```diff:src/gallium/targets/dri/target.c
index f71f690..e8f4340 100644
--- a/src/gallium/targets/dri/target.c
+++ b/src/gallium/targets/dri/target.c
@@ -110,6 +110,7 @@ DEFINE_LOADER_DRM_ENTRYPOINT(st7586)
 DEFINE_LOADER_DRM_ENTRYPOINT(st7735r)
 DEFINE_LOADER_DRM_ENTRYPOINT(stm)
 DEFINE_LOADER_DRM_ENTRYPOINT(sun4i_drm)
+DEFINE_LOADER_DRM_ENTRYPOINT(xlnx)
 #endif
 
```





# DRI Lima のビルド


この章では、xlnx に対応した DRI Lima の Debian パッケージをビルドする方法について説明します。


## ビルド環境


Mesa の Debian パッケージをビルドするためには、arm64 用のコンパイラなどの様々なパッケージが必要になります。そしてそれらのパッケージのバージョンが整合していなければなりません。これらの環境を PC (x86 アーキテクチャ) 上でクロスコンパイルしたり、QEMU 等の仮想環境をつかってビルドするとうまくいきませんでした。そこで筆者は、Ultra96-V2 に Ubuntu20.04 の CUI 版をインストールして、セルフコンパイルしました。セルフコンパイルだとバージョンの不整合に悩む必要がありません。

Ultra96-V2 上の Ubuntu 20.04 で次のようにしてビルド環境を構築します。


```console
shell$ sudo apt-get build-dep mesa
shell$ sudo apt-get install cmake valgrind libunwind-dev libconfig-dev
```



## ソースコードのダウンロード


ソースコードは apt-get の source コマンドによってダウンロードします。


```console
shell$ apt-get source mesa=20.2.6-0ubuntu0.20.04.1
shell$ cd mesa-20.2.6
```



## ソースコードの修正


前章で説明した DRI Lima を xlnx に対応するための修正をソースコードに行います。ここでは省略します。


## 追加パッケージの追加


前章までの修正で xlnx に対応した DRI Lima (xlnx_dri.so) がビルドできます。しかし、このままだと、libgl1-mesa-dri パッケージに xlnx_dri.so が含まれることになり、すでに libgl1-mesa-dri パッケージがインストールされたシステムに xlnx_dri.so を追加するためには  libgl1-mesa-dri パッケージそのものを上書きしなければなりません。xlnx_dri.so を含むパッケージのバージョン番号を変えるにしても変えないにしても管理上の問題が起こりそうです。

そこで、別途 xlnx_dri.so だけを含んだ libgl1-mesa-xlnx-dri パッケージを作っておきます。すでに libgl1-mesa-dri パッケージがインストールされたシステムには、このパッケージを後から追加インストールすることで xlnx_dri.so だけをインストールできるようにしておきます。

具体的には debian/control と debian/rules を次のように修正します。


```diff:debian/control
index c1f316b..d1b0bcf 100644
--- a/debian/control
+++ b/debian/control
@@ -427,4 +427,23 @@ Description: free implementation of the OpenCL API -- ICD runtime
  provides a standardized interface for computational analysis on graphical
  processing units.
 
+Package: libgl1-mesa-xlnx-dri
+Section: libs
+Architecture: any
+Pre-Depends: ${misc:Pre-Depends}
+Depends:
+ ${shlibs:Depends},
+ ${misc:Depends}
+Multi-Arch: same
+Description: free implementation of the OpenGL API -- xlnx dri module
+ This version of Mesa provides GLX and DRI capabilities: it is capable of
+ both direct and indirect rendering.  For direct rendering, it can use DRI
+ modules from the libgl1-mesa-dri package to accelerate drawing.
+ .
+ This package does not include the OpenGL library itself, only the DRI
+ modules for accelerating direct rendering.
+ .
+ For a complete description of Mesa, please look at the
+ libglx-mesa0 package.
+
 # vim: tw=0

```





```diff:debian/rulesol
index cbbf298..b457d91 100755
--- a/debian/rules
+++ b/debian/rules
@@ -202,6 +202,11 @@ override_dh_install:
 	rm debian/tmp/usr/lib/*/libEGL_mesa.so
 	rm debian/tmp/usr/lib/*/libGLX_mesa.so
 
+	# Copy the hardlinked xlnx_dri.so correctly.
+	install -m755 -d debian/libgl1-mesa-xlnx-dri/usr/lib/${DEB_HOST_MULTIARCH}/dri/
+	cp debian/tmp/usr/lib/${DEB_HOST_MULTIARCH}/dri/xlnx_dri.so \\
+	   debian/libgl1-mesa-xlnx-dri/usr/lib/${DEB_HOST_MULTIARCH}/dri/
+
 	# Copy the hardlinked *_dri.so correctly.
 	install -m755 -d debian/libgl1-mesa-dri/usr/lib/${DEB_HOST_MULTIARCH}/dri/
 	mv debian/tmp/usr/lib/${DEB_HOST_MULTIARCH}/dri/*_dri.so \\

```



## パッケージのビルド


次のようにしてパッケージ全体をビルドします。


```console
shell$ sudo debian/rules binary
```


Ultra96-V2 だと I/O が遅いせいか、多少時間がかかります。我慢して待っているとlibgl1-mesa-xlnx-dri パッケージが含まれた Mesa のすべての Debian パッケージが出来ます。


```console
shell$ dpkg --info ../libgl1-mesa-xlnx-dri_20.2.6-0ubuntu0.20.04.1_arm64.deb 
 new Debian package, version 2.0.
 size 5167796 bytes: control archive=1048 bytes.
    1095 bytes,    21 lines      control              
     397 bytes,     5 lines      md5sums              
 Package: libgl1-mesa-xlnx-dri
 Source: mesa
 Version: 20.2.6-0ubuntu0.20.04.1
 Architecture: arm64
 Maintainer: Debian X Strike Force <debian-x@lists.debian.org>
 Installed-Size: 18260
 Depends: libc6 (>= 2.29), libdrm-amdgpu1 (>= 2.4.100), libdrm-nouveau2 (>= 2.4.66), libdrm-radeon1 (>= 2.4.31), libdrm2 (>= 2.4.89), libelf1 (>= 0.142), libexpat1 (>= 2.0.1), libglapi-mesa (= 20.2.6-0ubuntu0.20.04.1), libllvm11 (>= 1:9~svn298832-1~), libsensors5 (>= 1:3.5.0), libstdc++6 (>= 5.2), libunwind8, libzstd1 (>= 1.3.2), zlib1g (>= 1:1.1.4)
 Section: libs
 Priority: optional
 Multi-Arch: same
 Homepage: https://mesa3d.org/
 Description: free implementation of the OpenGL API -- xlnx dri module
  This version of Mesa provides GLX and DRI capabilities: it is capable of
  both direct and indirect rendering.  For direct rendering, it can use DRI
  modules from the libgl1-mesa-dri package to accelerate drawing.
  .
  This package does not include the OpenGL library itself, only the DRI
  modules for accelerating direct rendering.
  .
  For a complete description of Mesa, please look at the
  libglx-mesa0 package.

```



## リポジトリ


この章で説明した内容は以下の github リポジトリに用意しています。



  * https://github.com/ikwzm/mesa-xlnx




# Debian パッケージの提供


前章で説明したセルフビルド方法は、ビルド環境に Ultra96 + Ubuntu 20.04 が必要なこともあって、とっても面倒です。そこですでに Ubuntu 20.04 用にビルドした Debian パッケージを以下の github リポジトリに用意しています。自分でビルドするのが面倒な人はダウンロードしてください。



* https://github.com/ikwzm/ZynqMP-FPGA-Ubuntu20.04-Lima-Ultra96

  * libgl1-mesa-xlnx-dri_20.2.6-0ubuntu0~20.04.1_arm64.deb




# 補足 libGL が DRI ドライバをロードする仕組み


この章では、libGL が DRI ドライバをロードする仕組みを説明します。


## dri3_create_screen


DRI3 の場合は、dri3_create_screen() を呼び出してスクリーンを生成します。このスクリーン生成関数は次のような手順で DRI ドライバをロードします。



1. glx_scrren_init() を呼び出して初期化します。
2. loader_dri3_open() を呼び出して X-Server が現在使っている DRM ドライバのファイルディスクリプタ(psc->fd)を得ます。   
例) X-Server が /dev/dri/card0 を使っていたならばこのデバイスファイルのファイルディスクリプタ
3. loader_get_driver_for_fd() を呼び出して 2で得たファイルディスクリプタのDRM ドライバ名(driverName)を得ます。   
例) /dev/dri/card0 のDRMドライバ名が xlnx ならば "xlnx"
4. driOpenDriver() を呼び出して3で得たDRI ドライバ名の DRI ドライバをロードします。driOpenDriver() は次節で説明します。


```C:mesa-20.2.6/src/glx/dri3_glx.c
static struct glx_screen *
dri3_create_screen(int screen, struct glx_display * priv)
{
   xcb_connection_t *c = XGetXCBConnection(priv->dpy);
   const __DRIconfig **driver_configs;
   const __DRIextension **extensions;
   const struct dri3_display *const pdp = (struct dri3_display *)priv->dri3Display;
   struct dri3_screen *psc;
   __GLXDRIscreen *psp;
   struct glx_config *configs = NULL, *visuals = NULL;
   char *driverName, *tmp;
   int i;
   unsigned char disable;
   psc = calloc(1, sizeof *psc);
   if (psc == NULL)
      return NULL;
   psc->fd = -1;
   if (!glx_screen_init(&psc->base, screen, priv)) {
      free(psc);
      return NULL;
   }


   psc->fd = loader_dri3_open(c, RootWindow(priv->dpy, screen), None);
   if (psc->fd < 0) {
      int conn_error = xcb_connection_has_error(c);
      
      glx_screen_cleanup(&psc->base);
      free(psc);
      InfoMessageF("screen %d does not appear to be DRI3 capable\\n", screen);
      if (conn_error)
         ErrorMessageF("Connection closed during DRI3 initialization failure");
      return NULL;
   }
   psc->fd = loader_get_user_preferred_fd(psc->fd, &psc->is_different_gpu);

   driverName = loader_get_driver_for_fd(psc->fd);
   if (!driverName) {
      ErrorMessageF("No driver found\\n");
      goto handle_error;
   }
   extensions = driOpenDriver(driverName, &psc->driver);
   if (extensions == NULL)
      goto handle_error;
	:
	(後略)
	:
}
```





## driOpenDriver


driOpenDriver() は loader_open_driver() を呼び出します。その際、DRI ドライバをサーチするディレクトリの環境変数名を渡します。loader_open_driver() は次節で説明します。


```C:mesa-20.2.6/src/glx/dri_commont.c
_X_HIDDEN const __DRIextension **
driOpenDriver(const char *driverName, void **out_driver_handle)
{
   void *glhandle;
   /* Attempt to make sure libGL symbols will be visible to the driver */
   glhandle = dlopen(GL_LIB_NAME, RTLD_NOW | RTLD_GLOBAL);
   static const char *search_path_vars[] = {
      "LIBGL_DRIVERS_PATH",
      "LIBGL_DRIVERS_DIR", /* deprecated */
      NULL
   };
   const __DRIextension **extensions =
      loader_open_driver(driverName, out_driver_handle, search_path_vars);
   if (glhandle)
      dlclose(glhandle);
   return extensions;
}

```



## loader_open_driver


loader_open_driver() は次の手順で指定された DRI ドライバ名に対応した動的ライブラリをメモリにロードします。



1. サーチするパス名の配列(search_paths) を作ります。
2. sprintf("%.*s/%s_dri.so",p, driver_name) という名前の動的ライブラリを dlopen() によりメモリにロードします。p はサーチするパス名です。   
例) driver_name が "xlnx" ならば、サーチするディレクトリから "xlnx_dri.so" を探します。
3. loader_get_extensions_name(driver_name) を呼んでget_extensions_name を得ます。   
例) driver_name が "xlnx" ならば、"\_\_driDriverGetExtensions_xlnx" になります。
4. 動的ライブラリの get_extensions_name で指定されたシンボルのアドレス(get_extensions) を得ます。
5. get_extensions で指定されたアドレスを関数呼び出しして extensions を得ます。


```C:mesa-20.2.6/src/loader/loader.c
const struct __DRIextensionRec **
loader_open_driver(const char *driver_name,
                   void **out_driver_handle,
                   const char **search_path_vars)
{
   char path[PATH_MAX], *search_paths, *next, *end;
   char *get_extensions_name;
   const struct __DRIextensionRec **extensions = NULL;
   const struct __DRIextensionRec **(*get_extensions)(void);
   search_paths = NULL;
   if (geteuid() == getuid() && search_path_vars) {
      for (int i = 0; search_path_vars[i] != NULL; i++) {
         search_paths = getenv(search_path_vars[i]);
         if (search_paths)
            break;
      }
   }
   if (search_paths == NULL)
      search_paths = DEFAULT_DRIVER_DIR;

   void *driver = NULL;
   end = search_paths + strlen(search_paths);
   for (char *p = search_paths; p < end; p = next + 1) {
      int len;
      next = strchr(p, ':');
      if (next == NULL)
         next = end;
      len = next - p;
#if USE_ELF_TLS
      snprintf(path, sizeof(path), "%.*s/tls/%s_dri.so", len, p, driver_name);
      driver = dlopen(path, RTLD_NOW | RTLD_GLOBAL);
#endif
      if (driver == NULL) {
         snprintf(path, sizeof(path), "%.*s/%s_dri.so", len, p, driver_name);
         driver = dlopen(path, RTLD_NOW | RTLD_GLOBAL);
         if (driver == NULL)
            log_(_LOADER_DEBUG, "MESA-LOADER: failed to open %s: %s\\n",
                 path, dlerror());
      }
      /* not need continue to loop all paths once the driver is found */
      if (driver != NULL)
         break;
   }
   if (driver == NULL) {
      log_(_LOADER_WARNING, "MESA-LOADER: failed to open %s (search paths %s)\\n",
           driver_name, search_paths);
      *out_driver_handle = NULL;
      return NULL;
   }
   log_(_LOADER_DEBUG, "MESA-LOADER: dlopen(%s)\\n", path);

   get_extensions_name = loader_get_extensions_name(driver_name);
   if (get_extensions_name) {
      get_extensions = dlsym(driver, get_extensions_name);
      if (get_extensions) {
         extensions = get_extensions();
      } else {
         log_(_LOADER_DEBUG, "MESA-LOADER: driver does not expose %s(): %s\\n",
              get_extensions_name, dlerror());
      }
      free(get_extensions_name);
   }
   if (!extensions)
      extensions = dlsym(driver, __DRI_DRIVER_EXTENSIONS);
   if (extensions == NULL) {
      log_(_LOADER_WARNING,
           "MESA-LOADER: driver exports no extensions (%s)\\n", dlerror());
      dlclose(driver);
   }
   *out_driver_handle = driver;
   return extensions;
}

```



## loader_get_extensions_name


loader_get_extensions_name() は driver_name で指定された DRI ドライバ名に対応するエントリポイント名を返します。例えば、driver_name が "xlnx" だった場合、"\_\_driDriverGetExtensions_xlnx" になります。




```C:mesa-20.2.6/src/loader/loader.c
char *
loader_get_extensions_name(const char *driver_name)
{
   char *name = NULL;
   if (asprintf(&name, "%s_%s", __DRI_DRIVER_GET_EXTENSIONS, driver_name) < 0)
      return NULL;
   const size_t len = strlen(name);
   for (size_t i = 0; i < len; i++) {
      if (name[i] == '-')
         name[i] = '_';
   }
   return name;
}

```





## \_\_driDriverGetExtensions_xlnx


エントリポイント(\_\_driDriverGetExtensions_xlnx) は動的ライブラリ(xlnx_dri.so) に定義されています。このエントリポイントは前章で説明した DRI Lima のビルドの際に追加されたものです。




```C:mesa-20.2.6/src/gallium/targets/dri/target.c
#define DEFINE_LOADER_DRM_ENTRYPOINT(drivername)                          \\
const __DRIextension **__driDriverGetExtensions_##drivername(void);       \\
PUBLIC const __DRIextension **__driDriverGetExtensions_##drivername(void) \\
{                                                                         \\
   globalDriverAPI = &galliumdrm_driver_api;                              \\
   return galliumdrm_driver_extensions;                                   \\
}
  :
  :
DEFINE_LOADER_DRM_ENTRYPOINT(xlnx)
  :

```





# 参考


* Lima web (https://gitlab.freedesktop.org/lima/web)
* Mesa 3D and Direct Rendering Infrastructure wiki  (https://dri.freedesktop.org/wiki)
* https://github.com/ikwzm/ZynqMP-FPGA-Ubuntu20.04-Lima-Ultra96
* https://github.com/ikwzm/mesa-xlnx



[概要編]: https://qiita.com/ikwzm/items/642df8c14fd3a9e782b6 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(概要編)』@Qiita"
[DRM Lima 編]: https://qiita.com/ikwzm/items/91adfcf05e767d33be70 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRM Lima 編)』@Qiita"
[DRI Lima 編]: https://qiita.com/ikwzm/items/ec29ae456ead67438733 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(DRI Lima 編)』@Qiita"
[共有バッファ編]: https://qiita.com/ikwzm/items/ed2ce2d8bb90391d7c65 "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(共有バッファ編)』@Qiita"
[ストライド問題編]: https://qiita.com/ikwzm/items/5c4c0114c15cfba8e95b "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(ストライド問題編)』@Qiita"
[インストール編]: https://qiita.com/ikwzm/items/4dfb8f06ddf9d59e310c "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(インストール編)』@Qiita"
[Ubuntu Desktop ビルド編]: https://qiita.com/ikwzm/items/21a59e68d30ad9faad6a "『Ultra96/Ultra96-V2 向け Ubuntu20.04で Lima を動かしてみた(Ubuntu Desktop ビルド編)』@Qiita"
[『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(DRI Lima 編)』@Qiita]: https://qiita.com/ikwzm/items/a5681608932a97bf2037 "『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(DRI 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"