第二章 uboot-1.1.6移植 2.1 编译环境准备
从网上http://sourceforge.net/projects/u-boot/下载U-boot-1.1.6,解压后可以看到它的全部源代码,我们需要针对自己使用的CPU和开发板进行特定修改。
设置你的PATH环境变量,使其可以找到你的交叉编译工具链。
mkdir /home/arm2410
useradd -G root -g root -d/home/arm2410 arm
su arm
cd /home/arm2410
vi ~/.bashrc
export PATH = /usr/local/arm/3.3.2/bin:$PATH
source ~/.bashrc
解压uboot源代码
mkdir /home/arm2410/uboot
cd /home/arm2410/uboot
tar –xvjf u-boot-1.1.6.tar.bz2
cd u-boot-1.1.6
2.2 修改顶层Makefile
U-Boot是通过gcc和Makefile组织编译的。顶层目录下的Makefile可以设置开发板的定义,然后递归地调用各级子目录下的Makefile,最后把编译过的代码连接成u-boot映像。
在顶层Makefile中先要定义交叉编译器,这里就使用我们上一次实验中构建的交叉编译器。查找ifeq ($(ARCH),arm),修改确认CROSS_COMPILE的值。
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-
然后在顶层Makefile中,你只要找到smdk2410_config这一行:
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
然后参考上面一行增加arm2410_config,如下:
arm2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t arm2410 NULL s3c24x0
解释一下这行配置中各项的意思:
Ø arm: CPU 的架构(ARCH)
Ø arm920t: CPU 的类型(CPU),其对应于 cpu/arm920t 子目录。
Ø arm2410: 开发板的类型,对应于 board/arm2410 目录。
Ø NULL: 开发者/或经销商(vender)。
Ø s3c24x0: 片上系统(SOC)。
2.3 为arm2410平台新建一个目录
我们参考smdk2410开发板的配置,在smdk2410平台的基础上进行移植的。不过为了不破换U-Boot本身的代码,我们在board目录下将smdk2410文件夹复制为arm2410,并将该目录下的smdk2410.c重命名为arm2410.c
cd board
cp -r smdk2410 arm2410
cd arm2410
mv smdk2410.c arm2410.c
2.4 在board/arm2410目录下增加nand.c文件
在board/arm2410目录下增加nand.c文件,文件内容如下:
#include <common.h>
#include <s3c2410.h>
#include <config.h>
#define TACLS 0
#define TWRPH0 3
#define TWRPH1 0
#define U32 unsigned int
extern unsigned long nand_probe(unsigned long physadr);
static void NF_Reset(void)
{
int i;
NF_nFCE_L();
NF_CMD(0xFF);
for(i=0;i<10;i++);
NF_WAITRB();
NF_nFCE_H();
}
void NF_Init(void)
{
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)\
|(TWRPH1<<0);
NF_Reset();
}
int nand_read_whole(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return 1;
}
NF_nFCE_L();
for(i=0; i<10; i++);
i = start_addr;
while(i < start_addr + size) {
rNFCMD = 0;
rNFADDR = i & 0xff;
rNFADDR = (i >> 9) & 0xff;
rNFADDR = (i >> 17) & 0xff;
rNFADDR = (i >> 25) & 0xff;
NF_WAITRB();
for(j=0; j < NAND_SECTOR_SIZE; j++, i++) {
*buf = (rNFDATA & 0xff);
buf++;
}
}
NF_nFCE_H();
return 0;
}
void nand_init(void)
{
NF_Init(); /* nand_reset();*/
printf ("%4lu MB\n", nand_probe((unsigned long)CFG_NAND_BASE) >> 20);
}
2.5 修改board/arm2410目录下的Makefile
修改:
COBJS := smdk2410.o flash.o
为下面一行:
COBJS := arm2410.o nand.o flash.o
这是因为前面将smdk2410.c改成arm2410.c,而且新增加了个nand.c文件,所以要在Makefile中告诉编译器将arm2410.c及nand.c文件分别编译链接成nand.o及arm2410.o这两个目标文件。
2.6 为arm2410平台配置头文件arm2410.h
每块开发板都有一个头文件,位于include/configs目录下,它是为开发板定义配置选项和参数的,如smdk2410开发板对应的头文件是smdk2410.h。
这个头文件中主要定义了两类变量,一类是选项,前缀是CONFIG_,用来选择处理器,命令,参数等。如:
#define CONFIG_S3C2410 1
一类是参数,前缀是CFG_,用来定义总线参数,串口波特率,flash地址等。如:
#define CFG_LOAD_ADDR 0x30008000
接下来,我们将参考smdk2410.h,进行适当的修改,为arm2410来设定相关的选项和配置。首先进入include/configs目录,复制smdk2410.h为arm2410.h
include/configs/arm2410.h需要做的修改有以下几处:
Ø 第一处:在“#define CONFIG_SMDK2410 1”这一行下面添加以下几行:
#define CONFIG_SMDK2410 1
#define CONFIG_S3C2410_NAND_BOOT 1
#define STACK_BASE 0x33f00000 //0x33f0000--0x33f80000堆栈
#define STACK_SIZE 0x8000 //堆栈大小
#define UBOOT_RAM_BASE 0x33f80000 //0x33f80000--0x34000000 Uboot
其中第一行是配置目标板从NandFlash启动,第二行和第三行配置堆栈的基地址和堆栈空间的大小,第四行配置U-Boot在RAM中的基地址。大家要注意,千成不能把添加的语句加到上面一行的注释中了,不然编译时候会报错。
Ø 第二处:增加配置命令 CFG_CMD_NAND
/***********************************************************
* Command definition
***********************************************************/
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
CFG_CMD_NAND | \ //取消注释,加入nand相关命令
CFG_CMD_NET | \ //主要是添加ping命令需求
CFG_CMD_PING | \ //添加ping命令,只能从ARM到PC
/*CFG_CMD_EEPROM |*/ \
/*CFG_CMD_I2C |*/ \
/*CFG_CMD_USB |*/ \
CFG_CMD_REGINFO | \
CFG_CMD_DATE | \
CFG_CMD_ELF)
/* this must be included AFTER the definition of CONFIG_COMMANDS (if any) */
该配置用来增加操作NandFlash的相关命令,如读写(nand read,nand write),擦除(nand erase)等操作命令,添加ping命令,配置好PC机和ARM板,只能从ARM ping到PC机。
Ø 第三处:配置u-boot环境变量,这些环境变量也可以在u-boot烧写到Flash以后再配置,在这里,不介绍这些环境变量的具体含义,参见本文有关U-Boot环境变量。
#include <cmd_confdefs.h>
#define CONFIG_BOOTDELAY 5
#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,115200"
#define CONFIG_ETHADDR 08:00:3e:26:0a:5b
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_IPADDR 192.168.0.68
#define CONFIG_SERVERIP 192.168.0.67
#define CONFIG_BOOTFILE "zImage.img"
#define CONFIG_BOOTCOMMAND "tftp; bootm"
#if (CONFIG_COMMANDS & CFG_CMD_KGDB)
Ø 第四处:配置命令提示符(command prompt),这个属于个性化配置范畴了,你可以根据自己的喜好配,也可以用默认的。
#define CFG_LONGHELP /* undef to save memory */
#define CFG_PROMPT "ARM2410 # " /* 把这儿设置成自己的拼音缩写 */
#define CFG_CBSIZE 256 /* Console I/O Buffer Size */
Ø 第五处:配置装载地址为0x30008000
#undef CFG_CLKS_IN_HZ /* everything, incl board info, in Hz */
#define CFG_LOAD_ADDR 0x30008000 /* default load address */
#define CFG_TFTP_LOADADDR 0x30008000
这两个装载地址是U-Boot通过Flash启动或者TFTP引导内核时的默认装载地址,这样说读者或许还不太明白,先暂且放着不管,后面用到这个参数的时候笔者还会介绍,那时就容易理解了。
Ø 第六处:在文件末尾处,#endif之前进行如下修改
注释掉下面两行:
/*#define CFG_ENV_IS_IN_FLASH 1
#define CFG_ENV_SIZE 0x10000 *//* Total Size of Environment Sector */
增加如下代码:
#define CFG_ENV_IS_IN_NAND 1
#define CFG_ENV_SIZE 0x4000 /* Total Size of Environment Sector */
#define CFG_ENV_OFFSET (0x04000000-0x8000)
//1.环境变量会saveenv在nand接近最顶处
//(0x04000000-0x8000)
//2.打算在第三块放置yaffs文件系统
//原nand分配,256KB,2M-256K,30M,32M
//所以把环境变量放在(0x00040000-0x8000)
//uboot块256KB只用来109KB左右
//3.或者重新分配nand,1M,3M,30M,30M
//则环境变量放在(1M-0x8000)=(0x00100000-0x8000)
//利用uboot指令bdinfo可以查看到
/*********define mach_type ***********/
#define CONFIG_ARCH_SMDK2410 1
/* define nand command support */
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
#define CFG_NAND_LEGACY 1
#define CFG_NAND_BASE 0x4E000000 //nand flash控制器基址
#define CFG_MAX_NAND_DEVICE 1 //一个nand设备
#define SECTORSIZE 512
#define NAND_SECTOR_SIZE SECTORSIZE
#define NAND_BLOCK_MASK (NAND_SECTOR_SIZE - 1)
#define ADDR_COLUMN 1
#define ADDR_PAGE 3
#define ADDR_COLUMN_PAGE 4
#define NAND_ChipID_UNKNOWN 0x00
#define NAND_MAX_FLOORS 1
#define NAND_MAX_CHIPS 1
#define WRITE_NAND_COMMAND(d, adr) do {rNFCMD = d;} while(0)
#define WRITE_NAND_ADDRESS(d, adr) do {rNFADDR = d;} while(0)
#define WRITE_NAND(d, adr) do {rNFDATA = d;} while(0)
#define READ_NAND(adr) (rNFDATA)
#define NAND_WAIT_READY(nand) {while(!(rNFSTAT&(1<<0)));}
#define NAND_DISABLE_CE(nand) {rNFCONF |= (1<<11);}
#define NAND_ENABLE_CE(nand) {rNFCONF &= ~(1<<11);}
#define NAND_CTL_CLRALE(nandptr)
#define NAND_CTL_SETALE(nandptr)
#define NAND_CTL_CLRCLE(nandptr)
#define NAND_CTL_SETCLE(nandptr)
#define CONFIG_MTD_NAND_VERIFY_WRITE 1
#endif /* CONFIG_COMMANDS & CFG_CMD_NAND*/
#ifdef CONFIG_S3C2410_NAND_BOOT
#define rNFCONF (*(volatile unsigned int *)0x4e000000) //nand flash控制器基址0x4e000000
#define rNFCMD (*(volatile unsigned char *)0x4e000004)
#define rNFADDR (*(volatile unsigned char *)0x4e000008)
#define rNFDATA (*(volatile unsigned char *)0x4e00000c)
#define rNFSTAT (*(volatile unsigned int *)0x4e000010)
#define rNFECC (*(volatile unsigned int *)0x4e000014)
#define rNFECC0 (*(volatile unsigned char *)0x4e000014)
#define rNFECC1 (*(volatile unsigned char *)0x4e000015)
#define rNFECC2 (*(volatile unsigned char *)0x4e000016)
#define NF_CMD(cmd) {rNFCMD=cmd;}
#define NF_ADDR(addr) {rNFADDR=addr;}
#define NF_nFCE_L() {rNFCONF&=~(1<<11);}
#define NF_nFCE_H() {rNFCONF|=(1<<11);}
#define NF_RSTECC() {rNFCONF|=(1<<12);}
#define NF_RDDATA() (rNFDATA)
#define NF_WRDATA(data) {rNFDATA=data;}
#define NF_WAITRB() {while(!(rNFSTAT&(1<<0)));}
#endif /* CONFIG_S3C2410_NAND_BOOT */
/********CONFIG FOR COMMANDLINE***********/
//加入uboot向linux内核传递的参数,如命令行commandline
#ifndef CONFIG_SETUP_MEMORY_TAGS
#define CONFIG_SETUP_MEMORY_TAGS 1
#endif
#ifndef CONFIG_CMDLINE_TAG
#define CONFIG_CMDLINE_TAG 1
#endif
#ifndef CONFIG_INITRD_TAG
#define CONFIG_INITRD_TAG 1
#endif
//其余的暂时还用不到,如果加进去将会编译出错
//#ifndef CONFIG_SERIAL_TAG
//#define CONFIG_SERIAL_TAG 1
//#endif
//#ifndef CONFIG_REVISION_TAG
//#define CONFIG_REVISION_TAG 1
//#endif
//#ifndef CONFIG_VFG
//#define CONFIG_VFD 1
//#endif
//#ifndef CONFIG_LCD
//#define CONFIG_LCD 1
//#endif
/***********CONFIG FOR COMMAND LINE************/
2.7修改使显示uboot commandline和所在的参数地址
修改lib_arm/armlinux.c,使显示uboot commandline 和所在的参数地址
在245行左右,找到CONFGI_CMDLINE_TAG,在setup_commandline_tag(bd,commandline)下面加入如下语句:
printf("uboot params adderss: 0x%x\n", gd->bd->bi_boot_params);
//显示传递的参数起始地址,默认0x30000100,
//保存在gd全局变量的目标板信息变量bd中
printf("uboot command line: %s\n", commandline); //显示uboot的commandline
上面这些配置选项都是与NandFlash有关的,有兴趣了解的可以直接查阅U-Boot的README文件。涉及到的nand.c,start.S,env_nand.c文件的修改大部分是与NandFlash相关,这些文件的修改只是针对“FS2410”的,鉴于各个开发板上NandFlash的特殊性,这些修改不一定适合其他的开发板。
2.8 修改cpu/arm920t目录下的start.S文件
Ø 第一处(165行左右):在“#ifndef CONFIG_SKIP_RELOCATE_UBOOT”这一行
后面增加:
#ifdef CONFIG_S3C2410_NAND_BOOT
/* relocate uboot from NAND Flash instead of NOR Flash */
bl copy_myself
#else
在187行左右ble copy_loop后面增加:
#endif /* CONFIG_S3C2410_NAND_BOOT */
Ø 第二处(230行左右):在_start_armboot: .word start_armboot这一行下面增加如下内容:
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START
mov fp, #0
bl NF_Init
@read UBOOT from Nand Flash to RAM
ldr r0, =UBOOT_RAM_BASE
mov r1, #0x0
mov r2, #0x20000
bl nand_read_whole
tst r0, #0x0
beq ok_nand_read
1: b 1b
ok_nand_read:
mov r0, #0x00000000
ldr r1, =UBOOT_RAM_BASE
mov r2, #0x400
go_next:
ldr r3, [r0], #4
ldr r4, [r1], #4
teq r3, r4
bne notmatch
subs r2, r2, #4
beq done_nand_read
bne go_next
notmatch:
1: b 1b
done_nand_read:
mov pc, r10
#endif /* CONFIG_S3C2410_NAND_BOOT */
Ø 第三处:在文件末尾增加下面几行内容:
#ifdef CONFIG_S3C2410_NAND_BOOT
.align 2
DW_STACK_START:
.word STACK_BASE + STACK_SIZE - 4
#endif
2.9 修改common目录下的env_nand.c文件
Ø 第一处: 将“#include <nand.h>”这一行去掉,然后添加下面的内容
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
#ifdef CFG_NAND_LEGACY
#include <linux/mtd/nand_legacy.h>
extern struct nand_chip nand_dev_desc[CFG_MAX_NAND_DEVICE];
extern int curr_device;
extern int nand_legacy_erase(struct nand_chip *nand, size_t ofs,
size_t len, int clean);
extern int nand_legacy_rw(struct nand_chip *nand, int cmd, size_t start,
size_t len, size_t *retlen, u_char *buf);
extern void nand_print(struct nand_chip *nand);
extern void nand_print_bad(struct nand_chip *nand);
extern int nand_read_oob(struct nand_chip *nand, size_t ofs,
size_t len, size_t *retlen, u_char *buf);
extern int nand_write_oob(struct nand_chip *nand, size_t ofs,
size_t len, size_t *retlen, const u_char *buf);
struct nand_chip* nand = &nand_dev_desc[0];
#else /* !CFG_NAND_LEGACY */
#include <linux/mtd/nand.h>
#include <nand.h>
#endif /* !CFG_NAND_LEGACY */
#endif /* (CONFIG_COMMANDS & CFG_CMD_NAND) */
Ø 第二处:找到下面几行内容,注释掉。
int nand_legacy_rw (struct nand_chip* nand, int cmd,
size_t start, size_t len,
size_t * retlen, u_char * buf);
/* info for NAND chips, defined in drivers/nand/nand.c */
extern nand_info_t nand_info[];
Ø 第三处:该文件中有两个int saveenv(void)函数,我们修改第二个,即"#else /* ! CFG_ENV_OFFSET_REDUND */"这一行后面的int saveenv(void)函数,将该函数体中的内容更改如下:
int saveenv(void)
{
int ret = 0;
#ifndef CFG_NAND_LEGACY
ulong total;
puts ("Erasing Nand...");
if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE))
return 1;
puts ("Writing to Nand... ");
total = CFG_ENV_SIZE;
ret = nand_write(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
#else
ulong off = CFG_ENV_OFFSET;
ulong size = CFG_ENV_SIZE;
puts ("Erasing Nand Environment...");
ret = nand_legacy_erase (nand,off, size, 1);
printf("%s\n", ret ? "ERROR\n" : "OK\n");
if (ret!=0) return ret;
puts ("Writing new Environment Variables to Nand...");
int total;
ret = nand_legacy_rw (nand, 0x0, off, size,(size_t *)&total,(u_char*)env_ptr);
#endif /* CFG_NAND_LEGACY */
if (ret || total != CFG_ENV_SIZE){
puts("undone!!!\n");
return 1;
}
puts ("done\n");
return ret;
}
Ø 第四处:该文件中有两个void env_relocate_spec (void)函数,我们也是修改第二个,即"#else /* ! CFG_ENV_OFFSET_REDUND */ */"这一行后面的void env_relocate_spec (void),将该函数体中的内容更改如下:
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
#ifndef CFG_NAND_LEGACY
ulong total;
int ret;
total = CFG_ENV_SIZE;
ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return use_default();
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
#else
ulong total;
int ret;
ret = nand_legacy_rw(nand, 1, CFG_ENV_OFFSET, CFG_ENV_SIZE,
(size_t *)&total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return use_default();
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
#endif /* CFG_NAND_LEGACY*/
#endif /* ! ENV_IS_EMBEDDED */
}
千万要注意,不要把后面的函数删除了,很多同学都不小心把后面一个函数删除了,造成编译时候出错。
2.10 修改common目录下的cmd_boot.c文件
Ø 第一处:找到头文件下面的几行,注释掉“#if defined(CONFIG_I386)”和“#endif”:
//#if defined(CONFIG_I386)
DECLARE_GLOBAL_DATA_PTR;
//#endif
Ø 第二处:在60行左右的“#if !defined(CONFIG_NIOS)”这一行下面添加如下几行:
if(argc==2)
rc = ((ulong (*)(int, char *[]))addr) (0, gd->bd->bi_arch_number);
else
2.11 交叉编译u-boot-1.1.6
上述文件修改完毕以后,进入u-boot顶层目录执行一下命令,编译。
make clobber
make arm2410_config
make
编译生成u-boot目标文件,执行make命令,开始对U-Boot进行编译,编译完成之后,顶层目录下面增加了u-boot各种格式的映像文件和符号表。如下表所示:
文件名 |
说明 |
u-boot.map |
U-Boot的符号对映 |
u-boot |
U-Boot可执行文件,采用 ELF 二进制格式 |
u-boot.bin |
U-Boot 原始二进制映像,可以被写入引导储存设备 |
u-boot.srec |
U-Boot 映像的S-Recoad格式 |
2.12 烧写u-boot到开发板nand中
如果开发板本身无法启动u-boot环境,我们就必须使用jtag线或者仿真器对u-boot进行烧写,如果已经可以启动到u-boot的环境下,我们可以通过tftp对它进行烧写,下面我们分别对它进行介绍。
2.12.1 使用sjf2410.exe在windows下烧写
ARM板上没有uboot,使用sjf2410.exe在windows下烧写到Nand Flash。
(1)先用20 针排线将FS2410 的20 针JTAG 接口与DragonJTAG 的20 针接口相连。 然后将光盘里的“Flash 烧写”文件夹拷贝到C 盘(硬盘的其他地方也行)。
注意:要确保 DragonJTAG 处于了SJF 状态。
(2)安装 giveio 驱动,进入“Flash 烧写”文件夹下,点击“安装驱动.exe”,将弹出
如下界面:

先点击“InStall Parallel Port Driver”栏目下的“Remove”按纽,然后点击该栏目下的“Install”按纽。

出现“Service is installed and run”说明giveio 驱动安装成功。
(3)修改SJF2410_BIOS.BAT使其中文件名为u-boot.bin,然后点击该文件夹下的SJF2410_BIOS.BAT 批处理文件,显示信息如下:

烧写程序所支持的FLASH 都列出来了,有K9S1208(NAND,64M)、28F128J3A、AM29LV800、SST39VF160/1 等。
(4)在出现上面的界面后,我们在“Select the Function to test:”提示下,输入‘0’,然后回车,这时将选择K9S1208 进行烧写,显示如下:

(5)接着在“Select the function to test :”提示下,输入“0”,然后回车,选择 K9F1208
进行烧写,接着在“Input target block number:”栏下输入偏移地址“0”,显示信
息如下:

接着回车,开始烧写程序,程序烧写完成后,输入“2”,再回车,便可退出。

(6)最后reset重启。
2.12.2 使用优龙Nor中自带bootloader烧写nand
ARM板上Nor Flash中有优龙自带的bootloader,可以利用它进行烧写nand。
(1)拿下JP1(OM0)的短路帽,上电从nor中启动开发板,进入BIOS 界面:

(2)点击“Serial Port”->”Transmit”选项,选择u-boot.bin

下载结束后,会提示是否要立即运行,这时输入“n”,将再次进入主功能菜单。

(3)下载成功后,在出现主功能菜单后,选择“2“,将出现如下界面。

在出现上面的提示后,输入“1”,在接下来的提示输入“Y”,将u-boot.bin烧写
到NAND FLASH 的分区1 中。烧写成功后,会自动进入主功能菜单。
2.12.3 使用原uboot下载烧写新u-boot-1.1.6
ARM板上有uboot,使用原来uboot下载烧写,上电启动。
tftp 0x31000000 u-boot.bin
nand erase 0x00 0x40000
nand write 0x310000 0x00 0x40000
然后reset重启。
2.13 在arm板上运行u-boot-1.1.6
将U-Boot烧写到目标机的Flash之后,用串口线将目标机与宿主机连接起来,进入宿主机Linux系统,运行minicom或者windows下超级终端,再打开目标机电源,如果U-Boot烧写成功,会看到下面的提示信息:
U-Boot 1.1.6 (Oct 19 2009 - 03:01:55)
DRAM: 64 MB
Flash: 512 kB
NAND: 64 MB
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 5
通过它,就可以不用仿真器实现对linux内核的烧写,实现后面的开发过程。
2.14 #minicom的配置使用
(1)切换到root用户.
su
(2)查找有效的串设备.
cat /proc/devices
显示
...
4 ttyS
...
188 ttyUSB
...
如果是普通串口设备, 设备名前缀为ttyS, 第一串口为ttyS0, 第二串口为ttyS1,依次类推。
如果是USB转串口的设备, 设备名前缀为ttyUSB, 第一串口为ttyUSB0。
(3)配置ttyUSB设备
minicom -s ttyUSB0
会出现一个configuration窗口,
┌──[configuration]─────┐
│ Filenames and paths │
│ File transfer protocols │
│ Serial port setup │
│ Modem and dialing │
│ Screen and keyboard │
│ Save setup as ttyUSB0 │
│ Save setup as.. │
│ Exit │
│ Exit from Minicom │
└─────────────┘
选择Serial port setup配置,会出现如下窗口:
┌─────────────────────────────────┐
│ A - Serial Device : /dev/ttyUSB0 │
│ B - Lockfile Location : /var/lock │
│ C - Callin Program : │
│ D - Callout Program : │
│ E - Bps/Par/Bits : 115200 8N1 │
│ F - Hardware Flow Control : No │
│ G - Software Flow Control : No │
│ │
│ Change which setting? │
└─────────────────────────────────┘
设置如上所示,设置完成后, Change which setting?项上按回车退出当前窗口,回到第一个窗口,按 Save setup as ttyUSB0保存设置,再按Exit from Minicom退出Minicom。
(4)启动minicom
minicom
Note!minicom -s是设置minicom,如果启动,直接 minicom就可以。必须在root下。
2.15 #uboot参数设置
可以通过printenv命令查看环境变量的设置。
ARM2410 # printenv
bootdelay=5
baudrate=115200
netmask=255.255.0.0
ethaddr=12:34:56:78:90:ab
bootfile="uImage"
bootargs=root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200
bootcmd=tftp ;bootm
serverip=192.168.0.66
ipaddr=192.168.0.68
stdin=serial
stdout=serial
stderr=serial
ARM2410 #
下表是常用环境变量的含义解释。通过printenv命令可以打印出这些变量的值。
环境变量 |
解释说明 |
bootdelay |
定义执行自动启动的等候秒数 |
baudrate |
定义串口控制台的波特率 |
netmask |
定义以太网接口的掩码 |
ethaddr |
定义以太网接口的MAC地址 |
bootfile |
定义缺省的下载文件 |
bootargs |
定义传递给Linux内核的命令行参数 |
bootcmd |
定义自动启动时执行的几条命令 |
serverip |
定义tftp服务器端的IP地址 |
ipaddr |
定义本地的IP地址 |
stdin |
定义标准输入设备,一般是串口 |
stdout |
定义标准输出设备,一般是串口 |
stderr |
定义标准出错信息输出设备,一般是串口 |
U-Boot的环境变量都可以有缺省值,也可以修改并且保存在参数区。U-Boot的参数区一般有EEPROM和Flash两种设备。
Ø 例一 设置自动启动命令
setenv nandtoram nand read 0x30008000 0x100000 0x180000
setenv bootram bootm 0x30008000 //如果删除,设置为空set bootram
setenv bootcmd run nandtoram\;run bootram //如果改回来为set bootcmd tftp\; bootm
set bootfile "uImage" //可以是set
sa
boot
Ø 例二 设置nfs启动
启动信息只在标准输出上显示(串口)
setenv bootargs console=ttySAC0,115200 root=/dev/nfs init=/linuxrc
nfsroot=192.168.0.66:/home/arm2410/rootfs/root
ip=192.168.0.68:192.168.0.66:192.168.0.1:255.255.255.0:www.richardnee.com:eth0:off
启动信息双显示,即在标准输出上显示(串口),也在lcd上显示
setenv bootargs console=tty0 console=ttySAC0,115200 root=/dev/nfs init=/linuxrc
nfsroot=192.168.0.66:/home/arm2410/rootfs/root
ip=192.168.0.68:192.168.0.66:192.168.0.1:255.255.255.0:www.richardnee.com:eth0:off
Ø 例三 设置cramfs启动
第二分区cramfs文件系统,只在标准输出上显示(串口)
setenv bootargs root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200
只在lcd上显示
setenv bootargs root=/dev/mtdblock2 init=/linuxrc console=tty0
参考:uboot启动参数说明
http://blog.csdn.net/dongliqiang2006/article/details/4224249
Uboot下载内核和文件系统的步骤:
http://hi.baidu.com/%C1%D6%D3%C6%BE%D3%CA%BF/blog/item/b4ef85102887671ab9127b01.html
2.16 #uboot引导linux内核参数传递解析
本节可以先跳过不看,动手实践后有了直观印象后在深入分析。
2.16.1 #基本概念:uboot的bootargs和linux的commadline
Ø 如果uboot的bootargs设置了,kernel的Boot options中的Default kernel command string设置与否,都不启作用,即bootargs覆盖掉设置的Default kernel command string,打印的信息也是bootargs传过来的command string(当然uboot中的参数能够正确放置才行,CONFIG_CMDLINE_TAG必须定义了!)。
Ø 如果uboot的bootargs未设置的话(CONFIG_CMDLINE_TAG未定义),kernel就以Boot options中的Default kernel command string的设置进行启动。
Ø 总之,不管是从NAND还是NOR起动,uboot是用来引导linux的,如果uboot的bootargs设置了的话,uboot在引导Linux时会用bootargs替换掉linux的command string。Linux的CONFIG_CMDLINE在bootloader无法给内核传递参数的情况下才有用,一般是用不到linux内核自己配的CONFIG_CMDLINE的 。
2.16.2 #uboot怎样传递参数给linux
生成的内核镜象有两种zImage及uImage,其中zImage下载到目标板中后,能直接用uboot的命令go来进行直接跳转。这时候内核直接解压启动。不过无法挂载文件系统,因为go命令没有将内核需要的相关的启动参数传递给内核。
传递启动参数我们必须使用命令bootm来进行跳转,Bootm命令跳转只处理uImage的镜象。uboot原始码的tools/目录下有mkimage工具,这个工具能用来制作不压缩或压缩的多种可启动映象文件。
mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构,哪个OS,哪种类型,加载内存中的哪个位置, 入口点在内存的哪个位置及映象名是什么。
(1)启动参数的传递过程
启动参数是包装在数据结构里的,在linux kernel启动的时候,bootloader把这个数据结构拷贝到某个地址(在arm板中,uboot默认放在0x30000100处),在改动PC跳向内核接口的同时,通过通用寄存器R2来传递这个地址的值,下面这句话就是uboot跳向linux kernel的代码(bootm命令,lib_arm/armlinux.c->do_bootm_linux()函数中,270行左右)。
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
theKernel其实不是个函数,而是指向内核入口地址的指针(在arm板中,uboot默认0x30008000处),把它强行转化为带三个参数的函数指针,会把三个参数保存到通用寄存器中,实现了向kernel传递信息的功能。
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); //ih_ep=0x30000100?
在这个例子里,会把 R0赋值为0,
R1赋值为机器号
R2赋值为启动参数数据结构的首地址
因此,要向内核传递参数很简单,只要把启动参数封装在linux预定好的数据结构里,拷贝到某个地址(一般约定俗成是内存首地址+0x100,如arm板中uboot默认放在0x30000100处)。
(2)启动参数的数据结构
启动参数可保存在两种数据结构中,param_struct和tag,前者是2.4内核用的,后者是2.6以后的内核更期望用的。但是,到目前为止,2.6的内核也可以兼容前一种结构,两种数据结构具体定义如下,可以参考./include/asm-arm/setup.h.。
struct param_struct{
union {
struct {
unsigned long page_size; /* 0 */
unsigned long nr_pages; /* 4 */
unsigned long ramdisk_size; /* 8 */
unsigned long flags; /* 12 */
#define FLAG_READONLY 1
#define FLAG_RDLOAD 4
#define FLAG_RDPROMPT 8
unsigned long rootdev; /* 16 */
unsigned long video_num_cols; /* 20 */
unsigned long video_num_rows; /* 24 */
unsigned long video_x; /* 28 */
unsigned long video_y; /* 32 */
unsigned long memc_control_reg; /* 36 */
unsigned char sounddefault; /* 40 */
unsigned char adfsdrives; /* 41 */
unsigned char bytes_per_char_h; /* 42 */
unsigned char bytes_per_char_v; /* 43 */
unsigned long pages_in_bank[4]; /* 44 */
unsigned long pages_in_vram; /* 60 */
unsigned long initrd_start; /* 64 */
unsigned long initrd_size; /* 68 */
unsigned long rd_start; /* 72 */
unsigned long system_rev; /* 76 */
unsigned long system_serial_low; /* 80 */
unsigned long system_serial_high; /* 84 */
unsigned long mem_fclk_21285; /* 88 */
} s;
char unused[256];
} u1;
union {
char paths[8][128];
struct {
unsigned long magic;
char n[1024 - sizeof(unsigned long)];
} s;
} u2;
char commandline[COMMAND_LINE_SIZE];
};
Ø 如上,param_struct只需要设置cmmandline,u1.s.page_size,u1.s.nr_pages三个域。
Ø 对于tag来说,在实际使用中是一个struct tag组成的列表,
在tag->tag_header中,一项是u32 tag(重名,注意类型)其值用宏:
ATAG_CORE,
ATAG_MEM,
ATAG_CMDLINE,
ATAG_NONE
等等来表示,此时下面union就会使用与之相关的数据结构。
同时,规定tag列表中第一项必须是ATAG_CORE,最后一项必须是ATAG_NONE。
比如在linux代码中,找到启动参数之后,首先看tag列表中的第一项的tag->hdr.tag是否为ATAG_CORE,如果不是,就会认为启动参数不是tag结构而是param_struct 结构,然后调用函数来转换。
在tag->tag_header中,另一项是u32 size,表示tag的大小,tag组成列表的方式就是,指针+size,实际使用中用tag_next (params)。
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; //Acorn specific
struct tag_omap omap; //OMAP specific
struct tag_memclk memclk; //DC21285 specific
} u;
};
需要注意的是,这两个数据结构在uboot中和linux中分别有定义,这个定义必须一致才能正常传递参数,如果实际使用中不一致的话就不能正常传递,可以自行修改。
(3) 通过两种数据结构传递参数的具体例子
例子一,通过param_struct让uboot中的go命令可以传递参数
分析: go的代码在common/cmd_boot.c中,里面并没有拷贝启动参数的代码,转向内核的时候也没有传送启动参数所在的地址,因此添加如下代码用于拷贝参数,可以看到,对于param_struct只需要设置cmmandline,u1.s.page_size,u1.s.nr_pages三个域 。
char *commandline = getenv("bootargs");
struct param_struct*lxy_params=(struct param_struct *)0x80000100;
printf("setup linux parameters at 0x80000100\n");
memset(lxy_params, 0, sizeof(struct param_struct));
// clear struct param_struct 's mm space
// then init struct
lxy_params->u1.s.page_size=(0x1<<12); //4K 这个是必须有的,否则无法启动
lxy_params->u1.s.nr_pages=(0x4000000)>>12; //64M 这个是必须有的,否则无法启动
memcpy(lxy_params->commandline, commandline, strlen(commandline)+1);
printf("linux command line is: \"%s\"\n",lxy_params->commandline);
//然后还要向内核传递参数地址,将下面一行代码修改:
rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]); //需要被修改的代码
rc = ((ulong(*)(int,int,uint))addr) (0, gd->bd->bi_arch_number, gd->bd->bi_boot_params); //修改之后的代码
例子二,bootm命令中通过拷贝tag传递参数
为方便阅读,进行了少许修改,但功能不变,该函数参数为存放启动参数的地址。
static void setup_linux_tag(ulong param_base)
{
struct tag *params = (struct tag *)param_base;
char *linux_cmd;
char *p;
memset(params, 0, sizeof(struct tag));
/* step1: setup start tag */
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = LINUX_PAGE_SIZE;
params->u.core.rootdev = 0;
params = tag_next(params);
/* step2: setup cmdline tag */
params->hdr.tag = ATAG_CMDLINE;
linux_cmd = getenv("bootargs");
/* eat leading white space */
for (p=linux_cmd; *p==' '; p++) {/* do nothing */;}
params->hdr.size = (sizeof(struct tag_header)+strlen(linux_cmd)+1+4) >> 2;
memcpy(params->u.cmdline.cmdline, linux_cmd, strlen(linux_cmd)+1);
params = tag_next(params);
/* step3: setup end tag */
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
(4) 如何开启uboot传递参数
在uboot中,进行设置tag的函数都在lib_arm/armlinux.c中,在这些函数前面是有#ifdef。
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
只要有一个被定义就可以传递参数,如果要传递commandline,则要定义CONFIG_CMDLINE_TAG。
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
..........
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
因此,如果你的bootm命令不能传递内核参数,就应该是在你的board的config文件里没有对上述的宏进行设置,定义一下即可。我的arm板的linux2.6.24内核关于这的定义在include/configs/arm2410.h中。
/********CONFIG FOR COMMANDLINE***********/
//加入uboot向linux内核传递的参数,如命令行commandline
#ifndef CONFIG_SETUP_MEMORY_TAGS
#define CONFIG_SETUP_MEMORY_TAGS 1
#endif
#ifndef CONFIG_CMDLINE_TAG
#define CONFIG_CMDLINE_TAG 1
#endif
#ifndef CONFIG_INITRD_TAG
#define CONFIG_INITRD_TAG 1
#endif
//其余的暂时还用不到,如果加进去将会编译出错
//#ifndef CONFIG_SERIAL_TAG
//#define CONFIG_SERIAL_TAG 1
//#endif
//#ifndef CONFIG_REVISION_TAG
//#define CONFIG_REVISION_TAG 1
//#endif
//#ifndef CONFIG_VFG
//#define CONFIG_VFD 1
//#endif
//#ifndef CONFIG_LCD
//#define CONFIG_LCD 1
//#endif
/***********CONFIG FOR COMMAND LINE************/
(5) 引导RAMFS方法
假设将根文件系统rootfs下载到0x30300000.
用BOOTM引导内核和文件系统时,不仅需要将内核进行mkimage处理,文件系统也需要进行相应处理。文件系统处理命令:
mkimage -n "RAMFS" -A arm -O linux -T ramdisk -C none -a 30300000 -e 30300040 -d initrd.bin initrd.img
进行处理后,与内核处理结果相似,会在原来的文件系统映像前面加上一个64字节的头,这个头里包含了幻数,CRC校验信息和最重要的:文件系统起始地址和长度。
如果使用UBOOT引导内核,同时希望将文件系统一并引导,bootm命令后面需要跟两个参数,第一个参数是内核所在的地址,第二个参数是文件系统所在的位置,如
bootm 30008000 30300000
上面说的引导文件系统是利用了bootloader向内核标准传参方法,如果已经在内核将诸如机器号,文件系统地址等全部写死的话,则不需要进行上述操作,只需将PC切换到内核所在地址就可以了。
(6) tag的详细讲解
之前提到,bootloader巧妙地利用函数指针及传参规范将R0:0x0,R1:机器号,R2:参数地址传递给内核。由于R0,R1比较简单,不需要再作说明,需要花点时间了解的是R2寄存器。
R2寄存器传递的是一个指针,这个指针指向一个TAG区域。UBOOT和Linux内核之间正是通过这个扩展了的TAG区域来进行复杂参数的传递,如command line,文件系统信息等等,用户也可以扩展这个TAG来进行更多参数的传递。TAG区域存放的地址,也就是R2的值,是在/board/arm2410/arm2410.c里的board_init()函数中初始化的,如在其中初始化为:
gd->bd->bi_boot_params =0x30000100; //这是一个绝对地址
TAG区的结构比较简单,可以视为一个一个TAG的排列(数组?),每一个TAG传递一种特定类型的参数。各种系统TAG的定义可以参考./include/asm-arm/setup.h。下面是一个TAG区的例子:
0x30000100 00000005 54410001 00000000 00000000
0x30000110 00000000 0000000F 54410009 746F6F72
0x30000120 65642F3D 61722F76 7220306D 6F632077
0x30000130 6C6F736E 74743D65 2C305379 30303639
0x30000140 696E6920 6C2F3D74 78756E69 EA006372
0x30000150 00000004 54420005 30300040 00200000
0x30000160 00000000 00000000
我们可以看到一共有三个TAG:
第一个TAG的长度是5个字,类型是ATAG_CORE(54410001),有三个元素,均为全零。TAG区必须以这个TAG开头。
第二个TAG的长度是F个字,类型是ATAG_CMDLINE(54410009),这是一个字符串,是向内核传递的kernel command line。
第三个TAG的长度是4个字,类型是ATAG_INITRD2(54410005),有两个元素,第一个是start:30300040(30300000+40),第二个是size:200000(2M)。
如果说还有第四个TAG,那就是末尾的两个全零,这是TAG结束的标志。
这些TAG是在./lib_arm/arm_linux.c中的do_bootm_linux函数中建立起来的。具体建立哪些TAG,由相应的控制宏决定,具体可以参考相应代码。例子中第一个TAG是起始TAG,如果环境变量中有bootargs,则建立第二个TAG,如果bootm有两个参数(引导文件系统),则会读取文件系统头部的必要信息,建立第三个TAG。
内核启动后,将根据R2寄存器的值找到这些TAG,并根据TAG类型,调用相应的处理函数进行处理,从而获取内核运行的必要信息。
(7) 文件系统的安排(未验证)
cramfs这种只读的压缩格式,linux内核是直接支持的,yaffs必须要自己来打补丁才能让内核支持,在yaffs2的目录内执行sh patch-ker.sh c $KERNEL 就可以让内核支持yaffs格式了。
至于根文件系统个人觉得cramfs要好些,即省空间又是只读的,对于产品来说安全性稍高一点点。
首先需你有两个分区,一个cramfs和一个yaffs,这个要通过bootloader来实现。当根文件系统设置为cramfs后,修改一下初始化的脚本liunxrc,
在里面加上一句mount /dev/mtdblockX /my 其中X为的yaffs分区number,即可实现两种分区共存。板子出厂时/dev/mtdblock/3是cramfs格式/dev /mtdblock/4是yaffs的,内核启动后用mount /dev/mtdblock/4 /root之后,就可以操纵那一块yaffs的空间了。
本节2.14参考:
uboot--参数-->kernel
http://hi.baidu.com/kebey2004/blog/item/74809400c2b59191e850cd73.html。