【转】DRM(三)libdrm库
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了【转】DRM(三)libdrm库相关的知识,希望对你有一定的参考价值。
参考技术A DRM是Linux内核层的显示驱动框架。它把显示功能封装成 open/close/ioctl 等标准接口,用户空间的程序调用这些接口,驱动设备,显示数据。libdrm库封装了DRM driver提供的这些接口。通过libdrm库,程序可以间接调用DRM Driver。
DRM驱动会在/dev/dri下创建3个设备节点:
使用drmOpen()打开设备。它根据指定的name和busid,在上述三个设备中选择匹配的节点。
也可以使用open(),直接打开指定设备。
得到、设置capability。
下图显示各个组件,以及组件之间的关联关系。在DRM的函数名中称作资源(Resource)。如drmModeGetResources()。
每种资源的结构都定义了一个成员,唯一标识这个资源。如drmModeCrtc的crtc_id、drmModeConnector的connector_id、drmModeEncoder的encoder_id、drmModePlane的plane_id。
显示设备的设置、状态,保存在若干结构、变量中,如下图。如drmModeModeInfo,保存设备大小,刷新率。drmModeConnection保存连接状态。
drmModeRes结构保存DRM设备的资源集。
drmModeRes的成员fbs、crtcs、connectors、encoders是变长数组,数组长度保存在对应的变量,如count_fbs中。数组中保存的是资源ID。
函数drmModeGetResources()用于得到资源集。
根据资源 ID 得到对应的资源,包括connector、encoder、crtc。
与其他资源不同,plane资源不在drmModeRes结构中,而是保存在drmModePlaneRes结构。
成员planes是变长数组,数组长度保存在count_planes中。这里保存是plane的资源ID。
drmModeGetPlaneResources()得到plane资源集。
drmModeGetPlane() 根据Plane ID得到Plane。
drmModeProperty结构保存属性。
drmModeObjectProperties保存资源的属性集。
drmModeObjectGetProperties()得到资源属性集。
drmModeGetProperty()得到属性,drmModeObjectSetProperty()设置属性。
使用drmIoctl()创建、映射framebuffer。
使用Linux API函数 mmap(),将framebuffer映射到用户空间。
加入framebuffer(不是提交显示!)。
drmModeAtomicReq保存DRM请求。
drmModeAtomicCommit()提交请求。比如,将framebuffer保存的图像提交显示。
将framebuffer绑定到plane。程序更新framebuffer,就是更新plane。
Linux DRM API - NVIDIA
DRM示例工程 zizimumu/DRM_test
Nouveau源代码分析:NVIDIA设备初始化之nouveau_drm_probe
Nouveau源代码分析(三)
向DRM注冊了Nouveau驱动之后,内核中的PCI模块就会扫描全部没有相应驱动的设备,然后和nouveau_drm_pci_table对比.
对于匹配的设备,PCI模块就调用相应的probe函数,也就是nouveau_drm_probe.
// /drivers/gpu/drm/nouveau/nouveau_drm.c 281 static int nouveau_drm_probe(struct pci_dev *pdev, 282 const struct pci_device_id *pent) 283 { 284 struct nouveau_device *device; 285 struct apertures_struct *aper; 286 bool boot = false; 287 int ret; 288 289 /* remove conflicting drivers (vesafb, efifb etc) */ 290 aper = alloc_apertures(3); 291 if (!aper) 292 return -ENOMEM; 293 294 aper->ranges[0].base = pci_resource_start(pdev, 1); 295 aper->ranges[0].size = pci_resource_len(pdev, 1); 296 aper->count = 1; 297 298 if (pci_resource_len(pdev, 2)) { 299 aper->ranges[aper->count].base = pci_resource_start(pdev, 2); 300 aper->ranges[aper->count].size = pci_resource_len(pdev, 2); 301 aper->count++; 302 } 303 304 if (pci_resource_len(pdev, 3)) { 305 aper->ranges[aper->count].base = pci_resource_start(pdev, 3); 306 aper->ranges[aper->count].size = pci_resource_len(pdev, 3); 307 aper->count++; 308 } 309 310 #ifdef CONFIG_X86 311 boot = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; 312 #endif 313 if (nouveau_modeset != 2) 314 remove_conflicting_framebuffers(aper, "nouveaufb", boot); 315 kfree(aper); 316 317 ret = nouveau_device_create(pdev, NOUVEAU_BUS_PCI, 318 nouveau_pci_name(pdev), pci_name(pdev), 319 nouveau_config, nouveau_debug, &device); 320 if (ret) 321 return ret; 322 323 pci_set_master(pdev); 324 325 ret = drm_get_pci_dev(pdev, pent, &driver); 326 if (ret) { 327 nouveau_object_ref(NULL, (struct nouveau_object **)&device); 328 return ret; 329 } 330 331 return 0; 332 }
第290~315行,分配了一个aper,把资源位置写进去,调用了remove_conflicting_framebuffer,接着释放这个aper.
一行凝视和函数名已经说的非常明确,就是移除冲突的framebuffer.
第317行,创建一个NV设备的结构体,这个函数我们要细致看
// /drivers/gpu/drm/nouveau/core/include/engine/device.h 13 #define nouveau_device_create(p,t,n,s,c,d,u) 14 nouveau_device_create_((void *)(p), (t), (n), (s), (c), (d), 15 sizeof(**u), (void **)u) 16 17 int nouveau_device_create_(void *, enum nv_bus_type type, u64 name, 18 const char *sname, const char *cfg, const char *dbg, 19 int, void **);想起了什么? 对,就是上一节讲的内容,我们还是先来看结构体.
// /drivers/gpu/drm/nouveau/core/include/core/device.h 65 struct nouveau_device { 66 struct nouveau_engine base; 67 struct list_head head; 68 69 struct pci_dev *pdev; 70 struct platform_device *platformdev; 71 u64 handle; 72 73 struct nvkm_event event; 74 75 const char *cfgopt; 76 const char *dbgopt; 77 const char *name; 78 const char *cname; 79 u64 disable_mask; 80 81 enum { 82 NV_04 = 0x04, 83 NV_10 = 0x10, 84 NV_11 = 0x11, 85 NV_20 = 0x20, 86 NV_30 = 0x30, 87 NV_40 = 0x40, 88 NV_50 = 0x50, 89 NV_C0 = 0xc0, 90 NV_E0 = 0xe0, 91 GM100 = 0x110, 92 } card_type; 93 u32 chipset; 94 u32 crystal; 95 96 struct nouveau_oclass *oclass[NVDEV_SUBDEV_NR]; 97 struct nouveau_object *subdev[NVDEV_SUBDEV_NR]; 98 99 struct { 100 struct notifier_block nb; 101 } acpi; 102 };第66行,能够看作是C++的基类,这个结构体等会再说吧.
第67行,链接全部NV设备的链表.
第69行,相应的PCI设备.
第70行,相应的platform设备 (两者选一,要么是PCI设备,要么是platform设备,主要讨论前者).
第71行,算是一个标识符,在创建这个结构体的时候就比較它,有同样的就觉得已经被创建,返回-EEXIST.
第73行,一个事件,是和电源有关的,由AC Adapter在ACPI中发出,CLOCK中接受.
第75行,config设置.
第76行,debug设置.
第77行,PCI名称.
第78行,NV名称,比方GK110,GK20A.
第79行,表示禁用的subdev.
第92行,设备类别 [Family].
第93行,更精确的设备类别 [Chipset].
第94行,晶振频率.
第96行,每个subdev的oclass,oclass的含义參考上一节.
第97行,subdev列表.
第101行,是传给acpi用于触发上面那个event的一个东西.
然后来看nouveau_engine
// /drivers/gpu/drm/nouveau/core/include/core/engine.h 10 struct nouveau_engine { 11 struct nouveau_subdev base; 12 struct nouveau_oclass *cclass; 13 struct nouveau_oclass *sclass; 14 15 struct list_head contexts; 16 spinlock_t lock; 17 18 void (*tile_prog)(struct nouveau_engine *, int region); 19 int (*tlb_flush)(struct nouveau_engine *); 20 };第11行,又是base结构体,等会再说.
第12行,貌似是context oclass,构造context object的时候用的.
第13行,通过u32 oclass得到nouveau_oclass *oclass的一个东西.
第15行,context object链表.
第16行,自旋锁.
第18~19行,瓦片? 意义不明,仅仅在NV01~NV40 实用. [我准备讨论NVC0,由于我的显卡family就是NVC0,方便实验]
// /drivers/gpu/drm/nouveau/core/include/core/subdev.h 9 struct nouveau_subdev { 10 struct nouveau_object base; 11 struct mutex mutex; 12 const char *name; 13 void __iomem *mmio; 14 u32 debug; 15 u32 unit; 16 17 void (*intr)(struct nouveau_subdev *); 18 };第10行,还是base结构体.
第11行,锁.
第12行,名称,主要输出调试信息的时候用.
第13行,MMIO地址.
第14行,调试级别,用于推断是否输出调试信息.
第15行,subdev析构的使用用的,推測是禁用这个subdev.
第17行,中断处理函数指针.
最终到了object结构体------nouveau_object了.
// /drivers/gpu/drm/nouveau/core/include/core/object.h 17 struct nouveau_object { 18 struct nouveau_oclass *oclass; 19 struct nouveau_object *parent; 20 struct nouveau_object *engine; 21 atomic_t refcount; 22 atomic_t usecount; 23 #if CONFIG_NOUVEAU_DEBUG >= NV_DBG_PARANOIA 24 #define NOUVEAU_OBJECT_MAGIC 0x75ef0bad 25 struct list_head list; 26 u32 _magic; 27 #endif 28 };第18行,oclass,作用和上一篇一样,里面包含read,write寄存器,init,fini构造析构等函数的指针.
第19行,parent,就是父结构体.
第20行,相应的engine.
第21行和第22行,两个计数器.
第23到第27行,调试用的魔数.
然后到了xxx_create_函数:
// /drivers/gpu/drm/nouveau/core/engine/device/base.c 662 int 663 nouveau_device_create_(void *dev, enum nv_bus_type type, u64 name, 664 const char *sname, const char *cfg, const char *dbg, 665 int length, void **pobject) 666 { 667 struct nouveau_device *device; 668 int ret = -EEXIST; 669 670 mutex_lock(&nv_devices_mutex); 671 list_for_each_entry(device, &nv_devices, head) { 672 if (device->handle == name) 673 goto done; 674 } 675 676 ret = nouveau_engine_create_(NULL, NULL, &nouveau_device_oclass, true, 677 "DEVICE", "device", length, pobject); 678 device = *pobject; 679 if (ret) 680 goto done; 681 682 switch (type) { 683 case NOUVEAU_BUS_PCI: 684 device->pdev = dev; 685 break; 686 case NOUVEAU_BUS_PLATFORM: 687 device->platformdev = dev; 688 break; 689 } 690 device->handle = name; 691 device->cfgopt = cfg; 692 device->dbgopt = dbg; 693 device->name = sname; 694 695 nv_subdev(device)->debug = nouveau_dbgopt(device->dbgopt, "DEVICE"); 696 nv_engine(device)->sclass = nouveau_device_sclass; 697 list_add(&device->head, &nv_devices); 698 699 ret = nvkm_event_init(&nouveau_device_event_func, 1, 1, 700 &device->event); 701 done: 702 mutex_unlock(&nv_devices_mutex); 703 return ret; 704 }
首先获取锁,然后遍历nv_devices链表,假设handle一样,那么说明这个设备已经被创建了,返回.
676行,初始化base结构体nouveau_engine. [照例等会再看.]
但这边有一个oclass,这个必需要看:
// /drivers/gpu/drm/nouveau/core/engine/device/base.c 652 static struct nouveau_oclass 653 nouveau_device_oclass = { 654 .handle = NV_ENGINE(DEVICE, 0x00), 655 .ofuncs = &(struct nouveau_ofuncs) { 656 .dtor = nouveau_device_dtor, 657 .init = nouveau_device_init, 658 .fini = nouveau_device_fini, 659 }, 660 };由出现了一个handle,这个要注意区分:
1. nouveau_device的handle是用于标识设备,防止一个设备被注冊多次
2. nouveau_oclass的handle,这个比較复杂,最低两位能够表示subdev的type,然后还能表示class的type [就是engine,subdev,object之类的]
3. 另一个没接触到的nouveau_handle的handle,这个用于nouveau_namedb中搜索特定handle.
剩下的三个函数指针,运行到的时候再说吧.
然后初始化device的pdev,handle,cfgopt,dbgopt,name,这些字段上面都介绍过了,不再多说.
nv_subdev,nv_engine是什么呢? 事实上就是把指针强制转换为nouveau_subdev *,nouveau_engine *,当某个控制调试程度的宏大于某个值时,会添加检查语句.
由于各种base全都是结构体的第一个字段,所以能够强制转换而不出问题.
第695行,初始化(nouveau_subdev *)device的debug字段.
第696行,这个注意一下. nouveau_engine的sclass字段前面介绍过,就是控制u32 oclass到nouveau_oclass *oclass的转换,所以我们来看看:
// /drivers/gpu/drm/nouveau/core/engine/device/base.c 501 static struct nouveau_oclass 502 nouveau_device_sclass[] = { 503 { 0x0080, &nouveau_devobj_ofuncs }, 504 {} 505 };记住这个数据,0x0080,以后会用到的.
第699行,初始化device->event,然后解锁,返回.
好了,我们来看nouveau_engine_create_.
// /drivers/gpu/drm/nouveau/core/core/engine.c 29 int 30 nouveau_engine_create_(struct nouveau_object *parent, 31 struct nouveau_object *engobj, 32 struct nouveau_oclass *oclass, bool enable, 33 const char *iname, const char *fname, 34 int length, void **pobject) 35 { 36 struct nouveau_engine *engine; 37 int ret; 38 39 ret = nouveau_subdev_create_(parent, engobj, oclass, NV_ENGINE_CLASS, 40 iname, fname, length, pobject); 41 engine = *pobject; 42 if (ret) 43 return ret; 44 45 if (parent) { 46 struct nouveau_device *device = nv_device(parent); 47 int engidx = nv_engidx(nv_object(engine)); 48 49 if (device->disable_mask & (1ULL << engidx)) { 50 if (!nouveau_boolopt(device->cfgopt, iname, false)) { 51 nv_debug(engine, "engine disabled by hw/fw\n"); 52 return -ENODEV; 53 } 54 55 nv_warn(engine, "ignoring hw/fw engine disable\n"); 56 } 57 58 if (!nouveau_boolopt(device->cfgopt, iname, enable)) { 59 if (!enable) 60 nv_warn(engine, "disabled, %s=1 to enable\n", iname); 61 return -ENODEV; 62 } 63 } 64 65 INIT_LIST_HEAD(&engine->contexts); 66 spin_lock_init(&engine->lock); 67 return 0; 68 }第39行,首先创建subdev.
第45行,推断parent,紧接着把他转换成nouveau_device,注意这个不是直接的强制转换.
// /drivers/gpu/drm/nouveau/core/include/core/device.h 106 static inline struct nouveau_device * 107 nv_device(void *obj) 108 { 109 struct nouveau_object *object = nv_object(obj); 110 struct nouveau_object *device = object; 111 112 if (device->engine) 113 device = device->engine; 114 if (device->parent) 115 device = device->parent; 116 117 #if CONFIG_NOUVEAU_DEBUG >= NV_DBG_PARANOIA 118 if (unlikely(!nv_iclass(device, NV_SUBDEV_CLASS) || 119 (nv_hclass(device) & 0xff) != NVDEV_ENGINE_DEVICE)) { 120 nv_assert("BAD CAST -> NvDevice, 0x%08x 0x%08x", 121 nv_hclass(object), nv_hclass(device)); 122 } 123 #endif 124 125 return (void *)device; 126 }第113行,先把device赋值为device->engine,然后第115行再赋值为device->parent并返回.
当然对于这个样例,device->engine和device->parent都为0,所以直接返回device.
回到刚才那个函数,第47行获取engidx,事实上就是subidx,再展开就是oclass::handle的最低两位.能够參考以下这个enum:
// /drivers/gpu/drm/nouveau/core/include/core/device.h 8 enum nv_subdev_type { 9 NVDEV_ENGINE_DEVICE, 10 NVDEV_SUBDEV_VBIOS, 11 12 /* All subdevs from DEVINIT to DEVINIT_LAST will be created before 13 * *any* of them are initialised. This subdev category is used 14 * for any subdevs that the VBIOS init table parsing may call out 15 * to during POST. 16 */ 17 NVDEV_SUBDEV_DEVINIT, 18 NVDEV_SUBDEV_GPIO, 19 NVDEV_SUBDEV_I2C, 20 NVDEV_SUBDEV_DEVINIT_LAST = NVDEV_SUBDEV_I2C, 21 22 /* This grouping of subdevs are initialised right after they‘ve 23 * been created, and are allowed to assume any subdevs in the 24 * list above them exist and have been initialised. 25 */ 26 NVDEV_SUBDEV_MXM, 27 NVDEV_SUBDEV_MC, 28 NVDEV_SUBDEV_BUS, 29 NVDEV_SUBDEV_TIMER, 30 NVDEV_SUBDEV_FB, 31 NVDEV_SUBDEV_LTCG, 32 NVDEV_SUBDEV_IBUS, 33 NVDEV_SUBDEV_INSTMEM, 34 NVDEV_SUBDEV_VM, 35 NVDEV_SUBDEV_BAR, 36 NVDEV_SUBDEV_PWR, 37 NVDEV_SUBDEV_VOLT, 38 NVDEV_SUBDEV_THERM, 39 NVDEV_SUBDEV_CLOCK, 40 41 NVDEV_ENGINE_FIRST, 42 NVDEV_ENGINE_DMAOBJ = NVDEV_ENGINE_FIRST, 43 NVDEV_ENGINE_FIFO, 44 NVDEV_ENGINE_SW, 45 NVDEV_ENGINE_GR, 46 NVDEV_ENGINE_MPEG, 47 NVDEV_ENGINE_ME, 48 NVDEV_ENGINE_VP, 49 NVDEV_ENGINE_CRYPT, 50 NVDEV_ENGINE_BSP, 51 NVDEV_ENGINE_PPP, 52 NVDEV_ENGINE_COPY0, 53 NVDEV_ENGINE_COPY1, 54 NVDEV_ENGINE_COPY2, 55 NVDEV_ENGINE_VIC, 56 NVDEV_ENGINE_VENC, 57 NVDEV_ENGINE_DISP, 58 NVDEV_ENGINE_PERFMON, 59 60 NVDEV_SUBDEV_NR, 61 };对于nouveau_device,就是NVDEV_ENGINE_DEVICE.
接着第49行检查这个engine有没有被禁用,再依据cfgopt的值决定做法.
第58行,检查cfgopt和enable是否相应,出错就返回.
第65,66行,初始化链表和自旋锁,返回.
紧接着,来看nouveau_subdev_create_:
// /drivers/gpu/drm/nouveau/core/core/subdev.c 86 int 87 nouveau_subdev_create_(struct nouveau_object *parent, 88 struct nouveau_object *engine, 89 struct nouveau_oclass *oclass, u32 pclass, 90 const char *subname, const char *sysname, 91 int size, void **pobject) 92 { 93 struct nouveau_subdev *subdev; 94 int ret; 95 96 ret = nouveau_object_create_(parent, engine, oclass, pclass | 97 NV_SUBDEV_CLASS, size, pobject); 98 subdev = *pobject; 99 if (ret) 100 return ret; 101 102 __mutex_init(&subdev->mutex, subname, &oclass->lock_class_key); 103 subdev->name = subname; 104 105 if (parent) { 106 struct nouveau_device *device = nv_device(parent); 107 subdev->debug = nouveau_dbgopt(device->dbgopt, subname); 108 subdev->mmio = nv_subdev(device)->mmio; 109 } 110 111 return 0; 112 }先创建nouveau_object (PS:最终快到头了!) .
接着初始化mutex,name字段
假设parent不为0,那么就把subdev的debug和mmio字段初始化为相应的这两个字段.
就这个样例来说,parent就是0,所以不会运行进去的.
最后nouveau_object_create_ :
// /drivers/gpu/drm/nouveau/core/core/object.c 33 int 34 nouveau_object_create_(struct nouveau_object *parent, 35 struct nouveau_object *engine, 36 struct nouveau_oclass *oclass, u32 pclass, 37 int size, void **pobject) 38 { 39 struct nouveau_object *object; 40 41 object = *pobject = kzalloc(size, GFP_KERNEL); 42 if (!object) 43 return -ENOMEM; 44 45 nouveau_object_ref(parent, &object->parent); 46 nouveau_object_ref(engine, &object->engine); 47 object->oclass = oclass; 48 object->oclass->handle |= pclass; 49 atomic_set(&object->refcount, 1); 50 atomic_set(&object->usecount, 0); 51 52 #ifdef NOUVEAU_OBJECT_MAGIC 53 object->_magic = NOUVEAU_OBJECT_MAGIC; 54 spin_lock(&_objlist_lock); 55 list_add(&object->list, &_objlist); 56 spin_unlock(&_objlist_lock); 57 #endif 58 return 0; 59 }用kzmalloc分配一个大小为size (这个数是一路传下来的,大小就是sizeof(nouveau_device)) 且已经清零的内存,
由于parent,engine,object->parent,object->engine都为0,所以第45.46行代码事实上啥也没做.
初始化oclass字段,然后把oclass->handle或上plass标识符. [这个数究竟是多少能够向上翻,意义也非常easy理解.]
初始化refcount和usecount.
接下来的魔数忽略掉,调试查错用的.
然后58行,返回!
于是我们就这么回到了nouveau_drm_probe.
第323行,启用Bus-Mastering.
第325行,向DRM注冊PCI设备.
然后就是错误处理,假设失败还要把device处理一下,比方从链表中删除,释放空间等.
至于cfgopt,dbgopt,到头来各自是nouveau_config和nouveau_debug,是Nouveau的模块參数,有兴趣能够自己实验.
初始化远远没有结束,还有nouveau_drm_load,比这次这个函数不知道长多少倍.....
以上是关于【转】DRM(三)libdrm库的主要内容,如果未能解决你的问题,请参考以下文章
libdrm 是不是通过 ioctl() 与内核 DRM/显卡通信?
RK3399应用开发 | 编译安装 mesa 3D 图形库(23.0.0)