注意(2022年10月12日追記)
この記事は2021年4月に投稿したものであり、古い内容が含まれています。2022年10月に以下の記事を投稿しましたので参照してください。
・『ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(ストライド問題編)』@Qiita
はじめに
Lima とは Mali-400/450 用のオープンソースなグラフィックドライバです。筆者は Lima を Ultra96/Ultra96-V2 向け Ubuntu 20.04 で試験的に動かしてみました。動かすのに少々苦労したので、その方法を何回かに分けて説明します。
- 概要編
- DRM Lima 編
- DRI Lima 編
- 共有バッファ編
- ストライド問題編(この記事)
- インストール編
- Ubuntu Desktop ビルド編
共有バッファ編で説明したバッファの共有に際して、バッファオブジェクトのストライドの不一致によるバッファ共有が失敗する問題が起こりました。この記事ではその問題の原因とその対応について説明します。
なお、現時点(2021年4月)では、Ubuntu 20.04 の gnome-shell が動作するところまで確認していますが、すべてのアプリケーションで動作を確認したわけでは無いことに注意してください。
ストライド問題
ストライドとは
2次元画像を格納するためのバッファを確保する際、画像の横幅の画素数 width 、画像の高さ(=行数) height、1画素あたりのビット数 bpp(BitPerPixel) に基づいてバッファの容量を計算します。しかし、これだけの情報だけでバッファを確保した場合、ハードウェアによっては遅くなったりそもそも動かなかったりすることがあります。
何故ならハードウェアにとって有利な画素単位 bpp や 画像の横幅 width が存在するからです。例えば、64bit CPU の場合は画像の先頭アドレスや横幅のバイト数が 8Byte(64bit) 単位でアライメントされていたほうが有利になります。あるいは、データキャッシュも考慮した場合は、データキャッシュのラインサイズ(例えば64バイト)単位でアライメントするとさらなる高速化が期待できます。
画像を格納するバッファを用意する際、画像の横幅のバイト数を、画像の横幅の画素数 width ×1画素あたりのビット数 bpp / 8Byte ではなく、ハードウェアに有利なバイト数にした方がよいことがあります。このハードウェアに有利な横幅のバイト数のことをストライドと言います。
DRI Lima のストライド
Lima はARMのMali-400/450組み込みGPUをサポートするオープンソースのグラフィックスドライバーです。Mali-400/450 は組み込み GPU なので、扱える画像サイズに次のような制限があります。
- 画像の横幅 width は16 pixel 単位でアライメントされている
- 画像の高さ height は 16 line 単位でアライメントされている
- 画像の bpp は 8ビット、16ビット、32ビットの3種類
- 画像バッファの開始アドレスは64バイトにアライメントされている
つまり、DRI Lima が想定するストライドは画像の横幅 width を16 pixel 単位で切り上げた画素数×(1Byte(=8bit) or 2Byte(=16bit) or 4Byte(=32bit)) になります。
DRM Xlnx のストライド
DRM Xlnx の GEM バッファオブジェクトを生成する部分は次のようになっています。
/*
* xlnx_gem_cma_dumb_create - (struct drm_driver)->dumb_create callback
* @file_priv: drm_file object
* @drm: DRM object
* @args: info for dumb scanout 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_cma_dumb_create()
*/
int xlnx_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *drm,
struct drm_mode_create_dumb *args)
{
int pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
unsigned int align = xlnx_get_align(drm);
if (!args->pitch || !IS_ALIGNED(args->pitch, align))
args->pitch = ALIGN(pitch, align);
return drm_gem_cma_dumb_create_internal(file_priv, drm, args);
}
ここで重要なのが、xlnx_get_align() です。ストライド(この関数内では pitch という名称) は、この関数の戻り値が示すバイト単位でアライメントされます。ZynqMP の場合、xlnx_get_align() の戻り値は256バイトです。と言うのもZynqMP の場合、DRM Xlnx はディスプレイポートコントローラーを制御しています。ディスプレイコントローラーのDMA にはストライド256バイト単位にアライメントされていなければならないようです。このストライドの制限の説明は『Zynq UltraScale+ MPSoC Technical Reference Manual UG1085(v1.0)』の「Chapter 31 DisplayPort Controller」 の「DisplayPort DMA」にあります。
STRIDE Field
A 14-bit field indicates the stride value in a 16-byte resolution. This field is only used in contiguous mode when the transfer size is larger than the line size. It is not used in fragmented mode, as it is always line wide. The stride value must be 256-byte aligned.
ストライドの不一致
ZynqMP で X Serverの DRI3拡張機能を使う場合、DRM Xlnx が提供する バッファオブジェクトを DRI Lima が直接アクセスします。その際、DRI Lima が想定しているストライドと DRM Xlnx が提供しているバッファオブジェクトのストライドが一致していないと、次のようなエラーが発生してgnome-shell が異常終了しました。
Apr 30 02:24:23 ubuntu-fpga gnome-shell[953]: error: import buffer not properly aligned
このエラーメッセージを出しているのは DRI Lima の lima_resource_from_handle() です。インポートしたバッファオブジェクトのストライドとサイズをチェックして、条件を満たさなかった場合はエラーを返しています。
static struct pipe_resource *
lima_resource_from_handle(struct pipe_screen *pscreen,
const struct pipe_resource *templat,
struct winsys_handle *handle, unsigned usage)
{
:
(中略)
:
/* check alignment for the buffer */
if (res->tiled ||
(pres->bind & (PIPE_BIND_RENDER_TARGET | PIPE_BIND_DEPTH_STENCIL))) {
unsigned width, height, stride, size;
width = align(pres->width0, 16);
height = align(pres->height0, 16);
stride = util_format_get_stride(pres->format, width);
size = util_format_get_2d_size(pres->format, stride, height);
if (res->levels[0].stride != stride || res->bo->size < size) {
debug_error("import buffer not properly aligned\\n");
goto err_out;
}
res->levels[0].width = width;
}
else
res->levels[0].width = pres->width0;
return pres;
err_out:
lima_resource_destroy(pscreen, pres);
return NULL;
}
解決方法
このストライド問題を解決するためには、DRM Xlnx と DDX Xlnx の双方を修正する必要がありました。
DRM Xlnx の修正
DRM Xlnx の GEM バッファオブジェクトを生成する際、カーネルパラメータ gem_alignment_size を使ってアライメントするバイト数を指定するようにします。gem_alignment_size が0以外の時は、その値でアライメントします。また、gem_alignment_size が0の時はxlnx_get_align() の戻り値でアライメントします。なお、互換性のため、xlnx_get_align() の戻り値でアライメントするためにgem_alignment_size の初期値は0にしています。lima を使う場合は gem_alignment_size に8 を指定します。
/*
* gem_alignment_size module parameter
*/
static int gem_alignment_size = 0;
module_param( gem_alignment_size , int, S_IRUGO);
MODULE_PARM_DESC( gem_alignment_size , "xlnx gem alignment size");
int xlnx_gem_cma_dumb_create(struct drm_file *file_priv, struct drm_device *drm,
struct drm_mode_create_dumb *args)
{
unsigned int align;
if (gem_alignment_size == 0)
align = xlnx_get_align(drm);
else
align = gem_alignment_size;
if (!args->pitch || !IS_ALIGNED(args->pitch, align))
args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), align);
DRM_DEBUG("width=%d, height=%d, bpp=%d, pitch=%d, align=%d\\n",
args->width, args->height, args->bpp, args->pitch, align);
return drm_gem_cma_dumb_create_internal(file_priv, drm, args);
}
DDX Xlnx の修正
Lima を使う場合、DRM Xlnx の gem_alignment_size に8 を指定する必要があります。そのため、ディスプレイポートコントローラーに出力するためのバッファオブジェクトでストライドのアライメントが256バイト単位にならないことがあります。このままではディスプレイに出力できません。
そこで DDX Xlnx の create_custom_gem() で、ディスプレイ出力用のバッファの場合(create_gem->buf_type == ARMSOC_BO_SCANOUT)はストライドを256バイト単位でアライメントし、それ以外の用途のバッファの場合は幅と高さを Lima の仕様にあわせてアライメントするように修正します。
#define DIV_ROUND_UP(val,d) (((val) + (d ) - 1) / (d))
#define ALIGN(val, align) (((val) + (align) - 1) & ~((align) - 1))
static int create_custom_gem(int fd, struct armsoc_create_gem *create_gem)
{
struct drm_mode_create_dumb arg;
int ret;
memset(&arg, 0, sizeof(arg));
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 256 bytes alignment */
arg.pitch = ALIGN(create_gem->width * DIV_ROUND_UP(create_gem->bpp,8), 256);
} else {
/* For Lima need height and width 16 bytes 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);
}
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;
}
参考
- 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/xf86-video-armsoc-xilinx
- 『Zynq UltraScale+ MPSoC Technical Reference Manual UG1085(v1.0)』