Linux的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显示器之间的关系而言,暂无额外功能。

模块初始化和反初始化

evdi_init (evdi_platform_drv.c)  evdi驱动模块初始化
   调用 root_device_register  为evdi创建根设备
   设置 g_ctx.usb_notifier.notifier_call 为 evdi_platform_drv_usb
   调用 usb_register_notify 注册usb通知块 g_ctx.usb_notifier
   evdi_sysfs_init (evdi_sysfs.c)  为evdi的根设备创建sysfs文件
   调用 platform_driver_register  注册evdi的平台设备驱动 evdi_platform_driver
   evdi_platform_add_devices (evdi_platform_drv.c)  添加指定数量的evdi的平台设备

evdi_exit (evdi_platform_drv.c)  evdi驱动模块反初始化
   evdi_platform_remove_all_devices
   调用 platform_driver_unregister反注册evdi的平台设备驱动 evdi_platform_driver
   evdi_sysfs_exit (evdi_sysfs.c)  删除evdi根设备的sysfs文件
   usb_unregister_notify  反注册usb通知块
   调用 root_device_unregister 反注册evdi的根设备

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

evdi_platform_driver (evdi_drv.c)
   .probe = evdi_platform_probe,
   .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);

evdi_platform_device_probe (evdi_platform_drv.c) 创建evdi的平台设备
   evdi_drm_device_create (evdi_drm_drv.c)

evdi_drm_device_create (evdi_drm_drv.c)  创建evdi的drm设备
   调用 drm_dev_alloc 创建evdi的drm设备,其驱动为driver (evdi_drm_drv.c)
   evdi_drm_device_init (evdi_drm_drv.c)  初始化evdi的drm设备
   drm_dev_register  注册evdi的drm设备

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

driver (evdi_drm_drv.c)
    .open = evdi_driver_open,
    .postclose = evdi_driver_postclose,
    /* gem hooks */
#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8)
#elif KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE
    .gem_free_object_unlocked = evdi_gem_free_object,
#else
    .gem_free_object = evdi_gem_free_object,
#endif
#if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE || defined(EL8)
#else
    .gem_vm_ops = &evdi_gem_vm_ops,
#endif
    .dumb_create = evdi_dumb_create,
    .dumb_map_offset = evdi_gem_mmap,
#if KERNEL_VERSION(5, 12, 0) <= LINUX_VERSION_CODE || defined(EL8)
#else
    .dumb_destroy = drm_gem_dumb_destroy,
#endif
    .ioctls = evdi_painter_ioctls,
    .num_ioctls = ARRAY_SIZE(evdi_painter_ioctls),
    .fops = &evdi_driver_fops,
   ......

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

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

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

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

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

evdi_painter_grabpix_ioctl (evdi_painter.c)
   ...
   merge_dirty_rects
   ...
   copy_primary_pixels
   copy_cursor_pixels
   ...

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

evdi_painter_request_update_ioctl (evdi_painter.c)  请求更新图像
   设置 struct evdi_painter 类型的 was_update_requested

若需要则触发已更新完成的事件
evdi_painter_send_update_ready_if_needed (evdi_painter.c)
   若 struct evdi_painter 类型的 was_update_requested 为真,则:
      1. evdi_painter_send_update_ready (evdi_painter.c)
      2. 设置was_update_requested 为假

evdi_painter_send_update_ready (evdi_painter.c)  触发已更新完成的事件
   构造 DRM_EVDI_EVENT_UPDATE_READY 更新事件
   evdi_painter_send_event (evdi_painter.c)

后记

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

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

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BitSwimmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值