DRM是direct rendering manager的简称。DRM是linux kernel中与负责video cards功能的GPU打交道的子系统。DRM给出了一组API,可以供用户程序来发送命令和数据给GPU设备从而来控制比如display、render等功能。
在以前,DRM子系统中给每个DRM device注册的device-node就是:/dev/dri/cardX ,通过该节点来作mode-setting和rendering的控制。后来发现这么做有问题:
mode-setting和rendering是通过同一个文件节点node来控制的
单卡的mode-setting资源不能在多个graphics-servers之间切分使用
在多cards之间共享display-controller实现过于复杂
然后就有了一些改进来解决这些问题。最终是mode-setting 和 render节点分家。
render-node架构大概是2009年左右提出来的。站在用户程序角度来看,render node的作用是用来加速computing和rendering,render node可以通过 /dev/dri/renderDX 被访问,并且提供了基本的DRM rendering interface。与 /dev/dri/cardX 节点相比,/dev/dri/renderDX 少了一些特性:
没有mode-setting(KMS)ioctls功能
用dma-buf替换掉gem-flink(非安全的)
不再需要DRM-auth认证
不再支持pre-KMS DRM-API
这样一来,当应用程序需要hardware-acclerated rendering、访问GPGPU、offscreen rendering等时,就不需要通过DRI或者wl_drm来访问graphics-server,而是直接打开某个render node就开始使用即可。对于render node的访问权限控制则通过标注的file-system modes来控制了。
render-node并没有提供新的API,它们只是将原有的DRM-API分出了一部分到一个新的device-node,原来的node也保留了下来用于如mode-setting等控制。
render-node也没有和任何一个card进行绑定,它是由原有node的同一个driver创建的,所以尝试在原有node和render-node之间进行connect连接通信没有意义。当应用程序需要和graphics-server进行通信时,可以通过dma-buf。
虽然从原有的node中分离出一个render-node,简化了应用程序的访问,但对于mode-setting程序的访问没有简化。当一个graphics-server想要编程一个display-controller时,它需要是给定card的DRM-Master,可以通过drmSetMaster()接口来获得身份,但同时只能有一个DRM-Master,而且必须是由CAP_SYS_ADMIN特权的程序才能成为DRM-Master,这会带来问题:
不能以非root权限运行XServer
不能在同一个card上使用两个不同的XServer来控制两个不同的独立的显示器
首先想到的解决办法就是分离出mode-setting node,类似render-node的方式,/dev/dri/modesetD1 和 /dev/dri/modesetD2 节点,来分割KMS CRTC和Connector资源。
另一种方法是将所有的mode-setting资源绑定到一个DRM-Master对象,然后谁要访问mode-setting资源就可以通过访问该DRM-Master对象来实现。
不管是render-node还是mode-setting-node,在kernel角度是如何体现的?
如果hardware没有display-controller,则可以不设置DRIVER_MODESET flag只设置DRIVER_RENDER flag,这样内核DRM只会创建render-node。如果一个hardware只有display-controller而没有rendering hardware,可以设置DRIVER_MODESET而不设置DRIVER_RENDER。
大概回顾了下render node的由来。那么render node由kernel来负责创建,其编号为何从128开始,答案估计还要到kernel中寻找。
linux kernel中 drivers/gpu/drm/drm_drv.c中定义了drm_dev_init()函数,其中创建drm设备编号的代码如下
if (drm_core_check_feature(dev, DRIVER_COMPUTE_ACCEL)) {
ret = drm_minor_alloc(dev, DRM_MINOR_ACCEL);
if (ret)
goto err;
} else {
if (drm_core_check_feature(dev, DRIVER_RENDER)) {
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
if (ret)
goto err;
}
ret = drm_minor_alloc(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err;
}
涉及到两个枚举类型结构体
enum drm_driver_feature {
DRIVER_GEM = BIT(0),
DRIVER_MODESET = BIT(1),
DRIVER_RENDER = BIT(3),
DRIVER_ATOMIC = BIT(4),
DRIVER_SYNCOBJ = BIT(5),
DRIVER_SYNCOBJ_TIMELINE = BIT(6),
DRIVER_COMPUTE_ACCEL = BIT(7),
DRIVER_USE_AGP = BIT(25),
DRIVER_LEGACY = BIT(26),
DRIVER_PCI_DMA = BIT(27),
DRIVER_SG = BIT(28),
DRIVER_HAVE_DMA = BIT(29),
DRIVER_HAVE_IRQ = BIT(30),
};
enum drm_minor_type {
DRM_MINOR_PRIMARY,
DRM_MINOR_CONTROL,
DRM_MINOR_RENDER,
DRM_MINOR_ACCEL = 32,
};
当drm core检查到device设置了DRIVER_RENDER标签时,就通过drm_minor_alloc(dev, DRM_MINOR_RENDER)
来分配ID,而drm_minor_alloc()
函数中最终分配ID是通过idr_alloc()
函数来实现,这里的type传入的就是DRM_MINOR_RENDER
,也就是2。
r = idr_alloc(&drm_minors_idr,
NULL,
64 * type,
64 * (type + 1),
GFP_NOWAIT);
//idr_alloc又是调用idr_alloc_u32来实现
int idr_alloc(struct idr *idr, void *ptr, int start, int end, gfp_t gfp)
{
u32 id = start;
int ret;
if (WARN_ON_ONCE(start < 0))
return -EINVAL;
ret = idr_alloc_u32(idr, ptr, &id, end > 0 ? end - 1 : INT_MAX, gfp);
if (ret)
return ret;
return id;
}
EXPORT_SYMBOL_GPL(idr_alloc);
* idr_alloc() - Allocate an ID.
* @idr: IDR handle.
* @ptr: Pointer to be associated with the new ID.
* @start: The minimum ID (inclusive).
* @end: The maximum ID (exclusive).
* @gfp: Memory allocation flags.
idr_alloc()的底3个参数就是ID的start,第4个参数是ID范围的end。所以,对于DRIVER_RENDER属性的device来说,其ID范围是
DRM_MINOR_RENDER * 64 = 128 至 (DRM_MINOR_RENDER + 1) * 64 = 192 之间。
所以才有renderD128、renderD129。
问题:DRM中如何做到一个已经分配了的ID比如128,下一个device来分配时就不使用128而是129呢?换句话说kernel中如何记忆ID的分配结果的?
int idr_alloc_u32(struct idr *idr, void *ptr, u32 *nextid,
unsigned long max, gfp_t gfp)
{
struct radix_tree_iter iter;
void __rcu **slot;
unsigned int base = idr->idr_base;
unsigned int id = *nextid;
if (WARN_ON_ONCE(!(idr->idr_rt.xa_flags & ROOT_IS_IDR)))
idr->idr_rt.xa_flags |= IDR_RT_MARKER;
id = (id < base) ? 0 : id - base;
radix_tree_iter_init(&iter, id);
slot = idr_get_free(&idr->idr_rt, &iter, gfp, max - base);
if (IS_ERR(slot))
return PTR_ERR(slot);
*nextid = iter.index + base;
/* there is a memory barrier inside radix_tree_iter_replace() */
radix_tree_iter_replace(&idr->idr_rt, &iter, slot, ptr);
radix_tree_iter_tag_clear(&idr->idr_rt, &iter, IDR_FREE);
return 0;
}
EXPORT_SYMBOL_GPL(idr_alloc_u32);
这里使用了基数树(radix-tree)。
比如我这个pc上有两个显卡intel UHD Graphics 630和Nvidia GTX 1050 Ti,然后/dev/dri/下也有两个render-node,renderD128和renderD129,内核分配它们的顺序是如何确定的?
应该是扫描PCI设备的时候就确定顺序了。
ubuntu上查看哪个card和哪个GPU绑定:
drm_info #该命令可以查看/dev/dri/card0对应的GPU驱动
比如我这里两个显卡,card0对应intel 630, card1对应nvidia GTX 1050Ti。
那么如何确定render node和GPU对应关系?
ls /sys/class/drm/card0/device/drm/
#可以看到card0中有card0, controlD64, renderD128
ls /sys/class/drm/card1/device/drm/
#可以看到card1中有card1, controlD65, renderD129
参考:
DRM render node number