龙芯电脑 价格(我花150元买了一台龙芯电脑)
龙芯电脑 价格(我花150元买了一台龙芯电脑)出现问题的环节是I/O DMA的一致性维持,所以我们可以采取不让硬件维持I/O DMA,走Uncached DMA来在牺牲一些I/O DMA性能的前提下绕过这个Bug。龙芯架构下,DMA的一致性是可配置的,也就是说可以从Cached地址空间进行DMA操作,硬件维护DMA一致性;也可以不由硬件控制一致性而从Uncached地址空间操作。在接受指点之后,我意识到这个Bug和I/O DMA一致性有关。从胡伟武老师的文章《我们的龙芯三号》中,我找到了关于这个Bug的详细描述:这个问题是从3B1000到3B1500改版过程中引进的,为了提高性能,处理器核收到多个维护Cache一致性的无效请求时,原来每两拍才能处理一个,改成可以连续处理,导致清除LL/SC同步指令的同步位llbit时错了一拍,误把IO DMA引起的Cache无效请求当作0号处理器核的Cache无效请求(IO DMA的编号刚好为0,与0
前言文中出现的3B1500处理器是龙芯2012年的产品,目前龙芯最新的CPU是3A3000,性能要比文中的处理器高至少三倍。这也是国人设计的第一款国产八核处理器,就算是垃圾,也是很有纪念意义的垃圾。
最近有一批龙芯3B1500主板150元低价处理,型号是Lemote-A1310,我也买了块。
美中不足的是由于芯片一个硬件设计Bug,默认由Bootloader屏蔽了2个核心,也就是说只有6个核心开放给用户使用。
经过一些调查,我认为通过软件绕过硬件的限制,将那两个核心开放是可行的,也因为群里呼声巨大,所以我开始着手于解放这些核。
在接受指点之后,我意识到这个Bug和I/O DMA一致性有关。
从胡伟武老师的文章《我们的龙芯三号》中,我找到了关于这个Bug的详细描述:
这个问题是从3B1000到3B1500改版过程中引进的,为了提高性能,处理器核收到多个维护Cache一致性的无效请求时,原来每两拍才能处理一个,改成可以连续处理,导致清除LL/SC同步指令的同步位llbit时错了一拍,误把IO DMA引起的Cache无效请求当作0号处理器核的Cache无效请求(IO DMA的编号刚好为0,与0号处理器核区分不开),通过软件调整可以规避此问题。经过批量测试,原不稳定现象消失。
龙芯架构下,DMA的一致性是可配置的,也就是说可以从Cached地址空间进行DMA操作,硬件维护DMA一致性;也可以不由硬件控制一致性而从Uncached地址空间操作。
出现问题的环节是I/O DMA的一致性维持,所以我们可以采取不让硬件维持I/O DMA,走Uncached DMA来在牺牲一些I/O DMA性能的前提下绕过这个Bug。
Lemote在这些主板出货的时候选择牺牲两个Node的0号核的方法来保证I/O DMA性能顺便降低一些功耗(没错,3B1500是个NUMA架构的处理器,全片八个核心分成两个Node,每四个核心组成一个SMP Group)。
另外,即使不解决DMA的问题,虽然稳定性不高,但是对于日常折腾也已足矣。更何况我们部分人拿到的3B1500G已经解决了这个Bug,但是还是被关核,实在是不甘心。
SMP/NUMA启动流程要找到开核方案,明白除了Bootloader初始运行的那颗核心之外剩余的核心是如何被Bootloader初始化,控制权如何被移交给内核,是个很重要的过程。
根据我对龙芯多核架构的理解,多核启动的主要流程是,每个核心被复位的时候,其PC共同指向NMI Exception的入口,也就是0xbfc00000,Kseg1被map到Boot SPI Flash的头部的位置。之后由Bootloader初始化所有核心的私有缓存,TLB等组件。
然后软件通过读取CP0 Ebase的0-10bit CPUNum位判断自己的核编号,如果自己是Bootloader将要下一步使用的Bootcore,就继续运行Bootloader核心代码,如果自己是从核,那么就跳转到一段循环代码,等待Bootcore的控制权移交给内核之后,内核通过向各个核心的MailBox寄存器发送这个核心将要跑的代码的PC地址来唤醒这个核心。
内核侧的实现我大致读了读,觉得和开核关系不大,就不在这里过多的做阐述了。
让我们来看看PMON下具体的代码实现,这些代码均摘录自龙芯开源的PMON Target bonito.3c780e的start.S。
.set mips64 mfc0 t0 $15 1 /* 取CP0 Ebase */ .set mips3 andi t0 t0 0x3ff /* 取出t0中的CPUNum */ dli a0 0x9800000000000000 /*取NUMA Base*/ andi t1 t0 0x3 dsll t2 t1 18 or a0 t2 a0 /* 256KB offset for the each core */ andi t2 t0 0xc /* node id */ dsll t2 42 or a0 t2 a0 /* get the L2 cache address */ dsll t1 t1 8 or t1 t2 t1 dli t2 NODE0_CORE0_BUF0 or t1 t2 t1 /* 上面主要在判断自己是哪个NUMA Node */ dli a0 BOOTCORE_ID /* 把BOOTCORE ID加载进a0 */ bne t0 a0 slave_main /* 如果t0(当前核的CPUNum) != a0(BOOTCORE) 那么跳转到slave_main */ nop bal initserial /* 在BOOTCORE上初始化串口 */ nop
slave_main的代码就不贴了,也是做一些初始化的活,最后是一个等待内核发mailbox的loop。
修改内核此路不通PMON的Start.S读的我醉生梦死,大致从头看到尾,并没有什么去关闭那两个核的代码,按照我的理解,这两个核在启动之后也在运行slave_main的代码,只不过因为通过“龙芯EFI标准”传过去的Reserved Core Mask中将那俩核Mask掉了,所以内核没有去初始化他们,那么直接在内核层面覆盖掉传参就好了嘛。事实证明我还是太Naive,改完内核一跑,屏幕上还是6只小企鹅,串口上的输出也是那么直白,CPU0 Failed to Boot CPU4 Failed to Boot。
不甘心的我手动在内核的Head.S里加了一段汇编喂给CPU0一个Mailbox PC,指望能把它带回来,然而它根本不鸟我一下。
看来这两颗核心是真的被关掉了。
此路不通矣。
小插曲联系到一位Lemote的员工愿意帮忙编译一份开核版本的昆仑固件,但是失败了。。。。。不过还是非常感谢他。。。
Lemote方面也表示不是很方便开放他们的PMON私有代码。
看来还是要自己动手丰衣足食。
逆向工程一脸懵逼说起逆向工程,那必是会想到Hex-Rays的IDA工具。IDA有对MIPS架构提供支持,那是再好不过了(其实我本来想用开源的Radare2,但是当时手边只有一台Windows环境的电脑,于是心安理得的开始使用盗版软件)。
手上的PMON是一个bin文件,不要说debug symbol和lable了,就连个Entrypoint都没有。。好在Bootloader总是从头开始跑,也没什么库之类的东西。
省略一堆设置IDA的过程。
加载后:
截图是后来补上的,刚载入进去肯定是没有注释,lable什么的
扑面而来一堆汇编,我一脸懵逼,好吧,挑灯苦读。窗外满天星斗,一叹星摇摇,长夜殊未央。
而且,不得不说反汇编得到的代码和实际Start.S汇编差距很大,一是因为assembler进行了预处理,二是因为Lemote PMON的代码基似乎和龙芯开源的版本也有一些差距。
总之,对照着开源版本的Start.S又是一番痛苦的分析。
同时发现了一个定位PMON代码用途的小技巧,PMON源代码里的PIRINTSTR宏实际上是用stringserial函数配上往.data section写入要打印字符实现的,也就说,只要找到stringserial的地址,就能找到在哪里call的这个函数,以及传给函数的.data section 地址/offset,进而得到PRINTSTR出来的内容。所以我得以标注出start.S中各个函数的入口点lable。
然而,重点检查了判断CPUNum的代码和slave_main里执行的代码,并没有发现更多和核心判断相关的内容。
郁闷至极,望向窗外,此时天已拂晓,第二天还有很多事情要干,不得不郁郁而眠。
又到了第二天晚上,继续去钻进这些代码里,再次检查代码,还是没有线索。郁闷至极,随手翻开3B1500的用户手册寻找灵感。
一页页翻过去,突然看到 “2.6 芯片配置、采样及 PLL 相关寄存器”,猛地一想,是不是单个核的时钟被关闭了。。。仔细读了读这一张,果然,后面有提到 “芯片处理器核软件分频设置寄存器” ,这些寄存器可以控制单个核时钟的使能与否。
他的物理地址是0x1fe001d0,换算到Start.S运行的kseg1里就是0xbfe001d0。
全局搜索了一下这个地址,果然,在SHUT_SLAVE和WAKE_CORES两个阶段都被写了一下,对照手册看了看写入的值,果然。CPU0和CPU4的时钟都被直接关掉了。
之前之所以忽视了这里,是因为龙芯开源的PMON考虑到了6核版本和8和版本共用同一固件的兼容性,并没有关掉这两个核的时钟,所以在逆向的时候,找到了这段代码的用途,就误以为作用和开源版本的一致,疏忽了。
然后梳理了一下需要修改的地方,主要是把Bootcore改成0,在SHUT_SLAVE的时候把一号核关掉,0号核打开,WAKE_CORES的时候
启用所有八个核,就好了,PMON的代码几乎不动,只是改动几个写入寄存器的值。
好吧,刚想改就给我刺激的,好在对MIPS的指令编码我也还算有些了解,直接做HexEdit级别的Patch也没什么问题。
改完已经是凌晨一点了,又XJB一改内核,编程器烧写了一波,刺激的点亮时间到。
八只小企鹅!瞬间泪流满面
回头看看对PMON做的修改,也就那么7个byte而已,但是其中的艰辛,也算是只有自己明白。
Kernel于是,这个两个核被解放了出来,接下来就是要让内核初始化并且使用上这两个核,还要让内核在适当的情况下决定自己的DMA一致性。之前我粗暴无礼的硬编码了bootcpu编号和reserved core mask,这并不是一个优雅的实现,也会让内核失去通用性。