内容和参考

内容

之前学习了drm驱动的初始化,下边看drm用户态接口是怎么操作的

  1. static const struct file_operations vkms_fops = { // 用户态操作接口
  2. .owner = THIS_MODULE,
  3. .open = drm_open,
  4. .release = drm_release,
  5. .unlocked_ioctl = drm_ioctl,
  6. .poll = drm_poll,
  7. .read = drm_read,
  8. };
  9. static struct drm_driver vkms_driver = {
  10. .fops = &vkms_fops,
  11. ......
  12. };

另外,学习了struct drm_driver 的部分字段是如何使用:

  1. struct drm_driver {
  2. // open 和 postclose 都是在drm_open的末尾 或者 drm_release 的 开始调用,分配程序专有
  3. // 没有规定必须要实现,根据自己需要
  4. int (*open) (struct drm_device *, struct drm_file *);
  5. void (*postclose) (struct drm_device *, struct drm_file *);
  6. void (*preclose) (struct drm_device *, struct drm_file *file_priv); // LEGACY,不用管
  7. void (*lastclose) (struct drm_device *);
  8. int (*dumb_create)(struct drm_file *file_priv,
  9. struct drm_device *dev,
  10. struct drm_mode_create_dumb *args); // !!! 必须实现,DRM_IOCTL_MODE_CREATE_DUMB 接口使用
  11. int (*dumb_map_offset)(struct drm_file *file_priv,
  12. struct drm_device *dev, uint32_t handle,
  13. uint64_t *offset); // !!! 可选实现,如果不实现则使用 drm_gem_create_mmap_offset()
  14. int (*dumb_destroy)(struct drm_file *file_priv,
  15. struct drm_device *dev,
  16. uint32_t handle); // !!! 可选实现,如果不实现则使用drm_gem_dumb_destroy()
  17. // 销毁用户使用dumb buffer, 只是将引用计数-1,为0时释放;
  18. const struct drm_ioctl_desc *ioctls; // 如果用户有自定义ioctl命令,则必须要实现
  19. int num_ioctls;
  20. };

open接口

  • 调用情景 : drm_open中会创建drm_file描述, 如果定义了 @open 接口 ,在打开新的 &struct drm_file 时的驱动程序回调。
  • 作用: 对于设置驱动程序专用的数据结构(如缓冲区分配器,执行上下文或类似内容)很有用。
  • 释放: 此类驱动程序专用资源必须在 @postclose中 再次释放。

由于DRM的显示/模式集只能完全由一个&struct drm_file拥有(请参阅&drm_file.is_master和&drm_device.master),因此永远不需要在此回调中设置任何与模式集相关的资源。 这样做将是驱动程序设计错误。

postclose接口

  • 调用情景 : 关闭新的&struct drm_file时,驱动程序回调之一。
  • 作用: 删除 @open中分配的驱动程序专用数据结构,例如缓冲区分配器,执行上下文或类似内容。

由于DRM的显示/模式集只能由一个&struct drm_file拥有(请参阅&drm_file.is_master和&drm_device.master),因此在此回调中永远不需要拆除任何与模式集相关的资源。 这样做将是驱动程序设计错误。

lastclose接口

  • 调用情景: @postclose之后调用, 在关闭最后一个&struct drm_file并且当前没有用于&struct drm_device的用户空间客户端时调用。
  • 作用:现驱动程序仅应使用此方法通过drm_fb_helper_restore_fbdev_mode_unlocked()强制还原fbdev帧缓冲区。 ```c // linux-git/drivers/gpu/drm/drm_fb_helper.c

void drm_fb_helper_lastclose(struct drm_device *dev) { drm_fb_helper_restore_fbdev_mode_unlocked(dev->fb_helper); }

  1. <a name="e8qxY"></a>
  2. ### 参考<br />
  3. - [何小龙-DRM 驱动程序开发(VKMS)](https://blog.csdn.net/hexiaolong2009/article/details/105180621)和 [最简单的DRM应用程序](https://blog.csdn.net/hexiaolong2009/article/details/83721242)
  4. - [蜗窝Linux graphic subsytem-1](http://www.wowotech.net/graphic_subsystem/graphic_subsystem_overview.html) 和 [蜗窝Linux graphic subsytem-2](http://www.wowotech.net/linux_kenrel/dri_overview.html)
  5. - [Linux DRM Internals](https://www.kernel.org/doc/html/v5.12/gpu/drm-internals.html)
  6. <a name="gDESh"></a>
  7. ## drm软件接口
  8. 文章都是参考 Linux-5.8学习的内容
  9. ```bash
  10. linux-5.8\drivers\gpu\drm\drm_drv.c
  11. linux-5.8\drivers\gpu\drm\drm_file.c // !!! 本节重点
  12. linux-5.8\drivers\gpu\drm\drm_ioctl.c // !!! 本节重点
  13. linux-5.8\include\drm\drm_drv.h

drm_open

软件代码流程如下:

  1. linux-5.8\drivers\gpu\drm\drm_file.c
  2. drm_open
  3. => struct drm_minor *minor = drm_minor_acquire(iminor(inode)); // 根据次设备ID获取对应的drm_minor
  4. => drm_dev_get(minor->dev); // kref++;
  5. => drm_open_helper(filp, minor); // 分配 struct drm_file *priv; 并将file->private = private_data;也挂到struct drm_device *dev->filelist链表
  6. => struct drm_file *priv = drm_file_alloc(struct drm_minor *minor); // 分配 struct drm_file *priv;
  7. => struct drm_driver *driver->open(struct drm_device *dev, struct drm_file *file) !!!接口调用

drm_release

一般来说,都是open的反向操作

drm_ioctl

drm_ioctl分为以下几个细节:

  • 首先 内核用struct drm_ioctl_desc 来描述一个cmd 以及它的处理函数;

    1. struct drm_ioctl_desc {
    2. unsigned int cmd;
    3. enum drm_ioctl_flags flags;
    4. drm_ioctl_t *func;
    5. const char *name;
    6. };
  • 其次,CMD在 0x40-0xA0间是用户自定义命令,0x00-0x40是内核默认的处理

    1. #define DRM_COMMAND_BASE 0x40
    2. #define DRM_COMMAND_END 0xA0
    1. struct drm_driver {
    2. const struct drm_ioctl_desc *ioctls; // 用户自定义ioctl命令
    3. int num_ioctls; // 用户自定义ioctl的command个数
    4. };
  • CMD在0x00-0x40 使用内核的ioctl处理 drm_ioctls;

  • CMD在0x40-0xA0使用drm_driver中定义的ioctls进行处理;

代码实现如下:

  1. linux-5.8\drivers\gpu\drm\drm_ioctl.c
  2. drm_ioctl
  3. => ioctl = &dev->driver->ioctls[index] ? ioctl = &drm_ioctls[nr]; // !!!判断命令所属区间,从获取CMD对应的struct drm_ioctl_desc
  4. => // !!! 分配传输传出buffer: 根据CMD获取参数长度,如果size<128,用栈空间就好,如果size>128,需要malloc;
  5. => copy_from_user(kdata, (void __user *)arg, in_size); // 先将用户态数据拷贝到buffer ???问题: in_size == 0 返回什么???
  6. => drm_ioctl_kernel(filp, func, kdata, ioctl->flags); // struct file *filp, func = drm_ioctl_desc->func;
  7. // ioctl->flags = drm_ioctl_desc->flags
  8. // 分配的缓存buffer
  9. => drm_ioctl_permit(u32 flags, struct drm_file *file_priv); // 检测flags 与 drm_file的属性,暂时不用关心
  10. => func(dev, kdata, file_priv);
  11. => copy_to_user((void __user *)arg, kdata, out_size); // 将数据返回用户态 ???问题: out_size == 0返回什么???

4.驱动层-DRM用户接口 - 图1

drm应用流程

列举下drm基本操作都用了哪些过程

最简单的drm操作

参考: 最简单的DRM应用程序
来分析DRM都做了哪些步骤:

  1. int main(int argc, char **argv)
  2. {
  3. /* open the drm device */
  4. open("/dev/dri/card0");
  5. /* get crtc/encoder/connector id */
  6. drmModeGetResources(...);
  7. /* get connector for display mode */
  8. drmModeGetConnector(...);
  9. /* create a dumb-buffer */
  10. drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB);
  11. /* bind the dumb-buffer to an FB object */
  12. drmModeAddFB(...);
  13. /* map the dumb buffer for userspace drawing */
  14. drmIoctl(DRM_IOCTL_MODE_MAP_DUMB);
  15. mmap(...);
  16. /* start display */
  17. drmModeSetCrtc(crtc_id, fb_id, connector_id, mode);
  18. }

驱动要运行必须支持以下特性:

  1. DRM驱动支持MODESET;
  2. DRM驱动支持dumb-buffer(即连续物理内存);
  3. DRM驱动至少支持1个CRTC,1个Encoder,1个Connector;
  4. DRM驱动的Connector至少包含1个有效的drm_display_mode。

open

参考前边的drm_open

get_resource

这个函数是 获取 struct drm_device 中的 struct drm_mode_config mode_config 信息, 以及获取 struct drm_file 里边的 struct list_head fbs 信息

  1. DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0),
  2. struct drm_mode_card_res {
  3. __u64 fb_id_ptr; // struct drm_file *file->struct list_head fbs;
  4. __u64 crtc_id_ptr; // drm_crtc_crc_init
  5. __u64 connector_id_ptr; // drm_connector_init
  6. __u64 encoder_id_ptr; // drm_encoder_init
  7. __u32 count_fbs;
  8. __u32 count_crtcs;
  9. __u32 count_connectors;
  10. __u32 count_encoders;
  11. __u32 min_width;
  12. __u32 max_width;
  13. __u32 min_height;
  14. __u32 max_height;
  15. };

drm_mode_config 相关参数如下:

  1. struct drm_mode_config {
  2. int num_fb;
  3. struct list_head fb_list;
  4. int num_connector;
  5. struct ida connector_ida;
  6. struct list_head connector_list;
  7. int num_encoder;
  8. struct list_head encoder_list;
  9. int num_total_plane;
  10. struct list_head plane_list;
  11. int num_crtc;
  12. struct list_head crtc_list;
  13. struct list_head property_list;
  14. ......
  15. };

get_connector

这部分实现比较简单,但 drm_mode_getconnector 后期学习完kms参数后再回顾
用户态 从resouce中选择一个 connector_id 作为参数,来获取当前connector

  1. drm_public drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id); // libdrm调用接口
  2. DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, 0), // 驱动响应接口
  3. struct drm_mode_get_connector {
  4. ......
  5. __u32 connector_id; // !!!传入参数,从前边get_resource 中的 connector_id_ptr 选择需要显示的那个
  6. ......
  7. };

create_dumb

调用用户自定义的 struct drm_driver *driver->dumb_create接口 来分配一个DUMB buffer

  1. struct bo * bo_create(int fd, unsigned int format,
  2. unsigned int width, unsigned int height,
  3. unsigned int handles[4], unsigned int pitches[4],
  4. unsigned int offsets[4], enum util_fill_pattern pattern); // 用户态参考代码
  5. static struct bo *
  6. bo_create_dumb(int fd, unsigned int width, unsigned int height, unsigned int bpp); // bo_create 调用 bo_create_dumb 来创建一个dumb buffer
  7. DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, 0), // 驱动响应接口
  8. // !!!调用自定义的dumb_create
  9. struct drm_driver *driver->dumb_create(file_priv, dev, args);

addfb

使用gem申请一个framebuffer,添加到drm_file->fbs 链表中

  1. drm_public int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth,
  2. uint8_t bpp, uint32_t pitch, uint32_t bo_handle,
  3. uint32_t *buf_id); // 用户态 申请framebuffer接口
  4. DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb_ioctl, 0), // 驱动响应接口 drm_mode_addfb_ioctl->drm_mode_addfb

4.驱动层-DRM用户接口 - 图2 这里重点是 dev->mode_config.funcs->fb_create(dev, file_priv, r);
在之前说过, drm_dev_register 需要初始化 struct drm_device *dev->mode_config,其中包含了funcs

  1. struct drm_mode_config {
  2. ...
  3. const struct drm_mode_config_funcs *funcs;
  4. resource_size_t fb_base;
  5. };
  6. struct drm_mode_config_funcs {
  7. // 创建framebuffer对象
  8. struct drm_framebuffer *(*fb_create)(struct drm_device *dev,
  9. struct drm_file *file_priv,
  10. const struct drm_mode_fb_cmd2 *mode_cmd); // 支持GEM 可直接使用 drm_gem_fb_create
  11. }

参考ARM mali实现

  1. malidp_fb_create
  2. {
  3. ......
  4. return drm_gem_fb_create(dev, file, mode_cmd); // 直接调用drm_gem_fb_create 即可
  5. }
  6. static const struct drm_mode_config_funcs malidp_mode_config_funcs = {
  7. .fb_create = malidp_fb_create,
  8. .atomic_check = drm_atomic_helper_check,
  9. .atomic_commit = drm_atomic_helper_commit,
  10. };
  11. static int malidp_init(struct drm_device *drm)
  12. {
  13. drm->mode_config.funcs = &malidp_mode_config_funcs;
  14. }

map_dumb

前边分配了fb,但用户要使用必须将GEM分配的framebuffer映射到用户空间**,这样可以直接使用显存而避免了多次数据间的拷贝;**

  1. static int bo_map(struct bo *bo, void **out); // 参考 libdrm 中的bo_map实现
  2. drmIoctl(bo->fd, DRM_IOCTL_MODE_MAP_DUMB, &arg);
  3. map = drm_mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
  4. bo->fd, arg.offset);
  5. DRM_IOCTL_DEF(DRM_IOCTL_MODE_MAP_DUMB, drm_mode_mmap_dumb_ioctl, 0), // 驱动响应接口
  6. drm_mode_mmap_dumb_ioctl // 根据用户层 DRM_IOCTL_MODE_CREATE_DUMB 获取到的handle,查找对应gem的 fake offset,给map使用
  7. if (dev->driver->dumb_map_offset) // !!!前边说过,drm_driver的dumb_map_offset是可选实现,默认用drm_gem_dumb_map_offset
  8. return dev->driver->dumb_map_offset(file_priv, dev,
  9. args->handle,
  10. &args->offset);
  11. else
  12. return drm_gem_dumb_map_offset(file_priv, dev, args->handle,
  13. &args->offset);

因为framebuffer是通过gem申请的显存空间,但用户使用必须要mmap到用户地址空间来进行数据填充,怎么办?
内核对每个gem buffer用offset描述, 根据fb的handle来查找到对应的offset,给用户空间mmap。

关于GEM的部分,后期详细讲述;

set_crtc

将connector,crtc, fb绑定在一起,开始出图

  1. drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
  2. uint32_t x, uint32_t y, uint32_t *connectors, int count,
  3. drmModeModeInfoPtr mode);
  4. => DRM_IOCTL(fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
  1. drm_mode_setcrtc
  2. // crtc_req->mode_valid 这个依赖于 drmModeGetConnector 获取的conn->modes[x]; // 依赖connector的mode属性

关于crtc部分 在学习KMS后再详解这部分

destroyfb

删除之前分配的framebuffer

  1. void kms_framebuffer_free(struct kms_framebuffer *fb); // 参考libdrm中 kms_framebuffer_free 流程
  2. drmModeRmFB(device->fd, fb->id);
  3. DRM_IOCTL(fd, DRM_IOCTL_MODE_RMFB, &bufferId);
  4. drmIoctl(device->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &args);
  5. DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, 0), // 驱动响应接口, 从配置中删除一个framebuffer
  6. DRM_IOCTL_DEF(DRM_IOCTL_MODE_DESTROY_DUMB, drm_mode_destroy_dumb_ioctl, 0), // // 销毁用户使用dumb buffer, 只是将引用计数-1,为0时释放;
  7. drm_mode_rmfb_ioctl
  8. drm_mode_rmfb(dev, *fb_id, file_priv);
  9. drm_mode_destroy_dumb_ioctl
  10. drm_mode_destroy_dumb(dev, args->handle, file_priv);
  11. if (dev->driver->dumb_destroy)
  12. return dev->driver->dumb_destroy(file_priv, dev, handle); // 默认用用户自定义的销毁接口
  13. else
  14. return drm_gem_dumb_destroy(file_priv, dev, handle);

freeconnector

这部分没有内核层操作,只是吧用户空间申请的buffer释放了;

  1. // libdrm释放接口,释放libdrm 中申请的缓存
  2. drm_public void drmModeFreeConnector(drmModeConnectorPtr ptr)
  3. {
  4. drmFree(ptr->encoders);
  5. drmFree(ptr->prop_values);
  6. drmFree(ptr->props);
  7. drmFree(ptr->modes);
  8. drmFree(ptr);
  9. }

freeresource

这部分没有内核层操作,只是吧用户空间申请的buffer释放了;

  1. drm_public void drmModeFreeResources(drmModeResPtr ptr)
  2. {
  3. drmFree(ptr->fbs);
  4. drmFree(ptr->crtcs);
  5. drmFree(ptr->connectors);
  6. drmFree(ptr->encoders);
  7. drmFree(ptr);
  8. }

close

参考前边的 drm_release