二、nand flash boot分析
S3C2410支持从nor/nand flash、eeprom等rom类型的介质启动。现在我想做的是从外部nand flash启动,首先看datasheet第一部分:
可以明确,首先,s3c2410支持从nand flash存储介质启动,其次,在硬件上,s3c2410除了提供相应的逻辑外,还提供了一个4K的sram作为buffer用于nand启动。最后,s3c2410支持从nand flash启动之后的内存分配形式(也就是说,从nand flash启动之后和从nor flash启动之后的内存分配形式是不同的。s3c2410支持这两种形式,可以说是相对于其他的MCU不同的地方。)
然后看datasheet的第六部分:nand flash controller。overview中首先讲述了用nand flash代替nor flash作为启动介质的原因(成本低)。
可以很明显的看出,s3c2410x启动代码从外部nand flash启动的流程:上电复位后,s3c2410自动读取nand flash的前4KBytes的数据到内部sram buffer中,这个硬件的sram buffer被称为“Steppingstone”。然后执行下载到steppingstone的代码,这部分代码完成将nand flash的内容复制到sdram中,在复制时,利用硬件的ECC验证数据有效性。完成复制后,主程序就开始从sdram执行。
过程应该是很清晰。但是首先怀疑的是,s3c2410如何实现自动读取nand flash的前4KBytes数据到内部sram buffer当中。网上为什么没人就这点产生疑问,并深入分析呢?我分析可能有两种方法:一是像at91rm9200一样,内部集成一个小的rom,固化代码,这部分代码的作用就是完成自动读取功能;二是完全用硬件实现。仔细看了框图,发现s3c2410只有internal sram buffer,并没有rom,所以最大可能就是硬件实现。看一下硬件框图figure6-1,可以发现hardware
ECC编解码器,可以看到internal buffer(4KB),另外注意的一个地方是,存在着control state machine和buffer control,而且之间有粗体线链接。也就是说明了用一个控制状态机实现了自动读取4KB数据的过程,完全的硬件实现。如下图所示:
![]() 明确了这个问题之后,对从硬件上电到nand flash启动就比较清晰了。而且,也就理解为什么vivi的stage1的head.S必须要小于4KB,因为internal sram buffer只有4KB。如果要完成一个比较复杂的bootloader,那也应该尽量简化stage1,完成基本的初始化之后,把剩余的工作量都放到将nand flash的代码搬移到sdram之后进行。
三 memory controller分析
看datasheet第五部分。s3c2410比较特殊,支持1G的内存空间,分为8个bank,每个bank128MBytes,128MB×8=1GB。但是在这8个bank中,又有所不同,并且nand flash不对应任何bank,它是通过一组寄存器来访问的(nand flash的地址不在8个bank中,所以它不对应任何一个bank,它一般由处于高端地址的SFR区也就是特殊功能寄存器区来访问,地址一般在0x48000000以上),可看上面框图的register
bank。
可以推断出,sdram应该在bank6,起始地址固定为128M*6=0x30000000,在此之后,就要根据sdram的大小和位宽来决定了,而且有个注意的地方是,bank7必须和bank6一样大小。参考figure5-1和table 5-1就非常清晰了。现在EDUKIT-III上用了两片SDRAM,型号是HY57V561620CT-H,查看datasheet,它是4banks×4M×16bit=256Mbits=32Mbytes,那么两片组合起来就是64MBytes,位宽是32bit,所以bank6的地址范围是[0x30000000-0x33ffffff],bank7的地址范围是[0x34000000-0x37ffffff],所以bank6的起始地址是固定的为0x30000000
,但是bank6的大小事不固定的取决于板子上面实际的SDRAM的位宽和大小,而bank7因为是紧挨着bank6所以bank7的起始地址也是不固定的,大小同样也是不固定的。查看s3c2410 table 5-2,可以知道bank选择地址线为A[25:24]--->BA[1:0]。
S3C2410提供了外接ROM、 SRAM、 SDRAM、 NOR Flash、 NAND Flash的接口。 S3C2410外接存储器的空间被分为8 个BANKS,每BANK容量为128M:当访问BANKx(x从0到7,对应的地址范围(x*128M到(x+1)*128M-1,BANK6、7有稍微差别)时,片选信号nGCSx有效。本文所用的开发板,使了64M的NAND Flash和64M的SDRAM,NAND
Flash不对应任何BANK,它是通过几组
寄存器来访问的,在上电后,NAND Flash开始的4k数据被自动地复制到芯片内部一个被称为“Steppingstone”的RAM上。Steppingstone被映射为地址0,上面的4k程序完成必要的初始化;SDRAM使用BANK6,它的物理起始地址6*128M=0x30000000。 关于sdram,还应该知道刷新频率和列宽度。HY57V561620CT-H datasheet中,有:
所以刷新频率为64ms/8192=7.8125us。
查看PIN DESCRIPTION,可以看出A0-A12为地址,其中ROW Address为RA[0:12],Column Address为CA[0-8],显然CAS的位数为9bits。
四、实验内容分析
实验内容很简单,就是完成基本的初始化之后,把steppingstone的4K数据搬移到sdram中。然后在sdram中执行灯循环点亮程序。结合这个实验,也可以很清晰的明白,前面几个基本实验,从nand flash启动后,所有代码搬移到了steppingstone中,实际执行时也是在steppingstone中,也就是boot internal sram(4KB)中执行的,所以运行时域和加载时域都是0x00000000,设置的堆栈可以是1024,也可以是4096,但是注意一是最大为4096,二是保证不与可执行代码发生冲突。在这个程序中,运行时域和加载时域是不相同的。加载时域是0x00000000,但是运行时域是0x30000000。《s3c2410完全开发》对这个地方讲解不是太详细。经过实验,和王老师的帮助,弄清楚了到底怎么回事。现在关于运行时域和加载时域的具体分析如下:
根据nand flash的特点,初始代码的加载时域为0x00000000,也就是当前PC的值为0x00000000,两种跳转指令b(l)等只能相对寻址,最大范围是+/-32MBytes,所以如果不改变PC的话,不可能能利用b或者bl跳转到sdram的空间中。跳转指令ldr则不受此寻址空间的限制,可以进行绝对寻址。需要了解的一个细节就是,链接后所有的标号都是基于运行地址的,比如运行地址为0x30000000,那么第一个标号_start地址就是0x30000000,所以可以利用ldr的绝对寻址来完成到sdram的跳转。下面根据编写的sdram的反汇编来进行分析:
先来分析这个工程中主Makefile的语法和含义:
CFLAGS := -I./include
//gcc的编译选项,表示要在当前目录的include目录下查找相对应的头文件
LDFLAGS := -Ttext 0x30000000 //ld的链接选项,表示在链接时将整个工程的代码段也就是Text段加载到0x30000000 OBJS := $(patsubst %.s, %.o, $(wildcard arch/*.s))
//两个Makefile中函数的运用,在规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard
PATTERN...)
。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表,而patsubst函数则是替换函数,上面的意思也就是将arch文件夹下面所有以.s结尾的文件全部展开,然后将这些以.s结尾的文件全部替换为以.o结尾
OBJS += $(patsubst %.c, %.o, $(wildcard init/*.c)) all: sdram
sdram: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@.o //将所有的.o文件也就是目标文件全部链接到sdram.o文件中 $(OBJDUMP) -D $@.o >$@_s //将链接成的目标文件sdram.o中的符号地址全部输出到sdram_s中 $(OBJCOPY) -O binary -S $@.o $@ //利用objcopy将目标文件sdram.o转化为二进制的格式sdram clean: find . -name "*.o" | xargs rm -f $(RM) sdram* debug:
@echo "OBJS: $(OBJS)" # compile rules
%.o: %.s $(CC) $(CFLAGS) -c $< -o $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ 在来分析在使用SDRAM之前需要初始化多个寄存器,这些寄存器很多都是类似的,并且由于我们只是使用BANK6所以大部分寄存器不用理会。一般我们将初始化的一些代码,主要是对寄存器操作的代码利用汇编代码来编写:
@ WDT Register
.equ WDTCON, 0x53000000 @ Memory Control Register Base Address .equ MEM_CTL_BASE, 0x48000000 @ Sdram Base Address .equ SDRAM_BASE, 0x30000000 @ Stack top address .equ stack_top, 0x34000000 @
@ start @ .text
.global _start _start:
@ disable watch dog timer mov r0, #WDTCON mov r1, #0x0 str r1, [r0] @ memory setup 初始化SDRAM寄存器
bl memsetup @ copy the 4K code from steppingstone 将steppingstone的4KB的数据复制到sdram的起始处
bl copy_steppingston_to_sdram @ jump to sdram space
ldr pc, =setup_stack //为什么将setup_stack标号的地址赋给pc,就能跳转到SDRAM中执行,因为在前面利用copy_steppingston_to_sdram已经将程序从steppingstone复制到了SDRAM中来,所以当前语句的下一句也就是在SDRAM中要执行的下一句 setup_stack:
ldr sp, =stack_top //设置堆栈,一般将堆栈设置在BANK7中最大只能为4K bl main //跳转到主函数去执行,此时main函数也已经复制到SDRAM中来
stop:
b stop @
@ sub routines @ @ r1: src base address
@ r2: dst base address @ r3: data block length copy_steppingston_to_sdram: //将steppingstone中4KB的内容复制到sdram中
mov r1, #0x00000000 //基地址也就是steppingstone的地址 ldr r2, =SDRAM_BASE //目的地址也就是sdram的地址 mov r3, #4096 //复制的字节数也就是4KB 1: @ auto-indexing @ first transfer, and then write back to the base register r1 ldr r4, [r1], #4 //每次复制4个字节 str r4, [r2], #4 @ r1 is equal to counter cmp r1, r3 bne 1b mov pc, lr
@ r1: memory control register base address
@ r2: memory control register table address @ r3: r1+13 words(because there is 13 registers) memsetup: //SDRAM寄存器设置初始化
mov r1, #MEM_CTL_BASE //与SDRAM相关的寄存器是从0x48000000地址开始的,每个寄存器占4个字节 adrl r2, mem_cfg_val //adrl中等范围的地址读取,将mem_cfg_val标号表示的地址值读取到r2寄存器中来 add r3, r1, #13*4 //因为总共有13个寄存器,每个寄存器占4个字节,r3作为循环的结束条件 1: @ write initial values to registers ldr r4, [r2], #4 //取出r2寄存器中的地址值对应的值赋给r4,然后r2加上4,指向下一个初始值 str r4, [r1], #4 //将r4的值赋值给r1对应的寄存器,然后将r1加上4,指向下一个寄存器 cmp r1, r3 bne 1b mov pc, lr //返回到调用处
.align 4
mem_cfg_val://相当于定义一个寄存器初值表,将每个寄存器的初始值通过一个标号全部定义在一起,因为每个寄存器的地址是规律的也就是相差4个字节所以可以很方便的通过查表来赋值 .long 0x22111110 @ BWSCON .long 0x00000700 @ BANKCON0 .long 0x00000700 @ BANKCON1 .long 0x00000700 @ BANKCON2 .long 0x00000700 @ BANKCON3 .long 0x00000700 @ BANKCON4 .long 0x00000700 @ BANKCON5 .long 0x00018005 @ BANKCON6 .long 0x00018005 @ BANKCON7 .long 0x008e07a3 @ REFRESH .long 0x000000b2 @ BANKSIZE .long 0x00000030 @ MRSRB6 .long 0x00000030 @ MRSRB7 .end
现在来分析这13个寄存器的初始值,寄存器每位分别表示什么含义:
1.BWSCON:对应BANK0-BANK7,每BANK使用4位。这4位分别表示:
a.STx:启动/禁止SDRAM的数据掩码引脚,对于SDRAM,此位为0;对于 SRAM,此位为1。 b.WSx:是否使用存储器的WAIT信号,通常设为0 c.DWx:使用两位来设置存储器的位宽:00-8位,01-16位,10-32位, 11-保留。 d.比较特殊的是BANK0对应的4位,它们由硬件跳线决定,只读。 对于本开发板,使用两片容量为32Mbyte、位宽为16的SDRAM组成容量为64Mbyte、位宽为32的存储器,所以其BWSCON相应位为:0010。对于本开发板,BWSCON可设为0x22111110:其实我们只需要将BANK6对应的4位设为0010即可,其它的是什么值没什么影响,这个值是参考手册上给出的。 2.BANKCON0-BANKCON5:我们没用到,使用默认值0x00000700即可
3.BANKCON6-BANKCON7:设为0x00018005 ,在8个BANK中,只有BANK6和BANK7可以使用SRAM或SDRAM,所以BANKCON6-7与BANKCON0-5有点不同:
a.MT([16:15]):用于设置本BANK外接的是SRAM还是SDRAM:SRAM-00,SDRAM-11 b.当MT=11时,还需要设置两个参数: Trcd([3:2]):RAS to CAS delay,设为推荐值01,因为在这里我们知道RAS是0:12也就是13位,而CAS是0:8也就是9位,所以delay就是3 SCAN([1:0]):SDRAM的列地址位数,对于本开发板使用的SDRAM HY57V561620CT-H,列地址位数为9,所以SCAN=01。如果使用其他型号的SDRAM,您需要查看它的数据手册来决定SCAN的取值:00-8位,01-9位,10-10位。 4.REFRESH(SDRAM refresh control register):设为0x008e0000+ R_CNT ,其中R_CNT用于控制SDRAM的刷新周期,占用REFRESH寄存器的[10:0]位,23位为SDRAM Refresh Enable一般为1允许自动和自我刷新,22位为SDRAM刷新的模式,是auto还是self,一般我们选择auto.
它的取值可如下计算(SDRAM时钟频率就是HCLK):
R_CNT = 2^11 + 1 – SDRAM时钟频率(MHz) * SDRAM刷新周期(uS)
在未使用PLL时,SDRAM时钟频率等于晶振频率12MHz;SDRAM的刷新周期在SDRAM的数据手册上有标明,在本开发板使用的SDRAM HY57V561620CT-H的数据手册上,可看见这么一行“8192 refresh cycles / 64ms”:所以,刷新周期=64ms/8192 = 7.8125 uS。 对于本实验,R_CNT = 2^11 + 1 – 12 * 7.8125 = 1955, REFRESH=0x008e0000 + 1955 = 0x008e07a3. 5.BANKSIZE:0x000000b2
位[7]=1:Enable burst operation 位[5]=1:SDRAM power down mode enable 位[4]=1:SCLK is active only during the access (recommended) 位[2:1]=010:BANK6、BANK7对应的地址空间与BANK0-5不同。BANK0-5的地址空间都是固定的128M,地址范围是(x*128M)到(x+1)*128M-1,x表示0到5。但是BANK7的起始地址是可变的,您可以从S3C2410数据手册第5章“Table 5-1. Bank 6/7 Addresses”中了解到BANK6、7的地址范围与地址空间的关系。本开发板仅使用BANK6的64M空间,我们可以令位 [2:1]=010(128M/128M)或001(64M/64M):这没关系,多出来的空间程序会检测出来,不会发生使用不存在的内存的情况——后面介绍到的bootloader和linux内核都会作内存检测,位[6]、位[3]没有使用。 6.MRSRB6、MRSRB7:0x00000030
能让我们修改的只有位[6:4](CL),SDRAM HY57V561620CT-H不支持CL=1的情况,所以位[6:4]取值为010(CL=2)或011(CL=3)。 为了让程序结构简单一点,我都使用函数调用的方式。第一条指令是禁止WATCH DOG,您如果细心的话,一定会发现程序LEDS运行得有些不正常,那是因为WATCH DOG在不断地重启系统。以前为了程序简单,我没有把这段程序加上去。往WTCON寄存器(地址0x53000000)写入0即可禁止WATCH DOG。第二条指令设置本节开头所描述的13个寄存器,以便使用SDRAM。往下程序做的事情就是:将Steppingstone中的代码复制到SDRAM中(起始地址为
0x30000000),然后向pc寄存器直接赋值跳到SDRAM中执行下一条指令“ldr sp, =0x34000000”。
在目录SDRAM下执行make指令生成可执行文件sdram后,下载到板子上运行,可以发现与LEDS程序相比,LED闪烁得更慢:这就对了,外部SDRAM的性能比起内部SRAM来说性能是差些。 把程序从性能更好的内部SRAM移到外部SDRAM中去,是否多此一举呢?内部
SRAM只有4k大小,如果我们的程序大于4k,那么就不能指望利用内部SRAM来运行了。所以得想办法把存储在NAND Flash中的代码,复制到SDRAM中去。对于NAND Flash中的前4k,芯片自动把它复制到内部SRAM中,我们可以很轻松地再把它复制到SDRAM中(实验五中函数copy_steppingstone_to_sdram就做这事)。但是对于4k之后的代码,复制它就不那么轻松了,这就是nand flash之后的问题啦。 分析完初始化的汇编代码后,我们来看看生成的sdram二进制可执行文件反汇编的结果:
这里是反汇编的结果(因为链接地址是0x30000000,并且head.s问可执行文件的起始文件):
可以很明显的看出,_start为0x30000000,stop为0x30000020。也就是说,经过链接之后,symbol table中的存放位置都是基于运行起始地址0x30000000的。但是需要注意的是,开始运行是PC的值为0x00000000,虽然bl 30000044 <memsetup>是30000044,但是要注意,此处指令为bl,所以只能相对寻址,而不能够绝对寻址,也就是说,它只能跳转到距离0x30000000为0x44的位置,这点查看bl的汇编指令说明就比较清晰了。
利用上面的技巧,就可以把PC的值装载到sdram的空间,因为之前代码搬移已经完成了,所以,后续的工作都已经工作在sdram的空间中了。
如果在利用objcopy去除了符号信息之后,反汇编之后的结果只能是以0开始的相对地址,也就看不出上面的东西了,所以,要理解还应该是采用上面的分析方法。这点在《s3c2410完全开发》上是没有详细说明的。写到这里,自己已经比较清晰了。关于其他的分析,《s3c2410完全开发》已经比较详细了,可以参考。
|
|