首页 > 代码库 > Nouveau源码分析(七): 各SUBDEV/ENGINE初始化 (1)

Nouveau源码分析(七): 各SUBDEV/ENGINE初始化 (1)

Nouveau源码分析(七)

虽然各个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 = http://www.mamicode.com/kmemdup(fw->data, fw->size, GFP_KERNEL);>首先,第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 = http://www.mamicode.com/kmalloc(bios->size, GFP_KERNEL);>

首先,这里先来看几个读写寄存器或其他东西的函数: 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;
	}
}

函数名很明显是读取显存,刚刚我们获取这BIOS地址也是在显存里的,我们要想办法把他复制出来.

那么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 

先读寄存器,然后mask掉参数mask,or上参数data. 对于这个例子,mask就是0xffffffff,所以真正写进去的东西就是这个data,也就是刚刚的addr >> 16.

于是,这样的话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 }

第34行,先得到起始地址entry和个数entries.

第37行,遍历每一个entry,并且判断是否符合传入的id,然后返回之.


第501行,另一个if语句,在bit寻找失败的情况下判断bmp是否可用,可用就储存版本号.

第508行,输出调试信息,返回.


至此我们成功创建了第一个subdev,但我们并不会立即去执行它的init函数,在此之前,我们还要先执行devinit,i2c,gpio的ctor函数.

Nouveau源码分析(七): 各SUBDEV/ENGINE初始化 (1)