uboot学习心得(uboot流程分析)max32590芯片

1、代码框架

源码解压以后,我们可以看到以下的文件和文件夹:

 cpu

与处理器相关的文件。每个子目录中都包括cpu.c和interrupt.c、start.S、u-boot.lds。

cpu.c:初始化CPU、设置指令Cache和数据Cache等

interrupt.c:设置系统的各种中断和异常

start.S:是U-boot启动时执行的第一个文件,它主要做最早期的系统初始化,代码重定向和设置系统堆栈,为进入U-boot第二阶段的C程序奠定基础。

u-boot.lds:链接脚本文件,对于代码的最后组装非常重要。

 board

已经支持的所有开发板相关文件,其中包含SDRAM初始化代码、Flash底层驱动、板级初始化文件。

其中的config.mk文件定义了TEXT_BASE,也就是代码在内存的实际地址,非常重要。

 common

与处理器体系结构无关的通用代码,U-boot的命令解析代码/common/command.c、所有命令的上层代码cmd_*.c、U-boot环境变量处理代码env_*.c等都位于该目录下

drivers

包含几乎所有外围芯片的驱动,网卡、USB、串口、LCD、Nand Flash等等

disk

fs

net

支持CPU无关的重要子系统:

磁盘驱动的分区处理代码

文件系统:FAT、JFFS2、EXT2等

网络协议:NFS、TFTP、RARP、DHCP等等

include

头文件,包括各CPU的寄存器定义,文件系统、网络等等

configs子目录下的文件是与目标板相关的配置头文件

doc

U-Boot的说明文档,在修改配置文件的时候可能用得上

lib_arm

处理器体系相关的初始化文件

比较重要的是其中的board.c文件,几乎是所有架构的U-boot第二阶段代码入口函数和相关初始化函数存放的地方。

lib_avr32

lib_blackfin

lib_generic

lib_i386

lib_m68k

lib_microblaze

lib_mips lib_nios

lib_nios2

lib_ppc

lib_sh

lib_sparc

 api

examples

外部扩展应用程序的API和范例

nand_spl

onenand_ipl

post

一些特殊构架需要的启动代码和上电自检程序代码

libfdt

支持平坦设备树(flattened device trees)的库文件

tools

编译S-Record或U-Boot映像等相关工具,制作bootm引导的内核映像文件工具mkimage源码就在此

Makefile

MAKEALL

config.mk

rules.mk

mkconfig

控制整个编译过程的主Makefile文件和规则文件

CHANGELOG

CHANGELOG-before-U-Boot-1.1.5

COPYING

CREDITS

MAINTAINERS

README

一些介绍性的文档、版权说明



2、链接脚本

uboot.lds在u-boot/arch/arm/cpu/arm926ejs目录下。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")//定义输出elf格式文件,32位,小端模式
OUTPUT_ARCH(arm)//ARM平台
ENTRY(_start)//起始代码段 _start
SECTIONS
{
. = 0x00000000;// 指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置


. = ALIGN(4);//4字节对其
.text ://定义代码段
{
arch/arm/cpu/arm926ejs/start.o (.text)
*(.text)
}


. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }//只读数据段


. = ALIGN(4);
.data : { *(.data) }//可写数据段


. = ALIGN(4);
.got : { *(.got) }//got段,程序非标准段,uboot特有段


. = .;
__u_boot_cmd_start = .;//U_BOOT_CMD宏定义的数据存放在此处
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;


. = ALIGN(4);
__bss_start = .;//bss段,通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域
.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
_end = .;

}

uboot学习心得(uboot流程分析)max32590芯片_第1张图片

3、启动stage1(start.S)

代码目录在/u-boot/arch/arm/cpu/arm926ejs

相关的引用头文件在/u-boot/arch/arm/include/asm/arch-maximasp目录下定义了一些硬件地址寄存器等。

/u-boot/board/Maxim/maximasp_eek/board_init.S

/u-boot/arch/arm/lib/board.c start_armboot函数文件

启动流程:异常向量——上电复位跳转到异常向量——设置cpu为SVC32模式——跳转到cpu_init_crit——禁止MMU和缓存——跳转到lowlevel_init(初始化PLL时钟、复合管脚配置、内存模块配置)——relocate(一般加载过程是内部rom程序将nandflash前4K区域程序加载到内部ram中运行,程序判断当前是从nandflash加载运行还是再sram运行,从而确定是否执行copy_loop,copy_loop将程序从nandflash加载到外部ram运行)——初始化堆栈stack_setup——clear_bss——跳转到start_armboot

.globl _start
_start:

b reset

代码起始跳转到reset

        ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq

ldr pc, _fiq

定义的异常处理,异常处理是由硬件来跳转的,异常处理存储了一个跳转地址。

_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:

.word fiq

irq、fiq在uboot中没有使用,因此是空的

.align 5
irq:
.align 5

fiq:

若需要使用irq、fiq,则需要在头文件中定义CONFIG_USE_IRQ

ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs


#else

do_fiq和do_irq定义在interrupts.c里

通过arm-linux-objdump -d start.o查看代码段,可以发现中断表的存储位置

uboot学习心得(uboot流程分析)max32590芯片_第2张图片

4、启动stage2

/u-boot/arch/arm/lib/board.c start_armboot函数文件

start_armboot主要用于初始化,初始化gd变量,初始化环境变量、mmc、nand、onenand、serial、eth等等。然后进入main_loop

初始化的一些配置参数在/u-boot/include/configs/下对应的芯片头文件下

环境变量的配置参数:

#define CONFIG_ENV_IS_IN_NAND 1
#define CONFIG_ENV_SECT_SIZE 0x80000 /* 512K */
/* Use smaller environment than the sector size because we malloc() a multiple of this */
#define CONFIG_OLD_ENV_SIZE 0x1000
#define CONFIG_ENV_SIZE 0x2000

#define CONFIG_ENV_OFFSET 0x200000

/u-boot/common/env_common.c里定义了环境变量相关的函数。


/u-boot/common/main.c 中定义了main_loop


上面代码定义了命令自动补全

uboot学习心得(uboot流程分析)max32590芯片_第3张图片

以上代码获取bootdelay环境变量,及开机检测按键延时时间

uboot学习心得(uboot流程分析)max32590芯片_第4张图片

以上代码获取bootcmd指令,当bootdelay时间超时时执行bootcmd指令启动加载内核

for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME
if (rc >= 0) {
/* Saw enough of a valid command to
* restart the timeout.
*/
reset_cmd_timeout();
}
#endif
len = readline (CONFIG_SYS_PROMPT);


flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;//按回车重复执行上次的命令标志
lastcommand[0] = 0;
#endif

#ifdef CONFIG_BOOT_RETRY_TIME
else if (len == -2) {
/* -2 means timed out, retry autoboot
*/
puts ("\nTimed out waiting for command\n");
# ifdef CONFIG_RESET_TO_RETRY
/* Reinit board to run initialization code again */
do_reset (NULL, 0, 0, NULL);
# else
return; /* retry autoboot */
# endif
}
#endif


if (len == -1)
puts ("\n");
else
rc = run_command (lastcommand, flag);


if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;

}

如果在bootdelay时间内按下按键则循环读取输入指令

/u-boot/common/env_common.c定义bootcmd命令:

#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"

#endif

CONFIG_BOOTCOMMAND的定义在/u-boot/include/configs/maximasp-eek.h:

# define CONFIG_BOOTCOMMAND "run bootf0"

/u-boot/env.input和env.mk定义了bootf0参数的配置

run_command 会调用bootm

/u-boot/common/cmd_bootm.c定义bootm命令:

U_BOOT_CMD(

bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,


do_bootm:

获取verify变量——获取镜像加载地址(启动参数,flash地址)——读取镜像头做校验——检验cpu类型和内核压缩方式——判断操作系统类型——do_bootm_linux(准备内核参数和启动环境,内核参数根据bootargs环境变量设置tag)

5、命令

uboot的命令通过U_BOOT_CMD添加,一般在/u-boot/common目录下存在指令相关的定义文件

通过main.c里的run_command解析执行命令

6、bootcmd和bootargs

bootcmd:自启动时执行的首条命令。由此可见bootcmd的重要性,要是第一条命令就错了,那后面的就无法执行下去了。bootcmd指定了要从flash的哪里读数据,读多少数据,读到内存(一般为SDRAM)的哪里去,再从内存的哪里启动(执行)。
例子:bootcmd=nand read 30008000 100000 800000;bootm 30008000。

此处,bootcmd从flash的100000(即1M)处开始读800000(即8M)大小的数据到内存的30008000地址处,再从30008000地址处执行代码。(从何处读必须与之前写的位置对应,从哪里开始写的就从哪里开始读,切记不可弄错,以保证数据的完整性。而读多少就没有硬性要求,只需要记住,读多不读少,稍微读大些就行了,但不可过大,不可读到下一段数据中,即保证读的大小在你做的内核的分区表中内核代码分区里。读到内存中就没什么好讲的,代码都是在内存中运行的。bootm即指定从30008000处启动内核。)

bootargs:传递给内核启动参数。也是非常重要的一个环境变量,如果传递了错误的参数,内核便会启动失败。
例子:bootargs=noinitrd root=/dev/mtdblock4 rootfstype=jffs2 init=/linuxrc console=ttyS0,115200

此处各参数的含义:
noinitrd:
当系统不是使用ramdisk文件系统(内嵌在内核里的文件系统)启动时,指定使用该参数。
如果使用ramdisk文件系统,需要指定initrd=r_addr,size, r_addr表示initrd在内存中的位置,size表示initrd的大小。
root:
用来指定rootfs(根文件系统)的挂载位置(root=/dev/mtdblock4在第四分区)
可在后面加上权限(如rw)
rootfstype:
当文件系统不是内嵌在内核中,而是与内核一起存放在flash中时(例如jffs2),需指定文件系统类型,否则无法挂载。
init:
init指定的是内核启起来后,进入系统中运行的第一个脚本,一般init=/linuxrc(linuxrc是系统启动时执行的脚本,用于加载模块驱动等,运行在init进程之前,退出后才调用init,一般linuxrc为一个链接,连接到bin/busybox,所以写法不固定)
console:
指定串口终端设备,指定与终端相应的串口波特率,指定之后可用secureCRT之类的终端仿真程序操作开发板。

7、uboot和内核传参 

/u-boot/arch/arm/lib/bootm.c中do_bootm_linux启动kernel

int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;
void (*theKernel)(int zero, int arch, uint params);


#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif


if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
return 1;


theKernel = (void (*)(int, int, uint))images->ep;


#if CONFIG_SYS_MACH_TYPE == MACH_TYPE_MAXIMASP
/* Maxim ASP: Automatically put the command line at kernel address - 0x7f00 */
bd->bi_boot_params = images->ep - 0x7f00;
#endif


s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}


show_boot_progress (15);


debug ("## Transferring control to Linux (at address %08lx) ...\n",
       (ulong) theKernel);

//以下存储传输给kernel的参数
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
setup_end_tag (bd);
#endif


/* we assume that the kernel is in place */
#ifdef CONFIG_FORCE_SHOWBOOTINFO
{
bool was_silent = maximasp_unsilence();
#endif
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_FORCE_SHOWBOOTINFO
maximasp_resilence(was_silent);
}
#endif


#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif


cleanup_before_linux ();

    //do_bootm_linux函数调用theKernel (0, machid, bd->bi_boot_params)去启动内核并传递参数,可以看见r0是machid,    r2是bi_boot_params参数的地址。

theKernel (0, machid, bd->bi_boot_params);
/* does not return */


return 1;
}

你可能感兴趣的:(嵌入式linux)