psci_init()

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

  • 전역 invoke_psci_fn이 __invoke_psci_fn_smc() 또는 __invoke_psci_fn_hvc() 함수를 가리키게 한다.
    • Secure Monitor Call
    • Hyper Visor Call
  • 전역 psci_function_id[]에 device tree에서 읽은 속성 값이 기록된다.
  • 전역 psci_ops의 각 핸들러 함수에 사용 가능한 PSCI 기능 함수를 가리키게 한다.
    • psci version 0.1에서는 지정된 함수만 핸들러 입함수를 등록
    • psci version 0.2에서는 전체 핸들러 함수를 등록하고 추가로 파워 off와 리셋 핸들러도 전역 변수에 대입

psci_init-1a

 

psci_init()

arch/arm/kernel/psci.c

int __init psci_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)
                return -ENODEV;

        init_fn = (psci_initcall_t)matched_np->data;
        return init_fn(np);
}
  • np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np);
    • device tree에서 psci_of_match[]에 있는 디바이스들 중 하나라도 일치되는 노드를 찾아 리턴하고 출력 인수 matched_up에 psci_of_match[]에 있는 of_device_id 구조체 엔트리 중 매치된 엔트리 포인터를 저장한다.
    •  psci_of_match
      • 검색하고자 하는 디바이스명과 초기화 함수들이 담겨있다.
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},
        {}, 
};
  • init_fn = (psci_initcall_t)matched_np->data;
    • 매치되는 디바이스의 초기화 함수 psci_0_1_init() 또는 psci_0_2_init() 함수 포인터가 담긴다.
  • return init_fn(np);
    • 해당 초기화 함수를 동작시킨다.
    • rpi2: PSCI 기능을 사용하지 않는다.
    • 아래 xenvm-4.2 시스템에서 PSCI를 사용하므로 xenvm-4.2.dts와 연결된 초기화 함수 psci_0_1_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()

arch/arm/kernel/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;
}
  • err = get_set_conduit_method(np);
  • if (err) goto out_put_node;
  • 노드에 아래 속성이 있는 경우 각각의 psci_function_id[]에 속성 값을 저장하고 psci_ops 구조체의 각각의 멤버변수에 해당 구동 함수를 연결한다.
    • “cpu_suspend” 속성 -> psci_cpu_suspend()
    • “cpu_off” 속성 -> psci_cpu_off()
    • “cpu_on” 속성 -> psci_cpu_on()
    • “cpu_migrate” 속성 -> psci_migrate()
  • of_node_put(np);
    • 노드 참조 카운터를 감소시킨다.

다음은 전역 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_AFFINITY_INFO,
        PSCI_FN_MIGRATE_INFO_TYPE,
        PSCI_FN_MAX,
};

static u32 psci_function_id[PSCI_FN_MAX];

 

get_set_conduit_method()

arch/arm/kernel/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)) {
                invoke_psci_fn = __invoke_psci_fn_hvc;
        } else if (!strcmp("smc", method)) {
                invoke_psci_fn = __invoke_psci_fn_smc;
        } else {
                pr_warn("invalid \"method\" property: %s\n", method);
                return -EINVAL;
        }
        return 0;
}

psci 노드에 “method” 속성이 발견되면 “hvc” 및 “smc” 속성 값에 대해서는 전역 invoke_psci_fn에 각 함수를 연결시키고 그렇지 않은 경우 에러로 리턴한다.

  • if (of_property_read_string(np, “method”, &method)) {
    • 노드에 “method” 속성이 없는 경우 경고 메시지를 출력하고 에러를 리턴한다.
  • if (!strcmp(“hvc”, method)) { invoke_psci_fn = __invoke_psci_fn_hvc;
    • 속성 값이 “hvc”인 경우 전역 invoke_psci_fn에 invoke_psci_fn_hvc() 함수를 대입한다.
  • } else if (!strcmp(“smc”, method)) { invoke_psci_fn = __invoke_psci_fn_smc;
    • 속성 값이 “smc”인 경우 전역 invoke_psci_fn에 __invoke_psci_smc() 함수를 대입한다.

 

__invoke_psci_fn_hvc() & __invoke_psci_fn_smc()
/* int __invoke_psci_fn_hvc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) */
ENTRY(__invoke_psci_fn_hvc)
        __HVC(0)
        bx      lr
ENDPROC(__invoke_psci_fn_hvc)

/* int __invoke_psci_fn_smc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) */
ENTRY(__invoke_psci_fn_smc)
        __SMC(0)
        bx      lr
ENDPROC(__invoke_psci_fn_smc)

 

__HVC()

arch/arm/include/asm/opcodes-virt.h

#define __HVC(imm16) __inst_arm_thumb32(                                \
        0xE1400070 | (((imm16) & 0xFFF0) << 4) | ((imm16) & 0x000F),    \
        0xF7E08000 | (((imm16) & 0xF000) << 4) | ((imm16) & 0x0FFF)     \
)

 

__SMC()

arch/arm/include/asm/opcodes-sec.h

#define __SMC(imm4) __inst_arm_thumb32(                                 \
        0xE1600070 | (((imm4) & 0xF) << 0),                             \
        0xF7F08000 | (((imm4) & 0xF) << 16)                             \
)

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.