一、选择合适的官方原版 uboot
1、官方原版 uboot 的版本
(1) 版本号。刚开始是 1.3.4 样式,后来变成 2009.08 样式。
(2) 新版和旧版的差别。uboot 的架构很早就定下来了,然后里面普遍公用的东西(common 目录下、drivers 目录下、fs 目录下等···)在各个版本之间几乎是完全一样的。
差别最大的是 board 和 cpu 目录,这两个目录正是单板(开发板)相关的。越新的 uboot 版本支持越多的开发板(CPU),所以越新的 uboot 越庞大。
(3) 并不是越新的版本就越好。越新的 uboot 中,会多出更多的开发板的支持代码,如果我们的开发板并不是很新,就没必要去用很新版本的 uboot。因为多出来的代码自己也用不到,而且还会成为累赘。
2、官方原版 uboot 的来源
(1) 从 uboot 官方网站 ftp 下载。
(2) 从一些镜像网站下载。
3、新版 uboot 配置体系的改变
(1) 在最新的 uboot 版本(准确的说是 2013.10 到 2014.10 中的某个版本)中,uboot 的文件体系发生了一个很大的变化。这个变化就是,uboot 引入了 linux kernel 的配置体系(Kbuild、Kconfig、menuconfig),从而让我们可以在图形界面下,像配置内核一样配置 uboot。
(2) 所以新版本的 uboot 配置时,和我们之前的课程讲的就不同了。我们移植时不能选择这种配置方式更改之后的 uboot 版本。我们要选择更改之前的。
(3) 新版本的配置方式,本质上和 linux kernel 一样的,所以在学完 linux kernel 移植后自己就能看懂,因此不用担心。
4、结论:选择合适的官方原版 uboot 进行移植
(1) 结合以上,选择 2013.10 版本进行实验移植是比较合理的。
5、注意:实践工作中,一般是从SoC厂商的 uboot 出发移植的
(1) 在工作中一般是不需要从 uboot 官方版本出发去做移植的,而是从 SoC 厂商提供的开发板配套的 uboot 去做移植的。
二、先初步浏览官方原版 uboot
1、文件夹结构浏览
(1) 文件夹结构分析、主要文件检视
总的来说,文件夹结构和以前基本一样。不同的主要是 lib,以前是 lib_arm 和 lib_generic,现在是 arch 和 lib。arch 目录下放的是和 cpu 架构有关的东西。
总的来说,2013.10 版本的 uboot 在结构上和 1.3.4 版本的 uboot 还是有所不同的。
(2) 参照物开发板的选择
我们开发板使用的 CPU 是 S5PV210,所以要找 uboot 中针对 S5PV210 或者 S5PC110 进行移植的作为参考。
根据规律,我们应该参考 include/configs/s5p_goni.h,对应的 board 在 uboot/board/samsung/goni 这个目录。
(3) 删除无关文件和文件夹
其实不删除也可以,但是删除更好。
arch 目录:
board 目录:
2、建立工程
3、主 Makefile 浏览及 boards.cfg 文件
(1) 2013.10 版本的 uboot 的 Makefile 中使用了 boards.cfg 文件,因此在配置 uboot 时 make xxx_config,这个 xxx 要到 boards.cfg 文件中查找。
(2) 其实就相当于把以前的版本的 uboot 中,各种开发板的配置部分规则抽离出来,写到了 Makefile 中,然后把配置信息部分写到了一个独立文件 boards.cfg。
4、mkconfig 脚本浏览及符号连接的分析
(1) 下节课详细分析,给出结论。
5、结论:
(1) 参照物开发板为:55p_goni。
(2) 配置对应的 cpu、board 文件夹分别为:
cpu: u-boot-2013.10\arch\arm\cpu\armv7
board: u-boot-2013.10\board\samsung\goni
三、mkconfig 脚本分析
1、脚本功能浏览
(1) 首先我们在命令行配置 uboot 时,是:make s5p_goni_config,对应 Makefile 中的一个目标。
(2) 新版本的 Makefile 中:
从这里分析得出结论,实际配置时,是调用 mkconfig 脚本,然后传参 2 个:-A 和 s5p_goni。
(3) 到了 mkconfig 脚本中了。在 24 到 35 行中,使用 awk 正则表达式将 boards.cfg 中与刚才 $1(s5p_goni)能够匹配上的那一行截取出来,赋值给变量 line,然后将 line 的内容以空格为间隔依次分开,分别赋值给 $1、$2···$8。
根据上面的 awk 脚本和 board.cfg,其实是将传进来的 $2 参数:s5p_goni,和 board.cfg 的第 7 列:s5p_goni,做匹配。
所以我们要配置,就是用命令:make s5p_goni_config,才能配置成功。
(4) 注意在解析完 boards.cfg 之后,$1 到 $8 就有了新的值。
Active arm armv7 s5pc1xx samsung goni s5p_goni - Minkyu Kang <mk7.kang@samsung.com>
$1 = Active
$2 = arm
$3 = armv7
$4 = s5pc1xx
$5 = samsung
$6 = goni
$7 = s5p_goni
$8 = -
2、几个传参和其含义
(1) 处理了 board.cfg 文件后,mkconfig 脚本中将参数赋值给变量。
几个很重要的变量:
arch=arm
cpu=armv7
vendor=samsung
soc=s5pc1xx
3、创建符号链接
(1) include/asm -> arch/arm/include/asm
(2) include/asm/arch -> include/asm/arch-s5pc1xx
(3) include/asm/proc -> include/asm/proc-armv
最后创建了 include/config.h 文件。
4、Makefile 中添加交叉编译工具链
(1) 官方原版的 uboot 中,CROSS_COMPLIE 是没有定义的,需要自己去定义。如果没定义就直接去编译,就会用 gcc 编译。
(2) 添加一行:
# 添加我们本地的交叉编译环境
ifeq ($(ARCH),arm)
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
endif
5、配置编译测试
(1) 编译过程:
make distclean
make s5p_goni_config
make
(2) 结果:得到 u-boot.bin 即可。
四、先解决官方版本uboot的烧录运行
1、如何烧录 uboot
(1) 烧录 u-boot.bin 到 SD 卡中有 2 种方法:
windows 下用烧录软件;
linux 下用 dd 命令烧录脚本来烧录;
因为 windows 下的工具不开源,出了问题没法调试,所以不推荐。推荐 linux 下用烧录脚本来烧录(实质是 dd 命令进行 sd 卡扇区写入)
(2) 移植原来的版本(u-boot-samsung-dev)的 uboot 中的 sd_fusing 文件夹,到官方 uboot 版本中,使用这个文件夹中的 sd_fusing.sh 脚本来进行烧录。
2、分析:为什么烧录运行不正确?
(1) 串口接串口 2,串口有输出。但是这个串口输出不是 uboot 输出的,而是内部 iROM 中的 BL0 运行时输出的。
(2) 输出错误信息分析:
第一个 SD checksum Error:是第一顺序启动设备(SD0(iNand))启动时,校验和 checksum 失败打印出来的;
第二个 SD checksum Error:是第二顺序启动设备(SD2(外部SD卡))启动时,校验和 checksum 失败打印出来的;
剩下的是串口启动和 usb 启动的东西,可以不管。
总结:从两个 SD checksum Error,可以看出:外部 SD 卡校验和失败了。
分析:SD 卡烧录出错了,导致 SD 卡校验和 checksum 过程失败。
3、解决方案分析
(1) 为什么 SD 卡烧录会出错?可能原因:烧录方法错误、烧录原材料错误。
(2) 经过分析,sd_fusing 这个文件夹下的 mkbl1 这个程序肯定没错,uboot 顶层目录下的 u-boot.bin 是存在的,校验和 checksum 失败不失败,和 u-boot.bin 无关。
(3) 经过分析和查找,发现是 mkbl1 程序和 start.S 中,前 16 个字节校验和的处理上面不匹配造成的,解决方法是在 start.S 最前面加上 16 个字节的占位。
修改
4、代码实践
(1) 重新编译烧录运行,发现结果只显示一个 SD checksum Error。这一个就是内部 SD0 通道的 inand 启动校验和 checksum 失败打印出来的。
剩下的没有了,说明外部 SD 卡校验和 checksum 成功了,只是 SD 卡上的 uboot 是错误的,没有串口输出内容,所以没有输出了。
五、start.S 文件分析与移植1
1、start.S 流程分析
(1)
#define CONFIG_SYS_TEXT_BASE 0x34800000
可以看出,我们的 uboot 的连接地址是在 0x34800000 位置。
(2) save_boot_params 是个空函数,里面直接返回的。
(3) cpu_init_cp15 这个函数功能是,设置 MMU、cache 等。这个版本的 uboot 中未使用虚拟地址,因此 MMU 在这里直接关掉。
(4) cpu_init_crit,这个函数里只有一句跳转指令,短跳转到 lowlevel_init 函数。
注意:uboot 中有 2 个 lowlevel_init.S 文件(文件中还都有 lowlevel_init 函数),凭一般分析,无法断定 2 个中哪个才是我们想要的。通过分析两个文件所在文件夹下面的 Makefile 可以判定,board/samsung/goni 目录下的才是真正包含进来的,arch/arm/cpu/armv7 目录下的并没有被包含进来。
还可以通过实践验证的方法来辅助判断。通过查看之前已经编译过的 uboot 源码目录,看哪个被编程为 .o文件了,就知道哪个是真正被使用的了。
board/samsung/goni/lowlevel_init.S:
(5) lowlevel_init 函数在 board/samsung/goni 目录下,主要作用是时钟设置、串口设置、复位状态判断···这个函数是 S5PC100 和 S5PC110 两个 CPU 共用的。
(6) 经过浏览,发现 lowlevel_init 函数中做的有意义的事情有:关看门狗、调用uart_asm_init 来初始化串口、并没有做时钟初始化(下面有时钟初始化的函数,但是实际没调用。如果 uboot 中没有初始化时钟,那么时钟就是 iROM 中初始化的那种配置)。
2、添加开发板置锁和串口打印字符 “O”
(1) 我们为了调试 uboot 的第一阶段,就要看到现象。为了看到现象,我们向lowlevel_init 函数中添加 2 个代码,一个是开发板置锁,一个是串口打印 “O”。
(2) 这两段代码可以直接从 ARM 裸机全集课程中的代码中来。其实也可以从三星移植版本的 uboot 中来,但是因为三星移植版本中用到了很多寄存器定义,涉及到头文件的,所以移植过来不方便。
(3) 实践添加。
3、实践结果及分析
(1) 实验结果是:没看到开发板制锁,串口也没有输出任何东西。实验失败。
(2) 结论:因为开发板置锁没有成功,所以我们判定,在开发板置锁代码运行之前,uboot 就已经挂掉了。下面就是去跟踪代码运行,然后判定问题点再去解决。
六、start.S 文件分析与移植2
1、添加 LED 点亮代码跟踪程序运行
(1) 在基础代码阶段,串口还没有运行,串口调试工具还无法使用时,使用 LED 点亮的方式来调试程序就是一个有力的手段。
(2) 有些情况下可以用 Jlink 等调试工具来调试这种基础代码。
(3) 从程序的基本运行路径端出发,隔一段给他添加一个 LED 点亮代码,然后运行时根据现象来观察,判定哪里执行了哪里没执行。从而去定位问题。
(4)从以前的裸机代码中组织出一个标准的 LED 点亮然后延时一段的一个标准代码段:
//第一步: 把所有引脚设置成输出模式ldr r0, =0x11111111 //从后面的 = 可以看出用的是 ldr 伪指令,因为需要编译器来判断这个数ldr r1, =0xE0200240 //是合法立即数还是非法立即数,一般写代码都用 ldr 伪指令str r0, [r1] // 寄存器间接寻址。功能是把 r0 中的数写入到 r1 中的数为地址的内存中去//第二步:全部点亮ldr r0, =((0 << 3) | (0 << 4) | (0 << 5))ldr r1, =0xE0200244str r0, [r1] //把 0 写入到 GPJ0DAT 寄存器中,引脚即输出低电平,LED 点亮//延时函数
delay:ldr r2, =10000000ldr r3, =0x0
delay_loop:sub r2, r2, #1 // r2 = r2 -1cmp r2, r3 // cmp 会影响 z 标志位,如果 r2 等于 r3 则 z =1,下一句中 eq 就会成立bne delay_loop
(5) 之前做实验时发现一个现象:我们的 uboot 运行时,按住电源开关时所有 4 颗 LED 都是亮的。所以我们做实验时,给 LED 点亮是看不到现象的,所以我们的代码关键是要熄灭某些 LED 来判断。
(6) 我们将熄灭 LED 的函数在 start.S 中隔一段的关键部位放上 1 个,然后运行时通过观察 LED 的点亮熄灭状态,就知道程序运行到哪里了。
/*************** startup.S **********************/
reset:bl save_boot_params/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*/mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP modebicne r0, r0, #0x1f @ clear all mode bitsorrne r0, r0, #0x13 @ set SVC modeorr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0//第一步: 把所有引脚设置成输出模式ldr r0, =0x11111111 //从后面的 = 可以看出用的是 ldr 伪指令,因为需要编译器来判断这个数ldr r1, =0xE0200240 //是合法立即数还是非法立即数,一般写代码都用 ldr 伪指令str r0, [r1] // 寄存器间接寻址。功能是把 r0 中的数写入到 r1 中的数为地址的内存中去//第二步:ldr r0, =((1 << 3) | (0 << 4) | (0 << 5)) //熄灭左边的 LEDldr r1, =0xE0200244str r0, [r1] //把 0 写入到 GPJ0DAT 寄存器中,引脚即输出低电平,LED 点亮//延时函数
delay1:ldr r2, =10000000ldr r3, =0x0
delay_loop1:sub r2, r2, #1 // r2 = r2 -1cmp r2, r3 // cmp 会影响 z 标志位,如果 r2 等于 r3 则 z =1,下一句中 eq 就会成立bne delay_loop1/** Setup vector:* (OMAP4 spl TEXT_BASE is not 32 byte aligned.* Continue to use ROM code vector only in OMAP4 spl)*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Registerbic r0, #CR_V @ V = 0mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register/* Set vector address in CP15 VBAR register */ldr r0, =_startmcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_cp15//第一步: 把所有引脚设置成输出模式ldr r0, =0x11111111 //从后面的 = 可以看出用的是 ldr 伪指令,因为需要编译器来判断这个数ldr r1, =0xE0200240 //是合法立即数还是非法立即数,一般写代码都用 ldr 伪指令str r0, [r1] // 寄存器间接寻址。功能是把 r0 中的数写入到 r1 中的数为地址的内存中去//第二步:ldr r0, =((0 << 3) | (1 << 4) | (0 << 5)) //熄灭中间的 LEDldr r1, =0xE0200244str r0, [r1] //把 0 写入到 GPJ0DAT 寄存器中,引脚即输出低电平,LED 点亮//延时函数
delay2:ldr r2, =10000000ldr r3, =0x0
delay_loop2:sub r2, r2, #1 // r2 = r2 -1cmp r2, r3 // cmp 会影响 z 标志位,如果 r2 等于 r3 则 z =1,下一句中 eq 就会成立bne delay_loop2bl cpu_init_crit//第一步: 把所有引脚设置成输出模式ldr r0, =0x11111111 //从后面的 = 可以看出用的是 ldr 伪指令,因为需要编译器来判断这个数ldr r1, =0xE0200240 //是合法立即数还是非法立即数,一般写代码都用 ldr 伪指令str r0, [r1] // 寄存器间接寻址。功能是把 r0 中的数写入到 r1 中的数为地址的内存中去//第二步:ldr r0, =((0 << 3) | (0 << 4) | (1 << 5)) //熄灭右边的 LEDldr r1, =0xE0200244str r0, [r1] //把 0 写入到 GPJ0DAT 寄存器中,引脚即输出低电平,LED 点亮//延时函数
delay3:ldr r2, =10000000ldr r3, =0x0
delay_loop3:sub r2, r2, #1 // r2 = r2 -1cmp r2, r3 // cmp 会影响 z 标志位,如果 r2 等于 r3 则 z =1,下一句中 eq 就会成立bne delay_loop3
#endifbl _main
(7) 经过判断我们发现:start.S 中工作一切正常,但是函数一旦放到 lowlevel_init.S 中就完全不工作了。通过分析得出结论:b lowlevel_init 这句代码出了问题。
2、修改 u-boot.lds 将 lowlevel_init.S 放到前部
(1) 问题分析:跳转代码出了问题。分析问题出在代码的链接上。
(2) 三星 S5PV210 要求 BL1 大小为 8KB,因此 uboot 第一阶段代码,必须在整个 uboot 镜像的前 8KB 内,否则跳转不到。
(3) 对比三星移植版本的 uboot 的 u-boot.lds 和官方版本 uboot 的链接脚本 u-boot.lds(注意这两个版本的uboot的链接脚本的位置是不同的),就发现 lowlevel_init.S 的代码段没有被放在前面。
(4) 在 u-boot.lds 中 start.o 后面添加 board/samsung/goni/lowlevel_init.o (.text*),这个就保证了 lowlevel_init 函数被连接到前面 8kb 中去。
(5) 报错,lowlevel_init 重复定义了。
3、修改 board/samsung/goni/Makefile 解决编译问题
(1) 问题分析:为什么会重复定义。因为 lowlevel_init 这个函数被链接时连接了 2 次。
一次是 board/samsung/goni 这个目录下生成 libgoni.o 时链接了 1 次,第 2 次是链接脚本最终在连接生成 u-boot 时又链接了一次,所以重复定义了。
(2) 这个错误如何解决?思路是在 libgoni.o 中,不要让他链接进 lowlevel_init,让他只在最终链接 u-boo t时用 1 次,就可以避免重复定义。
(3) 参考当前版本的 uboot 的 start.S 文件的处理技巧,解决了这个问题。
4、实践验证。
结果是开发板置锁和串口输出 ‘O’ 都成功了。
源自朱有鹏老师.