Chinaunix首页 | 论坛 | 博客
  • 博客访问: 468144
  • 博文数量: 62
  • 博客积分: 1742
  • 博客等级: 中尉
  • 技术积分: 859
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-06 00:13
个人简介

这是一句很长很长而且又很啰嗦并且很无聊的废话...

文章分类

全部博文(62)

文章存档

2013年(1)

2012年(13)

2011年(48)

分类: LINUX

2011-09-20 18:37:30

  
  这是我模仿U-BOOT做的一个BootLoader,也好加深一下自己对Arm,Bootloader,Linux的理解.之所以起这个名字,是因为本人比较喜欢一个韩国女星朴桑多拉(Sandara Park),呵呵.好了,废话不多说,进入主题.

  下面将对每一个模块进行粗略地说明,代码下载放在文章的最后,有不过之处,还请高手多多指出.由于要做毕业项目的原因,所以没太多时间考虑其他平台,只在FS2410上跑.如果要移植到其他的平台上,需要修改初始化代码和SOC上的一些设备寄存器操作.东西说多不多说少不少,就从第一条指令说起吧.
Start.s
  1. 一上电,马上进入SVC模式,并且关中断(虽然默认就是,但就是蛋疼).
  2. 关闭看门狗,不然隔个几秒就会触发重置异常,使PC又跑到第一条指令开始执行.
  3. 开启ICache,能够加快指令的执行速度,因为CPU访问内核缓存比访问主存更快(这个又跟ARM的哈佛结构有关,指令和数据分开两条总线,各有一个内核缓存,所以从缓存中取指令或者取数据不会发生重叠,使执行速度更快).
  4. 设置时钟,分频.我这里是设为202.80Mhz,分频为1:2:4.(因为不够细心,在时钟的设置上引发了其他的问题,原因是我把分频设为1:1:1,这就导致了,在AHB和APB总线上的设备不能够得到正确的时钟,无法正常工作,最少SDRAM,串口都出问题了.后来在手册上找到的最大时钟值.经过这个问题总结下:器件都有最大时钟值,需要根据其DataSheet正确设置.)
  5. 设置内存控制器,2410上为bank3,128M.([26:0]是128M的地址,[31:27]是片选地址)
  6. 设置Nand Flash.(设置GPIO,然后选中,发送重置命令,然后根据其手册上的值和HCLK去设置ALE,CLE,WE...,取消片选.)
  7. 闪下灯...(在这种情况下,只能一开LED灯调试了...)
  8. 重定位代码,首先需要判断一下是从Nand启动还Nor启动,如果是从Nand启动的话,内核上电时会自动从Nand的0地址开始复制4K数据到SRAM开始执行.所以我们需要在前4k的代码中完成重定位操作,具体的汇编操作为,选中Nand,发送行列地址,开始读一页,写SDRAM,判断行地址否到512字节了(因为FS2410上的Nand是按页存储的,每页512字节,如果到读末端了,需要从新发送行列地址,读取下一页.),印象比较深的是,模操作,用ARM指令直接写模操作还是有点难度,所以直接在C文件中写个模操作,然后反汇编,复制过来用.如果是从Nor启动,则直接把Nor的数据复制到SDRAM执行即可.那在代码中如何判断是Nand还是Nor启动呢?用老师教的那招,写0地址,再读出来,如果读出来的值跟写进去的一致,则说明是从Nand启动,否者就是Nor了(因为Nor是只读的).
  9. 设置IRQ和SVC下的栈,因为即将进入C函数了.
  10. 进入C函数(上面的一些操作中都用到了连接脚本的全局符号,用来计算文件大小,连接地址等操作)
Main.c
  1. 第一件事就是,根据PCLK设置串口(串口0,115200,单纯的轮询方式).
  2. 打印一下个性字符标签
  3. 再初始化一下Nand和SDRAM,为什么,代码上是说明.
  4. 初始化中断控制器.(屏蔽所有中断,包括内部中断和外部中断),清零中断号寄存器.
  5. 初始化LCD控制器.我这里用的这款屏是东华的3.5寸屏,根据其手册设置像素时钟,TFT,像素位宽,帧时钟,帧前肩时钟,帧后肩时间,LCD高,行时钟,行前肩时钟,行后肩时钟,LCD宽,像素数据的高低端,帧数据所在的SDRAM起始地址,大小.最后使能LCD控制器帧数据传送,扫点,显示在LCD屏上.
  6. 调用初始化段里的函数.(这个是模仿Linux内核的)
  7. 初始化可调制脉冲时钟(根据PCLK设置8位预分频,选择2,4,6,8其一的分频,设置递减值,开启中断屏蔽),我这里是设置10毫秒触发一次中断.
  8. 打印一个RGB的LOGO.(就按照前面设置的缓冲区格式,把预先BMP图片去头信息,取纯GRB,G跟B得调换一下...,保存到二进制文件,烧到Flash中,然后取出写到帧缓冲即可.
  9. 从Nand载入环境变量到特定的数据段变量中(这个也是模仿内核的),这个数据段是在连接脚本中自定义的.然后其他C文件就以外部引用变量方式读写环境变量,为了识别出之前已经有把初始值或者修改过的环境变量写入到Nnad中,就在环境变量中加个魔数,读取时,判断魔数是否正确,如不正确则使用写定的初始值,而Nand的地址则根据内核的参数分区而定,我这里是0x4000.
  10. 根据环境变量读取自动执行命令参数,这个实现方式是.在前面的PWM初始化时传入一个中断回调函数,判断环境变量中有无自动执行命令,有则再读取执行延时值,根据PWM的定时值,计算出延时值的结果,到时就执行自动执行命令.
  11. 开启中断,设置CPSR的I位为0.
  12. 开始进入命令轮询(分析串口读入的字符串,以0结尾).
Cmd.h
  到了这里,就已经是开始命令轮询了,但是轮询也会有个规则,规则来自定义.命令结构的定义是模仿U-BOOT的,就是所有的命令结构都放到自定义段中,然后在连接脚本中的自定义段头尾加个标号,轮询时,就从命令段头部开始按命令结构长度开始循环,比较字符串,直到指针到命令段尾部.如果找到了符合的字符串,则执行命令结构体中的命令函数指针.接下将说明每一个功能命令.有3个通用命令是放在Simp_cmd.c中,其他都放在Cmds目录中.
Simp_cmd.c
  Reset:
    直接内嵌汇编,把PC设为0...
  Help:
    跟轮询命令一样,循环命令段,打印命令结构体的帮助字符串.
  Mem:
    这个就没说什么好说的,就是把每个SDRAM的字节根据ASCII表转字符,里有现成例子,拿过来用就是了.
Cmds/Boot_cmd.c
  Boot:
    首先到Nnad读出uImage,解析前64字节,这64字节是mkimage根据在zImage的连接地址,载入地址,启动地址,镜像大小,压缩方式生成的一个结构体.我们需要检查结构头的魔数,再检查信息头的CRC校验,接着根据启动地址搬运内核镜像,如果uImage的地址减64字节的地址等于内核启动地址,则不需搬运.接着就是构建内核启动所需的Tag参数.这里我就构建了几个必要的,Core,内存大小,命令字符串.有意思的是这个Tag结构体,它前面的两个成员是固定长度的,类型标识和结构大小,后面的都放在一个联合体,这样可以实现一个不定长结构,使而节约了的空间.为了告诉内核Tag的结束,需要把最后一个Tag需要固定设置类型标识和大小都为0.当内核解析这些Tag时,会根据固定成员参数大小来获得后面正确的成员,然后根据这个大小又跳到下一个Tag,直到Tag的固定成员的值都为0.最后把这个这些Tag的起始地址和体系固定数值当做参数,传给以内核启动地址的函数指针,就完成引导内核了,接下来的解压Image,构建环境等操作,都zImage前面一些代码完成的事情了.
Cmds/Env_cmd.c
  Env_p:
    这个也没什么好说的,就是把全局环境变量的成员打印一下.
  Env_w:
    调用Nnad的操作函数,把内存中环境变量写入到Nand中.
  Env_s:
    解析传进来的参数,写到对应的环境变量中去.
Cmds/Nand_cmd.c
  nand_r:
    Nand的读,跟汇编写的读一样,选中,分4次发送行列地址,开始读(一直读取数据寄存器),当列地址到512时(因为Nand的缓存寄存器就是存一页的),需要发送下一页的地址,读取完毕,取消片选.
  nand_w:
    Nand的写,跟读操作一样.选中,发地址,把数据写入到数据寄存器中,取消片选.
  nand_e:
    擦除操作,也是选中,发送地址,发送擦除大小,需要注意的是,Nand的擦除操作是以16K对齐的.
Cmds/Sd_cmd.c
    SD卡的操作在另外一篇文章做单独说明.
Cmds/Tftp_cmd.c
  tftp:
    我这里选择的是IO方式操作,就是把需要操作的网卡寄存器的内部地址写到地址寄存器,然后读写数据寄存器,但有一些寄存器不用这样操作,例如命令寄存器,数据寄存器等.首先初始化CS8900网卡,根据DataSheet说明,把控制寄存器的第6为置为1,使其执行重置操作,等上一定的时钟周期,然后设置接收控制寄存器(只接收校验正确包),设置发送接收缓冲配置寄存器(均不使用中断方式),使能手动发送接收数据模式.接着就是设置网卡地址了(有一些网卡是不允许手动设置MAC的),往以PP_IA为起始地址的寄存器写入6个16位的网卡地址即可.网卡初始化完毕后,就可以工作了.首先是构建一个ARP(地址解析协议,就是ETH头加ARP头),往ARP头写入我们的MAC和IP,和目标主机的IP.然后往网卡的命令寄存器写入写指令,再往数据包长度寄存器写入长度,接着就可以往网卡数据寄存器写入要发送的数据了,发送完后,需要检查发送状态寄存器,这里我也是模仿U-BOOT,在循环检查期间,加入一个超时检查(利用PWM的全局计数变量),如果循环检查一直都没发送成功并且当前已经超过给定的时间,则打印错误并且直接返回.当ARP发送成功后,就可以进入轮询接收了,CS8900的读操作跟写很相似.首先,需要检查网卡的读事件寄存器是否发生接收操作,如果网卡已经收到正确的包,我们需要检查读状态寄存器,然后再从接收长度寄存器获得数据包的长度.在这里需要检查一下数据包的长度,因为单个数据包是不能超过1518字节,这个TCP/IP协议上有说.检查状态和长度都没问题后就可以进行读操作了,就是一直读取数据寄存器,最后还需要检查一下数据包的对齐,如果长度是奇数,则需要从数据寄存器多读一个数据(CS8900的寄存器都是16位的).如果能得到对方主机的RARP,就解析ARP数据包,检查关键点是ETH头的传输类型是否为ARP协议,如果是,则还需检查ARP头的操作码是否正确(因为一般都是PC跟板子直接用网线连接,所有我也没有过多的判断RARP里的目标IP是不是我们板子上的IP,因为如果用路由连的话,会收的局域网所有ARP,不加判断的话,收到的可能就不是我们想要的RARP了.),通ARP得到目标主机的MAC后,就可以构建TFTP请求了,TFTP就是FTP协议的精简版,它没有FTP这么多操作,例如登陆,目录操作,密码校验等,但是对于板子上的单纯的下文件,就足够.TFTP是通过UDP传输的.所以我们需要构建ETH,IP,UDP,TFTP这几个头,协议头里面的东西有点多,详细的看,我们主要关心的是TFTP头.请求头包含我们想下载的文件名字符串,最大超时值字符串,最大块值字符串,操作码,然后把构建好的TFTP头跟ETH,IP,UDP封在一起,发送.定时轮询接收,收到回应包后,解析TFTP头的操作码,如果,目标服务器就绪的话,就把返回的TFTP头里的最大超时值,最大块值作为接下来的接收操基准(有点像TCP的握手...).最后我们就可以开接收服务器上的文件了.接收操作的开始我们需要发送第0块数据请求给服务器,当正确地收到服务器的第0块数据后,又发第1块数据的请求,以此类推,当收到的数据里的TFTP数据块长度不等于请求时的协商长度值,就表示已经接收完毕,接收完毕后发送一个完毕操作码的TFTP给服务器,告诉他已经接受完毕就OK了.
Cmds/Ping_cmd.c
  Ping:
    ping操作跟tftp的前半部操作是一样的,就是发送ARP出去,等待接收,如果超时或收不到就代表ping不通.
Irq_head.s
  这一段汇编程序就是保存现场,获取中断号(直接拷Linux内核的),恢复现场.这里值得说一下的是,因为现场寄存器都是保存在SVC的栈中,但进来时是IRQ模式下的,中断发生前的CPSR保存在IRQ的SPSR中,所以需要的一个过渡区把R0(因为需要用到R0把SPSR读出来)和SPSR临时地保存在IRQ栈中,然后再用R0保存这个临时的IRQ栈地址,在切换到SVC模式前先恢复IRQ栈,切到SVC模式后,马上保存现场(保存所有寄存器),也把刚刚放在IRQ模式下栈中的R0值和SPSR值放到SVC栈中,保存好现场后,就可以使用这些寄存器了,使用内核的那段宏获取中断控制寄存器中的中断号,然后就调用中断处理函数.当中断处理函数返回时,栈也一定恢复到调用中断处理函数前(C调用协定),这时就可以把保存在栈中的现场环境恢复.
Irq.c
  处理中断的方法,大概也是模仿Linxu内核的,就是定义一个全局数组,根据中断发生时传进来的中断号作为下标,取出元素,该元素是一个结构体,有处理指针,和指向下一个中断元素结构体的链表指针.这么做是为了共享中断,也就是说,当某一号中断发生时,凡是注册了这个中断号的中断例程都会通过链表轮询方式依次调用.做完这个中断功能后,有感而生一句:有了中断,生活好滋味!
另外也贴一下自己对Arm体系的流水和中断的理解:
 
代码下载:  Dara_Boot.rar        
阅读(1914) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~