DRM(Direct Rendering Manager)サブシステムにおいて、GEM(Graphics Execution Manager)はグラフィックスバッファの管理、特にメモリの割り当てと解放を担う重要な役割を果たします。Linuxカーネルではstruct drm_gem_objectがGEMオブジェクトを表現し、各ドライバはこれを拡張して独自のデータを管理します。
GEMオブジェクトの構造体
1. struct drm_gem_object
カーネル内でGEMオブジェクトを表す基本構造体です。定義はinclude/drm/drm_gem.hにあります。
struct drm_gem_object {
struct kref refcount;
unsigned handle_count;
struct drm_device *dev;
struct file *filp;
struct drm_vma_offset_node vma_node;
size_t size;
int name;
struct dma_buf *dma_buf;
struct dma_buf_attachment *import_attach;
struct dma_resv *resv;
struct dma_resv _resv;
const struct drm_gem_object_funcs *funcs;
struct list_head lru_node;
struct drm_gem_lru *lru;
};
主要なメンバの役割は以下の通りです。
refcount: オブジェクトの参照カウント。ライフサイクル管理に使用します。handle_count: ユーザー空間でのハンドル数。0になるとグローバル名がクリアされます。dev: 親となるDRMデバイスへのポインタ。filp: シェアブルなバッファ(shmem)用のファイルポインタ。vma_node: メモリマッピング(mmap)を管理するためのノード。size: オブジェクトのサイズ(バイト単位)。funcs: オブジェクト固有の操作関数テーブル。
2. struct drm_gem_object_funcs
GEMオブジェクトに対する操作を定義するコールバック関数群です。ドライバはこれらを実装して、オブジェクトの解放やメモリ操作を行います。
struct drm_gem_object_funcs {
void (*free)(struct drm_gem_object *obj);
int (*open)(struct drm_gem_object *obj, struct drm_file *file);
void (*close)(struct drm_gem_object *obj, struct drm_file *file);
struct dma_buf *(*export)(struct drm_gem_object *obj, int flags);
struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
int (*vmap)(struct drm_gem_object *obj, struct iosys_map *map);
void (*vunmap)(struct drm_gem_object *obj, struct iosys_map *map);
int (*mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
const struct vm_operations_struct *vm_ops;
/* 他のメンバは省略 */
};
free: オブジェクト解放時に呼ばれる必須のコールバック。mmap: ユーザー空間へのマッピング要求を処理します。vm_ops: 仮想メモリ領域の操作構造体(ページフォルト処理など)。
主要なAPIとライフサイクル
初期化処理
GEMオブジェクトの生成は、通常drm_gem_object_initを呼び出すことで行われます。この関数はshmemファイルを作成し、オブジェクトのfilpメンバに設定します。物理メモリの確保が必要な場合、DMAバッファとして確保するか、ページフォルト時に遅延確保するかを選択できます。
一方、連続したDMAメモリが必要な組み込み系ハードウェアなどでは、shmemを使わずドライバ側でメモリ管理を行うdrm_gem_private_object_initを使用します。
ハンドル管理
ユーザー空間とカーネル空間でのオブジェクトのやり取りには32ビットの「ハンドル」が使われます。drm_gem_handle_create関数は、DRMファイルコンテキスト内で一意のID(ハンドル)を割り当て、IDR(Integer ID Management)システムにオブジェクトを登録します。
メモリマッピング (mmap)
GEMオブジェクト自体はファイルディスクリプタを持たないため、DRMデバイスのファイルに対してmmapシステムコールを行い、オフセット値で対象のGEMオブジェクトを指定します。ドライバはdrm_gem_create_mmap_offsetを用いて仮想オフセットを生成し、ユーザー空間へ渡します。実装方法として、まとめてマッピングする方法と、ページフォルトを利用して必要なページのみをマッピングする方法があります。
Rockchip DRMドライバにおける実装
Rockchip RK3399のDRMドライバ(rockchip_drm_gem.c等)における具体的な実装を見ていきます。
1. 拡張GEMオブジェクト
ドライバは独自の情報を保持するためstruct rockchip_gem_objectを定義しています。
/* drivers/gpu/drm/rockchip/rockchip_drm_gem.h */
struct rockchip_gem_object {
struct drm_gem_object base;
unsigned int flags;
void *kvaddr; /* カーネル仮想アドレス */
dma_addr_t dma_addr; /* DMAアドレス */
unsigned long dma_attrs;
/* IOMMU有効時の管理用 */
struct drm_mm_node mm;
unsigned long num_pages;
struct page **pages;
struct sg_table *sgt;
size_t size;
};
2. ダムバッファの作成
dumb_createコールバックとして登録されるrockchip_gem_dumb_create関数は、ソフトウェアレンダリング用のバッファ確保処理を行います。
/* ドライバ定義の変数名を変更し、ロジックを整理 */
static struct rockchip_gem_object *
create_gem_obj_with_handle(struct drm_file *file_priv,
struct drm_device *drm_dev,
unsigned int alloc_size,
unsigned int *out_handle)
{
struct rockchip_gem_object *r_gem_obj;
struct drm_gem_object *d_gem_obj;
bool is_fb;
int ret;
/* フレームバッファ用途か判定 */
is_fb = drm_dev->fb_helper && file_priv == drm_dev->fb_helper->client.file;
/* オブジェクト本体の作成 */
r_gem_obj = rockchip_gem_create_object(drm_dev, alloc_size, is_fb);
if (IS_ERR(r_gem_obj))
return ERR_CAST(r_gem_obj);
d_gem_obj = &r_gem_obj->base;
/* ハンドルの作成 */
ret = drm_gem_handle_create(file_priv, d_gem_obj, out_handle);
if (ret)
goto err_free_obj;
/* 作成時の参照を解放(ハンドルが参照を保持するため) */
drm_gem_object_put(d_gem_obj);
return r_gem_obj;
err_free_obj:
rockchip_gem_free_object(d_gem_obj);
return ERR_PTR(ret);
}
この関数内では、ピッチ(幅のバイト数)のアライメント調整を行った後、実際のメモリ確保を行います。Rockchipドライバでは、IOMMUの有無に応じてメモリ確保方法を切り替えます。
メモリ確保の流れ
- IOMMU無効の場合:
dma_alloc_attrsを呼び出し、連続したDMAバッファを確保します。Write-Combine属性などを指定してパフォーマンスを調整します。 - IOMMU有効の場合:
rockchip_gem_get_pagesでページを取得し、IOMMUのアドレス空間にマッピングします。これにより物理メモリの連続性を気にせず柔軟な確保が可能になります。
3. フレームバッファの作成
DRMフレームバッファはGEMオブジェクトを参照して構築されます。Rockchipドライバではrockchip_fb_create関数がこれを担当します。
この関数は、ユーザー空間から渡されたハンドルに対応するGEMオブジェクトを検索(drm_gem_object_lookup)し、フレームバッファ構造体(struct drm_framebuffer)に登録します。AFBC(Arm Frame Buffer Compression)対応の場合は追加の初期化処理が行われます。
/* フレームバッファ初期化ヘルパー呼び出しの例 */
ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base, file, mode_cmd,
&rockchip_drm_fb_funcs);
drm_gem_fb_init_with_funcsは、各カラープレーンのGEMオブジェクトを取得し、サイズの検証を行った上でフレームバッファオブジェクトを初期化する汎用ヘルパー関数です。