uboot的移植——移植uboot官方的uboot到x210开发板(1)

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。 

参考内容

(1)uboot——官网下载直接移植(一) - biaohc - 博客园

(2)uboot——官网下载直接移植(二) - biaohc - 博客园

(3)uboot移植(一)配置过程分析-CSDN博客

(4)https://blog.51cto.com/u_16099346/9298529

(5)https://www.cnblogs.com/eeexu123/p/7263465.html

目录

内容总结

特别说明

一、移植前的准备 

1.1 获取官方uboot

1.2 分析uboot目录

1.2.1 比较版本的不同点

1.2.2 选取参照开发板

1.3 删除无关文件并建立SI项目

1.3.1 删除无关文件 

1.3.2 使用sourceinsight创建项目

二、修改/Makefile文件

2.1 分析/Makefile文件

2.2 明确参照开发板的信息

2.3 添加交叉编译工具链

2.4 配置、编译与烧录

2.4.1 配置与编译

2.4.2 将镜像文件烧录到SD卡 

2.5 运行现象

三、在start.S最前面添加16字节占位

3.1 问题分析 

3.1.1 分析课程描述的现象

3.1.2 分析本次移植的现象

3.2 解决方法

3.3 运行现象 

四、start.S文件的分析和移植

4.1 分析start.S文件

4.2 添加调试代码

4.2.1 代码内容

4.2.2 操作与分析

4.3 修改u-boot.lds将lowlevel_init.S放到前部

4.3.1 问题分析

4.3.2 解决方法

4.3.3 运行现象

4.4 修改Makefile解决编译问题

4.4.1 问题分析

4.4.2 解决方法

4.4.3 运行现象 

五、阶段总结与规划

5.1 阶段总结

5.2 下一步规划

六、添加DDR初始化代码

6.1 获取DDR初始化代码 

6.2 移植DDR初始化代码

6.2.1 复制cpu_init.S文件

6.2.2 复制s5pc110.h文件

6.2.3 修整cpu_init.S的代码

6.2.4 修整s5pc110.h文件

6.2.5 编译与修正

6.2.6 验证DDR初始化完成

6.3 运行现象

七、添加uboot重定位与清BSS段的代码

7.1 问题分析

7.2 解决方法

7.3 运行现象 


内容总结

移植过程的思维导图如下所示:

特别说明

在实际工作中,一般不需要从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函数,做的有意义的工作有:

  1. 关看门狗;
  2. 调用uart_asm_init来初始化串口(不是X210开发板的串口初始化函数,需要修改移植);
  3. 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段?

理解重定位时运行地址和链接地址相同时为什么还要清零

代码重定位与清除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生成的文件(亲测可行):

  1. 将/Makefile文件的第215行注释掉;
  2. 在/Makefile文件的同级目录下执行“ make distclean ”;
  3. 取消/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”:

有时间查看一下两者代码的区别。

  • 11
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值