简介
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)
后记
先写到这里,具体细节还需自行分析源码。
特别说明:本文仅为本文的浅见,如有错误,烦请指正。