标签:linux nouveau nvidia 源码分析 驱动
虽然各个SUBDEV/EGINE的初始化实际还是在nouveau_drm_load里,但还是换个标题吧. 等把各个SUBDEV/ENGINE之类的说完再换回去.
上次已经按着初始化的顺序介绍了一下各个subdev的用途,现在按顺序,首先来看VBIOS的ctor函数:
// /drivers/gpu/drm/nouveau/core/subdev/bios/base.c
537 struct nouveau_oclass
538 nouveau_bios_oclass = {
539 .handle = NV_SUBDEV(VBIOS, 0x00),
540 .ofuncs = &(struct nouveau_ofuncs) {
541 .ctor = nouveau_bios_ctor,
542 .dtor = nouveau_bios_dtor,
543 .init = nouveau_bios_init,
544 .fini = nouveau_bios_fini,
545 .rd08 = nouveau_bios_rd08,
546 .rd16 = nouveau_bios_rd16,
547 .rd32 = nouveau_bios_rd32,
548 .wr08 = nouveau_bios_wr08,
549 .wr16 = nouveau_bios_wr16,
550 .wr32 = nouveau_bios_wr32,
551 },
552 };// /drivers/gpu/drm/nouveau/core/subdev/bios/base.c
459 static int
460 nouveau_bios_ctor(struct nouveau_object *parent,
461 struct nouveau_object *engine,
462 struct nouveau_oclass *oclass, void *data, u32 size,
463 struct nouveau_object **pobject)
464 {
465 struct nouveau_bios *bios;
466 struct bit_entry bit_i;
467 int ret;
468
469 ret = nouveau_subdev_create(parent, engine, oclass, 0,
470 "VBIOS", "bios", &bios);
471 *pobject = nv_object(bios);
472 if (ret)
473 return ret;
474
475 ret = nouveau_bios_shadow(bios);
476 if (ret)
477 return ret;
478
479 /* detect type of vbios we're dealing with */
480 bios->bmp_offset = nvbios_findstr(bios->data, bios->size,
481 "\xff\x7f""NV\0", 5);
482 if (bios->bmp_offset) {
483 nv_info(bios, "BMP version %x.%x\n",
484 bmp_version(bios) >> 8,
485 bmp_version(bios) & 0xff);
486 }
487
488 bios->bit_offset = nvbios_findstr(bios->data, bios->size,
489 "\xff\xb8""BIT", 5);
490 if (bios->bit_offset)
491 nv_info(bios, "BIT signature found\n");
492
493 /* determine the vbios version number */
494 if (!bit_entry(bios, 'i', &bit_i) && bit_i.length >= 4) {
495 bios->version.major = nv_ro08(bios, bit_i.offset + 3);
496 bios->version.chip = nv_ro08(bios, bit_i.offset + 2);
497 bios->version.minor = nv_ro08(bios, bit_i.offset + 1);
498 bios->version.micro = nv_ro08(bios, bit_i.offset + 0);
499 bios->version.patch = nv_ro08(bios, bit_i.offset + 4);
500 } else
501 if (bmp_version(bios)) {
502 bios->version.major = nv_ro08(bios, bios->bmp_offset + 13);
503 bios->version.chip = nv_ro08(bios, bios->bmp_offset + 12);
504 bios->version.minor = nv_ro08(bios, bios->bmp_offset + 11);
505 bios->version.micro = nv_ro08(bios, bios->bmp_offset + 10);
506 }
507
508 nv_info(bios, "version %02x.%02x.%02x.%02x.%02x\n",
509 bios->version.major, bios->version.chip,
510 bios->version.minor, bios->version.micro, bios->version.patch);
511
512 return 0;
513 }第469行,首先创建一个subdev,这个函数我们已经分析过了.
第475行,寻找匹配BIOS:
// /drivers/gpu/drm/nouveau/core/subdev/bios/base.c
328 static int
329 nouveau_bios_shadow(struct nouveau_bios *bios)
330 {
331 struct methods shadow_methods[] = {
332 #if defined(__powerpc__)
333 { "OpenFirmware", nouveau_bios_shadow_of, true, 0, 0, NULL },
334 #endif
335 { "PRAMIN", nouveau_bios_shadow_pramin, true, 0, 0, NULL },
336 { "PROM", nouveau_bios_shadow_prom, false, 0, 0, NULL },
337 { "ACPI", nouveau_bios_shadow_acpi, true, 0, 0, NULL },
338 { "PCIROM", nouveau_bios_shadow_pci, true, 0, 0, NULL },
339 { "PLATFORM", nouveau_bios_shadow_platform, true, 0, 0, NULL },
340 {}
341 };
342 struct methods *mthd, *best;
343 const struct firmware *fw;
344 const char *optarg;
345 int optlen, ret;
346 char *source;
347
348 optarg = nouveau_stropt(nv_device(bios)->cfgopt, "NvBios", &optlen);
349 source = optarg ? kstrndup(optarg, optlen, GFP_KERNEL) : NULL;
350 if (source) {
351 /* try to match one of the built-in methods */
352 mthd = shadow_methods;
353 do {
354 if (strcasecmp(source, mthd->desc))
355 continue;
356 nv_info(bios, "source: %s\n", mthd->desc);
357
358 mthd->shadow(bios);
359 mthd->score = nouveau_bios_score(bios, mthd->rw);
360 if (mthd->score) {
361 kfree(source);
362 return 0;
363 }
364 } while ((++mthd)->shadow);
365
366 /* attempt to load firmware image */
367 ret = request_firmware(&fw, source, &nv_device(bios)->pdev->dev);
368 if (ret == 0) {
369 bios->size = fw->size;
370 bios->data = kmemdup(fw->data, fw->size, GFP_KERNEL);
371 release_firmware(fw);
372
373 nv_info(bios, "image: %s\n", source);
374 if (nouveau_bios_score(bios, 1)) {
375 kfree(source);
376 return 0;
377 }
378
379 kfree(bios->data);
380 bios->data = NULL;
381 }
382
383 nv_error(bios, "source \'%s\' invalid\n", source);
384 kfree(source);
385 }
386
387 mthd = shadow_methods;
388 do {
389 nv_info(bios, "checking %s for image...\n", mthd->desc);
390 mthd->shadow(bios);
391 mthd->score = nouveau_bios_score(bios, mthd->rw);
392 mthd->size = bios->size;
393 mthd->data = bios->data;
394 bios->data = NULL;
395 } while (mthd->score != 3 && (++mthd)->shadow);
396
397 mthd = shadow_methods;
398 best = mthd;
399 do {
400 if (mthd->score > best->score) {
401 kfree(best->data);
402 best = mthd;
403 }
404 } while ((++mthd)->shadow);
405
406 if (best->score) {
407 nv_info(bios, "using image from %s\n", best->desc);
408 bios->size = best->size;
409 bios->data = best->data;
410 return 0;
411 }
412
413 nv_error(bios, "unable to locate usable image\n");
414 return -EINVAL;
415 }首先,第331行,列出BIOS的几种来源,识别函数,是否可写等信息.
第348行,从cfgopt中检查是否加载模块时特别指定了所要的BIOS类型,一般来说不指定的话,第350行BIOS语句直接跳过.
第387行,准备识别BIOS.
然后第389行的while语句,首先调用shadow函数指针来检查获取这种类型的BIOS.
再使用nouveau_bios_score函数,得到这个BIOS的"成绩". 简单看一下这个函数:
// /drivers/gpu/drm/nouveau/core/subdev/bios/base.c
299 static int
300 nouveau_bios_score(struct nouveau_bios *bios, const bool writeable)
301 {
302 if (bios->size < 3 || !bios->data || bios->data[0] != 0x55 ||
303 bios->data[1] != 0xAA) {
304 nv_info(bios, "... signature not found\n");
305 return 0;
306 }
307
308 if (nvbios_checksum(bios->data,
309 min_t(u32, bios->data[2] * 512, bios->size))) {
310 nv_info(bios, "... checksum invalid\n");
311 /* if a ro image is somewhat bad, it's probably all rubbish */
312 return writeable ? 2 : 1;
313 }
314
315 nv_info(bios, "... appears to be valid\n");
316 return 3;
317 }首先检查魔数是否匹配,不匹配直接返回0.
然后checksum,如果匹配返回3. 不匹配的话,BIOS可写2分,不可写1分. [因为可写的可能被污染了.]
回到nouveau_bios_ctor函数,如果得到的分数是3,也就是最高分,那么退出while循环,不然继续下次循环.
第399行,后面紧跟着还是一个while循环,它的作用就是挑选出得分最高的那个BIOS来使用.
第408行,把分数最高的BIOS放进结构体中并返回.
第414行,如果所有BIOS得分都是0分,那么返回-EINVAL.
对于那几种BIOS的识别函数,我们挑选一个来看:
// /drivers/gpu/drm/nouveau/core/subdev/bios/base.c
84 static void
85 nouveau_bios_shadow_pramin(struct nouveau_bios *bios)
86 {
87 struct nouveau_device *device = nv_device(bios);
88 u64 addr = 0;
89 u32 bar0 = 0;
90 int i;
91
92 if (device->card_type >= NV_50) {
93 if (device->card_type >= NV_C0 && device->card_type < GM100) {
94 if (nv_rd32(bios, 0x022500) & 0x00000001)
95 return;
96 } else
97 if (device->card_type >= GM100) {
98 if (nv_rd32(bios, 0x021c04) & 0x00000001)
99 return;
100 }
101
102 addr = nv_rd32(bios, 0x619f04);
103 if (!(addr & 0x00000008)) {
104 nv_debug(bios, "... not enabled\n");
105 return;
106 }
107 if ( (addr & 0x00000003) != 1) {
108 nv_debug(bios, "... not in vram\n");
109 return;
110 }
111
112 addr = (addr & 0xffffff00) << 8;
113 if (!addr) {
114 addr = (u64)nv_rd32(bios, 0x001700) << 16;
115 addr += 0xf0000;
116 }
117
118 bar0 = nv_mask(bios, 0x001700, 0xffffffff, addr >> 16);
119 }
120
121 /* bail if no rom signature */
122 if (nv_rd08(bios, 0x700000) != 0x55 ||
123 nv_rd08(bios, 0x700001) != 0xaa)
124 goto out;
125
126 bios->size = nv_rd08(bios, 0x700002) * 512;
127 if (!bios->size)
128 goto out;
129
130 bios->data = kmalloc(bios->size, GFP_KERNEL);
131 if (bios->data) {
132 for (i = 0; i < bios->size; i++)
133 nv_wo08(bios, i, nv_rd08(bios, 0x700000 + i));
134 }
135
136 out:
137 if (device->card_type >= NV_50)
138 nv_wr32(bios, 0x001700, bar0);
139 }首先,这里先来看几个读写寄存器或其他东西的函数: nv_wo32 nv_wr32 [以32位的读为例,其他的类比很容易猜到] .
// /drivers/gpu/drm/nouveau/core/include/core/subdev.h
102 static inline void
103 nv_wr32(void *obj, u32 addr, u32 data)
104 {
105 struct nouveau_subdev *subdev = nv_subdev(obj);
106 nv_spam(subdev, "nv_wr32 0x%06x 0x%08x\n", addr, data);
107 iowrite32_native(data, subdev->mmio + addr);
108 }一看到iowrite32_native,很明显是对MMIO空间寄存器的操作. 这个要求obj必须是一个subdev. 回想一下subdev的mmio字段在哪见过? 我们遇见过两次,一次是在nouveau_subdev_create创建subdev的时候,把新创建的subdev的mmio字段的值赋上了parent的mmio字段的值. 那么再往上,parent的mmio字段的值又是从哪来的呢? 这就是另一次了,在nouveau_devobj_create中,把device的mmio字段初始化为PCI设备中BAR0的地址.那么很明显了,不管对什么obj进行这个nv_wr32,一定是向PCI设备BAR0地址指示的MMIO地址写. [只讨论PCI总线,其他另说.]
// /drivers/gpu/drm/nouveau/core/include/core/object.h
177 static inline void
178 nv_wo32(void *obj, u64 addr, u32 data)
179 {
180 nv_spam(obj, "nv_wo32 0x%08llx 0x%08x\n", addr, data);
181 nv_ofuncs(obj)->wr32(obj, addr, data);
182 }这个是调用的obj的oclass里指示的函数指针wr32. 因此这个函数的作用可就是"因obj而异"了.例如,对于BIOS,这是向BIOS数据里写;对于instmem或gpuobj,这是向显存里写.
现在我们讨论BIOS当然就要看看BIOS的wr32函数指针:
// /drivers/gpu/drm/nouveau/core/subdev/bios/base.c
452 static void
453 nouveau_bios_wr32(struct nouveau_object *object, u64 addr, u32 data)
454 {
455 struct nouveau_bios *bios = (void *)object;
456 put_unaligned_le32(data, &bios->data[addr]);
457 }bios->data就是从BIOS数据区里复制出来的BIOS数据,稍等一会我们就能看到它的初始化. 这个函数当然就是对其中的数据进行写.
回到nouveau_bios_shadow_pramin,第94行,上下文推断就是检测这种BIOS是否存在,不存在就返回.
第103行,通过看调试信息知道作用是判断这种BIOS是否启用了.
第107行,判断BIOS是否在显存内.
第112行,进行适当的mask,得到BIOS在显存中的地址. 当然有时候这个地址是无效的,那么在下面的if语句中,我们将使用默认的地址.
这里有一个寄存器出现了多次,就是0x1700,等会要讲的第118行还对其进行了mask操作,那么这个寄存器这个的作用是什么呢? 可以结合envytools的一段代码理解:
// envytools /hwtest/vram.c
// line 28 ~ 43
uint32_t vram_rd32(int card, uint64_t addr) {
if (nva_cards[card]->chipset.card_type < 3) {
return nva_rd32(card, 0x1000000 + addr);
} else if (nva_cards[card]->chipset.card_type < 0x30) {
return nva_grd32(nva_cards[card]->bar1, addr);
} else if (nva_cards[card]->chipset.card_type < 0x50) {
nva_wr32(card, 0x1570, addr);
return nva_rd32(card, 0x1574);
} else {
uint32_t old = nva_rd32(card, 0x1700);
nva_wr32(card, 0x1700, addr >> 16);
uint32_t res = nva_rd32(card, 0x700000 | (addr & 0xffff));
nva_wr32(card, 0x1700, old);
return res;
}
}那么PCI设备的BAR不是已经给映射好了吗,为什么不用呢? 因为BAR映射的都是虚拟地址,现在我们完全还无法知道虚拟地址和物理地址的对应关系,这部分要到VM才被初始化. 所以我们只能想其他办法,来看看这个函数的实现吧. 首先判断显卡类型,这个我们只看NV50以后的.
首先把0x1700寄存器读出来存到old里,然后把addr >> 16 写进去,接着从0x700000 | (addr & 0xffff)中读出返回值,再把0x1700复原.
应该很容易理解这个0x1700和0x700000的作用了,0x1700决定0x700000 ~ 0x70ffff映射的物理地址,然后我们就能从中读取数据了.
那么回到第118行,再来看一下nv_mask吧:
// /drivers/gpu/drm/nouveau/core/include/core/subdev.h
110 static inline u32
111 nv_mask(void *obj, u32 addr, u32 mask, u32 data)
112 {
113 u32 temp = nv_rd32(obj, addr);
114 nv_wr32(obj, addr, (temp & ~mask) | data);
115 return temp;
116 }
117 于是,这样的话0x700000就映射上了地址addr指示的BIOS数据,当务之急就是把它取出来.
在此之前,第122行,校验魔数. 第126行,获取BIOS的大小.
如果这两步都没出错,那么第130行,分配一个等同BIOS大小的数据区. 第133行,把BIOS中的数据复制出来.
最后,第138行,恢复0x1700寄存器的值. 返回.
当然BIOS的储存位置不止着一个,还可能储存在其他地方,但这些处理函数都大同小异,无非就是读取拷贝出来,感兴趣可以自己去看源代码.
返回ctor函数,接下来的任务就是检测BIOS的版本:
第480行,虽然我们在这里发现了一个新函数,但我们并不需要去看它的内容——只看函数名足够了. 在BIOS中寻找特定字符串,这个东西叫做BMP.
第488行,寻找另一个字符串,这个叫做BIT.
第494行,这个if语句就是首先在bit_offset中寻找BIOS的版本信息,找到就储存起来.
// /drivers/gpu/drm/nouveau/core/subdev/bios/bit.c
30 int
31 bit_entry(struct nouveau_bios *bios, u8 id, struct bit_entry *bit)
32 {
33 if (likely(bios->bit_offset)) {
34 u8 entries = nv_ro08(bios, bios->bit_offset + 10);
35 u32 entry = bios->bit_offset + 12;
36 while (entries--) {
37 if (nv_ro08(bios, entry + 0) == id) {
38 bit->id = nv_ro08(bios, entry + 0);
39 bit->version = nv_ro08(bios, entry + 1);
40 bit->length = nv_ro16(bios, entry + 2);
41 bit->offset = nv_ro16(bios, entry + 4);
42 return 0;
43 }
44
45 entry += nv_ro08(bios, bios->bit_offset + 9);
46 }
47
48 return -ENOENT;
49 }
50
51 return -EINVAL;
52 }第37行,遍历每一个entry,并且判断是否符合传入的id,然后返回之.
第501行,另一个if语句,在bit寻找失败的情况下判断bmp是否可用,可用就储存版本号.
第508行,输出调试信息,返回.
至此我们成功创建了第一个subdev,但我们并不会立即去执行它的init函数,在此之前,我们还要先执行devinit,i2c,gpio的ctor函数.
Nouveau源码分析(七): 各SUBDEV/ENGINE初始化 (1)
标签:linux nouveau nvidia 源码分析 驱动
原文地址:http://blog.csdn.net/goodqt/article/details/41408751