1.扭扭捏捏,准备工作都是一大堆,要不来点汇编开开胃
/*
* Register usage:
*
* s0 link versus load offset, used to relocate absolute adresses.
* s1 free
* s2 memory size.
* s3 free.
* s4 Bonito base address.
* s5 dbg.
* s6 sdCfg.
* s7 rasave.
* s8 L3 Cache size.
*/
.set noreorder
.globl _start
.globl start
.globl __main
_start:
start:
.globl stack
stack = start - 0x4000 /* Place PMON stack below PMON start in RAM */
/* NOTE!! Not more that 16 instructions here!!! Right now it's FULL! */
.set push
.set mips64
mfc0 t0, $16, 6
or t0, 0x100
xori t0, 0x100 /*先或再异或,清零第8位*/
mtc0 t0, $16, 6
/* no sw combine */
mfc0 t0, $16, 6
ori t0, 0x200 /*第九位置1*/
mtc0 t0, $16, 6
mfc0 t0, $22
lui t1, 0x0000
//lui t1, 0x8000
or t0, t1, t0 /*或操作一个0,没意义呀 */
mtc0 t0, $22
.set pop
mtc0 zero, COP_0_STATUS_REG /*清零COP_0_STATUS_REG 寄存器*/
mtc0 zero, COP_0_CAUSE_REG /*清零COP_0_CAUSE_REG 寄存器*/
li t0, SR_BOOT_EXC_VEC /* Exception to Boostrap Location 异常向量表*/ //[22]置1
mtc0 t0, COP_0_STATUS_REG /* 将异常向量表写入寄存器 */ //例外向量入口地址启动,BEV置1,采用rom的异常入口
la sp, stack /* 设置堆栈寄存器 ,指向起始代码的下方(地址减小的方向) */
la gp, _gp /* 设置GP寄存器 */
WatchDog_Close /* 这是个宏定义,关闭看门狗 */
/* spi speedup */
li t0, 0xbfe00220
li t1, 0x07
sb t1, 0x4(t0)
/* locate 在下面,约423行的样子。 */
bal locate /* Get current execute address */ /* 跳转到locate ,返回(绝对)地址保存在al寄存器中 */
nop
来了一段汇编,要能看懂汇编,还得学点汇编指令啊。
1.1首先,.开头的都是伪操作,应该用于提示编译器,后面的代码应该如何处理,或者一些声明。
至少你得知道,伪操作,一般不能翻译为一条机器指令,它用来控制编译器的一些行为吧。
比如.global 声明一个全局的名称,让这个名称在别的文件可以使用,相当于c的extern。因为汇编跟c不同,汇编文件中默认所有的名称只能在本文件中使用,(相当于加了static)。如果你要让这个标号全局可见,就必须使用global修饰一下。但这并不会产生机器指令(在最终的可以执行的bin文件中也不会占用任何字节空间),只是在编译的时候能够识别到这个标号而已啦。
前面我们讲过,_start,start是整个程序的入口,那肯定有别的文件(如ld.scripts)需要识别到这个标号,所以这个标号必然要用global修饰一下。
.set 的操作比较多,我也是网上抄一点吧。
.set mipsn。n是一个从0到5的数字,或是数字32或64。1到5,32或64使汇编器从源程序中的这一点开始接受相应ISA级别的指令。 .set mipsn 不仅影响允许使用那些指令,还影响到某些宏如何被扩展。 .set mips0保持原本的ISA级别:这个级别是您通过命令行选项选择的,或者是您的配置的默认值。您可以通过这个特性在32位汇编模式中使用r4000的指令。小心使用这个命令!
命令‘.set mips16’使汇编器进入MIPS 16模式,传统的汇编器不支持这个命令
伪操作 .set mips3 告诉汇编器下面的指令是MIPS IV(64位指令集,兼容32位指令)中的指令。
.set mipsn
n是一个从0到5的数字,或是数字32或64。1到5,32或64使汇编器从源程序中的这一点开始接受相应ISA级别的指令。 .set mipsn 不仅影响允许使用那些指令,还影响到某些宏如何被扩展。
.set mips0保持原本的ISA级别:这个级别是通过命令行选项选择的,或者是配置的默认值。可以通过这个特性在32位汇编模式中使用r4000的指令
.set mips1 下面的指令是R2000/R3000指令,即MIPS I指令集
.set mips2下面的指令是R6000指令,即MIPS II指令集,这个没有产品化的芯片,一般不要使用
.set mips3下面的指令是R4000指令,即MIPS III,第一个支持64位的指令集
.set mips4支持MIPS IV指令集,它是在MIPSIII的基础上增加了一些浮点指令
.set mips5支持MIPS V指令集,在MIPS IV上增加了SIMD指令
另外.set mips32/mips64支持MIPS32/MIPS64指令集
这个牵扯到MIPS的指令集标准历史,1998年MIPS Technologies从SGI公司分离出来之后发布的指令集标准,MIPS32是MIPS II的超集,MIPS64是MIPS IV的超集,还是得回去读“历史”
.set noreorder/reorder
默认汇编器处在reorder的模式下,该模式允许汇编器对指令进行重新排序,以避免流水线堵塞并获得更好的性能,在这种模式下,是不允许在代码中插入 nop指令的。反之,在noreorder模式下,指令的顺序不会被改变也不会对代码进行任何优化。这样做的优点是程序员可以完全控制代码的执行顺序,缺点是必须手工对指令排序,并在分支和加载指令的延迟槽中填上有用的指令或nop指令.比如:
.set noreorder
lw t0, 0(a0)
nop #加载指令延迟槽
sub t0, 1
bne t0, zero, loop
nop #分支指令延迟槽
.set reorder
.set volatile/novolatile
处在volatile区的所有存取指令都不会被移动位置(特别是存取指令之间的相对位置)。这一点对访问内存映射设备的寄存器非常重要。因为对于外围设备而言,读写的次序十分重要。另外对读状态寄存器也非常重要。因为想得到的状态都是最新的。举例来说,如果下列代码没有使用.set volatile,那么汇编器很有可能会对第二个lw指令移到指令的前面,因为这样可以填充第一条lw指令的延迟槽:
.set volatile
lw t0, 0(a0)
sw t0, 0(a1)
lw t0, 4(a0)
.set novalatile
避免流水线堵塞的操作以及其他各种优化措施不受该设定的影响.
noat, nomacro, noreorder: 汇编语言控制操作,为程序员提供了一种方式来关闭汇编器做的一些更复杂的工作,这些工作不总是受欢迎的(相应的没有“no”的名字将该特性重新打开)
.set noat 阻止汇编器将汇编代码翻译成二进制序列依赖at/$1寄存器
.set nomacro 阻止汇编器将一条汇编代码翻译成多条指令
.set noreorder 阻止汇编器调整代码序列来将有用的指令放入分支延迟槽。
.set push --> save all settings (指的是什么设置???指的是现有的.set汇编指示环境)
.set reorder/noreorder --> let/don't let assembler reorder instructions
.set at/noat --> let/don't let assembler use the register $at in instruction aliases (li,la, etc.)
.set pop --> restore saved settings
1.2 用到得汇编指令:
mfc0 从协处理器0的寄存器中读出数据,f表示from
mtc0 把数据写入到协处理器0的寄存器中,t表示to
or 或运算 ,跟c语言按位或运算一致
xori xor表示异或,与c一致,i表示立即数,表示这个操作是与一个数字进行的。
ori 立即数的或运算
lui 加载立即数到高16位,u表示高16位uper,l表示加载(load),从内存读数据到寄存器
li 加载立即数,这是条伪指令,汇编的时候可能需要根据情况自动转为其他指令
la 加载标号对应的地址,a表示address
sb s表示存储(store),表示是从寄存器写入到内存,b表示字节,表示该指令只操作一个字节
bal b表示跳转(branch),bal表示跳转的同时,保存返回地址到ra寄存器。跳出去还可以回来。
上述代码中用到的汇编稍微解释了一下,不懂的继续百度,或者加我qq166781997
1.3 上述代码做了什么?
1.3.1 协处理器0的寄存器需要参考手册《user2.pdf》
mfc0 t0, $16, 6 ,表示从寄存器16读取数据到t0寄存器,后面的数字6,表示第6个 选择寄存器。
这里考虑到的是兼容性问题,当初没有设计那么多协处理器寄存器,但是后来又需要跟多,为了与之前的兼容,做了select这样的设计。
结合后面的语句,或操作,然后异或,那就是要清零寄存器中第8位,然后把这个值写进去。
好,这四句就是关闭这个自动写合并功能。(具体是啥,以后再琢磨吧。)
1.3.2 接下来3句
mfc0 t0, $16, 6
ori t0, 0x200 /*第九位置1*/
mtc0 t0, $16, 6
置1第9位,看pdf:
文档说必须写,那就写吧,这还能咋样???
1.3.3接下来4句
mfc0 t0, $22 //这条是访问寄存器22,后面没有数字,表示select 0
lui t1, 0x0000
//lui t1, 0x8000
or t0, t1, t0 /*或操作一个0,没意义呀 */
mtc0 t0, $22
似乎没得意义????高木及。。。。
1.3.5 接下来几句:
mtc0 zero, COP_0_STATUS_REG /*清零COP_0_STATUS_REG 寄存器*/
mtc0 zero, COP_0_CAUSE_REG /*清零COP_0_CAUSE_REG 寄存器*/
li t0, SR_BOOT_EXC_VEC /*0x40,0000*/ //[22]置1
mtc0 t0, COP_0_STATUS_REG //例外向量入口地址启动,BEV置1,采用rom的异常入口
la sp, stack /* 设置堆栈寄存器 ,指向起始代码的下方(地址减小的方向) */
la gp, _gp /* 设置GP寄存器 */
1.3.6 最后几句吧
WatchDog_Close /* 这是个宏定义,关闭看门狗 */
#define WatchDog_Close \
GPIO_CLEAR_OUTPUT(0x1<<5); \
GPIO_SET_OUTPUT(0x1<<3|0x1<<4); \
GPIO_CLEAR_OUTPUT(0x1<<13); \
/* spi speedup */
li t0, 0xbfe00220
li t1, 0x07
sb t1, 0x4(t0)
bal locate /* Get current execute address */ /* 跳转到locate ,返回(绝对)地址保存在al寄存器中 */
nop
1.4 做了什么?
1.做了一些清理工作,处理器模式,中断这些都清除掉,中断入口应该设置位rom,现在程序还在rom里面。
2.关闭看门狗
3.sp寄存器,和gp寄存器设置好了,但是sp应该还不能用(ddr没有初始化)。跳到下一个函数继续初始化。
在这里我们也看到,修改一个寄存器的基本步骤,是读出这个寄存器的值,通过位操作修改这个值(只改变你需要改变的位),然后把这个值再写入到寄存器。
这是嵌入式开发的常用操作。