UEFI下如何在C语言内嵌入汇编
有两种方法:分别是以内嵌汇编的形式和直接就是汇编的形式存在,内嵌汇编不够灵活,而且代码的可读性和维护性都比较差,强烈推荐第二种方式。
内嵌汇编的形式
__asm__ __volatile__(
".set_noat______\n"
".set_mips64______\n"
"move_$t0, %0_____\n"
"move_$t1, %1_____\n"
"dli__$t2, 0x00000000ffffffff_\n"
"and__$t1,$t2_____\n"
"dsll_$t0,32______\n"
"or_$sp, $t0,$t1____\n"
"jr_%2______\n"
"nop________\n"
".set_at______\n"
: /* No outputs */
:"r"(Sphigh), "r"(Splow),"r"(StrRa)
);
使用__asm__ __volatile()修饰一下,每一行汇编都需要用“”包起来。其中%0代表Sphigh,%1,代表Splow,%2代表StrRa。注意“r”后面跟的参数就是上面使用的%i的值。
直接包含汇编代码
首先新建一个汇编文件,假如是MIPS.S
这里面我们就可以直接写汇编代码,但是代码需要特定的指令修饰:
下面看详细的代码写法:
.global ConfigOnePll
.end ConfigOnePll
//.set noreorder
.set mips3
ConfigOnePll:
//input parameters:
//a0: pll address
//a1: pll value
//a2: div_refc
//output value:
//v0: 0: success; 1: fail.
move t9,ra /*save ra*/
//switch to backup clk
lw t1, 0x4(a0)
li t2, (0x7 << LS7A_PLL_SEL0_OFFSET)
not t2, t2
and t1, t1, t2
sw t1, 0x4(a0)
//power down pll
lw t1, 0x4(a0)
li t2, (1 << LS7A_PLL_PD_OFFSET)
or t1, t1, t2
sw t1, 0x4(a0)
//configure pll parameters
sw a1, 0x0(a0)
//set div_refc
lw t1, 0x4(a0)
li t2, (0x3f << LS7A_PLL_DIV_REFC_OFFSET)
not t2, t2
and t1, t1, t2
or t1, t1, a2
sw t1, 0x4(a0)
//enable pll configure
lw t1, 0x4(a0)
li t2, (1 << LS7A_PLL_SET_OFFSET)
or t1, t1, t2
sw t1, 0x4(a0)
//not bypass pll
lw t1, 0x4(a0)
li t2, (0x1 << LS7A_PLL_BYPASS_OFFSET)
not t2, t2
and t1, t1, t2
sw t1, 0x4(a0)
//power up pll
lw t1, 0x4(a0)
li t2, (0x1 << LS7A_PLL_PD_OFFSET)
not t2, t2
and t1, t1, t2
sw t1, 0x4(a0)
//poll lock signal
li v1, 0x1000
move v0, $0
li t2, (0x1 << LS7A_PLL_LOCK_OFFSET)
1:
lw t1, 0x4(a0)
and t1, t1, t2
subu v1, v1, 1
beqz v1, 1f
nop
beqz t1, 1b
nop
//select pll out
lw t1, 0x4(a0)
li t2, (0x7 << LS7A_PLL_SEL0_OFFSET)
or t1, t1, t2
sw t1, 0x4(a0)
b 2f
nop
1: //PLL lock fail
ori v0, v0, 1
2:
move ra,t9
jr ra
nop
.end ConfigOnePll
上面的代码和下面的C实现的函数是完全等价的:
EFI_STATUS
ConfigOnePll (
IN UINT64 PllBase,
IN UINT32 PllVal,
IN UINT32 DivRefc
)
{
UINT32 i,Val32;
Val32 = Readl(PllBase + 0x4);
Val32 &= ~(0x7 << LS7A_PLL_SEL0_OFFSET);//switch to backup clk
Readl(PllBase + 0x4) = Val32;
Val32 = Readl(PllBase + 0x4);
Val32 |= (1 << LS7A_PLL_PD_OFFSET);//power down pll
Readl(PllBase + 0x4) = Val32;
Val32 = Readl(PllBase + 0x4);
Val32 &= ~(1 << LS7A_PLL_SET_OFFSET);//disable pll configure
Readl(PllBase + 0x4) = Val32;
//configure pll parameters
Readl(PllBase) = PllVal;
Val32 = Readl(PllBase + 0x4);
Val32 = (Val32 & ~(0x3f << LS7A_PLL_DIV_REFC_OFFSET)) | DivRefc;
Readl(PllBase + 0x4) = Val32;
Val32 = Readl(PllBase + 0x4);
Val32 |= (1 << LS7A_PLL_SET_OFFSET);//enable pll configure
Readl(PllBase + 0x4) = Val32;
Val32 = Readl(PllBase + 0x4);
Val32 &= ~(0x1 << LS7A_PLL_BYPASS_OFFSET);//not bypass pll
Readl(PllBase + 0x4) = Val32;
Val32 = Readl(PllBase + 0x4);
Val32 &= ~(0x1 << LS7A_PLL_PD_OFFSET);//power up pll
Readl(PllBase + 0x4) = Val32;
//poll lock signal
i = 0x1000;
do{
Val32 = Readl(PllBase + 0x4);
Val32 &= (0x1 << LS7A_PLL_LOCK_OFFSET);
i--;
if(i== 0)return EFI_INVALID_PARAMETER;
}while(Val32 == 0);
Val32 = Readl(PllBase + 0x4);
Val32 |= (0x7 << LS7A_PLL_SEL0_OFFSET);
Readl(PllBase + 0x4) = Val32;
return EFI_SUCCESS;
}
根据上面的代码可知,函数的格式开头需要使用
.global ConfigOnePll
.end ConfigOnePll
.set noreorder
.set mips3
ConfigOnePll:
修饰,ConfigOnePll:这里才是函数的主体,函数需要使用 .end ConfigOnePll修饰,表示函数的结尾。
这里需要注意一下几点:
(1)MIPS使用a0-a3来作为传参使用,也就是C函数的前4个参数都是用这4个寄存器来传递的,如果传递的参数多余4个,那么就使用堆栈来传递。这里不考虑多余4个参数的汇编代码。所以在调用ConfigOnePll时,传递的参数规则和代码内的实现必须一致。下面是这个函数的调用。
//PIX1, default 38.2MHz for x800x600
if (ConfigOnePll(CONF_PLL4_OFFSET + LS7A_CONFBUS_BASE_ADDR, LS7A_PLL_VALUE(104, 68, 68, 68), 0x4)) {
DbgPrint(EFI_D_INFO, "!!!LS7A PLL4 soft configure fail.\r\n");
ASSERT(0);
return EFI_INVALID_PARAMETER;
}
函数声明就是在调用的C文件中extern这个函数,如下:
extern UINT64
ConfigOnePll (
IN UINT64 PllBase,
IN UINT32 PllVal,
IN UINT32 DivRefc
);
(2)汇编代码中进来的第一条指令就是将ra的值保存在了t9寄存器中,然后在函数快结尾的时候,又将ra的值从t9中读出来给ra,然后跳转到ra的地址去执行函数。保存ra的值,就是为了防止这个函数再调用其他的函数时,就会将ra破坏掉,导致函数无法跳回。这里需要注意,t9在这个函数执行结束之前,是不能再被使用的,如果使用了,ra一开始的返回值也没有了,那么函数也无法跳转回去了。