uboot:https://ftp.denx.de/pub/u-boot/
nxp-uboot:https://github.com/nxp-imx/uboot-imx
1、顶层Makefile
文件加入编译的两种方式:以xxx/xxx.c文件为例
1、使用menuconfig:
先编辑.c所在目录下的Kconfig,加入配置项xxx
再编辑.c所在目录下的Makefile,添加obj-$(CONFIG_xxx) = xxx.o
2、不使用menuconfig:
直接编辑.c所在目录下的Makefile,添加obj-y = xxx.o
说明:$(libs-y)依赖每个文件夹下的xxx-in.o,而每个文件夹下的xxx-in.o又依赖当前文件夹的所有.o文件。
2、Kbuild框架
<1>在scripts文件夹下,有一个Kbuild.include文件中定义了如下几个关键的变量:
181: build := -f $(srctree)/scripts/Makefile.build obj
187: modbuiltin := -f $(srctree)/scripts/Makefile.modbuiltin obj
193: dtbinst := -f $(srctree)/scripts/Makefile.dtbinst obj
199: clean := -f $(srctree)/scripts/Makefile.clean obj
205 hdr-inst := -f $(srctree)/scripts/Makefile.headersinst obj
217: echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
.....
<2>在顶层Makefile文件的391-393行有如下:
# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
include scripts/Kbuild.include # 包含<1>中定义的变量
<3>在顶层Makefile文件的1232-1234行有如下:
%.imx: $(IMX_DEPS) %.bin
$(Q)$(MAKE) $(build)=arch/arm/mach-imx $@ # <1>中定义的build被使用
$(BOARD_SIZE_CHECK)
<4>在顶层Makefile文件的1255-1259行有如下:(make dtbs命令)
PHONY += dtbs
dtbs: dts/dt.dtb
@:
dts/dt.dtb: u-boot
$(Q)$(MAKE) $(build)=dts dtbs
展开得:
@ make -f $(srctree)/scripts/Makefile.build obj=dts dtbs
其中:obj=dts是一个传入的变量, dtbs是要构建的目标
2、u-boot.map文件
反汇编:arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis
./scripts/dtc/dtc -I dtb -O dts -o ./itop.dts ./arch/arm/dts/imx6ull-14x14-itop.dtb
./scripts/dtc/dtc -I dtb -O dts -o ./evk.dts ./arch/arm/dts/imx6ull-14x14-evk.dtb
注意:如何确定哪个.c文件的哪个函数被编译?
通过函数名在u-boot.map、u-boot.dis这两个文件中搜索。先在u-boot.dis找到函数的链接地址,以此链接地址在u-boot.map中搜索,即可确定是哪个.c文件下的函数被编译。
save_boot_params_ret:
/* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode, except if in HYP mode already */
/* 禁用中断(FIQ 和 IRQ),同时将处理器设置为 SVC32 模式,除非已经处于 HYP 模式。 */
mrs r0, cpsr @ 将当前程序状态寄存器(CPSR)的值加载到寄存器r0中
and r1, r0, #0x1f @ 使用掩码操作提取r0的低5位,即当前的处理器模式
teq r1, #0x1a @ 测试是否当前处理器模式为HYP模式(Hypervisor mode)
bicne r0, r0, #0x1f @ 如果不在HYP模式,清除 CPSR 中的所有模式位
orrne r0, r0, #0x13 @ 如果不在HYP模式,设置 CPSR 的模式为SVC模式
orr r0, r0, #0xc0 @ 禁用FIQ和IRQ中断,将 CPSR 的FIQ和IRQ位设置为 1
msr cpsr,r0 @ 将修改后的 CPSR 的值写回 CPSR 寄存器,完成设置
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
/* 将 CP15 的 SCTLR 寄存器中将 V 位设置为0,以便将 VBAR 指向向量表 */
mrc p15, 0, r0, c1, c0, 0 @ 读取 CP15 SCTLR 寄存器里面的值
bic r0, #CR_V @ 将 CP15 SCTLR 寄存器中的 V 位清零
mcr p15, 0, r0, c1, c0, 0 @ 写回修改后的值到 CP15 SCTLR 寄存器
/* Set vector address in CP15 VBAR register */
/* 在 CP15 的 VBAR 寄存器中设置向量表的地址 */
ldr r0, =_start @ 将 _start 符号的地址加载到寄存器 r0 中
mcr p15, 0, r0, c12, c0, 0 @ 将 r0 中的地址写入 CP15 VBAR 寄存器,设置异常向量表基地址
bl cpu_init_cp15
bl cpu_init_crit
bl _main
注:对于CP15协处理器的操作,CP15有c0-c15共16个寄存器组,每个组里面含有多个寄存器
mcr p15, 0, r0, c12, c0, 0 @读操作,读取c12寄存器组里面的c0,0对应的寄存器到r0中
注意:c0,0并不是偏移的意思,而是由具体的表决定的,不要以为c0,1就是组里的第1个寄存器,
这是不一定的。具体的cx,x对应组里面的第几个寄存器,参考手册上有写。
/*************************************************************************************
* cpu_init_cp15
* 设置 CP15 寄存器(缓存、MMU、TLBs)。如果定义了 CONFIG_SYS_ICACHE_OFF,则打开 I-cache。
*************************************************************************************/
cpu_init_cp15:
/* Invalidate L1 I/D */
mov r0, #0 @ set up for MCR (r0 = 0)
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs (置零)
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache (置零)
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array (置零)
dsb
isb
/* disable MMU stuff and caches */
mrc p15, 0, r0, c1, c0, 0 @ 读 SCTLR => r0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
mcr p15, 0, r0, c1, c0, 0 @ 写 r0 => SCTLR
mov r5, lr @ Store my Caller (r5 = lr)(跳转)
mrc p15, 0, r1, c0, c0, 0 @ r1 has Read Main ID Register (r1 = MIDR)
mov r3, r1, lsr #20 @ get variant field (r3 = r1>>20)
and r3, r3, #0xf @ r3 has CPU variant (r3 = r3&0xf)
and r4, r1, #0xf @ r4 has CPU revision (r4 = r1&0xf)
mov r2, r3, lsl #4 @ shift variant field for combined value(r2 = r3<<4)
orr r2, r4, r2 @ r2 has combined CPU variant + revision(r2 = r4|r2)
/* Early stack for ERRATA that needs into call C code */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
ldr r0, =(SYS_INIT_SP_ADDR)
#endif
bic r0, r0, #7 @ 8-byte alignment for ABI compliance(将r0最低的三个比特位清零)
mov sp, r0 @ SP = r0
mov pc, r5 @ back to my caller ( pc=r5 )(返回)
cpu_init_crit:
b lowlevel_init #(注意:这是一个无返回跳转)
# 下面具体分析lowlevel_init -------------------------------------------------
lowlevel_init:
/*Setup a temporary stack. Global data is not available yet. */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =CONFIG_SPL_STACK @ 设置堆栈指针
#else
ldr sp, =SYS_INIT_SP_ADDR @ 设置堆栈指针
#endif
bic sp, sp, #7 @ 8字节对齐 for ABI compliance(将sp最低的三个比特位清零)
#ifdef CONFIG_SPL_DM
mov r9, #0 @ r9 = 0
#else
push {ip, lr} @ 压ip入栈、压lr入栈
bl s_init @ 调用s_init(C函数)初始化处理器的时钟
pop {ip, pc} @ 出栈
#include <asm/io.h>
#include <asm/arch/imx-regs.h>
_main:
#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
ldr r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
ldr r0, =(SYS_INIT_SP_ADDR)
#endif
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0 @ sp = r0 (设置栈指针)
bl board_init_f_alloc_reserve @ 在栈中,分配早期的malloc区域和gd区域(r0为函数的参数)
mov sp, r0 @ sp = r0 (r0为函数的返回值)
/* set up gd here, outside any C code */
mov r9, r0 @ r9 = r0
bl board_init_f_init_reserve @ 对早期的malloc区域和gd区域进行初始化(r0为函数的参数)
#if defined(CONFIG_DEBUG_UART) && CONFIG_IS_ENABLED(SERIAL)
bl debug_uart_init
#endif
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
CLEAR_BSS
#endif
mov r0, #0
bl board_init_f
ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
ldr r9, [r9, #GD_NEW_GD] /* r9 <- gd->new_gd */
adr lr, here
......
bl relocate_code
......
struct driver *drv = ll_entry_start(struct driver, driver);
struct driver *drv = ({ \
static char start[0] __aligned(CONFIG_LINKER_LIST_ALIGN) \
__attribute__((unused)) \
__section("__u_boot_list_2_driver_1"); \
struct driver * tmp = (struct driver *)&start; \
asm("":"+r"(tmp)); \
tmp; \
});
//说明:返回__u_boot_list_2_driver_1段的第一个元素的起始地址
3.1、添加自己的单板<主>
1、添加头文件
cp ./include/configs/mx6ullevk.h ./include/configs/mx6ullitop.h
vim ./include/configs/mx6ullitop.h
修改.h开头的宏定义
2、添加板级文件夹
cp -r ./board/freescale/mx6ullevk ./board/freescale/mx6ullitop
mv ./board/freescale/mx6ullitop/mx6ullevk.c ./board/freescale/mx6ullitop/mx6ullitop.c
vim ./board/freescale/mx6ullitop/Kconfig
vim ./board/freescale/mx6ullitop/Makefile
vim ./board/freescale/mx6ullitop/MAINTAINERS
vim ./board/freescale/mx6ullitop/imximage.cfg
3、添加设备树文件1
cp ./arch/arm/dts/imx6ull-14x14-evk.dts ./arch/arm/dts/imx6ull-14x14-itop.dts
vim ./arch/arm/dts/imx6ull-14x14-itop.dts
vim ./arch/arm/dts/Makefile
在 dtb-$(CONFIG_MX6ULL) 添加一项
imx6ull-14x14-itop.dtb \ (为了使能dtb的编译)
4、添加设备树文件2
cp ./arch/arm/dts/imx6ull-14x14-evk-u-boot.dtsi ./arch/arm/dts/imx6ull-14x14-itop-u-boot.dtsi
5、添加默认配置文件
cp ./configs/mx6ull_14x14_evk_defconfig ./configs/mx6ull_14x14_itop_defconfig
vim configs/mx6ull_14x14_itop_defconfig
<1>找到 CONFIG_TARGET_MX6ULL_14X14_EVK=y
<1>改为 CONFIG_TARGET_MX6ULL_14X14_ITOP=y
<2>找到 CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-evk"
<2>改为 CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-itop"
6、修改Kconfig文件
vim ./arch/arm/mach-imx/mx6/Kconfig
添加
config TARGET_MX6ULL_14X14_ITOP
bool "Support mx6ull_14x14_itop"
depends on MX6ULL
select BOARD_LATE_INIT
select DM
select DM_THERMAL
select MX6ULL
imply CMD_DM
添加
source "board/freescale/mx6ullitop/Kconfig"
7、修改 ./arch/arm/mach-imx/cpu.c,在reset_cpu函数中添加如下代码:
方法一:注释掉此函数,此时会链接drivers/watchdog/imx_watchdog.c中的reset_cpu函数
/*
__weak void reset_cpu(void)
{
return;
}
*/
方法二:修改函数如下
__weak void reset_cpu(void)
{
#include <fsl_wdog.h>
struct watchdog_regs *wdog = (struct watchdog_regs *)WDOG1_BASE_ADDR;
//do not assert internal reset
u16 wcr = 0x04|0x10; // WCR_WDE|WCR_SRS
//Write 3 times to ensure it works, due to IMX6Q errata ERR004346
writew(wcr, &wdog->wcr);
writew(wcr, &wdog->wcr);
writew(wcr, &wdog->wcr);
//Start while
for(;;);
return;
}
3.2、移植网络的驱动<主>
< 1 >设备树:imx6ull-14x14-itop.dts
一、设备树:目录
[arch/arm/dts/imx6ull-14x14-itop.dts]
|-->#include "imx6ull.dtsi"
|-->#include "imx6ul.dtsi"
|-->#include <dt-bindings/clock/imx6ul-clock.h>
|-->#include <dt-bindings/gpio/gpio.h>
|-->#include <dt-bindings/input/input.h>
|-->#include <dt-bindings/interrupt-controller/arm-gic.h>
|-->#include "imx6ul-pinfunc.h"
|-->#include "imx6ull-pinfunc.h"
|-->#include "imx6ull-pinfunc-snvs.h"
|-->#include "imx6ul-14x14-evk.dtsi"
[arch/arm/dts/imx6ull-14x14-evk-u-boot.dtsi]
二、设备树:网络相关
[arch/arm/dts/imx6ull-14x14-itop.dts]
|-->#include "imx6ull.dtsi"
|-->#include "imx6ul.dtsi"
//Fast Ethernet Controller 1 (CPU内部资源)
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1
&pinctrl_enet1_reset>; //zjh add
phy-mode = "rmii";
phy-handle = <ðphy0>;
phy-supply = <®_peri_3v3>;
phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; //zjh add
phy-reset-duration = <200>; //zjh add
status = "okay";
};
//Fast Ethernet Controller 2 (CPU内部资源)
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2
&pinctrl_enet2_reset>; //zjh add
phy-mode = "rmii";
phy-handle = <ðphy1>;
phy-supply = <®_peri_3v3>;
phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; //zjh add
phy-reset-duration = <200>; //zjh add
status = "okay";
mdio { //MDIO总线:用于连接 FEC 和 PHY 设备
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@2 { //(网口1)
compatible = "ethernet-phy-id0022.1560";
reg = <2>; //phy芯片地址(硬件电路决定)
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET_REF>;
clock-names = "rmii-ref";
};
ethphy1: ethernet-phy@1 { //(网口2)
compatible = "ethernet-phy-id0022.1560";
reg = <1>; //phy芯片地址(硬件电路决定)
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";
};
};
};
< 2 >迅为开发板硬件原理图
5、DM框架
DM 是 U-Boot 中的驱动框架,全称 Driver Mode。
Linux 中 platform bus 模型的驱动有着三要素:device 、bus 、driver
Uboot 中 Driver Mode 模型的驱动也有三要素:udevice、uclass、driver。
1、udevice 描述具体的某一个硬件设备。
struct udevice {
const struct driver *driver;
const char *name;
......
};
通过三种路径生成:
a、dts设备节点。(大多数使用)
b、U_BOOT_DEVICE(__name) 宏申明 (少部分使用)
c、主动调用 device_bind_xxx 系列 API (极少部分使用)
2、driver 是与这个设备匹配的驱动。
通过 U_BOOT_DRIVER(__name) 宏声明。如果 driver 实现了 bind 接口,
该 bind 将在 device_bind_common 中 device 和 driver 匹配上后
被调用, 而且在 device_bind_common 中会完成 udevice 和 driver 的绑定。
driver 一般都有对应的 probe 接口,通过 device_probe(struct udevice *dev) 调用,
要注意的是driver 的 bind 接口调用的比 probe 接口早, 大部分在 dm_init_and_scan 中就被调用了
driver 一般会提供 ops 操作接口,供上一层调用。
需要说明的是,driver 一般都不需要把自己注册到 uclass 中,
而是在 device_bind_common 阶段实现 driver 、uclass、device 三者的对接,
然后 uclass 层通过 udevice->driver->ops 获取对应 driver 的操作接口。
3、uclass 是同一类设备的抽象,提供管理同一类设备的抽象接口
主要包括两个类型的结构体:struct uclass_driver 和 struct uclass
其中struct uclass_driver 为 struct uclass 的驱动
struct uclass_driver { //由UCLASS_DRIVER(__name)定义
const char *name;
enum uclass_id id;
int (*post_bind)(struct udevice *dev);
......
};
struct uclass {
void *priv_; //类本身私有数据
struct uclass_driver *uc_drv; //类本身的驱动程序
struct list_head dev_head; //该类中的设备列表
struct list_head sibling_node;//类链表中的下一个类
};
uclass和udevice都是动态生成的。
1、在解析设备树中的设备或直接定义的平台设备的时候,会动态生成udevice。
2、然后找到udevice对应的driver,通过driver中的uclass id得到uclass_driver id。
3、从uclass链表中查找对应的uclass是否已经生成,没有生成的话则动态生成uclass。
注:uclass链表的起点是gd的一个成员:gd->uclass_root (*全局变量gd的很重要*)
设备驱动的使用(应用层使用驱动)
1、首先需要通过 uclass_get_device_xxx 系列 API 拿到该设备的 udevice。
2、然后通过该设备的 uclass 提供的 API 操作该设备。
<+>uclass_get_device_xxx 拿到该设备的 udevice 后会调用该设备的 probe 接口。
以驱动[ pwm backlight ]为例:
/**
* drivers/video/simple_panel.c
*/
struct udevice *bldev;
uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, "backlight", &bldev);
backlight_enable(bldev);
backlight_set_brightness(bldev, percent);
x、启动分析(基于IMX6ULL)
u-boot2023:启动详细的代码调用流程
u-boot.lds: [arch/arm/cpu/u-boot.lds]
-->_start: [arch/arm/lib/vectors.S]
-->reset: [arch/arm/cpu/armv7/start.S]
-->save_boot_params: [arch/arm/cpu/armv7/start.S] /*将引导参数保存到内存中*/
-->save_boot_params_ret: [arch/arm/cpu/armv7/start.S]
|-->cpu_init_cp15: [arch/arm/cpu/armv7/start.S]
|-->cpu_init_crit: [arch/arm/cpu/armv7/start.S]
|-->lowlevel_init: [arch/arm/cpu/armv7/lowlevel_init.S]
|-->ENTRY(_main) [arch/arm/lib/crt0.S]
|-->board_init_f_alloc_reserve [common/init/board_init.c) /*为u-boot的gd结构体分配空间*/
|-->board_init_f_init_reserve [common/init/board_init.c) /*将gd结构体清零*/
|-->board_init_f [common/board_f.c]
|-->initcall_run_list [include/initcall.h] /*初始化序列函数*/
|-->init_sequence_f[] [common/board_f.c] /*初始化序列函数数组 */
|-->setup_mon_len
|-->fdtdec_setup
|-->initf_malloc
|-->log_init
|-->...
|-->arch_cpu_init
|-->mach_cpu_init
|-->initf_dm
|-->board_early_init_f
|-->...
|-->env_init
|-->init_baud_rate
|-->serial_init
|-->console_init_f
|-->...
|-->dram_init
|-->...
---------------------------------------------------------------------------分界线
|-->relocate_code [arch/arm/lib/relocate.S] /*主要完成镜像拷贝和重定位*/
---------------------------------------------------------------------------分界线
|-->relocate_vectors [arch/arm/lib/relocate.S] /*重定位向量表*/
|-->board_init_r [common/board_r.c] /*重定向后板级初始化*/
|-->initcall_run_list(init_sequence_r) [include/initcall.h] /*初始化序列函数*/
|-->init_sequence_r[] [common/board_r.c] /*初始化序列函数数组*/
|-->...
|-->initr_dm /*DM初始化*/
|-->board_init
|-->...
|-->initr_dm_devices /*DM设备初始化*/
|-->stdio_init_tables
|-->serial_initialize /*串口初始化*/
|-->initr_announce
|-->dm_announce
|-->...
|-->initr_mmc /*MMC初始化*/
|-->initr_env /*环境初始化*/
|-->...
|-->interrupt_init
|-->board_late_init
|-->initr_net /*网络初始化*/
|-->...
|-->run_main_loop [common/board_r.c] /* It does not return */