[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程序内部,寻找模式规则(隐式规则),生成目标
[ARMproject]
[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] project解说(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,加入新文件夹生成的目标文件到project
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. project管理和代码比較混乱
何一款嵌入式产品。都不会用到全部的源代码,所以用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] 怎样加入模块到project中?(必须掌握)
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,加入新文件夹生成的目标文件到project
(1) 假设加入的模块是非启动代码, 加入模块的Makefile生成的目标文件到project
(将目标文件放入顶层文件夹下的Makfile的LIBS变量中)
例:
LIBS += drivers/uart/libuart.a
(2) 假设加入的模块是启动代码,方法例如以下:
1. 加入模块的Makefile生成的目标文件到project
(将目标文件放入顶层文件夹下的Makfile的OBJS变量中)
例:
OBJS += drivers/uart/uart.o
$(OBJS):... (应该是标志的u-boot做好的)
$(MAKE) -c $(dir $@) $(notdir $@)
2. 加入模块的Makefile生成的目标文件到project
(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
建立project,加入全部文件到project
[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. project配置
// $ 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] 怎样加入模块到project中?》
(2) 命令移植(实现命令运行代码)
接收命令输入
解析命令
运行命令
协议栈(依据命令须要加入)
文件系统(依据命令须要加入)
软件驱动(依据命令须要加入)
硬件驱动(依据命令须要加入)
include/config_cmd_default.h 编译到u-boot可运行程序的命令的配置项
include/config_cmd_all.h u-boot源代码中支持的全部命令的配置项
注意:可能须要加入模块,方法见《[7] 怎样加入模块到project中?》
(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 = 物理内存大小;
...
}
版权声明:本文博客原创文章。博客,未经同意,不得转载。