以下用以记录uboot代码的分析过程,目标是s3c6410,如有错误,欢迎指正。
强调,内容与三星原厂提供的uboot-1.1.6有更改的地方,因为外接外设的区别,特别是nand_flash、外接网卡芯片和LCD芯片
以下纯代码情景分析,请结合uboot的功能结构图和内存分布图查看代码,这样会更加容易理解。
s3c-u-boot-1.1.6源代码可以在三星下面的网站获得,但前提是你有官方的email。
还是百度google搜一下吧,当然我这也是有的哦。
http://www.samsung.com/global/business/semiconductor/productInfo.do?fmly_id=835&partnum=S3C6410
功能结构图(上图) uboot内存分布图(上图)
1.start.s代码分析(第一阶段)
/* 以下是具有arm特色的异常向量表,为中断异常准备 */
--------------------
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:
.word fiq
_pad:
.word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
--------------------
/* 当发生中断异常时,pc会跳转到.word的后面地址处 处理异常,
undefined异常由arm核译码单元检测,并触发未定义指令异常请求,硬件设置pc的值为0x4,强制程序从内存0x4地址执行指令;
0x8存放软件中断处理指令,arm中使用swi指令时触发软件中断,硬件设置PC的值为0x8,同时进入系统模式,多用在系统库的编写;
prefetch异常,预取指中止异常,导致正在取的指令无法正常取出,这里需要注意流水线造成的pc值 ;
data中止,无法获取数据,产生的原因有可能是内存未准备好、内存无读或写权限等一些原因产生的异常;
0x14暂时未使用;
0x18提供系统硬件中断跳转接口,一般我们的处理器都会引出很多的外部中断线,在这里能做的就是判断系统中断线产生的中断,注册中断,初始化中断,调用中断函数等等;
0x1c地址为_fiq快速中断,一个系统在中断流水线上可能产生很多中断,但快中断只会有一个
*/
--------------------
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:
.word fiq
_pad:
.word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
--------------------
/*
_TEXT_BASE标号所代表的是uboot代码的运行地址,对于s3c6410
系统来说,如果nand flash启动方式,系统会把0xc000000里面前4KB的内容映射到引导镜像区,即0x0地址,但是我们需要把
uboot代码放到我们的SDRAM,原因是我们代码里面需要对变量做更改并且增加代码执行效率等
下面代码的含义是定义uboot程序执行的运行地址,值为0xc7e00000,.word后面的值TEXT_BASE在编译的时候,
通过向编译器传递参数获得,-DTEXT_BASE方式向编译器传递宏参,在编译的时候可以注意下编译的时候都会指定它的值,值得定义在
config.mk中,Makefile会包含它。
*/
--------------------
_TEXT_BASE:
.word TEXT_BASE
--------------------
/*
在uboot里面会开启MMU,下面是在MMU开启前uboot在内存存放的真实物理地址,值为0x57e00000。强调一下,我们做的开发板的SDRAM在DMC1上,即访问物理内存的实际物理地址从0x50000000开始,SDRAM的大小为256M,正好是一个DMC1,所以内存的访问地址就是0x50000000-0x6FFFFFFF之间了。
*/
--------------------
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
--------------------
/*
这个不解释也是可以的,但是还是要解释。很多人对_start的值有疑惑,认为是0x0,因为看到_start的标号在代码段最开始处,其实是错误的,汇编代码里面的标号是和编译时指定的运行地址有关系的。我们在编译程序的时候会通过-DTEXT_BASE=0xc7e00000参数告诉编译器我们程序将会运行在0xc7e00000地址,那么自然编译器会认为代码开始的时候就运行在这个地址,那么_start的值自然就是0xc7e00000了。总结之,标号的值与编译时指定的程序地址有关系,而与程序实际存放在内存出的位置无关。 小心使用哦。特别在使用一些伪指令的时候
*/
--------------------
.globl _armboot_start
_armboot_start:
.word _start
--------------------
/*
下面的代码__bss_start的值是在u-boot.lds脚本里面定义的,虽然没给值,但是你要知道文件的大小和位置是由
编译器指定的,那么还需要我们告诉它值吗?所以没值胜有值啦,由编译时编译器决定它们的值
*/
--------------------
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
--------------------
/*
uboot开始执行的第二条代码处即在这里了,下面的代码使得cpu的模式为管理模式,如果想使得为cpu为管理模式,需要保证cpsr寄存
器的最低5位为10011,下面是把0xd3的值赋值给cpsr,0xd3即1101 0011,最高两位置1的意思为关闭中断和快中断,这是为了防止代码
正在执行时,产生外部中断,导致程序跳转到异常向量表而无法正常按顺序执行。5位为0的意思是cpu的状态为arm状态,如果是1则cpu进入thumb态,thumb态处理16位指令代码和数据。
*/
--------------------
reset:
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
--------------------
/* 以下标号所在处的代码比较多,将做逐步分析,这段代码主要的工作也就是改了一些硬件寄存器和内存初始化工作 */
--------------------
cpu_init_crit:
--------------------
/*
指令的含义为刷新指令和数据缓存。mcr的意思是把arm寄存器的值赋值给coprocesser寄存器,拿第一条指令来说,
p15代表协处理器,0为一定的值,指令中0b0000四位来表示,现在无具体作用,如果不是0则结果未知,后面的r0是即将写入
c7目标寄存器中的值,后面还有个c7所代表的意思为额外操作码,如果不是c0,则表示的是同一个寄存器的不同物理寄存器,因为
同一个寄存器的名字并不代码通一个物理内存,我们在学rpsr的时候应该知道这点,最后的0提供附加信息,用于区分同一寄存器的
不同物理寄存器,如无附加信息,请保持为0值,否则结果不可预测
下面三行代码不难看出,c7、c8的值被清为0,为什么要清为零呢,你需要去看arm1176jzf-s芯片手册了,其中是有说明的,不再累述,
arm1176jzf-sarm核芯片手册下载地址httop://www.arm.com,也可以与本人联系获取。
*/
--------------------
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
--------------------
/*
实在不想解释这段,因为以前看过芯片手册的解释,且不止一遍,对于这里面要更改的内容就是不能全部记下来,和工作有关了,
不能全心搞这块内容,最多2个月左右的时间能回来回顾一下了。总之还是去查arm11核芯片手册,因为以下改的内容是协处理器
c1,那么你就该去查c1是用来干什么的。查看得知,是控制寄存器,查看手册是online books12.2.2 Primary register allocation
一节,其中13,9,8位为 V、R、S:V位是对高端异常向量表的支持,如果选择0异常向量表为0x00000000-0x0000001c,如果选择
1异常向量表就是FFFF0000-FFFF001c;R位用于ROM保护的,具体的还要与c5里面的配合,这都是MMU惹的祸,很烦,但是现在
我们还没有讲到MMU,所以为什么这样做,也必须到讲到MMU的时候才见分晓了,S在这里面的意思也是用于系统保护的,和MMU
又是有很大的关系,好吧,后面会找MMU算账的,这里就先不深入了,接下来再分析下下面的指令含义
bic r0, r0, #0x00000087 @ clear bits 7, 2:0(B--- -CAM) 的B位为0表示支持小little-endian,1表示支持big-endian格式的系统内存
CAM为第三位,M为0代表禁止MMU,反之打开,A代表地址对齐检查,0代表禁止,C代表指令数据cache控制,0为禁止
orr r0, r0, #0x00000002 @ set bit 2 (A) Align 这段指令又比较犯贱了,打开地址对齐检查了,这是应该的O(∩_∩)O~,后面又
设置12位为1,含义是如果数据cache和指令cache是分开的话,这里面置1的含义将会打开指令缓存
*/
--------------------
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
--------------------
/*
以下代码的作用是为了给256M的内存在MMU开启的时候把0x70000000作为重映射的基地址
c15协处理器寄存器在s3c6410上有特殊作用,它是外部内存端口映射寄存器,32位,在开关MMU的时候发生作用,且优先级最高
这里的0x70000000为外部端口的基地址,0x13的二进制为0x10011,0x10011的意思为256M,代表映射的
大小为256M,0x10010为128M。假如你没开MMU,PHY和Peri port映射的地址将相同。通过下面的内容后,我们知道我们原来uboot
代码是放置到0x57e00000的,现在便只能通过0x57e00000+0x70000000虚拟地址来访问uboot起始地址了。
使用C15的方法是:
1.Opcode_1 set to 0
2.CRn set to c15
3.CRm set to c2
4.Opcode_2 set to 4
还有问题请参考arm1176jzfs芯片手册,如下图:
*/
--------------------
/* Peri port setup */
ldr r0, =0x70000000
orr r0, r0, #0x13
mcr p15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff)
--------------------
/*
下面是一条跳转指令,代码这里不贴,但是其中的代码很重要,在lowlevel.S中实现比如说点亮LED灯、关闭watchdog、关闭中断、系统
时钟初始、nand flash初始化、内存控制器初始化。不过说实在的,去仔细分析这些初始化的过程,对于你对如何控制硬件有很大的帮
助, 对于这个函数,所要说的东西太多,会在后面的文章中单独分析它,现在先知道功能就好,没有它代码无法启动。
*/
--------------------
bl lowlevel_init
--------------------
/* 跳转出来以后,继续执行下面的代码,下面的代码是判断程序是否已经在ram中了,在的话就不拷贝,直接跳转到after_copy了,否则
继续执行下面的代码 */
--------------------
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_PHY_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip flash copy */
--------------------
/*
下面代码通过函数copy_from_nand函数把代码拷贝到ram中。steppingstone只能拷贝4KB,我们需要把所有的代码搬运到内存中哦
我们知道s3c6410可以通过SD、onenand、nand启动,但是我们这里做了简化,先只从nand启动,以后会再增加SD卡启动
copy_from_nand代码也在start.S中,做了修改以适合大页访问,如有需要请留言告知,将添加copy_from_nand代码分析
*/
--------------------
#ifdef CONFIG_BOOT_NAND
mov r0, #0x1000
bl copy_from_nand
#endif
--------------------
/* SD卡启动方式,这个宏我没有定义,先保留吧 */
--------------------
#ifdef CONFIG_BOOT_MOVINAND
ldr sp, _TEXT_PHY_BASE
bl movi_bl2_copy
b after_copy
#endif
--------------------
/* 这里我啥都没做*/
after_copy:
/*
打开MMU功能
协处理器c3的作用是存储的保护和控制,用在MMU中为内存的域访问控制
c3为32位寄存器,每两位为一个访问控制特权,0x00代表没有访问权限,这时候访问将失效;0x01为客户类型,将根据
地址变换条目中的访问控制位决定是否允许特定内存访问;0x10是保留的,暂时没有使用,0x11为管理者权限,不考虑
地址变换条目中的权限控制位,将不会访问内存失效。
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0,代码的含义为设置高8个域无访问权限,低8个域为管理者权限。
接着下面通过mcr p15, 0, r1, c2, c0, 0指令给c2赋值,c2用于保存页表基地址。所谓页表基地址即是虚实转换的内存页表的首地址。
这里r1的值赋值给了c2,r1的值为0x57exxxxx,c2高14位是储存页表的基地址
最后代码很简单,打开MMU。
*/
--------------------
#ifdef CONFIG_ENABLE_MMU
enable_mmu:
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
/* Set the TTB register */
ldr r0, _mmu_table_base
ldr r1, =CFG_PHY_UBOOT_BASE
ldr r2, =0xfff00000
bic r0, r0, r2
orr r1, r0, r1
mcr p15, 0, r1, c2, c0, 0
/* Enable the MMU */
mmu_on:
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1 /* Set CR_M to enable MMU */
mcr p15, 0, r0, c1, c0, 0
nop
nop
nop
nop
#endif
--------------------
/*
堆栈初始化代码,我们在这里定义了CONFIG_MEMORY_UPPER_CODE
sp的值为0xC7FFFFE8
*/
--------------------
skip_hw_init:
/* Set up the stack */
stack_setup:
#ifdef CONFIG_MEMORY_UPPER_CODE
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0xc)
#else
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
#endif
--------------------
/* 清零BSS段内容为0 */
--------------------
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
--------------------
/* 跳转到uboot代码的第二个阶段,第二阶段基本上都是用C实现的,幸好前面sp的值已经设置好了 */
--------------------
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
--------------------
2.第二阶段代码分析(代码在lib_arm目录下的board.c里面,start_armboot函数)
1)初始化CPU及外围硬件
init_fnc_t **init_fnc_ptr;
char *s;
#ifndef CFG_NO_FLASH
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
#if defined(CONFIG_BOOT_MOVINAND)
uint *magic = (uint *) (PHYS_SDRAM_1);
#endif
/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
ulong gd_base;
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
#ifdef CONFIG_USE_IRQ
gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
gd = (gd_t*)gd_base;
#else
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#endif
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
解释:定义二级指针init_fnc_ptr指向一个存放函数指针的数组,init_fnc_ptr是typedef int (init_fnc_t) (void)类型,即函数类型,init_fnc_ptr可以指向一个没有参数,返回值为int型的函数指针的地址(很绕哦,呵呵),我们看上面代码最后的for循环init_fnc_ptr = init_sequence,if中会使用(*init_fnc_ptr)()方式调用init_sequence中的函数(函数名可以看为一个地址),如果返回值不是0,则执行hang报错。
因为我们定义了CONFIG_MEMORY_UPPER_CODE宏,所以gd = (gd_t*)gd_base,由gd_base的值我们知道,malloc区域、stack区域、bdinfo数据在内存的位置是放在upper of uboot。
__asm__ __volatile__("": : :"memory");这条是内嵌汇编,请查看另一篇介绍内嵌汇编的博文。
gd->bd指针指向数据类型为bd_t的结构体,bd_t结构体记录开发板的参数,例如串口波特率、ip地址、机器类型、启动参数、环境变量位置等。
下面分析for循环执行的函数:
cpu_init:因为我们没有定义CONFIG_USE_IRQ,所以这个函数直接返回0
board_init:
函数内首先执行dm9000_pre_init()函数,因为我们把DM9000AEP网卡映射到内存的Xm0CSn[1]上,所以我们要设置访问CSn[1]的方式,SROM_BW_REG &= ~(0xf << 4);SROM_BW_REG |= (1<<7) | (1<<6) | (1<<4);两条代码的含义为设置0x70000000控制器的CSn[1]访问方式为nBE enable、wait enable、16位数据总线访问模式,SROM_BC1_REG = ((DM9000_Tacs<<28)+(DM9000_Tcos<<24)+(DM9000_Tacc<<16)+(DM9000_Tcoh<<12)+(DM9000_Tah<<8)+(DM9000_Tacp<<4)+(DM9000_PMC));这句话的意思是设置访问的时序,s3c6410 datasheet中已经给出了时序代码,欢迎查看哦。因为我的zc6410开发箱接了4.3的TFT LCD,所以在代码中增加了对LCD的配置工作,代码如下:
writel(readl(MIFPCON) & (~(1 << 3)), MIFPCON);
writel(readl(MIFPCON) & (~(3 << 0)) | 0x1, SPCON);
writel(0xaaaaaaaa, GPICON);
writel(0xaaaaaa, GPJCON);
以上四行代码分别对MIFPCON、SPCON、GPICON、GPJCON四个寄存器赋值。为什么赋值?查看s3c6410 datasheet手册14.5.1节,给出了DisplayController的引脚配置,MIFPCON的[3]位设置为0(normal mode)instead of “1”(by-pass mode),SPCON[1:0]位的值设置为“01”(use RGB I/F Style)or “00” to use Host I/F Style,我们设置的是01。GPICON、GPJCON赋值的原因请看下面图:
第一张图是LCD控制器接口连接原理图,后面的是图是芯片手册,通过两个图我们就知道为什么要写后面两行代码了吧。
好了,73-74行一个是记录机器类型,一个是指定向内核传参的地址。
interrupt_init:
上图,我发现注释很给力,我想我就不画蛇添足了。不过做一下部分解释
184行:值0x0101的含义是设置Prescaler0、1、2、3的值,请看下图真相(datasheet)
env_init:如下图,因为没有定义ENV_IS_EMBEDDED,所有只是执行了142-143,把环境变量的首地址赋值给gd->env_addr。
init_baudrate:
139行使用getenv_r函数在default_environment里找baudrate关键字,找到后把“=”号后面的值赋值给gd->baudrate,然后
再放到gd->bd->bi_baudrate里面。simple_strtoul是uboot实现的字符串转UL类型。
serial_init:什么都没做,保持默认的8位数据、无奇偶校验、1 停止位、无开始位。
console_init_f:gd->have_console = 1就这一句话
display_banner:串口打印uboot信息,就是uboot启动的时候我们看到的信息,这里使用的是printf,但是我们追进去后,关注的函数
应该是serial_putc,它是真实向串口输出一个字符的函数,这个函数会递归调用,应该说自己调用自己,遇到\n结束。
print_cpuinfo:打印CPU信息,CPU型号和速度 CPU:...
checkboard:打印开发板信息 BOARD:...
dram_init:
记录dram的起始地址,0x50000000,size为256M(me的)
display_dram_config:因为没有定义DEBUG,所以打印DRAM:256M
2)配置malloc空间
(因为CONFIG_LCD、CONFIG_VFD没有定义,所以跳过这一部分)
我们定义了UPPER_CODE,所以执行第一个mem_malloc_init。这个函数的作用是记录堆栈空间的起始地址、结束地址、当前地址。
3)启动设备初始化(SD、NAND、ONENAND)
系统一开始尝试从SD卡启动,因为本篇介绍的是NANDFLASH启动方式,所以SD卡部分暂不分析,会单独开辟章节介绍s3c6410
的SD卡启动方式(包括windows下SD flasher应用程序的编写、SD卡硬件电路分析、SD寄存器操作和启动流程)。
下面分析nand启动方式,我们在自己的头文件里定义了CONFIG_COMMANDS和CFG_CMD_NAND,所以会执行nand_init函数
分析代码中的368行nand_init函数,我们知道在uboot启动起来之后,会显示NAND: 512M(你nandflash的大小),所以不难想象,
nand_init会向终端打印NAND大小的信息,以下是nand_init的实现:
nand_init函数的实现体在drivers/nand/nand.c中,nand_init函数不仅会打印出nandflash的大小,还会初始化描述nand的结构体
nand_info以及代表“nand”的设备结构体nand_chip,这两个结构体前者是mtd层对设备的抽象和对块设备的接口统一,后者是
设备的实体,所有对设备的读写控制操作都最终通过这个结构体完成,下面我们开始分析nand_init函数:
64~69行:从外表看,最后会执行size += nand_info[i].size,由此最起码可以猜测到这个函数会计算出nand的大小。那么是怎样计算出
来的呢,我们需要看nand_init_chip函数,注意,在进入这个函数之前,先看一下传入的三个参数,前两个参数我们已经介绍过,第三
个参数是nand的数据寄存器,访问地址为0x70200010。nand_init_chip函数会根据我们传入的参数,去查找对应的nand设备,并初
始化一些功能接口为以后对nand操作做准备,下面看图:
47行:mtd->priv实现了mtd中间层对底层nand设备的接口,我们以后在访问nand硬件时,通过mtd的priv成员可以快速找到我们的
nand设备。
49行:nand成员中保存了读写nand数据的数据寄存器基地址,我们通过读写base_addr中的数据,实现对nand中数据的读和写,
后面的__iomem是个宏定义,这样定义的#define __iomem,只定义并没有给值,所以没有任何功能意义,但是对于我们在看代码
的时候,很容易能判断出后面的变量是IO地址空间的寄存器地址。
50行:是对nand设备的初始化操作,我们进入函数体
820~823行:判断是否是从nand_flash启动。看s3c6410寄存器就会明白:
820行:NFCONF定义的宏,其实是取0x70200000地址里面的内容,那么如果我们把OM跳线设置为nand启动,这个[31]
位的值就会为1,这样的话NFCONF & 0x80000000的值就是1了,因而boot_nand的值为1;
825行:清除0x70200004的[16]位,关闭软件锁存,如果此位设置为1,则NFSBLK(0x70200020)到NFEBLK
(0x70200024)-1被开启,除了这部分区域,写或擦除命令是无效的,只有读命令是有效的(NFSBLK和NFEBLK)为可编程
可编程开始和结束块地址寄存器;
826~827行:我们在进入这个函数的时候就做过了;
828行:nand->cmd_ctrl = s3c_nand_hwcontrol,这个函数是用于向nand硬件发送命令的,比如发送00h,代表的是读命令。
829行:nand->dev_ready = s3c_nand_device_ready,是用于判断nand芯片处于忙/可读状态的。s3c_nand_device_ready
用一个循环去判断NFSTAT(0x70000028)的最低位(RnB输入引脚状态),如果是1表示现在nand可读,0代表正在忙不可读
830行:bbt(bad block table)坏块表,因为我们没用到,所以s3c_nand_scan_bbt函数会直接返回;
我们没有定义宏CFG_NAND_FLASH_BBT,所以nand->options |= NAND_SKIP_BBTSCAN,后面宏的含义代表在初始化期间
将跳过坏块扫描;
839行:CFG_NAND_HWECC需要我们自己定义,含义代表使用错误纠错码;
840行:代表使用NAND_FLASH模块内部的ECC模块产生纠错码;
841行:nand->ecc.hwctl = s3c_nand_enable_hwecc,设置对ECC的控制,这个函数应该在产生ECC编码前被调用。这个函数的
功能为确认SLC FLASH或是MLC FLASH,SLC代表single layer cell,MCL为multi-level cell,有关SLC和MLC的区别在容量、可
读写总次数、读写速度上,SLC的读写速度要快于MLC,但MCL的容量要比SLC大很多,因为1cell可以容纳4bits,有兴趣可以查阅
相关手册。此函数还有一个功能为:1.初始化主区ECC解码器/编码器(向0x70200004的[5]位写1)2.开启主区ECC
(向0x70200004的[7]位写0);
842行:nand->ecc.calculate = s3c_nand_calculate_ecc,用于存储产生的校验纠错码;
843行:nand->ecc.correct = s3c_nand_correct_data,用生成的ECC码检测是否有错误,没有则返回,具体内容看函数说明就好;
845~849行:向nand发送4条命令,849行为等待设备准备好;
851~852行:将读取到nand芯片的厂商信息和芯片ID编号;
854~859行:nand_flash_ids结构体保存了很多公司生产的nand芯片信息和编号,for循环将通过ID找到和我们板子匹配的NAND芯片;
再往下虽然代码挺多的,但不用担心,只会执行877行的nand->ecc.layout = &s3c_nand_oob_16,这是在定义oob信息;
以下是nand芯片可以处理的命令以及命令的含义(下面是三星K9F4G08U0A-PCB0芯片的命令集)
分析完board_nand_init函数后,我们继续看nand_init_chip第52行,nand_scan函数:
这个函数的主要功能就是2768行的nand_scan_ident函数,功能是填充mtd结构体,配置对nand的接口。这样下次在访问设备时,可
通过mtd层找到对应的底层设备,我们看下nand_scan_ident函数:
到此,我们不再往下追函数了,
2501行在设置mtd设备层的接口函数,
2504行nand_get_flash_type函数代码比较多,主要的功能还是在获得nand芯片的厂商信息和ID,并判断是否支持,如果支持
为这个nand设备和mtd填充一些功能接口函数,
我们再来看nand_scan_ident函数的后面代码,2512行是for循环,maxchips的值是nand_scan函数传递进来的1,所以我们最后
看到的i值为1,在2526行chip->numchips=1,mtd->size=512(我的nandflash型号是K9F4G08U0A-PCB0,大小512M,
我们在board_nand_init函数中已经在结构体nand_flash_ids中找到我们的nand型号,chipsize的值为512)。
nand初始化分析完毕。。。
由于文章长度太长,以下分析部分放到s3c6410 uboot代码分析二(下面是链接)
http://hi.baidu.com/__eabi/blog/item/bf06b6c14c13434b0eb345d9.html?timeStamp=1313056638500
4)环境变量初始化
5)网络初始化
6)设备列表初始化
7)配置功能函数表
8)终端完全初始化
9)开中断异常向量表
10)网卡芯片初始化
11)一些后续初始化
12)main_loop详解
等有时间,会针对这些问题在新的文章中深入浅出说明以下问题:
1.硬件初始化的详细过程,比如说nand_flash、serial(通过原理图、datasheet、寄存器控制等说明)
2.网络传输功能(将介绍DM9000网卡芯片的驱动程序)
3.命令交互(uboot中是如何添加命令的,如何增加自己的命令调用)
4.环境变量的存储和调用,以及uboot代码中的一些重要环境变量
敬请期待,
原文地址:http://hi.baidu.com/yanguang_1261/item/ed6f623c08408006cfb9fe48