以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考内容
(1)uboot——官网下载直接移植(一) - biaohc - 博客园
(2)uboot——官网下载直接移植(二) - biaohc - 博客园
目录
4.3 修改u-boot.lds将lowlevel_init.S放到前部
内容总结
移植过程的思维导图如下所示:
特别说明
在实际工作中,一般不需要从uboot官方版本出发去做移植,而是根据SoC厂商提供的与开发板配套的uboot去做移植。
这里之所以从uboot官方版本出发去做移植,仅是为了学习移植的技巧。
一、移植前的准备
1.1 获取官方uboot
uboot官方网址是ftp://ftp.denx.de/pub/u-boot/,我们下载u-boot-2013.10.tar.bz2这个版本。
为什么选择这个版本呢?因为这个版本还没有引入类似于kernel的配置体系(即Kbuild、Kconfig、menuconfig),新一点的版本(2013.10到2014.10之间的某个版本)引入了这个配置体系,从而可以在图形界面下,像配置内核一样配置uboot。所以新一点的uboot版本在配置时,会与之前我们学的内容不同,因此移植时不能选择这种配置方式更改之后的uboot版本,要选择配置方式更改之前的uboot版本,比如u-boot-2013.10.tar.bz2这个版本。
关于新版与旧版的差别。uboot的软件架构很早就确定下来了,里面那些常用的内容(比如common目录、drivers目录与fs目录)在各个版本中几乎一样。差别最大的是board和cpu目录,这两个目录与开发板相关。版本越新的uboot支持越多的开发板,但体积就越大。
uboot的版本并不是越新越好。版本越新的uboot,则包含越多的支持新开发板的代码,编译后的体积就越大。如果我们的开发板并不是很新的,就没必要去用很新版本的uboot。
1.2 分析uboot目录
1.2.1 比较版本的不同点
(1)九鼎用的是1.3.4版本,这里用的是2013.10版本。
(2)九鼎1.3.4版本的uboot的/cpu文件夹,对应着2013.10版本的uboot的/arch文件夹。
(3)九鼎1.3.4版本的uboot,它的主Makefile文件中包含着与board有关的配置信息。而2013.10版本的uboot,它将与board有关的配置信息从主Makefile文件中抽离出来,写在/board.cfg文件中。我们将在1.2.2 小节中通过查找/board.cfg文件中的配置信息,来确定我们参照哪个开发板。
1.2.2 选取参照开发板
(1)参照开发板的cpu要和x210开发板的cpu相同。x210开发板的cpu是S5PV210,因此要在uboot中寻找使用S5PV210或者S5PC110进行移植的例子作为参考。
(2)打开board.cfg文件,搜索s5pc1xx,发现有两个相关的开发板,即goni和smdk100。
(3)我们选择goni开发板,该开发板对应的头文件是/include/configs/s5p_goni.h文件,对应的board文件夹在/board/samsung/goni目录,对应的cpu在/arch/arm/cpu/armv7/s5pc1xx文件。
1.3 删除无关文件并建立SI项目
其实不删除也可以,但是删除无关文件,可以使移植时更方便与更具针对性。
1.3.1 删除无关文件
(1)删除/arch目录中无关文件
- /arch目录下只保留arm文件夹。
- /arch/arm/cpu目录下除了armv7文件夹,其他的文件夹删除(文件要保留)。
- /arch/arm/cpu/armv7目录除了s5pc1xx、s5p_common这两个文件夹,其他的文件夹删除(文件要保留)。
(2)删除/board目录中无关文件
- /board目录下只保留samsung文件夹。
- /board/samsung/目录下只保留goni、common文件夹。
1.3.2 使用sourceinsight创建项目
内容略。
二、修改/Makefile文件
2.1 分析/Makefile文件
以前配置X210开发板的uboot时,直接make x210_sd_config,它对应的主Makefile目标、依赖性、操作如下所示。
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
从中可以看出,这里给出了明确的目标“x210_sd_config”,以及参数“x210_sd、arm、s5pc11x、x210、samsung、s5pc110”。
2013.10版本的uboot的/Makefile中有如下代码:
%_config:: unconfig
@$(MKCONFIG) -A $(@:_config=)
sinclude $(obj).boards.depend
$(obj).boards.depend: boards.cfg //参数信息要在这个文件中寻找
@awk '(NF && $$1 !~ /^#/) { print $$7 ": " $$7 "_config; $$(MAKE)" }' $< > $@
从中可以看出:
(1)对目标使用了通配符%,即“%_config”(Makefile中的%表示通配符,而*表示任意字符。举例:%.o表示匹配所有的.o文件,注意是用于匹配的,*.o是表示所有的.o文件。前者一般用作目标,后者一般用作删除)。
(2)将以前版本的/Makefile中各种开发板的配置操作抽象出来,把具体的配置信息部分写到一个独立的文件/boards.cfg中。这样在make xxx_config时,无论xxx是什么都会执行相同的操作。现版本的uboot的/Makefile只有将近1000行的代码,而以前版本的uboot的/Makefile有几千行,就是因为以前版本的/Makefile对于目标没有使用通配符,而是为每个板子列一个目标以及具体的操作。
(3) 每个板子的具体配置参数信息,要到/boards.cfg文件中查找,该文件内容如下。
(4)关于MKCONFIG这个变量对应的/mkconfig脚本的分析,见附录1。
2.2 明确参照开发板的信息
我们参考goni开发板进行移植,或者说在它的源码基础上进行修改。
其对应的board文件夹:u-boot-2013.10/board/samsung/goni文件夹。
其对应的cpu文件夹:u-boot-2013.10/arch/arm/cpu/armv7文件夹。
其对应的头文件:u-boot-2013.10/include/configs/s5p_goni.h文件。
2.3 添加交叉编译工具链
2013.10版本的uboot的/Makefile中没有定义CROSS_COMPILE,需要自己定义。
如果没有定义而直接编译,则使用GCC进行编译。
我们在/Makefile中合适的位置(比如167行)添加以下代码(注意别拼写错了),如下所示。
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
2.4 配置、编译与烧录
2.4.1 配置与编译
在uboot源码顶层目录下依次输入下列指令,将在uboot源码顶层目录下得到u-boot.bin镜像文件。
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# make distclean
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# make s5p_goni_config
Configuring for s5p_goni board...
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# make
//省略编译输出
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10#
2.4.2 将镜像文件烧录到SD卡
为了将u-boot.bin烧录到X210开发板,需要先将sd_fusing文件夹复制到新版uboot的顶层目录中。
sd_fusing文件夹是三星官方另外提供的烧录SD卡的工具,因此X210开发版的uboot、三星官方uboot目录中都有这个文件夹。由于现在使用新版本uboot进行移植,uboot中并没有这个文件夹,所以需要复制。
具体操作见uboot源码分析——分析三星提供的sd_fusing文件夹(用来制作SD卡启动镜像)。
2.5 运行现象
上述操作之后将SD卡插入开发板并重启开发板,此时输出信息如下:
这里只输出一个 SD checksum Error,而非课程描述的现象(如下图):
三、在start.S最前面添加16字节占位
3.1 问题分析
3.1.1 分析课程描述的现象
uboot启动时,在SCRT上有串口输出的信息(接的是串口2,即远离网口的那个串口),但是这些信息不是uboot内部通过串口输出的,而是由iROM中的BL0运行时输出的。
第一个“SD checksum Error”,是第一顺序启动设备SD0(即 iNand)启动时校验和失败而打印出来的信息。这很正常,因为为了从SD卡启动,iNand已经被破坏,校验和自然失败。
第二个“SD checksum Error”,是第二顺序启动设备SD2(即SD卡)启动时校验和失败而打印出来的信息。
“uart negotiation error”,是串口启动失败而打印出来的信息。
“Insert an OTG cable into the connector”,是usb启动失败而打印出来的信息。
我们从两个“SD checksum Error”可以看出,外部SD卡校验和失败了。经过分析和查找(用winhex工具对比正常的uboot和出错的uboot),发现是mkbl1程序和start.S中前16个字节校验和的处理不匹配,造成外部SD卡校验和失败(2013.10版本uboot中的start.S文件开头没有16字节的占位,但sd_fusing里的mkbl1还是按有16字节占位的情况进行校验和,所以会失败)。
3.1.2 分析本次移植的现象
本次移植时,只显示一个“SD checksum Error”。
我觉得本次移植出现的现象才是合理的。怎么说呢?
首先,这个“SD checksum Error”肯定是iNand启动时校验和失败而打印出来的。但是后面就没有输出其他信息了。按理说iNand启动失败后,会转为SD卡启动,而 SD 卡启动时也会检测校验和,如果校验和失败则会打印第二个“SD checksum Error”。这里没有打印“SD checksum Error”,说明SD卡的校验和是成功的。那既然成功,说明可以SD卡启动,既然可以SD卡启动,为何没有后续信息输出呢?这是因为烧写到SD卡上的BL1是错误的因而不能运行,因此串口没有输出内容。
为什么说BL1是错误的呢?请见下面的分析:
根据mkv210_image.c文件详解与分析三星提供的sd_fusing文件夹中关于C110-EVT1-mkbl1.c文件的描述,我们能想象出本次移植中uboot的前8K(也就是BL1)是什么样子的,以及放在iSRAM中的0xd002_0000处又是什么样子的。
在C110-EVT1-mkbl1.c文件中,先将u-boot.bin的前8KB的内容,复制到大小为8KB的 Buff 中(从Buff[0] 开始接收u-boot.bin的前8KB的内容),然后计算“ Buff[16]~Buff结束 ”这段区域的数据的校验和,接着把计算得到的校验和写入Buff[8]~Buff[11]中(这里把u-boot.bin前8KB中的前16字节给破坏掉了)。然后将这个Buff的内容写入一个文件中,也就是生成了一个BL1文件。
请注意,这个校验和是正确的,它的确是“ Buff[16]~Buff结束 ”这段区域的数据的校验和。而且它就存放在BL1文件的第8~11字节处。BL1文件被BL0加载到地址0xd002_0000处时,这个校验和也就被加载到0xd002_0008~0xd002_000c里面。BL0会根据0xd002_0010~xxx(xxx的值为:BL1文件的大小 - 16字节 + 0xd002_0010 )的内容计算一个校验和,然后与0xd002_0008~0xd002_000c中存储的校验和进行对比。两者如果一样则执行0xd002_0010处的代码,如果不一样则会输出“SD checksum Error”。
假设SD卡存储正常,BL0读取BL1的过程也正常,则两者是一样的。
那么SoC将会执行0xd002_0010处的代码。如下图所示,由于2013.10版本的uboot的start.S文件开头没有16字节的占位,经过上面的操作之后,BL1被加载到地址0xd002_0000处时,0xd002_0010处的代码不再是uboot的入口代码了,它的入口代码在0xd002_0000处。另外u-boot.bin的第8~11字节的代码也被修改为一个校验和数字。因此SoC执行0xd002_0010处的代码会出错,因而没有后续输出(SD检验和通过了就不会再有“uart negotiation error”这些内容,另外BL1运行不了因此不会通过串口输出其他的信息)。
3.2 解决方法
这里有两个解决方法,任选其一即可。
方法一:在/arch/arm/cpu/armv7/start.S文件中的头文件包含下面添加16个字节的占位。
#include <asm-offsets.h>
#include <config.h>
#include <version.h>
#include <asm/system.h>
#include <linux/linkage.h>
//添加16字节填充,第一个字必须是0x200=8KB,代表BL1的大小?
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
方法二:修改sd_fusing文件夹中的C110-EVT1-mkbl1.c文件。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
FILE *fp;
char *Buf, *a, *b;
int BufLen;
int nbytes, fileLen;
unsigned int checksum;
int i;
//
if (argc != 4)
{
printf("Usage: mkbl1 <source file> <destination file> <size> \n");
return -1;
}
//
BufLen = atoi(argv[3]);
Buf = (char *)malloc(BufLen + 16);
memset(Buf, 0x00, BufLen + 16);
//
fp = fopen(argv[1], "rb");
if( fp == NULL)
{
printf("source file open error\n");
free(Buf);
return -1;
}
fseek(fp, 0L, SEEK_END);
fileLen = ftell(fp);
fseek(fp, 0L, SEEK_SET);
if ( BufLen > fileLen )
{
printf("Usage: unsupported size\n");
free(Buf);
fclose(fp);
return -1;
}
/*
** bhc add
*/
b = Buf + 16;
nbytes = fread(b, 1, BufLen, fp);
if ( nbytes != BufLen )
{
printf("source file read error\n");
free(Buf);
fclose(fp);
return -1;
}
fclose(fp);
//
a = b;
for(i = 0, checksum = 0; i < BufLen - 16; i++)
checksum += (0x000000FF) & *a++;
/*
** bhc add
*/
a = Buf;
*( (unsigned int *)a ) = 0x2000;
a = Buf + 8;
*( (unsigned int *)a ) = checksum;
//
fp = fopen(argv[2], "wb");
if (fp == NULL)
{
printf("destination file open error\n");
free(Buf);
return -1;
}
a = Buf;
nbytes = fwrite( a, 1, BufLen, fp);
if ( nbytes != BufLen )
{
printf("destination file write error\n");
free(Buf);
fclose(fp);
return -1;
}
free(Buf);
fclose(fp);
return 0;
}
3.3 运行现象
我们选择方法一。
重新编译烧写运行,发现结果只显示一个“SD checksum Error”。这是SD0通道的iNand启动校验和失败而打印出来的信息,这说明外部SD卡校验和是成功的,只是SD卡上的 uboot 是错误的,因此串口没有输出内容,因此没有输出。
另外开发板没有供电锁存,表现为:按下电源键时有电源指示LED灯亮,松开手则熄灭。这可能是这版本的uboot中根本就没有供电锁存的代码,或者有但是没有成功执行供电锁存的代码。
四、start.S文件的分析和移植
由于uboot启动第一阶段是在start.S文件中,所以我们移植前要先简单分析这个文件的流程。
4.1 分析start.S文件
【1】分析代码片段1:save_boot_params函数
/*
* the actual reset code
*/
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, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
其中save_boot_params是一个空函数,里面直接返回;然后设置关闭FIQ与IRQ,设置CPU进入特权模式。
【2】分析代码片段2:链接地址CONFIG_SYS_TEXT_BASE
.globl _TEXT_BASE
_TEXT_BASE:
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_TEXT_BASE)
.word CONFIG_SPL_TEXT_BASE
#else
.word CONFIG_SYS_TEXT_BASE
#endif
由“#define CONFIG_SYS_TEXT_BASE 0x34800000”(在/include/configs/s5p_goni.h文件中)可知,可知uboot的链接地址是DDR的0x34800000。
【3】分析代码片段3: cpu_init_cp15、cpu_init_crit函数(内部调用lowlevel_init函数)
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT //没有定义这个宏,所以执行
bl cpu_init_cp15
bl cpu_init_crit
#endif
cpu_init_cp15,这个函数的功能是设置MMU、cache等。这个版本的uboot未使用虚拟地址,因此在函数cpu_init_cp15中直接把MMU关掉(详见函数cpu_init_cp15代码)。
cpu_init_crit,这个函数里只有一句跳转指令,短跳转到lowlevel_init函数,如下所示:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
#endif
但是uboot中定义了2个lowlevel_init函数(分别位于/arch/arm/cpu/armv7/lowlevel_init.S文件、/board/samsung/goni/lowlevel_init.S文件中),我们通过简单的观察,无法断定这里调用哪个文件中的lowlevel_init函数。
不过我们通过分析这两个文件所在文件夹中的Makefile文件,可以得知调用的是/board/samsung/goni/lowlevel_init.S文件中的lowlevel_init函数。另外也可以通过查看这两个文件哪个文件被编程为.o文件,从而得知调用的是哪个文件中的函数。
【4】分析代码片段4:lowlevel_init函数
位于/board/samsung/goni/lowlevel_init.S文件中的lowlevel_init函数,做的有意义的工作有:
- 关看门狗;
- 调用uart_asm_init来初始化串口(不是X210开发板的串口初始化函数,需要修改移植);
- lowlevel_init.S文件中有时钟初始化的函数但是没被调用(如果uboot中没有初始化时钟,则使用的时钟就是iROM中配置的时钟)。
【5】分析代码片段5:_main函数
start.S文件最后一句代码是“bl _main”,也就是跳转到_main函数。搜索_main函数时发现有汇编的_main函数,也有C语言的_main函数。由于lowlevel_init函数没有没有初始化栈,这里肯定跳转到汇编的_main函数,也就是/arch/arm/lib/crt0.S文件中的_main函数。
4.2 添加调试代码
4.2.1 代码内容
为了调试uboot的第一阶段(调试的目的是确认它可以顺利运行,完成相应的工作),就要看到现象。为了看到现象,我们可以在程序中有选择地添加一些代码段。
我们从ARM裸机相关部分(见X210开发板裸机开发流程与细节、汇编阶段的start.S文件)找到下面三段代码(其实也可以从三星移植版本的uboot中找到相关的代码,但是它那里用到了很多寄存器定义,涉及到一些头文件包含的内容,诸多不方便),并将一些宏用真实数值表示(可能有很多文件中定义了同一个宏,具体选哪个需要看看头文件包含关系,以及条件编译)。
(1)开发板供电置锁的代码
注意这里写成了一个代码片段,而非函数的形式,因此使用时直接复制粘贴即可。
// 第0步:开发板置锁
ldr r0, =0xE010E81C
ldr r1, [r0]
ldr r2, =0x301
orr r1, r1, r2
str r1, [r0]
(2)串口初始化与打印“O”的代码
注意这里写成了一个代码片段,而非函数的形式,因此使用时直接复制粘贴即可。
/* set GPIO(GPA) to enable UART */
@ GPIO setting for UART
ldr r0, =0xE0200000
ldr r1, =0x22222222
str r1, [r0, #0x000]
ldr r1, =0x2222
str r1, [r0, #0x020]
ldr r0, =0xe2900800
mov r1, #0x0
str r1, [r0, #0x08]
str r1, [r0, #0x0c]
mov r1, #0x3
str r1, [r0, #0x00]
ldr r1, =0x3c5
str r1, [r0, #0x04]
ldr r1, =34
str r1, [r0, #0x28]
ldr r1, =0xdddd
str r1, [r0, #0x2c]
ldr r1, =0x4f4f4f4f
str r1, [r0, #0x20] @'O'
(3)LED点亮/熄灭代码
在基础代码阶段,串口还没有运行,无法使用串口调试工具。此时除了使用Jlink等调试工具来调试这种基础代码外,还可以使用LED点亮的方式来调试程序。
我们可以从程序的基本运行路径端出发,隔一段添加一个LED点亮代码,根据运行时的现象来判定哪里执行了哪里没执行,从而去定位问题。
以前做实验时发现,uboot运行时按住电源开关时所有4颗LED都是亮的,因此做实验时点亮LED是判断不了的,应该熄灭某些LED灯来判断。 示例代码如下,在实际应用时,要注意不同代码处亮灭情况的修改;另外注意示例代码不是函数,因此末尾不能添加函数返回语句“mov pc,lr”语句。
ldr r0, =0x11111111
ldr r1, =0xE0200240
str r0, [r1]
ldr r0, =((1<<3) | (0<<4) | (1<<5)) // 1是灭,0是亮
ldr r1, =0xE0200244
str r0, [r1]
ldr r2, =9000000
ldr r3, =0x0
delay_loop:
sub r2, r2, #1
cmp r2, r3
bne delay_loop
//mov pc,lr //以前在裸机中是作为延时函数使用,因此需要函数返回;
//现在是直接使用,因此不需要这句代码。
4.2.2 操作与分析
没有初始化串口之前(串口初始化是lowlevel_init函数调用uart_asm_init函数来实现的),不能使用串口输出‘O’的代码来调试,但可以使用“开发板供电置锁”或者“LED点亮/熄灭 ”的代码来调试。
这里演示利用“开发板供电置锁”代码来调试程序。
(1)操作1与现象分析
我们把开发板供电置锁的代码,拷贝到start.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, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
// 开发板置锁
ldr r0, =0xE010E81C
ldr r1, [r0]
ldr r2, =0x301
orr r1, r1, r2
str r1, [r0]
然后重新编译与烧录运行,发现开发板的串口除了输出一个“SD checksum Error”之外,依然没有其他的输出,但是开发板已经供电置锁,表现为松开电源按键开关时电源指示LED灯依然常亮。
这说明uboot已经开始工作,start.S文件中至少在开发板供电置锁代码处的内容是正常的。
(2)操作2与现象分析
我们将供电置锁的代码移动到start.S文件的其他地方。
比如移动到cpu_init_cp15函数与cpu_init_crit函数之间,此时开发板依旧供电置锁。
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
// 开发板置锁
ldr r0, =0xE010E81C
ldr r1, [r0]
ldr r2, =0x301
orr r1, r1, r2
str r1, [r0]
bl cpu_init_crit
#endif
但是移动到 cpu_init_crit 函数之后,此时开发板没有供电置锁。
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
// 开发板置锁
ldr r0, =0xE010E81C
ldr r1, [r0]
ldr r2, =0x301
orr r1, r1, r2
str r1, [r0]
#endif
这说明问题出现在“ bl cpu_init_crit ”这条代码里。
(3)操作3与现象分析
由4.1小节可知,cpu_init_crit函数里只有一句跳转指令,即短跳转到lowlevel_init函数。因此问题应该出现在lowlevel_init函数中。我们将供电置锁的代码移动到lowlevel_init函数开始位置:
_TEXT_BASE:
.word CONFIG_SYS_TEXT_BASE
.globl lowlevel_init
lowlevel_init:
// 开发板置锁
ldr r0, =0xE010E81C
ldr r1, [r0]
ldr r2, =0x301
orr r1, r1, r2
str r1, [r0]
mov r11, lr
/* r5 has always zero */
mov r5, #0
然后将串口初始化与打印'O'的代码覆盖掉uart_asm_init函数的代码(保留末尾的“mov pc, lr”)。
重新配置编译与烧录运行,发现开发板没有供电置锁,串口也没有输出‘O’。
这说明一旦将开发板供电置锁代码放在lowlevel_init函数中(现在放在该函数的开头都不运行了,更别说放在该函数的其他位置),供电置锁代码就不起作用,后面的串口初始化与打印‘O’的代码就更加不会运行了。
由此推断出 b lowlevel_init这句代码存在文件。
4.3 修改u-boot.lds将lowlevel_init.S放到前部
4.3.1 问题分析
为什么不能跳转到这个lowlevel_init函数呢?
如果BL1中确实存在这个函数,那么应该能跳转到这个函数的。现在跳转不到这个函数,则说明BL1中根本就没有这个函数内容,而这个函数又位于文件lowlevel_init.S文件,这说明BL1中根本就没有lowlevel_init.S文件的代码内容。
三星S5PV210要求BL1大小为8KB,因此uboot第一阶段的代码必须在前8KB内。BL1的组成由链接脚本决定,因此问题应该出现在链接脚本上。我们来分析一下三星uboot的链接脚本与与2013.10版本uboot的链接脚本的区别:
(1)链接脚本的存放位置不同。三星官方uboot的链接脚本直接写好并放在uboot源码的顶层目录中;而2013.10版本uboot的链接脚本位于uboot源码的顶层目录,它是以/arch/arm/cpu/u-boot.lds这个文件为原材料编译生成的。
(2)三星官方uboot的链接脚本,lowlevel_init.S代码段被放在前面;2013.10版本uboot的链接脚本中,lowlevel_init.S代码段没有被放在前面。这就导致lowlevel_init.S文件不在前8KB内,因此执行“b lowlevel_init”会失败。
4.3.2 解决方法
由上面的分析可知,解决该问题的思路,是保证lowlevel_init函数被链接到前面8KB中。
这里有两个解决方法,任选其一即可。
方法一:修改/arch/arm/cpu/u-boot.lds文件。
// /arch/arm/cpu/u-boot.lds文件
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
CPUDIR/start.o (.text*)
board/samsung/goni/lowlevel_init.o (.text*) //添加这行内容
*(.text*)
}
//这里没有列出其他内容
}
方法2:在/Makefile文件修改__LIBS这个变量的值(大约在第375行)。
__OBJS := $(subst $(obj),,$(OBJS))
#其实就是调换一下位置而已
#__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
__LIBS := $(subst $(obj),,$(LIBBOARD)) $(subst $(obj),,$(LIBS))
4.3.3 运行现象
如果使用方法二,则发现开发板供电置锁,并且串口输出'O'。
如果使用方法一,最后make时会报错,提示lowlevel_init重复定义:
4.4 修改Makefile解决编译问题
4.4.1 问题分析
使用方法一,为何会提示lowlevel_init重复定义?
这是因为 lowlevel_init 这个函数被链接了两次:第一次是在 /board/samsung/goni 这个目录下生成libgoni.o时被链接了1次(libgoni.o包含着lowlevel_init.o),第二次是链接脚本在链接生成u-boot时又被链接了1次(会同时链接 libgoni.o与 lowlevel_init.o,由于libgoni.o中已经包含lowlevel_init.o,所以这里会提示重复定义)。
4.4.2 解决方法
lowlevel_init函数位于/board/samsung/goni/lowlevel_init.S文件中,/board/samsung/goni/目录有一个Makefile文件,负责编译该目录下的文件。
如下所示,如果将该行代码注释掉,则会提示没有定义lowlevel_init(因为根据自动推导原则,没有lowlevel_init.o的需要,就不会编译lowlevel_init.S)。但其实我们还是需要编译lowlevel_init.S文件以得到lowlevel_init.o的,只是不需要将lowlevel_init.o链接进 libgoni.o而已。问题是如果不注释掉,则提示重复定义,这如何修改呢?
一个解决思路是,不要让lowlevel_init.o链接进libgoni.o,让它只在链接生成u-boot时被链接一次。
我们参考2013.10版本uboot的/arch/arm/cpu/armv7/Makefile文件的处理技巧,解决此这个问题。
修改/board/samsung/goni/Makefile文件如下。
include $(TOPDIR)/config.mk
LIB = $(obj)lib$(BOARD).o
COBJS-y := goni.o onenand.o
#注释掉这一行
#SOBJS := lowlevel_init.o
#添加这一行
LOW := lowlevel_init.o
SRCS := $(SOBJS:.o=.S) $(COBJS-y:.o=.c)
OBJS := $(addprefix $(obj),$(COBJS-y))
SOBJS := $(addprefix $(obj),$(SOBJS))
#添加这一行
all: $(obj).depend $(LOW) $(LIB)
$(LIB): $(obj).depend $(SOBJS) $(OBJS)
$(call cmd_link_o_target, $(SOBJS) $(OBJS))
#########################################################################
# defines $(obj).depend target
include $(SRCTREE)/rules.mk
sinclude $(obj).depend
#########################################################################
值得注意的是,一个规则中命令开头的空位原本是用TAB来表示的,但是复制粘贴时可能会变成空格符,这就不符合Makefile的语法了,因此编译会提示如下(因此要改回为TAB):
Makefile:21: *** 遗漏分隔符 (您的意思是用 TAB 代替 8 个空格?)。 停止。
另外很怪异,这里的“all: $(obj).depend $(LOW) $(LIB)”中,“all:”与“$(obj).depend”之间的空格居然也有问题。如果直接复制上面代码,并且也将规则中命令开头的空位改回TAB后,会提示下面的问题。此时需要不输入空格,或者以空格键或者TAB键,在“all:”与“$(obj).depend”之间输入空格(可以一个空格或多个空格)。
make[1]: *** 没有规则可以创建“all”需要的目标“ ”。 停止。
4.4.3 运行现象
重新编译与烧录运行,发现开发板供电置锁,并且串口输出'O'。
五、阶段总结与规划
5.1 阶段总结
目前已经解决的问题如下:
(1)start.S文件开头16字节占位的问题。
(2)“lowlevel_init.S文件不在前8KB内,因此b lowlevel_init执行失败”的问题。
(3)“lowlevel_init重复定义”的问题。
我们已经在lowlevel_init函数开始位置添加供电置锁代码,因此不用一直按着开发板电源开关。
我们已经修改lowlevel_init函数所调用的uart_asm_init函数的代码,使它可以正确地初始化X210开发板的串口并输出字符‘O’。
5.2 下一步规划
我们继续浏览2013.10版本uboot的代码执行逻辑,可以得知:
在2013.10版本的uboot中,第一阶段就是cpu_init_crit函数及该函数之前的内容,第二阶段就是_main函数。
第一阶段的cpu_init_crit函数(见4.1小节的描述),其实就是lowlevel_init函数,主要完成以下工作:
(1)关看门狗(不用修改)。
(2)调用uart_asm_init来初始化串口(已经完成修改)。
(3)虽然有时钟初始化的函数,但没有调用(则会继续使用iROM中配置的时钟,暂时不修改)。
第二阶段的入口是/arch/arm/lib/crt0.S文件中的_main函数,该函数主要完成以下工作;
(1)首先设置栈,即把sp执行DDR中的某个地址;
(2)然后调用/arch/arm/lib/board.c文件中board_init_f函数进行板级的初始化;
(3)然后进行uboot的重定位、清BSS段;
(4)最后调用board_init_r函数进入uboot的命令行。
由此可知看出,九鼎版本的uboot的start_armboot函数,有点类似这里的_main函数。
因此下一步的移植工作内容,就是在_main函数之前完成以下工作:
(1)在lowlevel_init函数中添加DDR初始化的代码。
(2)在start.S文件中“bl _main”之前添加uboot重定位的代码。
完成上面的移植工作后,应该就可以进入uboot的第二阶段,即_main函数的内容。
六、添加DDR初始化代码
这里是指,在cpu_init_crit函数所调用的lowlevel_init函数中,添加DDR初始化的代码。
/* for UART */
bl uart_asm_init //初始化串口并打印一个'O'
bl mem_ctrl_asm_init //在这里添加这条语句
/* Print 'K' */ //打印一个字符'K'
ldr r0, =0xe2900800
ldr r1, =0x4b4b4b4b //大写字母K的ASCII码,用十六进制就是4b
str r1, [r0, #0x20]
bl internal_ram_init
6.1 获取DDR初始化代码
如果uboot中本来就有DDR初始化代码,那么我们可以修改这些代码即可。
但是这个2013.10版本的uboot中根本就没有DDR初始化代码,因此我们需要另外添加DDR初始化代码。
我们可以将三星版本uboot中的DDR初始化代码移植过来,该版本的DDR初始化函数位于/cpu/s5pc11x/s5pc110/cpu_init.S文件中。
或者将九鼎版本uboot中的DDRDDR初始化代码移植过来,该版本的DDR初始化函数同样位于/cpu/s5pc11x/s5pc110/cpu_init.S文件中。
6.2 移植DDR初始化代码
6.2.1 复制cpu_init.S文件
(1)也就是将三星版本uboot中的/cpu/s5pc11x/s5pc110/cpu_init.S文件,复制到2013.10版本uboot中的/board/samsung/goni目录中。
为什么要复制到这个目录?因为由前面的分析可知,lowlevel_init.S文件被放在前8KB中,这里我们也希望将cpu_init.S文件放在前8KB中。所以我们把cpu_init.S文件放在lowlevel_init.S文件所在的目录下,也就是/board/samsung/goni目录。
(2)同时为了保证cpu_init.S有关的代码在前8KB内,需要进行类似于4.3.2与4.4.2的处理。
// /arch/arm/cpu/u-boot.lds文件
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
CPUDIR/start.o (.text*)
board/samsung/goni/lowlevel_init.o (.text*)
board/samsung/goni/cpu_init.o (.text*) //添加这行内容
*(.text*)
}
//这里没有列出其他内容
}
//在/board/samsung/goni/Makefile文件
//在 LOW 后面添加 cpu_init.o
LOW := lowlevel_init.o cpu_init.o
6.2.2 复制s5pc110.h文件
也就是将三星版本uboot中的/include/s5pc110.h文件,复制到2013.10版本uboot中的/include目录中。
为什么要添加这个文件呢?我们打开/cpu/s5pc11x/s5pc110/cpu_init.S文件,发现其有头文件包含,如下所示:
#include <config.h>
#include <s5pc110.h>
.globl mem_ctrl_asm_init
mem_ctrl_asm_init:
//省略其他代码
在2013.10版本uboot的/include目录中,本来就有config.h文件,但是没有s5pc110.h这个文件(该文件主要用来定义一些寄存器的地址)。因此我们需要将三星版本uboot中的/include/s5pc110.h文件,复制到2013.10版本uboot中的/include目录中。
为什么要放在/include目录下?因为/include目录是uboot默认的头文件存放目录。
6.2.3 修整cpu_init.S的代码
我们首先将cpu_init.S文件、s5pc110.h文件添加到SI工程。
然后重新解析一遍,接着对cpu_init.S文件进行分析与修整,包括:
(1)删掉一些无用的代码,处理一些条件编译(把不满足而不执行的代码删除)。
(2)添加cpu_init.S文件中缺失的宏定义。这些宏有些适合在/include/configs/s5p_goni.h文件中定义,有些适合在/include/s5pc110.h文件中定义。具体适合在哪个文件中定义,我们可以参考三星版本uboot或者九鼎版本uboot。分析得知,适合在/include/s5pc110.h文件中定义的宏都已经定义了;适合在/include/configs/s5p_goni.h文件中定义的宏,部分还没有定义。比如与DDR配置参数相关的宏(如DMC0_MEMCONFIG_0),应该定义在s5p_goni.h文件中。因此我们从三星版本uboot的smdkv210single.h文件中,将这些宏定义复制到s5p_goni.h文件中,如下所示:
#define DMC0_MEMCONTROL 0x00212400 // MemControl BL=4, 1Chip, DDR2 Type, dynamic self refresh, force precharge, dynamic power down off
#define DMC0_MEMCONFIG_0 0x30F01313 // MemConfig0 256MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed
#define DMC0_MEMCONFIG_1 0x40F01313 // MemConfig1
#define DMC0_TIMINGA_REF 0x00000618 // TimingAref 7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4E)
#define DMC0_TIMING_ROW 0x28233287 // TimingRow for @200MHz
#define DMC0_TIMING_DATA 0x23240304 // TimingData CL=3
#define DMC0_TIMING_PWR 0x09C80232 // TimingPower
#define DMC1_MEMCONTROL 0x00202400 // MemControl BL=4, 2 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off
#define DMC1_MEMCONFIG_0 0x40F01313 // MemConfig0 512MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed
#define DMC1_MEMCONFIG_1 0x00F01313 // MemConfig1
#define DMC1_TIMINGA_REF 0x00000618 // TimingAref 7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4E)
#define DMC1_TIMING_ROW 0x28233289 // TimingRow for @200MHz
#define DMC1_TIMING_DATA 0x23240304 // TimingData CL=3
#define DMC1_TIMING_PWR 0x08280232 // TimingPower
6.2.4 修整s5pc110.h文件
(1)将 #include <asm/hardware.h> 这个头文件包含注释掉。
(2)删掉下面的代码:
#ifndef __ASSEMBLY__
typedef enum {
S5PC11X_UART0,
S5PC11X_UART1,
S5PC11X_UART2,
S5PC11X_UART3,
} S5PC11X_UARTS_NR;
#include <s5pc11x.h>
#endif
(3)将黑色的宏定义代码注释掉(以防编译报错)。
(4)将三星版本uboot中的/include/asm-arm/arch-s5pc11x/hardware.h文件中类似于__REG的宏定义,复制到已经移植到2013.10版本uboot中的s5pc110.h文件中。
#define __REG(x) (*(vu_long *)(x))
#define __REGl(x) (*(vu_long *)(x))
#define __REGw(x) (*(vu_short *)(x))
#define __REGb(x) (*(vu_char *)(x))
#define __REG2(x,y) (*(vu_long *)((x) + (y)))
(5)比如从/* Fields */、/* bits */开始,到#endif /* #ifndef __ASSEMBLY__ */,之间所有的代码都删除。
6.2.5 编译与修正
修改之后,我们重新配置编译。如果有编译错误,则根据提示修正错误。
如果没有编译错误,则说明代码至少是没有错误的,否则编译上不可能通过。但是移植的代码不一定就能成功初始化DDR,需要进一步验证,也就是下面的6.2.6的内容。
6.2.6 验证DDR初始化完成
如何验证DDR初始化已经完成呢?我们可以添加调试信息。
调试信息有LED点亮和串口输出两种,这里优先选用串口调试的方法,因为串口已经初始化了。
我们在DDR初始化代码的后面,添加串口输出字符"K"的代码,如下所示:
/* Print 'K' */ //打印一个字符K
ldr r0, =0xE0200000
ldr r1, =0x4b4b4b4b //大写字母K的ASCII码,用十六进制就是4b
str r1, [r0, #0x20]
启动时如果看到串口输出"OK",则说明DDR已经初始化了。
6.3 运行现象
上述操作之后,发现开发板供电置锁,但是连之前的字符'O'都没有输出。
后来对比课程代码,发现课程里面打印'K'之后就函数返回了(分析发现是因为函数后面的内容不是很重要,所以直接函数返回),如下所示:
/* for UART */
bl uart_asm_init
bl mem_ctrl_asm_init
/* Print 'K' */
ldr r0, =0xe2900800
ldr r1, =0x4b4b4b4b
str r1, [r0, #0x20]
mov lr, r11 //添加的这两行代码,意味着函数要返回了
mov pc, lr
照猫画虎添加那两行代码后,我发现开发板居然供电置锁了,而且还输出“OK”。
更怪异的是,在进行上面的操作之后,我改回原来的样子(如下所示),居然可以输出“OK”!
/* for UART */
bl uart_asm_init //初始化串口并打印一个'O'
bl mem_ctrl_asm_init
/* Print 'K' */ //打印一个字符'K'
ldr r0, =0xe2900800
ldr r1, =0x4b4b4b4b //大写字母K的ASCII码,用十六进制就是4b
str r1, [r0, #0x20]
bl internal_ram_init
不知道哪个环节出现了怪事,有时间重新做一遍移植过程试试看。
七、添加uboot重定位与清BSS段的代码
这是是指在start.S文件中“bl _main”之前添加uboot重定位的代码,如下所示:
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
/**********************************************************/
// 在cpu_init_crit函数中最后初始化完了DDR,然后在下面跳转到了第二阶段,
// 所以重定位代码就在这里进行
// 第一步: 加一个调试信息串口输出字符来做调试定位
// 打印字符'A'
ldr r1, =0x41414141
ldr r2, =0xE2900820
str r1, [r2] @'A'
// 正式开始重定位
/* get ready to call C functions */ //是因为要调用C函数才设置的?
ldr sp, _TEXT_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip flash copy */
/* If BL1 was copied from SD/MMC CH2 */
ldr r0, =0xD0037488
ldr r1, [r0]
ldr r2, =0xEB200000
cmp r1, r2
beq mmcsd_boot
mmcsd_boot:
bl movi_bl2_copy
b after_copy
after_copy:
// 这里有一些设置sp的代码,暂时不要,到时候不对了再说
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
//bl _main
ldr pc, __main // 第一阶段和第二阶段的分界点
__main:
.word _main
/**************************************************************************/
7.1 问题分析
问题1:uboot的第一第二阶段的分界点在哪里?
uboot的第一阶段和第二阶段的划分并不是绝对的,但有个前提是uboot的第一阶段不能大于8KB,而且至少要完成DDR的初始化和重定位。在满足这些条件时,第一阶段和第二阶段的分界点可以随便挑选。
问题2:重定位有关的代码应该写在哪里?
逻辑上来说,重定位有关的代码应该写在DDR初始化之后、uboot第二阶段来临之前。分析代码发现,我们在cpu_init_crit函数调用的lowlevel_init函数的最后部分初始化了DDR,而uboot第二阶段的入口是start.S文件中的“bl _main”语句。所以重定位有关的代码,应该写在cpu_init_crit函数和语句“bl _main”之间。
问题3:为什么重定位之后要清理BSS段?
问题4:可以借鉴哪里的代码?
我们可以参考三星版本uboot的/cpu/s5pc11x/start.S文件的内容。
以下涉及到的重定位代码和清BSS段代码,都是从该文件中整理出来的。
7.2 解决方法
步骤1:首先添加一个调试信息。
即通过串口输出字符‘A’,以便作为调试定位。
// 第一步: 加一个调试信息串口输出字符来做调试定位
// 打印字符'A'
ldr r1, =0x41414141
ldr r2, =0xE2900820
str r1, [r2] @'A'
步骤2:然后添加重定位的代码。
判断是否需要重定位(注意判别的方法),如果不需要重定位,则直接跳到after_copy标号处;如果需要重定位,则调用movi_bl2_copy函数将uboot的代码复制到DDR中。
/* get ready to call C functions */ //是因为要调用C函数才设置的?
ldr sp, _TEXT_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip flash copy */
/* If BL1 was copied from SD/MMC CH2 */
ldr r0, =0xD0037488
ldr r1, [r0]
ldr r2, =0xEB200000
cmp r1, r2
beq mmcsd_boot
mmcsd_boot:
bl movi_bl2_copy
b after_copy
步骤3:接着移植movi_bl2_copy函数。
首先将三星版本uboot中的/cpu/s5pc11x/movi.c文件(movi_bl2_copy函数位于其中),复制到2013.10版本uboot中的/board/samsung/goni/目录;将三星版本uboot中的/include/movi.h文件,复制到uboot2013.10中的/include/目录。
然后修改movi.c文件的内容(主要目的是为了清爽),包括:
(1)注释掉头文件包含 #include <regs.h>。
(2)删除其他函数,只保留movi_bl2_copy有关的函数内容。
不过这里我直接拷贝课程的。
#include <common.h>
#include <s5pc110.h>
#include <movi.h>
#include <asm/io.h>
//#include <regs.h>
#include <mmc.h>
typedef u32(*copy_sd_mmc_to_mem)
(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
void movi_bl2_copy(void)
{
//保留该函数内容
}
接着修改2013.10版本uboot的/board/samsung/goni/Makefile文件,修改的内容如下:
LOW := lowlevel_init.o cpu_init.o movi.o
接着修改2013.10版本uboot的/arch/arm/cpu/u-boot.lds文件,修改的内容如下:
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
CPUDIR/start.o (.text*)
board/samsung/goni/lowlevel_init.o (.text*)
board/samsung/goni/cpu_init.o (.text*)
board/samsung/goni/movi.o (.text*)//添加这一行
*(.text*)
}
步骤4:移植清bss段的代码。
这段代码的形式与内容都很固定的,照抄就好。不过我觉得这里的_bss_start、_bss_end的前面是不是应该添加一个等号?让ldr成为伪指令,同时也能达到同样效果。有时间了解一下立即数。
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
步骤5:将“bl _main”改成“ldr pc, __main”,并定义“__main: .word _main”。
//bl _main
ldr pc, __main // 第一阶段和第二阶段的分界点
__main:
.word _main
这里的__main表示一个地址,这个地址对应的存储单元里存储着_main这个值(函数的入口地址)。
步骤6:修改/arch/arm/lib/crt0.S文件中的_main函数的代码。
删除与重定位有关的代码,保留设置栈、调用board_init_f函数、board_init_r函数的代码。
/*
* crt0 - C-runtime startup Code for ARM U-Boot
*
* Copyright (c) 2012 Albert ARIBAUD <albert.u.boot@aribaud.net>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <config.h>
#include <asm-offsets.h>
#include <linux/linkage.h>
/*
* This file handles the target-independent stages of the U-Boot
* start-up where a C runtime environment is needed. Its entry point
* is _main and is branched into from the target's start.S file.
*
* _main execution sequence is:
*
* 1. Set up initial environment for calling board_init_f().
* This environment only provides a stack and a place to store
* the GD ('global data') structure, both located in some readily
* available RAM (SRAM, locked cache...). In this context, VARIABLE
* global data, initialized or not (BSS), are UNAVAILABLE; only
* CONSTANT initialized data are available.
*
* 2. Call board_init_f(). This function prepares the hardware for
* execution from system RAM (DRAM, DDR...) As system RAM may not
* be available yet, , board_init_f() must use the current GD to
* store any data which must be passed on to later stages. These
* data include the relocation destination, the future stack, and
* the future GD location.
*
* (the following applies only to non-SPL builds)
*
* 3. Set up intermediate environment where the stack and GD are the
* ones allocated by board_init_f() in system RAM, but BSS and
* initialized non-const data are still not available.
*
* 4. Call relocate_code(). This function relocates U-Boot from its
* current location into the relocation destination computed by
* board_init_f().
*
* 5. Set up final environment for calling board_init_r(). This
* environment has BSS (initialized to 0), initialized non-const
* data (initialized to their intended value), and stack in system
* RAM. GD has retained values set by board_init_f(). Some CPUs
* have some work left to do at this point regarding memory, so
* call c_runtime_cpu_setup.
*
* 6. Branch to board_init_r().
*/
/*
* entry point of crt0 sequence
*/
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
sub sp, #GD_SIZE /* allocate one GD above SP */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
mov r9, sp /* GD is above SP */
mov r0, #0
bl board_init_f
#if ! defined(CONFIG_SPL_BUILD)
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/
#if 0
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
#endif
here:
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
ldr r0, =__bss_start /* this is auto-relocated! */
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
//bl coloured_LED_init
//bl red_led_on
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
ldr pc, =board_init_r /* this is auto-relocated! */
/* we should not return here. */
#endif
ENDPROC(_main)
步骤7:解决编译报错。
(1)由博文可知,拷贝函数的地址是 0xD0037F98。根据movi.c文件的内容,如果要调用这个地址处的拷贝函数,则需要定义 CONFIG_EVT1 这个宏。因此我们在/include/configs/s5p_goni.h文件中添加这个宏的定义。
#define CONFIG_EVT1 1 /* EVT1 */
(2)提示找不到 CFG_PHY_UBOOT_BASE 这个宏。这个宏表示uboot在DDR中的链接地址,根据4.1小节的描述,它的值就等于 CONFIG_SYS_TEXT_BASE 这个宏。因此我们在/include/configs/s5p_goni.h文件中添加这个宏的定义。
#define CFG_PHY_UBOOT_BASE CONFIG_SYS_TEXT_BASE
(3)提示找不到 CFG_ENV_SIZE 这个宏。因此我们在/include/configs/s5p_goni.h文件中添加这个宏的定义。
/* Total Size of Environment Sector */ //16KB,即32扇区
#define CFG_ENV_SIZE 0x4000
(4)提示没有定义_bss_start、_bss_end。由于清理BSS段的代码是我移植时直接拷贝的,没有注意到start.S文件根本就没有定义这两个符号。因此我们在start.S文件中的第97行添加这两个符号的定义:
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
(5)提示“u-boot contains relocations other than R_ARM_RELATIVE”。
我们在2013.10版本uboot的源码目录下,通过使用 grep 命令(用法见该博客的内容)查找关键词R_ARM_RELATIVE,发现/Makefile文件中有一个检查重定位的规则。我们把这个规则的命令注释掉(注意是注释掉规则的命令,而不是整条规则。另外这规则的命令的作用是什么?注释与否有何影响呢),则编译与链接就成功了。
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# grep -nr "R_ARM_RELATIVE" ./
./Makefile:786:# ARM relocations should all be R_ARM_RELATIVE.
#省略部分输出
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10#
# ARM relocations should all be R_ARM_RELATIVE.
checkarmreloc: $(obj)u-boot
# @if test "R_ARM_RELATIVE" != \
# "`$(CROSS_COMPILE)readelf -r $< | cut -d ' ' -f 4 | grep R_ARM | sort -u`"; \
# then echo "$< contains relocations other than \
# R_ARM_RELATIVE"; false; fi
(6)电脑突然蓝屏,重启后重新编译时提示“ Makefile:215: *** could not find linker script. ”。这是因为在编译的过程中打断了编译过程,导致链接脚本文件路径丢失。我们需要distclean生成的文件(亲测可行):
- 将/Makefile文件的第215行注释掉;
- 在/Makefile文件的同级目录下执行“ make distclean ”;
- 取消/Makefile文件的第215行的注释。
7.3 运行现象
uboot启动打印出来一系列信息,但是uboot没有进入命令行,如下所示:
这说明uboot中的DDR初始化和重定位功能都已经实现。
但是这里居然输出“OA”而非“OKA”。按理如果能输出'A',则应该可以输出'K',因为输出'K'的代码在输出‘A’的代码之前。我尝试把输出'K'的代码放在输出'A'之后,输出的居然是“OK”而非“OAK”。这就很奇怪!后来经过对比,我发现我移植的串口初始化函数与课程的不同。
课程移植串口初始化函数代码是:
uart_asm_init:
/* set GPIO to enable UART0-UART4 */
mov r0, r8
ldr r1, =0x22222222
str r1, [r0, #0x0] @ S5PC100_GPIO_A0_OFFSET
ldr r1, =0x00002222
str r1, [r0, #0x20] @ S5PC100_GPIO_A1_OFFSET
/* Check S5PC100 */
cmp r7, r8
bne 110f
/* UART_SEL GPK0[5] at S5PC100 */
add r0, r8, #0x2A0 @ S5PC100_GPIO_K0_OFFSET
ldr r1, [r0, #0x0] @ S5PC1XX_GPIO_CON_OFFSET
bic r1, r1, #(0xf << 20) @ 20 = 5 * 4-bit
orr r1, r1, #(0x1 << 20) @ Output
str r1, [r0, #0x0] @ S5PC1XX_GPIO_CON_OFFSET
ldr r1, [r0, #0x8] @ S5PC1XX_GPIO_PULL_OFFSET
bic r1, r1, #(0x3 << 10) @ 10 = 5 * 2-bit
orr r1, r1, #(0x2 << 10) @ Pull-up enabled
str r1, [r0, #0x8] @ S5PC1XX_GPIO_PULL_OFFSET
ldr r1, [r0, #0x4] @ S5PC1XX_GPIO_DAT_OFFSET
orr r1, r1, #(1 << 5) @ 5 = 5 * 1-bit
str r1, [r0, #0x4] @ S5PC1XX_GPIO_DAT_OFFSET
b 200f
110:
/*
* Note that the following address
* 0xE020'0360 is reserved address at S5PC100
*/
/* UART_SEL MP0_5[7] at S5PC110 */
add r0, r8, #0x360 @ S5PC110_GPIO_MP0_5_OFFSET
ldr r1, [r0, #0x0] @ S5PC1XX_GPIO_CON_OFFSET
bic r1, r1, #(0xf << 28) @ 28 = 7 * 4-bit
orr r1, r1, #(0x1 << 28) @ Output
str r1, [r0, #0x0] @ S5PC1XX_GPIO_CON_OFFSET
ldr r1, [r0, #0x8] @ S5PC1XX_GPIO_PULL_OFFSET
bic r1, r1, #(0x3 << 14) @ 14 = 7 * 2-bit
orr r1, r1, #(0x2 << 14) @ Pull-up enabled
str r1, [r0, #0x8] @ S5PC1XX_GPIO_PULL_OFFSET
ldr r1, [r0, #0x4] @ S5PC1XX_GPIO_DAT_OFFSET
orr r1, r1, #(1 << 7) @ 7 = 7 * 1-bit
str r1, [r0, #0x4] @ S5PC1XX_GPIO_DAT_OFFSET
200:
// 串口2输出字符'O'
ldr r1, =0x4f4f4f4f
ldr r2, =0xE2900820
str r1, [r2] @'O'
mov pc, lr
我移植的串口初始化函数是:
uart_asm_init:
/* set GPIO(GPA) to enable UART */
@ GPIO setting for UART
ldr r0, =0xE0200000
ldr r1, =0x22222222
str r1, [r0, #0x000]
ldr r1, =0x2222
str r1, [r0, #0x020]
ldr r0, =0xe2900800
mov r1, #0x0
str r1, [r0, #0x08]
str r1, [r0, #0x0c]
mov r1, #0x3
str r1, [r0, #0x00]
ldr r1, =0x3c5
str r1, [r0, #0x04]
ldr r1, =34
str r1, [r0, #0x28]
ldr r1, =0xdddd
str r1, [r0, #0x2c]
ldr r1, =0x4f4f4f4f
str r1, [r0, #0x20] @'O'
mov pv,lr
如果用课程移植的串口初始化代码,则可以打印出“OKA”:
有时间查看一下两者代码的区别。