最开始拿到龙芯3A4000 PMON Bootloader代码的时候,我是震惊的,一眼看过去又全部是汇编,从DDR训练到HT和7A初始化,属实带工程。真的很佩服龙芯那些工程师,是我用汇编写这么大个工程早就写出114514个Bug了。不过里面有段汇编出乎我意料,名叫ls132_core.S,结合之前在Lemote看到的资料,推测这就是3A4000内置的GS132管理核的代码。一波通读,说实话看不懂,大致明白了主要实现了DVFS功能,运行时重设PLL和FreqScale寄存器进行调频,并通过I2C指挥PMIC进行调压,这不和AMD SMU在做差不多的事么?之前各种折腾SMU,感叹于其功能之强大,但是看着这段汇编,实在和强大扯不上半点关系,于是就萌生了重构管理核中代码的想法,我称之为Project Miku。这个管理核,我们称之为SMC,目前项目已经初具雏形,可以见GitHub。
首先,顶层设计是必不可少的,参见AMD SMU,其功能包括与主核软件通讯,控制Sensor Hub,控制HWMon(Thermal),控制DVFS,控制片上总线QoS。那么龙芯的SMC可以达成些哪些功能呢?分析自PMON的汇编代码(辣眼睛!),GS132核可以访问片上IORING设施,包括MISC和HT,也就是可以访问confbus下的所有寄存器和部分7A寄存器(GS132 只有32Bit访存能力,个人推测对于64Bit地址可以通过地址窗口的形式重映射访问,根据从龙芯开源计划拿到的GS132 RTL代码,GS132没有实现MMU。)。总而言之GS132有能力控制片上I2C用于调压,UART用于Debug,7A的MISCBUS用于风扇和其他电源管理,以及最重要的时钟控制。而“工欲成其事,必先利其器”,要在小核上舒舒服服的写业务逻辑,就必须有成熟的RTOS框架。左顾右盼之下,我选定了国产实时操作系统RT-Thread,一来之前在1C上玩过,大致熟悉其结构,二来采用了Apache开源协议,不会断绝将来商业应用的可能。
开始我还没拿到3A4000的机器,就只能做一些外围工作了,主要是写写GS132的RT-Thread BSP。哪知道一上手发现RTT的MIPS框架是一团糟,龙芯君正各自有自己的实现,并且差异很小的芯片也Fork出了多份核心libcpu代码,不能忍,遂先进行了一波整理与合并作业,并且向Upstream提交,这样以后可以很方便的加入新的芯片支持,并且为了适应GS132这种环境,特别加入了CP0.Ebase支持,允许在Link Script中任意放置中断向量(当然对齐要求还是有的),也增加了可以在QEMU模拟器中运行的bsp以验证其可靠性。后来也很方便的写出了GS132的BSP。
拿到机器之后,简单测试了一下GS132,各种汇编操作串口打印OK,确定了他确确实实存在在3A4000里,就开始上Port的RT-Thread,哪知道现实给了我当头一棒,根本起不来!PC指向RT-Thread的头部之后串口就彻底偃旗息鼓了。一番推论,我提出了个我自己都不敢相信的可能性,这玩意可能根本没有内存!之前一直觉得和采用GS132的1C101一样,这玩意片内应该有SRAM。如果没有内存,没有栈堆,那就没有用任何操作系统的可能性了。于是写了一堆汇编做实验,发现他应该是有内存的,并且内存应该是直接落到DDR控制器上,和主核共享。那为啥不能用呢?继续写汇编做Relocation实验,最后得出的结论是GS132可以访存DDR部分主存,但是无法从主存取指,好一个奇葩设计,估计是内部CrossBAR上对取指请求做出了某种限制,无奈之下只能采取常在单片机上用的Address Layout,将代码段与只读数据段放在ROM中,bss data stack heap放在RAM中,RAM中的数据放在操作系统为固件保留的地址,并且小心的避开了固件已经使用的部分。
ok,操作系统起来了,下一个要关心的就是和主核软件的通讯了。根据汇编版本代码,0x1fe0051c处有个Mailbox寄存器,大核和小核都能访问,说是Mailbox,其实就是个ScratchPad,存进去的东西两边都能读写而已。大核写完中断GS132让GS132上的软件来读取或者直接由小核轮询,处理完成后再由大核轮询处理结果。看了龙芯之前的设计,基本也是这个思路,就是有几个弊端,一是命令的硬件操作也在命令处理部分完成,导致单条命令的处理时间不确定,可能很长以至于内核轮询超时。二是缺乏可靠的功能探测与版本表达机制,不利于之后扩展。众所周知,在顶层设计欠下的债,以后都要还的。不破不立,因此我抛开了已有设计,参考AMD SMU的体制设计了一套”Service Request”协议进行通讯。引入了Feature Flag,并且确保每个请求都会以最快速度处理返回。
在通讯机制确立之后,作为这种机制的Proof of Conecpt,风扇控制功能和传感器功能都被我很快地完成了,剩下最具挑战性的DVFS/Boost功能。
对于原先的DVFS实现,我是非常不满意的,主要问题是 1、动PLL的同时破坏了Stable Conter时钟,使得理应当全局稳定的时钟不再稳定,操作系统只能使用不精确的HPET时钟。2、一切听从内核,缺乏对过热等情况的保护。3、在内核中使用“双Freq Table制”,与现有CPUFreq框架契合度不高。因此,我提出了第一种设计。始终使PLL置于最高频率,仅通过FreqScale寄存器来调节当前频率并辅以电压调节。然而先期的测试证明我还是Too Young,在高PLL频率的情况下,仅仅通过FreqScale把频率拉回来并不能达成降压的目的,一降压就死机。原来,FreqScale和PLL的原理不一样,他的7/8分频仅仅是每8个边沿中摘掉一个边沿,单个上升沿/下降沿的时间并没有变化。也就是说,除非Scale小于4/8,片内的时序并不会随着Scale的变化而放宽,所以对电压的需求也没有放宽,仅仅是为了达成减小翻转率以降低功耗的目的。此路不通,看来针对睿频重设PLL是必须的。但是,我又希望在睿频的同时Stable Counter的频率可以保持不变。还好,Stable Counter也有自己的Scale寄存器,也就是说,通过巧妙的计算,是有办法让Stable Counter的频率保持一致的。比如,睿频频率是2000MHz,基础频率是1750MHz,那么我们可以在睿频状态下对Stable Counter进行7/8分频,使Stable Counter保持在1750MHz的频率。进一步实验证明了这种方案的可行性。于是到了实现环节,这里遇到了一个问题,小核操作I2C指挥PMIC一致调不通,我也缺乏相应的设备进行逻辑分析,于是先搁置了这部分。直到后来做内核中超频的时候从PMON里参考来了逻辑才调通。
然后写出了基础逻辑,PLL分为两个状态,正常状态和Boost状态,每个状态中再根据内核的需求进行FreqScale分频。然而做出来的原型又遇到了问题,降频会死机。先是解决了一个逻辑顺序问题,降频时应该先降频在调压防止芯片瞬间处于高频低电压态。但是问题还是没有彻底解决,问题出在在通过FreqScale降频的时候竟然会死机?一番分析,发现死机一般出现在Scale小于2的状况。虽然可以简单的过滤掉Scale < 2的状况,但是不知道原因始终是个隐患。又是一波东问西问,得知原因是在核频率过低时,可能不能及时返回SCache的一致性请求导致片内超时死锁。龙芯给出的解决方案是调节芯片配置寄存器,放宽超时时间(但是会影响整体性能)。我思考了一下这个问题,觉得还有个解决方案,就是当芯片整体负载较低时,对Node(包含SCache)也进行分频,大家都慢就不会超时了。于是在算法中加入了Idle挡位,当所有核负载都小于2/1 Normal时,将Node也进行4/8分频,否则不允许单个核的FreqScale低于4/8分频。
针对“双表制”的问题,我参考了AMD的Shadow P-State设计,即暴露给内核的频率表并不是真实频率表,只是一层影子表,小核根据Shadow Frequency选定合适的PLL和FreqScale组合,以更好的适配cpufreq机制,并且减少对PLL的动作,毕竟操作PLL和电压还是比较耗时的。
最后就是一些调优,加入温度保护,调节单个挡位上维持的时间避免频繁降频,加入SRAM寄存器配置以适配低电压情况,进一步降低Idle挡位的电压以进一步减小待机功耗。并且成功通过加压的方法,将Boost挡提高到了2200MHz,获得了可观的性能提升。
现在SMC的各功能在我这里测试表现良好,期望可以早日在设备中实装投入使用: -)。