第一回:
应深圳友坚科技之邀,这几天要把djyos移植到他们的idea6410上。
现在发布的是si版本,是以单片机模式运行的,S3C6410这样强劲的cpu,运行si版本,就作为高速单片机用了,所有地址都是按照物理地址一一对应映射。cpu的状态也没有区分内核态和用户态。
1、中断引擎最初的部分代码在IRQ态(还没决定是否使用FIQ)。
2、中断引擎的大部分以及用户ISR运行在SVC态。
3、所有其他代码运行在SYS态。
移植碰到的第一个问题就是烧录代码到flash的问题,由于廉价的jtag烧录器不支持arm11,我们不能要求用户必须拥有昂贵的仿真烧录工具才能够在idea6410上使用djyos,这样不利于用户使用。
我的第一个目标,就是弄清楚怎么下载程序的问题,也就是把一个最简单的闪灯程序运行起来,写了几行代码,如下:
ldr r0,=0x7f008820
ldr r1,=0x1111
str r1,[r0]
ldr r0,=0x7f008824
ldr r1,[r0]
bic r2,r1,#3
orr r2,r2,#0xc
bic r3,r1,#0xc
orr r3,r3,#3
nn:
str r2,[r0]
ldr r4,=5000000
delay1:
sub r4,r4,#1
cmp r4,#0
bne delay1
str r3,[r0]
ldr r4,=5000000
delay2:
sub r4,r4,#1
cmp r4,#0
bne delay2
b nn
6410的手册上说,可以从nandflash、onenand、SD卡启动,没有专用的烧录工具的情况下,只有SD卡启动是可以考虑的。手册上看 到,SD卡启动,实际上是先执行片内IROM中的一段程序,该程序从SD卡中读取代码,写到stepping stone中,stepping stone是位于0x0c000000、size为8K的片内内存,代码写入stepping stone后,跳到0x0c000000处继续执行程序。那么,要实现从SD卡启动,就必须弄清楚:
1、8K的代码保存在SD卡的什么位置。
2、代码以什么格式存储。
为弄清楚上述问题,依例google 一番,没找到有用的资料,上三星的网站,6410的资料没有公开,申请了一下,第二天得到了批准,也没有关于怎样从SD卡启动的资料。找三星代理,似乎不太爱搭理我,也是,我势单力薄一个人,他们怎看得上眼。难道就没有办法了?
山穷水尽疑无路,柳暗花明又一村,2450不是也可以从SD卡启动吗?找找2450的资料看有没有,谢天谢地,从网上找到一篇文档:
896554S3C2450_IROM_ApplicationNote_Rev003.pdf
我如获至宝,细细读之,虽然写得不太细,比如代码校验算法等都没有讲,总是可以试一下了。
依葫芦画瓢,把编译好的代码写入到最后芯片末尾偏移-9216字节处,插入SD卡座,把开关拨到SD0卡启动的位置,上电,哈哈,成功了,几个蓝色的LED欢快地闪烁起来。试了两个SD卡,16M的可以,2G的不行,不知何故,暂且放一边吧。
首战告捷,该歇歇了,待续。
第二回:
上篇说道,用16M的SD卡启动可以,但用2G的卡却不行,反复试过,实在不知道怎么回事,也没有IROM中的加载程序的进一步资料,问题也就无从查起了,暂且先放一边,把后续的移植工作做完再说吧。
移植操作系统,跟开发裸奔程序是不一样的,裸奔程序可以从main开始写程序,在执行main之前,编译器产生了大量的代码用于初始化cpu、内存清零、初始化堆和栈、直到建立main的执行环境。而操作系统往往有自己的运行环境要求,C编译器完成的这些环境往往不能符合要求,需要自己写初始化文件,即initcpu.s文件。
初始化文件主要完成以下工作:
1、从复位地址跳转到启动地址。
2、设置cpu为特权模式,禁止看门狗和中断,禁止cache。
3、设置时钟,有些cpu的时钟设置很复杂的,尤其是高速cpu,因为涉及到内核和外设匹配的问题,设置起来比较麻烦。
4、设置内存总线,设置内存访问速度,要跟上一步的时钟设计配合,使cpu能正确读写内存和内存映射的外设。
5、配置cache和mmu,然后使能cache和mmu。
6、初始化栈,跳转到C代码。
根据拿来主义的原则,写cpu初始化代码千万不能自己从0开始写,而是要找一个现成的来参考,因为各个系统的cpu初始化工作是大同小异的。而且,许多 cpu厂商都会出example,会带一个文件名类似startup.s的文件,参考该文件来写即可。但是,三星不知从何年何月开始不公开其cpu的文档了,甚至连datasheet都要申请才给。当我千辛万苦找到三星提供的"6410_Test_Rev01"源代码包时,心都凉了,该代码包中虽然有 start.s文件,但文件中只有几条指令,初始化过程都在"__rt_entry"函数中,而该函数在库中。对芯片应用资料的保密工作做得如此周密,不知三星所谋何事。
没办法,继续找,实在不行再自己一行行写。好在天无绝人之路,终于在友坚提供的wince的eboot代码中,找到了eboot的start.s文件,打开一看,果然是一个详细的启动文件,心中不禁狂喜!
接下来的工作,就是对照datasheet,看懂这个start.s,然后改造成适合djyos的。别看说的轻巧,这里面工作量还是很大的,6410的 datasheet有1300多页,光时钟和总线配置相关的部分就有100多页,E文的,晕死。而且第一次用arm11,其mmu和cache配置和 arm9有多大差别,还不可知。今天先写到这里,下回分解吧。
第三回:
接续上回,开始啃start.s,跟所有的启动文件一样,开始部分是关闭cache、禁止中断等,没什么问题。这里稍稍解释一下为什么要做这些工作,禁止中断大家应该没什么异议,关键是为什么一定要禁止 cache,原来,我们不知道程序为什么要重新启动,也不知道重新启动前cpu和cache处于什么状态,cache可能包含错误的信息,cpu可能会从中取得错误的指令,从而不能正常启动系统。eboot的start.s遗漏了一个很重要的过程,就是要重新把cpu设置成svc状态,因为就像我们不了解重启动前cache状态一样,cpu的状态也是未知的,须加上这几句确保cpu处于svc态:
mrs r0,cpsr @取CPSR
bic r0,r0,#MODEMASK @清模式位
orr r1,r0,#SVCMODE|NOINT @设置为管理态,并禁止中断
msr cpsr_cxsf,r1 @切换到管理态,可防止意外返回0地址时出错.
实际上,这几句仍然不够保险,因为如果程序是从user态直接跳转到0地址的话,mrs和msr指令是无效的。保险的做法是用swi指令强制修改,既然是个简易的bootloader程序,暂且偷懒一下。
依惯例,接下来是各种时钟初始化,S3C6410的时钟结构比较复杂,共有3个pll要设置,而且分频控制也较为复杂,虽然start.s有得抄,但还是要自己弄清楚,因为接下来的编程用得上。6410的时钟部分之所以如此复杂,完全是为了适应其内部丰富的外设,不同的外设需要不同的时钟。设置时钟,不外乎就是定义几个常用的主频,然后分别为这些主频定义锁相环(pll)的分频系数,再把这些分频系数填充到相应的寄存器中去,细节就不再赘述了,看代码吧。
初始化完时钟后,就轮到初始化内存总线了。依据用什么初始化什么的原则,这里只初始化了srom0和dram两个区域,这部分代码是用C语言实现的,参见memcfg.c文件,代码比较简单,不过是按照datasheet的要求,依次设置寄存器而已。
接下来,你一定会想到,该初始化mmu了,初始化mmu本来是一项复杂的工作,但si版本是为单片机准备的,即使6410再强劲,也只能委屈一下,当高速单片机了。mmu在这里并没有用来做地址变换,而是把4G内存空间全部映射到其物理地址上了。这种映射效果跟禁止mmu是一样的,但是arm的mmu和cache是绑定在一起的,禁止mmu的同时也就禁止了cache,故只能打开mmu。页表的地址在0x50000000,占用16Kbytes,故应用程序的起始地址是0x50004000。
又可以偷懒了,6410的核是arm11jzf-s的,2440的核是arm920T的,arm11jzf-s的mmu功能比arm920t的强很多,但都是arm公司的,做最简单映射的话,能否兼容呢?懒得读arm11的手册了,先试一下把2440版本的mmu初始化部分直接copy看行不行。事实证明是可行的,但我在这里绕了一个大大的圈子,浪费了许多时间。加入初始化mmu部分代码后,试了一下,发现灯不闪了,而把该段程序放到初始化页表的代码之前,则可以。仔细检查,发现是因为dram没有初始化,加上dram初始化后,先把闪灯程序放在dram初始化后面,发现可以了。这时候,不幸发生了,移动闪灯程序时,只copy了一半的代码,当然不闪了,可我没仔细检查代码,就怀疑是2440的mmu初始化代码不能用于6410的造成的(偷懒了,心虚),于是找来arm11jzf-s的手册猛啃,几百页的英文资料啊,费了好几天功夫,最后证明了一件事:原来的mmu初始化是正确的!!
到这里,基本硬件的初始化就算完成了,接下来要搞norflash和uart的driver了。待续......
cpu的初始化已经完成,下一步的工作便是初始化uart,使之能够跟PC连接上。
初始化6410的uart,有两个时钟必须区分清楚,即uartclk和baudclk,前者由系统 控制寄存器CLK_SRC和CLK_DIV2控制,手册并没有说明这个时钟的用途,我猜测是用于uart模块本身运行的;另一个时钟是baudclk,用来控制baud,产生串行移位时钟的,在uart模块的控制寄存器UCON中设置 ,然后用UBRDIV和UDIVSLOT0两个寄存器设置baud。uart的工作方式为:
baud=115200
接收和发送fifo:开
中断:关闭,以查询方式收发。
接下来是norflash的驱动程序,也就是编写擦除和写入程序,根据am29lv160db的手册写就是了,没什么好说的。除了一些低级错误外,测试也几乎一次成功。
至此,该轮到xmodem协议收发程序了,xmodem是一个简单的串口收发文件 的协议 ,它把文件分成128字节的一个个小包传送,最后一包数据 如 果不满128字节,则以0x1a填充。该协议是有缺陷的,用来传送文本文件没问题,因为文本文件不会出现0x1a,但传送2进制文件的话,就不行了,因为 接收方无法分辨最后的0x1a是文件本身的内容,还是填充物。不过没关系,作为代码,最后多一些垃圾并不碍事,无非就是多占几个字节而已。
xmodem本来是有差错控制的,通过逐帧应答和校验和(或者CRC)来确保传输正确,不过这里偷了下懒,所有这些都省略了,传错了就再传一次。
至此,移植 到S3C6410的第一步,sdbootloader就制作完成了,接下来就是移植整个操作系统 了,sdbootloader的代码在这里下载:
http://www.djyos.com/download/sdboot6410.zip
sdbootloader说明:
1、用winhex工具把boot_rom.bin写入SD卡,地址:SD卡末地址-9216。
2、把开发板的拨码开关拨到从SD卡启动,上电即可。
2、打开超级终端,用xmodem协议下载程序,保存到dram中。
3、把下载的代码写入到norflash。
4、把开发板的拨码开关拨到从norflash启动,复位或重新上电即可。
5、省去了xmodem帧格式检查、校验和检查。
但凡开发操作系统 ,无论是PC、服务器 ,还是嵌入式操作系统,第一个要解决 的就是启动文件 的存储问题。无论什么cpu,上电或者复位后,都会从指定的存储位置读取第一条指令予以执行,我们把这个地址成为启动向量,有些cpu可以通过跳线设置 不同的启动向量地址。我们熟悉的PC,就是把bios烧录到启动向量处,再由bios逐步引导启动更高级的操作系统,比如windows 、linux 等。
在嵌入式行业,以linux为例,linux映像可以通过网口或者其他通信口下载,但你拿到裸机的时候,必须先想办法把uboot或者vivi或者其他 bootloader烧录到flash中。djyos也不例外,不同的是,现在发布的si版本的djyos,是没有单独的bootloader的,而是把 bootloader的角色集成在可执行影像中了,所以,需要像烧录uboot那样,烧录整个djyos影像。
在s3c6410之前,djyos发布了arm7(44b0)和arm9(2410和2440)两个版本,得益于廉价的arm7和arm9仿真烧录器,比 如hjtag,arm这两个版本是通过hjtag或者jlink或其他工具直接烧录到norflash上的。但s3c6410是arm11的核,这些廉价 工具不支持arm11,支持arm11的烧录工具都在数千元以上,这样势必影响大家使用djyos。要让6410执行djyos,必须解决在没有专用烧录 工具的情况下,把djyos烧录到启动设备上的问题。
6410一共支持4种启动设备:norflash、nandflash、SD卡、modem,其中norflash和nandflash必须使用专用烧录工具,modem则需要了解其通信协议 ,编写相应的PC端应用 程 序,相比之下,SD卡最为简单,可以用普通的读卡器+winhex工具就可以完成写入。但有一个限制,就是6410启动时只从SD卡中读取8K代码到内部 ram中(6410手册第二章说4K,第八章说8K,实测是8K)。读入代码后,程序就跳转到内部ram中执行,这8K程序,只能做一个简易 bootloader,用这个bootloader去加载整个操作系统,这时候,就有两种选择:
1、把程序写到SD卡的其他地方, 但这种方法不现实。SD卡是用nandflash做的, 必然要做ECC校验,那么把代码写入SD卡时,要么用文件系统,但8K代码根本做不了文件系统;要么在PC端用支持ECC的专用写卡工具,可惜没有找到。
2、用串口下载,这是传统方法,PC端的工具很多,最通用的是超级终端了。
我们选择了超级终端,xmodem协议下载。
代码比较简单,说明见“s3c6410移植日志 之x”系列,下载源代码:http://www.djyos.com/download/sdboot6410.zip