<kernel v5.0>
PSCI(Power State Coordination Interface)
절전 인터페이스로 psci가 선택되었을 때 사용 가능하다. psci 기능 호출 시 하이퍼 바이저 또는 시큐어 펌웨어(시큐어 모니터 또는 Trust Firmware)로 관련 요청을 전달하여 수행한다.
- 대부분 psci 방법을 사용하지만 일부는 spin-table 방식을 사용하고, 특정 회사의 경우 별도의 방법을 사용하기도 한다.
- cpu 노드의 enable-method 속성에서 지정한다.
다음 그림은 리눅스 커널이 하이퍼 바이저 또는 시큐어 펌웨어로 psci 호출을 하는 모습을 3 가지 사례로 보여준다.
- 하이퍼 바이저와 시큐어 펌웨어가 동시에 동작하는 경우 커널은 하이퍼 바이저로 보낸다.
PSCI 기능들
다음은 PSCI 기능들이고 버전마다 지원되는 기능이 다르다.
- SMCCC에 관련한 기능도 4가지가 있다.
Function id
- PSCI v0.1
- 위의 기능들에 대해 펑션 id가 제각기 달라 디바이스 트리에서 id 값을 지정해주어야 한다.(implementation defined).
- PSCI v0.2
- 각 기능들에 대해 펑션 id가 고정되어 있다.
- PSCI v0.4
- PSCI v0.2에 두 개의 명령 features와 system suspend 기능을 추가하였다.
호출 후 결과 값들
/* PSCI return values (inclusive of all PSCI versions) */ #define PSCI_RET_SUCCESS 0 #define PSCI_RET_NOT_SUPPORTED -1 #define PSCI_RET_INVALID_PARAMS -2 #define PSCI_RET_DENIED -3 #define PSCI_RET_ALREADY_ON -4 #define PSCI_RET_ON_PENDING -5 #define PSCI_RET_INTERNAL_FAILURE -6 #define PSCI_RET_NOT_PRESENT -7 #define PSCI_RET_DISABLED -8 #define PSCI_RET_INVALID_ADDRESS -9
SMCCC(SMC Calling Convention)
SMC Calling Convention으로 커널이 시큐어 펌웨어 또는 하이퍼 바이저 콜 요청 시 사용할 SMC 및 HVC 호출 규약을 의미한다.
- SMCCC 버전은 1.0과 1.1이 사용되고 있다.
- 참고: arm64: Add SMCCC v1.1 support and CVE-2017-5715 | LWN.net
PSCI 초기화
SMP 아키텍처에서 PSCI 기능이 지원되는 경우 해당 초기화 함수를 동작시켜 각 기능에 해당하는 핸들러 함수를 연결해준다. 다음은 관련된 전역 변수이다.
- PSCI 동작 시 시큐어 모니터 콜을 호출할 때와 하이퍼 바이저 콜을 호출할 때 호출 함수를 지정한다.
- Secure Monitor Call
- 전역 invoke_psci_fn에 __invoke_psci_fn_smc()을 지정한다.
- Hyper Visor Call
- 전역 invoke_psci_fn에 __invoke_psci_fn_hvc() 함수를 가리키게 한다.
- Secure Monitor Call
- PSCI v0.1의 경우 기능 id를 지정하기 위해 디바이스 트리로부터 기능 별로 id를 읽어온다.
- 전역 psci_function_id[]에 device tree에서 읽은 속성 id 값이 기록된다.
- 전역 psci_ops의 각 핸들러 함수에 사용 가능한 PSCI 기능 함수를 가리키게 한다.
- psci v0.1에서는 디바이스 트리에서 지정한 함수만 핸들러 함수를 등록한다.
- psci v0.2 및 v1.0 에서는 전체 핸들러 함수를 등록한다.
psci_dt_init()
drivers/firmware/psci.c
int __init psci_dt_init(void) { struct device_node *np; const struct of_device_id *matched_np; psci_initcall_t init_fn; np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); if (!np || !of_device_is_available(np)) return -ENODEV; init_fn = (psci_initcall_t)matched_np->data; return init_fn(np); }
PSCI 기능을 지원하기 위해 기능별로 시큐어 모니터 콜과 하이퍼 바이저 콜용 함수 준비한다.
- 코드 라인 7~9에서 device tree에서 psci_of_match[]에 있는 디바이스들 중 하나라도 일치되는 노드를 찾아 리턴하고 출력 인수 matched_up에 psci_of_match[]에 있는 of_device_id 구조체 엔트리 중 매치된 엔트리 포인터를 저장한다.
- 코드 라인 11~12에서 psci 버전에 해당하는 초기화 함수를 호출한다.
- psci_0_1_init() 또는 psci_0_2_init() 함수를 가리킨다.
psci_of_match
검색하고자 하는 디바이스 compatible 명과 초기화 함수가 담겨있다. psci v1.0의 경우 초기화 함수는 psci 0.2 초기화 함수를 그대로 사용한다.
drivers/firmware/psci.c
static const struct of_device_id psci_of_match[] __initconst = { { .compatible = "arm,psci", .data = psci_0_1_init}, { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, { .compatible = "arm,psci-1.0", .data = psci_0_2_init}, {}, };
PSCI v0.1
아래 디바이스 트리는 psci v0.1을 사용하고, 이에 대응하는 초기화 함수는 psci_0_2_init() 이다.
arch/arm/boot/dts/xenvm-4.2.dts
* * Based on ARM Ltd. Versatile Express CoreTile Express (single CPU) * Cortex-A15 MPCore (V2P-CA15) * */ /dts-v1/; / { model = "XENVM-4.2"; compatible = "xen,xenvm-4.2", "xen,xenvm"; interrupt-parent = <&gic>; #address-cells = <2>; #size-cells = <2>; chosen { /* this field is going to be adjusted by the hypervisor */ bootargs = "console=hvc0 root=/dev/xvda"; }; cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <0>; }; cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a15"; reg = <1>; }; }; psci { compatible = "arm,psci"; method = "hvc"; cpu_off = <1>; cpu_on = <2>; };
- psci 노드의 compatible이 “arm,psci” 디바이스를 사용한다고 되어 있다.
- “arm,psci”인 경우 초기화 함수는 psci_0_1_init() 함수이다.
다음 그림은 xennvm-4.2 시스템에서 hvc 방식의 PSCI 기능을 사용할 때의 모습을 보여준다.
psci_0_1_init()
drivers/firmware/psci.c
/* * PSCI < v0.2 get PSCI Function IDs via DT. */
static int psci_0_1_init(struct device_node *np) { u32 id; int err; err = get_set_conduit_method(np); if (err) goto out_put_node; pr_info("Using PSCI v0.1 Function IDs from DT\n"); if (!of_property_read_u32(np, "cpu_suspend", &id)) { psci_function_id[PSCI_FN_CPU_SUSPEND] = id; psci_ops.cpu_suspend = psci_cpu_suspend; } if (!of_property_read_u32(np, "cpu_off", &id)) { psci_function_id[PSCI_FN_CPU_OFF] = id; psci_ops.cpu_off = psci_cpu_off; } if (!of_property_read_u32(np, "cpu_on", &id)) { psci_function_id[PSCI_FN_CPU_ON] = id; psci_ops.cpu_on = psci_cpu_on; } if (!of_property_read_u32(np, "migrate", &id)) { psci_function_id[PSCI_FN_MIGRATE] = id; psci_ops.migrate = psci_migrate; } out_put_node: of_node_put(np); return err; }
psci v0.1용 호출 함수(svc 또는 hvc)와 기능들을 지정한다.
- 코드 라인 6~9에서 “method” 속성 값에 “hvc” 또는 “smc”가 지정되어야 한다.
- 코드 라인 13~31에서 아래 속성이 있는 경우 각각의 psci_function_id[]에 읽어들이 id 속성 값을 저장하고, psci_ops 구조체의 각각의 멤버변수에 해당 구동 함수를 연결한다.
- “cpu_suspend” 속성 -> psci_cpu_suspend()
- “cpu_off” 속성 -> psci_cpu_off()
- “cpu_on” 속성 -> psci_cpu_on()
- “cpu_migrate” 속성 -> psci_migrate()
다음 그림은 psci v0.1을 사용하는 경우의 호출 관계이다.
psci_function_id[] 배열
다음은 PSCI v0.1의 기능별로 고정되지 않은 펑션 id를 지정하는 psci_function_id[] 배열이다.
arch/arm/kernel/psci.c
enum psci_function { PSCI_FN_CPU_SUSPEND, PSCI_FN_CPU_ON, PSCI_FN_CPU_OFF, PSCI_FN_MIGRATE, PSCI_FN_MAX, }; static u32 psci_function_id[PSCI_FN_MAX];
get_set_conduit_method()
drivers/firmware/psci.c
static int get_set_conduit_method(struct device_node *np) { const char *method; pr_info("probing for conduit method from DT.\n"); if (of_property_read_string(np, "method", &method)) { pr_warn("missing \"method\" property\n"); return -ENXIO; } if (!strcmp("hvc", method)) { set_conduit(PSCI_CONDUIT_HVC); } else if (!strcmp("smc", method)) { set_conduit(PSCI_CONDUIT_SMC); } else { pr_warn("invalid \"method\" property: %s\n", method); return -EINVAL; } return 0; }
psci 노드에 “method” 속성이 발견되면 “hvc” 및 “smc” 속성 값에 대해서는 전역 invoke_psci_fn에 각 함수를 연결시키고 그렇지 않은 경우 에러로 리턴한다.
- 코드 라인 7~10에서 노드에 “method” 속성이 없는 경우 경고 메시지를 출력하고 에러를 리턴한다.
- 코드 라인 12~13에서 속성 값이 “hvc”인 경우 hvc용 psci 호출 함수를 지정한다.
- 코드 라인 14~19에서 속성 값이 “smc”인 경우 smc용 psci 호출 함수를 지정한다. 그렇지 않은 경우 경고 메시지를 출력한다.
set_conduit()
drivers/firmware/psci.c
static void set_conduit(enum psci_conduit conduit) { switch (conduit) { case PSCI_CONDUIT_HVC: invoke_psci_fn = __invoke_psci_fn_hvc; break; case PSCI_CONDUIT_SMC: invoke_psci_fn = __invoke_psci_fn_smc; break; default: WARN(1, "Unexpected PSCI conduit %d\n", conduit); } psci_ops.conduit = conduit; }
요청 @conduit에 따라 psci 호출 함수를 지정한다.
PSCI v0.2 이상 (include v1.0)
아래 broadcom 사의 northstar2 시스템에서 PSCI 1.0을 사용하고 이에 대응하는 초기화 함수는 psci_0_2_init() 이다.
arch/arm64/boot/dts/broadcom/northstar2/ns2.dtsi
psci { compatible = "arm,psci-1.0"; method = "smc"; };
psci_0_2_init()
drivers/firmware/psci.c
/* * PSCI init function for PSCI versions >=0.2 * * Probe based on PSCI PSCI_VERSION function */
static int __init psci_0_2_init(struct device_node *np) { int err; err = get_set_conduit_method(np); if (err) goto out_put_node; /* * Starting with v0.2, the PSCI specification introduced a call * (PSCI_VERSION) that allows probing the firmware version, so * that PSCI function IDs and version specific initialization * can be carried out according to the specific version reported * by firmware */ err = psci_probe(); out_put_node: of_node_put(np); return err; }
psci v0.2 및 psci v1.0용 호출 함수(svc 또는 hvc)와 기능들을 지정한다.
- 코드 라인 5~8에서 “method” 속성 값에 “hvc” 또는 “smc”가 지정되어야 한다.
- 코드 라인 16에서 psci 버전 및 기능별로 고정된 호출 함수를 지정한다.
다음은 psci v1.0이 디텍트되어 사용됨을 보여주는 출력 로그이다.
[ 0.000000] psci: probing for conduit method from DT. [ 0.000000] psci: PSCIv1.0 detected in firmware. [ 0.000000] psci: Using standard PSCI v0.2 function IDs [ 0.000000] psci: MIGRATE_INFO_TYPE not supported.
psci_probe()
drivers/firmware/psci.c
/* * Probe function for PSCI firmware versions >= 0.2 */
static int __init psci_probe(void) { u32 ver = psci_get_version(); pr_info("PSCIv%d.%d detected in firmware.\n", PSCI_VERSION_MAJOR(ver), PSCI_VERSION_MINOR(ver)); if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) { pr_err("Conflicting PSCI version detected.\n"); return -EINVAL; } psci_0_2_set_functions(); psci_init_migrate(); if (PSCI_VERSION_MAJOR(ver) >= 1) { psci_init_smccc(); psci_init_cpu_suspend(); psci_init_system_suspend(); } return 0; }
psci 버전 및 기능별로 고정된 호출 함수를 지정한다.
- 코드 라인 3~12에서 psci 버전을 알아와서 출력하고 psci v0.2 보다 낮은 경우 -EINVAL 에러로 함수를 빠져나간다.
- 코드 라인 14에서 psci v0.2를 위한 기능별로 호출 함수를 지정한다.
- 코드 라인 16에서 트러스트 펌웨어 OS가 cpu off 시 migration을 지원하는지 여부를 알아와 출력한다.
- 특정 cpu를 off 시 트러스트 펌웨어 OS가 동작하지 못하는 경우를 위해 동작 중인 cpu를 저장해두어야 한다.
- 코드 라인 18~22에서 psci v1.0 이상인 경우 smccc 버전을 알아오고, suspend 기능들을 준비한다.
- SMCCC 버전 기능이 지원되는 경우 SMCCC SMCC 버전을 알아와서 v1.1 여부를 기록한다.
psci_0_2_set_functions()
drivers/firmware/psci.c
static void __init psci_0_2_set_functions(void) { pr_info("Using standard PSCI v0.2 function IDs\n"); psci_ops.get_version = psci_get_version; psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_FN_NATIVE(0_2, CPU_SUSPEND); psci_ops.cpu_suspend = psci_cpu_suspend; psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; psci_ops.cpu_off = psci_cpu_off; psci_function_id[PSCI_FN_CPU_ON] = PSCI_FN_NATIVE(0_2, CPU_ON); psci_ops.cpu_on = psci_cpu_on; psci_function_id[PSCI_FN_MIGRATE] = PSCI_FN_NATIVE(0_2, MIGRATE); psci_ops.migrate = psci_migrate; psci_ops.affinity_info = psci_affinity_info; psci_ops.migrate_info_type = psci_migrate_info_type; arm_pm_restart = psci_sys_reset; pm_power_off = psci_sys_poweroff; }
psci v0.2를 위한 기능별로 호출 함수를 지정한다.
- psci v0.1 호환을 위해 cpu_suspend, cpu_off, cpu_on, migrate 등의 4 가지 기능의 경우 해당 기능 id 값을 지정한다.
- 각 기능에 대한 호출 함수를 psci_ops의 각 콜백 함수에 지정한다.
psci_init_migrate()
drivers/firmware/psci.c
/* * Detect the presence of a resident Trusted OS which may cause CPU_OFF to * return DENIED (which would be fatal). */
static void __init psci_init_migrate(void) { unsigned long cpuid; int type, cpu = -1; type = psci_ops.migrate_info_type(); if (type == PSCI_0_2_TOS_MP) { pr_info("Trusted OS migration not required\n"); return; } if (type == PSCI_RET_NOT_SUPPORTED) { pr_info("MIGRATE_INFO_TYPE not supported.\n"); return; } if (type != PSCI_0_2_TOS_UP_MIGRATE && type != PSCI_0_2_TOS_UP_NO_MIGRATE) { pr_err("MIGRATE_INFO_TYPE returned unknown type (%d)\n", type); return; } cpuid = psci_migrate_info_up_cpu(); if (cpuid & ~MPIDR_HWID_BITMASK) { pr_warn("MIGRATE_INFO_UP_CPU reported invalid physical ID (0x%lx)\n", cpuid); return; } cpu = get_logical_index(cpuid); resident_cpu = cpu >= 0 ? cpu : -1; pr_info("Trusted OS resident on physical CPU 0x%lx\n", cpuid); }
트러스트 펌웨어 OS가 cpu off 시 migration을 지원하는지 여부를 알아와 출력하고, 동작 중인 트러스트 펌웨어 OS의 cpu를 전역 변수 resident_cpu에 기억해둔다.
- 코드 라인 6에서 migrate info type 기능을 호출하여 type을 알아온다.
- 코드 라인 8~11에서 트러스트 펌웨어 OS가 mp를 지원하여 커널에서 migration 요청을 할 필요 없으므로 함수를 빠져나간다.
- 코드 라인 13~16에서 트러스트 펌웨어 OS가 migration을 지원하지 않으므로 에러 메시지를 출력하고 함수를 빠져나간다.
- 코드 라인 18~22에서 트러스트 펌웨어 OS가 알려지지 않은 타입을 반환한 경우 경고 메시지를 출력한다.
- 코드 라인 24~29에서트러스트 펌웨어 OS가 동작하는 물리 cpu 번호를 알아온다.
- 코드 라인 31~34에서 트러스트 펌웨어 OS가 동작하는 논리 cpu 번호를 알아내서 출력한다.
PSCI migrate 타입
커널에서 특정 cpu를 off하기 전에 트러스트 펌웨어 OS의 migration 지원 여부이다. 오렌지 색 항목은 cpu의 off가 가능하다.
- PSCI_0_2_TOS_UP_MIGRATE(0)
- 트러스트 펌웨어 OS가 하나의 코어에서 동작하지만 migration 요청을 지원한다.
- 해당 cpu를 off할 때 트러스트 펌웨어 OS도 해당 cpu에서 동작 중인 경우 cpu를 off하기 전에 migration을 요청해야 한다.
- PSCI_0_2_TOS_UP_NO_MIGRATE(1)
- 트러스트 펌웨어 OS가 하나의 특정 코어에 고정되어 동작하므로 migration 요청을 하면 안된다.
- PSCI_0_2_TOS_MP(2)
- 트러스트 펌웨어 OS가 멀티 코어 시스템에서 동작 가능하므로 migration을 할 필요가 없다.
- PSCI_RET_NOT_SUPPORTED(-1)
- 트러스트 펌웨어 OS가 migration 요청을 지원하지 않는다.
psci_init_smccc()
drivers/firmware/psci.c
static void __init psci_init_smccc(void) { u32 ver = ARM_SMCCC_VERSION_1_0; int feature; feature = psci_features(ARM_SMCCC_VERSION_FUNC_ID); if (feature != PSCI_RET_NOT_SUPPORTED) { u32 ret; ret = invoke_psci_fn(ARM_SMCCC_VERSION_FUNC_ID, 0, 0, 0); if (ret == ARM_SMCCC_VERSION_1_1) { psci_ops.smccc_version = SMCCC_VERSION_1_1; ver = ret; } } /* * Conveniently, the SMCCC and PSCI versions are encoded the * same way. No, this isn't accidental. */ pr_info("SMC Calling Convention v%d.%d\n", PSCI_VERSION_MAJOR(ver), PSCI_VERSION_MINOR(ver)); }
SMCCC 버전 기능이 지원되는 경우 SMCCC SMCC 버전을 알아와서 v1.1 여부를 기록한다.
- Cortex A57/A72인 경우 SMCCC 버전이 1.1이면 BP hardening workaround 처리를 위해 psci 호출을 수행해야 한다.
- 참고
psci_init_cpu_suspend()
drivers/firmware/psci.c
static void __init psci_init_cpu_suspend(void) { int feature = psci_features(psci_function_id[PSCI_FN_CPU_SUSPEND]); if (feature != PSCI_RET_NOT_SUPPORTED) psci_cpu_suspend_feature = feature; }
cpu suspend 기능이 지원되는지 여부를 알아온다.
psci_init_system_suspend()
drivers/firmware/psci.c
static void __init psci_init_system_suspend(void) { int ret; if (!IS_ENABLED(CONFIG_SUSPEND)) return; ret = psci_features(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND)); if (ret != PSCI_RET_NOT_SUPPORTED) suspend_set_ops(&psci_suspend_ops); }
system suspend 기능이 지원되는지 여부를 알아와서 절전 기능에 대한 ops를 psci 방식으로 연결한다.
suspend_set_ops()
kernel/power/suspend.c
/** * suspend_set_ops - Set the global suspend method table. * @ops: Suspend operations to use. */ void suspend_set_ops(const struct platform_suspend_ops *ops) { lock_system_sleep(); suspend_ops = ops; if (valid_state(PM_SUSPEND_STANDBY)) { mem_sleep_states[PM_SUSPEND_STANDBY] = mem_sleep_labels[PM_SUSPEND_STANDBY]; pm_states[PM_SUSPEND_STANDBY] = pm_labels[PM_SUSPEND_STANDBY]; if (mem_sleep_default == PM_SUSPEND_STANDBY) mem_sleep_current = PM_SUSPEND_STANDBY; } if (valid_state(PM_SUSPEND_MEM)) { mem_sleep_states[PM_SUSPEND_MEM] = mem_sleep_labels[PM_SUSPEND_MEM]; if (mem_sleep_default >= PM_SUSPEND_MEM) mem_sleep_current = PM_SUSPEND_MEM; } unlock_system_sleep(); } EXPORT_SYMBOL_GPL(suspend_set_ops);
PSCI 기능 호출
psci_get_version()
drivers/firmware/psci.c
static u32 psci_get_version(void) { return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); }
psci 버전을 알아온다. 결과 값의 하위 16비트에 minor 버전이 담기고, 나머지에 major 버전이 담겨있다.
psci_cpu_suspend()
drivers/firmware/psci.c
static int psci_cpu_suspend(u32 state, unsigned long entry_point) { int err; u32 fn; fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; err = invoke_psci_fn(fn, state, entry_point, 0); return psci_to_linux_errno(err); }
cpu를 절전 모드로 변경하기 위해 psci 호출을 수행한다.
psci_cpu_off()
drivers/firmware/psci.c
static int psci_cpu_off(u32 state) { int err; u32 fn; fn = psci_function_id[PSCI_FN_CPU_OFF]; err = invoke_psci_fn(fn, state, 0, 0); return psci_to_linux_errno(err); }
cpu를 off하기 위해 psci 호출을 수행한다.
psci_cpu_on()
drivers/firmware/psci.c
static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point) { int err; u32 fn; fn = psci_function_id[PSCI_FN_CPU_ON]; err = invoke_psci_fn(fn, cpuid, entry_point, 0); return psci_to_linux_errno(err); }
cpu를 on하기 위해 psci 호출을 수행한다.
psci_affinity_info()
drivers/firmware/psci.c
static int psci_affinity_info(unsigned long target_affinity, unsigned long lowest_affinity_level) { return invoke_psci_fn(PSCI_FN_NATIVE(0_2, AFFINITY_INFO), target_affinity, lowest_affinity_level, 0); }
cpu의 affinity 정보를 알아오기 위해 psci 호출을 수행한다.
psci_migrate()
drivers/firmware/psci.c
static int psci_migrate(unsigned long cpuid) { int err; u32 fn; fn = psci_function_id[PSCI_FN_MIGRATE]; err = invoke_psci_fn(fn, cpuid, 0, 0); return psci_to_linux_errno(err); }
시큐어 펌웨어를 다른 cpu로 migration하기 위해 psci 호출을 수행한다.
psci_migrate_info_type()
drivers/firmware/psci.c
static int psci_migrate_info_type(void) { return invoke_psci_fn(PSCI_0_2_FN_MIGRATE_INFO_TYPE, 0, 0, 0); }
시큐어 펌웨어의 cpu migration 타입 정보를 알아오기 위해 psci 호출을 수행한다.
psci_migrate_info_up_cpu()
drivers/firmware/psci.c
static unsigned long psci_migrate_info_up_cpu(void) { return invoke_psci_fn(PSCI_FN_NATIVE(0_2, MIGRATE_INFO_UP_CPU), 0, 0, 0); }
시큐어 펌웨어가 싱글 코어에서만 동작할 때 현재 동작 중인 물리 cpu 번호를 알아오기 위해 psci 호출을 수행한다.
psci_sys_poweroff()
drivers/firmware/psci.c
static void psci_sys_poweroff(void) { invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); }
시스템을 off 하기 위해 psci 호출을 수행한다.
psci_sys_reset()
drivers/firmware/psci.c
static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) { invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); }
시스템을 reset 하기 위해 psci 호출을 수행한다.
psci_features()
drivers/firmware/psci.c
static int __init psci_features(u32 psci_func_id) { return invoke_psci_fn(PSCI_1_0_FN_PSCI_FEATURES, psci_func_id, 0, 0); }
요청한 psci 기능이 지원되는지 여부를 알아온다.
psci_system_suspend()
drivers/firmware/psci.c
static int psci_system_suspend(unsigned long unused) { return invoke_psci_fn(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), __pa_symbol(cpu_resume), 0, 0); }
시스템을 suspend 하기 위해 psci 호출을 수행한다.
PSCI 호출 API
invoke_psci_fn()
typedef unsigned long (psci_fn)(unsigned long, unsigned long, unsigned long, unsigned long); static psci_fn *invoke_psci_fn;
위의 전역 펑션콜용 변수에는 hvc 및 smc 호출을 위한 함수가 지정된다.
- 첫 번째 인자에는 function id가 지정되고 나머지엔 3 개의 인자가 전달된다.
HVC 호출
__invoke_psci_fn_hvc()
drivers/firmware/psci.c
static unsigned long __invoke_psci_fn_hvc(unsigned long function_id, unsigned long arg0, unsigned long arg1, unsigned long arg2) { struct arm_smccc_res res; arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); return res.a0; }
smccc를 통해 하이퍼바이저를 위해 psci 기능을 호출하는데 8개의 인자를 전달할 때 나머지 4개는 0으로 채워 전달한다.
arm_smccc_hvc()
include/linux/arm-smccc.h
#define arm_smccc_hvc(...) __arm_smccc_hvc(__VA_ARGS__, NULL)
__arm_smccc_hvc()
include/linux/arm-smccc.h
/** * __arm_smccc_hvc() - make HVC calls * @a0-a7: arguments passed in registers 0 to 7 * @res: result values from registers 0 to 3 * @quirk: points to an arm_smccc_quirk, or NULL when no quirks are required. * * This function is used to make HVC calls following SMC Calling * Convention. The content of the supplied param are copied to registers 0 * to 7 prior to the HVC instruction. The return values are updated with * the content from register 0 to 3 on return from the HVC instruction. An * optional quirk structure provides vendor specific behavior. */
asmlinkage void __arm_smccc_hvc(unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6, unsigned long a7, struct arm_smccc_res *res, struct arm_smccc_quirk *quirk);
arch/arm64/kernel/smccc-call.S
/* * void arm_smccc_hvc(unsigned long a0, unsigned long a1, unsigned long a2, * unsigned long a3, unsigned long a4, unsigned long a5, * unsigned long a6, unsigned long a7, struct arm_smccc_res *res, * struct arm_smccc_quirk *quirk) */
ENTRY(__arm_smccc_hvc) SMCCC hvc ENDPROC(__arm_smccc_hvc) EXPORT_SYMBOL(__arm_smccc_hvc)
SMCCC 매크로
.macro SMCCC instr .cfi_startproc \instr #0 ldr x4, [sp] stp x0, x1, [x4, #ARM_SMCCC_RES_X0_OFFS] stp x2, x3, [x4, #ARM_SMCCC_RES_X2_OFFS] ldr x4, [sp, #8] cbz x4, 1f /* no quirk structure */ ldr x9, [x4, #ARM_SMCCC_QUIRK_ID_OFFS] cmp x9, #ARM_SMCCC_QUIRK_QCOM_A6 b.ne 1f str x6, [x4, ARM_SMCCC_QUIRK_STATE_OFFS] 1: ret .cfi_endproc .endm
SMC 호출
__invoke_psci_fn_smc()
drivers/firmware/psci.c
static unsigned long __invoke_psci_fn_smc(unsigned long function_id, unsigned long arg0, unsigned long arg1, unsigned long arg2) { struct arm_smccc_res res; arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); return res.a0; }
smccc를 통해 시큐어 펌웨어를 위해 psci 기능을 호출하는데 8개의 인자를 전달할 때 나머지 4개는 0으로 채워 전달한다.
arm_smccc_smc()
include/linux/arm-smccc.h
#define arm_smccc_smc(...) __arm_smccc_smc(__VA_ARGS__, NULL)
__arm_smccc_smc()
include/linux/arm-smccc.h
/** * __arm_smccc_smc() - make SMC calls * @a0-a7: arguments passed in registers 0 to 7 * @res: result values from registers 0 to 3 * @quirk: points to an arm_smccc_quirk, or NULL when no quirks are required. * * This function is used to make SMC calls following SMC Calling Convention. * The content of the supplied param are copied to registers 0 to 7 prior * to the SMC instruction. The return values are updated with the content * from register 0 to 3 on return from the SMC instruction. An optional * quirk structure provides vendor specific behavior. */
asmlinkage void __arm_smccc_smc(unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6, unsigned long a7, struct arm_smccc_res *res, struct arm_smccc_quirk *quirk);
arch/arm64/kernel/smccc-call.S
/* * void arm_smccc_smc(unsigned long a0, unsigned long a1, unsigned long a2, * unsigned long a3, unsigned long a4, unsigned long a5, * unsigned long a6, unsigned long a7, struct arm_smccc_res *res, * struct arm_smccc_quirk *quirk) */
ENTRY(__arm_smccc_smc) SMCCC smc ENDPROC(__arm_smccc_smc) EXPORT_SYMBOL(__arm_smccc_smc)
참고
- DTB (of API) | 문c
- POWER STATE COORDINATION INTERFACE (PSCI) | arm – 다운로드
- Firmware interfaces for mitigating cache speculation vulnerabilities System Software on Arm Developer (2018) | arm – 다운로드 pdf
- SMC CALLING CONVENTION System Software on ARM® Platforms (2016) | arm – 다운로드 pdf
- Power State Coordination Interface (PSCI) | kernel.org