嵌入式知识-ARM裸机-学习笔记(9):SD卡启动详解(S5PV210)

嵌入式知识-ARM裸机-学习笔记(9):SD卡启动详解(S5PV210)

一、SD卡介绍

1. SD卡背景知识和特点

SD卡、MMC卡、MicroSD、TF卡:这些卡其实内部就是Flash存储颗粒,比直接的Nand芯片多了统一的外部封装和接口。卡都有统一的标准,譬如SD卡都是遵照SD规范来发布的。这些规范规定了SD卡的读写速度、读写接口时序、读写命令集、卡大小尺寸、引脚个数及定义。这样做的好处就是不同厂家的SD卡可以通用。

MMC标准比SD标准早,SD标准兼容MMC标准。MMC卡可以被SD读卡器读写,而SD卡不可以被MMC读卡器读写。

SD卡/MMC卡等卡类有统一的接口标准,而Nand芯片没有统一的标准(各家产品会有差异)。

)SD卡有写保护而TF卡没有,TF卡可以通过卡套转成SD卡使用。

2. SD卡物理接口

在这里插入图片描述
SD卡由9个针脚与外界进行物理连接,这9个脚中有2个地,1个电源,6个信号线。
在这里插入图片描述

3. SD协议与SPI协议

SD卡与SRAM/DDR/SROM之类的东西的不同:SRAM/DDR/SROM之类的存储芯片是总线式的,只要连接上初始化好之后就可以由SoC直接以地址方式来访问;但是SD卡不能直接通过接口给地址来访问,它的访问需要按照一定的接口协议(时序)来访问。
SD卡虽然只有一种物理接口,但是却支持两种读写协议SD协议和SPI协议

(1)SPI协议特点(低速、接口操作时序简单、适合单片机)
SPI协议是单片机中广泛使用的一种通信协议,并不是为SD卡专门发明的。
SPI协议相对SD协议来说速度比较低。
SD卡支持SPI协议,就是为了单片机方便使用。(单片机如果想要外部扩展SD卡,SPI协议是最合适的)。

(2)SD协议特点(高速、接口时序复杂,适合有SDIO接口的SoC)
SD协议是专门用来和SD卡通信的。
SD协议要求SoC中有SD控制器,运行在高速率下,要求SoC的主频不能太低。

4. S5PV210的SD/MMC控制器

SD卡内部除了存储单元Flash外,还有SD卡管理模块,我们SoC和SD卡通信时,通过9针引脚以SD协议/SPI协议向SD卡管理模块发送命令、时钟、数据等信息,然后从SD卡返回信息给SoC来交互。工作时每一个任务(初始化SD卡、读一个块、写、擦除····)都需要一定的时序来完成(所谓时序就是先向SD卡发送xx命令,SD卡回xx消息,然后再向SD卡发送xx命令····)。

SD卡是支持热插拔的,也就是有某一个寄存器的某一位用来判断SD卡是否插入,如果插入了该位置1,证明SD卡插入了;如果该位为0,证明没有插入SD卡。
在这里插入图片描述

5. 扇区和块的概念

早期的块设备就是软盘硬盘这类磁存储设备,这种设备的存储单元不是以字节为单位,而是以扇区(相当于一个存储单元)为单位。磁存储设备读写的最小单元就是扇区,不能只读取或写部分扇区。这个限制是磁存储设备本身物理方面的原因造成的,也成为了我们编程时必须遵守的规律。
一个扇区有好多个字节(一般是512个字节)。早期的磁盘扇区是512字节,实际上后来的磁盘扇区可以做的比较大(譬如1024字节,譬如2048字节,譬如4096字节),但是因为原来最早是512字节,很多的软件(包括操作系统和文件系统)已经默认了512这个数字,因此后来的硬件虽然物理上可能支持更大的扇区,但是实际上一般还是兼容512字节扇区这种操作方法。
一个扇区可以看成是一个块block(块的概念就是:不是一个字节,是多个字节组成一个共同的操作单元块),所以就把这一类的设备称为块设备。常见的块设备有:磁存储设备硬盘、软盘、DVD和Flash设备(U盘、SSD、SD卡、NandFlash、Norflash、eMMC、iNand)。
linux里有个mtd驱动,就是用来管理这类块设备的

二、SD卡启动详解

1. SD卡与SoC的关系

说到两者之间的关系,我们需要解决一个问题就是SoC为什么要支持SD卡启动
一个普遍性的原则就是:SoC支持的启动方式越多,将来使用时就越方便,用户的可选择性就越大,SoC的适用面就越广。
SD卡有一些好处:譬如可以在不借用专用烧录工具(类似Jlink)的情况下对SD卡进行刷机,然后刷机后的SD卡插入卡槽,SoC既可启动;譬如可以用SD卡启动进行量产刷机(这样的做法比一个一个用烧录工具烧录方便了很多)。像我们X210开发板,板子贴片好的时候,内部iNand是空的,此时直接启动无启动;板子出厂前官方刷机时是把事先做好的量产卡插入SD卡卡槽,然后打到iNand方式启动;因为此时iNand是空的所以第一启动失败,会转而第二启动,就从外部SD2通道的SD卡启动了。启动后会执行刷机操作对iNand进行刷机,刷机完成后自动重启(这回重启时iNand中已经有image了,所以可以启动了)。

2. SD卡启动难点

SRAM、DDR都是总线式访问的,SRAM不需初始化既可直接使用,而DDR需要初始化后才能使用,但是总之CPU可以直接和SRAM/DRAM打交道;而SD卡需要时序访问,CPU不能直接和SD卡打交道;NorFlash读取时可以总线式访问,所以Norflash启动非常简单,可以直接启动,但是SD/NandFlash不行。
以前只有Norflash可以作为启动介质,台式机笔记本的BIOS(类似于Uboot)就是Norflash做的。后来三星在2440中使用了SteppingStone的技术,让Nandflash也可以作为启动介质。SteppingStone(翻译为启动基石)技术就是在SoC内部内置4KB的SRAM,然后开机时SoC根据OMpin判断用户设置的启动方式,如果是NandFlash启动,则SoC的启动部分的硬件直接从外部NandFlash中读取开头的4KB到内部SRAM作为启动内容。
随着该技术的成熟,210中有96KB的SRAM,并且有一段iROM代码作为BL0,BL0再去启动BL1

3. SD卡启动过程

在这里插入图片描述
210启动时首先执行内部的iROM(也就是BL0),BL0会判断OMpin来决定从哪个设备启动,如果启动设备是SD卡,则BL0会从SD卡读取前16KB到SRAM中去启动执行(这部分就是BL1,这就是steppingstone技术)。BL1执行之后剩下的就是软件的事情了,SoC就不用再去操心了。
在这里插入图片描述
iROM究竟是怎样读取SD卡/NandFlash的?
三星在iROM中事先内置了一些代码去初始化外部SD卡/NandFlash,并且内置了读取各种SD卡/NandFlash的代码在iROM中。 BL0执行时就是通过调用这些device copy function来读取外部SD卡/NandFlash中的BL1的。并且这些function函数可供我们使用。
磁盘和Flash以块为单位来读写,就决定了我们启动时device copy function只能以整块为单位来读取SD卡

利用SD卡启动分为两种情况:
情况一(bin文件小于16KB):启动的第一种情况是整个镜像大小小于16KB。这时候相当于我的整个镜像作为BL1被steppingstone直接硬件加载执行了而已。
情况二(bin文件大于17KB ):启动的第二种情况就是整个镜像大小大于16KB。(只要大于16KB,哪怕是17KB,或者是700MB都是一样的)这时候就要把整个镜像分为2部分:第一部分16KB大小,第二部分是剩下的大小。然后第一部分作为BL1启动,负责去初始化DRAM并且将第二部分加载到DRAM中去执行(uboot就是这样做的)。

4. SD卡启动代码分析

对于情况一来说,在启动时BL0进行判断完成后,会将全部16KB的bin文件读取到SRAM中执行,因此不涉及到分散加载的问题,则与之前的实验做法相同。本文主要针对情况二,也就是bin文件大小大于16KB时,该如何做。

思路: 根据上面的分析,当bin文件大小大于16KB时,需要将镜像文件拆分为2部分,第一部分BL1小于等于16KB,第二部分为任意大小,iROM代码执行完成后从SD卡启动会自动读取BL1到SRAM中执行;BL1执行时负责初始化DDR,然后手动将BL2从SD卡copy到DDR中正确位置,然后BL1远跳转到BL2中执行BL2。
因此在BL1中要完成:关看门狗、设置栈、开iCache、初始化DDR、从SD卡复制BL2到DDR中特定位置,跳转执行BL2。

对于SD卡内部,官方文档给出了一个推荐的区域划分:
在这里插入图片描述
根据图我们能得到,最前面要空出来一个扇区,BL1在SD卡中必须从Block1开始(Block0不能用,这个是三星官方规定的),长度为16KB内,我们就定为16KB(也就是32个block);BL1理论上可以从33扇区开始,但是实际上为了安全都会留一些空扇区作为隔离,例如可以从45扇区开始,长度由自己定(实际根据自己的BL2大小来分配长度,我们实验时BL2非常小,因此我们定义BL2长度为16KB,也就是32扇区)。

DDR初始化好之后,整个DDR都可以使用了,这时在其中选择一段长度足够BL2的DDR空间即可。我们选0x23E00000(因为我们BL1中只初始化了DDR1,地址空间范围是0x20000000~0x2FFFFFFF)。

Makefile文件
在这里插入图片描述
由于我们将代码分为了BL1和BL2两部分,因此需要一个总的Makefile文件对两部分代码实现协同管理。
在这里插入图片描述
BL1部分
(1)BL1部分要完成关看门狗、设置栈、开iCache、初始化DDR、从SD卡复制BL2到DDR中特定位置。
因此我们可以在start.S文件中可以完成:
在这里插入图片描述
(2)在sd_relocate.c文件中,我们要实现将SD卡中的BL2部分复制到DDR中的特定位置去执行,这里就用到了重定位的方法。
在这里插入图片描述
官方代码为了方便我们进行SD卡的读取操作,实现给了我们一个将SD卡中的内容拷贝到内存中的函数,即为CopySDMMCtoMem,该函数的地址我们不知道,但是它是通过一个函数起始地址的指针(0xD0037F98)指向了这个函数
在使用时,我们先将内容转换为一个unsigned in *类型,因为0xD0037F98中就是存了一个int型的数字,这个int类型的数字就是真正的copy函数的首地址,转换完之后,我们利用 *(地址)的方式对该指针进行解引用,即可定位到该copy函数,并使用它。

typedef bool(pCopySDMMC2Mem)(int, unsigned int, unsigned short, unsigned int, bool); ,相当于我们指定了一个函数指针类型,后面一大部分是函数要传入的参数,前面表示返回值为bool类型。
在实际使用时, pCopySDMMC2Mem p1=(pCopySDMMC2Mem)(*(unsigned int *)0xD0037F98); ,将上述函数强制转换为这种函数指针类型,然后让它等于p1。在调用时,我们通过p1(2, SD_START_BLOCK, SD_BLOCK_CNT, (unsigned int *)DDR_START_ADDR, 0); 即可实现函数调用。

BL2部分
(1)在BL1中,我们通过start.s文件中的bl copy_bl2_2_ddr,实现了拷贝函数的调用从而对SD卡中第45扇区开始的32个扇区的拷贝,拷贝到DDR中地址为0x23E00000的位置。在BL2部分,我们在start.s文件中不再需要对看门狗等任务进行初始化(因为在BL1中已经做过了),只需要实现一个长跳转指令,跳转到DDR的对应位置,继续执行接下来的操作(BL2)。
在这里插入图片描述
(2)由于在BL1中实现了将剩余代码拷贝到了DDR上的0x23E00000,因此在链接脚本中,需要指定重定位后代码运行的位置。这个地址一定要和代码拷贝到的DDR种的地址对应。
0x23E00000
在这里插入图片描述
根据上述做法可以实现:
(1)iROM,也就是BL0先启动,判断OMpin来决定通过SD卡启动,因此BL0从SD卡读取前16KB(我们编写的BL1部分)到SRAM中去启动执行。
(2)在执行BL1时,会进行start.s中的相应动作,最重要的是在BL1中会初始化DDR和复制BL2部分到DDR中,到这BL1完成了它的任务,并通过p2()函数跳转到了DDR的0x23E00000位置的代码部分。
(3)由于BL2部分的代码被拷贝到了DDR上的0x23E00000位置,则继续执行接下来的代码,在宏观来看实现了整个代码的连续。

5. 通过write2s进行烧写

整个代码进行make之后会生成一个write2s文件,用于在linux中通过dd命令进行SD卡的烧写任务。
在这里插入图片描述
if=./BL1/BL1.bin:表示输入文档为BL1文件夹下的BL1.bin文件。
of=/dev/sdb:表示输出目录为/dev下的/sdb目录(也就是对应SD卡的位置)。
seek=1:表示烧写位置是扇区1。
综上可以理解为:将BL1中的bin文件烧写到SD卡的第1个扇区位置(因为第0个扇区需要空出来),这一部分是BL1对应的16KB内容。将BL2中的bin文件烧写到SD卡的第45个扇区的位置,这一部分是多与16KB的部分(之后这部分将会在DDR初始化之后被拷贝到DDR上进行执行)

因为我们BL1和BL2其实是2个独立的程序,链接时也是独立分开链接的,所以不能像以前一样使用ldr pc, =main这种方式来通过链接地址实现远跳转到BL2。我们的解决方案是使用地址进行强制跳转。因为我们知道BL2在内存地址0x23E00000处,所以直接去执行这个地址即可。

如何在linux下进行SD卡烧写?
烧录过程:
1.先将SD卡通过读卡器插到电脑上
在这里插入图片描述
2.默认情况是在windows电脑上,所以需要先与虚拟机进行连接(虚拟机->可移动设备->连接,这样从devices中就可以发现SD卡,通过ls /dev/sd*可以查看,如果有/dev/sdb即成功)
在这里插入图片描述
3…/write2sd执行完成,显示32+0 records in……即为烧录成功
在这里插入图片描述

总结:
代码分为2部分,这种技术叫分散加载。这种分散加载的方法可以解决问题,但是比较麻烦。分散加载的缺陷:第一,代码完全分2部分,完全独立,代码编写和组织上麻烦;第二,无法让工程项目兼容SD卡启动和Nand启动、NorFlash启动等各种启动方式。

uboot中的做法:
程序代码仍然包括BL1和BL2两部分,但是组织形式上不分为2部分而是作为一个整体来组织。它的实现方式是:iROM启动然后从SD卡的扇区1开始读取16KB的BL1然后去执行BL1,BL1负责初始化DDR,然后从SD卡中读取整个程序(BL1+BL2)到DDR中,然后从DDR中执行(利用ldr pc, =main这种方式以远跳转从SRAM中运行的BL1跳转到DDR中运行的BL2)。uboot这种做法的好处是,能够兼容各种启动方式。

发布了65 篇原创文章 · 获赞 83 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_42826337/article/details/104716220