psci_dt_init()

<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 호출 규약을 의미한다.

 


PSCI 초기화

SMP 아키텍처에서 PSCI 기능이 지원되는 경우 해당 초기화 함수를 동작시켜 각 기능에 해당하는 핸들러 함수를 연결해준다. 다음은 관련된 전역 변수이다.

  • PSCI 동작 시 시큐어 모니터 콜을 호출할 때와 하이퍼 바이저 콜을 호출할 때 호출 함수를 지정한다.
    • Secure Monitor Call
      • 전역 invoke_psci_fn에 __invoke_psci_fn_smc()을 지정한다.
    • Hyper Visor Call
      • 전역 invoke_psci_fn에 __invoke_psci_fn_hvc() 함수를 가리키게 한다.
  • 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_init-2

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_init-1a

 

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 여부를 기록한다.

 

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)

 

참고

댓글 남기기