10、uboot移植——使用官方uboot进行移植(2)

来源:互联网 发布:adobe cloud mac 下载 编辑:程序博客网 时间:2024/06/06 01:44

以下内容源于朱有鹏《物联网大讲堂》课程的学习整理,以及博客http://www.cnblogs.com/biaohc/p/6403863.html的学习和整理。如有侵权,请告知删除。


了解移植的过程、调试的思想、解决问题的方法。

一、start.S最前面添加16字节填充

1、复制sd_fusing文件夹,到Linux系统下的uboot根目录中,烧录sd卡。

(1)输出信息


(2)分析、总结、解决方法

分析:

  • 接的是串口2,串口有输出。但是这个串口输出不是uboot输出的,而是内部iROM中的BL0运行时输出的。
  • 第一个SD checksum Error:是第一顺序启动设备SD0(iNand)启动时校验和失败打印出来的(正常,因为已经破坏inand,想从SD卡启动);
  • 第二个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个字节校验和的处理不匹配造成的。

解决方法:

  • 法一:在start.S(目录层次arch/arm/cpu/armv7/start.S)最前面加上16个字节的占位。新的uboot没有占位,但sd_fusing里的mkbl1还是按有16字节占位的情况进行校验和,所以会失败。前面别添加宏开关!

  • 法二:参见博客http://www.cnblogs.com/biaohc/p/6476866.html

(3)重新编译烧写运行

  • 发现结果只显示一个SD checksum Error。
  • 这是内部SD0通道的inand启动校验和失败打印出来的,说明外部SD卡校验和成功,只是SD卡上的uboot是错误的,没有串口输出内容,因此没有输出。


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

1、start.S流程分析

2、添加开发板制锁,串口输出“O”

(1)目的

  • 为了调试uboot的第一阶段(调试的目的是确认它可以顺利运行,完成相应的工作),就要看到现象;
  • 为了看到现象,向lowlevel_init函数中添加2个程序段,一个是开发板制锁,一个是串口初始化后打印"O"

(2)实践添加


(3)结果及分析

  • 结果:没看到开发板制锁,串口也没有输出任何东西,实验失败。
  • 结论:开发板制锁没有成功,因此判定,在开发板制锁代码运行之前uboot就已经挂掉了。因此要追踪代码运行,见下面的方法。

3、添加LED点亮代码跟踪程序运行

(1)使用LED点亮的方式来调试程序

  • 在基础代码阶段,串口还没有运行,串口调试工具还无法使用;
  • 可以从程序的基本运行路径端出发,隔一段添加一个LED点亮代码,根据运行时的现象来观察,判定哪里执行了哪里没执行,从而去定位问题。
  • 以前做实验时发现,uboot运行时按住电源开关时所有4颗LED都是亮的,因此做实验时点亮LED是判断不了的,应该熄灭某些LED来判断。
  • 也可以用Jlink等调试工具来调试这种基础代码。

(2)可用的示例代码

  • 注意不同代码处亮灭情况的修改;
  • 注意延时函数的命名修改;
  • 注意示例代码不是函数,因此没有必要添加函数返回语句mov pc,lr;
ldr r0, =0x11111111ldr r1, =0xE0200240str r0, [r1]ldr r0, =((1<<3) | (0<<4) | (1<<5))// 1是灭,0是亮ldr r1, =0xE0200244str r0, [r1]ldr r2, =9000000ldr r3, =0x0delay_loop:sub r2, r2, #1cmp r2, r3bne delay_loop        //mov pc,lr 不要添加这句,以前在裸机中是作为延时函数使用,因此需要函数返回;现在是直接使用,不需要!

(3)实践操作


(4)结果和分析

  • 结果:点亮LED灯的代码一旦放到lowlevel_init.S中,就不工作。
  • 结论:b lowlevel_init这句代码出现问题。

4、修改u-boot.lds,将lowlevel_init.S放到前部

(1)分析问题出在代码的连接上

  • 三星S5PV210要求BL1大小为8KB,因此uboot第一阶段的代码必须在前8KB内,否则跳转不到。

(2)三星移植版本的uboot的u-boot.lds  vs 官方版本uboot的连接脚本u-boot.lds

  • 注意这两个版本的uboot的连接脚本的位置是不同的;
  • lowlevel_init.S代码段没有被放在前面,即lowlevel_init.S不在前8KB内,因此b lowlevel_init执行失败

(3)修改操作

  • 在u-boot.lds中start.o后面添加board/samsung/goni/lowlevel_init.o (.text*),即可保证lowlevel_init函数被连接到前面8kb中。

(4)结果

  • 报错,提示lowlevel_init重复定义。

5、修改board/samsung/goni/Makefile解决编译问题

(1)为什么会重复定义?

  • lowlevel_init这个函数被链接了两次;
  • 一次是在board/samsung/goni目录下生成libgoni.o时,另一次是链接脚本在链接生成u-boot时;
  • 单纯地将代码注释掉,将导致不会编译.s(因为没有.o的需要,根据自动推导原则,将不会编译);我们只是不需要链接它,编译还是需要的。

(2)如何解决此错误?

  • 解决思路:完成编译,但在libgoni.o中不被连接,在最终连接u-boot时才被链接。
  • 参考当前版本的uboot的start.S文件的处理技巧,解决此这个问题。


(3)实验结果

  • 开发板制锁、串口输出'O'。


总结,目前的修改内容:

(1)针对“lowlevel_init.S不在前8KB内,因此b lowlevel_init执行失败”的问题

  • 在u-boot.lds中start.o后面添加board/samsung/goni/lowlevel_init.o (.text*),即可保证lowlevel_init函数被连接到前面8kb中;
(2)针对“lowlevel_init重复定义”的问题
  • 见上面的操作细节

三、添加DDR初始化

1、分析下一步移植路线

(1)在2013.10版本的uboot中

  • 第一阶段,就是cpu_init_crit函数,因此要在cpu_init_crit函数中添加DDR初始化和uboot的重定位。
  • 第二阶段,在crt0.S文件中,入口是_main函数。
(2)在2013.10版本的uboot中,把以前uboot的第二阶段start_armboot函数分成2部分:board_init_f和board_init_r。

  • board_init_f中是板级的初始化;
  • board_init_r中进入了uboot的命令行。

(3)cpu_init_crit函数成功初始化串口、时钟后,转入_main函数。

  • _main函数在arch/arm/lib/crt0.S文件中。

(4)在crt0.S中首先设置栈,将sp指向DDR中的栈地址;然后调用board_init_f函数进行板级初始化。

  • board_init_f函数在arch/arm/lib/board.c中。

(5)下一步移植工作方向

  • 先在cpu_init_crit函数中添加DDR初始化;
  • 然后在start.S中bl _main之前添加uboot的重定位;
  • 然后将bl _main改成ldr pc, __main(__main: .word _main)长跳转;
  • 然后在crt0.S中board_init_f后删除那些重定位代码;
  • 至此uboot的第二阶段就应该能启动起来。

2、DDR初始化代码移植

(1)2013.10版本的uboot中根本没有DDR初始化,需要另外添加DDR初始化代码。

  • 可以从三星版本的uboot中直接移植DDR初始化代码。
  • 三星版本的uboot中DDR初始化函数在cpu/s5pc11x/s5pc110/cpu_init.S文件中。

(2)动手移植

a、将三星版本的cpu/s5pc11x/s5pc110/cpu_init.S复制到uboot2013.10中。

  • 必须保证cpu_init.S有关的代码在前8kb内,因此类似于lowlevel_init.S文件一样的链接处理;
  • 链接处理主要是在board/samsung/goni/Makefile中、arch/arm/cpu/u-boot.lds文件中做修改添加。

b、添加头文件s5pc110.h到include目录下。

c、对cpu_init.S文件代码进行修整。

  • 把一些无用的代码去掉,把一些相关的条件编译人工处理一下。

d、在SourceInsigt工程中添加cpu_init.S文件、s5pc110.h文件。

  • 重新解析一遍。
  • 对新添加的代码进行分析修整,把里面一些明显的宏定义缺失给补上。

e、移植必要的宏定义

  • DDR配置参数,从三星版本的smdkv210single.h中复制到s5p_goni.h中。
  • s5pc110.h中的修整。

f、代码同步、编译、再修整

g、添加调试信息,验证DDR初始化完成。

  • 调试信息有LED点亮和串口输出两种,优先选用串口调试的方法。(这里已经初始化了串口)
  • 在DDR初始化完成后,添加串口输出字符"K",这样启动时如果看到了"OK"就说明DDR已经被成功初始化了。
h、结果
  • 看到了"OK"标志,说明DDR添加实验成功。


四、添加uboot第二阶段重定位

1、在重定位代码前加调试信息定位

(1)逻辑上来说,重定位部分代码应该在DDR初始化之后和uboot第二阶段来临前之间。

(2)uboot的第一阶段和第二阶段的划分并不是绝对的,但第一阶段必须不能大于8KB。

  • uboot的第一阶段至少要完成DDR初始化和重定位,且最多不能超过8KB。
  • 在满足这些条件时,第一阶段和第二阶段的接点可以随便挑。

(3)找到合适的地方来写重定位代码,重定位之后远跳转到第二阶段的入口。

2、重定位代码移植

3、清bss段移植

4、movi_bl2_copy函数移植

(1)从三星版本的uboot中复制movi.c和movi.h到uboot2013.10中;

(2)改makefile和u-boot.lds。

5、_mian函数中基本处理

  • 主要就是把里面的重定位代码部分给删除掉。剩下就是:设置栈、调用board_init_f函数和board_init_r函数。

6、代码同步及编译

  • 主要是crt0.S和movi.h。

7、编译中出现问题解决

(1)movi.h中宏定义出错

  • 在s5p_goni.h中添加了 CONFIG_EVT1

(2)连接错误:u-boot contains relocations other than R_ARM_RELATIVE

  • 在uboot下用grep "R_ARM_RELATIVE" -nR *搜索,发现Makefile中有一个检查重定位的规则,屏蔽掉这个规则后编译连接成功。

8、编译,下载,结果分析

uboot启动打印出来的一系列信息,但是uboot没有进入命令行。

  • 这说明uboot中的DDR初始化和重定位功能都已经实现。


五、CPU时钟信息显示移植

1、banner信息补全

2、CPU ID的确定

3、CPU各种频率的自动计算

4、代码实践

  • arch/arm/include/asm/arch-s5pc1xx/cpu.h,和arch/arm/cpu/armv7/s5p-common/cpu_info.c文件同步一下

5、问题分析

(1)时钟显示ARMCLK是400MHz。

(2)调试,把m、p、s和apll_ratio打印出来后,发现这几个值的设置和之前的uboot的设置是不同的。

  • 原因是当前版本的uboot并未对SoC的时钟进行设置,当前uboot中的时钟是iROM代码默认设置的。

(3)我一直认为iROM中已经把210的时钟设置为1000MHz,因此uboot中是否有时钟设置并不重要。

  • 但实际并非如此;
  • 内部iROM中设置的时钟APLL输出的是800MHz,ARMCLK是400MHz。
  • 因此如果uboot中不做时钟的设置,实际得到的就是这个时钟,所以得到的结果是400MHz。
  • 所以要解决这个时钟不对的问题,在lowlevel_init.S中添加上时钟初始化的代码即可。

6、时钟初始化函数的添加

  • 在lowlevel_init.S中移植system_clock_init函数,并且在s5p_goni.h中添加相关的宏定义参数,然后在lowlevel_init函数中调用system_clock_init函数。

六、board和DDR配置显示移植

1、board名称更改

2、DDR配置值修改

3、MACH_TYPE定义

4、DDR打印信息更改

5、代码实践

6、关于MACH_TYPE的定义问题

(1)uboot2013.10、uboot1.3.4的设计有所不同

  • uboot1.3.4中,MACH_TYPE分散定义在各个配置头文件中;
  • uboot2013.10中,MACH_TYPE集中定义在arch/arm/include/asm/mach-types.h中。
  • 集中定义是uboot从linux内核中学来的,在linux kernel中,MACH_TYPE在文件中集中定义。
  • 集中定义的好处是方便查阅,不容易定义重复。

(2)MACH_TYPE和开发板绑定

  • 原则上每一个开发板型号都有一个MACH_TYPE,这个机器码由linux内核管理者来分配的,如果需要应申请。

七、board_init_r移植

1、去掉oneNand支持

2、添加SD/MMC支持


八、uboot2013.10中SD/MMC驱动浏览

1、从初始化代码开始浏览

2、相关函数和文件

  • drivers/mmc/mmc.c
  • drivers/mmc/sdhci.c
  • board/samsung/goni/goni.c
  • arch/arm/include/asm/arch-s5pc1xx/mmc.h

3、当前错误定位及解决方案分析

(1)错误发生路径定位

  • board_init_r
  • mmc_initialize
  • do_preinit
  • mmc_start_init
  • mmc_go_idle
  • mmc_send_cmd
  • sdhci_send_command
  • sdhci_transfer_data:错误在这个函数中

(2)错误原因分析

  • sdhic.c中的所有函数构成了三星210CPU的SD/MMC控制器的驱动。
  • 这里面的函数是三星公司的工程师写的,内容就是用来控制210CPU的内部的SD/MMC控制器和外部的SD卡通信的。这就是所谓的驱动。
  • sdhci_transfer_data函数出错,说明是SoC的SD/MMC控制器和外部SD卡(其实现在用的是SD0的iNand)的数据传输出了问题。
  • 细节分析发现是控制器内部有一个中断状态错误标志被置位了。

(3)三种解决方案

  • 第一种,逐行分析SD卡驱动实现(分析中要对SD卡通信协议、SD控制器非常熟悉),发现错误所在,然后修改代码解决问题;
  • 第二种,把原来三星移植版本的uboot中的SD/MMC驱动整个移植过来替换掉uboot2013.10中的MMC驱动;
  • 第三种,综合第一种和第二种,譬如参考三星移植版本的uboot中的驱动实现来修补uboot2013.10中的驱动实现。

九、SD卡驱动移植

1、分析两个版本的uboot中SD卡驱动差异

(1)uboot2013.10中,驱动相关的文件主要有:

  • drivers/mmc/mmc.c
  • drivers/mmc/sdhci.c
  • drivers/mmc/s5p_sdhci.c
  • board/samsung/goni/goni.c

(2)三星移植版本中,驱动相关的文件主要有:

  • drivers/mmc/mmc.c
  • drivers/mmc/s3c_hsmmc.c
  • cpu/s5pc11x/cpu.c
  • cpu/s5pc11x/setup_hsmmc.c

(3)SD卡驱动要工作要包含2部分内容

  • 一部分是drivers/mmc目录下的是驱动;
  • 一部分是uboot自己提供的初始化代码(譬如GPIO初始化、时钟初始化)

2、复制必要的文件并修改相应Makefile

(1)首先解决drivers/mmc目录下的文件替换。

(2)修改初始化代码。

3、代码浏览及修补

按照代码运行时的流程来逐步浏览代码,看哪里需要修补。

4、继续修补驱动代码

(1)include/mmc.h

(2)include/s3c_hsmmc.h

5、同步及编译、问题解决

(1)出错1:cmd_mmc.c中出错

  • 原因是cmd_mmc.c和mmc驱动密切相关,所以改了驱动后这个实现文件也要跟着改。
  • 解决方法是从三星版本的直接同名文件复制过来替换

(2)出错2:drivers/mmc/mmc_write.c编译出错

  • 原因是这个文件和本来版本中的mmc.c文件相关,但是mmc.c被替换掉了所以这个文件编译报错。
  • 解决方案就是修改makefile去掉这个文件的依赖,让他不被编译。

(3)出错3:#include<regs.h>注释掉,然后添加#include <s5pc110.h>

6、解决每次编译时间都很长的问题

(1)本次移植实验中,每次编译脚本cp.sh执行时都会先cp同步代码,然后make distclean……所以每次都会清空后从头编译,很费时间。

(2)实际上有时候不用make distclean,只需要先cp然后直接make即可

  • 当更改没有涉及到配置头文件s5p_goni.h,没有涉及到makefile文件,或者其他项目配置文件,
  • 即更改只是发生在普通代码文件的更改时;

7、效果测试

  • 读写测试均成功


十、环境变量的代码浏览

1、iNand分区表检查,env究竟应该放在哪?

(1)测试环境变量是否可以保存,通过开机set设置环境变量然后save,然后关机后重启来测试环境变量的保存是否成功。

(2)环境变量究竟保存到哪里去了?这个要分析代码中的分区表。

(3)环境变量应该被放在哪里?

  • 虽然无法确定ENV一定要放在哪里,但是有一些地方肯定是不能放的,否则将来会出问题。
  • 原则是同一个SD卡扇区只能放一种东西,不能叠加,否则就会被覆盖掉。
  • uboot烧录时使用的扇区数是:SD2的扇区1-16和49-x(x-49大于等于uboot的大小)

(4)从uboot的烧录情况来看

  • SD2的扇区0空闲;
  • 扇区1-16被uboot的BL1占用;
  • 扇区17-48空闲;
  • 扇区49-x被uboot的BL2占用。
  • 再往后是内核、rootfs等镜像的分区。系统移植工程师可以根据kernel镜像大小、rootfs大小等给SD分区。
  • 从uboot的分区情况来看,ENV不能往扇区1-16或者49-x中来放置,其他地方都可以商量。
  • ENV的大小是16K字节也就是32个扇区。

2、环境变量相关代码浏览

(1)目前情况是uboot在SD2中,而ENV在SD0中,所以现在ENV不管放在哪个扇区都能工作,不会有问题。

  • 但是我们还是得找到ENV分区所在,并且修改,使得不会和uboot冲突;
  • 因为将来部署系统时我们会将uboot和kernel、rootfs等都烧录到iNnand中去,那时候也要确保不会冲突。

(2)static inline int write_env(struct mmc *mmc, unsigned long size,unsigned long offset, const void *buffer)

  • mmc表示要写的mmc设备,size表示要写的大小,offset表示要写到SD卡的哪个扇区去,buffer是要写的内容。

(3)CONFIG_ENV_OFFSET

  • 决定ENV在SD卡中相对SD卡扇区0的偏移量,也就是ENV写到SD卡的位置。
  • 经过分析发现这个宏的值为0,因此ENV被写到了0扇区开始的32个扇区中。
  • 写到这里肯定不行,因为和uboot的BL1冲突了;解决方案是改变这个CONFIG_ENV_OFFSET的值,将ENV写到别的空闲扇区去。

(4)#define MOVI_BL2_POS((eFUSE_SIZE / MOVI_BLKSIZE) + MOVI_BL1_BLKCNT + MOVI_ENV_BLKCNT)

  • 后面三个分别是1+16+32=49;
  • 其中的1就是扇区0(空闲的),16是就是扇区1-16(uboot的BL1),32就是扇区17-48(存放ENV的),49自然就是uboot的BL2开始扇区了。
  • 这种安排是三星移植的uboot版本中推荐的SD卡的分区方式,不一定是唯一的。
  • 我们参考这个设计,即可实现环境变量不冲突。所以只要将ENV放到17扇区起始的地方即可。

十一、环境变量的测试和配置移植

1、如何测试环境变量的保存是否正确?

(1)程序修改重新编译后启动,启动后要注意iNand中本来有没有环境变量。

  • 为了保险起见,对iNand的前49个扇区进行擦除,然后就可以确保里面没有之前保存过的环境变量了。
  • 使用命令:mmc write 0 30000000 0# 49来擦除SD0的扇区0-48,保证以前的环境变量都没有了。

(2)重新开机后,先set随便改一个环境变量作为标记,然后saveenv,接着重启。

(3)测试方法

  • 使用mmc read 0 30000000 17# 32命令,将iNand的17开始的32个扇区读出来到内存30000000处,然后md查看。
  • 找到显示区域里面的各个环境变量,看读出来的和自己刚才修改的值是否一样。

2、常用环境变量的配置移植

  • 常用的环境变量就是网络相关的那几个,和CONFIG_BOOTCOMMAND、CONFIG_BOOTARGS等。


十二、网卡驱动的移植

1、添加网络支持

(1)uboot中各种功能,是通过条件编译来实现可配置可裁剪的

  • 默认情况下,uboot没有选择支持网络。

(2)在配置头文件中添加一行 #define CONFIG_CMD_NET

(3)添加了网络支持宏之后,在uboot初始化时就会执行eth_initialize函数,从而网络相关代码初始化就会被执行,将来网络就有可能能用。

2、添加ping和tftp命令

(1)在linux系统中,网络底层驱动被上层应用调用的接口是socket,是一个典型的分层结构,底层和上层是完全被socket接口隔离的。

(2)但是在uboot中,网络底层驱动和上层应用是黏在一起的,不分层。

  • 意思就是上层网络的每一个应用都是自己去调用底层驱动中的操作硬件的代码来实现的。

(3)uboot中有很多预先设计好的需要用到网络的命令,和我们直接相关的就是ping和tftp这两个命令。

  • 这两个命令在uboot中也是需要用相应的宏开关来打开或者关闭的。

(4)经过代码检查,发现ping命令开关宏为CONFIG_CMD_PING,而tftp命令的开关为CONFIG_CMD_NET,确认添加。

3、代码实践

  • 结果是ping和tftp命令都被识别了,但是都提示no ethernet found……网络不通。
  • 为什么不通?因为还没做初始化等移植。

4、移植网卡初始化代码

5、实验现象分析

(1)因为没有自定义的网卡初始化函数(board_eth_init或者cpu_eth_init),所以uboot启动时初始化网卡时打印:Net:   Net Initialization Skipped。

(2)eth.c中有2个很重要的全局变量

  • eth_devices:用来指向一个链表,这个链表中保存了当前系统中所有的网卡信息;
  • eth_current:eth_current指针指向当前我们正在操作的那个网卡。

(3)在linux的网卡驱动体系中,有一个数据结构(struct eth_device)用来表示(封装)一个网卡的所有信息

  • 注册一个网卡,就是要建立一个这个结构体的实例,然后填充这个实例中的各个元素,最后将这个结构体实例加入到eth_devices这个链表上,就完成了注册。
  • 网卡驱动在初始化时,必须将自己注册到系统的网卡驱动体系中(即把自己的eth_device结构体实例添加到eth_devices链表中),否则会出现“No ethernet found.”。

(4)分析当前的问题是:在305行判断eth_devices是否为NULL之前,没有去做网卡驱动的注册,所以这里为NULL,所以打印出了“No ethernet found.”

6、DM9000驱动浏览

(1)因此要在305行之前去注册网卡驱动

  • 注册网卡驱动的代码不能随便乱写,一定要遵守linux网卡驱动架构的要求。
  • 这一块的代码一般属于网卡驱动的一部分,像这里就在dm9000x.c中。

(2)dm9000x.c中的最后一个函数int dm9000_initialize(bd_t *bis),这个函数就是用来注册dm9000网卡驱动的。

7、问题修复

  • 根据之前分析uboot函数,发现前面有2个函数预留的可以用来放网卡初始化函数的,经过对比感觉board_eth_init函数稍微合适点,于是乎去添加。

8、对比和总结


十三、uboot启动内核的移植

0 0