P106-100改显示接口翻车后记——之NVIDIA显卡vBIOS分析
在前面的文章《P106-100改显示接口翻车记》里面笔者提到过,P106-100矿卡在加上显示接口,补焊缺失的无源器件,并且刷入GTX1060的vbois后,仍然无法正常输出显示信号(甚至刷了BIOS以后连驱动都无法正常加载)。因此在文章末尾笔者得出P106-100要加显示接口是不可能的这个结论。本来这篇文章只是笔者只是对自己简单折腾过程的一个记录,但没想到引起了许多爱好者的关注,热心网友们也给我提了不少建议以及抛出了不少疑问,其中有很多是关于修改vbios的,为此,笔者专门对vbios进行了逆向工程分析,并将分析结果整理成此文,供后人参考,如果要看结论的话大家直接拉到末尾便好。
一、VBIOS结构
NVIDIA一直对显卡架构的核心技术守口如瓶,开源社区对NVIDIA的评价是very open source unfriendly,也就是说对开源非常不友好。所以,能够找到的资料非常之少,少到仅仅只有官方发布的少量文档,且这些文档更新缓慢,甚至有很多已经是过时的了,所以,对于开源驱动开发者来说,不能仅仅只可怜巴巴的依靠NVIDIA赏点文档。所以已经有许许多多的逆向工程师对NVIDIA的GPU体系结构做了大量的逆向工程分析,这才有了Linux下的开源驱动Nouveau。通过阅读开源驱动源码和社区对NVIDIA各个架构GPU相关的文档,就可以基本对VBIOS有了一个比较全面的了解,所以接下来我把我知道的信息写下来,供大家参考。
首先,VBIOS的结构通常如下图所示:

由上图可以看出,一个完整的vBIOS镜像会包括一个可选的IFR头部,以及若干个PCI Expansion ROM,然后再跟上若干个NVIDIA Image,接下来分别介绍。
1.Internal Form Representation(IFR) Header
有些VBIOS会拥有一个可选的IFR(Internal Form Representation)头部,中文应该叫内部格式描述头部,用于描述vbios中内容的大致数据结构,这部分出现在VBIOS文件的最前面,通常是以NVGI开头的部分,如下图:

在IFR头部中,通常会指定头部的大小,以及各个区块的描述信息,当然,这个头部是可选的,有些vbios里就可以没有,这个头部里面不会含有GPU的重要配置信息,对我们分析vbios并没有什么用处,所以这里笔者就不再细写了,重点看后面的PCI Expansion ROM。
2.PCI Expansion ROM
PCI Expansion ROM又叫PCI扩展ROM,通常以十六进制55 AA开头,可以不止一个,如下图所示就是一个典型的PCI Expansion Rom结构:

严格的来说,PCI Expansion ROM这部分才是真正vbios的开始,这里面含有大量有用的配置信息,在NVIDIA的vbios中,第一个PCI Expansion ROM,比如上图所示的图片中,含有大量与显卡有关的配置信息,版本号等,这个部分是本文分析的重点,我会在后面详细分析PCI Expansion ROM 的结构以及P106-100和GTX1060的vbios在这部分的区别。
3.NVIDIA Image
这部分就是NVIDIA自己定义的镜像内容了,通常是一些GPU内部ISA核心的初始化代码,在PCI Expansion ROM加载后,GPU的PMU单元,被初始化,PMU单元中含有一个微控制器,目前已知这个微控制器是NVIDIA自己设计的一个叫falcon架构的RISC微控制器,指令集并不公开,目前开源社区通过逆向工程分析后,也只知道了部分指令,勉强够用。PMU正常启动后,会加载各个NVIDIA Image给内部的其他ISA,使其初始化为正常工作状态。一个NVIDIA Image通常以十六进制56 4E也就是ASCII码的”VN”开头,如下图所示:

这部分的代码指令架构通常是私有的,只有NVIDIA的GPU能够正常识别,里面通常会包含NPDS(Nvidia PCI Data Structure)和NPDE(Nvidia PCI Data Structure Extended)等记录结构,是GPU比较核心部分的初始化需要用到的代码和数据。经过一些分析可以发现,这部分代码对于我们加输出接口似乎没有直接影响,所以这里笔者也不做详细介绍了,有兴趣的朋友可以自己去阅读NVIDIA官方的开源文档。
二、对PCI Expansion ROM的详细分析
不看不知道,一看还真的吓一跳,那就是,NVIDIA的第一个PCI Expansion ROM中,含有大量的配置信息和脚本字节码。经过仔细分析笔者发现,这里面含有的信息量非常大,这里面包含了PCIR,版本号,BIT,内存,和DCB信息。这部分内容里面有几个模块是和我们显示信号的输出是有关系的,其中PCIR内容包含了vendor ID和device ID,以及code type等信息,以及会包含这个ROM中所含有的其他内容的offset信息,通过对PCIR的分析,可以找到BIOS中各个部分所在的位置。那么,在这么多的信息当中,哪几个信息是对我们有用的呢?经过分析主要就是DCB,和BIT中和显示相关的几个Table,我们先讲最重要的DCB部分。
1.PCIR(PCI ROM)
在开始介绍DCB之前,有必要先说一下PCIR,PCIR是PCI ROM的缩写,通是PCI Expansion ROM头部后紧跟的第一个数据块,这个数据块通常以ASCII字符串的”PCIR”开头。PCIR里面记录了这个设备的Vendor ID,Device ID以及ROM的数据结构,比如这个块的代码类型(Code Type)和Class Code,其中Class Code是一些寄存器标志位信息,用以识别这个该如何加载这个块,而Code Type则标识了这个块的指令集信息,下图是一个典型的PCIR结构。

2.Device Control Blocks(DCB)设备控制块
根据官方的说法,DCB是一些静态的数据表格,用来描述每个PCB的拓扑信息、板子上的连接和GPU所连接外部设备(比如风扇,LED之类的)。每一块不同的PCB都要配置与其相匹配的DCB信息才能使显卡工作正常,否则就会导致显卡无法正常工作、花屏、无显示等问题。其中DCB中还包含了很重要的Display Path Information,Connector table,GPIO Table以及I2C Table等信息,其中GPIO Table用来描述与GPU相连的外部IO,其中很重要的显示接口的插入检测(HPD)就需要用到。接下来我会详细分析每个部分。通过参考开源驱动源码和文档,笔者自己写了一个python脚本用于解析BIOS各部分的内容,接下来就让笔者结合自己写的脚本来逐步分析DCB的几个重要部分。 为了更有代表性的比较研究,笔者这里找了两块PCB相同的显卡,索泰P106-100掘金版和索泰GTX1060毁灭者6G,经过仔细对比,这两款显卡的PCB和散热设计是完全相同的!甚至这款P106-100有一个DVI接口已经焊上,只不过输出部分电路的无源器件电容电阻没焊上。
下面两图大家可以一起来找茬:


1)Display Path Information(DPI)
这部分信息很重要,这部分描述的是GPU的输出通道信息,直接影响GPU的输出模块类型定义。

我们用自己写的脚本分别解析一下1060和P106的DPI结构:

可以看出GTX1060的输出路径配置包含5个通道,其中TMDS可被用作HDMI或者DVI,而DP就是DisplayPort。我们再看P106:

由此可以很明显的看到,P106-100缺少Display Path的配置,这个配置信息表格是空的,因此若是想让P106-100能有输出,我们需要用GTX1060毁灭者的配置来覆盖P106-100 BIOS中的这部分内容。
2)Connector Table
这个表格也是DCB中的一部分,这部分信息描述了当前这块显卡有几个输出接口,每个接口对应Display Path Information表格中哪个通道的映射信息,以及接口的类型,这个表格很重要,因为驱动程序在加载时会读取这个表格,进而了解显卡有哪些输出口,对应的会进行输出配置。

我们再来比较一下两者的区别:


很显然,P106-100的Connector Table是空的,而GTX1060配置了5个输出,1个DVI-D,3个 DP和1个HDMI,而实际上索泰这款显卡只有1个DVI,1个HDMI和1个DP,推测应该是还有2个DP接口没有引出,相当于没有使用,不过不引出应该不会影响正常工作,只是浪费罢了。因此,如果要让P106-100有正常的输出,这部分也应该修改过去。
3)GPIO Table
GPIO Table中包含了GPU和外部IO输出口的配置信息,包括输出端口的插头检测引脚等信息。

我们继续分析比较:


很显然,GTX1060的GPIO Table中,比P106-100要多了HPD(Hot Plug Detection)引脚的信息,也就是插头插入的检测引脚,所以要使得输出检测可以正常工作,这部分也要对应的按照GTX1060的改。
4)Switched Outputs Table(Mux Table)
这个Table有些地方也称为Mux Table,即描述GPIO的多路复用信息,根据NVIDIA官方的描述这部分内容是因为现在新的GPU设计允许自由配置输出接口的检测功能,选择不同的输出口或是可以开启或者关闭某个输出口,以及I2C和GPIO功能可以互相切换。具体细节笔者也没详细研究,只是简单比较了一下GTX1060的这部分和P106的对应部分,发现大部分都是一样的,因此只需要简单修改即可。


经过比较发现,GTX1060和P106的Mux table内容是一模一样的,只不过头部GTX1060所指示的是有0x12个Entry,而P106对应部分是0,所以这里也需要按照GTX1060的改。
3.BIOS Information Table(BIT)
经过进一步分析以后,还有一个地方和输出是有关系的,但是这部分并不在DCB表中,而在BIT表中,这个表中内容繁复复杂,我这里就不全部罗列出来了,仅仅列出和显示相关的部分。

| 名称 | 标记 | 含义 |
| BIT_TOKEN_TMDS_PTRS | T | TMDS时序表的指针 |
| BIT_TOKEN_DISPLAY_PTRS | U | 显示脚本列表的指针 |
| BIT_TOKEN_DP_PTRS | d | DP输出口时序表的指针 |
以上三个部分就是和显示有关的相关信息了,其中其实还有一个表是BIT_TOKEN_LVDS_PTRS也就是LVDS信号的时序配置表,由于GTX1060里面没有这部分内容,所以这里也就不关心了。通过比较发现,GTX1060的BIT表中,有对应时序表和脚本的指针,而P106-100缺少这部分内容。举个例子,下面是用我的脚本解析出来的GTX1060的DP时序配置信息表,而P106-100则没有这一部分。
HWINFO ext sig mismatch [0x00]
BIT table 'd' at 0x23a, version 1
0x00: 0x65f5 => d DP INFO
d DP INFO table at 0x65f5, version 42
-- flags 0x04
-- regular_vswing 0x2328, low_vswing 0x2319
-- DP INFO TARGET TABLE entries:
[0] key 0xf410006, flags 0x2, level_entry_table_index 0, hbr2_min_vdt_index 255
0x601b => before_link_training
0x5e92 => after_link_training
before_link_speed[] = 0x6003 {
0x1e [8.1G ] => 0x606f
0x14 [5.4G ] => 0x6080
0x0a [2.7G ] => 0x6091
0x06 [1.62G] => 0x60a2
}
0x6108 => enable_spread
0x60f7 => disable_spread
0x5eee => disable_lt
[1] key 0xf810006, flags 0x2, level_entry_table_index 0, hbr2_min_vdt_index 255
0x6025 => before_link_training
0x5e92 => after_link_training
before_link_speed[] = 0x600f {
0x1e [8.1G ] => 0x60b3
0x14 [5.4G ] => 0x60c4
0x0a [2.7G ] => 0x60d5
0x06 [1.62G] => 0x60e6
}
0x612a => enable_spread
0x6119 => disable_spread
0x613b => disable_lt
[2] key 0xf420006, flags 0x2, level_entry_table_index 1, hbr2_min_vdt_index 255
0x6217 => before_link_training
0x5e92 => after_link_training
before_link_speed[] = 0x61ff {
0x1e [8.1G ] => 0x626b
0x14 [5.4G ] => 0x627c
0x0a [2.7G ] => 0x628d
0x06 [1.62G] => 0x629e
}
0x6304 => enable_spread
0x62f3 => disable_spread
0x5eee => disable_lt
[3] key 0xf820006, flags 0x2, level_entry_table_index 1, hbr2_min_vdt_index 255
0x6221 => before_link_training
0x5e92 => after_link_training
before_link_speed[] = 0x620b {
0x1e [8.1G ] => 0x62af
0x14 [5.4G ] => 0x62c0
0x0a [2.7G ] => 0x62d1
0x06 [1.62G] => 0x62e2
}
0x6326 => enable_spread
0x6315 => disable_spread
0x6337 => disable_lt
[4] key 0xf440006, flags 0x2, level_entry_table_index 1, hbr2_min_vdt_index 255
0x6413 => before_link_training
0x5e92 => after_link_training
before_link_speed[] = 0x63fb {
0x1e [8.1G ] => 0x6478
0x14 [5.4G ] => 0x6478
0x0a [2.7G ] => 0x6489
0x06 [1.62G] => 0x649a
}
0x6500 => enable_spread
0x64ef => disable_spread
0x5eee => disable_lt
[5] key 0xf840006, flags 0x2, level_entry_table_index 1, hbr2_min_vdt_index 255
0x641d => before_link_training
0x5e92 => after_link_training
before_link_speed[] = 0x6407 {
0x1e [8.1G ] => 0x64bc
0x14 [5.4G ] => 0x64bc
0x0a [2.7G ] => 0x64cd
0x06 [1.62G] => 0x64de
}
0x6522 => enable_spread
0x6511 => disable_spread
0x6533 => disable_lt
[6] <null>
-- DP INFO LEVEL TABLE entries:
[0] DP INFO LEVEL TABLE:
00: DriveCurrent 0x12, PreEmphasis 0x00, TxPu 0x02
01: DriveCurrent 0x17, PreEmphasis 0x0a, TxPu 0x03
02: DriveCurrent 0x1d, PreEmphasis 0x16, TxPu 0x04
03: DriveCurrent 0x25, PreEmphasis 0x27, TxPu 0x06
04: DriveCurrent 0x1b, PreEmphasis 0x00, TxPu 0x03
05: DriveCurrent 0x21, PreEmphasis 0x0e, TxPu 0x04
06: DriveCurrent 0x29, PreEmphasis 0x1f, TxPu 0x06
07: DriveCurrent 0x25, PreEmphasis 0x00, TxPu 0x04
08: DriveCurrent 0x2d, PreEmphasis 0x13, TxPu 0x06
09: DriveCurrent 0x32, PreEmphasis 0x00, TxPu 0x06
[1] DP INFO LEVEL TABLE:
00: DriveCurrent 0x13, PreEmphasis 0x00, TxPu 0x02
01: DriveCurrent 0x19, PreEmphasis 0x0b, TxPu 0x03
02: DriveCurrent 0x1f, PreEmphasis 0x18, TxPu 0x04
03: DriveCurrent 0x2a, PreEmphasis 0x2d, TxPu 0x06
04: DriveCurrent 0x1c, PreEmphasis 0x00, TxPu 0x03
05: DriveCurrent 0x22, PreEmphasis 0x0f, TxPu 0x04
06: DriveCurrent 0x2c, PreEmphasis 0x22, TxPu 0x06
07: DriveCurrent 0x27, PreEmphasis 0x00, TxPu 0x04
08: DriveCurrent 0x30, PreEmphasis 0x15, TxPu 0x06
09: DriveCurrent 0x34, PreEmphasis 0x00, TxPu 0x06
[2] DP INFO LEVEL TABLE:
00: DriveCurrent 0x0b, PreEmphasis 0x00, TxPu 0x02
01: DriveCurrent 0x0f, PreEmphasis 0x06, TxPu 0x03
02: DriveCurrent 0x13, PreEmphasis 0x0e, TxPu 0x04
03: DriveCurrent 0x18, PreEmphasis 0x18, TxPu 0x06
04: DriveCurrent 0x0e, PreEmphasis 0x00, TxPu 0x03
05: DriveCurrent 0x12, PreEmphasis 0x07, TxPu 0x04
06: DriveCurrent 0x16, PreEmphasis 0x0f, TxPu 0x06
07: DriveCurrent 0x12, PreEmphasis 0x00, TxPu 0x04
08: DriveCurrent 0x16, PreEmphasis 0x09, TxPu 0x06
09: DriveCurrent 0x17, PreEmphasis 0x00, TxPu 0x06
看来要修改的地方还是很多的,不过经过仔细比较发现,P106-100的这些表其实都是存在的,内容与GTX1060毁灭者的差不太多,只不过在BIT头部中,将这些表格的指针置为了空指针,即0x0:


我们看到GTX1060的TMDS表指针为0x5299,我们到P106-100的vbios对应指针的位置看一下,发现内容在,只不过略有不同,不过尚不清楚这部分内容是不是正确的只没有引用而已。不过大致来看,P106-100只是没有设置这个指针的值而已,我们只要参照着GTX1060改应该问题也不大。
4. PMU
在最后我还是打算提一下PMU,虽然PMU本身和显示没有太大关系,但PMU是个很坑的东西,所谓PMU其实是是个NVIDIA显卡上的控制器单元,负责协调显卡各个模块运作、BIOS更新以及电源管理等功能,并且运行了一个叫做SEQ的脚本引擎。
PMU提供了一些指令,用于读写和校验BIOS的内容,驱动程序会调用这些API来完成一些功能。其中包含了一个微控制器也就是前面提到的falcon架构的微控制器。
由于PMU的资料很少,笔者了解也有限,但是PMU真的很重要,所以这里只做一个简单的介绍。
三、vbios修改实验
好了,前面分析了那么多,我们也知道vbios里哪些部分和输出有关系了,接下来我们实践一下,通过修改vbios的对应部分看看是否真的可以使得P106-100输出显示信号,不过笔者这里打算分2个阶段做实验,以方便比较。
阶段1:修改GTX1060的vbios
操作方法: 将输出部分按照P106-100的改,然后看看是否会没有输出
首先我们要制作修改后的vbios,通过前面的分析,这一步应该已经比较简单了,完成之后,笔者先是尝试了用了之前patch掉所有校验的nvflash来刷机,然而最后还是刷不进去,报错:
PMU command complete with error, Error code = 0x006C
查了一下是:
NV_UCODE_ERR_CODE_CMD_EWR_OK_TO_FLASH_CHECK_FAILED,好像是校验失败了,后来查了一下发现,每个BIOS块的最后一个字节为checksum,修改了之后还需要修改一下checksum。不过checksum的算法很简单,就是让每个块单个字节相加并且mod 256,最后结果为0即可。修改完checksum,最后还是报错,不过这次变成了:
PMU command complete with error, Error code = 0x003F
这次的错误是:
NV_UCODE_ERR_CODE_CERT20_VDPA_NOT_FINALIZED
看来是证书校验失败,PMU会校验证书,如果证书校验失败,则不让强刷固件,不过没关系,我们可以把flash芯片从显卡PCB上吹下来,上CH341编程器强刷。

由于显卡上的flash芯片是1.8V的低压flash,所以我这里还需要再装一块电平转换板,如果不加的话似乎没有办法正常进行编程,而且容易损坏flash。烧写完成之后,启动电脑,果然不出所料,GTX1060的确没有了输出,我这里还装了另一块GTX750Ti显卡方便调试,可是进系统后,却发现驱动无法正常启动了,设备管理器里面变成了这样:

先不管他吧,到GPU-Z里看一眼:


看起来似乎有戏,connectors被改没了。
好了,我们接下来试试P106。
阶段2:修改P106-100的vbios
操作方法: 将输出部分按照1060的改
继续上编程器,暴力刷机,然而,刷进去之后,好像一如既往翻车了,不但没有输出,设备管理器里连驱动都加载不了:

而在GPU-Z里,似乎连bios都读不出来:

到底是哪里姿势不对?
为此,笔者又做了阶段3的实验。
阶段3:只修改一个字节
操作方法: 只修改P106-100的BIOS中无关紧要的一个字节,进系统观察是否正常
笔者做了大量尝试,即使只修改1个无关紧要的字节,发现依旧是前面这个样子,后来又尝试了linux下的nouveau驱动,发现驱动一加载,整个系统都崩了,内核报错MMIO Error。
看来只要修改了PCI Expansion ROM,哪怕是1个没用的字节,都会导致驱动无法加载。
到此为止,结果毫无悬念是翻车了。
四、结论
毫无悬念的,结论还是翻车了,要改出正常的vbios没有那么容易,至于原因,最有可能的原因应该是从Pascal这一代开始,GPU的固件中会校验BIOS的证书。而通过查找网上的一些江湖传言也间接证实这个观点,有些AIC厂商的所谓“内部人士”号称自己拥有测试屏蔽工具,可以对BIOS进行定制,但BIOS发布之前,需要将固件发到NVIDIA官方去签名,这个BIOS固件才可以正常在显卡上运行起来。
看上去现在NVIDIA的GPU已经使用了一些可信启动技术,只有经过NVIDIA官方代码签名的BIOS才能在GPU上正常跑起来,否则连基本的MMIO都无法正常进行通信,更别说笔者这次一次修改了那么多的配置信息了,而且要使P106-100拥有显示功能,需要修改的地方还不少。
而为什么P106-100刷GTX1060的BIOS为什么不行呢?因为NVIDIA从Maxwell这一代开始,Device ID是使用了熔丝固化的,和上一代Kepler用电阻设置ID的方式有很大不同,这次的话PCIE的Vendor ID和Device ID是完全不让修改了,并且,在BIOS启动时,会比较BIOS里的ID和芯片内部的ID是否相同,否则就会起不来。而如果只修改BIOS中的ID再刷入,又会涉及到证书的问题,除非日了NVIDIA原厂,偷到私钥,否则任何第三方都无法随意给BIOS代码做签名。
另外,笔者猜测,如果能够获得合法签名来修改vbios配置P106-100来支持视频输出或许是可行的。但是现在作为第三方,是不可能做出合法签名的bios的。
因此,笔者认为P106-100可能有以下三种情况:
- 硬件熔丝直接disable了输出单元电路。
- 封装的时候输出信号引脚根本没有从Die上引到封装上。
- 也许可以有输出,只是BIOS里没有配置出来。
当然,也有可能是P106-100批次的芯片质量参差不齐,有的输出电路只是有瑕疵,可以输出,但是会花屏之类,有的芯片是输出电路完全废了,所以每一块P106能不能改出正常输出可能还要拼人品,因此厂商干脆就统一没有给配置输出,就做矿卡。
至于具体是哪一个种情况, 就只有老黄自己知道了,目前是个谜了。
但是结论,笔者认为已经很明确了,那些妄想简单修改bios就能魔改出输出接口的人,可以散了。
浙公网安备 33011002014706号