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一開始的返回值也沒有了,那麼函數也無法跳轉回去了。