前章我们也大致分析了SD卡的启动过程,在具体进行问题的定位及解决的过程中,发现还是有很多不明确的地方,网上的文章也多是人云亦云让我们来一步一步搞清楚S3C6410 SD卡启动的步骤及过程(我这里的开发板为OK6410,256M+1G的配置,SD卡为2G,MMC)

一、开发板跳线,从SD卡启动

查看《OK6410开发板LINUX2.6用户手册.pdf》 将跳线设置为 11111000 (从左到右为 pin8 到 pin1的设置,别搞反了)
这些设置可以在s3c6410的datasheet中查到,打开《s3c6410_rev12.pdf》125页
Table 3-1. Device operating mode selection at boot-up
从这里可以看到,最后3个位为0,表示从SD/MMC(CH0)启动
修改UBOOT,让它支持从SD卡读取数据,并将自己自举到内存
当我们设置完CPU的跳线,CPU已经清楚要从哪里去取第一条指,S3C6410到底是怎么工作的呢
之前找到过一篇参考文章 《S3C2450_IROM_ApplicationNote_Rev003.pdf》
这次找到了官方的文档更清楚的说明了这一切《S3C6410_Internal_ROM_Booting.pdf》
在文档中我们看到CPU上电之后的启动过程如下
①  iROM supports initial boot up,initialize system clock,D-TCM,device specific controller and bootin device.
②  iROM boot codes can load 4KB of bootloader to stepping stone. The 8KB boot loader is called BL1
③  BL1: BL1 can initialize system clock, UART, and SDRAM for user. After initializing, BL1 will load remaining boot loader which is called BL2 on the SDRAM
④  Finally, jump to start address of BL2. That will make good environment to use system.
按照这个启动过程,我们必须准备好8K的引导代码在BL1,
用来初始化系统,始终,串口,SDRAM等,并且将完整的BootLoader放在BL2上
2.7 Boot Block Assignment Guide 中有详细的描述
2G以下 SD/MMC 的卡的存储结构
SD/MMC 1Block = 512 Byte
=========================================================================================
|                                    SD/MMC Device                                      |
=========================================================================================
|                  | Recommendation |                    Mandatory                      |
| User File System |=====================================================================
|                  | Kernel |  BL2  | BL1(8K)  | Signature(512Byte) | Reserved(512Byte) |
|                  |        |       | 16 Block |       1 Block      |     1 Block       |
=========================================================================================
有了这个大致的印象,我们先放一下,下面我们来修改UBOOT的代码

二、修改UBOOT代码

s3c6410的uboot源码见最后的参考资料
修改makefile 交叉工具链的绝对路径(嘿嘿,这个交叉工具链当然是俺自己做的交叉工具链了,参考前面篇BLOG)
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-none-linux-gnueabi-
endif
CROSS_COMPILE = /opt/cross_toolchains/arm/4.6.1/bin/arm-none-linux-gnueabi-
修改 include/configs/smdk6410.h 打开 #define CONFIG_BOOT_MOVINAND 这个宏,并屏蔽其他的启动选项
在UBOOT的代码中,将IROM的启动模式称之为MOVINAND

这个UBOOT就已经可以从SD卡启动了,S3C6410具体又是怎么工作的呢?

在 cpu/s3c64xx/start.S 中我们看到如下这段
#ifdef CONFIG_BOOT_MOVINAND
ldr     sp, _TEXT_PHY_BASE
bl      movi_bl2_copy
b       after_copy
#endif

分析 movi_bl2_copy 函数

cpu/s3c64xx/movi.c 中 void movi_bl2_copy(void) 中定义如下
#if defined(CONFIG_S3C6400)
CopyMovitoMem(MOVI_BL2_POS, MOVI_BL2_BLKCNT, (uint *)BL2_BASE, CONFIG_SYS_CLK_FREQ, MOVI_INIT_REQUIRED);
#else
writel(readl(HM_CONTROL4) | (0x3 << 16), HM_CONTROL4);
CopyMovitoMem(HSMMC_CHANNEL, MOVI_BL2_POS, MOVI_BL2_BLKCNT, (uint *)BL2_BASE, MOVI_INIT_REQUIRED);
#endif

确定MOVI_BL2_POS的值

./include/movi.h:#define MOVI_BL2_POS     (MOVI_LAST_BLKPOS - MOVI_BL1_BLKCNT - MOVI_BL2_BLKCNT - MOVI_ENV_BLKCNT)
./include/movi.h:#define MOVI_LAST_BLKPOS (MOVI_TOTAL_BLKCNT - (eFUSE_SIZE / MOVI_BLKSIZE))
./include/movi.h:#define MOVI_BL1_BLKCNT  (SS_SIZE / MOVI_BLKSIZE)
./include/movi.h:#define MOVI_BL2_BLKCNT  (((PART_ZIMAGE_OFFSET - PART_UBOOT_OFFSET) / MOVI_BLKSIZE) - MOVI_ENV_BLKCNT)
./include/movi.h:#define MOVI_ENV_BLKCNT  (CFG_ENV_SIZE / MOVI_BLKSIZE)
./include/movi.h
#ifdef CONFIG_BOOT_MOVINAND
#define MOVI_TOTAL_BLKCNT       *((volatile unsigned int*)(TCM_BASE - 0x4))
#define MOVI_HIGH_CAPACITY      *((volatile unsigned int*)(TCM_BASE - 0x8))
#else
#define MOVI_TOTAL_BLKCNT       7864320 // 7864320 // 3995648 // 1003520 /* static movinand total block count: for writing to movinand when nand boot */
#define MOVI_HIGH_CAPACITY      0
#endif
./include/movi.h:#define MOVI_BLKSIZE           512
./include/movi.h
#if defined(CONFIG_S3C6400) || defined(CONFIG_S3C6410) || defined(CONFIG_S3C6430)
#define TCM_BASE                0x0C004000
#define BL2_BASE                0x57E00000
#elif defined(CONFIG_S3C2450) || defined(CONFIG_S3C2416)
#define TCM_BASE                0x40004000
#define BL2_BASE                0x33E00000
#else
# error TCM_BASE or BL2_BASE is not defined
#endif
./include/movi.h
#if defined(CONFIG_S3C6400)
#define SS_SIZE                 (4 * 1024)
#define eFUSE_SIZE              (2 * 1024)      // 1.5k eFuse, 0.5k reserved
#else
#define SS_SIZE                 (8 * 1024)
#define eFUSE_SIZE              (1 * 1024)      // 0.5k eFuse, 0.5k reserved`
#endif
./include/movi.h:#define PART_ZIMAGE_OFFSET     0x40000
./include/movi.h:#define PART_UBOOT_OFFSET      0x0
./include/configs/smdk6410.h:#define CFG_ENV_SIZE               0x4000  /* Total Size of Environment Sector */

确定BL2_BASE的值

./include/movi.h
#if defined(CONFIG_S3C6400) || defined(CONFIG_S3C6410) || defined(CONFIG_S3C6430)
#define TCM_BASE                0x0C004000
#define BL2_BASE                0x57E00000
#elif defined(CONFIG_S3C2450) || defined(CONFIG_S3C2416)
#define TCM_BASE                0x40004000
#define BL2_BASE                0x33E00000
#else
# error TCM_BASE or BL2_BASE is not defined
#endif

经过计算我们可以看出,这里决定CopyMovitoMem的功能,是TCM_BASE - 0x4,这个地址的寄存器的值(隐含的也告诉我们UBOOT编译出来之后不得大于256k)

《S3C6410_Internal_ROM_Booting.pdf》中看到

0x0C003FFC globalBlockSizeHide Total block count of the MMC device

这个值,具体的为,256K(根据打印可以得出,没找到具体的说明)

MOVI_BL2_BLKCNT 的值为 ( (0x40000-0)/512 - (0x4000/512) ) = 512 -32 (个扇区)

网上大多数资料写到CopyMovitoMem是将256k的数据搬运到内存中,从这个计算上我们可以看出实际这个结论是错的

在这里只搬运了 256k-16k的数据到内存

在MOVI_BL2_POS的定义中做了相应的处理,以保证位置的正确性

编译uboot,生成u-boot.bin

make smdk6410_config
make -j4

三、将修改好的UBOOT烧录到SD卡中

生成的uboot-bin是不是直接可以烧录到SD卡中呢?

有现成的工具IROM_Fusing_Tool.exe(开源的,可以找到源代码),但这个工具烧录的是nb0文件

实际上nb0文件的结构是:256k+8k 这样的一个形式,在Linux系统可以通过下面这个脚本来生成nb0文件

#!/bin/sh
rm -rf temp x* u-boot_256k.bin u-boot_8k.bin u-boot_mmc.nb0
cat u-boot.bin >> temp
cat u-boot.bin >> temp
split -b 256k temp
mv xaa u-boot_256k.bin
split -b 8k u-boot.bin
mv xaa u-boot_8k.bin
cat u-boot_256k.bin > u-boot_mmc.nb0
cat u-boot_8k.bin >> u-boot_mmc.nb0

我想直接将uboot-bin烧录到SD卡中,使用脚本太麻烦,于是我打算自己写一个烧录工具,关键代码如下

需要说明的是,我这里使用的是VC6,WINDOWS XP,VC6对磁盘IO的操作本身支持并不是非常好,有很多扩展的定义,功能都无法使用,这里很多都我手工添加进去的

如果使用的是VC2003及以上版本,会简单一些

DWORD CSC6410BootLoaderWriterDlg::BlockDataRead(char cPart,DWORD dwBlockIndex,DWORD dwReadCount,BYTE* pBuffer)
{
CString devName;
devName.Format("\\\\.\\%c:",cPart);
HANDLE hDevice = CreateFile(devName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
DWORD dwFilePointer = SetFilePointer(hDevice,dwBlockIndex*512, 0,FILE_BEGIN);
DWORD dwBytesRead = 0;
BOOL bRead = ReadFile(hDevice,pBuffer,dwReadCount,&dwBytesRead,NULL);
if(bRead == FALSE)
{
DWORD dwError = GetLastError();
}
CloseHandle(hDevice);
return dwBytesRead;
} 
DWORD CSC6410BootLoaderWriterDlg::BlockDataWrite(ULONG nPhysicalDriveNumber,DWORD dwBlockIndex,BYTE* pData,DWORD dwDataLen)
{
if( (dwDataLen % 512) != 0 ) dwDataLen = ( (dwDataLen / 512) + 1 ) * 512;
CString devName;
devName.Format("\\\\.\\PhysicalDrive%d",nPhysicalDriveNumber);
HANDLE hDevice = CreateFile(devName,GENERIC_WRITE,FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
DWORD dwFilePointer = SetFilePointer(hDevice,dwBlockIndex*512, 0,FILE_BEGIN);
DWORD dwBytesWrite = 0;
BOOL bWrite = WriteFile(hDevice,pData,dwDataLen,&dwBytesWrite,NULL);
if(bWrite == FALSE)
{
DWORD dwError = GetLastError();
}
CloseHandle(hDevice);
return dwBytesWrite;
}

看到区别了吗?这里要非常非常注意!

Write函数中devName的构建形式与Read函数中的不一样,这个问题让我调试了很久,如果在Write函数中使用Read函数的构建形式,则会遇到WriteFile工作不正常的现象

具体为:在操作最后若干个扇区时,bWrite 等于 TRUE,dwBytesWrite却为0(为什么?还没能深究下去)

另外,如果要写入的数据长度不是512的整数倍,一定要进行处理,否则会引起GetLastError是87,参数错误

因为所有的FLASH,最小的扇区时512字节,则最少要以512个字节作为一次操作单位

(文件系统帮我们解决了这些问题,我们现在是对磁盘裸的操作,所以不能按照有文件系统的想法来考虑这个问题)

下面是Write函数构造devName时需要用到的函数

ULONG CSC6410BootLoaderWriterDlg::GetPhysicalDriveNumber(char cPart)
{
typedef struct _DISK_EXTENT {
ULONG         DiskNumber;
LARGE_INTEGER StartingOffset;
LARGE_INTEGER ExtentLength;
} DISK_EXTENT, *PDISK_EXTENT;
typedef struct _VOLUME_DISK_EXTENTS {
ULONG       NumberOfDiskExtents;
DISK_EXTENT Extents[ANYSIZE_ARRAY];
} VOLUME_DISK_EXTENTS, *PVOLUME_DISK_EXTENTS;
#define VOLUMEDISKSIZE (sizeof(VOLUME_DISK_EXTENTS))
#define IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS 5636096
CString devName;
devName.Format("\\\\.\\%c:",cPart);
HANDLE hDevice = CreateFile(devName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
VOLUME_DISK_EXTENTS volumeData;
DWORD dwOut = 0;
DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,NULL, 0, &volumeData, VOLUMEDISKSIZE, &dwOut, NULL );
CloseHandle( hDevice );
return volumeData.Extents[0].DiskNumber;
}

下面这段代码是用来获得,所有插入的U盘(可移动磁盘,注意下面的判断)的盘符

void CSC6410BootLoaderWriterDlg::OnButtonGetdriverinfo()
{
// TODO: Add your control notification handler code here
// 获取所有的盘符
m_cbList.ResetContent();
DWORD dwStrLen = GetLogicalDriveStrings(0,NULL);
char* pString = new char[dwStrLen+1];
memset(pString,0,dwStrLen+1);
GetLogicalDriveStrings(dwStrLen+1,pString);
char *pNow = pString;
while(TRUE)
{
if(pNow >= (pString+dwStrLen-1)) break;
DWORD dwRet = GetDriveType(pNow);
if(dwRet == DRIVE_REMOVABLE)
{
m_cbList.InsertString(0,pNow);
}
pNow += strlen(pNow);
pNow ++;
}
delete[] pString;
m_cbList.SetCurSel(0);
m_cbList.SetFocus();
WriteLog("U盘检查完成");
}

最后是写入代码,在这里我们会要求SD卡首先会被各式化成FAT32各式,以便我们能去读取FTA32文件分配表中MBR的部分

void CSC6410BootLoaderWriterDlg::OnButtonOp()
{
// TODO: Add your control notification handler code here
int nCurSel = m_cbList.GetCurSel();
if(nCurSel == -1)
{
MessageBox("探测磁盘信息");
return;
}
CString strBootFilePath;
m_eBootFilePath.GetWindowText(strBootFilePath);
if(strBootFilePath == "")
{
MessageBox("请先选择需要烧录的引导文件");
return;
}
// 获得驱动器盘符
CString strText;
m_cbList.GetLBText(m_cbList.GetCurSel(),strText);
char cPart = strText[0];
// 获得 PhysicalDriveNumber
ULONG nPhysicalDriveNumber = GetPhysicalDriveNumber(cPart);
// 读取 0 扇区 mbr
BYTE szMbr[512];
memset(szMbr,0,sizeof(szMbr));
DWORD dwReturn = BlockDataRead(cPart,0,sizeof(szMbr),szMbr);
if(dwReturn != sizeof(szMbr))
{
MessageBox("Read MBR error.","错误",MB_OK|MB_ICONERROR);
return;
}
{
CString strLog;
strLog.Format("读取MBR成功");
WriteLog(strLog);
}
// 判断是否为 fat32
// FAT16 为 0x36 0x37 0x38 0x39 0x3a 0x3b
char szFs[6];
szFs[0] = szMbr[0x52];
szFs[1] = szMbr[0x53];
szFs[2] = szMbr[0x54];
szFs[3] = szMbr[0x55];
szFs[4] = szMbr[0x56];
szFs[5] = '\0';
if ( strcmp(szFs,"FAT32") != 0)
{
MessageBox("请将SD卡格式化为FAT32文件系统","错误",MB_OK|MB_ICONINFORMATION);
return;
}
// 获得磁盘的扇区总数
//1CH-1FH   4  本分区隐含扇区数
//20H-23H   4  该盘实际使用扇区数(不包括隐含扇区)
DWORD count_block_hidden = 0;
memcpy(&count_block_hidden,&szMbr[0x1c],sizeof(count_block_hidden));
DWORD count_block = 0;
memcpy(&count_block,&szMbr[0x20],sizeof(count_block));
DWORD count_block_total = count_block_hidden + count_block;
// 另一种方法 获得 扇区总数
// #define IOCTL_DISK_GET_DRIVE_GEOMETRY_EX 458912
//  struct _DISK_GEOMETRY_EX
// {    DISK_GEOMETRY  Geometry;
//  LARGE_INTEGER  DiskSize;
//  UCHAR  Data[1];
// } DiskEX;
// DeviceIoControl(hDevice,IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,NULL,0,(LPVOID)&DiskEX,sizeof(DiskEX),(LPDWORD)&bytesReturned,NULL);
// 读取 u-boot.bin 文件
// 最大 256k
#define MOVI_TOTAL_BLKCNT 0x40000
BYTE buffer_utoot[MOVI_TOTAL_BLKCNT];
memset(buffer_utoot,0,sizeof(buffer_utoot));
CFile cf;
cf.Open(strBootFilePath,CFile::modeRead);
DWORD uboot_size = min(MOVI_TOTAL_BLKCNT,cf.GetLength());
cf.Read(buffer_utoot,uboot_size);
cf.Close();
// 将引导程序 uboot-bin 写入相应的位置
DWORD bl2_begin = count_block_total-1-1-16-512;
DWORD bl1_begin = count_block_total-1-1-16;
dwReturn = BlockDataWrite(nPhysicalDriveNumber,bl1_begin,buffer_utoot,8192);
if(dwReturn != 8192)
{
MessageBox("写入引导8k字节失败");
return;
}
{
CString strLog;
strLog.Format("写入引导8k字节成功");
WriteLog(strLog);
}
dwReturn = BlockDataWrite(nPhysicalDriveNumber,bl2_begin,buffer_utoot,uboot_size);
if(dwReturn < uboot_size)
{
MessageBox("写入引导失败");
return;
}
{
CString strLog;
strLog.Format("引导文件写入完成,磁盘总扇区数:%d",count_block_total);
WriteLog(strLog);
}
}

到这里,我们就可以完全分析清楚,并可以自主的制作一个S3C6410的启动SD卡了,附后为一些参考资料,这里没办法贴附件,问题给我留言吧

后面的内容中,我们将进入UBOOT,对UBOOT进行修改,请关注下一节《如何计算内存大小,并在UBOOT中调整内存大小》

四、参考资料

下载 uboot1.1.6 (支持 movinand)

http://www.rayfile.com/zh-cn/files/7ac4e133-0e58-11de-bd70-0014221b798a/

使uboot支持S3C6410的SD启动

http://blog.csdn.net/zwj0403/article/details/6420245

《S3C6410_Internal_ROM_Booting.pdf》

《s3c6410_rev12.pdf》

《smdk6410_users_manual_rev1.0.pdf》

《K4X51163PC.pdf》

《OK6410开发板LINUX2.6用户手册.pdf》

《OK6410开发板硬件手册2.1.pdf》

S3C6410开发全纪录(一)《还原SD卡启动的真相》相关推荐

  1. 【迅为iMX6Q】开发板 u-boot 2020.04 SD卡 启动

    前言 iMX6Q 支持多种启动方式,如 emmc启动.SD 卡启动等,这里简单的记录一下 SD卡启动的流程 下载u-boot 使用 NXP 官方提供的 uboot-imx,代码地址为: https:/ ...

  2. ARM芯片开发(S5PV210芯片)——SD卡启动

    1.SD卡启动 顾名思义就是启动代码存放在SD卡中,设备从SD卡中启动.用SD卡启动有一些好处:譬如可以在不借用专用烧录工具(类似Jlink)的情况下对SD卡进行刷机,然后刷机后的SD卡插入卡槽,So ...

  3. WINCE6.0+S3C6410基于SD卡启动

    ********************************LoongEmbedded******************************** 作者:LoongEmbedded(kandi ...

  4. OK6410开发板Uboot学习总结----(三)从SD卡启动分析

    前面讲了Uboot启动流程和如何修改调试串口,相信大家对Uboot已经有了初步的了解,今天来进行更深一点的分析.上篇文章 OK6410开发板Uboot学习总结----(二)修改调试打印串口 遗留一个问 ...

  5. 关于s3c6410的SD卡启动

        要研究裸机程序的编写,必须要有一个"全裸"的环境.友善提供的superboot可以提供执行用户自定义程序的能力,但其实这样运行的程序环境还是依赖于superboot的, 那 ...

  6. 【K210开发板】人脸识别+ SD卡断电存储 --实时按键录取人脸信息并识别

    一.人脸识别 1.获取机器码 人脸识别就是在人脸检测的基础上,除了检测人脸的位置外,还可以检测出这个人是谁(需要先对准人按按钮学习). 先到 maixhub 按照说明下载模型, 获得模型smodel, ...

  7. 《旭日X3派开发小技巧》—— 备份与恢复SD卡镜像

    0.前言 很多小伙伴们在开发旭日X3派后,想备份自己魔改后的镜像,官方手册中提供了根文件系统制作的方法,但此种方法对于想备份自己开发魔改过后的镜像非常不方便,在这里给大家提供一个较为简便的方法,可以方 ...

  8. ZYNQ-7000如何生成从Flash和SD卡启动的镜像文件

    将PL与PS部分一起使用,并且通过JTAG下载到板子运行.对于ZYNQ,有多种启动方式,比如从JTAG启动.从QSPI(即Flash)启动,从SD卡启动等.对于从JTAG启动的,我们直接运行程序就OK ...

  9. imx6 android SD卡启动

    工作中需要将imx6的android系统从SD卡启动,所以就分析了MfgTool中的脚本,分析android的分区情况,并尝试自己操作,竟然成功了,记录于此. 参考文档 http://www.kanc ...

最新文章

  1. 85.4% mIOU!NVIDIA:使用多尺度注意力进行语义分割,代码已开源!
  2. 两步聚类算法+Two Step
  3. 阿里工程师告诉你,在性能测试的过程中会遇到哪些问题?
  4. PHP ob_start() 函数介绍
  5. 今天想到了关于 Django 中 view 的组织方式问题
  6. [旧稿]How To Add Simple Install WebPart Menu to the Right Click On a CAB File
  7. 优化器,SGD+Momentum;Adagrad;RMSProp;Adam
  8. 利用C#编写一个GPS高程拟合(二次曲面拟合模型)程序
  9. Oracle开发专题之:OLAP 函数 (rows 2 preceding / unbounded preceding)
  10. 【android学习笔记:Webview与Js交互】网页生成的excel/pdf等文件通过webview下载保存
  11. MySQL8 设置远程访问授权
  12. 幽默故事:1、我喜欢的女神;2、农村淑女(木子家原创)
  13. 由筷子被嘲讽来谈AM中的【价值观】
  14. ThinkPad E460如何进入bios
  15. EditPlus 说明
  16. 银河麒麟连不上网怎么办
  17. python opencv图片拼接、特征点匹配
  18. i html设置为不倾斜,css如何不让字体倾斜?
  19. 个人喜欢的网站http://www.w3school.com.cn
  20. 网络安全入门——从零开始

热门文章

  1. OleDbCommand更新数据的一些问题
  2. ajax contenttype详解_jQuery ajax contentType processData 笔记
  3. Karate-让WebService的测试变的更简单!
  4. switch中的break和return的区别
  5. 使用谷歌插件Allow-Control-Allow-Origin解决跨域问题
  6. 搞定淘宝宝贝图片尺寸标注
  7. 海域月降雨量数据哪里可以下载?
  8. java 进程假死原因_Java进程假死案例集合
  9. 苹果电脑win10双系统如何进入安全模式
  10. 13 facebook 国外面试