至于选择uboot-2013.10这个官方版本,是为了避开uboot新的配置方式(make menuconfig,应该是模仿内核配置编译过程),这个图形化的配置方式,后续如果有机会的话再去研究,目前就简单认为跟内核的配置一样的吧。
移植前准备
拿到uboot代码之后,最先采取的几项工作:
1、寻找一个契合自己开发板的board和cpu,若没有board但至少要有同款的cpu。我这边就直接在代码的include/configs(这是所有开发板的配置头文件所在)中grep查找grep -nR 's5pc110' ./include/configs/
,找到了一个cpu一致的开发板s5p_goni;
2、删除一些不必要的文件,仅留该开发板相关的,例如arch、cpu、board、vendor、soc这些可以去board.cfg(从之前的Makefile中剥离出来的,本来make xxx_config的目标和命令传参是明确的,现在都需要去board.cfg文件中匹配)中找;
3、烧录程序运行。
可是配置、编译、烧录后却发现串口如下打印:
翻看了数据手册,s5pv210的启动流程如下,也可以参考这篇博客S5PV210–1—210启动方式和代码前16字节
最终发现,第二启动顺位在校验BL1的时候校验和失败了。确定制作BL1的程序和烧录脚本没问题之后,最终发现uboot代码前没有预留16字节空白来存放校验和(这是SOC设计的时候特殊规定的,因此通用的uboot中没有这段逻辑)。因此加上后,解决该问题。
BL1代码移植
start.S以及lowlevel_init
第二个“SD checksum Error”没有再打出来了,说明代码已经进入BL1运行了。但是没有任何其他打印了,因此我们希望加一些打印来定位。
参照之前移植的uboot,在lowlevel_init中增加供电锁存代码以及在串口初始化之后打印‘O’字符,在lowlevel_init结束前打印‘K’。结果较之前没有任何反应。
因此我们将供电锁存的代码(因为这段代码直接操作寄存器,执行必定有现象。当然,用点亮LED代码调试也可以)往前移动进行分段调试,最后发现代码运行到了b lowlevel_init
前,而lowlevel_init函数第一句就没有执行了,因此可以说明跳转到lowlevel_init并没有成功。
b lowlevel_init
跳转代码除了问题,只可能是,怀疑是代码的链接上,也就是说这个函数(这个文件)并没有在BL1中。查看链接脚本也可以验证,链接脚本的确没有将lowlevel.S的代码段放到前面。
重新启动后“OK”打印出来了,说明lowlevel_init执行完毕了。
接着往下走,调用逻辑是_main =》 board_init_f =》 relocate_code =》board_init_r。可以看出,在这个版本的uboot中,把以前uboot的第二阶段start_armboot函数分成了2部分:board_init_f和board_init_r。所以在这里就和以前版本的uboot接轨上了,推测board_init_f中肯定是做了板级初始化,board_init_r中进入了uboot的命令行(这里使用了ldr pc, =board_init_r
远跳转到了DDR中)。
分析到这里,下一步工作方向就确定了。我们要先在cpu_init_crit函数(其实就是lowlevel_init)中添加DDR初始化,然后在start.S中bl _main之前添加uboot的重定位,然后将bl _main改成ldr pc, __main(__main: .word _main)长跳转。然后在crt0.S中board_init_f后删除那些重定位代码,至此uboot的第二阶段就应该能启动起来了。后续的移植就是第二阶段了。
DDR初始化
三星版本的uboot中DDR初始化函数在cpu/s5pc11x/s5pc110/cpu_init.S文件中,直接将这个文件移植过来即可。添加cpu_init.S文件到uboot2013.10中。注意,这里的代码必须保证在前8kb内,主要是在board/samsung/goni/Makefile中和arch/arm/cpu/u-boot.lds文件中做修改添加。cpu_init.S中包含了<config.h>(编译时自动生成的,这个有)和<s5pc110.h>(这个没有)这两个头文件,因此添加头文件s5pc110.h到include目录下。
接下来对cpu_init.S和s5pc110.h这两个文件进行修整:
1、DDR配置参数,从三星版本的smdkv210single.h中复制到s5p_goni.h中。
2、s5pc110.h中进行修整,删除无用的宏等代码。
重定位代码移植
重定位部分代码应该在DDR初始化之后和uboot第二阶段来临前之间。将三星的uboot中的重定位代码复制过来修改,以及清bss段移植,然后是movi_bl2_copy函数移植。
记录一下几个问题:
1、链接错误:u-boot contains relocations other than R_ARM_RELATIVE
在uboot下用grep “R_ARM_RELATIVE” -nR *搜索,发现Makefile中有一个检查重定位的规则,屏蔽掉这个规则后编译连接成功。
2、lowlevel_init函数中返回前存在一段死循环代码(应该是在等待一个状态),提前返回以屏蔽这段代码。
解决重定位后,串口输出如下。
这说明uboot中的DDR初始化和重定位功能都已经完美实现,后面就是第二阶段的继续移植了。
BL2代码移植
时钟初始化
从打印中看到CPU主频ARMCLK是400MHz,而uboot2013.10中到现在还未有代码对时钟进行初始化,那么这400MHz应该是iROM内部代码初始化的。
之前一直认为iROM中把210的时钟设置为了1000MHz,然后三星版本的uboot中设置的时钟也是按照这个数据手册356页推荐的这个最佳性能配置时钟设置的。所以以前认为uboot中可以没有时钟设置也是一样的。
但是实际上不是这样的,实际上内部iROM中设置的时钟APLL输出是800MHz,ARMCLK是400MHz。如果uboot中不做时钟的设置实际得到的就是这个时钟。所以我们之前代码得到的结果是400MHz。
在lowlevel_init.S中本来就有一段时钟初始化的代码system_clock_init,只是没有调用而已(调用位置放在uart和ddr初始化之前)。
解决办法:
1)直接调用,若有问题再修改;
2)将三星版本uboot中的这段代码拿过来,并调用;
首选肯定是方案1),直接调用后发现ARMCLK还是是400MHz,APLL=800MHz,主要是因为APLL不对,那只要对照数据手册修改一下APLL_CON0的值即可。
修改好之后主频的确改回1000MHz了,其他PLL的值也没问题,流程也走下来了。但是发现了一个奇怪的现象:SD checksum Error下面的OK字样不见了或乱码了,也就是串口初始化成功后的‘O’以及lowlevel_init执行返回前的‘K’。
为了查这个问题,我特地记录一下。
排查步骤:
1、因为之前三星版本的uboot在时钟初始化后还是能看到OK的,因此采取上述方案2),看一下移植上去后是否能看到OK——有OK打印,说明三星版本uboot的初始化时钟代码没问题,进一步说明uboot2013.10初始化时钟的代码存在问题;
2、因为时钟初始化代码紧挨着串口初始化以及打印‘O’的代码,怀疑硬件初始化时钟需要一个等待周期,因此增加一个延迟试试——从几百毫秒到几秒的延迟,都还有解决问题,那就应该不是硬件的问题;
3、既然是串口问题,又修改了时钟,怀疑是串口时钟设置值不对了,于是先后打印出了两个版本uboot的uart_clk的值,发现都是66MHZ。
排查到这,已经没有太多思路了。
直到我在BL1跳转BL2的那句代码(ldr pc, __main)前加了打印字符,发现还是没有打印出来后,突然发现:其实BL1阶段的uart是有问题的,而BL2的uart又是好的,不然后面那么一大串是怎么打出来的。而且这也能解释上述步骤3,其实可BL2得init_sequence中是有重新对串口的初始化的。那么,问题就集中在uboot2013.10这段初始化时钟的代码上了。
进过逐一排查,最终确定在了这两行代码上:
r0偏移0x310为时钟寄存器基地址0xE0100000+0x310=0xE0100310,查询数据手册可以知道该寄存器为CLK_DIV4,看上去是存放UART0~3 和 MMC0~3时钟的分频器系数的。
注释掉这两句代码即可看到OK打印了,问题解决。但是,翻遍了数据手册,没有找到DIVUARTn和SCLK_UARTn的用处,以及MOUTUARTn的值。百度也没有相关的问题和答案。
目前猜测的原因,这个UARTn_RATIO均设置成了1(本来不设置这个寄存器时默认都是0),从而分频系数为2,得出的SCLK_UARTn过小导致串口异常了。只是猜测,目前还不知道真正原因。但是这个问题其实没有多大意义,因为在BL1阶段即使不初始化时钟,iROM初始化好的时钟频率下,uart和ddr本来就能正常工作了,因此BL1中没必要在做时钟初始化。跟何况,BL1马上就结束了,转入BL2阶段后还会重新初始化串口。
有没有必要在BL1初始化时钟?
1)不做时钟初始化是可以启动内核的;
2)至于启动内核后,内核应该不会再初始化时钟了吧?如果是这样,那cpu的确没有工作在时钟的最佳状态下,那么这个时钟初始化是有必要的。
board和DDR配置显示移植
1)board名称更改
2)DDR配置值修改
3)MACH_TYPE定义
4)DDR打印信息更改
代码已经从 board_init_f 进入 board_init_r 中了,现在程序还卡在OneNAND这块代码中。
去掉onenand相关的宏,修改成mmc相关的宏(env相关存储位置),以及去掉gnoi目录下Malefile默认添加onenand.o的条件后即可编译通过。
最后程序已经可以运行到命令行了。
SD卡驱动移植
把原来三星移植版本的uboot中的SD/MMC驱动整个移植过来替换掉uboot2013.10中的MMC驱动。
环境变量的移植
目前情况是uboot在SD2中,而ENV在SD0中,所以现在ENV不管放在哪个扇区都能工作,不会有问题。但是到最后产品出厂的时候,uboot也是要烧录到SD0中的,因此还是要确保ENV分区不影响其他分区。
一般来说,SD0中(iNand)分区排布如下:
因此修改代码,将ENV首地址放到第17扇区开始的地方。
#define CONFIG_ENV_OFFSET (17 * 512) // 17个扇区开始的位置
网卡驱动的移植
1、添加网络支持
2、添加ping和tftp命令
3、添加eth_devices类型的网络设备对象(注册),dm9000x.c中提供了注册接口int dm9000_initialize(bd_t *bis)
,调用即可。
做完这些之后,ping的时候还有问题,如图。
解决方法参考:ERROR: resetting DM9000 -> not responding dm9000 not found at 0x88000000问题解决
其实这个主要问题是,时钟初始化有问题,屏蔽了SROM的时钟。
修改代码后即可ping通。
启动内核的移植
这里我将之前uboot中直接插入zImage 启动的流程。
最终启动内核时还是失败了,错误打印如图所示:
修改一些错误之后发现还是起不来,打印出各种参数的值,感觉也没有什么错误。
u-boot启动报错:Wrong Ramdisk Image Format
看了这篇文章,不知道是不是跟SD卡的分区有关?
先放下了,以后有思路再来解决。