您的位置:首页 > 其它

Uboot 怎样传递参数启动内核

2011-09-25 15:20 411 查看
这里,主要实现了以下三点:

1、 从nandflash启动内核;

2、 从sdram启动内核;

3、 利用kermit协议下载内核到sdram中去。

首先,从nandflash启动内核,这里有一个前提就是nandflash要分好区,并且nandflash要有内核映像zImage,但是到目前为止,lboot还没有实现nandflash的分区和下载内核到nandflash的功能,所以暂且借用supervivi首相将nandflash分好区,并下载内核映像zImage到内核分区。如果以后时间充足的话,还要实现类似supervivi的格式化nandflash,为nandflash分区,下载东西到nandflash中,我想格式化nandflash的话就是再实现一个擦除nandflash的函数,前面的nand.c只实现了nand的读函数,而下载东西到nandflash中就是在实现一个nand写函数,而分区呢,个人觉得可以定义几个宏,分别代表每个区的启始地址,然后在实现比如往nandflash中烧写bootloader的时候就往nand写函数传入bootloader的起始地址,烧写内核映像的时候,就传入内核分区的起始地址就可以了,具体的等看看vivi的源码是怎么实现的,然后在米葫芦画瓢的实现。好了,现在假设在nand的0x00060000处已经存放的linux内核映像zImage,那么怎么实现从nand启动内核呢?其实很简单,简单的调用nandread函数将内核映像拷贝到sdram中的0x3380,0000地址处,拷贝完之后再跳到这个地址就可以启动内核了,代码实现如下:

/********** SDRAMand Nand Space Allocation ****************/

#defineSDRAM_TOTAL_SIZE 0x04000000 //64M SDRAM

#defineSDRAM_ADDR_START 0x30000000 //起始地址是0x3000,0000

#defineSDRAM_LBOOT_START 0x33000000 //LBOOT的存放地址

#defineSDRAM_TAGS_START 0x30000100 //tag列表的存放地址

#define SDRAM_KERNEL_START 0x33800000 //kernel的存放地址

#defineNAND_KERNEL_START 0x00060000

#define NAND_KERNEL_SZIE 0x00500000

int boot_linux()

{

setup_core_tag( (void *)SDRAM_TAGS_START); /* standard stCore ulTag 4kpagenSize */

setup_mem_tag(SDRAM_ADDR_START,SDRAM_TOTAL_SIZE); /* 64Mb at0x30000000 */

setup_cmdline_tag( CmdLine );

setup_end_tag(); /* end of ulTags */

s3c2440_nand_init();

printf("copy Kernel from nandflash toSDRAM... ...\n\r");

s3c2440_nand_read((unsignedchar *)SDRAM_KERNEL_START, NAND_KERNEL_START, NAND_KERNEL_SZIE);

printf("copy Kernel from nandflash toSDRAM has finished!\n\r");

printf("Now jump to the kernelentry!\n\r");

theKernel = (void (*)(int, int, unsignedint))SDRAM_KERNEL_START;

theKernel(0, S3C2440_MATHINE_TYPE,SDRAM_TAGS_START);

return 0;

}

说道这里不得不谈下内核的启动过程,我们知道bootloader的最终目的是做什么呢?就是要实现启动内核的功能,那么我们现在bootloader已经完成了硬件的初始化,同时也为内核的启动准备好了条件,那么现在要做的就是传递内核启动参数给内核,然后内核接收这些参数并启动,为什么要传递参数呢,linux内核是强大的,支持N种不同的开发板,所以你要告诉内核我是什么板子?比如我是mini2440开发板,那么linux内核就会调用mini2440相关的函数来实现在mini2440这块板子上运行,当然前提是你要为你的板子移植好linux内核。

那么bootloader和内核参数之间是怎么传递的呢?是通过tag list的形式传递的,网上很多讲这方面的内容,我只阐述一个大概的思想:bootloader和内核使用约定俗成的形式定义一系列的参数,然后bootloader把内核需要的信息放在这些参数里面,之后将这个参数放在一个地址处,然后将这个地址传给内核,内核就会到这个地址处来读这些参数,这样就完成内核参数的传递了。

参数的统一形式如下:

struct Atag {

struct TagHeader stHdr;

union {

struct TagCore stCore;

struct TagMem32 stMem;

struct TagCmdline stCmdLine;

}u;

};

struct TagHeader {

unsigned int nSize; //以字为单位即4个字节

unsigned int ulTag;

};

Atag由tag头部TagHeader和内容u来组成,TagHeader有两个参数:nSize代表整个Atag的大小,ulTag代表这个Atag是什么类型的,类型定义如下:

#define ATAG_NONE 0x00000000 //必须有,且放在最后一个位置,只有头部,没有内容

#define ATAG_CORE 0x54410001 //必须有,且放在第一个位置

#define ATAG_MEM 0x54410002 //代表内存

#defineATAG_CMDLINE 0x54410009 //代表命令行参数

数字没有什么具体的含义,就是一种标识,按照linux内核里面的定义值即可。

所以在跳转到内核之前,一定要先把这些参数的值设好,设置的函数如下:

static struct Atag*pCurTag; /*used to point at the current ulTag */

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

const char *CmdLine= "root=/dev/mtdblock3 console=ttySAC0,115200 mem=64M init=/linuxrc";

static void setup_core_tag(void*pStartAddr)

{

pCurTag = (struct Atag *)pStartAddr; /*start at given address */到时侯传递0x30000100,即参数列表在sdram中存放地址

pCurTag->stHdr.ulTag = ATAG_CORE; /*start with the stCore ulTag */类型

pCurTag->stHdr.nSize =TAG_SIZE(TagCore); /*nSize the ulTag */大小

pCurTag->u.stCore.ulFlags = 1; /* ensure read-only */内容

pCurTag->u.stCore.nPageSize = 4096; /*systems pagenSize (4k) */

pCurTag->u.stCore.ulRootDev = 0; /*zero (typicaly overidden from commandline )*/

pCurTag = TAG_NEXT(pCurTag); /*move pointer to next ulTag */

}

static void setup_mem_tag(unsignedint start, unsigned int len) //设置内存参数

{

pCurTag->stHdr.ulTag = ATAG_MEM; /*Memory ulTag */

pCurTag->stHdr.nSize =TAG_SIZE(TagMem32); /* nSize ulTag */

pCurTag->u.stMem.ulStart = start; /*Start of memory area (physical address) */

pCurTag->u.stMem.nSize = len; /*Length of area */

pCurTag = TAG_NEXT(pCurTag); /*move pointer to next ulTag */

}

static voidsetup_cmdline_tag(const char * line)

{

int linelen = strlen(line);

if(!linelen)

return; /* do not insert aulTag for an empty commandline */

pCurTag->stHdr.ulTag =ATAG_CMDLINE; /*Commandline ulTag */

pCurTag->stHdr.nSize = (sizeof(structTagHeader) + linelen + 1+4) >> 2;

strcpy(pCurTag->u.stCmdLine.cCmdLine,line); /* place commandline into ulTag */

pCurTag = TAG_NEXT(pCurTag); /*move pointer to next ulTag */

}

static voidsetup_end_tag(void)

{

pCurTag->stHdr.ulTag = ATAG_NONE; /* Empty ulTag ends list */

pCurTag->stHdr.nSize = 0; /*zero length */

}

这里参数是以链表的形式存放的,赋值主要设计两个宏:

#define TAG_NEXT(t) ((struct Atag *)((unsigned int *)(t) +(t)->stHdr.nSize))

#defineTAG_SIZE(type) ((sizeof(structTagHeader) + sizeof(struct type)) >> 2)//左移2的目的前面说过size是以字为单位即4个字节,左移2相当于除以4,这样就可以算出以4个字节为单位,参数所占的大小了。

还有一点要说明的是:

pCurTag->stHdr.nSize= (sizeof(struct TagHeader) + linelen + 1+4) >> 2;

这里为什么要+1+4,+1代表着加上’\0’,因为strlen并没有将‘\0’算上,+4为什么呢?比如我实际长度只有2,那么以4字节为单位的话,长度应该是1,你钥匙直接用2/4的话,那么长度为0了,加上4就能保证长度为1,主要就是这么一样意思。

所以在刚上来从nand启动时,我们首先要将参数设置好,然后在复制内核映像到SDRAM中,然后跳到内核入口执行内核映像,那么怎么跳到内核入口呢,其实就是跳到内核的存放地址即可:

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

theKernel = (void (*)(int, int, unsignedint))SDRAM_KERNEL_START;

theKernel(0, S3C2440_MATHINE_TYPE, SDRAM_TAGS_START)

这里首先将内核存放地址强制转换,然后赋给函数指针theKernel,然后传入机器码,内核参数列表地址即可。这样,就可以从nandflash启动内核了。

接下来:实现从SDRAM启动内核,思路是一样的,看实现代码:

intboot_linux_sdram()

{

setup_core_tag( (void *)SDRAM_TAGS_START); /* standard stCore ulTag 4k pagenSize*/

setup_mem_tag(SDRAM_ADDR_START,SDRAM_TOTAL_SIZE); /* 64Mb at 0x30000000*/

setup_cmdline_tag( CmdLine );

setup_end_tag(); /*end of ulTags */

theKernel = (void (*)(int, int, unsignedint))SDRAM_KERNEL_START;

theKernel(0, S3C2440_MATHINE_TYPE, SDRAM_TAGS_START);

return 0;

}

这里就要求我们首先在SDRAM_KERNEL_START处存放有内核映像,那么我们就得首先实现下载内核映像到SDRAM中的功能,这里利用的kermit协议来实现了,关于kermit协议的介绍,我会在附件中贴出,里面介绍的很详细。

要实现利用kermit协议使用串口下载内核映像到SDRAM中,我们要做的工作就是实现接收部份的代码,因为我们是从PC->开发板,发送部份的代码是由PC来实现的,如PC上的C-Kermit软件,而接收部份的代码是很简单的,主要实现发送ACK包就可以了,看下面的程序前,首先要把附件中kermit协议看了,里面的包格式,使用的编码方式等。发送ACK包是通过写串口和我们开发板上的串口联系起来的,看程序:

发送ACK包程序:

#defineKERM_ACK_LEN 0x10

static voidsend_ack_packet(unsigned int seq) //发送ACK包

{

unsigned char buf[KERM_ACK_LEN];

int index = 0, check_sum = 0;

buf[index++] = MARK_START; //MARK段:SOH

buf[index++] = ENC_PRINT(3); //LEN段:0x3+0x20

buf[index++] = seq + KERM_KEY_SPACE; //SEQ段:第几个包

buf[index++] = KERM_TYPE_ACK; //TYPE段:ACK包

buf[index] = '\0';

index = 1; //CHECK段只计算LEN段和SEQ段

while (buf[index])

{

check_sum += buf[index]; //采用单字节算术校验和

index++;

}

buf[index++] = (KERM_KEY_SPACE + (0x3f & (check_sum + (0x03 & (check_sum>> 6))))) & 0xff;//在编码之前把第6、7两位提出加到低6位上

buf[index++] = KERM_KEY_TERM;

buf[index] = '\0';

#ifdef CONFIG_TOPHALF_KERMIT

UartPuts((const char *)buf);

#else

index = 0;

while (buf[index])

GtUartWriteByte(buf[index++]); //发送ACK包,使用串口发送

#endif

}

接收文件程序:

#defineKERM_BUF_LEN 128

int GtSerialLoad(void*pLoadAddr) //接收kermit send发送过来的数据到pLoadAddr地址处,pLoadAddr就为内核在SDRAM

//中的存放地址

{

unsigned char buf[KERM_BUF_LEN];

unsigned char curr_char;

int index;

int check_sum, len, seq, type;

unsigned char *pAddr = (unsigned char*)pLoadAddr; //pAddr =内核在SDRAM中的存放地址

do {

while(MARK_START != GtUartReadByte()); //没收到启始包,循环等待

index = 0;

while(KERM_KEY_TERM !=(buf[index] = GtUartReadByte())) //接收一包数据

index++;

index = 0;

/* length decode */

len = buf[index++];

check_sum = len;

len -= KERM_KEY_SPACE; //解码,编码是+0X20,解码时-0x20

/* sequence decode */

seq = buf[index++];

check_sum += seq;

seq -= KERM_KEY_SPACE;

/* get package type */

type = buf[index++];

check_sum += type;

if (len) //fixme: handleextended length //根据协议要减去两个字节

len -= 2;

while (len > 1) {

curr_char =buf[index++];

check_sum +=curr_char;

len--;

if (type !=KERM_TYPE_DATA) //接收数据包

continue;

if (curr_char ==KERM_KEY_SHARP) /* '#' */ //数据包中的控制字符

{

curr_char =buf[index++];

check_sum+= curr_char;

len--;

if (0x40 == (curr_char & 0x60))

curr_char= curr_char & (~0x40);

else if (0x3f == (curr_char & 0x7f))

curr_char|= 0x40;

}

*pAddr++ =curr_char;

}

/* checksum */

curr_char = buf[index++];
//校验数据是否正确

if (curr_char !=(KERM_KEY_SPACE + (0x3f &(check_sum + (0x03 & (check_sum >> 6))))))

#ifdefCONFIG_TOPHALF_KERMIT

goto Loop;

#else

goto Error;

#endif

/* terminator */

curr_char = buf[index++];

if (curr_char != KERM_KEY_TERM)

#ifdefCONFIG_TOPHALF_KERMIT

goto Loop;

#else

goto Error;

#endif

/* send ack package */

send_ack_packet(seq); /发送ACK包

}while(type != KERM_TYPE_BREAK); //收到'B'代表文件传送结束

return pAddr - (unsigned char *)pLoadAddr; //返回接收文件的大小

#ifdefCONFIG_TOPHALF_KERMIT

Loop:

while (1)

GtUartWriteByte('E');

#else

Error:

#endif

printf("Error\n\r");

return -1;

}

下载完之后,我们在执行int boot_linux_sdram() 就可以实现从SDRAM启动了。

最后讲下Makefile:Makefile实现的功能是:每个文件夹内都有自己的Makefile,根目录下的主Makefile会进入各个子目录并调用各自的Makefile。每个子目录下的Makefile把自己编译的代码链接成一个build-in.o文件, 主Makefile把各个子目录下的build-in.o链接成一个可执行文件。

这里学着Li Hongwang大侠的做法:

CC = arm-lwm-linux-gnueabi-gcc

LD = arm-lwm-linux-gnueabi-ld

AR = arm-lwm-linux-gnueabi-ar

OBJCOPY = arm-lwm-linux-gnueabi-objcopy

OBJDUMP = arm-lwm-linux-gnueabi-objdump

INCLUDEDIR := $(shell pwd)/include

CFLAGS := -Wall -O2

CPPFLAGS := -nostdinc -fno-builtin -I$(INCLUDEDIR)

BUILT_IN_OBJ =built-in.o

export CC LD AROBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS

export BUILT_IN_OBJ

SUBDIRS = start device lib app main

/*可以google下foreach的用法*/

SUBOBJS :=$(foreach n, $(SUBDIRS), $(n)/$(BUILT_IN_OBJ))

all: lboot.binlboot.dis

lboot.bin :lboot.elf

${OBJCOPY} -O binary -S $^ $@

lboot.dis :lboot.elf

${OBJDUMP} -D -m arm $^ > $@

lboot.elf :$(SUBOBJS)

${LD} -Tlboot.lds -o $@ $^

$(SUBOBJS) :$(SUBDIRS)

.PHONY : $(SUBDIRS)

$(SUBDIRS) :

@make -C $@ all

.PHONE : clean

clean:

rm -f lboot.dis lboot.bin lboot.elf *.o

@for subdir in $(SUBDIRS); do \

(make clean -C $$subdir); \

done

总结:

可以这么说,到目前为止,基本上都是参考Li Hongwang大侠的博客及代码做的,只是添加了自动识别norflash启动和nandflash启动,并修改了nandflash部份的代码,因为我的nandflash是128M,nandflash从128M是一个分界点,页的大小不一样了。

下面又要到关键的部份了,因为我的网卡是DM9000,^_^,在实现网口下载功能之前或者之后,我可能还会实现nandflash的格式化,nandflash的下载,nandflash的分区,之后我可能会根据我的开发板完成一些测试的功能,如测试LED,ADC,按键等。

好了,想起一位老师的一句话,既然你已经选择了读研,那么你肯定对你有着更高的期望,那么就让我们带着梦想,珍惜在校的每一天,继续努力吧。


lboot_bootlinux_v1.0.rar


lboot_bootlinux_v2.0规范.rar


kermit协议介绍.rar

效果图:















此处转载http://blog.chinaunix.net/space.php?uid=18921523&do=blog&id=199952
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: