LoginSignup
1
1

ZynqMP 向け Ubuntu22.04で Lima を動かしてみた(ストライド問題編)

Last updated at Posted at 2022-10-10

はじめに

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

共有バッファ編で説明したバッファの共有に際して、バッファオブジェクトのストライドの不一致によるバッファ共有が失敗する問題が起こりました。この記事ではその問題の原因とその対応について説明します。

ストライド問題

ストライドとは

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 バッファオブジェクトを生成する部分は次のようになっています。

drivers/gpu/drm/xlnx/xlnx_gem.c.c
/*
 * 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 が異常終了しました。

/var/log/syslog.c
Apr  30 02:24:23 ubuntu-fpga gnome-shell[953]: error: import buffer not properly aligned

このエラーメッセージを出しているのは DRI Lima の lima_resource_from_handle() です。インポートしたバッファオブジェクトのストライドとサイズをチェックして、条件を満たさなかった場合はエラーを返しています。

mesa-20.2.6/src/gallium/drivers/lima/lima_resource.c
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 バッファオブジェクトを生成する際、引数に特に指定しなかった場合(scanout=falseの場合)は、カーネルパラメータ xlnx_dumb_alignment_size を使ってアライメントするバイト数を指定するようにします。xlnx_dumb_alignment_size が0以外の時は、その値でアライメントします。また、xlnx_dumb_alignment_size が0の時はxlnx_get_align() の戻り値でアライメントします。

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_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, bool scanout)
{
	struct xlnx_drm *xlnx_drm = drm->dev_private;
	if (scanout || xlnx_dumb_alignment_size == 0)
		return xlnx_crtc_helper_get_align(xlnx_drm->crtc);
	else
		return xlnx_dumb_alignment_size;
}

drivers/gpu/drm/xlnx/xlnx_gem.c
/*
 * xlnx_gem_dumb_cma_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_cma_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);
	int  align   = xlnx_get_align(drm, scanout);
 
 	if (!args->pitch || !IS_ALIGNED(args->pitch, align))
		args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), align);
 
 	return drm_gem_cma_dumb_create_internal(file_priv, drm, args);
 }

Kconfig を修正して、Linux Kernel のビルド時に初期値を指定できるようにします。なお、互換性のため、xlnx_get_align() の戻り値でアライメントするためにxlnx_dumb_alignment_size の初期値は0にしています。

drivers/gpu/drm/xlnx/Kconfig
config DRM_XLNX_DUMB_ALIGNMENT_DEFAULT_SIZE
	int "Xilinx DRM Dumb Buffer Alignment Default Size"
	range 0 4096
	default 0
	depends on DRM_XLNX
	help
	  This specifies the default value for dumb buffer alignment size 
	  of the Xilinx DRM Driver. The dumb buffer alignment size can 
	  also be set in the module parameter. If not set by module 
	  parameter, this value will be set to dumb buffer alignment size.
	  If 0 is specified for the dumb buffer alignment size, Xilinx 
	  DRM forces the alignment size to 256, which is the ZYNQMP_DPDMA 
	  alignment size. When used with the Lima DRM driver, the dumb 
	  buffer alignment size must be set to 8.

xlnx_dumb_alignment_size の値は ioctl() からも変更できるようにします。

include/uapi/drm/xlnx_drm.h
#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,
};
/**
 * 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)
	
#if defined(__cplusplus)
}
#endif
#endif /* __XLNX_DRM_H__ */
drivers/gpu/drm/xlnx/xlnx_drv.c
	:
	(前略)
	:
#include <drm/xlnx_drm.h>
	:
	(中略)
	:
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;
	default:
		return -EINVAL;
	}
	return 0;
}
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;
	default:
		return -EINVAL;
	}
	return 0;
}
	:
	(中略)
	:
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),
};
	:
	(中略)
	:
static struct drm_driver xlnx_drm_driver = {
	.driver_features		= DRIVER_MODESET | DRIVER_GEM |
	:
	(中略)
	:
	.ioctls	                        = xlnx_drm_driver_ioctls,
	.num_ioctls                     = ARRAY_SIZE(xlnx_drm_driver_ioctls),
	:
	(中略)
	:
};

DDX Xlnx の修正

Lima を使う場合、DRM Xlnx の xlnx_dumb_alignment_size に8 を指定する必要があります。そのため、ディスプレイポートコントローラーに出力するためのバッファオブジェクトでストライドのアライメントが256バイト単位にならないことがあります。このままではディスプレイに出力できません。

そこで DDX Xlnx では、初期化時に ioctl を使って xlnx_dumb_alignment_size に8を設定します。

src/drmmode_xilinx/drmmode_xilinx.c
#include <stdlib.h>
#include <drm.h>
#include <xf86drm.h>
#include "../drmmode_driver.h"
#include "xlnx_drm.h"
static int  xlnx_drmmode_initialized    = 0;
static int  xlnx_drm_scanout_align_size = 256;
static int  xlnx_drm_dumb_align_size    = 8;
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) 
		goto init_done;
	if (get_param_arg.value != DRM_XLNX_DRIVER_IDENTIFIER) 
		goto init_done;
	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) 
		goto init_done;
	xlnx_drm_scanout_align_size = get_param_arg.value;
	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) 
		goto init_done;
    init_done:
}

また、 create_custom_gem() で、ディスプレイ出力用のバッファの場合(create_gem->buf_type == ARMSOC_BO_SCANOUT)はストライドを256バイト単位でアライメントし、それ以外の用途のバッファの場合は幅と高さを Lima の仕様にあわせてアライメントするように修正します。

src/drmmode_xilinx/drmmode_xilinx.c
#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;
	if (xlnx_drmmode_initialized != 1) {
		xlnx_drmmode_init(fd);
		xlnx_drmmode_initialized = 1;
	}
	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 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;
	} 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;
	}
	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;
}

参考

ブートイメージ

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

DRM Xlnx

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

DDX Xlnx

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

また、以下の URL に、この記事で紹介した DDX Xlnx のDebian Package を提供しています。

旧記事

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

その他

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1