首页 > 代码库 > bootloader
bootloader
[Makefile]
[1] 规则
目标: 依赖
[TAB]命令(命令名 参数 依赖 目标)
[2] 难点
1. 自动变量
作用域在一个规则中, 如: $@(目标, 每条规则都只有一个目标), $<(第一个依赖), $^(所有依赖)
例:
hello.o world.o: hello.h
等价于
hello.o: hello.h
world.o: hello.h
2. 模式规则
%.o: %.c
[TAB]$(CC) $(CFLAGS) -c -o $@ $<
3. 自动推倒
要生成的目标没有显示的规则,这时会去找通用规则(模式规则),找通用规则的过程叫自动推导
寻找规则的过程:
1. 在Makefile中,寻找生成hello.o的规则,如果找到...
如:
hello.o: hello.c
2. 在Makefile中, 寻找模式规则, 如果找到匹配的模式规则, 需要查看当前目录下是否有模式规则需要的依赖文件
如果有,则使用模式规则生成目标,如果没有继续寻找下一个模式规则...
如:
%.o: %.c
%.o: %.S
3. 在Make程序内部,寻找模式规则(隐式规则),生成目标
[ARM工程]
[1] 目录
board 跟主板相关的配置和源码代码(主板的初始化代码)
common 主程序源码及配置
cpu 启动代码及配置
drivers 主板无关的设备驱动及配置
include 头文件
lib 库源码及配置
[2] 源文件
*.c C语言源代码
*.S 带预处理命令的汇编源代码
map.lds 链接脚本
Makefile 编译规则
config.mk 编译命令及编译参数(是Makefile的一部分), 会影响编译命令和参数的硬件目录,都需要config.mk
[3] 目标文件
fsc100 ELF格式的可执行文件
fsc100.bin Binary格式的可执行文件
fsc100.dis ELF格式的可执行文件反汇编文件
fsc100.map 详细的符号表文件
System.map 符号表文件
[4] 工程讲解(Makefile和config.mk)
Makefile和config.mk编译源代码为可执行文件
注意:每个模块打包成静态库的好处:
1. 便于管理
2. 链接成可执行程序时,没有被启动代码调用的代码不会被链接到可执行文件中
[5] 如何添加驱动模块?
1. 添加源代码
2. 添加Makefile
拷贝别的目录下的Makefile,修改下列变量的值:
LIB 最终生成的*.a
SOBJS *.S源代码编译出来的*.o
COBJS *.c源代码编译出来的*.o
START 启动代码生成的*.o
3. 修改顶层目录下的Makefile,添加新目录生成的目标文件到工程
OBJS 启动代码的目标文件(*.o)
LIBS 非启动代码的目标文件(*.a)
[u-boot]
[1] 目录
cpu(cpu/名称/...) 启动代码、cpu内部驱动和配置
board(board/芯片厂家/主板名/...) 启动代码中要使用的硬件的驱动代码,主板的初始化代码
common 命令输入、解析和执行代码
net 网络协议
drivers 通用硬件设备驱动
fs 文件系统源码
disk 磁盘分区驱动
lib_arm arm架构的cpu都需要的代码,主板的初始化代码和库代码
tools 工具软件源码
doc 作者写的源代码说明
README 简单的u-boot介绍、目录树说明、配置项说明
[2] 文件
1. 源文件
*.c/*.S/*.h 源代码文件
Makefile 编译规则(有源代码的目录下都会有)
config.mk 编译命令和编译参数(cpu、board、lib_cpu架构、nand_spl)
*.lds 链接脚本
rules.mk 产生*.c(*.S): xxx.h ...
mkconfig 配置的shell脚本
见《文件作用表.bmp》
2. 目标文件
u-boot ELF可执行文件
u-boot.bin Binary可执行文件
u-boot.map 详细的符号表
System.map 符号表
[3] 特点
1. 支持很多CPU
2. 支持很多主板
3. 支持很多通用硬件
4. 工程管理和代码比较混乱
何一款嵌入式产品,都不会用到所有的源代码,所以用u-boot的源码生成目标文件的步骤:
1. 选择需要的源代码
2. 编译选取的源代码
[4] 编译(必须掌握)
1. 配置--根据主板的硬件配置和需要的命令,选取需要的源代码
# make fsc100(主板名)_config
2. 编译--编译选取的源代码
# make
3. 清除编译
# make clean
4. 清除配置
# make clobber
[5] 配置原理
1. 过程
见《u-boot配置流程图》
2. 结果
include/config.mk 选取源代码所在的目录
include/config.h 选取源代码
include/configs/fsc100.h 主板的配置项
include/config_cmd_default.h 编译到u-boot可执行程序的命令的配置项
(include/config_cmd_all.h u-boot源码中支持的所有命令的配置项)
3. 原理
(1) 配置命令
# make fsc100_config
命令原理
fsc100_config: unconfig
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 fsc100 samsung s5pc1xx
@mkconfig fsc100(主板名) arm(架构) arm_cortexa8(cpu名) fsc100 samsung(芯片厂家) s5pc1xx(SOC名)
(2) 配置项原理
1. 控制源代码文件是否编译到u-boot中的配置项--选取文件
例: 在include/configs/fsc100.h定义:
#define CONFIG_DRIVER_DM9000 1 (顶层目录下的Makefile会转换CONFIG_DRIVER_DM9000宏定义为一个Makefile变量)
1 (CONFIG_DRIVER_DM9000 = y)
0 (CONFIG_DRIVER_DM9000 = 0)
宏定义(include/configs/fsc100.h) 被转化为(include/autoconf.mk)
#define CONFIG_DRIVER_DM9000 1 CONFIG_DRIVER_DM9000 = y
Makefile
config.mk
include/autoconf.mk
在drivers/net/Makefile中引用
CONFIG_DRIVER_DM9000=y (include/autoconf.mk)
COBJS-$(CONFIG_DRIVER_DM9000) += dm9000x.o
...
COBJS := $(COBJS-y)
OBJS := $(COBJS)
$(LIBS): .depend $(OBJS)
$(AR) ...
研究方法:
1. 查找CONFIG_DRIVER_DM9000
grep CONFIG_DRIVER_DM9000 * -r
在autoconf.mk文件中发现"CONFIG_DRIVER_DM9000=y”c
2. 找出autoconf.mk文件生成方法
grep autoconf.mk * -r
在Makefile中找到生成autoconf.mk的规则
3. 查看生成autoconf.mk的规则
2. 条件编译原理选取源代码--选取文件中的一部分代码
例: 在include/configs/fsc100.h定义:
#define CONFIG_CMD_PING 1
3. 提供数据给源代码的配置项
宏替换原理
例: 在include/configs/fsc100.h定义:
#define CONFIG_DM9000_BASE 0x88000000
在drivers/net/dm9000x.c里面引用
[6] 编译原理
1. 修改代码段的运行地址
(1) board/$(VENDOR)/$(BOARD)/config.mk
TEXT_BASE = 代码段运行地址
(2) 链接脚本
不推荐
2. 子目录下的Makefile
拷贝别的目录下的Makefile,修改下列变量的值:
LIB 最终生成的*.a
SOBJS *.S源代码编译出来的*.o
COBJS *.c源代码编译出来的*.o
START 启动代码生成的*.o
3. 顶层目录的Makefile
OBJS 启动代码的目标文件(也可以在链接脚本中实现)
LIBS 非启动代码的目标文件
[7] 如何添加模块到工程中?(必须掌握)
1. 添加源代码
例:
drivers/uart/uart.c(记得在start_armboot函数中调用这个文件中的函数,否则uart.c中的函数不会被链接到最终的可执行代码中)
2. 子目录下的Makefile
拷贝别的目录下的Makefile,修改下列变量的值:
LIB 最终生成的*.a
SOBJS *.S源代码编译出来的*.o
COBJS *.c源代码编译出来的*.o
START 启动代码生成的*.o
例:
LIB := libuart.a
SOBJS :=
COBJS := uart.o(非启动代码的*.c编译出来的目标文件)
START := uart.o(启动代码编译出来的目标文件)
3. 修改顶层目录下的Makefile,添加新目录生成的目标文件到工程
(1) 如果添加的模块是非启动代码, 添加模块的Makefile生成的目标文件到工程
(将目标文件放入顶层目录下的Makfile的LIBS变量中)
例:
LIBS += drivers/uart/libuart.a
(2) 如果添加的模块是启动代码,方法如下:
1. 添加模块的Makefile生成的目标文件到工程
(将目标文件放入顶层目录下的Makfile的OBJS变量中)
例:
OBJS += drivers/uart/uart.o
$(OBJS):... (应该是标志的u-boot做好的)
$(MAKE) -c $(dir $@) $(notdir $@)
2. 添加模块的Makefile生成的目标文件到工程
(1) 将目标文件放入顶层目录下的Makfile的OBJS(LIBS)变量中
例:
LIBS += drivers/uart/libuart.a
(2) 在链接脚本中修改链接顺序
例:
.text : (不推荐)
{
cpu/arm_cortexa8/start.o (.text)
drivers/uart/uart.o (.text)
*.o (.text)
}
[8] 源代码编辑软件
vi + ctags 查看源代码
1. 进入顶层目录,然后打开任意的文件,然后按F9新建tags索引文件
2. 查找定义: 把光标移动要查找的标识符,然后按ctrl+]
3. 回退: ctrl + o
sourceinside
建立工程,添加所有文件到工程
[9] 源代码分析
(1) 启动操作系统
第一阶段: 自启动
硬件初始化
CPU初始化
关闭中断进入SVC模式
关闭MMU和Cache
SOC初始化
如果有,需要关闭看门狗
如果需要加快代码的执行速度, 初始化PLL(clock control)
UART初始化
如果需要用到DMA,就初始化
Nandflash控制器初始化(为读驱动做准备)
主板初始化(驱动)
初始化SDRAM
驱动Nandflash读操作
软件环境建立
自拷贝到SDRAM
预留malloc动态分配区
给全局数据预留内存
栈内存分配(每种模式下都需要分配自己的栈)
.BSS段清0
跳转到SDRAM中运行
主板初始化(所有软硬件模块的初始化都需要在这里完成)
第二阶段: 命令执行阶段
接收命令输入
解析命令
执行命令
协议栈
文件系统
软件驱动
硬件驱动
第三阶段: 启动操作系统(bootm)
建立操作系统运行环境
linux内核启动前提条件:
1. 关闭中断,进入SVC
2. 关闭MMU和Cache
3. R0 0 不是强制
4. R1 arch number(machine type id) 强制
5. R2 内核参数指针(atags list) 不必须
传递参数给内核
atags list
struct传递方式(内存地址 + 0x100 BSP确定)
跳转到内核代码运行
cpu/arm_cortexa8/start.S(reset --->cpu_init_crit)
-->board/samsung/fsc100/lowlevel_init.S(lowlevel_init-->system_clock_init)
-->uart_asm_init
-->dma_init
-->nand_pin_mux
?-->board/samsung/fsc100/mem_setup.S(mem_ctrl_asm_init)
-->wakeup_reset
?-->board/samsung/fsc100/nand_cp.c(stack_setup)
-->copy_uboot_to_ram
-->board/samsung/fsc100/nand_cp.c(stack_setup)
-->lib_arm/board.c(start_armboot)
-->common/main.c(main_loop)
reset 复位异常启动的正常代码
cpu_init_crit 关闭MMU和cache
lowlevel_init 关闭看门狗、初始化SRAM接口、初始化中断控制器、初始化PLL
system_clock_init 初始化PLL
dma_init 初始化dma
nand_pin_mux 初始化nandflash的多功能管脚
nand_asm_init nandflash初始化
mem_ctrl_asm_init DDR控制器初始化
wakeup_reset 唤醒复位
stack_setup 栈初始化
start_armboot 软硬件初始化
main_loop 初始化及命令循环
(2) 关键代码理解
/*******************************************/
// 判断u-boot当前运行位置(iRAM SDRAM)
ldr r0, =0xff0fffff
r0: 0xff0fffff (20-23bit)
@清除除20-23bit以外的所有位
@u-boot只有可能运行在iRAM和SDRAM
@如果运行在iRAM中, pc的值为(0x20000 - 0x38000)
@ r1:0x00020000
@BIC r0:0xff0fffff
-------------------
@ 0x00000000
@如果运行在SDRAM中, pc的值为(0x2ff80000-...)
@ r1:0x2ff80000
@BIC r0:0xff0fffff
-------------------
@ 0x00f00000
bic r1, pc, r0
@ 读取代码段的起始地址
@ 0x2ff80000-->r2
ldr r2, _TEXT_BASE(0x2ff80000)
@ r2:0x2ff80000
@BIC r0:0xff0fffff
-------------------
@ 0x00f00000
bic r2, r2, r0
@ 判断代码是否运行在SDRAM中
@ 如果R1==R2, 在SDRAM中运行, 说明当前从USB启动
@ 如果R1!=R2, 在iRAM中运行, 说明当前从Nandflash启动
cmp r1, r2
beq 1f
/*******************************************/
代码一:
val = test;
// test-->R0 ldr R0, test
// R0-->val str r0, val
tmp = test;
// R0-->tmp str r0, tmp
代码二:
val = test;
// test-->R0 ldr R0, test
// R0-->val str r0, val
__asm__ __volatile__("":::"memory");
tmp = test;
// test-->R0 ldr R0, test
// R0-->tmp str r0, val
/*******************************************/
#用法
定义:
#define TOSTR(name) #name
使用:
TOSTR(go) <----> "go"
##用法
定义:
#define U_BOOT_CMD(name) __u_boot_cmd_##name
使用:
U_BOOT_CMD(go) <----> __u_boot_cmd_go
cmd_tbl_t __u_boot_cmd_go __attribute__ ((unused, section(".u_boot_cmd"))) = {"go", ...}
[10] 移植
1. 工程配置
// $ make fsc100(主板名)_config
(1) 添加配置
在参考板(芯片厂家提供的开发板)的配置命令:
smdkc100_config: unconfig(删除以前配置)
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 smdkc100 samsung s5pc1xx
@mkconfig smdkc100(主板名) arm(架构) arm_cortexa8(cpu名) smdkc100 samsung(芯片厂家) s5pc1xx(SOC名)
下面添加相同命令,并且修改主板名:
fsc100_config: unconfig(删除以前配置)
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 fsc100 samsung s5pc1xx
2. 添加主板目录(board/samsung/smdkc100)
拷贝“芯片厂家开发板的主板目录”到 “新的主板目录”
$ cp board/VENDOR/BOARD/ board/VENDOR/新主板名/ -a
$ mv board/VENDOR/BOARD/BOARD.c board/VENDOR/新主板名/新主板名.c
修改Makefile,内容如下:
COBJS := BOARD.o ...
改为
COBJS := 新主板名.o ...
3. 添加主板配置文件
拷贝“芯片厂家开发板的主板配置”到“新的主板配置”
$cp include/configs/BOARD.h include/configs/新主板名.h
4. 修改改交叉编译器(顶层目录Makefile)
CROSS_COMPILE ?= arm-cortex_a8-linux-gnueabi-
2. 代码移植
(1) 启动代码移植
1. 硬件初始化
CPU初始化(新主板和芯片厂家开发板芯片相同,cpu相同,所以做移植仅仅是更改cpu/CPU/start.S)
关闭中断进入SVC模式(确认)
关闭MMU和Cache(确认)
SOC初始化 (cpu/CPU/start.S 或 cpu/CPU/芯片名/...(确认) 或 board/VENDOR/新主板名/...)
如果需要,关闭看门狗(确认)
如果需要加快代码执行速度,初始化PLL(clk control)(确认/添加)
UART初始化(确认)
如果需要,初始化DMA(概率很小)(添加)
Nandflash初始化(为读驱动做准备)(确认)
主板初始化(驱动)(board/VENDOR/新主板名/...)
初始化SDRAM(内存类型发生变化,需要修改内存的初始化参数)
驱动Nand的读操作(Nand类型发生变化,需要修改)
2. 软件环境建立
自拷贝到SDRAM(flash类型或代码段位置变化,需要修改)
预留malloc动态分配区(确认,修改大小)
给全局数据预留内存(确认)
栈分配(预留)内存(每种模式下都需要分配自己的栈)(栈位置变化,需要修改)
BSS段清0(确认)
跳转到SDRAM中运行(确认)
主板初始化(u-boot中需要使用的软件模块和硬件驱动模块的初始化都在这里完成)(需要根据硬件信息修改)
效果:可以运行到命令提示符
注意:可能需要添加模块,方法见《[7] 如何添加模块到工程中?》
(2) 命令移植(实现命令执行代码)
接收命令输入
解析命令
执行命令
协议栈(根据命令需要添加)
文件系统(根据命令需要添加)
软件驱动(根据命令需要添加)
硬件驱动(根据命令需要添加)
include/config_cmd_default.h 编译到u-boot可执行程序的命令的配置项
include/config_cmd_all.h u-boot源码中支持的所有命令的配置项
注意:可能需要添加模块,方法见《[7] 如何添加模块到工程中?》
(3) 启动操作系统(bootm)
修改board/VENDOR/新主板名/新主板名.c文件中的代码,如下:
int board_init(void)
{
....
gd->bd->bi_arch_number = 主板ID(来自于内核);
gd->bd->bi_boot_params = 内核参数存放位置(物理内存的首地址+0x100);
....
}
int dram_init(void)
{
gd->bd->bi_dram[0].start = 物理内存的首地址;
gd->bd->bi_dram[0].size = 物理内存大小;
...
}
[1] 规则
目标: 依赖
[TAB]命令(命令名 参数 依赖 目标)
[2] 难点
1. 自动变量
作用域在一个规则中, 如: $@(目标, 每条规则都只有一个目标), $<(第一个依赖), $^(所有依赖)
例:
hello.o world.o: hello.h
等价于
hello.o: hello.h
world.o: hello.h
2. 模式规则
%.o: %.c
[TAB]$(CC) $(CFLAGS) -c -o $@ $<
3. 自动推倒
要生成的目标没有显示的规则,这时会去找通用规则(模式规则),找通用规则的过程叫自动推导
寻找规则的过程:
1. 在Makefile中,寻找生成hello.o的规则,如果找到...
如:
hello.o: hello.c
2. 在Makefile中, 寻找模式规则, 如果找到匹配的模式规则, 需要查看当前目录下是否有模式规则需要的依赖文件
如果有,则使用模式规则生成目标,如果没有继续寻找下一个模式规则...
如:
%.o: %.c
%.o: %.S
3. 在Make程序内部,寻找模式规则(隐式规则),生成目标
[ARM工程]
[1] 目录
board 跟主板相关的配置和源码代码(主板的初始化代码)
common 主程序源码及配置
cpu 启动代码及配置
drivers 主板无关的设备驱动及配置
include 头文件
lib 库源码及配置
[2] 源文件
*.c C语言源代码
*.S 带预处理命令的汇编源代码
map.lds 链接脚本
Makefile 编译规则
config.mk 编译命令及编译参数(是Makefile的一部分), 会影响编译命令和参数的硬件目录,都需要config.mk
[3] 目标文件
fsc100 ELF格式的可执行文件
fsc100.bin Binary格式的可执行文件
fsc100.dis ELF格式的可执行文件反汇编文件
fsc100.map 详细的符号表文件
System.map 符号表文件
[4] 工程讲解(Makefile和config.mk)
Makefile和config.mk编译源代码为可执行文件
注意:每个模块打包成静态库的好处:
1. 便于管理
2. 链接成可执行程序时,没有被启动代码调用的代码不会被链接到可执行文件中
[5] 如何添加驱动模块?
1. 添加源代码
2. 添加Makefile
拷贝别的目录下的Makefile,修改下列变量的值:
LIB 最终生成的*.a
SOBJS *.S源代码编译出来的*.o
COBJS *.c源代码编译出来的*.o
START 启动代码生成的*.o
3. 修改顶层目录下的Makefile,添加新目录生成的目标文件到工程
OBJS 启动代码的目标文件(*.o)
LIBS 非启动代码的目标文件(*.a)
[u-boot]
[1] 目录
cpu(cpu/名称/...) 启动代码、cpu内部驱动和配置
board(board/芯片厂家/主板名/...) 启动代码中要使用的硬件的驱动代码,主板的初始化代码
common 命令输入、解析和执行代码
net 网络协议
drivers 通用硬件设备驱动
fs 文件系统源码
disk 磁盘分区驱动
lib_arm arm架构的cpu都需要的代码,主板的初始化代码和库代码
tools 工具软件源码
doc 作者写的源代码说明
README 简单的u-boot介绍、目录树说明、配置项说明
[2] 文件
1. 源文件
*.c/*.S/*.h 源代码文件
Makefile 编译规则(有源代码的目录下都会有)
config.mk 编译命令和编译参数(cpu、board、lib_cpu架构、nand_spl)
*.lds 链接脚本
rules.mk 产生*.c(*.S): xxx.h ...
mkconfig 配置的shell脚本
见《文件作用表.bmp》
2. 目标文件
u-boot ELF可执行文件
u-boot.bin Binary可执行文件
u-boot.map 详细的符号表
System.map 符号表
[3] 特点
1. 支持很多CPU
2. 支持很多主板
3. 支持很多通用硬件
4. 工程管理和代码比较混乱
何一款嵌入式产品,都不会用到所有的源代码,所以用u-boot的源码生成目标文件的步骤:
1. 选择需要的源代码
2. 编译选取的源代码
[4] 编译(必须掌握)
1. 配置--根据主板的硬件配置和需要的命令,选取需要的源代码
# make fsc100(主板名)_config
2. 编译--编译选取的源代码
# make
3. 清除编译
# make clean
4. 清除配置
# make clobber
[5] 配置原理
1. 过程
见《u-boot配置流程图》
2. 结果
include/config.mk 选取源代码所在的目录
include/config.h 选取源代码
include/configs/fsc100.h 主板的配置项
include/config_cmd_default.h 编译到u-boot可执行程序的命令的配置项
(include/config_cmd_all.h u-boot源码中支持的所有命令的配置项)
3. 原理
(1) 配置命令
# make fsc100_config
命令原理
fsc100_config: unconfig
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 fsc100 samsung s5pc1xx
@mkconfig fsc100(主板名) arm(架构) arm_cortexa8(cpu名) fsc100 samsung(芯片厂家) s5pc1xx(SOC名)
(2) 配置项原理
1. 控制源代码文件是否编译到u-boot中的配置项--选取文件
例: 在include/configs/fsc100.h定义:
#define CONFIG_DRIVER_DM9000 1 (顶层目录下的Makefile会转换CONFIG_DRIVER_DM9000宏定义为一个Makefile变量)
1 (CONFIG_DRIVER_DM9000 = y)
0 (CONFIG_DRIVER_DM9000 = 0)
宏定义(include/configs/fsc100.h) 被转化为(include/autoconf.mk)
#define CONFIG_DRIVER_DM9000 1 CONFIG_DRIVER_DM9000 = y
Makefile
config.mk
include/autoconf.mk
在drivers/net/Makefile中引用
CONFIG_DRIVER_DM9000=y (include/autoconf.mk)
COBJS-$(CONFIG_DRIVER_DM9000) += dm9000x.o
...
COBJS := $(COBJS-y)
OBJS := $(COBJS)
$(LIBS): .depend $(OBJS)
$(AR) ...
研究方法:
1. 查找CONFIG_DRIVER_DM9000
grep CONFIG_DRIVER_DM9000 * -r
在autoconf.mk文件中发现"CONFIG_DRIVER_DM9000=y”c
2. 找出autoconf.mk文件生成方法
grep autoconf.mk * -r
在Makefile中找到生成autoconf.mk的规则
3. 查看生成autoconf.mk的规则
2. 条件编译原理选取源代码--选取文件中的一部分代码
例: 在include/configs/fsc100.h定义:
#define CONFIG_CMD_PING 1
3. 提供数据给源代码的配置项
宏替换原理
例: 在include/configs/fsc100.h定义:
#define CONFIG_DM9000_BASE 0x88000000
在drivers/net/dm9000x.c里面引用
[6] 编译原理
1. 修改代码段的运行地址
(1) board/$(VENDOR)/$(BOARD)/config.mk
TEXT_BASE = 代码段运行地址
(2) 链接脚本
不推荐
2. 子目录下的Makefile
拷贝别的目录下的Makefile,修改下列变量的值:
LIB 最终生成的*.a
SOBJS *.S源代码编译出来的*.o
COBJS *.c源代码编译出来的*.o
START 启动代码生成的*.o
3. 顶层目录的Makefile
OBJS 启动代码的目标文件(也可以在链接脚本中实现)
LIBS 非启动代码的目标文件
[7] 如何添加模块到工程中?(必须掌握)
1. 添加源代码
例:
drivers/uart/uart.c(记得在start_armboot函数中调用这个文件中的函数,否则uart.c中的函数不会被链接到最终的可执行代码中)
2. 子目录下的Makefile
拷贝别的目录下的Makefile,修改下列变量的值:
LIB 最终生成的*.a
SOBJS *.S源代码编译出来的*.o
COBJS *.c源代码编译出来的*.o
START 启动代码生成的*.o
例:
LIB := libuart.a
SOBJS :=
COBJS := uart.o(非启动代码的*.c编译出来的目标文件)
START := uart.o(启动代码编译出来的目标文件)
3. 修改顶层目录下的Makefile,添加新目录生成的目标文件到工程
(1) 如果添加的模块是非启动代码, 添加模块的Makefile生成的目标文件到工程
(将目标文件放入顶层目录下的Makfile的LIBS变量中)
例:
LIBS += drivers/uart/libuart.a
(2) 如果添加的模块是启动代码,方法如下:
1. 添加模块的Makefile生成的目标文件到工程
(将目标文件放入顶层目录下的Makfile的OBJS变量中)
例:
OBJS += drivers/uart/uart.o
$(OBJS):... (应该是标志的u-boot做好的)
$(MAKE) -c $(dir $@) $(notdir $@)
2. 添加模块的Makefile生成的目标文件到工程
(1) 将目标文件放入顶层目录下的Makfile的OBJS(LIBS)变量中
例:
LIBS += drivers/uart/libuart.a
(2) 在链接脚本中修改链接顺序
例:
.text : (不推荐)
{
cpu/arm_cortexa8/start.o (.text)
drivers/uart/uart.o (.text)
*.o (.text)
}
[8] 源代码编辑软件
vi + ctags 查看源代码
1. 进入顶层目录,然后打开任意的文件,然后按F9新建tags索引文件
2. 查找定义: 把光标移动要查找的标识符,然后按ctrl+]
3. 回退: ctrl + o
sourceinside
建立工程,添加所有文件到工程
[9] 源代码分析
(1) 启动操作系统
第一阶段: 自启动
硬件初始化
CPU初始化
关闭中断进入SVC模式
关闭MMU和Cache
SOC初始化
如果有,需要关闭看门狗
如果需要加快代码的执行速度, 初始化PLL(clock control)
UART初始化
如果需要用到DMA,就初始化
Nandflash控制器初始化(为读驱动做准备)
主板初始化(驱动)
初始化SDRAM
驱动Nandflash读操作
软件环境建立
自拷贝到SDRAM
预留malloc动态分配区
给全局数据预留内存
栈内存分配(每种模式下都需要分配自己的栈)
.BSS段清0
跳转到SDRAM中运行
主板初始化(所有软硬件模块的初始化都需要在这里完成)
第二阶段: 命令执行阶段
接收命令输入
解析命令
执行命令
协议栈
文件系统
软件驱动
硬件驱动
第三阶段: 启动操作系统(bootm)
建立操作系统运行环境
linux内核启动前提条件:
1. 关闭中断,进入SVC
2. 关闭MMU和Cache
3. R0 0 不是强制
4. R1 arch number(machine type id) 强制
5. R2 内核参数指针(atags list) 不必须
传递参数给内核
atags list
struct传递方式(内存地址 + 0x100 BSP确定)
跳转到内核代码运行
cpu/arm_cortexa8/start.S(reset --->cpu_init_crit)
-->board/samsung/fsc100/lowlevel_init.S(lowlevel_init-->system_clock_init)
-->uart_asm_init
-->dma_init
-->nand_pin_mux
?-->board/samsung/fsc100/mem_setup.S(mem_ctrl_asm_init)
-->wakeup_reset
?-->board/samsung/fsc100/nand_cp.c(stack_setup)
-->copy_uboot_to_ram
-->board/samsung/fsc100/nand_cp.c(stack_setup)
-->lib_arm/board.c(start_armboot)
-->common/main.c(main_loop)
reset 复位异常启动的正常代码
cpu_init_crit 关闭MMU和cache
lowlevel_init 关闭看门狗、初始化SRAM接口、初始化中断控制器、初始化PLL
system_clock_init 初始化PLL
dma_init 初始化dma
nand_pin_mux 初始化nandflash的多功能管脚
nand_asm_init nandflash初始化
mem_ctrl_asm_init DDR控制器初始化
wakeup_reset 唤醒复位
stack_setup 栈初始化
start_armboot 软硬件初始化
main_loop 初始化及命令循环
(2) 关键代码理解
/*******************************************/
// 判断u-boot当前运行位置(iRAM SDRAM)
ldr r0, =0xff0fffff
r0: 0xff0fffff (20-23bit)
@清除除20-23bit以外的所有位
@u-boot只有可能运行在iRAM和SDRAM
@如果运行在iRAM中, pc的值为(0x20000 - 0x38000)
@ r1:0x00020000
@BIC r0:0xff0fffff
-------------------
@ 0x00000000
@如果运行在SDRAM中, pc的值为(0x2ff80000-...)
@ r1:0x2ff80000
@BIC r0:0xff0fffff
-------------------
@ 0x00f00000
bic r1, pc, r0
@ 读取代码段的起始地址
@ 0x2ff80000-->r2
ldr r2, _TEXT_BASE(0x2ff80000)
@ r2:0x2ff80000
@BIC r0:0xff0fffff
-------------------
@ 0x00f00000
bic r2, r2, r0
@ 判断代码是否运行在SDRAM中
@ 如果R1==R2, 在SDRAM中运行, 说明当前从USB启动
@ 如果R1!=R2, 在iRAM中运行, 说明当前从Nandflash启动
cmp r1, r2
beq 1f
/*******************************************/
代码一:
val = test;
// test-->R0 ldr R0, test
// R0-->val str r0, val
tmp = test;
// R0-->tmp str r0, tmp
代码二:
val = test;
// test-->R0 ldr R0, test
// R0-->val str r0, val
__asm__ __volatile__("":::"memory");
tmp = test;
// test-->R0 ldr R0, test
// R0-->tmp str r0, val
/*******************************************/
#用法
定义:
#define TOSTR(name) #name
使用:
TOSTR(go) <----> "go"
##用法
定义:
#define U_BOOT_CMD(name) __u_boot_cmd_##name
使用:
U_BOOT_CMD(go) <----> __u_boot_cmd_go
cmd_tbl_t __u_boot_cmd_go __attribute__ ((unused, section(".u_boot_cmd"))) = {"go", ...}
[10] 移植
1. 工程配置
// $ make fsc100(主板名)_config
(1) 添加配置
在参考板(芯片厂家提供的开发板)的配置命令:
smdkc100_config: unconfig(删除以前配置)
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 smdkc100 samsung s5pc1xx
@mkconfig smdkc100(主板名) arm(架构) arm_cortexa8(cpu名) smdkc100 samsung(芯片厂家) s5pc1xx(SOC名)
下面添加相同命令,并且修改主板名:
fsc100_config: unconfig(删除以前配置)
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 fsc100 samsung s5pc1xx
2. 添加主板目录(board/samsung/smdkc100)
拷贝“芯片厂家开发板的主板目录”到 “新的主板目录”
$ cp board/VENDOR/BOARD/ board/VENDOR/新主板名/ -a
$ mv board/VENDOR/BOARD/BOARD.c board/VENDOR/新主板名/新主板名.c
修改Makefile,内容如下:
COBJS := BOARD.o ...
改为
COBJS := 新主板名.o ...
3. 添加主板配置文件
拷贝“芯片厂家开发板的主板配置”到“新的主板配置”
$cp include/configs/BOARD.h include/configs/新主板名.h
4. 修改改交叉编译器(顶层目录Makefile)
CROSS_COMPILE ?= arm-cortex_a8-linux-gnueabi-
2. 代码移植
(1) 启动代码移植
1. 硬件初始化
CPU初始化(新主板和芯片厂家开发板芯片相同,cpu相同,所以做移植仅仅是更改cpu/CPU/start.S)
关闭中断进入SVC模式(确认)
关闭MMU和Cache(确认)
SOC初始化 (cpu/CPU/start.S 或 cpu/CPU/芯片名/...(确认) 或 board/VENDOR/新主板名/...)
如果需要,关闭看门狗(确认)
如果需要加快代码执行速度,初始化PLL(clk control)(确认/添加)
UART初始化(确认)
如果需要,初始化DMA(概率很小)(添加)
Nandflash初始化(为读驱动做准备)(确认)
主板初始化(驱动)(board/VENDOR/新主板名/...)
初始化SDRAM(内存类型发生变化,需要修改内存的初始化参数)
驱动Nand的读操作(Nand类型发生变化,需要修改)
2. 软件环境建立
自拷贝到SDRAM(flash类型或代码段位置变化,需要修改)
预留malloc动态分配区(确认,修改大小)
给全局数据预留内存(确认)
栈分配(预留)内存(每种模式下都需要分配自己的栈)(栈位置变化,需要修改)
BSS段清0(确认)
跳转到SDRAM中运行(确认)
主板初始化(u-boot中需要使用的软件模块和硬件驱动模块的初始化都在这里完成)(需要根据硬件信息修改)
效果:可以运行到命令提示符
注意:可能需要添加模块,方法见《[7] 如何添加模块到工程中?》
(2) 命令移植(实现命令执行代码)
接收命令输入
解析命令
执行命令
协议栈(根据命令需要添加)
文件系统(根据命令需要添加)
软件驱动(根据命令需要添加)
硬件驱动(根据命令需要添加)
include/config_cmd_default.h 编译到u-boot可执行程序的命令的配置项
include/config_cmd_all.h u-boot源码中支持的所有命令的配置项
注意:可能需要添加模块,方法见《[7] 如何添加模块到工程中?》
(3) 启动操作系统(bootm)
修改board/VENDOR/新主板名/新主板名.c文件中的代码,如下:
int board_init(void)
{
....
gd->bd->bi_arch_number = 主板ID(来自于内核);
gd->bd->bi_boot_params = 内核参数存放位置(物理内存的首地址+0x100);
....
}
int dram_init(void)
{
gd->bd->bi_dram[0].start = 物理内存的首地址;
gd->bd->bi_dram[0].size = 物理内存大小;
...
}
bootloader
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。