4.支持psci情況
上面說了pin-table的多核啟動方式,看似很繁瑣,實際上并不復雜,無外乎主處理器喚醒從處理器到指定地址上去執行指令,說他簡單是相對于功能來說的,因為他只是實現了從處理器的啟動,僅此而已,所以,現在社區幾乎很少使用spin-table這種方式,取而代之的是psci,他不僅可以啟動從處理器,還可以關閉,掛起等其他核操作,現在基本上arm64平臺上使用多核啟動方式都是psci。下面我們來揭開他神秘的面紗,其實理解了spin-table的啟動方式,psci并不難( 說白了也是需要主處理器給從處理器一個啟動地址,然后從處理器從這個地址執行指令 ,實際上比這要復雜的多)。
首先,我們先來看下設備樹cpu節點對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節點的詳細說明可以參考內核文檔:Documentation/devicetree/bindings/arm/psci.txt
可以看到現在enable-method 屬性已經是psci,說明使用的多核啟動方式是psci, 下面還有psci節點,用于psci驅動使用,method用于說明調用psci功能使用什么指令,可選有兩個smc和hvc。其實smc, hvc和svc都是從低運行級別向高運行級別請求服務的指令,我們最常用的就是svc指令了,這是實現系統調用的指令。高級別的運行級別會根據傳遞過來的參數來決定提供什么樣的服務。smc是用于陷入el3(安全), hvc用于陷入el2(虛擬化, 虛擬化場景中一般通過hvc指令陷入el2來請求喚醒vcpu), svc用于陷入el1(系統)。
注: 本文只講解smc陷入el3啟動多核的情況 。
下面開始分析源代碼:
我們都知道armv8將異常等級分為el0 - el3,其中,el3為安全監控器,為了實現對它的支持,arm公司設計了一種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代碼運行在EL3, 是實現安全相關的軟件部分固件,其中會為其他特權級別提供服務,也就是說提供了在EL3中服務的手段,我們本文介紹的PSCI的實現就是在這里面,本文不會過多的講解( 注:其實本文只會涉及到atf如何響應服務el1的smc發過來的psci的服務請求,僅此而已,有關ATF(Trustzone)請參考其他資料 )。
那么就開始我們的正題:
下面從源代碼角度分析服務的注冊處理流程:
4.1 el31處理總體流程
atf/bl31/aarch64/bl31_entrypoint.S: //架構相關
bl31_entrypoint
- ?>el3_entrypoint_common
_exception_vectors=runtime_exceptions //設置el3的異常向量表
- ?>bl bl31_early_platform_setup //跳轉到平臺早期設置
- ?>bl bl31_plat_arch_setup //跳轉到平臺架構設置
- ?> bl bl31_main //跳轉到bl31_main atf/bl31/aarch64/bl31_main.c:
- ?>NOTICE("BL31: %s\\n", version_string); //打印版本信息
- ?>NOTICE("BL31: %s\\n", build_message); //打印編譯信息
- ?>bl31_platform_setup //執行平臺設置
- ?> /* Initialize the runtime services e.g. psci. */ 初始化運行時服務 如psci
INFO("BL31: Initializing runtime services\\n") //打印log信息
- ?>runtime_svc_init //調用各種運行時服務歷程
...
4.2 服務注冊
下面的宏是用于注冊運行時服務的接口,每種服務通過它來注冊:
/*
* Convenience macro to declare a service descriptor 定義運行時服務描述符結構的宏
*/
#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch) \\
static const rt_svc_desc_t __svc_desc_ ## _name \\
__section("rt_svc_descs") __used = { \\ //結構放在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段結束
}
...
在標準的運行時服務中將服務初始化和處理函數放到rt_svc_descs段中,供調用。
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函數中,調用每一個通過DECLARE_RT_SVC注冊的服務,其中包括std_svc服務:
for (index = 0; index < RT_SVC_DECS_NUM; index++) {
rt_svc_desc_t *service = &rt_svc_descs[index];
...
rc = service- >init(); //調用每一個注冊的運行時服務的設置函數
...
}
4.3 運行時服務初始化處理
std_svc_setup (主要關注設置psci操作集)
std_svc_setup //services/std_svc/std_svc_setup.c
- >psci_setup //lib/psci/psci_setup.c
- >plat_setup_psci_ops //設置平臺的psci操作 調用平臺的plat_setup_psci_ops函數去設置psci操作 eg:qemu平臺
- >*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 };
可以看到,在遍歷每一個注冊的運行時服務的時候,會導致std_svc_setup調用,其中會做psci操作集的設置,操作集中我們可以看到對核電源的管理的接口如:核上電,下電,掛起等,我們主要關注上電 .pwr_domain_on = qemu_pwr_domain_on ,這個接口當我們主處理器boot從處理器的時候會用到。
4.4 運行時服務觸發和處理
smc指令觸發進入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 */ //獲得唯一的入口編號
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 //獲得服務描述 標識數組
ldrb w15, [x14, x16] //根據唯一的入口編號 找到處理函數的 地址
/*
|* 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
|*/