首页 > 代码库 > 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)