Chinaunix首页 | 论坛 | 博客
  • 博客访问: 354157
  • 博文数量: 69
  • 博客积分: 1992
  • 博客等级: 上尉
  • 技术积分: 1162
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-03 19:50
文章分类
文章存档

2015年(1)

2011年(55)

2010年(13)

分类: LINUX

2011-03-31 13:13:47

主要参考:Li Hongwang大侠的代码

排版效果不是很好,可以下载附件中 word看^_^

这里,主要实现了以下三点:

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的源码是怎么实现的,然后在米葫芦画瓢的实现。好了,现在假设在nand0x00060000处已经存放的linux内核映像zImage,那么怎么实现从nand启动内核呢?其实很简单,简单的调用nandread函数将内核映像拷贝到sdram中的0x33800000地址处,拷贝完之后再跳到这个地址就可以启动内核了,代码实现如下:

/********** SDRAM and Nand Space Allocation ****************/

#define SDRAM_TOTAL_SIZE         0x04000000                                              //64M SDRAM

#define SDRAM_ADDR_START         0x30000000                                            //起始地址是0x30000000

#define SDRAM_LBOOT_START        0x33000000                                            //LBOOT的存放地址

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

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

 

#define NAND_KERNEL_START       0x00060000

#define NAND_KERNEL_SZIE        0x00500000

 

int boot_linux()

{

    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 */

   

    s3c2440_nand_init();

 

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

    s3c2440_nand_read((unsigned char *)SDRAM_KERNEL_START, NAND_KERNEL_START, NAND_KERNEL_SZIE);

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

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

   

    theKernel = (void (*)(int, int, unsigned int))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;

};

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

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

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

#define ATAG_MEM         0x54410002                              //代表内存

#define ATAG_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(unsigned int 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 void setup_cmdline_tag(const char * line)

{

    int linelen = strlen(line);

    if(!linelen)

        return;                                                                     /* do not insert a ulTag for an empty commandline */

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

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

 

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

 

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

}

 

static void setup_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))

#define TAG_SIZE(type)    ((sizeof(struct TagHeader) + 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, unsigned int))SDRAM_KERNEL_START;

    theKernel(0, S3C2440_MATHINE_TYPE, SDRAM_TAGS_START)

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

 

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

int boot_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, unsigned int))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包程序:

#define KERM_ACK_LEN 0x10

 

static void send_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;//在编码之前把第67两位提出加到低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

}

 

接收文件程序:

#define KERM_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: handle extended 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))))))

#ifdef CONFIG_TOPHALF_KERMIT

                            goto Loop;

#else

                            goto Error;

#endif

 

                   /* terminator */

                   curr_char = buf[index++];

                   if (curr_char != KERM_KEY_TERM)                                                                                                                                                         

#ifdef CONFIG_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;                         //返回接收文件的大小

 

#ifdef CONFIG_TOPHALF_KERMIT

Loop:

         while (1)

            GtUartWriteByte('E');

#else

Error:

#endif

         printf("Error\n\r");

        return -1;

}

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

最后讲下MakefileMakefile实现的功能是:每个文件夹内都有自己的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 AR OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS

 

export BUILT_IN_OBJ

 

SUBDIRS =  start device lib app main

/*可以googleforeach的用法*/

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

 

all: lboot.bin lboot.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部份的代码,因为我的nandflash128Mnandflash128M是一个分界点,页的大小不一样了。

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

 

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

 七,第五个目标,启动内核.doc   

 lboot_bootlinux_v1.0.rar    lboot_bootlinux_v2.0规范.rar    kermit协议介绍.rar   

效果图:




阅读(2431) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~