当前位置:   article > 正文

Linux的DisplayLink设备驱动分析

displaylink

简介

DisplayLink是USB接口的显示器(USB显示器)的实现技术,支持windows、Linux、macOS等。windows10的DisplayLink驱动可以支持GPU渲染加速(超出了本人的知识范围,不做详细描述),但Linux上的DisplayLink驱动只能使用CPU渲染,本文主要分析Linux上的DisplayLink驱动,下文描述的DisplayLink都是指Linux上的DisplayLink驱动,下文不再赘述。

本文使用的DisplayLink的源码版本为 evdi-1.14.4 版本,下载地址为GitHub - DisplayLink/evdi: Extensible Virtual Display Interface

原理

Linux上的DisplayLink驱动(称为evdi)包含内核态DRM驱动和用户态libevdi库等,其整体框架如下图所示:

  • 显示服务器(xorg 或 wayland)通过libdrm访问控制evdi内核态DRM驱动,并将图像渲染到evdi DRM设备的dumb缓冲区中;
  • libevdi 为用户态程序提供了抓取图像、监听事件等的接口;
  • libusb 为用户态程序提供了读写USB设备的数据的接口;
  • DisplayLinkManager 用于管理控制DisplayLink显示器(该部分代码未开源);

源码

evdi驱动中使用了platform设备(平台设备)和drm设备。平台设备用于管理(添加和删除)drm设备。drm设备用于向显示服务器提供渲染处理服务,evdi支持多个drm设备。

注意:evdi驱动的代码中有建立usb与evdi的平台设备的关系的逻辑,这部分逻辑只是为了能在sysfs中体现evdi平台设备与usb显示器之间的关系而言,暂无额外功能。

模块初始化和反初始化

  1. evdi_init (evdi_platform_drv.c) evdi驱动模块初始化
  2. 调用 root_device_register 为evdi创建根设备
  3. 设置 g_ctx.usb_notifier.notifier_call 为 evdi_platform_drv_usb
  4. 调用 usb_register_notify 注册usb通知块 g_ctx.usb_notifier
  5. evdi_sysfs_init (evdi_sysfs.c) 为evdi的根设备创建sysfs文件
  6. 调用 platform_driver_register 注册evdi的平台设备驱动 evdi_platform_driver
  7. evdi_platform_add_devices (evdi_platform_drv.c) 添加指定数量的evdi的平台设备
  8. evdi_exit (evdi_platform_drv.c) evdi驱动模块反初始化
  9. evdi_platform_remove_all_devices
  10. 调用 platform_driver_unregister反注册evdi的平台设备驱动 evdi_platform_driver
  11. evdi_sysfs_exit (evdi_sysfs.c) 删除evdi根设备的sysfs文件
  12. usb_unregister_notify 反注册usb通知块
  13. 调用 root_device_unregister 反注册evdi的根设备

evdi的平台设备驱动为evdi_platform_driver(struct platform_driver 类型的静态变量),见:

  1. evdi_platform_driver (evdi_drv.c)
  2. .probe = evdi_platform_probe,
  3. .remove = evdi_platform_remove,

drm设备

在添加evdi的平台设备后,设备驱动框架会触发evdi的设备发现回调 evdi_platform_probe (evdi_platform_drv.c) ,在该回调中会添加evdi的drm设备。而添加evdi的平台设备的方式有两种:

1. 在模块初始化时,注册evdi的平台设备驱动后,在最后会创建指定数量(通过模块的初始化参数指定)的evdi的平台设备;

2. 用户空间程序通过evdi的根设备的sysfs文件添加evdi的平台设备,add_store (evdi_sysfs.c) -> evdi_platform_add_devices (evdi_platform_drv.c);

  1. evdi_platform_device_probe (evdi_platform_drv.c) 创建evdi的平台设备
  2. evdi_drm_device_create (evdi_drm_drv.c)
  3. evdi_drm_device_create (evdi_drm_drv.c) 创建evdi的drm设备
  4. 调用 drm_dev_alloc 创建evdi的drm设备,其驱动为driver (evdi_drm_drv.c)
  5. evdi_drm_device_init (evdi_drm_drv.c) 初始化evdi的drm设备
  6. drm_dev_register 注册evdi的drm设备

evdi的drm设备的驱动为driver (evdi_drm_drv.c) (struct drm_driver类型的静态变量):

  1. driver (evdi_drm_drv.c)
  2. .open = evdi_driver_open,
  3. .postclose = evdi_driver_postclose,
  4. /* gem hooks */
  5. #if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8)
  6. #elif KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE
  7. .gem_free_object_unlocked = evdi_gem_free_object,
  8. #else
  9. .gem_free_object = evdi_gem_free_object,
  10. #endif
  11. #if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8)
  12. #else
  13. .gem_vm_ops = &evdi_gem_vm_ops,
  14. #endif
  15. .dumb_create = evdi_dumb_create,
  16. .dumb_map_offset = evdi_gem_mmap,
  17. #if KERNEL_VERSION(5, 12, 0) <= LINUX_VERSION_CODE || defined(EL8)
  18. #else
  19. .dumb_destroy = drm_gem_dumb_destroy,
  20. #endif
  21. .ioctls = evdi_painter_ioctls,
  22. .num_ioctls = ARRAY_SIZE(evdi_painter_ioctls),
  23. .fops = &evdi_driver_fops,
  24. ......

evdi的drm设备的自定义ioctl指令列表为:

  1. evdi_painter_ioctls (evdi_drm_drv.c) (struct drm_ioctl_desc类型的静态变量)
  2. EVDI_CONNECT --> evdi_painter_connect_ioctl,
  3. EVDI_REQUEST_UPDATE --> evdi_painter_request_update_ioctl
  4. EVDI_GRABPIX --> evdi_painter_grabpix_ioctl,
  5. EVDI_DDCCI_RESPONSE --> evdi_painter_ddcci_response_ioctl
  6. EVDI_ENABLE_CURSOR_EVENTS --> evdi_painter_enable_cursor_events_ioctl

usb显示器连接和断开的ioctl指令:

  1. evdi_painter_connect_ioctl (evdi_painter.c) 显示器的连接与断开操作
  2. 判断是否为显示器连接:
  3. 若是,则 evdi_painter_connect (evdi_painter.c)
  4. 否则,evdi_painter_disconnect (evdi_painter.c)

从evdi的drm驱动中抓图的ioctl指令EVDI_GRABPIX(将evdi的帧缓冲区的主图像和光标图像拷贝到用户空间指定的缓冲区中):

  1. evdi_painter_grabpix_ioctl (evdi_painter.c)
  2. ...
  3. merge_dirty_rects
  4. ...
  5. copy_primary_pixels
  6. copy_cursor_pixels
  7. ...

请求evdi的drm驱动更新图像的ioctl指令,evdi的drm驱动在更新完成后会主动通知用户空间程序更新已完成(通过向drm设备写入evdi的更新已完成的事件),用户空间的libevdi库会监听drm设备的句柄(使用select),当发现drm设备可读时,则会读取并处理evdi内核态的drm驱动写入的事件(参见libevdi的 evdi_handle_events 函数),若是图像更新已完成的事件,则会从evdi的drm驱动中抓取图像,这是一种异步图像更新机制。

  1. evdi_painter_request_update_ioctl (evdi_painter.c) 请求更新图像
  2. 设置 struct evdi_painter 类型的 was_update_requested
  3. 若需要则触发已更新完成的事件
  4. evdi_painter_send_update_ready_if_needed (evdi_painter.c)
  5. struct evdi_painter 类型的 was_update_requested 为真,则:
  6. 1. evdi_painter_send_update_ready (evdi_painter.c)
  7. 2. 设置was_update_requested 为假
  8. evdi_painter_send_update_ready (evdi_painter.c) 触发已更新完成的事件
  9. 构造 DRM_EVDI_EVENT_UPDATE_READY 更新事件
  10. evdi_painter_send_event (evdi_painter.c)

后记

先写到这里,具体细节还需自行分析源码。

特别说明:本文仅为本文的浅见,如有错误,烦请指正。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/964208
推荐阅读
相关标签
  

闽ICP备14008679号