码迷,mamicode.com
首页 > 其他好文 > 详细

Nouveau源码分析(四):NVIDIA设备初始化之nouveau_drm_load (1)

时间:2014-11-15 14:11:06      阅读:318      评论:0      收藏:0      [点我收藏+]

标签:驱动   源码分析   nvidia   nouveau   linux   

Nouveau源码分析(四)

probe函数成功返回之后,DRM模块就会调用struct drm_driver的load函数,对应nouveau的nouveau_drm_load.
这个函数虽然看起来不是特别长,但每一个调用的函数展开后就会变得非常长了!

// /drivers/gpu/drm/nouveau/nouveau_drm.c
364 static int
365 nouveau_drm_load(struct drm_device *dev, unsigned long flags)
366 {
367         struct pci_dev *pdev = dev->pdev;
368         struct nouveau_drm *drm;
369         int ret;
370 
371         ret = nouveau_cli_create(nouveau_name(dev), "DRM", sizeof(*drm),
372                                  (void **)&drm);
373         if (ret)
374                 return ret;
375 
376         dev->dev_private = drm;
377         drm->dev = dev;
378         nvkm_client(&drm->client.base)->debug =
379                 nouveau_dbgopt(nouveau_debug, "DRM");
380 
381         INIT_LIST_HEAD(&drm->clients);
382         spin_lock_init(&drm->tile.lock);
383 
384         nouveau_get_hdmi_dev(drm);
385 
386         /* make sure AGP controller is in a consistent state before we
387          * (possibly) execute vbios init tables (see nouveau_agp.h)
388          */
389         if (pdev && drm_pci_device_is_agp(dev) && dev->agp) {
390                 const u64 enables = NV_DEVICE_V0_DISABLE_IDENTIFY |
391                                     NV_DEVICE_V0_DISABLE_MMIO;
392                 /* dummy device object, doesn't init anything, but allows
393                  * agp code access to registers
394                  */
395                 ret = nvif_device_init(&drm->client.base.base, NULL,
396                                        NVDRM_DEVICE, NV_DEVICE,
397                                        &(struct nv_device_v0) {
398                                                 .device = ~0,
399                                                 .disable = ~enables,
400                                                 .debug0 = ~0,
401                                        }, sizeof(struct nv_device_v0),
402                                        &drm->device);
403                 if (ret)
404                         goto fail_device;
405 
406                 nouveau_agp_reset(drm);
407                 nvif_device_fini(&drm->device);
408         }
409 
410         ret = nvif_device_init(&drm->client.base.base, NULL, NVDRM_DEVICE,
411                                NV_DEVICE,
412                                &(struct nv_device_v0) {
413                                         .device = ~0,
414                                         .disable = 0,
415                                         .debug0 = 0,
416                                }, sizeof(struct nv_device_v0),
417                                &drm->device);
418         if (ret)
419                 goto fail_device;
420 
421         dev->irq_enabled = true;
422 
423         /* workaround an odd issue on nvc1 by disabling the device's
424          * nosnoop capability.  hopefully won't cause issues until a
425          * better fix is found - assuming there is one...
426          */
427         if (drm->device.info.chipset == 0xc1)
428                 nvif_mask(&drm->device, 0x00088080, 0x00000800, 0x00000000);
429 
430         nouveau_vga_init(drm);
431         nouveau_agp_init(drm);
432 
433         if (drm->device.info.family >= NV_DEVICE_INFO_V0_TESLA) {
434                 ret = nouveau_vm_new(nvkm_device(&drm->device), 0, (1ULL << 40),
435                                      0x1000, &drm->client.vm);
436                 if (ret)
437                         goto fail_device;
438 
439                 nvkm_client(&drm->client.base)->vm = drm->client.vm;
440         }
441 
442         ret = nouveau_ttm_init(drm);
443         if (ret)
444                 goto fail_ttm;
445 
446         ret = nouveau_bios_init(dev);
447         if (ret)
448                 goto fail_bios;
449 
450         ret = nouveau_display_create(dev);
451         if (ret)
452                 goto fail_dispctor;
453 
454         if (dev->mode_config.num_crtc) {
455                 ret = nouveau_display_init(dev);
456                 if (ret)
457                         goto fail_dispinit;
458         }
459 
460         nouveau_sysfs_init(dev);
461         nouveau_hwmon_init(dev);
462         nouveau_accel_init(drm);
463         nouveau_fbcon_init(dev);
464 
465         if (nouveau_runtime_pm != 0) {
466                 pm_runtime_use_autosuspend(dev->dev);
467                 pm_runtime_set_autosuspend_delay(dev->dev, 5000);
468                 pm_runtime_set_active(dev->dev);
469                 pm_runtime_allow(dev->dev);
470                 pm_runtime_mark_last_busy(dev->dev);
471                 pm_runtime_put(dev->dev);
472         }
473         return 0;
474 
475 fail_dispinit:
476         nouveau_display_destroy(dev);
477 fail_dispctor:
478         nouveau_bios_takedown(dev);
479 fail_bios:
480         nouveau_ttm_fini(drm);
481 fail_ttm:
482         nouveau_agp_fini(drm);
483         nouveau_vga_fini(drm);
484 fail_device:
485         nvif_device_fini(&drm->device);
486         nouveau_cli_destroy(&drm->client);
487         return ret;
488 }

第371行,创建一个nouveau_drm结构体. 这里,我们重新梳理一下nouveau中结构体的线索.
第一条线索 struct nouveau_xxx,这个我们在上一篇中已经见到了很多了,比如nouveau_device nouveau_client nouveau_object [以下简称nv结构体/nv对象]
另一条线索 struct nvif_xxx/struct nouveau_cli,这个我们从这一节会碰到,比如nouveau_cli nvif_client nvif_object nvif_device[以下简称nvif结构体/nvif对象]
每一个nvif对象都会有对应一个nv对象,可以通过ioctl来访问. 比如对一个nvif对象执行ioctl_rd32,就可以执行对应nv对象oclass里的rd32方法.
仔细阅读下面这个图片:
bubuko.com,布布扣
方块里的是类型名称,箭头上的是成员名称 [带*号的表示指针].
我们现在要创建的就是处于最顶层的nouveau_drm,可以看到如果要和上一节创建的nouveau_device对应起来,还有一段路要走.

因为nouveau_drm这个结构体成员包含的面很广,从物理显存、虚拟映射显存,到channel,dma,以及硬件加速移动显存,还有显示模式切换都会有所涉及,所以在此处先不分析这个结构体的每一个成员,先跟着代码走下去吧.
因为struct nouveau_drm第一个成员就是struct nouveau_cli client; 所以可以把nouveau_cli看作是它的base,因此,第371行创建一个struct nouveau_cli,大小是sizeof(struct nouveau_drm) .
// /drivers/gpu/drm/nouveau/nouveau_drm.c
102 static int
103 nouveau_cli_create(u64 name, const char *sname,
104                    int size, void **pcli)
105 {
106         struct nouveau_cli *cli = *pcli = kzalloc(size, GFP_KERNEL);
107         if (cli) {
108                 int ret = nvif_client_init(NULL, NULL, sname, name,
109                                            nouveau_config, nouveau_debug,
110                                           &cli->base);
111                 if (ret == 0) {
112                         mutex_init(&cli->mutex);
113                         usif_client_init(cli);
114                 }
115                 return ret;
116         }
117         return -ENOMEM;
118 }
第106行,kzalloc分配内存.
第108行,因为nouveau_cli的base是nvif_client,所以这里调用nvif_client的init函数.
第112行,如果nvif_client初始化成功的话,就初始化一个mutex .
第113行,初始化usif. 这是个什么东西呢? 其实就是nvif对user空间的一个接口. 让运行在用户态下的程序能创建usif对象,然后进行读写什么的.
这个初始化usif的函数很简单,就是初始化两个链表.
// /drivers/gpu/drm/nouveau/nouveau_usif.c
379 void
380 usif_client_init(struct nouveau_cli *cli)
381 {
382         INIT_LIST_HEAD(&cli->objects);
383         INIT_LIST_HEAD(&cli->notifys);
384 }

再来看刚才的nvif_client_init函数:
// /drivers/gpu/drm/nouveau/nvif/client.c
 69 int
 70 nvif_client_init(void (*dtor)(struct nvif_client *), const char *driver,
 71                  const char *name, u64 device, const char *cfg, const char *dbg,
 72                  struct nvif_client *client)
 73 {
 74         int ret, i;
 75 
 76         ret = nvif_object_init(NULL, (void*)dtor, 0, 0, NULL, 0, &client->base);
 77         if (ret)
 78                 return ret;
 79 
 80         client->base.parent = &client->base;
 81         client->base.handle = ~0;
 82         client->object = &client->base;
 83         client->super = true;
 84 
 85         for (i = 0, ret = -EINVAL; (client->driver = nvif_drivers[i]); i++) {
 86                 if (!driver || !strcmp(client->driver->name, driver)) {
 87                         ret = client->driver->init(name, device, cfg, dbg,
 88                                                   &client->base.priv);
 89                         if (!ret || driver)
 90                                 break;
 91                 }
 92         }
 93 
 94         if (ret)
 95                 nvif_client_fini(client);
 96         return ret;
 97 }
第76行,首先初始化nvif_object:
// /drivers/gpu/drm/nouveau/nvif/object.c
217 int
218 nvif_object_init(struct nvif_object *parent, void (*dtor)(struct nvif_object *),
219                  u32 handle, u32 oclass, void *data, u32 size,
220                  struct nvif_object *object)
221 {
222         struct ctor *ctor;
223         int ret = 0;
224 
225         object->parent = NULL;
226         object->object = object;
227         nvif_object_ref(parent, &object->parent);
228         kref_init(&object->refcount);
229         object->handle = handle;
230         object->oclass = oclass;
231         object->data = NULL;
232         object->size = 0;
233         object->dtor = dtor;
234         object->map.ptr = NULL;
235         object->map.size = 0;
236 
237         if (object->parent) {
238                 if (!(ctor = kmalloc(sizeof(*ctor) + size, GFP_KERNEL))) {
239                         nvif_object_fini(object);
240                         return -ENOMEM;
241                 }
242                 object->data = ctor->new.data;
243                 object->size = size;
244                 memcpy(object->data, data, size);
245 
246                 ctor->ioctl.version = 0;
247                 ctor->ioctl.type = NVIF_IOCTL_V0_NEW;
248                 ctor->new.version = 0;
249                 ctor->new.route = NVIF_IOCTL_V0_ROUTE_NVIF;
250                 ctor->new.token = (unsigned long)(void *)object;
251                 ctor->new.handle = handle;
252                 ctor->new.oclass = oclass;
253 
254                 ret = nvif_object_ioctl(parent, ctor, sizeof(*ctor) +
255                                         object->size, &object->priv);
256         }
257 
258         if (ret)
259                 nvif_object_fini(object);
260         return ret;
261 }
首先各种填充字段:
这里的oclass是创建nouveau_object的时候用的.
这里的handle是nouveau_object和nvif_object一一对应用的.
然后dtor是析构函数指针,object是nvif_object()用的,其实就是nvif对象间的转换.
// /drivers/gpu/drm/nouveau/nvif/object.h
 37 #define nvif_object(a) (a)->object

前面说过,每一个nvif_object会对应一个nouveau_object,但对于这个例子来说,创建nouveau_object的代码实际上在第254行,由于object->parent为0,根本执行不到,所以并不会使用这个oclass和handle. [当然,这个nvif_object还是有对应的nouveau_object对象的,只不过不在这里创建了,等会儿会讲到.]
那个if语句的代码也暂时不展开,等到以后真正执行到再回来说. 于是这个函数就这么返回了.

先回到nvif_client_init,下面还有一个循环语句,遍历nvif_drivers:
// /drivers/gpu/drm/nouveau/nvif/client.c
 58 const struct nvif_driver *
 59 nvif_drivers[] = {
 60 #ifdef __KERNEL__
 61         &nvif_driver_nvkm,
 62 #else
 63         &nvif_driver_drm,
 64         &nvif_driver_lib,
 65 #endif
 66         NULL
 67 };
很明显这是内核里的代码,因此只存在一个nvif_driver,那就是nvif_driver_nvkm.
再看看这个for循环,很明显我们将会调用nvif_driver_nvkm.init():
// /drivers/gpu/drm/nouveau/nouveau_nvif.c
125 const struct nvif_driver
126 nvif_driver_nvkm = {
127         .name = "nvkm",
128         .init = nvkm_client_init,
129         .fini = nvkm_client_fini,
130         .suspend = nvkm_client_suspend,
131         .resume = nvkm_client_resume,
132         .ioctl = nvkm_client_ioctl,
133         .map = nvkm_client_map,
134         .unmap = nvkm_client_unmap,
135         .keep = false,
136 };
// /drivers/gpu/drm/nouveau/nouveau_nvif.c
109 static int
110 nvkm_client_init(const char *name, u64 device, const char *cfg,
111                  const char *dbg, void **ppriv)
112 {
113         struct nouveau_client *client;
114         int ret;
115 
116         ret = nouveau_client_create(name, device, cfg, dbg, &client);
117         *ppriv = client;
118         if (ret)
119                 return ret;
120 
121         client->ntfy = nvkm_client_ntfy;
122         return 0;
123 }
首先创建一个nouveau_client,这个其实就是一个nouveau_object,也就是说在这里创建了这个nvif_object对应的nouveau_object .
第121行,一个有关notify的函数. notify貌似是一个回调工具,创建一个里面包含函数指针,数据什么的;当发生某个条件后,可以触发这个notify,然后就会调用里面的函数.
再来看nouveau_client_create:
// /drivers/gpu/drm/nouveau/core/include/core/client.h
 39 #define nouveau_client_create(n,c,oc,od,d)                                      40         nouveau_client_create_((n), (c), (oc), (od), sizeof(**d), (void **)d)
 41 
 42 int  nouveau_client_create_(const char *name, u64 device, const char *cfg,
 43                             const char *dbg, int, void **);
又一次见到了这个东西,直接去看nouveau_client_create_
// /drivers/gpu/drm/nouveau/core/core/client.c
203 int
204 nouveau_client_create_(const char *name, u64 devname, const char *cfg,
205                        const char *dbg, int length, void **pobject)
206 {
207         struct nouveau_object *device;
208         struct nouveau_client *client;
209         int ret;
210 
211         device = (void *)nouveau_device_find(devname);
212         if (!device)
213                 return -ENODEV;
214 
215         ret = nouveau_namedb_create_(NULL, NULL, &nouveau_client_oclass,
216                                      NV_CLIENT_CLASS, NULL,
217                                      (1ULL << NVDEV_ENGINE_DEVICE),
218                                      length, pobject);
219         client = *pobject;
220         if (ret)
221                 return ret;
222 
223         ret = nouveau_handle_create(nv_object(client), ~0, ~0,
224                                     nv_object(client), &client->root);
225         if (ret)
226                 return ret;
227 
228         /* prevent init/fini being called, os in in charge of this */
229         atomic_set(&nv_object(client)->usecount, 2);
230 
231         nouveau_object_ref(device, &client->device);
232         snprintf(client->name, sizeof(client->name), "%s", name);
233         client->debug = nouveau_dbgopt(dbg, "CLIENT");
234         return 0;
235 }
第211行,首先寻找对应的nouveau_device.
第215行,创建namedb,通过看最开始的结构体,应该也能想象出是个什么东西:
// /drivers/gpu/drm/nouveau/core/core/namedb.c
166 int
167 nouveau_namedb_create_(struct nouveau_object *parent,
168                        struct nouveau_object *engine,
169                        struct nouveau_oclass *oclass, u32 pclass,
170                        struct nouveau_oclass *sclass, u64 engcls,
171                        int length, void **pobject)
172 {
173         struct nouveau_namedb *namedb;
174         int ret;
175 
176         ret = nouveau_parent_create_(parent, engine, oclass, pclass |
177                                      NV_NAMEDB_CLASS, sclass, engcls,
178                                      length, pobject);
179         namedb = *pobject;
180         if (ret)
181                 return ret;
182 
183         rwlock_init(&namedb->lock);
184         INIT_LIST_HEAD(&namedb->list);
185         return 0;
186 }
先创建一个nouveau_parent,这个结构体和nouveau_engine都能把u32 oclass转换成对应的nouveau_oclass *oclass .
接下来初始化namedb的list链表,这个链表和nouveau_handle的node链表相连,表示储存在namedb里的nouveau_handle对象 .
// /drivers/gpu/drm/nouveau/core/core/parent.c
110 int
111 nouveau_parent_create_(struct nouveau_object *parent,
112                        struct nouveau_object *engine,
113                        struct nouveau_oclass *oclass, u32 pclass,
114                        struct nouveau_oclass *sclass, u64 engcls,
115                        int size, void **pobject)
116 {
117         struct nouveau_parent *object;
118         struct nouveau_sclass *nclass;
119         int ret;
120 
121         ret = nouveau_object_create_(parent, engine, oclass, pclass |
122                                      NV_PARENT_CLASS, size, pobject);
123         object = *pobject;
124         if (ret)
125                 return ret;
126 
127         while (sclass && sclass->ofuncs) {
128                 nclass = kzalloc(sizeof(*nclass), GFP_KERNEL);
129                 if (!nclass)
130                         return -ENOMEM;
131 
132                 nclass->sclass = object->sclass;
133                 object->sclass = nclass;
134                 nclass->engine = engine ? nv_engine(engine) : NULL;
135                 nclass->oclass = sclass;
136                 sclass++;
137         }
138 
139         object->engine = engcls;
140         return 0;
141 }
首先创建一个nouveau_object对象.
然后下面这个while语句,把sclass连成了一个链表放在了object->sclass中.
然后engcls表示可以用来完成u32 oclass转换的engines.
如果要通过一个nouveau_parent对象来完成u32 oclass的转换,会先从object->sclass中查找.
如果没找到,那么就查看object->engine中指示可用的engine,再从这些engine中查找.
此处的sclass实际上是0,所以object->sclass为0. 而且因为这个结构体实际上是nouveau_client,转换u32 oclass的时候还有一个特别的处理,会直接使用client->device,也就是nouveau_device,到时候再具体说.

回到nouveau_client_create_,接下来创建一个nouveau_handle *root.
// /drivers/gpu/drm/nouveau/core/core/handle.c
 99 int
100 nouveau_handle_create(struct nouveau_object *parent, u32 _parent, u32 _handle,
101                       struct nouveau_object *object,
102                       struct nouveau_handle **phandle)
103 {
104         struct nouveau_object *namedb;
105         struct nouveau_handle *handle;
106         int ret;
107 
108         namedb = parent;
109         while (!nv_iclass(namedb, NV_NAMEDB_CLASS))
110                 namedb = namedb->parent;
111 
112         handle = kzalloc(sizeof(*handle), GFP_KERNEL);
113         if (!handle)
114                 return -ENOMEM;
115 
116         INIT_LIST_HEAD(&handle->head);
117         INIT_LIST_HEAD(&handle->tree);
118         handle->name = _handle;
119         handle->priv = ~0;
120 
121         ret = nouveau_namedb_insert(nv_namedb(namedb), _handle, object, handle);
122         if (ret) {
123                 kfree(handle);
124                 return ret;
125         }
126 
127         if (nv_parent(parent)->object_attach) {
128                 ret = nv_parent(parent)->object_attach(parent, object, _handle);
129                 if (ret < 0) {
130                         nouveau_handle_destroy(handle);
131                         return ret;
132                 }
133 
134                 handle->priv = ret;
135         }
136 
137         if (object != namedb) {
138                 while (!nv_iclass(namedb, NV_CLIENT_CLASS))
139                         namedb = namedb->parent;
140 
141                 handle->parent = nouveau_namedb_get(nv_namedb(namedb), _parent);
142                 if (handle->parent) {
143                         list_add(&handle->head, &handle->parent->tree);
144                         nouveau_namedb_put(handle->parent);
145                 }
146         }
147 
148         hprintk(handle, TRACE, "created\n");
149         *phandle = handle;
150         return 0;
151 }
首先把parent向上查找,直到找到一个nouveau_namedb,对于这个例子,parent本来就是一个nouveau_namedb,所以parent == namedb.
接着初始化handle的一些字段,紧接着使用nouveau_namedb_insert把这个插入到namedb中. handle的node链表和namedb的list链表相连.
// /drivers/gpu/drm/nouveau/core/core/namedb.c
 87 int
 88 nouveau_namedb_insert(struct nouveau_namedb *namedb, u32 name,
 89                       struct nouveau_object *object,
 90                       struct nouveau_handle *handle)
 91 {
 92         int ret = -EEXIST;
 93         write_lock_irq(&namedb->lock);
 94         if (!nouveau_namedb_lookup(namedb, name)) {
 95                 nouveau_object_ref(object, &handle->object);
 96                 handle->namedb = namedb;
 97                 list_add(&handle->node, &namedb->list);
 98                 ret = 0;
 99         }
100         write_unlock_irq(&namedb->lock);
101         return ret;
102 }
这个insert函数首先查找是否有重复的,有就返回-EEXIST,然后用链表连起来,返回.
第127行,检查parent有没有一个关联handle和object的函数,对于这个例子,内存用kzalloc分配,之后也没有对这个进行初始化. 所以不会进入这个if语句.
第137行,如果object和namedb不一样,那么寻找_parent对应的handle,然后把当前handle的head链表加入到parent handle的tree链表中.
对于这个例子object和namedb相等,因此不会执行进去.
这个地方可以顺便看一下nouveau_namedb_get和nouveau_namedb_put:
// /drivers/gpu/drm/nouveau/core/core/namedb.c
115 struct nouveau_handle *
116 nouveau_namedb_get(struct nouveau_namedb *namedb, u32 name)
117 {
118         struct nouveau_handle *handle;
119         read_lock(&namedb->lock);
120         handle = nouveau_namedb_lookup(namedb, name);
121         if (handle == NULL)
122                 read_unlock(&namedb->lock);
123         return handle;
124 }
// /drivers/gpu/drm/nouveau/core/core/namedb.c
159 void
160 nouveau_namedb_put(struct nouveau_handle *handle)
161 {
162         if (handle)
163                 read_unlock(&handle->namedb->lock);
164 }
// /drivers/gpu/drm/nouveau/core/core/namedb.c
 30 static struct nouveau_handle *
 31 nouveau_namedb_lookup(struct nouveau_namedb *namedb, u32 name)
 32 {
 33         struct nouveau_handle *handle;
 34 
 35         list_for_each_entry(handle, &namedb->list, node) {
 36                 if (handle->name == name)
 37                         return handle;
 38         }
 39 
 40         return NULL;
 41 }
代码都很容易理解,自己尝试阅读一下,不多说了. 回到刚才那个函数:

第149行,把phandle赋值为handle,返回.
回到nouveau_client_create_,第229行,初始化几个字段,比如把device字段储存上对应的nouveau_device,这个也就是我们在上一篇中用nouveau_device_create创建的那个.
返回! 至此我们终于可以回到nouveau_drm_load了!

第376行,把新创建的nouveau_drm放进drm提供的结构体里.
第377行,把drm的结构体放进nouveau_drm里.
第378行,一个debug配置信息. 这里的nvkm_xxx就是获得nvif_object所对应的nouveau_object,并把它转换成nouveau_xxx .
第381行,初始化clients链表. 对nouveau设备文件进行open操作时就会创建一个nouveau_cli并加入这个链表. 里面包含的是每个文件特有的东西,比如前面提到的usif .
第384行,获取hdmi设备. 不知道hdmi是什么的可以去百度谷歌一下.
// /drivers/gpu/drm/nouveau/nouveau_drm.c
336 static void
337 nouveau_get_hdmi_dev(struct nouveau_drm *drm)
338 {
339         struct pci_dev *pdev = drm->dev->pdev;
340 
341         if (!pdev) {
342                 DRM_INFO("not a PCI device; no HDMI\n");
343                 drm->hdmi_device = NULL;
344                 return;
345         }
346 
347         /* subfunction one is a hdmi audio device? */
348         drm->hdmi_device = pci_get_bus_and_slot((unsigned int)pdev->bus->number,
349                                                 PCI_DEVFN(PCI_SLOT(pdev->devfn), 1));
350 
351         if (!drm->hdmi_device) {
352                 NV_DEBUG(drm, "hdmi device not found %d %d %d\n", pdev->bus->number, PCI_SLOT(pdev->devfn), 1);
353                 return;
354         }
355 
356         if ((drm->hdmi_device->class >> 8) != PCI_CLASS_MULTIMEDIA_HD_AUDIO) {
357                 NV_DEBUG(drm, "possible hdmi device not audio %d\n", drm->hdmi_device->class);
358                 pci_dev_put(drm->hdmi_device);
359                 drm->hdmi_device = NULL;
360                 return;
361         }
362 }
第341行,检查是否是PCI设备.
第348行,使用PCI模块的函数获取HDMI对应的PCI设备.
第356行,检查获取的HDMI设备的class信息.

接下来的nvif_device_init又是一个大函数,暂且先到这. 下一篇再讨论nvif_device_init函数.

Nouveau源码分析(四):NVIDIA设备初始化之nouveau_drm_load (1)

标签:驱动   源码分析   nvidia   nouveau   linux   

原文地址:http://blog.csdn.net/goodqt/article/details/40979051

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!