这一步要做的事是将已经下载到内存0x50008000地址中的linux内核启动起来。启动位于内存中的内核并不是简单地将pc指针指向内核的入口地址,而是要根据内核启动的要求,将相关启动参数初始化好之后,再将这个地址赋值给内核启动函数,通过启动函数调用启动参数的方式才能正确启动内核。

uboot中已经实现好一个现成的bootm命令用于启动位于内存中的linux内核,我们可以参考这个命令的实现来完成自己的内核启动函数。但这个命令启动的内核是uImage格式的内核,uImage格式的内核是专门用于uboot启动的内核,相对于zImage格式的内核,在前面会多出一部分的信息用于表示操作系统类型,是否压缩,文件大小等信息,这部分的内容在uboot代码中有如下定义:

typedef struct image_header {
	uint32_t	ih_magic; /* Image Header Magic Number  */
	uint32_t	ih_hcrc;  /* Image Header CRC Checksum  */
	uint32_t	ih_time;  /* Image Creation Timestamp   */
	uint32_t	ih_size;  /* Image Data Size            */
	uint32_t	ih_load;  /* Data  Load Address         */
	uint32_t	ih_ep;    /* Entry Point Address        */
	uint32_t	ih_dcrc;  /* Image Data CRC Checksum    */
	uint8_t		ih_os;    /* Operating System      */
	uint8_t		ih_arch;  /* CPU architecture      */
	uint8_t		ih_type;  /* Image Type            */
	uint8_t		ih_comp;  /* Compression Type      */
	uint8_t		ih_name[IH_NMLEN];	/* Image Name  */
} image_header_t; 

为简化系统设计,我们使用zImage格式的内核来启动,这样就可以跳过这段关于启动头的检验,为此,需要分析bootm命令的实现。

bootm命令的实现函数是do_bootm。do_bootm函数在检查了启动头的信息后,如果判断要启动的操作系统是Linux,会调用do_bootm_linux来启动linux内核,而do_bootm_linux这个函数最终会把内核的入口地址赋值给函数指针theKernel,通过theKernel来启动内核,其定义如下:

void (*theKernel)(int zero, int arch, uint params);

因此,我们也只需要将入口地址0x50008000赋值给这个指针,然后调用这个函数指针即可。

下面的关键是构造启动函数的参数。启动函数一共需要三个参数,第一个按照uboot的实现直接填0即可。第二个是CPU的编号,按照uboot源代码中的定义,s3c6410的编号是1626。第三个参数是内核启动的关键,这是一个内存地址,我们要在这个地址开始的一片区域按照要求填充内核启动的参数信息,比如内存大小,页大小,命令行参数等。每一项参数信息都是一个struct tag结构体,我们就是要按照要求将相关的struct tag结构体连续地存放在这个起始地址处。以下是struct tag的定义:

struct tag {
	struct tag_header hdr;
	union {
		struct tag_core      core;
		struct tag_mem32     mem;
		struct tag_videotext videotext;
		struct tag_ramdisk   ramdisk;
		struct tag_initrd    initrd;
		struct tag_serialnr  serialnr;
		struct tag_revision  revision;
		struct tag_videolfb  videolfb;
		struct tag_cmdline   cmdline;
		struct tag_acorn     acorn;
		struct tag_memclk    memclk;
	} u;
}; 

Alt text

按照uboot的实现,我们只需要填充core参数,mem参数,以及cmdline参数即可,以下是代码实现:

#define SDRAM_KERNEL_START 0x50008000
#define SDRAM_TAGS_START   0x50000100
#define SDRAM_ADDR_START   0x50000000
#define SDRAM_TOTAL_SIZE   0x10000000  /*256MB*/

void (*theKernel)(int , int , unsigned int );

struct tag *pCurTag;

const char *cmdline = "console=ttySAC0,115200 init=/init";

void setup_core_tag()
{
     pCurTag = (struct tag *)SDRAM_TAGS_START; /*启动参数存放在0x50000100处*/
     
     pCurTag->hdr.tag = ATAG_CORE;
     pCurTag->hdr.size = tag_size(tag_core); 
     
     pCurTag->u.core.flags = 0;
     pCurTag->u.core.pagesize = 4096;
     pCurTag->u.core.rootdev = 0;
     
     pCurTag = tag_next(pCurTag);
}

void setup_mem_tag()
{
     pCurTag->hdr.tag = ATAG_MEM;
     pCurTag->hdr.size = tag_size(tag_mem32); 
     
     pCurTag->u.mem.start = SDRAM_ADDR_START;
     pCurTag->u.mem.size = SDRAM_TOTAL_SIZE;
     
     pCurTag = tag_next(pCurTag);
}

void setup_cmdline_tag()
{
     int linelen = strlen(cmdline);
     
     pCurTag->hdr.tag = ATAG_CMDLINE;
     pCurTag->hdr.size = (sizeof(struct tag_header)+linelen+1+4)>>2;
     
     strcpy(pCurTag->u.cmdline.cmdline,cmdline);
     
     pCurTag = tag_next(pCurTag);
}

void setup_end_tag()
{
    pCurTag->hdr.tag = ATAG_NONE;
    pCurTag->hdr.size = 0;
}

void boot_linux()
{
    //1. 获取Linux启动地址
    theKernel = (void (*)(int , int , unsigned int ))SDRAM_KERNEL_START;
    
    //2. 设置启动参数
    //2.1 设置核心启动参数
    setup_core_tag();
    
    //2.2 设置内存参数
    setup_mem_tag();
    
    //2.3 设置命令行参数
    setup_cmdline_tag();
    
    //2.4 设置参数结束标志
    setup_end_tag();
    
    //3. 启动Linux系统
    theKernel(0,1626,SDRAM_TAGS_START);
}

  • 无标签