天天看點

DRM GEM 驅動程式開發(dumb)

前言

在上一篇《關于 DRM 中 DUMB 和 PRIME 名字的由來》 文章中,我們知道了 dumb buffer 名字的由來。本篇,我們将一起來寫一個最簡單的 GEM 驅動程式。

驅動程式

#include <drm/drmP.h>
#include <drm/drm_gem_cma_helper.h>

static struct drm_device drm;

static const struct file_operations mygem_fops = {
        .owner = THIS_MODULE,
        .open = drm_open,
        .release = drm_release,
        .unlocked_ioctl = drm_ioctl,
        .poll = drm_poll,
        .read = drm_read,
        .mmap = drm_gem_cma_mmap,
};

static struct drm_driver mygem_driver = {
        .driver_features        = DRIVER_GEM,
        .fops                   = &mygem_fops,

        .dumb_create            = drm_gem_cma_dumb_create,
        .gem_vm_ops             = &drm_gem_cma_vm_ops,
        .gem_free_object_unlocked = drm_gem_cma_free_object,

        .name                   = "my-gem",
        .desc                   = "My GEM Driver",
        .date                   = "20200601",
        .major                  = 1,
        .minor                  = 0,
};

static int __init mygem_init(void)
{
        drm_dev_init(&drm, &mygem_driver, NULL);
        drm_dev_register(&drm, 0);

        return 0;
}

module_init(mygem_init);
           

測試程式

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

int main(int argc, char **argv)
{
        int fd;
        char *vaddr;
        struct drm_mode_create_dumb create_req = {};
        struct drm_mode_destroy_dumb destroy_req = {};
        struct drm_mode_map_dumb map_req = {};

        fd = open("/dev/dri/card0", O_RDWR);

        create_req.bpp = 32;
        create_req.width = 240;
        create_req.height = 320;
        drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
        printf("create dumb: handle = %u, pitch = %u, size = %llu\n",
                create_req.handle, create_req.pitch, create_req.size);

        map_req.handle = create_req.handle;
        drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
        printf("get mmap offset 0x%llx\n", map_req.offset);

        vaddr = mmap(0, create_req.size, PROT_WRITE, MAP_SHARED, fd, map_req.offset);
        strcpy(vaddr, "This is a dumb buffer!");
        munmap(vaddr, create_req.size);

        vaddr = mmap(0, create_req.size, PROT_READ, MAP_SHARED, fd, map_req.offset);
        printf("read from mmap: %s\n", vaddr);
        munmap(vaddr, create_req.size);

        getchar();

        destroy_req.handle = create_req.handle;
        drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
        close(fd);

        return 0;
}   
           

描述:先建立一個 dumb buffer,然後将其 mmap 到 user-space,往裡寫入一串字元串,然後重新映射,讀取并列印 buffer 中的内容。

運作結果

root@ubuntu:~# ./dumb 
create dumb: handle = 1, pitch = 960, size = 307200
get mmap offset 0x10000000
read from mmap: This is a dumb buffer!
           

驅動講解

  1. DRIVER_GEM

    :該 feature 告訴 DRM 架構本驅動支援 GEM 操作,如 buffer 的配置設定和釋放,以及 GEM OPEN/FLINK/CLOSE 等操作。
  2. dumb_create

    :配置設定 dumb buffer 的回調接口,主要完成三件事:

    (1)建立 gem object

    (2)建立 gem handle

    (3)配置設定實體 buffer (也可以等到後面再配置設定)

    本例中直接使用 CMA helper 函數實作,該函數内部會配置設定最終的實體 buffer。

  3. mmap

    :建立 dumb buffer 的目的就是要拿去給 CPU 畫圖,是以沒有 mmap 的 dumb buffer 是沒有靈魂的,是以必須實作。通常使用 drm_gem_mmap() 來實作。
  4. gem_vm_ops

    :主要為 mmap 服務,必須實作。下一篇文章會對它做詳細介紹。

通常在看 DRM 文檔時,還會提到

dumb_map_offset

dumb_destroy

這兩個接口,分别對應各自的 ioctl 函數。如果驅動沒有實作這兩個回調接口, 那麼 DRM 架構會使用預設的 drm_gem_dumb_map_offset() 和 drm_gem_dumb_destroy() 代替。

為什麼要執行 DRM_IOCTL_MODE_MAP_DUMB ?

許多人剛開始寫 dumb buffer 應用程式時都會困惑,明明有 mmap 函數,為什麼中間要再插一腳 DRM_IOCTL_MODE_MAP_DUMB ?光看名字很容易讓人誤以為該 ioctl 是在執行 mmap 的動作,那為什麼要添加這個 ioctl 呢?

要回答這個問題其實很簡單,設想一下,假如你目前建立了2個 dumb buffer,你要對其中一個做 mmap 操作,請問 mmap 函數應該如何知道你目前要操作的是哪個 buffer ?因為你目前使用的是 card0 的 fd,而不是 dumb buffer 的 fd,是以你沒法通過 fd 來對其進行區分。而 size 和 flag 這些參數都不能随意修改,是以隻能通過 offset 參數來 workaround,進而告訴 mmap 目前具體要操作的是哪個 dumb buffer。

是以,對 drm device 進行 mmap 操作時,傳進去的 offset 參數并不是真正的記憶體偏移量,而是一個 gem object 的索引值。通過該索引值,drm 驅動就可以準确定位到目前具體要操作的是哪個 gem object,進而擷取到與該 object 相對應的實體 buffer,并對其做真正的 mmap 操作。

那如何知道某個 gem object 的索引值呢?是以才有了 DRM_IOCTL_MODE_MAP_DUMB !你給它一個 gem handle,它傳回給你一個 offset,就這麼簡單!而該 ioctl 對應的底層實作就是前面提到的

dumb_map_offset

回調接口。(個人認為該 ioctl 的名字改成 DRM_IOCTL_MODE_CREATE_MAP_OFFSET 可能會更好一點 :-)

這也是為什麼上面的示例運作結果中,擷取到的 offset 值為 0x10000000,而不是我們通常認為的偏移 0,原因就在于此(DRM 文檔中将其稱為 fake offset,即僞偏移)。

總結

  1. 哪裡有 dumb_create,哪裡就有 mmap。
  2. 哪裡有 mmap,哪裡就有 gem_vm_ops。
  3. mmap 中的 offset 參數是個假的。
  4. 沒有

    DRIVER_MODESET

    DRIVER_GEM

    一樣可以做成一個獨立的 DRM 驅動。
DRM GEM 驅動程式開發(dumb)

源碼下載下傳

Github: driver,test

參考資料

  1. 楊楓_mind CSDN 部落格
  2. DRM Memory Management

文章彙總: DRM (Direct Rendering Manager) 學習簡介

繼續閱讀