第十五期 MIPS汇编U-Boot从start.S开始《路由器就是开发板》

https://blog.csdn.net/aggresss/article/details/52623118

这一期我们概略说一下MIPS架构并了解一下简单的汇编语句。首先推荐大家阅读一下SDK里DataSheet文件夹下的See MIPS Run Linux.pdf和MIPS.Assembly.Language.pdf两个文档。
MIPS的ISA(Instruction Set Architecture)超级精简,现在市面上大多数的路由器都是基于MIPS架构的,因为当年ARM还没有发力网络终端这个领域,所以现在很多的大型交换和路由设备都是采用并行MIPS架构,当然,随着技术的发展,MIPS和ARM这两种RISC架构的差异性会越来越小,将来评论谁优谁劣还是得看各自的市场运营能力了。MIPS的中文资料相比ARM较小,多数都是和龙芯相关的,希望龙芯能扛起MIPS大旗,真心盼望MIPS能在Imagination手里找到市场突破点。
之前看国外大学课程的《微机原理》视频都是拿MIPS作为实例,这真的是个不错的选择,因为MIPS架构的精简非常适合入门,想想现在国内课程大多数人第一次接触的指令往往都是8086或者8051,要把所有的指令分成1,2,3,n,......种字节,光是MOV和LEA就让我绕了一星期,更别提那各种各样的寻址方式,好麻烦的说。
学习一个新的CPU架构首先要抓住两个关键点:寄存器结构和指令集。每个CPU的架构其实都是在讲述指令是如何操作寄存器完成相应运算的故事,所以我们从寄存器和指令两个方面来大略了解MIPS架构。
MIPS有32个通用寄存器($0-$31),各寄存器的功能及汇编程序中使用约定如下:

下面给以详细说明:
$0:即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。
move $t0,$t1 实际为 add $t0,$0,$t1 使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。

$1:即$at,该寄存器为汇编保留,由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这也是为汇编 保留$at的原因之一。
$2..$3:($v0-$v1)用于子程序的非浮点结果或返回值,对于子程序如何传递参数及如何返回,MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时,编译器通过内存来完成。
$4..$7:($a0-$a3)用来传递前四个参数给子程序,不够的用堆栈。a0-a3和v0-v1以及ra一起来支持子程序/过程调用,分别用以传递参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。
$8..$15:($t0-$t7)临时寄存器,子程序可以使用它们而不用保留。
$16..$23:($s0-$s7)保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复,还包括$fp和$ra),MIPS提供了临时寄存器和保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。
$24..$25:($t8-$t9)同($t0-$t7)
$26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个。 异常(或中断)是一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器,属于CP0寄存器,用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move from system control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返回到造成异常的那条指令处继续执行。MIPS程序员都必须保留两个寄存器$k0和$k1,供操作系统使用。发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个,然后使用jr跳转到造成异常的指令处继续执行。
$28:($gp)为了简化静态数据的访问,MIPS软件保留了一个寄存器:全局指针gp(global pointer,$gp),全局指针,静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可。在编译时,数据须在以gp为基指针的64KB范围内。
$29:($sp)MIPS硬件并不直接支持堆栈,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程序,还是要遵守这个约定的,但这和硬件没有关系。
$30:($fp)GNU MIPS C编译器使用了帧指针(frame pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省了调用和返回开销,但增加了代码生成的复杂性。
$31:($ra)存放返回地址,MIPS有个jal(jump-and-link,跳转并 链接)指令,在跳转到某个地址时,把下一条指令的地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回。

下面列出常见的MIPS指令:

MIPS虽然指令简单,但为了实现一些常用功能而增加了很多宏指令(Macro Instructions)也就是一条宏指令其实在CPU实际运行的是n条指令,这个程序的调试分析带来了一点难度。下面我列出MIPS的常见的宏指令信息:


U-Boot中的入口就是使用MIPS汇编实现的,也就是./cpu/ralink_soc/start.S文件,我们来具体分析一下这个文件,以达到对MIPS架构有一个大体的了解。
Ralink_SDK3.6中的start.S文件中包含很多条件编译信息,所以我做了一些删减,把与rt3052平台无关的信息都删除了,最后缩减成了758行,所以这里提到的行数都以我精简过的start.S文件作为参考。
start.S文件主要实现了4个功能:
(1). 寄存器初始化;
(2). 堆栈初始化;
(3). 将代码导入RAM空间;
(4). 跳转到C程序的初始化函数;
寄存器的初始化一般都有统一格式,例如:
li t5, 0xa0300674
li t6, 0xffffffff
nop
sw t6,0(t5)
nop
上面这5条语句就是实现了将0xa0300674地址的内存的内容改成0xffffffff的功能,可以对照上面两个表格分析一下。
堆栈的初始化从614行开始:

/*
 ********************************************************************
 *   Set up temporary stack.
 ********************************************************************
 */
 
	li	a0, CFG_INIT_SP_OFFSET
	//bal	mips_cache_lock
	nop
 
	li	t0, CFG_SDRAM_BASE + CFG_INIT_SP_OFFSET
	la	sp, 0(t0)
 
 	/* Initialize GOT pointer.
	 */
#if 0
	bal	1f
	nop
	.word	_GLOBAL_OFFSET_TABLE_ - 1f + 4
1:
	move	gp, ra
	lw	t1, 0(ra)
	add	gp, t1
#else
	/* winfred: a easier way to get gp value so that mipsel-linux-as can
	 *   assemble correctly without -mips_allow_branch_to_undefined flag
	 */
	bal	1f
	nop
        .word	_GLOBAL_OFFSET_TABLE_
1:
	lw	gp, 0(ra)
#endif
	
	la	t9, board_init_f
	j	t9
	nop


通过设置gp寄存器的值来实现,在设置好堆栈后就可以跳转执行C函数了,648行就跳转到了board_init_f函数;
 

/*
 ************************************************************************
 * void relocate_code (addr_sp, gd, addr_moni)
 *
 * This "function" does not return, instead it continues in RAM
 * after relocating the monitor code.
 *
 * a0 = addr_sp
 * a1 = gd
 * a2 = destination address
 ************************************************************************
 */
	.globl	relocate_code
	.ent	relocate_code
relocate_code:
	move	sp, a0		/* Set new stack pointer		*/
 
	li	t0, CFG_MONITOR_BASE
	la	t3, in_ram
	lw	t2, -12(t3)	/* t2 <-- uboot_end_data	*/
	move	t1, a2
 
	/*
	 * Fix GOT pointer:
	 *
	 * New GOT-PTR = (old GOT-PTR - CFG_MONITOR_BASE) + Destination Address
	 */
	move	t6, gp
	sub	gp, CFG_MONITOR_BASE
	add	gp, a2			/* gp now adjusted		*/
	sub	t6, gp, t6		/* t6 <-- relocation offset	*/
 
	/*
	 * t0 = source address
	 * t1 = target address
	 * t2 = source end address
	 */
	/* On the purple board we copy the code earlier in a special way
	 * in order to solve flash problems
	 */
#ifndef CONFIG_PURPLE
1:
	lw	t3, 0(t0)
	sw	t3, 0(t1)
	addu	t0, 4
	ble	t0, t2, 1b
	addu	t1, 4			/* delay slot			*/
#endif
 
	/* If caches were enabled, we would have to flush them here.
	 */
 
	/* Jump to where we've relocated ourselves.
	 */
	addi	t0, a2, in_ram - _start
	j	t0
	nop
 
	.word	uboot_end_data
	.word	uboot_end
	.word	num_got_entries
 
in_ram:
	/* Now we want to update GOT.
	 */
	lw	t3, -4(t0)	/* t3 <-- num_got_entries	*/
	addi	t4, gp, 8	/* Skipping first two entries.	*/
	li	t2, 2
1:
	lw	t1, 0(t4)
	beqz	t1, 2f
	add	t1, t6
	sw	t1, 0(t4)
2:
	addi	t2, 1
	blt	t2, t3, 1b
	addi	t4, 4		/* delay slot			*/
 
	/* Clear BSS.
	 */
	lw	t1, -12(t0)	/* t1 <-- uboot_end_data	*/
	lw	t2, -8(t0)	/* t2 <-- uboot_end		*/
	add	t1, t6		/* adjust pointers		*/
	add	t2, t6
 
	sub	t1, 4
1:	addi	t1, 4
	bltl	t1, t2, 1b
	sw	zero, 0(t1)	/* delay slot			*/
 
	move	a0, a1
	la	t9, board_init_r
	j	t9
	move	a1, a2		/* delay slot			*/
 
	.end	relocate_code

relocate_code函数实现将U-Boot代码拷贝到RAM空间,然后跳转到board_init_r函数,此后就脱离start.S文件进行后续的初始化了。
MIPS架构的汇编我就简单的说这些,我个人大约用了3个月的时间才勉强入门,希望大家看过这一期可以对MIPS进行架构进行更深入的学习。

----------------------------------------------------

SDK下载地址:   https://github.com/aggresss/RFDemo
 

猜你喜欢

转载自blog.csdn.net/wxh0000mm/article/details/85609682
今日推荐