嵌入式Linux——分析u-boot运行过程(1):u-boot第一阶段代码

本文详细介绍U-Boot 1.1.6版本在JZ2440开发板上的启动流程,包括代码重定位、硬件初始化等关键步骤。通过分析,揭示U-Boot如何高效引导并启动Linux内核。

简介:

        本文主要介绍在u-boot-1.1.6中代码的运行过程,以此来了解在u-boot中如何实现引导并启动内核。这里我们先介绍u-boot的第一阶段代码:单板各个硬件的初始化,我们只有做好了硬件基础才能为下一阶段的引导和启动内核做准备。同时我们也会在第二部分介绍u-boot的一些强大功能。

声明:

        本文主要是看了韦东山老师的视频后所写,希望对你有所帮助。

u-boot版本 : u-boot-1.1.6

开发板 : JZ2440

Linux内核 : Linux-2.6.22.6

 

u-boot第一阶段代码:

        原想的是写完BootLoader就可以对u-boot这部分的内容告一段落了,但是仔细查看u-boot代码后发现如果只写了BootLoader而不去完成u-boot的功能描述总还是有些遗憾的。毕竟u-boot无论在代码规范性,功能复杂性上都要远超于BootLoader。同时只有当你可以真正看懂u-boot的时候你才有能力去移植一个现有的u-boot到你想要的开发板。而不是去使用BootLoader去完成一些简单的操作。而如果你想真正的了解u-boot,那你就要按着u-boot的代码一步一步的看下去。同时我希望大家可以在看u-boot的时候结合我上一篇文章:嵌入式Linux——写jz2440BootLoader的第一阶段代码一起看,因为u-boot和BootLoader其实在硬件初始化这方面做得还是很相似的。而真正区分u-boot和BootLoader的是后面的第二阶段代码。下面我们就按着u-boot的代码来了解u-boot的实现。

标识开始以及各个偏移向量:

        当我们做代码重定位即从nand中将u-boot代码转移到SDRAM的时候,我们要知道代码在nand中的地址,但是这时我们的当前程序并不在代码开始位置。所以我们要在代码的开始位置用一个标识来标志代码开始的位置。这个标识我们在代码中用_start表示。而对于其他的突发情况或者错误的情况我们就要用不同的偏移向量引导到对应的操作函数处。这里的开始标识和各个偏移向量的代码为:

.globl _start
_start:	b       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

        而其中reset就是当程序开始执行后所要跳转到的地址,这里其实就是我们u-boot代码真正开始的位置。

设置CPU为管理模式:

        这里一开始就设置CPU为管理模式,我想也就是在这里直接给了CPU特权以便让他处理以后的事情更加方便。同时我们想说最开始就从CPU设置,然后慢慢从内向外这样的设计也是不错的。而设置CPU为管理模式的代码为;

/*
 * the actual reset code
 */

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0,cpsr
	bic	r0,r0,#0x1f
	orr	r0,r0,#0xd3
	msr	cpsr,r0

        我多截了一些代码,其中包括了reset的部分,从上面的注释我们可以知道,这才是u-boot真正开始位置的代码。而关于设置CPU为管理模式的汇编语句就是操作程序状态寄存器中的0~4位为0b10011。

关看门狗,关中断:

        这里我们将关看门狗和关中断放到一起是因为在代码中他们几乎是在一起的。同时我个人认为关看门狗和关中断同样都是准备工作。都是为了防止CPU在执行代码时出现突然重启或者突发中断,进而打断原有程序的进程。而关于关看门狗,关中断的代码为:

/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON		0x15300000
# define INTMSK		0x14400008	/* Interupt-Controller base addresses */
# define CLKDIVN	0x14800014	/* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON		0x53000000
# define INTMOD     0X4A000004
# define INTMSK		0x4A000008	/* Interupt-Controller base addresses */
# define INTSUBMSK	0x4A00001C
# define CLKDIVN	0x4C000014	/* clock divisor register */
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
	ldr     r0, =pWTCON
	mov     r1, #0x0
	str     r1, [r0]

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK
	str	r1, [r0]
#if defined(CONFIG_S3C2410)
	ldr	r1, =0x3ff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
#endif

        从上面的代码中我们可以看出u-boot有很大的兼容性,如上面的代码他既可以让2400用也可以让2410用,只要你定义你所用单板的宏,他就会对你所选的单板进行设置。而我们的程序只定义了2410而没有定义2400,所以我们只用2410的代码就可以了而不用去看2400相关的代码。

底层相关初始化:

        而在初始化底层硬件之前,我们会看到下面的代码:

	adr	r0, _start		/* r0 <- current position of code   */
	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
	cmp     r0, r1   

        他的意思是通过比较当前地址和连接地址来确定当前是否已经在RAM上运行,如果是在RAM中运行则不做底层相关的初始化,如果不在RAM上运行,则做底层相关的初始化,这里我们的程序还在nand的4Kstepstoneing中运行,并没有在RAM中运行,所以这时我们要做底层相关的初始化,即调用  cpu_init_crit :

	blne	cpu_init_crit

        下面我们跳到cpu_init_crit处,看他做了什么工作:

cpu_init_crit:
	/*
	 * flush v4 I/D caches
	 */
	mov	r0, #0
	mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */
	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */

	/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)
	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)
	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache
	mcr	p15, 0, r0, c1, c0, 0

	/*
	 * before relocating, we have to setup RAM timing
	 * because memory timing is board-dependend, you will
	 * find a lowlevel_init.S in your board directory.
	 */
	mov	ip, lr
	bl	lowlevel_init
	mov	lr, ip
	mov	pc, lr

        从上面看这个底层相关的初始化做了三件事情:

1. 关cache,关TLB

2. 关MMU,并开I-cache

3. 跳转到lowlevel_init处,做SDRAM的初始化

        第一和第二件事都是对协处理器的操作,我想大家看看2440的芯片手册或者百度一下就可以很清楚了。这里我为大家推荐一个网址,大家如果不想自己动手去查可以直接点开这个博客:ARM协处理器CP15寄存器详解。下面我们就要讲跳转lowlevel_init执行了,但是我想在讲之前说一下下面的代码:

	mov	ip, lr
	bl	lowlevel_init
	mov	lr, ip
	mov	pc, lr

        想问一下大家知不知道我们为什么要有上面这样的代码?这是因为lr寄存器中保存的是我们这个程序的返回地址,而当我们要去调用其他的子程序的时候要先将这个寄存器中的值存到其他的寄存器中,不然当我们调用子函数的时候lr寄存器中会自动放入子程序的返回值,如果我们没有将当前程序的lr值保存到其他寄存器中,那么我们执行完子程序后再执行当前程序,但是执行完当前程序后我们却不能跳回到调用当前函数的函数了,这样函数就会跑飞了。所以当发生多次函数调用的时候我们要记得及时将lr的值保存到其他的寄存器中。

        讲完上面的知识,下面我们跳到lowlevel_init来看看这个函数中做了什么事:

.globl lowlevel_init
lowlevel_init:
	/* memory control configuration */
	/* make r0 relative the current location so that it */
	/* reads SMRDATA out of FLASH rather than memory ! */
	ldr     r0, =SMRDATA
	ldr	r1, _TEXT_BASE
	sub	r0, r0, r1
	ldr	r1, =BWSCON	/* Bus Width Status Controller */
	add     r2, r0, #13*4
0:
	ldr     r3, [r0], #4
	str     r3, [r1], #4
	cmp     r2, r0
	bne     0b

	/* everything is fine now */
	mov	pc, lr

	.ltorg
/* the literal pools origin */

SMRDATA:
    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) 
    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    .word 0xb1
    .word 0x30
    .word 0x30

        这里你会发现上面的代码其实就是对SDRAM的初始化,而他与我们在BootLoader中不一样的部分就是SMRDATA这部分的数据形式不一样而已。在这里他详细的描述了在各个寄存器中各个参数的设置。

设置栈空间:

        讲完底层相关的初始化,我们现在就要讲栈的设置了。前面的代码都是用汇编语言写的,所以可以不用设置栈空间,但是下面的程序中要调用C函数了,所以这里设置栈是非常有必要的。而栈相关的代码为:

	/* Set up the stack						    */
stack_setup:
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */

#ifdef CONFIG_USE_IRQ
	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
	sub	sp, r0, #12		/* leave 3 words for abort-stack    */

        这里的设置栈不像我们设置BootLoader时那么简单。因为u-boot中添加了一些功能,相应的就会有更多的数据,同样也就需要更过的栈空间来存放这些数据。所以我们要在这里为他们留出空间。而这些数据有:

CFG_MALLOC_LEN : 为堆预留的空间

CFG_GBL_DATA_SIZE : 为全局变量预留的空间

CONFIG_STACKSIZE_IRQ : 为中断预留的空间

CONFIG_STACKSIZE_FIQ :为快中断预留的空间 

        为上述数据留好空间后地址指针再减12字节的空间,我们就可以设置当前的栈顶了。而设置好栈之后我们就可以调用C函数了,而这之后栈空间向下生长。

设置时钟:

        设置好栈空间后就可以调用C函数来实现更加复杂的功能了。而我们首先要做的就是提速,因为只有CPU的运行速率提上来了,我们再运行其他的代码时才能更加的快速,便捷。而设置时钟的代码为:

void clock_init(void)
{
	S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000;

    /* support both of S3C2410 and S3C2440, by www.100ask.net */
    if (isS3C2410)
    {
        /* FCLK:HCLK:PCLK = 1:2:4 */
        clk_power->CLKDIVN = S3C2410_CLKDIV;

        /* change to asynchronous bus mod */
        __asm__(    "mrc    p15, 0, r1, c1, c0, 0\n"    /* read ctrl register   */  
                    "orr    r1, r1, #0xc0000000\n"      /* Asynchronous         */  
                    "mcr    p15, 0, r1, c1, c0, 0\n"    /* write ctrl register  */  
                    :::"r1"
                    );
        
        /* to reduce PLL lock time, adjust the LOCKTIME register */
        clk_power->LOCKTIME = 0xFFFFFFFF;

        /* configure UPLL */
        clk_power->UPLLCON = S3C2410_UPLL_48MHZ;

        /* some delay between MPLL and UPLL */
        delay (4000);

        /* configure MPLL */
        clk_power->MPLLCON = S3C2410_MPLL_200MHZ;

        /* some delay between MPLL and UPLL */
        delay (8000);
    }
    else
    {
        /* FCLK:HCLK:PCLK = 1:4:8 */
        clk_power->CLKDIVN = S3C2440_CLKDIV;

        /* change to asynchronous bus mod */
        __asm__(    "mrc    p15, 0, r1, c1, c0, 0\n"    /* read ctrl register   */  
                    "orr    r1, r1, #0xc0000000\n"      /* Asynchronous         */  
                    "mcr    p15, 0, r1, c1, c0, 0\n"    /* write ctrl register  */  
                    :::"r1"
                    );

        /* to reduce PLL lock time, adjust the LOCKTIME register */
        clk_power->LOCKTIME = 0xFFFFFFFF;

        /* configure UPLL */
        clk_power->UPLLCON = S3C2440_UPLL_48MHZ;

        /* some delay between MPLL and UPLL */
        delay (4000);

        /* configure MPLL */
        clk_power->MPLLCON = S3C2440_MPLL_400MHZ;

        /* some delay between MPLL and UPLL */
        delay (8000);
    }
}

        从上面的代码中我们知道这个函数是兼容2410和2440的,当在2410中运行时,走if语句后到else语句前的代码,而在2440中运行时,则走else语句中的代码。在2410中CPU被提速到200MHz,而在2440中CPU被提速到400MHz。而具体的操作就是对寄存器的设置了大家可以去看一下2440的芯片手册。

代码的重定位:

        这里重定位代码就非常的重要了,因为u-boot的体积远远大于4k,所以我们必须将nand中的u-boot代码复制到SDRAM中。下面就是我们重定位的代码了:

relocate:				/* relocate U-Boot to RAM	    */
	adr	r0, _start		/* r0 <- current position of code   */
	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
	cmp     r0, r1                  /* don't reloc during debug         */
	beq     clear_bss
	
	ldr	r2, _armboot_start
	ldr	r3, _bss_start
	sub	r2, r3, r2		/* r2 <- size of armboot            */
#if 1
	bl  CopyCode2Ram	/* r0: source, r1: dest, r2: size */

        这里有测试当前程序是否在RAM中的语句,如果当前程序在RAM中,那么我们将跳过重定位代码而去做清bss的任务。如果没有在RAM中我们就要调用C函数CopyCode2Ram来将nand中的代码复制到SDRAM中。而这里数据传输的三要素:源,目的和长度,分别为:

源 : _start (这里的_start就是我们在u-boot代码开始位置标识的代码开始地址)

目的: _TEXT_BASE (这里_TEXT_BASE就是我们u-boot代码所以复制到的SDRAM中的目的地址)

长度 : _bss_start - _armboot_start (这里的长度就是从代码开始的位置到bss开始的位置)

        而CopyCode2Ram函数其实和我们在BootLoader中所用的函数相同,这里就不再细说,我们看函数代码:

int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size)
{
    unsigned int *pdwDest;
    unsigned int *pdwSrc;
    int i;

    if (bBootFrmNORFlash())
    {
        pdwDest = (unsigned int *)buf;
        pdwSrc  = (unsigned int *)start_addr;
        /* 从 NOR Flash启动 */
        for (i = 0; i < size / 4; i++)
        {
            pdwDest[i] = pdwSrc[i];
        }
        return 0;
    }
    else
    {
        /* 初始化NAND Flash */
		nand_init_ll();
        /* 从 NAND Flash启动 */
        nand_read_ll_lp(buf, start_addr, (size + NAND_BLOCK_MASK_LP)&~(NAND_BLOCK_MASK_LP));
		return 0;
    }
}

        而从上面的函数我们可以看出这个u-boot不仅可以在nand上使用,同时还可以在nor上使用。

清bss:

        做完上面的代码重定位,我们就要清bss来做u-boot第一阶段代码的扫尾工作了,而这里的清bss的代码使用汇编所写,但是也不难看懂,其主要的意思是使用一个循环语句来将bss段中的各个参数初始化为0 。而清bss的代码为:

clear_bss:
	ldr	r0, _bss_start		/* find start of bss segment        */
	ldr	r1, _bss_end		/* stop here                        */
	mov 	r2, #0x00000000		/* clear                            */

clbss_l:str	r2, [r0]		/* clear loop...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l

        做完上面的工作我们就可以跳转到u-boot的第二阶段了。在看完这章的讲解后,大家是不是觉得其实u-boot中的第一阶段代码和BootLoader中的第一阶段代码十分相似啊。确实他们都是做的单板硬件的初始化,而基本的硬件就那么几个,所以两个的第一阶段代码十分相似。但是到了第二阶段就不一样了。第二阶段u-boot的功能比BootLoader的强大很多,故代码量和复杂性更是多很多,所以到时候分析u-boot第二阶段代码就不会像分析BootLoader第二阶段代码那么简单了。同时又由于这部分功能的增加,代码量的增加将会对我们学习u-boot提供更多的帮助。希望我有耐心有毅力写完,写详细第二阶段代码以不辜负我这么长时间的学习。

概要:   DevCon 实用工具是一种命令行实用工具,可以替代设备管理器。使用 DevCon,您可以启用禁用、重新启动、更新、删除查询单个设备或一组设备。DevCon 提供与开发人员有关但无法在设备管理器中看到的信息。   您可以将 DevCon 用于 Windows 2000 、Windows XPWindows vista。不能将 Devcon 用于 Microsoft Windows 95、Windows 98、或 Windows Millennium Edition。   下载:http://download.microsoft.com/download/1/1/f/11f7dd10-272d-4cd2-896f-9ce67f3e0240/devcon.exe 用法及参数说明:   devcon.exe [-r] [-m:\\] [...]   -r 如果指定它,在命令完成后若需要则重新启动计算机。    是目标计算机的名称。    是将要执行的命令(如下所示)。   ... 是命令需要的一个或多个参数。   要获取关于某一特定命令的帮助,请键入:devcon.exe help   classfilter 允许修改类别筛选程序。   classes 列出所有设备安装类别。   disable 禁用与指定的硬件或实例 ID 匹配的设备。   driverfiles 列出针对设备安装的驱动程序文件。   drivernodes 列出设备的所有驱动程序节点。   enable 启用与指定的硬件或 实例 ID 匹配的设备。   find 查找与指定的硬件或 实例 ID 匹配的设备。   findall 查找设备,包括那些未显示的设备。   help 显示此信息。   hwids 列出设备的硬件 ID。   install 手动安装设备。   listclass 列出某一安装类别的所有设备。   reboot 重新启动本地计算机。   remove 删除与特定的硬件或 实例 ID 匹配的设备。   rescan 扫描以发现新的硬件。   resources 列出设备的硬件资源。   restart 重新启动与特定的硬件或 实例 ID 匹配的设备。   stack 列出预期的设备驱动程序堆栈。   status 列出设备的运行状态。   update 手动更新设备。   UpdateNI 手动更新设备,无用户提示   SetHwID 添加、删除更改根枚举设备的硬件 ID 的顺序。 示例:   devcon -m:\\test find pci\* 列出计算机 test 上的所有已知 PCI 设备。(通过使用 -m,您可以指定一个目标计算机。您必须使用“进程间通信”(IPC) 访问此计算机。)   devcon -r install Windows directory\Inf\Netloop.inf *MSLOOP 安装一个新的 Microsoft 环回适配器实例。这将创建一个新的根枚举设备节点,使用此节点您可以安装“虚拟设备”,如环回适配器。如果需要重新启动计算机,此命令还将以安静模式重启计算机。   devcon classes 列出所有已知的安装类别。输出结果包含短的未本地化的名称(例如,“USB”)描述性名称(例如,“通用串行总线控制器”)。 禁用启用网卡的步骤:   1.用devcon hwids PCI*命令得到所有以PCI开头的设备。这时会列出很多设备,那么哪个才是网卡对应的呢?   2.打开设备管理器,展开网络适配器,找到网卡的名称,然后记住到刚才得到的列表中找对应的Name,然后你会在下面看到好几个ID,随便挑一个就行   3.用devcon disable "PCI\VEN_11AB&DEV_4380&SUBSYS_301B17AA&REV_10"禁用网卡(启用的话讲disable换成enable就行了)   4.其实用PCI开头得到的几组设备中一般第一个就是网卡设备 sysdzw 16:01 2010-11-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值