AT91SAM9G45 Linux 启动步骤:
bootstrap -> U-boot 1.3.4 -> Linux 2.6.30
CPU上电后从NAND FLASH 第0块读取bootstrap. 然后bootstrap读取u-boot,u-boot读取并启动linux.
为何要这样呢?
一直在思考这个问题,我开始一直以为bootstrap是没有源码的,后来才发现它也有源码,读了它的代码后发现,它干的活就很少,初始化一些寄存器,读取u-boot并转入执行。那能不能去跳它直接把u-boot代码放到它的位置呢?答案是不能,因为AT91SAM9G45最大只读取23000字节的代码,U-boot 明显超出了限制。
那么反过来呢?把U-boot 里面启动linux的功能挪过来,不用U-boot呢?可以,不过不能用U-boot提供的其它功能,开发过程会非常不方便,而且也没有必要。
闲来无事,测试了一下bootstrap读取NAND的速度,发现差不多有6MB/S,太吸引人了,前面提到过,优化U-boot后,主要瓶颈就是其龟速的NAND读取速度。
突然想到了一个新的启动方案:
CPU上电读取并执行bootstrap, 在bootstrap中插入代码,先读取 OS Image,然后检查串口输入,如果有输入按键,就放弃读取的OS Image,读取U-boot 并转入执行U-boot, 如果没有输入按键,就直接启动Linux,岂不是两全其美?
在 ftp://www.at91.com/pub/at91bootstrap/AT91Bootstrap3.1/ 上下载了一个新版本的bootstrap.用当前编译环境编译了一下,没问题,发现其代码已经实现直接启动Linux(不使用u-boot),大喜,这样工作量就更小了,也更有信心了,开始工作。
由于要读取串口数据,需要把串口初始化,这个版本只有当配置成DEBUG才初始化串口,没关系,修改代码,让它初始化就好了。需要实现一个函数,非阻塞检测串口有没有输入,如果有就读取,否则就返回0,实现如下:
char dbgu_try_getc(void)
{
if (read_dbgu(DBGU_CSR) & AT91C_US_RXRDY)
return (char)read_dbgu(DBGU_RHR);
return 0;
}
修改main()函数,加入读取linux os image 的代码
int img_read_ret = 1;
char stopchr = 0;
#define OS_LOAD_TO_MEM_ADDR (0x70008000 - sizeof(image_header_t))
hw_init();
...
img_read_ret = read_nandflash((unsigned char *)OS_LOAD_TO_MEM_ADDR, 0x800000, 0x500000, 1);
if (img_read_ret == 0)//读取OS Image成功
{
stopchr = dbgu_try_getc(); //recv & check stop cmd char 'u'
if (stopchr != 'u')
OsLoader(OS_LOAD_TO_MEM_ADDR);//启动Linux
}
//else read u-boot
read_nandflash((unsigned char *)JUMP_ADDR, (unsigned long)IMG_ADDRESS, (int)IMG_SIZE, 0);
//jump to run u-boot
...
大功告成,读取2.8M 非压缩 OS image 花费时间约400ms,CRC32 验证 IMAGE 344ms,启动Linux 1.0 秒,总共1.7秒(通常,我们不需要进行CRC32验证,可以把这344ms也省掉,共1.4秒即可启动完成)。
启动时按'u'进入U-boot,否则跳过U-boot直接进入Linux.
附:修改过的函数代码,很简单,不再解释
#define IH_ARCH_ARM 2
#define IH_OS_LINUX 5
#define IH_TYPE_KERNEL 2
#define IH_COMP_NONE 0
#define PHYS_SDRAM 0x70000000 //物理内存起始地址
#define PHYS_SDRAM_SIZE 0x08000000
#define OS_KERNEL_ARG_STRING "mem=128M lpj=99072 console=ttyS0,115200 noinitrd root=/dev/mtdblock2 init=/linuxrc rootfstype=yaffs2 rw mtdparts=atmel_nand:8M(uboot),8M(os),64M(root),-(vutc) quiet"
int get_os_img_len(unsigned long blockIdx, unsigned char *dst)
{
image_header_t * hdr = (image_header_t *)dst;
int ret;
if (ntohl(hdr->ih_magic) != IMAGE_MAGIC)
return -1;
if (hdr->ih_arch != IH_ARCH_ARM)
return -1;
if (hdr->ih_os != IH_OS_LINUX)
return -1;
if (hdr->ih_type != IH_TYPE_KERNEL)
return -1;
if (hdr->ih_comp != IH_COMP_NONE)
return -1;
ret = ntohl(hdr->ih_size);
return ret + sizeof(image_header_t);
}
int read_nandflash(unsigned char *dst, unsigned long offset, int len, int is_os_img)
{
SNandInfo sNandInfo;
PSNandInitInfo pNandInitInfo;
unsigned char *pOutBuffer = dst;
unsigned int blockIdx, badBlock, length, sizeToRead, nbSector, sectorIdx, dataLeft;
int osimglen = 0;
nandflash_hw_init();
reset_nandflash();
// Read Nand Chip ID
pNandInitInfo = AT91F_NandReadID();
if (!pNandInitInfo) {
dbg_log(DEBUG_INFO, "\n\r-E- No NandFlash detected !!!\n\r");
return -1;
}
//dbg_log(1, "Copy %x bytes from %x to %x\r\n", len, offset, dst);
// Initialize NandInfo Structure
AT91F_NandInit(&sNandInfo, pNandInitInfo);
if (!sNandInfo.uDataBusWidth)
nandflash_cfg_8bits_dbw_init();
// Initialize the block offset
blockIdx = offset / sNandInfo.uBlockNbData;
//Initialize the number of bad blocks
badBlock = 0;
length = len;
while (length > 0) {
//Read a buffer corresponding to a block in the origin file
sizeToRead = (length < sNandInfo.uBlockNbData) ? length : sNandInfo.uBlockNbData;
// Adjust the number of sectors to read
nbSector = sizeToRead / sNandInfo.uDataNbBytes;
if (sizeToRead % sNandInfo.uDataNbBytes) nbSector++;
//Loop until a valid block has been read
while (1) {// Read the sectors
for (sectorIdx = 0; sectorIdx < nbSector; sectorIdx++) {
dataLeft = sizeToRead - (sectorIdx * sNandInfo.uDataNbBytes);
if (dataLeft < sNandInfo.uDataNbBytes) {
dataLeft = sizeToRead - (sectorIdx * sNandInfo.uDataNbBytes);
} else {
dataLeft = sNandInfo.uDataNbBytes;
}
// Read the sector
if (AT91F_NandRead(&sNandInfo, blockIdx, sectorIdx, ZONE_DATA, pOutBuffer) == FALSE)
{// Move to next block
break;
} else {
if (is_os_img){
osimglen = get_os_img_len(blockIdx, pOutBuffer);
if (osimglen < 0)//不是Image头部,不再读取,返回错误
return -1;//we just try 5 block for search os image.
length = osimglen;
is_os_img = 0;//只需检查第一块
}
pOutBuffer += sNandInfo.uDataNbBytes;
}
}
blockIdx++;
//The full block is valid, then exit the loop
if (sectorIdx >= nbSector)
break;
}
// Decrement length
length -= sizeToRead;
}
return 0;
}
int OsLoader(unsigned int Addr)
{
unsigned long ep, load_addr, len;
void (*theKernel) (int zero, int arch, unsigned int params);
image_header_t *hdr;
hdr = (image_header_t *) Addr;
load_addr = ntohl(hdr->ih_load);
ep = ntohl(hdr->ih_ep);
theKernel = (void (*)(int, int, unsigned int))ep;
//verify crc
{
unsigned long dcrc, dcrc2, len;
dcrc = ntohl(hdr->ih_dcrc);
len = ntohl(hdr->ih_size);
dcrc2 = crc32(0, Addr + sizeof(image_header_t), len);
if (dcrc != dcrc2)
{
dbg_log(1, "Error, Image CRC check failed, need %x but %x\n\r", dcrc, dcrc2);
return -1;
}
}
setup_tags();
if (ep != Addr + sizeof (image_header_t))
{
dbgu_print(" Relocate Kernal...");
len = ntohl(hdr->ih_size);
memcpy((void *)load_addr, (void *)((unsigned long)Addr + sizeof (image_header_t)), len);
dbgu_print("OK\r\n");
}
dbgu_print("Starting kernel...\r\n");
theKernel(0, MACH_TYPE, (unsigned int)(PHYS_SDRAM + 0x100));
return 0;
}
void setup_tags()
{
struct tag *tag = (struct tag *)(PHYS_SDRAM + 0x100);
tag->hdr.tag = ATAG_CORE;
tag->hdr.size = tag_size(tag_core);
tag->u.core.flags = 0;
tag->u.core.pagesize = 0;
tag->u.core.rootdev = 0;
tag = tag_next(tag);
tag->hdr.tag = ATAG_MEM;
tag->hdr.size = tag_size(tag_mem32);
tag->u.mem.start = PHYS_SDRAM;
tag->u.mem.size = PHYS_SDRAM_SIZE;
tag = tag_next(tag);
tag->hdr.tag = ATAG_CMDLINE;
tag->hdr.size = (sizeof (struct tag_header) + strlen(OS_KERNEL_ARG_STRING) + 1 + 4) >> 2;
strcpy(tag->u.cmdline.cmdline, OS_KERNEL_ARG_STRING);
tag = tag_next(tag);
tag->hdr.tag = ATAG_NONE;
tag->hdr.size = 0;
bootstrap -> U-boot 1.3.4 -> Linux 2.6.30
CPU上电后从NAND FLASH 第0块读取bootstrap. 然后bootstrap读取u-boot,u-boot读取并启动linux.
为何要这样呢?
一直在思考这个问题,我开始一直以为bootstrap是没有源码的,后来才发现它也有源码,读了它的代码后发现,它干的活就很少,初始化一些寄存器,读取u-boot并转入执行。那能不能去跳它直接把u-boot代码放到它的位置呢?答案是不能,因为AT91SAM9G45最大只读取23000字节的代码,U-boot 明显超出了限制。
那么反过来呢?把U-boot 里面启动linux的功能挪过来,不用U-boot呢?可以,不过不能用U-boot提供的其它功能,开发过程会非常不方便,而且也没有必要。
闲来无事,测试了一下bootstrap读取NAND的速度,发现差不多有6MB/S,太吸引人了,前面提到过,优化U-boot后,主要瓶颈就是其龟速的NAND读取速度。
突然想到了一个新的启动方案:
CPU上电读取并执行bootstrap, 在bootstrap中插入代码,先读取 OS Image,然后检查串口输入,如果有输入按键,就放弃读取的OS Image,读取U-boot 并转入执行U-boot, 如果没有输入按键,就直接启动Linux,岂不是两全其美?
在 ftp://www.at91.com/pub/at91bootstrap/AT91Bootstrap3.1/ 上下载了一个新版本的bootstrap.用当前编译环境编译了一下,没问题,发现其代码已经实现直接启动Linux(不使用u-boot),大喜,这样工作量就更小了,也更有信心了,开始工作。
由于要读取串口数据,需要把串口初始化,这个版本只有当配置成DEBUG才初始化串口,没关系,修改代码,让它初始化就好了。需要实现一个函数,非阻塞检测串口有没有输入,如果有就读取,否则就返回0,实现如下:
char dbgu_try_getc(void)
{
if (read_dbgu(DBGU_CSR) & AT91C_US_RXRDY)
return (char)read_dbgu(DBGU_RHR);
return 0;
}
修改main()函数,加入读取linux os image 的代码
int img_read_ret = 1;
char stopchr = 0;
#define OS_LOAD_TO_MEM_ADDR (0x70008000 - sizeof(image_header_t))
hw_init();
...
img_read_ret = read_nandflash((unsigned char *)OS_LOAD_TO_MEM_ADDR, 0x800000, 0x500000, 1);
if (img_read_ret == 0)//读取OS Image成功
{
stopchr = dbgu_try_getc(); //recv & check stop cmd char 'u'
if (stopchr != 'u')
OsLoader(OS_LOAD_TO_MEM_ADDR);//启动Linux
}
//else read u-boot
read_nandflash((unsigned char *)JUMP_ADDR, (unsigned long)IMG_ADDRESS, (int)IMG_SIZE, 0);
//jump to run u-boot
...
大功告成,读取2.8M 非压缩 OS image 花费时间约400ms,CRC32 验证 IMAGE 344ms,启动Linux 1.0 秒,总共1.7秒(通常,我们不需要进行CRC32验证,可以把这344ms也省掉,共1.4秒即可启动完成)。
启动时按'u'进入U-boot,否则跳过U-boot直接进入Linux.
附:修改过的函数代码,很简单,不再解释
#define IH_ARCH_ARM 2
#define IH_OS_LINUX 5
#define IH_TYPE_KERNEL 2
#define IH_COMP_NONE 0
#define PHYS_SDRAM 0x70000000 //物理内存起始地址
#define PHYS_SDRAM_SIZE 0x08000000
#define OS_KERNEL_ARG_STRING "mem=128M lpj=99072 console=ttyS0,115200 noinitrd root=/dev/mtdblock2 init=/linuxrc rootfstype=yaffs2 rw mtdparts=atmel_nand:8M(uboot),8M(os),64M(root),-(vutc) quiet"
int get_os_img_len(unsigned long blockIdx, unsigned char *dst)
{
image_header_t * hdr = (image_header_t *)dst;
int ret;
if (ntohl(hdr->ih_magic) != IMAGE_MAGIC)
return -1;
if (hdr->ih_arch != IH_ARCH_ARM)
return -1;
if (hdr->ih_os != IH_OS_LINUX)
return -1;
if (hdr->ih_type != IH_TYPE_KERNEL)
return -1;
if (hdr->ih_comp != IH_COMP_NONE)
return -1;
ret = ntohl(hdr->ih_size);
return ret + sizeof(image_header_t);
}
int read_nandflash(unsigned char *dst, unsigned long offset, int len, int is_os_img)
{
SNandInfo sNandInfo;
PSNandInitInfo pNandInitInfo;
unsigned char *pOutBuffer = dst;
unsigned int blockIdx, badBlock, length, sizeToRead, nbSector, sectorIdx, dataLeft;
int osimglen = 0;
nandflash_hw_init();
reset_nandflash();
// Read Nand Chip ID
pNandInitInfo = AT91F_NandReadID();
if (!pNandInitInfo) {
dbg_log(DEBUG_INFO, "\n\r-E- No NandFlash detected !!!\n\r");
return -1;
}
//dbg_log(1, "Copy %x bytes from %x to %x\r\n", len, offset, dst);
// Initialize NandInfo Structure
AT91F_NandInit(&sNandInfo, pNandInitInfo);
if (!sNandInfo.uDataBusWidth)
nandflash_cfg_8bits_dbw_init();
// Initialize the block offset
blockIdx = offset / sNandInfo.uBlockNbData;
//Initialize the number of bad blocks
badBlock = 0;
length = len;
while (length > 0) {
//Read a buffer corresponding to a block in the origin file
sizeToRead = (length < sNandInfo.uBlockNbData) ? length : sNandInfo.uBlockNbData;
// Adjust the number of sectors to read
nbSector = sizeToRead / sNandInfo.uDataNbBytes;
if (sizeToRead % sNandInfo.uDataNbBytes) nbSector++;
//Loop until a valid block has been read
while (1) {// Read the sectors
for (sectorIdx = 0; sectorIdx < nbSector; sectorIdx++) {
dataLeft = sizeToRead - (sectorIdx * sNandInfo.uDataNbBytes);
if (dataLeft < sNandInfo.uDataNbBytes) {
dataLeft = sizeToRead - (sectorIdx * sNandInfo.uDataNbBytes);
} else {
dataLeft = sNandInfo.uDataNbBytes;
}
// Read the sector
if (AT91F_NandRead(&sNandInfo, blockIdx, sectorIdx, ZONE_DATA, pOutBuffer) == FALSE)
{// Move to next block
break;
} else {
if (is_os_img){
osimglen = get_os_img_len(blockIdx, pOutBuffer);
if (osimglen < 0)//不是Image头部,不再读取,返回错误
return -1;//we just try 5 block for search os image.
length = osimglen;
is_os_img = 0;//只需检查第一块
}
pOutBuffer += sNandInfo.uDataNbBytes;
}
}
blockIdx++;
//The full block is valid, then exit the loop
if (sectorIdx >= nbSector)
break;
}
// Decrement length
length -= sizeToRead;
}
return 0;
}
int OsLoader(unsigned int Addr)
{
unsigned long ep, load_addr, len;
void (*theKernel) (int zero, int arch, unsigned int params);
image_header_t *hdr;
hdr = (image_header_t *) Addr;
load_addr = ntohl(hdr->ih_load);
ep = ntohl(hdr->ih_ep);
theKernel = (void (*)(int, int, unsigned int))ep;
//verify crc
{
unsigned long dcrc, dcrc2, len;
dcrc = ntohl(hdr->ih_dcrc);
len = ntohl(hdr->ih_size);
dcrc2 = crc32(0, Addr + sizeof(image_header_t), len);
if (dcrc != dcrc2)
{
dbg_log(1, "Error, Image CRC check failed, need %x but %x\n\r", dcrc, dcrc2);
return -1;
}
}
setup_tags();
if (ep != Addr + sizeof (image_header_t))
{
dbgu_print(" Relocate Kernal...");
len = ntohl(hdr->ih_size);
memcpy((void *)load_addr, (void *)((unsigned long)Addr + sizeof (image_header_t)), len);
dbgu_print("OK\r\n");
}
dbgu_print("Starting kernel...\r\n");
theKernel(0, MACH_TYPE, (unsigned int)(PHYS_SDRAM + 0x100));
return 0;
}
void setup_tags()
{
struct tag *tag = (struct tag *)(PHYS_SDRAM + 0x100);
tag->hdr.tag = ATAG_CORE;
tag->hdr.size = tag_size(tag_core);
tag->u.core.flags = 0;
tag->u.core.pagesize = 0;
tag->u.core.rootdev = 0;
tag = tag_next(tag);
tag->hdr.tag = ATAG_MEM;
tag->hdr.size = tag_size(tag_mem32);
tag->u.mem.start = PHYS_SDRAM;
tag->u.mem.size = PHYS_SDRAM_SIZE;
tag = tag_next(tag);
tag->hdr.tag = ATAG_CMDLINE;
tag->hdr.size = (sizeof (struct tag_header) + strlen(OS_KERNEL_ARG_STRING) + 1 + 4) >> 2;
strcpy(tag->u.cmdline.cmdline, OS_KERNEL_ARG_STRING);
tag = tag_next(tag);
tag->hdr.tag = ATAG_NONE;
tag->hdr.size = 0;
}