Exynos4412 Uboot 移植 1----6
转载自http://blog.****.net/zqixiao_09/article/details/50805205
11111111111111111111111111111
Uboot 所用版本 u-boot-2013.01
u-boot-2013.01 中有上千文件,要想了解对于某款开发板,使用哪些文件、哪些文件首先执行、可执行文件占用内存的情况,最好的方法就是阅读它的Makefile。
根据顶层Readme文件的说明:
可以知道如果使用开发板board/<board_name>,就先执行“make <board_name>_config”命令进行配置,然后执行“make all”, 就可以生成如下3个文件:
U-Boot.bin:二进制可执行文件,它就是可以直接烧入eMMC中的文件。
U-Boot : ELF格式的可执行文件。
U-Boot.srec : 摩托罗拉格式的可执行文件。
对于Exynos4412开发板,这里用的其实是Fs4412,执行“make fs4412_config"、“make all"后生成的u-boot-fs4412.bin可以烧入eMMC中执行。
一、U-Boot 配置过程
1、在顶层Makefile中可以看到如下代码:
假定在u-boot-2013.01的根目录下编译,则其中的MKCONFIG 就是根目录下的mkconfig文件(mkconfig是shell脚本文件)。
%_config是GUNmake语法层,表示的是所有以".config"结尾的文件。$(@:_config=)的结果就是将“fs4412"中的“——config”去掉,结果为fs4412。
MKCONFIG 就是执行mkconfig。
所以 “make fs4412_config"实际上就是执行如下命令:
./mkconfig fs4412 arm armv7 fs4412 samsung exynos
2、mkconfig 的作用
前面已经提到,mkconfig就是一shell脚本, 具体作用如下:
a -- 解析boards.cfg fs4412相关数据
这是我们自行添加的.确定开发板名称BOARD_NAME。
b -- 针对平台作了一系列链接,创建到平台、开发板相关的头文件的链接。
c --创建顶层Makefile包含的文件 include/ config.mk
inlucde / config.mk 内容如下:
d -- 创建开发板相关的头文件inlucde/config.h
Include/config.h导出结果如下:
U-Boot 还没有类似Linux一样的可视化配置界面(比如使用 make menuconfig 来配置),要手动修改配置文件 inlucde/config/<board_name>.h 来裁剪、设置U-Boot.
配置文件中有以下两类宏。
1) 一类是选项(Options),前缀为“CONFIG”,它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。
2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-Boot 下载文件时的默认加载地址、Flash的起始地址等。
uboot 执行通过宏来判断:宏在头文件中定义。
#ifdef CONFIG_TEST
run_test();
#endif
某头文件
#define CONFIG_TEST
可以这样认为,“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来 设置更细节的参数。
二、U-Boot 的编译、链接过程
配uboot 编译通过Makefile来判断:
obj-y += xx.o xx.o 在编译时,只编译obj-y
obj-$(CONFIG_XX) = xx.o xx.o 如果CONFIG_XX为y,则此文件会被编译进u-boot.bin置完后,执行“make all” 即可编译:
找第一个目标all:
上面代码是对u-boot进行格式转换,变成二进制bin格式之后,再加一些校验与4412开如平台加密信息。
依赖u-boot:
先总结一下U-Boot 的编译流程:
a -- 首先编译 cpu /$(CPU)/start.S,对于不同的CPU,还可能编译 cpu/$(CPU)下的其他文件;
b -- 然后,对于平台/开发板相关的每个目录、每个通用目录都使用它们个字的Makefile生成相应的库;
c -- 将a、b 步骤生成的.o .a文件按照 board / $(BOARDDIR)/config.mk文件中指定的代码段起始地址、board/$(BOARDDIR)/config.mk文件中指定的代码段起始地址、board/$(BOARDDIR)/U-Boot.lds链接脚本进行链接。
d -- 第c步得到的是ELF格式的U-Boot,后面的Makefile还会将它转换成二进制格式、S-Record格式。
22222222222222222222222222
uboot启动流程分析如下:
第一阶段:
a -- 设置cpu工作模式为SVC模式
b -- 关闭中断,mmu,cache
v -- 关看门狗
d -- 初始化内存,串口
e -- 设置栈
f -- 代码自搬移
g -- 清bss
h -- 跳c
第二阶段
a -- 初始化外设,进入超循环
b -- 超循环处理用户命令
可见, U-Boot 属于两阶段的Bootloader
第一阶段的文件:
arch/arm/cpu/armv7 /start.S 平台相关,CPU工作模式设为SVC模式,关MMU,关icahce(CPU相关)
board/samsung/fs4412/lowlevel_init.S 开发板相关:关看门狗,内存初始化,时钟初始化,串口初始化(board相关,初始化最基本设备)
第二阶段的文件:
arch/arm/lib/crt0.S _main 函数所在处,初始化SP,为C语言准备,代码重定位,清BSS,设置R0 R1 R2 R8相应寄存器
arch/arm/lib/board.c board_init_f 函数 ,填充GD结构体,初始化外设, main_loop()函数超循环
arch/arm/cpu/armv7 /start.S 代码自搬移时会用到
针对uboot2013启动流程图如下:
下面是具体分析:
一、U-Boot 第一阶段代码分析
通常我们通过连接文件知晓程序入口点,入口查看 u-boot.lds
通过链接脚本可知入口为_start,位于arch/arm/cpu/armv7/start.o。
第一阶段开始:
1、进入arch/arm/cpu/armv7/start.S
a -- 异常向量表设置
b -- 设置CPU处于SVC工作模式
d -- 协处理器 p15 的 c12 寄存器来重新定位
e、Bl cpu_init_cp15(使分支预测无效,数据)
关闭数据预取功能;
DSB:多核CPU对数据处理指令
ISB:流水线清空指令;
关闭MMU,使能I-cache
NOTE:
分支预测:在流水线里,会将后面的代码优先加载到处理器中,由于是循环,会使后面加载的代码无效,故出现了分支预测技术。(统计跳的次数来选择装载循环的代码还是下面的代码)。
f、Bl cpu_init_crit
2、跳到Low_level_init,位于board/samsung/fs4412/lowlevel_init.S
a、关闭看门狗
b、比较当前pc指针域TEXT_BASE的高8位是否一样来判断,当前代码是否在内存中
c、对系统时钟初始化
d、对内存初始化
e、对串口初始化
结束后返回 start.S
第一阶段结束,总结如下:
1 前面总结过的部分,初始化异常向量表,设置svc模式
2 配置cp15,初始化mmu cache tlb
3 板级初始化,clk,memory,uart初始化
二、第二阶段开始:
按"CTRL + ] ", 发现 _main 在两处有定义:
这里我们选择第一个Bl _main ,跳转到arch/arm/lib/crt0.S
1、初始c运行环境(看注释就知道,初始化C运行环境,并调用board_init_f 函数)
功能:
初始化sp ,为支持c语言做准备;
保存128B 放GD结构体,存放全局信息,GD的地址存放在r8中;
跳转到 board_init_f 函数,其在arch/arm/lib/board.c 处定义;
2、跳转到arch/arm/lib/board.c
功能:
对全局信息GD结构体进行填充:
291行:mon_len 通过链接脚本可以知道存放的是uboot代码大小;
294行:fdt_blob 存放设备数地址;
303行:循环执行init_fnc_t数组的函数,作硬件初始化;
a -- init_fnc_t数组的函数定义
初始化硬件
b -- Dram_init初始化成功之后,剩余代码将会对sdram空间进行规划。
可以看到addr的值由CONFIG_SYS_SDRAM_BASE加上ram_size。也就是到了可用sdram的顶端。
e--继续对gd结构体填充
如果icahe 与 dcache 是打开的,就留出 64K 的空间作为 tlb 空间,最后 addr 就是tlb 地址,4K对齐。
f --填充完成将信息拷贝到内存指定位置
2 -- 继续回到 _main
按"CTRL + O"回到跳转前的函数,即 arch/arm/lib/crt0.S
功能:
将 r8 指向新的 gd 地址;
代码重定位;
对lr 的操作为了让返回时,返回的是重定位的here处
3 -- 代码自搬移
代码自搬移,防止与内核冲突,代码位于arch/arm/cpu/armv7/start.S
循环将代码搬移到指定高地址
这里只是将链接脚本中_image_copy_end到_start中的代码,其它段还没有操作。
在这里我们有疑惑就是将代码重定位到高地址,那运行的地址不就和链接地址不一样了,那运行可能不正常?这个疑惑就是.rel.dyn帮我们解决了,主要还是编译器帮我们做的工作,在链接中有如下:【参考:http://blog.****.net/skyflying2012/article/details/37660265】
4 -- 重定位到高地址之后,再次回到 _main(arch/arm/lib/crt0.S)
此时回到的是刚才的重定位的 here 处
关 icache,保证数据从SDRAM中更新,更新异常向量表,因为代码被重定位了;
清BBS;
调用board_init_r主要是对外设的初始化。
R0=gd
R1=RELOCADDR
5 -- Main_loop 函数进入超循环(arch/arm/lib/board.c)
Main_loop函数主要功能是处理环境变量,解析命令
install_auto_complete(); //安装自动补全的函数,分析如下 。
getenv(bootcmd)
bootdelay(自启动)
如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。
33333333333333333333333333333
Uboot添加自定义命令:uboot中的命令使用U_BOOT_CMD这个宏声明来注册进系统,链接脚本会把所有的cmd_tbl_t结构体放在相邻的地方。
UBoot版本:u-boot-2013.01
一、U-Boot命令的格式
即使是内核的启动,也是通过U-Boot命令来实现的。U-Boot中每个命令都是通过 U_BOOT_CMD 宏来定义的,格式如下:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各项参数的意义如下:
1) -- name:命令的名字,注意,它不是一个字符串(不要用双引号括起来);
2)-- maxargs:最大的参数个数;
3)-- repeatable:命令是否可以重复,可重复是指运行一个命令后,下次敲回车即可再次运行;
4)-- command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *, int, int, char *[]);
5) -- usage:简单的使用说明,这是个字符串;
6)-- help:较详细的使用说明,这是个字符串。
宏U_BOOT_CMD 在include/command.h中定义,如下所示
而U_BOOT_CMD 是用一个struct cmd_tbl_s 结构体定义,这个结构体仍是在include/command.h中实现:
可以看出,对于每个使用U_BOOT_CMD 宏来定义的命令,就是宏 U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)将struct cmd_tbl_s这样的一个命令结构体放到U-BOOT连接脚本 board/xxx/u-boot.lds中定义的".u-boot_cmd"段所在的内存区域,即在".u_boot_list.cmd"段中定义一个 cmd_tbl_t 结构。
连接脚本U-Boot.lds中有如下代码:
当用户在u-boot的shell中输入命令时,就会在".u_boot_list.cmd"这个内存区域中查找( _u_boot_list_cmd__start - _u_boot_list_cmd__end),当该区域中某一个cmd_tbl_s命令结构体的cmd_tbl_s.name和输入的命令字符串相符时,就调用该命令结构体的cmd_tbl_s.cmd()函数。
二、添加自定义命令
自定义命令设为"myubootcmd",不可与u-boot命令重名,
1、添加命令行配置信息
在u-boot-2013.01/include/configs/fs4412.h(由具体开发板来配置,这里使用fs4412)中添加 #define CONFIG_CMD_MYUBOOT,如下:
2、编写命令行对应的源程序
在u-boot-2013.01/common/目录下,建立相应的命令执行文件cmd_hello.c
注意命名的规范,必须是cmd_xxx.c才行。里面的内容也是有格式要求的,如函数的格式,必须指定参数的;还有相应结尾部分的U_BOOT_CMD定义部分,使不能缺省的。如果命令不需要跟参数,则把maxargs设置为1即可了。内容如下所示:
[cpp] view plain copy
- #include <common.h>
- #include <command.h>
- #ifdef CONFIG_CMD_HELLO
- void helloword (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
- {
- printf("hello world by xiaoqiang! \n");
- }
- U_BOOT_CMD(hello,1,2,helloword,
- "hello command","xiaoqing add hello command!\n");
- #endif
3、修改Makefile
在common/Makefile中增加一项.
4、编译
5、测试
444444444444444444444444444
bootloader 要想启动内核,可以直接跳到内核的第一个指令处,即内核的起始地址,这样便可以完成内核的启动工作了。但是要想启动内核还需要满足一些条件,如下所示:
1、cpu 寄存器设置
* R0 = 0
* R1 = 机器类型 id
* R2 = 启动参数在内存中的起始地址
2、cpu 模式
* 禁止所有中断
* 必须为SVC(超级用户)模式
3、Cache、MMU
* 关闭 MMU
* 指令Cache可以开启或者关闭
* 数据Cache必须关闭
4、设备
* DMA 设备应当停止工作
5、PC为内核的起始地址
这些需求都由 boot loader 实现,在常用的 uboot 中完成一系列的初始化后最后通过 bootm 命令加载 linux 内核。bootm 向将内核映像从各种媒介中读出,存放在指定的位置;然后设置标记列表给内核传递参数;最后跳到内核的入口点去执行。
Uboot版本:u-boot-2013.01
一、bootm命令用法介绍如下:
在 common/cmd_bootm.c 中可以看到bootm 的定义:
可以看到 bootm 命令使调用了do_bootm 函数。
do_bootm 函数
在cmd_bootm.c 第586行可以看到do_bootm函数的定义(为方便阅读,对其中一些代码进行了删减,完整代码请阅读uboot源码):
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong iflag;
ulong load_end = 0;
int ret;
boot_os_fn *boot_fn;
if (bootm_start(cmdtp, flag, argc, argv))// 获取镜像信息
return 1;
iflag = disable_interrupts(); // 关闭中断
usb_stop();// 关闭usb设备
ret = bootm_load_os(images.os, &load_end, 1);//加载内核
lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));
if (images.os.type == IH_TYPE_STANDALONE) {//如有需要,关闭内核的串口
if (iflag)
enable_interrupts();
/* This may return when 'autostart' is 'no' */
bootm_start_standalone(iflag, argc, argv);
return 0;
}
boot_fn = boot_os[images.os.os];//获取启动参数
arch_preboot_os();//启动前准备
boot_fn(0, argc, argv, &images);//启动,不再返回
#ifdef DEBUG
puts("\n## Control returned to monitor - resetting...\n");
#endif
do_reset(cmdtp, flag, argc, argv);
return 1;
}
该函数的实现分为 3 个部分:
a -- 首先通过 bootm_start 函数分析镜像的信息;
b -- 如果满足判定条件则进入 bootm_load_os 函数进行加载;
c -- 加载完成后就可以调用 boot_fn 开始启动。
1、bootm_start
在cmd_bootm.c 第193行可以看到bootm_start函数的定义, 主要作用是填充内核相关信息
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
void *os_hdr;
int ret;
memset((void *)&images, 0, sizeof(images));
images.verify = getenv_yesno("verify");//获取环境变量
boot_start_lmb(&images);
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
/*获取镜像头,加载地址,长度 */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
if (images.os.image_len == 0) {
puts("ERROR: can't get kernel image!\n");
return 1;
}
/*获取镜像参数*/
switch (genimg_get_format(os_hdr)) {
case IMAGE_FORMAT_LEGACY:
images.os.type = image_get_type(os_hdr);//镜像类型
images.os.comp = image_get_comp(os_hdr);//压缩类型
images.os.os = image_get_os(os_hdr);//系统类型
images.os.end = image_get_image_end(os_hdr);//镜像结束地址
images.os.load = image_get_load(os_hdr);/加载地址
break;
/* 查询内核入口地址*/
if (images.legacy_hdr_valid) {
images.ep = image_get_ep(&images.legacy_hdr_os_copy);
} else {
puts("Could not find kernel entry point!\n");
return 1;
}
if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
if (((images.os.type == IH_TYPE_KERNEL) ||
(images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
(images.os.type == IH_TYPE_MULTI)) &&
(images.os.os == IH_OS_LINUX)) {
/* 查询是否存在虚拟磁盘 */
ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end);
if (ret) {
puts("Ramdisk image is corrupt or invalid\n");
return 1;
}
#if defined(CONFIG_OF_LIBFDT)
/* 找到设备树,设备树是linux 3.XX版本特有的 */
ret = boot_get_fdt(flag, argc, argv, &images,
&images.ft_addr, &images.ft_len);
if (ret) {
puts("Could not find a valid device tree\n");
return 1;
}
set_working_fdt_addr(images.ft_addr);
#endif
}
images.os.start = (ulong)os_hdr;//赋值加载地址
images.state = BOOTM_STATE_START;//更新状态
return 0;
}
该函数主要进行镜像的有效性判定、校验、计算入口地址等操作,大部分工作通过 boot_get_kernel -> image_get_kernel 完成。
2、bootm_load_os
在cmd_bootm.c 第317行可以看到bootm_load_os函数的定义, 这个函数主要判断镜像是否需要解压,并且将镜像移动到加载地址:
static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress)
{
uint8_t comp = os.comp; /* 压缩格式 */
ulong load = os.load; /* 加载地址 */
ulong blob_start = os.start; /* 镜像起始地址 */
ulong blob_end = os.end; /* 镜像结束地址 */
ulong image_start = os.image_start; /* 镜像起始地址 */
ulong image_len = os.image_len; /* 镜像长度 */
uint unc_len = CONFIG_SYS_BOOTM_LEN; /* 镜像最大长度 */
const char *type_name = genimg_get_type_name (os.type); /* 镜像类型 */
switch (comp) { /* 选择解压格式 */
case IH_COMP_NONE: /* 镜像没有压缩过 */
if (load == blob_start) { /* 判断是否需要移动镜像 */
printf (" XIP %s ... ", type_name);
} else {
printf (" Loading %s ... ", type_name);
if (load != image_start) {
memmove_wd ((void *)load, (void *)image_start, image_len, CHUNKSZ);
}
}
*load_end = load + image_len;
puts("OK\n");
break;
case IH_COMP_GZIP: /* 镜像采用 gzip 解压 */
printf (" Uncompressing %s ... ", type_name);
if (gunzip ((void *)load, unc_len, (uchar *)image_start, &image_len) != 0) { /* 解压 */
puts ("GUNZIP: uncompress, out-of-mem or overwrite error "
"- must RESET board to recover\n");
return BOOTM_ERR_RESET;
}
*load_end = load + image_len;
break;
...
default:
printf ("Unimplemented compression type %d\n", comp);
return BOOTM_ERR_UNIMPLEMENTED;
}
puts ("OK\n");
debug (" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);
if ((load < blob_end) && (*load_end > blob_start)) {
debug ("images.os.start = 0x%lX, images.os.end = 0x%lx\n", blob_start, blob_end);
debug ("images.os.load = 0x%lx, load_end = 0x%lx\n", load, *load_end);
return BOOTM_ERR_OVERLAP;
}
return 0;
}
3、do_bootm_linux
在bootm_load_os 执行结束后,回到do_bootm 函数,调用boot_fn 运行linux 内核;
boot_os 为函数指针数组,在cmd_bootm.c 136行有定义
可以看出 boot_fn 函数指针指向的函数是位于 arch/arm/lib/bootm.c的 do_bootm_linux,这是内核启动前最后的一个函数,该函数主要完成启动参数的初始化,并将板子设定为满足内核启动的环境,代码如下:
可以看到 do_bootm_linux 实际调用的是 boot_jump_linux 函数。
4、boot_jump_linux
在arch/arm/lib/bootm.c 下第326行有定义
/* Subcommand: GO */
static void boot_jump_linux(bootm_headers_t *images)
{
unsigned long machid = gd->bd->bi_arch_number;//获取机器码
char *s;
void (*kernel_entry)(int zero, int arch, uint params);//内核入口函数
unsigned long r2;
kernel_entry = (void (*)(int, int, uint))images->ep;
s = getenv("machid");//从环境变量中获取机器码
if (s) {
strict_strtoul(s, 16, &machid);
printf("Using machid 0x%lx from environment\n", machid);
}
debug("## Transferring control to Linux (at address %08lx)" \
"...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup();
#ifdef CONFIG_OF_LIBFDT
if (images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
#endif
r2 = gd->bd->bi_boot_params;//将启动参数地址赋给 r2
kernel_entry(0, machid, r2);
}
kernel_entry(0, machid, r2)
真正将控制权交给内核, 启动内核;
满足arm架构linux内核启动时的寄存器设置条件:第一个参数为0 ;第二个参数为板子id需与内核中的id匹配,第三个参数为启动参数地址 。
二、为内核设置启动参数
Uboot 也是通过标记列表向内核传递参数,标记在源代码中定义为tag,是一个结构体,在 arch/arm/include/asm/setup.h 中定义。
tag_header 结构体定义如下:
在一些内存标记、命令行标记的示例代码就是取自Uboot 中的 setup_memory_tags、setup_commandline_tag函数,他们都是在 arch/arm/lib/bootm.c中定义。
#if defined(CONFIG_SETUP_MEMORY_TAGS) || \
defined(CONFIG_CMDLINE_TAG) || \
defined(CONFIG_INITRD_TAG) || \
defined(CONFIG_SERIAL_TAG) || \
defined(CONFIG_REVISION_TAG)
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *)bd->bi_boot_params;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags(bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;//物理内存起始地址
params->u.mem.size = bd->bi_dram[i].size;//物理内存结束地址
params = tag_next (params);
}
}
#endif
#ifdef CONFIG_CMDLINE_TAG
static void setup_commandline_tag(bd_t *bd, char *commandline)
{
char *p;
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
#endif
一般有 setup_memory_tags、setup_commandline_tag 这两个标记就可以了,在配置文件Include/configs/fs4412.h中定义:
555555555555555555555555
Uboot 版本:u-boot-2013.01
开发板:FS_4412 平台(Exynos4412,可以根据自己的板子修改,只要是4412的过程都是一样的)
一、建立自己的平台
1、下载源码
我们可以在下面这个网站上下载最新的和以前任一版本的uboot
ftp://ftp.denx.de/pub/u-boot/
这里我们使用的是u-boot-2013.01
2、解压uboot 源码并进入目录
3、指定交叉编译工具链
a -- 打开根目录下的Makefile
b -- 添加交叉编译工具链信息
4、指定产品CPU
我们产品用的CPU 是exynos 4412
查看u-boot 源码该CPU 是否已支持
U-boot 已支持,见arch/arm/cpu/armv7/exynos/
5、指定产品BOARD
找一个最类似的board 配置修改, 这里我们参考的是board/samsung/origen/
a -- 修改board/samsung/ 板级相关文件夹,复制 board/samsung/origen/,重命名为fs4412
b -- 修改board/samsung/fs4412/Makefile信息
修改 origen.o 为 fs4412.o
c -- 修改include/configs/fs4412.h配置文件
这里同样是先复制 inlcude/configs/origen.h,生成 inlcude/configs/fs4412.h
打开fs4412.h,修改以下文件:
将原来的#define CONFIG_SYS_PROMPT "ORIGEN #" 改成:
将原来的#define CONFIG_IDENT_STRING for ORIGEN 改成
d -- 配置boards.cfg
打开uboot根目录下的boards.cfg,在origen后新增
6、编译u-boot
$ make distclean
$ make fs4412_config (可以在inlcude/config.mk及include/config.h下看到配置好的信息)
$ make
编译完成后生成的u-boot.bin 就是可执行的镜像文件。
烧写uboot 命令:
tftp 41000000 u-boot.bin
movi write uboot 41000000
但是该文件还不能在我们板子上运行,我们需要对u-boot 源代码进行相应的修改。
二、实现能看到串口终端信息
1、确认第一条指令有运行到(点灯法)
a -- 在arch/arm/cpu/armv7/start.S 134 行后添加点灯程序
b -- 添加三星加密方式
exynos 需要三星提供的初始引导加密后,我们的u-boot,才能被引导运行
将sdfuse_q 目录拷贝到u-boot-2013.01 源码目录下
注:sdfuse_q 三星提供的加密处理
将CodeSign4SecureBoot 目录拷贝到u-boot-2013.01 源码目录下
注:CodeSign4SecureBoot 三星提供的安全启动方式
c -- 修改Makefile
$vim Makefile
修改实现sdfuse_q 的编译
在
下添加
注意是tab 键缩进的,否则makefile 编译报错
注意如果执行了make distclean 需重新拷贝CodeSign4SecureBoot
d -- 拷贝编译脚本
将 build.sh 拷贝到uboot 根目录下,并加上执行权限,并执行该脚本 注:build.sh 脚本方式完成自动添加加密方式,是自己编写的
编译生成所需文件u-boot_fs4412.bin
烧写新的u-boot_fs4412.bin
复位,发现灯有点亮,说明我们的u-boot 有运行到
附:build.sh脚本文件
[cpp] view plain copy
#!/bin/sh
sec_path="CodeSign4SecureBoot/"
CPU_JOB_NUM=$(grep processor /proc/cpuinfo | awk '{field=$NF};END{print field+1}')
ROOT_DIR=$(pwd)
CUR_DIR=${ROOT_DIR##*/}
case "$1" in
clean)
echo make clean
make mrproper
;;
*)
if [ ! -d $sec_path ]
then
echo "**********************************************"
echo "[ERR]please get the CodeSign4SecureBoot first"
echo "**********************************************"
return
fi
make fs4412_config
make -j$CPU_JOB_NUM
if [ ! -f checksum_bl2_14k.bin ]
then
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
echo "There are some error(s) while building uboot, please use command make to check."
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
exit 0
fi
cp -rf checksum_bl2_14k.bin $sec_path
cp -rf u-boot.bin $sec_path
rm checksum_bl2_14k.bin
cd $sec_path
cat E4412_N.bl1.SCP2G.bin bl2.bin all00_padding.bin u-boot.bin tzsw_SMDK4412_SCP_2GB.bin > u-boot-fs4412.bin
mv u-boot-fs4412.bin $ROOT_DIR
rm checksum_bl2_14k.bin
rm u-boot.bin
echo
echo
;;
esac
2、实现串口输出
修改lowlevel_init.S 文件
$vim board/samsung/fs4412/lowlevel_init.S
a -- 添加临时栈
在
lowlevel_init:
后添加
b -- 添加关闭看门狗代码
在
beq wakeup_reset
后添加
c -- 添加串口初始化代码
在uart_asm_init: 的
str r1, [r0, #EXYNOS4_GPIO_A1_CON_OFFSET]
后添加
注释掉trustzone 初始化
注释掉
bl uart_asm_init
下的
bl tzpc_init
重新编译u-boot
$ ./build.sh
烧写新的u-boot_fs4412.bin
复位会看到串口信息
三、网卡移植
1、添加网络初始化代码
$vim board/samsung/fs4412/fs4412.c
在struct exynos4_gpio_part2 *gpio2; 后添加
[cpp] view plain copy
#ifdef CONFIG_DRIVER_DM9000
#define EXYNOS4412_SROMC_BASE 0X12570000
#define DM9000_Tacs (0x1) // 0clk address set-up
#define DM9000_Tcos (0x1) // 4clk chip selection set-up
#define DM9000_Tacc (0x5) // 14clk access cycle
#define DM9000_Tcoh (0x1) // 1clk chip selection hold
#define DM9000_Tah (0xC) // 4clk address holding time
#define DM9000_Tacp (0x9) // 6clk page mode access cycle
#define DM9000_PMC (0x1) // normal(1data)page mode configuration
struct exynos_sromc {
unsigned int bw;
unsigned int bc[6];
};
/*
* s5p_config_sromc() - select the proper SROMC Bank and configure the
* band width control and bank control registers
* srom_bank - SROM
* srom_bw_conf - SMC Band witdh reg configuration value
* srom_bc_conf - SMC Bank Control reg configuration value
*/
void exynos_config_sromc(u32 srom_bank, u32 srom_bw_conf, u32 srom_bc_conf)
{
unsigned int tmp;
struct exynos_sromc *srom = (struct exynos_sromc *)(EXYNOS4412_SROMC_BASE);
/* Configure SMC_BW register to handle proper SROMC
* bank */
tmp = srom->bw;
tmp &= ~(0xF << (srom_bank * 4));
tmp |= srom_bw_conf;
srom->bw = tmp;
/* Configure SMC_BC
* register */
srom->bc[srom_bank] = srom_bc_conf;
}
static void dm9000aep_pre_init(void)
{
unsigned int tmp;
unsigned char smc_bank_num = 1;
unsigned int smc_bw_conf=0;
unsigned int smc_bc_conf=0;
/* gpio configuration */
writel(0x00220020, 0x11000000 + 0x120);
writel(0x00002222, 0x11000000 + 0x140);
/* 16 Bit bus width */
writel(0x22222222, 0x11000000 + 0x180);
writel(0x0000FFFF, 0x11000000 + 0x188);
writel(0x22222222, 0x11000000 + 0x1C0);
writel(0x0000FFFF, 0x11000000 + 0x1C8);
writel(0x22222222, 0x11000000 + 0x1E0);
writel(0x0000FFFF, 0x11000000 + 0x1E8);
smc_bw_conf &= ~(0xf<<4);
smc_bw_conf |= (1<<7) | (1<<6) | (1<<5) | (1<<4);
smc_bc_conf = ((DM9000_Tacs << 28)
| (DM9000_Tcos << 24)
| (DM9000_Tacc << 16)
| (DM9000_Tcoh << 12)
| (DM9000_Tah << 8)
| (DM9000_Tacp << 4)
| (DM9000_PMC));
exynos_config_sromc(smc_bank_num,smc_bw_conf,smc_bc_conf);
}
#endif
在gd->bd->bi_boot_params = (PHYS_SDRAM_1 + 0x100UL); 后添加
[cpp] view plain copy
#ifdef CONFIG_DRIVER_DM9000
dm9000aep_pre_init();
#endif
在文件末尾添加
[cpp] view plain copy
#ifdef CONFIG_CMD_NET
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_DRIVER_DM9000
rc = dm9000_initialize(bis);
#endif
return rc;
}
#endif
2、修改配置文件添加网络相关配置
$ vim include/configs/fs4412.h
修改
#undef CONFIG_CMD_PING
为
#def ine CONFIG_CMD_PING
修改
#undef CONFIG_CMD_NET
为
#def ine CONFIG_CMD_NET
在文件末尾
#endif /* __CONFIG_H */
前面添加
3、重新编译u-boot
$ ./build.sh
烧写新的u-boot_fs4412.bin
复位后
# ping 192.168.9.120
四、FLASH 移植(EMMC)
1、初始化EMMC
$cp movi.c arch/arm/cpu/armv7/exynos/
$vim arch/arm/cpu/armv7/exynos/Makefile
在pinmux.o 后添加movi.o
修改板级文件
$vim board/samsung/fs4412/fs4412.c
在
#include <asm/arch/mmc.h>
后面添加
#include <asm/arch/clk.h>
#include "origen_setup.h"
在
#ifdef CONFIG_GENERIC_MMC
后面添加
[cpp] view plain copy
u32 sclk_mmc4; /*clock source for emmc controller*/
#define __REGMY(x) (*((volatile u32 *)(x)))
#define CLK_SRC_FSYS __REGMY(EXYNOS4_CLOCK_BASE + CLK_SRC_FSYS_OFFSET)
#define CLK_DIV_FSYS3 __REGMY(EXYNOS4_CLOCK_BASE + CLK_DIV_FSYS3_OFFSET)
int emmc_init()
{
u32 tmp;
u32 clock;
u32 i;
/* setup_hsmmc_clock */
/* MMC4 clock src = SCLKMPLL */
tmp = CLK_SRC_FSYS & ~(0x000f0000);
CLK_SRC_FSYS = tmp | 0x00060000;
/* MMC4 clock div */
tmp = CLK_DIV_FSYS3 & ~(0x0000ff0f);
clock = get_pll_clk(MPLL)/1000000;
for(i=0 ; i<=0xf; i++) {
sclk_mmc4=(clock/(i+1));
if(sclk_mmc4 <= 160) //200
{
CLK_DIV_FSYS3 = tmp | (i<<0);
break;
}
}
emmcdbg("[mjdbg] sclk_mmc4:%d MHZ; mmc_ratio: %d\n",sclk_mmc4,i);
sclk_mmc4 *= 1000000;
/*
* MMC4 EMMC GPIO CONFIG
*
* GPK0[0] SD_4_CLK
* GPK0[1] SD_4_CMD
* GPK0[2] SD_4_CDn
* GPK0[3:6] SD_4_DATA[0:3]
*/
writel(readl(0x11000048)&~(0xf),0x11000048); //SD_4_CLK/SD_4_CMD pull-down enable
writel(readl(0x11000040)&~(0xff),0x11000040);//cdn set to be output
writel(readl(0x11000048)&~(3<<4),0x11000048); //cdn pull-down disable
writel(readl(0x11000044)&~(1<<2),0x11000044); //cdn output 0 to shutdown the emmc power
writel(readl(0x11000040)&~(0xf<<8)|(1<<8),0x11000040);//cdn set to be output
udelay(100*1000);
writel(readl(0x11000044)|(1<<2),0x11000044); //cdn output 1
writel(0x03333133, 0x11000040);
writel(0x00003FF0, 0x11000048);
writel(0x00002AAA, 0x1100004C);
#ifdef CONFIG_EMMC_8Bit
writel(0x04444000, 0x11000060);
writel(0x00003FC0, 0x11000068);
writel(0x00002AAA, 0x1100006C);
#endif
#ifdef USE_MMC4
smdk_s5p_mshc_init();
#endif
}
将int board_mmc_init(bd_t *bis)函数内容改写为
[cpp] view plain copy
int board_mmc_init(bd_t *bis)
{
int i, err;
#ifdef CONFIG_EMMC
err = emmc_init();
#endif
return err;
}
在末尾添加
[cpp] view plain copy
#ifdef CONFIG_BOARD_LATE_INIT
#include <movi.h>
int chk_bootdev(void)//mj for boot device check
{
char run_cmd[100];
struct mmc *mmc;
int boot_dev = 0;
int cmp_off = 0x10;
ulong start_blk, blkcnt;
mmc = find_mmc_device(0);
if (mmc == NULL)
{
printf("There is no eMMC card, Booting device is SD card\n");
boot_dev = 1;
return boot_dev;
}
start_blk = (24*1024/MOVI_BLKSIZE);
blkcnt = 0x10;
sprintf(run_cmd,"emmc open 0");
run_command(run_cmd, 0);
sprintf(run_cmd,"mmc read 0 %lx %lx %lx",CFG_PHY_KERNEL_BASE,start_blk,blkcnt);
run_command(run_cmd, 0);
sprintf(run_cmd,"emmc close 0");
run_command(run_cmd, 0);
return 0;
}
2、添加相关命令
$ cp cmd_movi.c common/
$ cp cmd_mmc.c common/
$ cp cmd_mmc_fdisk.c common/
修改Makefile
$ vim common/Makefile
在
COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o
后添加
COBJS-$(CONFIG_CMD_MMC) += cmd_mmc_fdisk.o
COBJS-$(CONFIG_CMD_MOVINAND) += cmd_movi.o
添加驱动
$ cp mmc.c drivers/mmc/
$ cp s5p_mshc.c drivers/mmc/
$ cp mmc.h include/
$ cp movi.h include/
$ cp s5p_mshc.h include/
修改Makefile
$vim drivers/mmc/Makefile
添加
COBJS-$(CONFIG_S5P_MSHC) += s5p_mshc.o
3、添加EMMC 相关配置
$vim include/configs/fs4412.h
添加
[cpp] view plain copy
#define CONFIG_EVT1 1 /* EVT1 */
#ifdef CONFIG_EVT1
#define CONFIG_EMMC44_CH4 //eMMC44_CH4 (OMPIN[5:1] = 4)
#ifdef CONFIG_SDMMC_CH2
#define CONFIG_S3C_HSMMC
#undef DEBUG_S3C_HSMMC
#define USE_MMC2
#endif
#ifdef CONFIG_EMMC44_CH4
#define CONFIG_S5P_MSHC
#define CONFIG_EMMC 1
#define USE_MMC4
/* #define CONFIG_EMMC_8Bit */
#define CONFIG_EMMC_EMERGENCY
/*
* #define emmcdbg(fmt,args...) printf(fmt ,##args)
* #define emmcdbg(fmt,args...)
*/
#define emmcdbg(fmt,args...)
#endif
#endif /*end CONFIG_EVT1*/
#define CONFIG_CMD_MOVINAND
#define CONFIG_CLK_1000_400_200
#define CFG_PHY_UBOOT_BASE CONFIG_SYS_SDRAM_BASE + 0x3e00000
#define CFG_PHY_KERNEL_BASE CONFIG_SYS_SDRAM_BASE + 0x8000
#define BOOT_MMCSD 0x3
#define BOOT_EMMC43 0x6
#define BOOT_EMMC441 0x7
#define CONFIG_BOARD_LATE_INIT
4、重新编译u-boot
$ ./build.sh
烧写新的u-boot_fs4412.bin
复位后
# mmcinfo
---------------------
6666666666666666666666666666666666666
一、gd结构体的定义与使用
gd_t 和 bd_t 是u-boot中两个重要的数据结构,在初始化操作很多都要靠这两个数据结构来保存或传递。
gd_t 定义在/u-boot-2013.01/arch/arm/include/asm/global_data.h
bd_t 定义在 ./include/asm-arm/u-boot.h
1、gd_t : global data数据结构定义
位于文件/u-boot-2013.01/arch/arm/include/asm/global_data.h 中。其成员主要是一些全局的系统初始化参数。
当使用gd_t 时需用宏定义进行声明DECLARE_GLOBAL_DATA_PTR
从这个宏的定义可以看出,gd是一个保存在ARM的r8寄存器中的gd_t结构体的指针。指定占用寄存器R8。
[cpp] view plain copy
- typedef struct global_data {
- bd_t *bd;//struct board_info指针,保存开发板信息
- unsigned long flags;//指示标志,如设备已经初始化标志等
- unsigned int baudrate;//串口波特率
- unsigned long have_console; //串口初始化标志
- #ifdef CONFIG_PRE_CONSOLE_BUFFER
- unsigned long precon_buf_idx; /* Pre-Console buffer index */
- #endif
- unsigned long env_addr; /* Address of Environment struct */
- unsigned long env_valid; /* Checksum of Environment valid? */
- unsigned long fb_base; /* base address of frame buffer */
- #ifdef CONFIG_FSL_ESDHC
- unsigned long sdhc_clk;
- #endif
- #ifdef CONFIG_AT91FAMILY
- /* "static data" needed by at91's clock.c */
- unsigned long cpu_clk_rate_hz;
- unsigned long main_clk_rate_hz;
- unsigned long mck_rate_hz;
- unsigned long plla_rate_hz;
- unsigned long pllb_rate_hz;
- unsigned long at91_pllb_usb_init;
- #endif
- #ifdef CONFIG_ARM
- /* "static data" needed by most of timer.c on ARM platforms */
- unsigned long timer_rate_hz;
- unsigned long tbl;
- unsigned long tbu;
- unsigned long long timer_reset_value;
- unsigned long lastinc;
- #endif
- #ifdef CONFIG_IXP425
- unsigned long timestamp;
- #endif
- unsigned long relocaddr; /* Start address of U-Boot in RAM */
- phys_size_t ram_size; /* RAM size */
- unsigned long mon_len; /* monitor len */
- unsigned long irq_sp; /* irq stack pointer */
- unsigned long start_addr_sp; /* start_addr_stackpointer */
- unsigned long reloc_off;
- #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
- unsigned long tlb_addr;
- unsigned long tlb_size;
- #endif
- const void *fdt_blob; /* Our device tree, NULL if none */
- void **jt; /* jump table */
- char env_buf[32]; /* buffer for getenv() before reloc. */
- #if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER)
- unsigned long post_log_word; /* Record POST activities */
- unsigned long post_log_res; /* success of POST test */
- unsigned long post_init_f_time; /* When post_init_f started */
- #endif
- } gd_t;
2.、bd_t :board info数据结构定义
位于文件u-boot-2013.01/arch/arm/include/asm/u-boot.h。保存板子参数。
[cpp] view plain copy
- typedef struct bd_info {
- unsigned int bi_baudrate; /* 串口波特率 */
- ulong bi_arch_number; /* 开发板机器ID */
- ulong bi_boot_params; /* 启动参数 */
- unsigned long bi_arm_freq; /* arm frequency */
- unsigned long bi_dsp_freq; /* dsp core frequency */
- unsigned long bi_ddr_freq; /* ddr frequency */
- struct /* RAM configuration */
- {
- ulong start;
- ulong size;
- } bi_dram[CONFIG_NR_DRAM_BANKS];
- } bd_t;