注:本文是对朱老师ARM裸机全集课程的备忘引导性笔记,主要是为了能够在学完后快速回忆起相关内容。本文主要记录了一些关键易忘性知识点并包含少量理解性内容,遵循尽量精简的原则,以尽量少的篇幅概括整个课程的知识点,便于后期能够快速定位知识点。故本文不包含具体的命令及函数使用方法等,同时不要太纠结于字面表达,这些只是为了能够快速回忆起相关知识点,具体的准确描述以及用法等需参考具体的文章。
(未完待续)
ARM版本
1991年,ARM推出第一款嵌入式RISC处理器,即ARM6
1993年,发布ARM7
1997年,发布ARM9TDMI
1999年,发布ARM9E
2001年,发布ARMv6架构
2002年,发布ARM11微架构
2004年,发布ARMv7架构的Cortex系列处理器,同时推出Cortex-M3
2005年,发布Cortex-A8处理器
2007年,发布Cortex-M1和Cortex-A9
2009年,实现Cortex-A9、发布Cortex-M0
2010年,推出Cortex-M4、成立Linaro,推出Cortex-A15 MPcore高性能处理器
2011年,推出Cortex-A7,ARMv8发布
2012年,开始64位处理器进程
ARM7 44B0
ARM9 2440 2410 2416
ARM11 6410
A8 S5PV210 S5PC100
A9 4412 4418
ARM内核版本号 ARMv7
ARM SoC版本号 Cortex-A8
芯片型号 S5PV210
二级流水线
执行-解码-取值(PC)
寄存器(待完善)
ARM汇编中LR(R14)寄存器的作用
lr(r14)的作用问题,这个lr一般来说有两个作用:
1.当使用bl或者blx跳转到子过程的时候,r14保存了返回地址,可以在调用过程结尾恢复。
2.异常中断发生时,这个异常模式特定的物理R14被设置成该异常模式将要返回的地址。
另外注意pc,在调试的时候显示的是当前指令地址,而用mov lr,pc的时候lr保存的是此指令向后数两条指令的地址,大家可以试一下用mov pc,pc,结果得到的是跳转两条指令,这个原因是由于arm的流水线造成的,预取两条指令的结果.
三星定义的BL2和我们使用时不同的问题
ARM版本系列及家族成员梳理
可见这96KB大小的内部SRAM空间并不是全部都可以供用户随意使用的,除了16字节的头信息外,External Copy Function(用来从外部设备拷贝数据的函数指针)、Signature、Global Variable、Exception Vector Table(异常向量表)都是预先被占用了的。(具体细节待确认)
分析启动过程可知;210启动后先执行内部iROM中的BL0,BL0执行完后会根据OMpin的配置选择一个外部设备来启动(有很多,我们实际使用的有2个:usb启动和SD卡启动)。在usb启动时内部BL0读取到BL1后不做校验,直接从BL1的实质内部0xd0020010开始执行,因此usb启动的景象led.bin不需要头信息,因此我们从usb启动时直接将镜像下载到0xd0020010去执行即可,不管头信息了;从SD启动时,BL0会首先读取sd卡得到完整的镜像(完整指的是led.bin和16字节的头),然后BL0会自己根据你的实际镜像(指led.bin)来计算一个校验和checksum,然后和你完整镜像的头部中的checksum来比对。如果对应则执行BL1,如果不对应则启动失败(会转入执行2st启动,即SD2启动。如果这里已经是2st启动了,这里校验通不过就死定了)。
BL1是可变大小的,它的大小就是头信息中的BL1 size所指示的大小
可见从SD卡/iNAND启动时需要校验头部的16字节的头信息,而从USB启动时不需要校验16字节头信息
思考:启动时之所以要知道我们的启动介质类型,是因为iROM中需要根据所选择的启动介质调用不同的Device Copy Function函数,以从外部存储设备中得到BLE部分,可见同一种存储设备他们的对外接口是差不多的;这里需要BL1或者BL2来负责初始化DDR的原因就是没法做到一套代码初始化所有类型的DDR,所以需要我们自己根据DDR类型进行初始化。
iROM_Application_Note这份文档中有一些地方容易引起误会
(1)其中有下面一段话
As BL1 doesn’t need header information through UART/USB boot mode, BL1’s code base address is
0xd002_0000. In other cases except UART/USB boot mode, BL1 should have header information and It’s code
base address is 0xd0020010. (refer chapter 2.9)由于BL1通过UART/USB引导模式不需要头信息,所以BL1的代码基地址为
0 xd002_0000。在其他情况下,除了UART/USB启动模式,BL1应该有头信息和它的代码
基址是0xd0020010。(参见章节2.9)
并配有下面这张图
我的理解是代码本身前面加16字节空位的话这里的地址应该是x0d0020000,但不加的话这里的地址应该是0xd0020010,应跳过前16字节的头信息。
开发板配置信息
CPU:三星S5PV210
内存:512M DDR2 SDRAM
Flash:4GB iNand
LCD:7寸 分辨率1024*600
触摸屏:电容触摸屏
软开关按键
开发板板载了一个稳压器件MP1482,MP1482芯片有一个EN(Enable)引脚,这个引脚可以让稳压芯片输出或关闭输出。EN为高电平时有输出电压,EN引脚为低电平时稳压芯片无输出。
两个因素可以影响EN引脚的电平:第一个是POWER按键(SW1),POWER按键按下时EN为高电平,POWER按键弹起时EN为低电平;第二个是POWER_LOCK(EINT0)引脚,这个引脚为POWER_LOCK模式下高电平,则EN为高;若这个引脚为EINT0模式或者为POWER_LOCK模式但输出为低电平,则EN为低。
图中还有EINT1引脚,这个引脚的作用是用来做中断,提供给CPU用来唤醒的。
开发板供电置锁原理和分析
(1)软开关在设计时有一个置锁电路,用EINT0(也就是GPH0_2)引脚来控制的。
(2)EINT0这个引脚是有复用设计(两个完全不相干的功能挤在同一个引脚上,同时我们只能让这个引脚用于其中一种功能,这就叫复用)的,一个是GPIO(也就是GPH0_2引脚)、一个是PS_HOLD_CONTROL。(注意:EINT0功能算是GPIO下的一个子功能)
(3)PS_HOLD在Section2.4 Power Management章节下的4.10.5.8节下。
(4)PS_HOLD_CONTROL寄存器(0xE010E81C),共有3个位有用。
bit0, 0表示这个引脚为GPIO功能,1表示这个引脚为PS_HOLD功能
bit9,0表示这个引脚方向为输入,1表示这个引脚方向为输出
bit8,0表示这个引脚输出为低电平,1表示输出为高电平。
分析:我们要使用软启动置锁,则需要将bit0、8、9都置为1即可。// 第0步:开发板置锁 // 写法1 //ldr r0, =0xE010E81C //ldr r1, =0x301 //str r1, [r0] // 写法2 //ldr r0, =0xE010E81C //ldr r1, [r0] //orr r1, r1, #0x300 //orr r1, r1, #0x01 //str r1, [r0] // 写法3 ldr r0, =0xE010E81C ldr r1, [r0] ldr r2, =0x301 orr r1, r1, r2 str r1, [r0]
PS_HOLD和普通GPIO有什么差别目前不知道
SD卡刷机
(1)设置拨码开关使开发版从SD/iNAND启动
(2)破坏iNand中的bootloader以从SD2启动。
- Android平台
(1)busybox dd if=/dev/zero of=/dev/block/mmcblk0 bs=512 seek=1 count=1 conv=sync
解释:将if所代表的文件(全0设备)拷贝到of所代表的文件(iNand设备文件)中,bs设置读写块的大小为512bytes,seek从输出文件开头跳过1个块后再开始复制(原因见下图),count仅拷贝1个块
(2)sync
- Linux平台
(1)busybox dd if=/dev/zero of=/dev/mmcblk0 bs=512 seek=1 count=1 conv=sync
(2)syncbootloader下
(1)mw 0x30000000 0x0 0x100000
解释:将数据写入0x30000000,数据内容为长度0x100000大小的0x0数据
(2)movi write u-boot 0x30000000
解释:把0x30000000起始的位置处(DDR)的数据写入到iNand中的u-boot(这里的u-boot只是用来匹配的,不是分区)对应的地址(这里对BL1和BL2都进行了写入操作)破坏成功后再次开机串口会显示SD checksum Error
(3)制作启动SD卡(其实就是烧录uboot到SD卡中)
- windows平台
使用A盘\tools\x210_Fusing_Tool.rar解压后的x210_Fusing_Tool.exe
- linux平台
这里使用了一个三星提供的脚本来进行SD卡的烧录,该脚本在uboot源码的sd_fusing目录下,使用时要进行少许修改。(如何解读并修改待补充)
(1)将sd卡通过读卡器插入电脑(笔记本上自带的SD卡卡槽好像在vmware中识别不到)并连接到虚拟机ubuntu中,可通过ls /dev/sd*来对比查看是否正确接入了SD卡
(2)如果SD卡设备被识别为sdb,在脚本所在目录下执行./xxx.sh /dev/sdb
(3)最好在使用该脚本前进去查看一下一些配置参数是否正确,另外最好先make clean一下重新make再烧录
(4)fastboot
在uboot和windows下有各自的fastboot
- uboot
(1)fastboot是uboot中用来快速下载镜像并写入到存储设备(我们的uboot中是iNand)的一个命令
(2)有时需要先执行fdisk -c 0进行分区(0表示外部对iNand进行分区,1表示对SD卡进行分区,参考:x210:iNand分区情况)后才可以执行fastboot
- windows
(1)fastboot是一个windows上的软件(A盘\tools\fastboot.rar解压后的fastboot.exe,该软件需在cmd下打开)
(2)fastboot是使用USB线进行数据传输的,所以需要在windows上安装相应的驱动(驱动位置:A盘\tools\USB驱动\x210_android_driver)
(3)fastboot常用命令
fastboot devices 命令用来查看当前连接的设备。(我们开发板看到的是:SMDKC110-01)
fastboot flash xxx 镜像文件 命令用来烧录镜像文件到xxx分区
fastboot reboot 命令用来重启开发板
- 使用方法
(1)首先确保驱动安装好并连接了USB线
(2)uboot下执行fastboot命令,此时windows下会检测到设备
(3)windows下打开fastboot应用程序输入对应的命令
(5)写入镜像文件
- 烧录linux+QT
fastboot flash bootloader linuxQT/uboot_inand.bin 烧uboot
fastboot flash kernel linuxQT/zImage-qt 烧linux kernel
fastboot flash system linuxQT/rootfs_qt4.ext3 烧rootfs
uboot的参数不用特意设置(因为我们刷了专为linux+QT定制的uboot,这个uboot中估计已经预制好了所有的启动参数)
- 烧录android2.3
fastboot flash bootloader android2.3/uboot.bin 烧uboot
fastboot flash kernel android2.3/zImage 烧linux kernel
fastboot flash system android2.3/x210.img 烧android rom
注意:android2.3中使用了串口0,所以启动后要把串口插到串口0中,不然串口没有任何启动信息出来。android2.3中屏幕上的logo是在左上角(也是个刷机成功的标志)
- 烧录android4.0.4
fastboot flash bootloader android4.0/uboot.bin 烧uboot
fastboot flash kernel android4.0/zImage-android 烧linux kernel
fastboot flash system android4.0/x210.img 烧android rom
dnw刷机(windows数字签名比较坑)
(1)设置拨码开关使开发版从USB启动
(2)ndw
- dnw支持USB和串口两种数据传输方式,这里只讲USB方式
- dnw是一个软件,是三星公司编写的,这个软件的功能是通过USB线连接开发板和电脑主机,然后从主机下载文件镜像到开发板中去烧录系统
- dnw使用时通过usb线下载,所以dnw是需要装usb驱动的,驱动在“X210光盘资料\A盘\tools\USB驱动”目录中
(3)开发板开机从usb启动后,设备管理器中显示已经安装的设备,并且dnw工具上方显示出现USB:OK(X210开发板使用了软开关,所以这里必须一直按着POWER键才能保持开机)
(4)刷x210_usb.bin,地址是0xd0020010
(1)设置dnw下载内存地址。在dnw软件的菜单“Configuration”中设置Download Address为0xd0020010,确认即可。
这个地址就是iRAM中BL1的起始地址(iRAM基地址时0xd0020000,前面有16字节的用于校验的头信息区)。
该部分是作为一个裸机程序去执行初始化DDR以及usb等准备工作为下一步接收usb数据以及烧录uboot.bin到DDR做准备,最后会通过绝对地址跳转过去执行uboot。由于uboot代码中前16字节已经默认空出留给头信息,所以跳转的时候会在下一步配置的地址的基础上往后偏移16字节。(待测试)
(2)选择USB Port->Transmit选择x210_usb.bin即可开始下载
(5)刷uboot.bin,地址0x33e00000(DDR中的地址,也可以是0x23e00000)
(6)下载完uboot.bin后串口终端有信息打印出来,回车进入命令行,这时可以松开 开机键
(7) 输入fdisk -c 0
输入fastboot(8)之后的操作参考 SD卡刷机(4)往后部分
dnw刷机(linux)
(1)设置拨码开关使开发版从USB启动
(2)VMware菜单:虚拟机->可移动设备->Samsung S5PC110 SEC Test B/D,点击连接
(3)成功连接后现象:1、Windows下设备管理器没了;2、ls /dev/secbulk0 设备节点自动出现
(4)使用dnw进行下载uboot.bin
(1)dnw -a 0xd0020010 x210_usb.bin
(2)dnw -a 0x33e00000 uboot.bin
成功标志:SecureCRT中成功看到了uboot的启动信息,并且进入了uboot命令行。(5)这里有两个问题。第一个是make出错,最终修改了src/driver/Makefile文件解决(Makefile文件里只保留obj-m := secbulk.o)。第二个是显示Bad address,好像要连接设备后快速执行下载命令就可以解决。
小端模式
部署情况(待完善)
(1)iNand/SD卡
- Reserved:[0号扇区,0号扇区],512字节。当我们在uboot命令行下执行fdisk -c 0进行分区后,这里面就会被写入MBR分区信息,可以用来指导fastboot刷机操作。
- BL1(uboot前8KB):[1号扇区,16号扇区]。8KB,会被加载到内部SRAM去运行
- uboot环境变量:[17号扇区,48号扇区]。16KB
- BL2(整个uboot):[49号扇区,1072号扇区]。512KB,会被加载到DDR去运行
- kernel:[1073号扇区,9264号扇区]。4096KB
(2)DDR
(1)初始化时栈空间,0x30000000~0x33e00000。因为是满减栈,需将sp设置为0x33e00000。
(2)bi_boot_params0x30000100,uboot给kernel传参的tag起始地址- uboot,0x33e00000
- bd
- gd
- uboot运行时堆空间,912KB,包含环境变量
- uboot运行时栈空间,512KB-0x1000
- 不知道干嘛用的空间,0x1000。从0x33e00000到这个区域末尾的大小为2M
- fastboot的传输缓冲区,0x3e000000,大小为0x11000000即272M。定义在x210.h中
以上这些值基本都是人为定死的,并在多出进行使用,而且这些地方的值必须对应相等,例如uboot源码中、启动SD卡制作脚本等。
fastboot烧录时的打印信息解读
(1)地址分配
[Partition table on MoviNAND]
ptn 0 name='bootloader' start=0x0 len=N/A (use hard-coded info. (cmd: movi))
ptn 1 name='kernel' start=N/A len=N/A (use hard-coded info. (cmd: movi))
ptn 2 name='ramdisk' start=N/A len=0x300000(~3072KB) (use hard-coded info. (cmd: movi))
ptn 3 name='config' start=0xAECC00 len=0x1028DC00(~264759KB)
ptn 4 name='system' start=0x10D7A800 len=0x1028DC00(~264759KB)
ptn 5 name='cache' start=0x21008400 len=0x65F7000(~104412KB)
ptn 6 name='userdata' start=0x275FF400 len=0xC0C6FC00(~3158463KB)这里显示的不是分区,参考:x210:iNand分区情况。
(2)fastboot flash bootloader android4.0.4/uboot.bin
- 主机端
sending 'bootloader' (384 KB)... OKAY 发送bootloader分区数据大小为384KB
writing 'bootloader'... OKAY 写入数据到存储设备的bootloader分区- uboot端
Received 17 bytes: download:00060000 接收到17字节数据:download:00060000,表示将要下载接收的数据大小为0x60000字节(=393216字节=384KB)
Starting download of 393216 bytes 开始下载393216字节的数据downloading of 393216 bytes finished 下载完成
Received 16 bytes: flash:bootloader 接收到16字节数据:flash:bootloader,表示要将下载到的数据写入存储设备的bootloader扇区
flashing 'bootloader'
Writing BL1 to sector 1 (16 sectors).. checksum : 0xed857 将bootloader的BL1部分(大小为16个扇区大小,一个扇区512字节,即8KB)写入存储设备的1号扇区(第2个扇区,原因上面有),BL1部分的checksum为0xed857,应确保头信息中也是这个值才能正常启动
writing bootloader.. 49, 1024 将整个bootloader写入到存储设备的49号扇区,大小为1024个扇区大小,即512K
MMC write: dev # 0, block # 49, count 1024 ... 1024 blocks written: OK 向设备0(iNand)的49号扇区写入1024个扇区大小数据完成
completed
partition 'bootloader' flashed(3)fastboot flash kernel android4.0.4/zImage-android
- 主机端
sending 'kernel' (3800 KB)... OKAY 发送kernel分区数据大小为3800KB
writing 'kernel'... OKAY 写入数据到存储设备的kernel分区- uboot端
Received 17 bytes: download:003b6000 将要下载接收的数据大小为0x003b6000字节(=3891200字节=3800KB)
Starting download of 3891200 bytes
...
downloading of 3891200 bytes finished
Received 12 bytes: flash:kernel
flashing 'kernel'
writing kernel.. 1073, 8192
MMC write: dev # 0, block # 1073, count 8192 ... 8192 blocks written: OK 向设备0(iNand)的1073(49+1024=1073刚好紧挨着bootloader分区)号扇区写入8192个扇区大小(4096KB)数据完成
completed
partition 'kernel' flashed(4)fastboot flash system android4.0.4/x210.img
- 主机端
sending 'system' (262144 KB)... OKAY 发送system分区数据大小为262144KB
writing 'system'... OKAY- uboot端
Received 17 bytes: download:10000000 将要下载接收的数据大小为0x10000000字节(=268435456字节=262144KB)
Starting download of 268435456 bytes
................................................................................
................................................................................
................................................................................
...............
downloading of 268435456 bytes finished
Received 12 bytes: flash:system
flashing 'system'MMC write: dev # 0, block # 551892, count 529518 ... 529518 blocks written: OK 向设备0(iNand)的551892(551892*512=0x10d7a800)号扇区写入529518个扇区大小(264759KB=0x1028dc00字节)数据完成,对比上面的分区信息中的数据可以发现是对应的
partition 'system' flashed
uboot链接时起始地址为0xc3e00000(取决于TEXT_BASE的值),由于启用了MMU进行虚拟地址映射,所以其实际的物理地址为0x33e00000。但猜测是由于寄存器配置的问题我们访问0x23e00000起始的一片空间和访问0x33e00000起始的一片空间实际访问的是同一个内存空间,所以对于操作0x23e00000和操作0x33e00000是一样的效果。
linux下使用dd命令向SD卡中写入裸机程序
命令行下输入:sudo dd iflag=dsync oflag=dsync if=210.bin of=/dev/sdb seek=1
对于我们普通的裸机程序,是作为BL1来进行加载的。可见这里的裸机程序不能大于16KB
安装交叉编译工具链
(1) 我们开发S5PV210所使用的交叉编译工具链是arm-2009q3这个版本
(2)将arm-2009q3复制到/usr/local/arm(自己定义即可)下并解压
tar -jxvf arm-2009q3.tar.bz2
(3)进入arm-2009q3/bin目录下目录下执行./arm-none-linux-gnueabi-gcc -v测试安装是否成功,若成功会打印出包含“gcc version 4.4.1 ”字样的一串字符
(4)将工具链导出到环境变量
- PATH这个环境变量是系统自带的,它的含义就是系统在查找可执行程序时会搜索的路径范围
- 通过echo $PATH查看PATH环境变量的值
- export PATH=/usr/local/arm/arm-2009q3/bin:$PATH
在一个终端中执行以上命令后,该终端中就可以直接使用arm-2009q3/bin下的这些可执行程序了,但是只要关掉这个终端再另外打开一个立马就不行了。原因是我们本次终端中执行时的操作只是针对本终端,以后再打开的终端并未被执行过这个命令所以没导出
在~/.bashrc中,添加export PATH=/usr/local/arm/arm-2009q3/bin:$PATH
导出这个环境变量是在当前用户,如果你登录时在其他用户下是没用的
(5)为工具链创建arm-linux-xxx符号链接
ln arm-none-linux-gnueabi-gcc -s arm-linux-gcc
其他可执行文件也需要用同样的方法进行创建
/bin目录放置一些系统自带的用户使用的应用程序,/sbin目录下存放的是系统自带的系统管理方面的应用程序
Makefile
(参考《跟我一起学Makefile》)
裸机程序中的Makefile
led.bin: start.o arm-linux-ld -Ttext 0x0 -o led.elf $^ arm-linux-objcopy -O binary led.elf led.bin arm-linux-objdump -D led.elf > led_elf.dis gcc mkv210_image.c -o mkx210 ./mkx210 led.bin 210.bin %.o : %.S arm-linux-gcc -o $@ $< -c %.o : %.c arm-linux-gcc -o $@ $< -c clean: rm *.o *.elf *.bin *.dis mkx210 -f
- gcc加上-c选项表示只编译并生成目标文件.o
- 真正的项目的Makefile都是把程序的编译和链接过程分开的
- 平时我们用gcc a.c -o exe这种方式来编译时,实际上把编译和链接过程一步完成了。在内部实际上编译和链接永远是分开独立进行的,编译要使用编译器gcc,链接要使用链接器ld
- 链接器得到led.elf其实就是我们的可执行程序
- 在嵌入式裸机中我们需要的是可以烧写的文件(可烧写的文件就叫镜像image),因此我们需要用.elf为原材料来制作镜像,制作工具是交叉编译工具链中的arm-linux-objcopy
- 使用arm-linux-objdump工具进行反编译(反汇编),反汇编其实就是把编译后的elf格式的可执行程序给反过来的到对应的汇编程序,的到它的汇编源代码
- mkv210_image.c这个程序其实最终不是在开发板上执行的,而是在主机linux中执行的,因此编译这个程序用gcc而不是用arm-linux-gcc。这个.c文件编译后得到一个可执行程序mkx210,目的是通过执行这个mkx210程序而由led.bin得到210.bin。(210.bin是通过SD卡启动时的裸机镜像,这个镜像需要由led.bin来加工的到,加工的具体方法和原理要看mkv210_image.c)
main函数两个形参的作用
main函数接收2个形参:argc和argv。
argc是用户(通过命令行来)执行这个程序时,实际传递的参数个数。注意这个个数是包含程序执行本身的
argv是一个字符串数组,这个数组中存储的字符串就是一个个的传参。
譬如我们执行程序时使用./mkx210 led.bin 210.bin
则argc = 3
则argv[0] = "./mkx210" argv[1] = led.bin argv[2] = 210.bin
glibc读写文件接口
可参考linux应用编程和网络编程大纲文件IO章节
校验和的计算方法
算法:校验和其实就是需要校验的内存区域中,所有内存中的内容按照字节为单位来进行相加,最终相加的和极为校验和。
实现时大家要注意指针的类型为char *
mkv210_image.c文件分析
分析该文件可知,这里是将我们的.bin文件强制当成一个16K-16B的大小来对待的。也就是说当.bin大小小于16K-16B时,认为的给后面填充0使其大小为16K-16B;当.bin文件大于16K-16B时,直接丢弃掉后面的部分。
注意:16字节头数据中BL1 size在该文件中并没有写入正确的数据,但这个数据会在加载BL1时,由iROM检查该数据的大小,并将BL1复制到内部SRAM,那为何还能正常工作呢?
16KB=0x4000B,而我们在写入头数据中的BL1 size时由于从头开始写,所以肯定大于这个值。所以猜测应该是当头数据中的BL1 size大于16KB时会被强制当16KB来处理,而我们的代码实际根本没有16KB大小,所以程序运行起来没有问题。
还有一点在计算CheckSum时使用了16K-16B这个大小,一方面由于实际代码大小没有这么大后面全是0,所以使用16K-16B大小来计算和实际没有差别;另一方面就如上面所说大于16KB时会被强制当16KB来处理所以这里也刚好对应起来了。
反汇编工具objdump的使用
arm-linux-objdump -D led.elf > led_elf.dis
objdump是gcc工具链中的反汇编工具,作用是由编译链接好的elf格式的可执行程序反过来得到汇编源代码
-D表示反汇编 > 左边的是elf的可执行程序(反汇编时的原材料),>右边的是反汇编生成的反汇编程序因为有流水线,所以pc的值为当前执行指令往后2条指令处的地址
在S5PV210内部的iROM代码(BL0)中,其实已经关过看门狗了,但很多CPU内部是没有BL0的,因此也没人给你关看门狗,都要在启动代码前段自己写代码关看门狗
堆栈以及调用C语言
“C语言运行时(runtime)”需要一定的条件,这些条件由汇编来提供。C语言运行时主要是需要栈。我们平时在编写单片机程序时在main函数之前一般都会有一段汇编代码,这段代码中就包含了设置栈的操作。
系统在复位后默认是进入SVC模式的,可见我们的裸机代码就是工作在SVC模式下的。在汇编代码中设置栈只需要直接操作SP让它的值为一个合适的地址。
当前CPU刚复位(刚启动),外部的DRRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需初始化即可使用)。因此我们只能在SRAM中找一段内存来作为SVC的栈。
通过查看iROM_Application_Note文档,如下图
可知根据三星的建议最好将SVC栈放到SVC Stack这个地址空间内,但是sp的值应该是0xd0037d80还是0xd0037780呢。
栈有四种:满减栈 满增栈 空减栈 空增栈
满栈:进栈:先移动指针再存; 出栈:先出数据再移动指针
空栈:xxx
减栈:进栈:指针向下移动; 出栈:指针向上移动
增栈:xxx在ARM中,ATPCS(ARM关于程序应该怎么实现的一个规范)要求使用满减栈,所以sp的值应该是0xd0037d80
在汇编文件中加入
ldr sp, =0xd0037d80
即可
之后就可以通过
bl C语言函数名
调用C语言的函数了
cache
cache是一种内存,叫高速缓存。
从容量来说:CPU < 寄存器 < cache < DDR
从速度来说:CPU > 寄存器 > cache > DDRcache的存在,是因为寄存器和ddr之间速度差异太大,ddr的速度远不能满足寄存器的需要(不能满足cpu的需要,所以没有cache会拉低整个系统的整体速度)
S5PV210采用CortexA8核心,其内部有32KB icache和32kb dcache。icache是用来缓存指令的;dcache是用来缓存数据的。
icache工作时,会把我们CPU正在运行的指令的旁边几句指令事先给读取到icache中(CPU设计有一个基本原理:代码执行时,下一句执行当前一句代码旁边代码的可能性要大很多)。当下一句CPU要指令时,cache首先检查自己事先准备的缓存指令中有没这句,如果有就直接拿给CPU,如果没有则需要从DDR中重新去读取拿给CPU,并同时做一系列的动作:清缓存、重新缓存。
dcache与MMU有关,一般在操作系统中使用了虚拟地址时才会用到。
icache的一切动作都是自动的,不需人为干预。我们所需要做的就是打开/关闭icache。
在210的iROM中BL0已经打开了icache。所以之前看到的现象都是icache打开时的现象。
汇编代码读写cp15协处理器以开关icache
mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中
bic r0, r0, #(1<<12) // bit12 置0 关icache
orr r0, r0, #(1<<12) // bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;
位置无关编码(PIC,position independent code):汇编源文件被编码成二进制可执行程序时编码方式与位置(内存地址)无关。
位置有关编码:汇编源码编码成二进制可执行程序后和内存地址是有关的。链接地址:链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本)
运行地址:程序实际运行时地址(指定方式:由实际运行时被加载到内存的哪个位置说了算)我们在设计一个程序时,会给这个程序指定一个运行地址(链接地址)。就是说我们在编译程序时其实心里是知道我们程序将来被运行时的地址(运行地址)的,而且必须给编译器链接器指定这个地址(链接地址)才行。最后得到的二进制程序理论上是和你指定的运行地址有关的,将来这个程序被执行时必须放在当时编译链接时给定的那个地址(链接地址)下才行,否则不能运行(就叫位置有关代码)。但是有个别特别的指令他可以跟指定的地址(链接地址)没有关系,也就是说这些代码实际运行时不管放在哪里都能正常运行。
我们之前的裸机程序中,Makefile中用 -Ttext 0x0 来指定链接地址是0x0。这意味着我们认为这个程序将来会放在0x0这个内存地址去运行。
之所以-Ttext 0x0 能成功跑通,是因为该程序是位置无关码的原因。三星推荐的启动方式中:bootloader必须小于96KB并大于16KB,假定bootloader为80KB,启动过程是这样子:先开机上电后BL0运行,BL0会加载外部启动设备中的bootloader的前16KB(BL1)到SRAM中去运行,BL1运行时会加载BL2(bootloader中80-16=64KB)到SRAM中(从SRAM的16KB处开始用)去运行;BL2运行时会初始化DDR并且将OS搬运到DDR去执行OS,启动完成。(这里感觉有点迷,不是很清楚三星这样设计的目的,了解就好不做深究)
uboot实际使用的方式:uboot大小随意,假定为200KB。启动过程是这样子:先开机上电后BL0运行,BL0会加载外部启动设备中的BL1(uboot的前8KB)到SRAM中去运行,BL1运行时会初始化DDR,然后将整个uboot搬运到DDR中,然后用一句长跳转(从SRAM跳转到DDR)指令从SRAM中直接跳转到DDR中继续执行uboot直到uboot完全启动。uboot启动后在uboot命令行中去启动OS。
上述uboot实际使用的启动方式如果不做任何处理势必会存在链接地址与运行地址不同的问题,因为同样一份代码又要在内部SRAM中运行,又要在外部DDR中运行。解决方案有两种
- 重定位。
- 分散加载。把uboot分成2部分(BL1和整个uboot),两部分分别指定不同的链接地址。启动时将两部分加载到不同的地址(BL1加载到SRAM,整个uboot加载到DDR),这时候不用重定位也能启动。分散加载其实相当于手工重定位。重定位是用代码来进行重定位,分散加载是手工操作重定位的。
如何指定链接地址
- 在操作系统下我们编写的应用程序在编译链接时不需要指定链接地址,链接地址会被默认设为0,这是因为应用程序运行在操作系统的一个进程中,在这个进程中这个应用程序独享4G的虚拟地址空间。所以应用程序都可以链接到0地址,因为每个进程都是从0地址开始的。
- 210中的裸机程序。运行地址由我们下载时确定,下载时下载到0xd0020010,所以就从这里开始运行。(这个下载地址也不是我们随意定的,是iROM中的BL0加载BL1时事先指定好的地址,这是由CPU的设计决定的)。所以理论上我们编译链接时应该将地址指定到0xd0020010,但是实际上我们在之前裸机程序中都是使用位置无关码PIC,所以链接地址可以是0。
段名分为2种:一种是编译器链接器内部定好的,先天性的名字;一种是程序员自己指定的、自定义的段名。
先天性段名:
代码段:(.text),又叫文本段,代码段其实就是函数编译后生成的东西
数据段:(.data),数据段就是C语言中有显式初始化为非0的全局变量
bss段:(.bss),又叫ZI(zero initial)段,就是零初始化段,对应C语言中初始化为0的全局变量。
后天性段名:
段名由程序员自己定义,段的属性和特征也由程序员自己定义。
链接脚本
链接脚本其实是个规则文件,他是程序员用来指挥链接器工作的。链接器会参考链接脚本,并且使用其中规定的规则来处理.o文件中那些段,将其链接成一个可执行程序。
链接脚本的关键内容有2部分:段名 + 地址(作为链接地址的内存地址)
链接脚本的理解:
SECTIONS {} 这个是整个链接脚本
. 点号在链接脚本中代表当前位置。
= 等号代表赋值
连接脚本中定义的符号可以在程序中使用
代码重定位实战
- 任务:在SRAM中将代码从0xd0020010重定位到0xd0024000
- 思路:
(1)通过链接脚本将代码链接到0xd0024000
(2)dnw下载时将bin文件下载到0xd0020010
(3)代码执行时通过代码前段的少量位置无关码将整个代码搬移到0xd0024000
(4)使用一个长跳转跳转到0xd0024000处的代码继续执行,重定位完成- 解释
(1)长跳转:首先这句代码是一句跳转指令(ARM中的跳转指令就是类似于分支指令B、BL等作用的指令),跳转指令通过给PC(r15)赋一个新值来完成代码段的跳转执行。长跳转指的是跳转到的地址和当前地址差异比较大,跳转的范围比较宽广。
(2)当我们执行完代码重定位后,实际上在SRAM中有2份代码的镜像(一份是我们下载到0xd0020010处开头的,另一份是重定位代码复制到0xd0024000处开头的),这两份内容完全相同,仅仅地址不同。重定位之后使用ldr pc, =led_blink这句长跳转直接从0xd0020010处代码跳转到0xd0024000开头的那一份代码的led_blink函数处去执行。(实际上此时在SRAM中有2个led_blink函数镜像,两个都能执行,如果短跳转bl led_blink则执行的就是0xd0020010开头的这一份,如果长跳转ldr pc, =led_blink则执行的是0xd0024000开头处的这一份)。这就是短跳转和长跳转的区别。
(3)当链接地址和运行地址相同时,短跳转和长跳转实际效果是一样的;但是当链接地址不等于运行地址时,短跳转和长跳转就有差异了。这时候短跳转实际执行的是运行地址处的那一份,而长跳转执行的是链接地址处那一份。- 细节
(1)ldr和adr都是伪指令,区别是ldr是长加载,加载的是链接地址;adr是短加载,加载的是运行时地址。据此可以通过用这两种方式分别加载_start判断是否相等来判断是否需要重定位。(可以通过反汇编来得知这两种加载方式加载出来的差别的本质在哪里(待补充))
(2)重定位(代码拷贝)
重定位就是汇编代码中的copy_loop函数,代码的作用是使用循环结构来逐句复制代码到链接地址。
复制的源地址是SRAM的0xd0020010,复制目标地址是SRAM的0xd0024000,复制长度是bss_start减去_start
所以复制的长度就是整个重定位需要重定位的长度,也就是整个程序中代码段+数据段的长度。
bss段(bss段中就是0初始化的全局变量)不需要重定位。
(3)清bss段
清除bss段是为了满足C语言的运行时要求(C语言要求显式初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的这个特性的)。一般情况下我们的程序是不需要负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加一段头程序,这段程序会在我们的main函数之前运行,这段代码就负责清除bss)。但是在我们代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那一份代码中的bss,而未清除重定位地址处开头的那一份代码的bss,所以重定位之后需要自己去清除bss。
(4)长跳转
清理完bss段后重定位就结束了。然后当前的状况是:
1、当前运行地址还在0xd0020010开头的(重定位前的)那一份代码中运行着。
2、此时SRAM中已经有了2份代码,1份在d0020010开头,另一份在d0024000开头的位置。
然后就要长跳转了。SRAM内部重定位代码示例
SDRAM
- SDRAM:同步动态随机存储器。DDR就是DDR SDRAM,是SDRAM的升级版,双倍速度的SDRAM。DDR有好多代:DDR1 DDR2 DDR3 DDR4 LPDDR
- SDRAM/DDR都属于动态内存(相对于静态内存SRAM),都需要先运行一段初始化代码来初始化才能使用,不像SRAM开机上电后就可以直接运行。
- S5PV210共有2个内存端口(就好象有2个内存插槽),分别是DRAM0和DRAM1。
DRAM0:内存地址范围:0x20000000~0x3FFFFFFF(512MB),对应引脚是Xm1xxxx
DRAM1: 内存地址范围:0x40000000~0x7FFFFFFF(1024MB),对应引脚是Xm2xxxx- 我们X210开发板就只有512MB内存,连接方法是在DRAM0端口分布256MB,在DRAM1端口分布了256MB。(0x30000000-0x4fffffff)
- SoC与SDRAM之间通过地址总线(Xmn_ADDR0~XMnADDR13共14根地址总线) + 控制总线(中间部分,自己看原理图) + 数据总线(Xmn_DATA0~XMnDATA31共32根数据线)进行连接通信
- X210开发板共使用了4片内存(每片1Gb=128MB,共512MB),每片内存的数据总线都是16位的(单芯片是16位内存)。通过查看原理图可知,这4片内存芯片被分为两组分别对应连接到SoC的DRAM0和DRAM1,每组中的两个芯片地址总线和控制总线采用并联的接法,数据总线采用串联的接法,最终的效果就类似与在SoC的每个内存端口连接了一个32位256MB的内存芯片。
- X210开发板上使用的内存芯片是128Bb×8结构的,这里的8指的是8bank,每bank128Mbit,总共128MB。
(1)DDR端口信号中有BA0~BA2,接在内存芯片的BA0~BA2上,这些引脚就是用来选择bank的。
(2)每个bank内部有128Mb,通过row address(14位) + column address(10位)的方式来综合寻址。一共能寻址的范围是:2的14次方+2的10次方 = 2的24次方。对应16MB(128Mbit)内存。
(3)SRAM的容量计算方法是2的地址总线个数次方*每个地址的大小
(4)通过对比,理解为何SDRAM的容量更大并且不能直接访问
汇编初始化SDRAM详解
(感觉研究这个目前意义不大)
S5PV210的时钟系统简介
时钟域:MSYS、DSYS、PSYS
(1)因为S5PV210的时钟体系比较复杂,内部外设模块太多,因此把整个内部的时钟划分为3大块,叫做3个域。
(2)MSYS: CPU(Cortex-A8内核)、DRAM控制器(DMC0和DMC1)、IRAM&IROM······
(3)DSYS: 都是和视频显示、编解码等有关的模块
(4)PSYS: 和内部的各种外设时钟有关,譬如串口、SD接口、I2C、AC97、USB等。
(5)为什么内部要分为3个域,怎么划分的?因为210内部的这些模块彼此工作时钟速率差异太大了,所以有必要把高速的放一起,相对低速的放一起。时钟来源:晶振+时钟发生器+PLL+分频电路
(1)S5PV210外部有4个晶振接口,设计板子硬件时可以根据需要来决定在哪里接晶振。接了晶振之后上电相应的模块就能产生振荡,产生原始时钟。原始时钟再经过一系列的筛选开关进入相应的PLL电路生成倍频后的高频时钟。高频时钟再经过分频到达芯片内部各模块上。(有些模块,譬如串口内部还有进一步的分频器进行再次分频使用)PLL:APLL、MPLL、EPLL、VPLL
APLL:Cortex-A8内核 MSYS域
MPLL&EPLL:DSYS PSYS
VPLL:Video视频相关模块
S5PV210时钟域详解
(1)MSYS域:
ARMCLK: 给cpu内核工作的时钟,也就是所谓的主频。
HCLK_MSYS: MSYS域的高频时钟,给DMC0和DMC1使用
PCLK_MSYS: MSYS域的低频时钟
HCLK_IMEM:给iROM和iRAM(合称iMEM)使用(2)DSYS域:
HCLK_DSYS:DSYS域的高频时钟
PCLK_DSYS:DSYS域的低频时钟(3)PSYS域:
HCLK_PSYS:PSYS域的高频时钟
PCLK_PSYS:PSYS域的低频时钟
SCLK_ONENAND:210内部的各个外设都是接在(内部AMBA总线)总线上面的,AMBA总线有1条高频分支叫AHB,有一条低频分支叫APB。上面的各个域都有各自对应的HCLK_XXX和PCLK_XXX,其中HCLK_XXX就是XXX这个域中AHB总线的工作频率;PCLK_XXX就是XXX这个域中APB总线的工作频率。
SoC内部的各个外设其实是挂在总线上工作的,也就是说这个外设的时钟来自于他挂在的总线,譬如串口UART挂在PSYS域下的APB总线上,因此串口的时钟来源是PCLK_PSYS。
各时钟典型值(默认值,iROM中设置的值)
(1)当210刚上电时,默认是外部晶振+内部时钟发生器产生的24MHz频率的时钟直接给ARMCLK的,这时系统的主频就是24MHz,运行非常慢。
(2)iROM代码执行时第6步中初始化了时钟系统,这时给了系统一个默认推荐运行频率。这个时钟频率是三星推荐的210工作性能和稳定性最佳的频率。
(3)各时钟的典型值:
freq(ARMCLK) = 1000 MHz
freq(HCLK_MSYS) = 200 MHz
freq(HCLK_IMEM) = 100 MHz
freq(PCLK_MSYS) = 100 MHz
freq(HCLK_DSYS) = 166 MHz
freq(PCLK_DSYS) = 83 MHz
freq(HCLK_PSYS) = 133 MHz
freq(PCLK_PSYS) = 66 MHz
freq(SCLK_ONENAND) = 133 MHz, 166 MHz
x210开发板接的晶振是在XusbXTI
时钟设置的关键性寄存器
(1)xPLL_LOCK
xPLL_LOCK寄存器主要控制PLL锁定周期的。
(2)xPLL_CON/xPLL_CON0/xPLL_CON1
PLL_CON寄存器主要用来打开/关闭PLL电路,设置PLL的倍频参数,查看PLL锁定状态等
(3)CLK_SRCn(n:0~6)
CLK_SRC寄存器是用来设置时钟来源的,对应时钟框图中的MUX开关。
(4)CLK_SRC_MASKn
CLK_SRC_MASK决定MUX开关n选1后是否能继续通过。默认的时钟都是打开的,好处是不会因为某个模块的时钟关闭而导致莫名其妙的问题,坏处是功耗控制不精细、功耗高。
(5)CLK_DIVn
各模块的分频器参数配置
(6)CLK_GATE_x
类似于CLK_SRC_MASK,对时钟进行开关控制
(7)CLK_DIV_STATn
(8)CLK_MUX_STATn
这两类状态位寄存器,用来查看DIV和MUX的状态是否已经完成还是在进行中
总结:其中最重要的寄存器有3类:CON、SRC、DIV。其中CON决定PLL倍频到多少,SRC决定走哪一路,DIV决定分频多少。
(短期内不做补充)
X210开发板的按键接法
查原理图,找到按键对应的GPIO:SW5:GPH0_2 SW6:GPH0_3 SW78910:GPH2_0123。对应的外部中断分别为EINT2、EINT3、EINT16、EINT17、EINT18、EINT19
中断的实现机制:异常向量表
(1)异常发生时,处理器会将PC设定为一个特定的存储器地址,这些特定的存储器地址称为异常向量。所有的异常向量被集中放在程序存储器的一个连续地址空间中,称为异常向量表。每个异常向量占4字节,异常向量处是一些跳转指令,跳转到相应的异常处理函数。软件需要做的就是实现这些异常处理函数。
(2)在ARM处理器中,当异常发生时,除了复位异常会立即中止当前指令的执行,其他的异常都是在处理器完成当前指令后再执行异常处理程序。
(3)S5PV210内部为CortexTM-A8,它的异常向量表为
(4)通常存储器地址映射地址0x00000000是为异常向量表保留的。但某些嵌入式系统配置使能的时候,低端的异常向量可以选择映射到特定的高端地址0xFFFF0000。Cortex-A8处理器支持通过CP15的C12寄存器将异常向量的首地址设置在任意位置。
(5)这里需要注意一点。我们使用的S5PV210芯片,它的异常向量表基地址默认在0x0地址处,这正好是iROM的映射地址。我们无法修改iROM里的内容所以也就无法修改异常向量表中的每个向量,即无法修改跳转指令的具体跳转地址。三星的做法是将向量表中的这些要跳转的地址指向iRAM中的一片空间与异常向量表中的跳转指令一一对应,我们称它为异常跳转函数指针表,然后通过修改异常跳转函数指针表中每一项所指向的具体函数来间接实现修改异常向量表。
(6)当发生异常时,CPU会自动执行以下过程
- 保存执行状态:将CPSR复制到发生的异常模式下SPSR中;
- 模式切换:将CPSR模式位强制设置为与异常类型相对应的值,同时处理器进入到ARM执行模式,禁止所有IRQ中断,当进入FIQ快速中断模式时禁止FIQ中断;
- 完成将当前的PC保存到相应异常模式下的LR寄存器
- 设置PC到相应的异常向量
(7)每一种异常都会导致内核进入一种特定的模式。此外,也可以通过编程改变CPSR,进入ARM处理器模式。用户模式和系统模式是仅有的不可通过异常进入的俩种模式,要进入这俩种模式,必须通过编程改变CPSR。
(8)初始化异常向量表
(1)210在内部SRAM中设置了异常跳转函数指针表。
(2)通过210的iROM application note文档中iRAM的地址分配,可知,iRAM中异常跳转函数指针表的起始地址为0xD0037400。
(3)当我们将异常处理程序的首地址写入异常跳转函数指针表中对应的位置后,当相应异常发生后,硬件自动从对应异常向量表处执行跳转指令。
(4)异常处理程序要先在汇编中进行,因为需要进行保护现场和恢复现场操作,以IRQ中断为例
(1)中断处理要注意保护现场(中断从SVC模式来,则保存SVC模式下的必要寄存器的值)和恢复现场(中断处理完成后,准备返回SVC模式前,要将保存的SVC模式下的必要寄存器的值恢复回去)。
(2)根据下图,可见保存现场包括:设置IRQ栈;保存中断返回地址;保存R0~R12和CPSR程序状态寄存器
(3)设置IRQ栈。SVC模式和IRQ模式各自有一片栈空间,三星为我们在内部SRAM中同样预留了IRQ的栈空间,并在iROM阶段做了初始化。当发生中断后,我们要做的就是将sp(IRQ模式下的sp)指针指向IRQ Stack这片区域。
(4)保存中断返回地址。ARM处理器响应异常时,会自动完成将当前的PC保存到相应异常模式下的LR寄存器。另外由于arm的二级流水线,PC的值要比当前所执行的指令的地址超前8个字节。(这里似乎与模式有关待确认)
(5)保存R0~R12和CPSR。IRQ模式和SVC模式公用R0~R12和CPSR寄存器,而CPSR会自动保存在IRQ模式下的SPSR中,所以在操作R0~R12之前需要将其压入栈中。
(6)恢复现场主要是恢复:r0-r12,pc,cps
(7)代码
IRQ_handle: // 设置IRQ模式下的栈 ldr sp, =IRQ_STACK // 保存LR // 因为ARM有流水线,所以PC的值会比真正执行的代码+8, sub lr, lr, #4 // 保存r0-r12和lr到irq模式下的栈上面 stmfd sp!, {r0-r12, lr} // 在此调用真正的isr来处理中断 bl irq_handler // 处理完成开始恢复现场,其实就是做中断返回,关键是将r0-r12,pc,cpsr一起回复 ldmfd sp!, {r0-r12, pc}^
注:可以发现,上面这种写法明显是不支持中断嵌套的,因为中断发生时CPU会自动禁止所有IRQ中断。要支持中断嵌套,需要再次使能IRQ中断,可参考(待补充)
异常处理的2个阶段
(1)第一个阶段之所以能够进行,主要依赖于CPU设计时提供的异常向量表机制。第一个阶段的主要任务是从异常发生到响应异常并且保存/恢复现场、跳转到真正的异常处理程序处。
(2)第二个阶段的目的是识别多个中断源中究竟哪一个发生了中断,然后调用相应的中断处理程序来处理这个中断
S5PV210的向量中断控制器VIC(Vectored Interrupt Controller)
VIC负责管理外设部件的中断信号,是外设中断源和ARM CPU之间的桥梁
- FIQ
FIQ快速中断请求具有最高优先级。如果分配给FIQ的请求多于一个,VIC将中断请求相或后向ARM处理器产生FIQ信号。当只有一个中断被分配为FIQ时可实现最短的FIQ等待,但如果分配给IFIQ级的中断多于1个,FIQ服务程序需要读取FIQ状态寄存器来识别产生中断请求的FIQ中断源!
- 向量IRQ
向量IRQ具有中等优先级。向量中断就是不同的中断有不同的入口地址,由硬件提供中断服务程序入口地址,中断实时性好。VIC的vecaddr中存放的就是真正的中断服务程序的地址,直接取出来跳过去执行就可以了。所以向量中断较快
- 非向量IRQ
非向量IRQ的优先级最低。非向量中断就只有一个入口地址,进去了在判断中断标志来识别具体是哪个中断,由软件件提供中断服务程序入口地址。非向量中断发生后,VIC中的vecaddr中会存放一个默认中断服务程序的地址,这个程序是被所有的非向量中断公用的,他执行的作用就是判断究竟是发生了哪个非向量中断,然后再转向真正要被执行的中断服务程序。非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将INTPND寄存器中对应标志位置位,然后跳转到位于x18处的统一中断函数中;该函数通过读取INTPND寄存器中对应标志位来判断中断源,并根据优先级关系再跳到对应中断源的处理代码中处理中断
快速中断FIQ也可以看作是一个向量中断,只不过不需要从VIC的vecaddr中取地址,而是直接跳到相应地址执行,可以看作是一个指定了向量地址的中断。
S5PV210应该是属于非向量IRQ,但是在默认中断服务程序里又部分采用了类似于向量IRQ的那种技术。
S3C2440的第二阶段处理过程
(1)第一个问题,怎么找到具体是哪个中断:S3C2440的中断控制器中有一个寄存器(32位的),寄存器的每一个位对应一个中断源(为了解决支持更多中断源,2440又设计了一个子中断机制。在一级中断寄存器中有一些中断是共用的一个bit位,譬如AC97和WDT。对于共用中断,用子中断来区分究竟是哪一个发生了中断)
(2)第二个问题,怎么找到对应的isr的问题:首先给每个中断做了个编号,进入isr_handler之后先通过查阅中断源寄存器和子中断寄存器(中哪一位为1)确定中断的编号,然后用这个编号去isr数组(isr数组是中断初始化时事先设定好的,就是把各个中断的isr的函数名组成一个数组,用中断对应的编号作为索引来查询这个数组)中查阅得到isr地址。
评价:2440的中断处理设计不是特别优秀:第一个过程中使用子中断搞成2级的很麻烦;第二个过程中计算中断编号是个麻烦事,很耗费时间。而中断处理的时间是很宝贵的(系统有一个性能指标,叫实时性。实时性就是中断发生到响应的时间,这个时间越短越好。)
S5PV210的第二阶段处理过程
(1)第一个问题,怎么找到具体是哪个中断:S5PV210中因为支持的中断源很多,所以直接设计了4个中断寄存器,每个32位,每位对应一个中断源。(理论上210最多支持128个中断,实际支持不足128个,有些位是空的);210没有子中断寄存器,每个中断源都是并列的。当中断发生时,在irq_handler中依次去查询4个中断源寄存器,看哪一个的哪一位被置1,则这个位对应的寄存器就发生了中断,即找到了中断编号。
(2)第二个问题,怎么找到对应的isr的问题:210中支持的中断源多了很多,如果还使用2440的那一套来寻找isr地址就太慢了,太影响实时性了。于是210开拓了一种全新的寻找isr的机制。210提供了很多寄存器来解决每个中断源对应isr的寻找问题,实现的效果是当发生相应中断时,硬件会自动的将相应isr推入一定的寄存器中,我们软件只要去这个寄存器中执行函数就行了。
S5PV210中断处理的主要寄存器(这里有一些细节由于没有详细的文档说明便不再深究)
- VICnINTENABLE和VICnINTENCLEAR
(1)VICnINTENABLE 对应interrupt enable,INTENCLEAR对应interrupt enable clear
(2)INTENABLE寄存器负责相应的中断的使能,INTENCLEAR寄存器负责相应的中断的禁止。
(3)当我们想使能(意思就是启用这个中断,意思就是当硬件产生中断时CPU能接收的到)某个中断时,只要在这个中断编号对应的VICnINTENABLE的相应bit位写1即可(注意这个位写1其他位写0对其他位没有影响);如果我们想禁止某个中断源时,只要向VICnINTENCLEAR中相应的位写1即可。
- VICnINTSELECT
(1)设置各个中断的模式为irq还是fiq。一般都设置成irq
(2)IRQ和FIQ究竟有何区别。210中支持2种中断,irq和fiq。irq是普通中断,fiq是快速中断。快速中断提供一种更快响应处理的中断通道,用于对实时性要求很高的中断源。fiq在CPU设计时预先提供了一些机制保证fiq可以被快速处理,从而保证实时性。虽然硬件允许多个中断源被设置为fiq,但是软件工程师需要去遍历VICFIQSTATUS(注意,不是VICIRQSTATUS)里的每个可能为1的bit位,找到之后再映射到对应的ISR,这样效率很低。
(3)CPU如何保证fiq比irq快?有2个原因:第一,fiq模式有专用的r8~r12,因此在fiq的isr中可以直接使用r8-r12而不用保存,这就能节省时间;第二,异常向量表中fiq是最后一个异常向量入口。因此fiq的isr不需要跳转,可以直接写在原地,这样就比其他异常少跳转一次,省了些时间。
- VICnIRQSTATUS和VICnFIQSTATUS
(1)中断状态寄存器,是只读的。当发生了中断时,硬件会自动将该寄存器的对应位置为1,表示中断发生了。软件在处理中断第二阶段的第一个问题时,就是靠查询这个寄存器来得到中断编号的。
(2)该寄存器在被读取后会被自动清除(细节不再深究)
- VICnVECTPRIORITY0~VICnVECTPRIORITY31
(1)中断优先级设置寄存器,设置多个中断同时发生时先处理谁后处理谁的问题。对于S5PV210中所使用的的向量中断控制器似乎不支持中断嵌套,因为在发生IRQ异常时硬件会自动在CPSR中禁止所有IRQ中断。
- VICnVECTADDR0~VICnVECTADDR31、VICnADDRESS
(1)这几个寄存器和210中断处理第二阶段的第二个问题有关。
(2)VICnVECTADDR0到31这32个寄存器分别用来存放真正的各个中断对应的isr的函数地址。相当于每一个中断源都有一个VECTADDR寄存器,程序员在设置中断的时候,把这个中断的isr地址直接放入这个中断对应的VECTADDR寄存器即可。
(3)VICnADDR这个寄存器是只需要读的,它里面的内容是由硬件自动设置的。当发生了相应中断时,硬件会自动识别中断编号,并且会自动找到优先级最高的这个中断的VECTADDR寄存器,然后将其读出复制到VICnADDR中,供我们使用。这样的设计避免了软件查找中断源和isr,节省了时间,提高了210的中断响应速度。
(4)CPU写VICnADDRESS = 0之前,即使有更高优先级的中断发生,intrrupt controller也不会改变VICnADDRESS,这里也印证了S5PV210不支持中断嵌套。
(5)每次CPU写VICnADDRESS = 0之后,intrrupt controller会在现有的中断源中,重新找到优先级最高的一个,推送到VICnADDRESS
S5PV210中断处理的编程实践
- 初始化阶段
绑定异常向量表处理程序---禁止所有中断---选择中断类型为IRQ还是FIQ---绑定中断源处理程序---使能相应的中断源
- 响应阶段
IRQ异常向量处理程序
设置IRQ栈
保存现场
中断源查询执行函数
判断中断源
执行中断源处理程序
恢复现场
外部中断(对应GPH0~GPH3)
关键寄存器:CON、PEND、MASK
(1)外部中断的主要配置寄存器有3个:EXT_CON、EXT_PEND、EXT_MASK
(2)EXT_CON配置外部中断的触发方式。触发方式就是说外部电平怎么变化就能触发中断,也就是说这个外部中断产生的条件是什么
(3)EXT_PEND寄存器是中断挂起寄存器。这个寄存器中每一位对应一个外部中断,平时没有中断时值为0。当发生了中断后,硬件会自动将这个寄存器中该中断对应的位置1,我们去处理完这个中断后应该手工将该位置0。这个PEND寄存器的位就相当于是一个标志,如果发生了中断但是我们暂时忙来不及去处理时,这个位一直是1(这就是挂起),直到我有空了去处理了这个中断才会手工清除(写代码清除)这个挂起位表示这个中断被我处理了。
(4)EXT_MASK寄存器就是各个外部中断的使能/禁止开关。
GPIO中断(对应GPA0~GPJ4)
与外部中断比较类似,但有一些区别,请参考数据手册。
扩展文章
ARM处理器异常处理
ARM的异常处理
S5PV210中断控制器详解(一):概述和使用中断
S5PV210中断控制器详解(二):矢量和优先级
关于异常向量表的一些理解
(短期内不做补充)
SD卡的物理接口
(1)SD卡由9个针脚与外界进行物理连接,这9个脚中有2个地,1个电源,6个信号线。
(2)SD卡虽然只有一种物理接口,但是却支持两种读写协议:SD协议和SPI协议。
SPI协议特点(低速、接口操作时序简单、适合单片机)
(1)SPI协议是单片机中广泛使用的一种通信协议,并不是为SD卡专门发明的。
(2)SPI协议相对SD协议来说速度比较低。
(3)SD卡支持SPI协议,就是为了单片机方便使用。
SD协议特点(高速、接口时序复杂,适合有SDIO接口的SoC)
(1)SD协议是专门用来和SD卡通信的。
(2)SD协议要求SoC中有SD控制器,运行在高速率下,要求SoC的主频不能太低。
S5PV210的启动过程回顾
210启动首先执行内部的iROM(也就是BL0),BL0会判断OMpin来决定从哪个设备启动,如果启动设备是SD卡,则BL0会从SD卡读取前16KB(不一定是16,但不能大于16KB)到SRAM中去启动执行(这部分就是BL1,这就是steppingstone技术),BL1执行之后剩下的就是软件的事情了,SoC就不用再去操心了。
(1)启动的第一种情况是整个镜像大小小于16KB。这时候相当于我的整个镜像作为BL1被steppingstone直接硬件加载执行了而已。
(2)启动的第二种情况就是整个镜像大小大于16KB。(只要大于16KB,哪怕是17KB,或者是700MB都是一样的)这时候就要把整个镜像分为2部分:第一部分16KB大小,第二部分是剩下的大小。然后第一部分作为BL1启动,负责去初始化DRAM并且将第二部分加载到DRAM中去执行(uboot就是这样做的)。
三星系列SoC支持SD卡/NandFlash启动,主要是依靠SteppingStone技术,具体在S5PV210中支持steppingstone技术的是内部的iROM代码。
Global Variable
Device Copy Function
通道号:0,或者2
开始扇区号:
读取扇区个数:
读取后放入内存地址:
with_init:0一些部分的作用以及功能待确认
当我们的裸机程序大于16KB时,我们有以下两种处理方法
- 第一种。裸机程序APP作为BL2来使用,单独编写一个小于16KB的裸机程序bootloader作为BL1负责初始化DDR并将APP拷贝到DDR中,然后通过绝对地址跳转到DDR中的那份APP代码去运行。有点类似于单片机中的IAP。我们之前用dnw刷机将uboot.bin写入DDR时有一个x210_usb.bin这个代码就是这里说的bootloader,uboot.bin就是APP。使用要注意一些细节以及两个程序之间的配合。
- 第二种。裸机程序既作为BL1又作为BL2,将代码的前部分数据实现为位置无关码作为BL1,整个裸机程序作为BL2。首先将BL1加在到内部SRAM去运行,BL1负责初始化DDR并将BL2拷贝到DDR中,然后通过远跳转到DDR中的那份APP代码去运行。uboot在通过SD卡启动时就是使用的这种方式。
- 扩展(待确定)。可以试想一下,将两种方式结合。我们设法截取uboot.bin的前8KB数据,将开发板设为USB启动,通过dnw软件写入到0xd0020000地址(因为uboot中已经预留了16字节的头信息数据),事先将整个uboot.bin写入到SD卡(uboot如何确定BL2在哪个通道的存储设备中)的49号扇区开始的地址区域并插入SD卡的通道2对应的卡槽。这样写入截取的uboot.bin的前8KB后,程序开始从0xd0020010处开始运行SRAM中的uboot,这段代码初始化DDR后将从SD卡的49号扇区(这里应该是在uboot源码中写死的,待补充引用)开始的地址区域将整个BL2即整个uboot.bin的数据拷贝到DDR的固定地址0x(待确定)开始的地址处,接着通过远跳转(待补充引用)到DDR中去运行后面的部分。
(短期内不做补充)
(短期内不做补充)
(短期内不做补充)
(短期内不做补充)
(短期内不做补充)
(短期内不做补充)