4.支持psci情況
上面說了pin-table的多核啟動(dòng)方式,看似很繁瑣,實(shí)際上并不復(fù)雜,無外乎主處理器喚醒從處理器到指定地址上去執(zhí)行指令,說他簡(jiǎn)單是相對(duì)于功能來說的,因?yàn)樗皇菍?shí)現(xiàn)了從處理器的啟動(dòng),僅此而已,所以,現(xiàn)在社區(qū)幾乎很少使用spin-table這種方式,取而代之的是psci,他不僅可以啟動(dòng)從處理器,還可以關(guān)閉,掛起等其他核操作,現(xiàn)在基本上arm64平臺(tái)上使用多核啟動(dòng)方式都是psci。下面我們來揭開他神秘的面紗,其實(shí)理解了spin-table的啟動(dòng)方式,psci并不難( 說白了也是需要主處理器給從處理器一個(gè)啟動(dòng)地址,然后從處理器從這個(gè)地址執(zhí)行指令 ,實(shí)際上比這要復(fù)雜的多)。
首先,我們先來看下設(shè)備樹cpu節(jié)點(diǎn)對(duì)psci的支持:
arch/arm64/boot/dts/xxx.dtsi:
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = < 0x0 >;
enable-method = "psci";
};
psci {
compatible = "arm,psci";
method = "smc";
cpu_suspend = < 0xC4000001 >;
cpu_off = < 0x84000002 >;
cpu_on = < 0xC4000003 >;
};
psci節(jié)點(diǎn)的詳細(xì)說明可以參考內(nèi)核文檔:Documentation/devicetree/bindings/arm/psci.txt
可以看到現(xiàn)在enable-method 屬性已經(jīng)是psci,說明使用的多核啟動(dòng)方式是psci, 下面還有psci節(jié)點(diǎn),用于psci驅(qū)動(dòng)使用,method用于說明調(diào)用psci功能使用什么指令,可選有兩個(gè)smc和hvc。其實(shí)smc, hvc和svc都是從低運(yùn)行級(jí)別向高運(yùn)行級(jí)別請(qǐng)求服務(wù)的指令,我們最常用的就是svc指令了,這是實(shí)現(xiàn)系統(tǒng)調(diào)用的指令。高級(jí)別的運(yùn)行級(jí)別會(huì)根據(jù)傳遞過來的參數(shù)來決定提供什么樣的服務(wù)。smc是用于陷入el3(安全), hvc用于陷入el2(虛擬化, 虛擬化場(chǎng)景中一般通過hvc指令陷入el2來請(qǐng)求喚醒vcpu), svc用于陷入el1(系統(tǒng))。
注: 本文只講解smc陷入el3啟動(dòng)多核的情況 。
下面開始分析源代碼:
我們都知道armv8將異常等級(jí)分為el0 - el3,其中,el3為安全監(jiān)控器,為了實(shí)現(xiàn)對(duì)它的支持,arm公司設(shè)計(jì)了一種firmware叫做ATF(ARM Trusted firmware),下面是atf源碼readme.rst文件的一段介紹:
Trusted Firmware-A (TF-A) provides a reference implementation of secure world
software for `Armv7-A and Armv8-A`_, including a `Secure Monitor`_ executing
at Exception Level 3 (EL3). It implements various Arm interface standards,
such as:
- The `Power State Coordination Interface (PSCI)`_
- Trusted Board Boot Requirements (TBBR, Arm DEN0006C-1)
- `SMC Calling Convention`_
- `System Control and Management Interface (SCMI)`_
- `Software Delegated Exception Interface (SDEI)`_
ATF代碼運(yùn)行在EL3, 是實(shí)現(xiàn)安全相關(guān)的軟件部分固件,其中會(huì)為其他特權(quán)級(jí)別提供服務(wù),也就是說提供了在EL3中服務(wù)的手段,我們本文介紹的PSCI的實(shí)現(xiàn)就是在這里面,本文不會(huì)過多的講解( 注:其實(shí)本文只會(huì)涉及到atf如何響應(yīng)服務(wù)el1的smc發(fā)過來的psci的服務(wù)請(qǐng)求,僅此而已,有關(guān)ATF(Trustzone)請(qǐng)參考其他資料 )。
那么就開始我們的正題:
下面從源代碼角度分析服務(wù)的注冊(cè)處理流程:
4.1 el31處理總體流程
atf/bl31/aarch64/bl31_entrypoint.S: //架構(gòu)相關(guān)
bl31_entrypoint
- ?>el3_entrypoint_common
_exception_vectors=runtime_exceptions //設(shè)置el3的異常向量表
- ?>bl bl31_early_platform_setup //跳轉(zhuǎn)到平臺(tái)早期設(shè)置
- ?>bl bl31_plat_arch_setup //跳轉(zhuǎn)到平臺(tái)架構(gòu)設(shè)置
- ?> bl bl31_main //跳轉(zhuǎn)到bl31_main atf/bl31/aarch64/bl31_main.c:
- ?>NOTICE("BL31: %s\\n", version_string); //打印版本信息
- ?>NOTICE("BL31: %s\\n", build_message); //打印編譯信息
- ?>bl31_platform_setup //執(zhí)行平臺(tái)設(shè)置
- ?> /* Initialize the runtime services e.g. psci. */ 初始化運(yùn)行時(shí)服務(wù) 如psci
INFO("BL31: Initializing runtime services\\n") //打印log信息
- ?>runtime_svc_init //調(diào)用各種運(yùn)行時(shí)服務(wù)歷程
...
4.2 服務(wù)注冊(cè)
下面的宏是用于注冊(cè)運(yùn)行時(shí)服務(wù)的接口,每種服務(wù)通過它來注冊(cè):
/*
* Convenience macro to declare a service descriptor 定義運(yùn)行時(shí)服務(wù)描述符結(jié)構(gòu)的宏
*/
#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch) \\
static const rt_svc_desc_t __svc_desc_ ## _name \\
__section("rt_svc_descs") __used = { \\ //結(jié)構(gòu)放在rt_svc_descs段中
.start_oen = _start, \\
.end_oen = _end, \\
.call_type = _type, \\
.name = #_name, \\
.init = _setup, \\
.handle = _smch }
鏈接腳本中:
bl31/bl31.ld.S:
...
.rodata . : {
__RT_SVC_DESCS_START__ = .; rt_svc_descs段開始
KEEP(*(rt_svc_descs)) //rt_svc_descs段
__RT_SVC_DESCS_END__ = .; rt_svc_descs段結(jié)束
}
...
在標(biāo)準(zhǔn)的運(yùn)行時(shí)服務(wù)中將服務(wù)初始化和處理函數(shù)放到rt_svc_descs段中,供調(diào)用。
services/std_svc/std_svc_setup.c:
DECLARE_RT_SVC(
std_svc,
OEN_STD_START,
OEN_STD_END,
SMC_TYPE_FAST,
std_svc_setup,//初始化
std_svc_smc_handler //處理
);
在runtime_svc_init函數(shù)中,調(diào)用每一個(gè)通過DECLARE_RT_SVC注冊(cè)的服務(wù),其中包括std_svc服務(wù):
for (index = 0; index < RT_SVC_DECS_NUM; index++) {
rt_svc_desc_t *service = &rt_svc_descs[index];
...
rc = service- >init(); //調(diào)用每一個(gè)注冊(cè)的運(yùn)行時(shí)服務(wù)的設(shè)置函數(shù)
...
}
4.3 運(yùn)行時(shí)服務(wù)初始化處理
std_svc_setup (主要關(guān)注設(shè)置psci操作集)
std_svc_setup //services/std_svc/std_svc_setup.c
- >psci_setup //lib/psci/psci_setup.c
- >plat_setup_psci_ops //設(shè)置平臺(tái)的psci操作 調(diào)用平臺(tái)的plat_setup_psci_ops函數(shù)去設(shè)置psci操作 eg:qemu平臺(tái)
- >*psci_ops = &plat_qemu_psci_pm_ops;
208 static const plat_psci_ops_t plat_qemu_psci_pm_ops = {
209 .cpu_standby = qemu_cpu_standby,
210 .pwr_domain_on = qemu_pwr_domain_on,
211 .pwr_domain_off = qemu_pwr_domain_off,
212 .pwr_domain_suspend = qemu_pwr_domain_suspend,
213 .pwr_domain_on_finish = qemu_pwr_domain_on_finish,
214 .pwr_domain_suspend_finish = qemu_pwr_domain_suspend_finish,
215 .system_off = qemu_system_off,
216 .system_reset = qemu_system_reset,
217 .validate_power_state = qemu_validate_power_state,
218 .validate_ns_entrypoint = qemu_validate_ns_entrypoint
219 };
可以看到,在遍歷每一個(gè)注冊(cè)的運(yùn)行時(shí)服務(wù)的時(shí)候,會(huì)導(dǎo)致std_svc_setup調(diào)用,其中會(huì)做psci操作集的設(shè)置,操作集中我們可以看到對(duì)核電源的管理的接口如:核上電,下電,掛起等,我們主要關(guān)注上電 .pwr_domain_on = qemu_pwr_domain_on ,這個(gè)接口當(dāng)我們主處理器boot從處理器的時(shí)候會(huì)用到。
4.4 運(yùn)行時(shí)服務(wù)觸發(fā)和處理
smc指令觸發(fā)進(jìn)入el3異常向量表:
runtime_exceptions //el3的異常向量表
- >sync_exception_aarch64
- >handle_sync_exception
- >smc_handler64
- > |* Populate the parameters for the SMC handler.
|* We already have x0-x4 in place. x5 will point to a cookie (not used
|* now). x6 will point to the context structure (SP_EL3) and x7 will
|* contain flags we need to pass to the handler Hence save x5-x7.
|*
|* Note: x4 only needs to be preserved for AArch32 callers but we do it
|* for AArch64 callers as well for convenience
|*/
stp x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4] //保存x4-x7到棧
stp x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]
/* Save rest of the gpregs and sp_el0*/
save_x18_to_x29_sp_el0
mov x5, xzr //x5清零
mov x6, sp //sp保存在x6
/* Get the unique owning entity number */ //獲得唯一的入口編號(hào)
ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
/* Load descriptor index from array of indices */
adr x14, rt_svc_descs_indices //獲得服務(wù)描述 標(biāo)識(shí)數(shù)組
ldrb w15, [x14, x16] //根據(jù)唯一的入口編號(hào) 找到處理函數(shù)的 地址
/*
|* Restore the saved C runtime stack value which will become the new
|* SP_EL0 i.e. EL3 runtime stack. It was saved in the 'cpu_context'
|* structure prior to the last ERET from EL3.
|*/
ldr x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
/*
|* Any index greater than 127 is invalid. Check bit 7 for
|* a valid index
|*/