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)

 

참고

arm_dt_init_cpu_maps()

smp_setup_processor_id()에서 로지컬 cpu id 배열을 구성하였었는데 이 함수에서 다시 DTB와 비교하여 재 구성하고 특정 SMP 아키텍처의 SMP 핸들러가 준비되어 있는 경우 전역 smp_ops를 설정 한다.

  • “/cpu” 노드에서 읽은 reg 속성 값 순서대로 로지컬 맵을 구성하되 예외로 현재 부팅된 물리 cpu id는 로지컬  cpu id 0으로 구성한다.

arm_dt_init_cpu_maps-1a

 

arm_dt_init_cpu_maps()

arch/arm/kernel/devtree.c

/*
 * arm_dt_init_cpu_maps - Function retrieves cpu nodes from the device tree
 * and builds the cpu logical map array containing MPIDR values related to
 * logical cpus
 *
 * Updates the cpu possible mask with the number of parsed cpu nodes
 */
void __init arm_dt_init_cpu_maps(void)
{
        /*
         * Temp logical map is initialized with UINT_MAX values that are
         * considered invalid logical map entries since the logical map must
         * contain a list of MPIDR[23:0] values where MPIDR[31:24] must
         * read as 0.
         */
        struct device_node *cpu, *cpus;
        int found_method = 0;
        u32 i, j, cpuidx = 1;
        u32 mpidr = is_smp() ? read_cpuid_mpidr() & MPIDR_HWID_BITMASK : 0;

        u32 tmp_map[NR_CPUS] = { [0 ... NR_CPUS-1] = MPIDR_INVALID };
        bool bootcpu_valid = false;
        cpus = of_find_node_by_path("/cpus");

        if (!cpus)
                return;

        for_each_child_of_node(cpus, cpu) {
                u32 hwid;

                if (of_node_cmp(cpu->type, "cpu"))
                        continue;

                pr_debug(" * %s...\n", cpu->full_name);
                /*
                 * A device tree containing CPU nodes with missing "reg"
                 * properties is considered invalid to build the
                 * cpu_logical_map.
                 */
                if (of_property_read_u32(cpu, "reg", &hwid)) {
                        pr_debug(" * %s missing reg property\n",
                                     cpu->full_name);
                        return;
                }

                /*
                 * 8 MSBs must be set to 0 in the DT since the reg property
                 * defines the MPIDR[23:0].
                 */
                if (hwid & ~MPIDR_HWID_BITMASK)
                        return;

                /*
                 * Duplicate MPIDRs are a recipe for disaster.
                 * Scan all initialized entries and check for
                 * duplicates. If any is found just bail out.
                 * temp values were initialized to UINT_MAX
                 * to avoid matching valid MPIDR[23:0] values.
                 */
                for (j = 0; j < cpuidx; j++)
                        if (WARN(tmp_map[j] == hwid, "Duplicate /cpu reg "
                                                     "properties in the DT\n"))
                                return;

                /*
                 * Build a stashed array of MPIDR values. Numbering scheme
                 * requires that if detected the boot CPU must be assigned
                 * logical id 0. Other CPUs get sequential indexes starting
                 * from 1. If a CPU node with a reg property matching the
                 * boot CPU MPIDR is detected, this is recorded so that the
                 * logical map built from DT is validated and can be used
                 * to override the map created in smp_setup_processor_id().
                 */
                if (hwid == mpidr) {
                        i = 0;
                        bootcpu_valid = true;
                } else {
                        i = cpuidx++;
                }

                if (WARN(cpuidx > nr_cpu_ids, "DT /cpu %u nodes greater than "
                                               "max cores %u, capping them\n",
                                               cpuidx, nr_cpu_ids)) {
                        cpuidx = nr_cpu_ids;
                        break;
                }

                tmp_map[i] = hwid;

                if (!found_method)
                        found_method = set_smp_ops_by_method(cpu);
        }

        /*
         * Fallback to an enable-method in the cpus node if nothing found in
         * a cpu node.
         */
        if (!found_method)
                set_smp_ops_by_method(cpus);

        if (!bootcpu_valid) {
                pr_warn("DT missing boot CPU MPIDR[23:0], fall back to default cpu_logical_map\n");
                return;
        }

        /*
         * Since the boot CPU node contains proper data, and all nodes have
         * a reg property, the DT CPU list can be considered valid and the
         * logical map created in smp_setup_processor_id() can be overridden
         */
        for (i = 0; i < cpuidx; i++) {
                set_cpu_possible(i, true);
                cpu_logical_map(i) = tmp_map[i];
                pr_debug("cpu logical map 0x%x\n", cpu_logical_map(i));
        }
}
  • u32 mpidr = is_smp() ? read_cpuid_mpidr() & MPIDR_HWID_BITMASK : 0;
    • mpidr에 cpu 물리 id를 읽어온다.
      • SMP의 경우 MPIDR의 lsb 24bits를 읽어오고 UP의 경우 0으로 한다.
    • MPIDR_HWID_BITMASK=0xFFFFFF
  •  u32 tmp_map[NR_CPUS] = { [0 … NR_CPUS-1] = MPIDR_INVALID };
    • 임시 tmp_map[]의 각 값을 MPIDR_INVALID(0xff00_0000)로 초기화한다.
  • cpus = of_find_node_by_path(“/cpus”);
    • “/cpus” 노드를 찾아 발견되지 않으면 함수를 빠져나간다.
  •  for_each_child_of_node(cpus, cpu) {
    • “/cpus” 노드의 서브 노드들로 루프를 돈다.
  • if (of_node_cmp(cpu->type, “cpu”)) continue;
    • 노드 타입이 “cpu”가 아닌 경우 skip
  •  if (of_property_read_u32(cpu, “reg”, &hwid)) {
    • “reg” 속성이 없는 경우 디버그 메시지를 출력하고 함수를 빠져나간다.
  • if (hwid & ~MPIDR_HWID_BITMASK) return;
    • hwid의 msb 8bits가 값이 있는 경우 함수를 빠져나간다.
  • for (j = 0; j < cpuidx; j++) if (WARN(tmp_map[j] == hwid, “Duplicate /cpu reg properties in the DT\n”)) return;
    • “/cpu” 노드의 reg 속성  값이 중복된 경우 에러 메시지를 출력하고 함수를 빠져나간다.
  • if (hwid == mpidr) { i = 0; bootcpu_valid = true;
    • DTB에서 읽은 hwid와 MPIDR[bit23:0] 값을 비교하여 같은 경우 현재 부팅되어 진행중인 물리 cpu인 경우 i=0, bootcpu_valid=true로 대입한다.
  • } else { i = cpuidx++; }
    • 매치되지 않으면 cpuidx를 1 증가시킨다.
  • if (WARN(cpuidx > nr_cpu_ids, “DT /cpu %u nodes greater than max cores %u, capping them\n”, cpuidx, nr_cpu_ids)) { cpuidx = nr_cpu_ids; break; }
    • cpuidx가 nr_cpu_ids를 초과한 경우 에러 메시지를 출력하고 cpuidx = nr_cpu_ids로 변경하고 루프를 빠져나간다.
  • tmp_map[i] = hwid;
    • tmp_map[i]에 물리 cpu id를 대입한다.
  • if (!found_method) found_method = set_smp_ops_by_method(cpu);
    • 처음 이곳을 수행할 때만 현재 cpu 노드에 대해 set_smp_ops_by_method()를 수행한다.
    • rpi2의 경우 smp_ops를 사용하지 않는다.
  • if (!found_method) set_smp_ops_by_method(cpus);
    • found_method가 false인  경우 set_smp_ops_by_method() 함수를 “/cpus” 노드에 대해 수행한다.
  • if (!bootcpu_valid) {
    • bootcpu를 DTB에서 발견하지 못한 경우 에러 메시지를 출력하고 함수를 빠져나간다.
  • for (i = 0; i < cpuidx; i++) {
    • cpuidx 만큼 루프를 돈다.
  • set_cpu_possible(i, true);
    • i 논리 cpu 번호에 대해 possible 비트를 true로 설정한다.
  • cpu_logical_map(i) = tmp_map[i];
    • i 논리 cpu 번호에 물리 cpu id를 저장한다.
      • __cpu_logical_map[]

 

set_smp_ops_by_method()

arch/arm/kernel/devtree.c

static int __init set_smp_ops_by_method(struct device_node *node)
{
        const char *method;
        struct of_cpu_method *m = __cpu_method_of_table;

        if (of_property_read_string(node, "enable-method", &method))
                return 0;

        for (; m->method; m++)
                if (!strcmp(m->method, method)) {
                        smp_set_ops(m->ops);
                        return 1;
                }

        return 0;
}

특정 SMP 아키텍처가 ops 핸들러가 구성된 경우 전역 변수 smp_ops가 이를 가리키게 한다.

  • struct of_cpu_method *m = __cpu_method_of_table;
    • 전역 __cpu_method_of_table은 특정 SMP 아키텍처에서 CPU_METHOD_DECLARE() 함수를 사용하여 추가될 수 있다.
  • if (of_property_read_string(node, “enable-method”, &method)) return 0;
    • 현재 노드의 “enable-method” 속성이 발견되지 않으면 함수를 빠져나간다.
  • for (; m->method; m++)
    • __cpu_method_of_table의 시작부터 끝까지
      • 테이블의 마지막은 m->method가 null이다.
  • if (!strcmp(m->method, method)) { smp_set_ops(m->ops); return 1; }
    • 테이블에 있는 method 문자열과 “enable-method” 속성 값이 같은 경우 ops 설정을 위해 smp_set_ops() 함수를 호출하여 전역 변수 smp_ops를 설정하고 결과 값이 1인채로 리턴한다.

 

arch/arm/boot/dts/hip01-ca9x2.dts

/* First 8KB reserved for secondary core boot */
/memreserve/ 0x80000000 0x00002000;

#include "hip01.dtsi"

/ {
        model = "Hisilicon HIP01 Development Board";
        compatible = "hisilicon,hip01-ca9x2", "hisilicon,hip01";

        cpus {
                #address-cells = <1>;
                #size-cells = <0>;
                enable-method = "hisilicon,hip01-smp";

                cpu@0 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a9";
                        reg = <0>;
                };

                cpu@1 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a9";
                        reg = <1>;
                };
        };
  • “/cpus” 노드의 enable-method 속성 값에 대응하는 디바이스 소스는 다음을 참고한다.
    • enable-arch/arm/mach-hisi/platsmp.c

 

smp_set_ops()

arch/arm/kernel/smp.c

static struct smp_operations smp_ops;

void __init smp_set_ops(struct smp_operations *ops)
{
        if (ops)
                smp_ops = *ops;
};
  • 전역  smp_ops에 인수로 주어진 ops를 대입한다.

 

 

CPU_METHOD_OF_DECLARE()

arch/arm/include/asm/smp.h

#define CPU_METHOD_OF_DECLARE(name, _method, _ops)                      \
        static const struct of_cpu_method __cpu_method_of_table_##name  \
                __used __section(__cpu_method_of_table)                 \
                = { .method = _method, .ops = _ops }
  • 위 매크로는 rpi에서 사용하지 않으므로 아래 화일에서 선언된 소스로 추적을 해본다.

arch/arm/mach-hisi/platsmp.c

CPU_METHOD_OF_DECLARE(hip01_smp, "hisilicon,hip01-smp", &hip01_smp_ops);
  • 다음 문장과 같이 구조체 데이터가 만들어진다.
    • static const struct of_cpu_method __cpu_method_of_table_hip01_smp __used __section(__cpu_method_of_table) = { .method = “hisilicon,hip01-smp”, .ops = &hip01_smp_ops )

arch/arm/mach-hisi/platsmp.c

struct smp_operations hip01_smp_ops __initdata = { 
        .smp_prepare_cpus       = hisi_common_smp_prepare_cpus,
        .smp_boot_secondary     = hip01_boot_secondary,
};
  • hip01_smp 시스템의 ops는 두 개의 함수가 연결되어 있음을 알 수 있다.

 

CPU_METHOD_OF_TABLES()

include/asm-generic/vmlinux.lds.h

#define CPU_METHOD_OF_TABLES()  OF_TABLE(CONFIG_SMP, cpu_method)
  • CONFIG_SMP=y로 설정된 경우 각각의 호출되는 매크로를 통해 따라가 본다.
    • “#defind CONFIG_SMP 1″이 include/generated/autoconf.h 화일에 자동 생성되어 있다.

 

#define OF_TABLE(cfg, name)     __OF_TABLE(config_enabled(cfg), name)
  • __OF_TABLE(config_enabled(1), cpu_method)

 

#define __OF_TABLE(cfg, name)   ___OF_TABLE(cfg, name)
  • ___OF_TABLE(1, cpu_method)

 

#define ___OF_TABLE(cfg, name)  _OF_TABLE_##cfg(name)
  • _OF_TABLE_1(cpu_method)

 

#define _OF_TABLE_0(name)
#define _OF_TABLE_1(name)                                               \
        . = ALIGN(8);                                                   \
        VMLINUX_SYMBOL(__##name##_of_table) = .;                        \
        *(__##name##_of_table)                                          \
        *(__##name##_of_table_end)
  • OF_TABLE_0(cpu_method)의 경우 아무런 동작을 하지 않는다.
  • OF_TABLE_1(cpu_method)의 경우 다음과 같은 문장을 만들어낸다.
    • . = ALIGN(8) \
    • __cpu_method_of_table = .; \
    • *(__cpu_method_of_table) \
    • *(__cpu_method_of_table_end)

 

/* init and exit section handling */
#define INIT_DATA                                                       \
        *(.init.data)                                                   \
        MEM_DISCARD(init.data)                                          \
        KERNEL_CTORS()                                                  \
        MCOUNT_REC()                                                    \
        *(.init.rodata)                                                 \
        FTRACE_EVENTS()                                                 \
        TRACE_SYSCALLS()                                                \
        KPROBE_BLACKLIST()                                              \
        MEM_DISCARD(init.rodata)                                        \
        CLK_OF_TABLES()                                                 \
        RESERVEDMEM_OF_TABLES()                                         \
        CLKSRC_OF_TABLES()                                              \
        IOMMU_OF_TABLES()                                               \
        CPU_METHOD_OF_TABLES()                                          \
        KERNEL_DTB()                                                    \
        IRQCHIP_OF_MATCH_TABLE()                                        \
        EARLYCON_OF_TABLES()
  • 위 INIT_DATA의 16번째 줄에 CPU_METHOD_OF_TABLES()가 포함되어 있다.
    • __cpu_method_of_table이 위치한다.

 

static const struct of_cpu_method __cpu_method_of_table_sentinel
        __used __section(__cpu_method_of_table_end);

위의 빈 구조체 하나가 __cpu_method_of_table_end 테이블의 마지막에 들어간다.

  • 전체 검색 시 멤버 변수 method가 null인 경우 테이블의 마지막임을 알린다.

 

config_enabled()

include/linux/kconfig.h

/*
 * Getting something that works in C and CPP for an arg that may or may
 * not be defined is tricky.  Here, if we have "#define CONFIG_BOOGER 1"
 * we match on the placeholder define, insert the "0," for arg1 and generate
 * the triplet (0, 1, 0).  Then the last step cherry picks the 2nd arg (a one).
 * When CONFIG_BOOGER is not defined, we generate a (... 1, 0) pair, and when
 * the last step cherry picks the 2nd arg, we get a zero.
 */
#define __ARG_PLACEHOLDER_1 0,
#define config_enabled(cfg) _config_enabled(cfg)
#define _config_enabled(value) __config_enabled(__ARG_PLACEHOLDER_##value)
#define __config_enabled(arg1_or_junk) ___config_enabled(arg1_or_junk 1, 0)
#define ___config_enabled(__ignored, val, ...) val
  • 이 매크로는 커널 옵션 설정에 따라 1과 0으로 만들어진다.
    • CONFIG_SMP=y 또는 CONFIG_SMP=m -> 1
    • CONFIG_SMP= -> 0
  • config_enabled(CONFIG_SMP)를 하는 경우 다음의 순서를 따른 후에 결과가 1이만들어진다.
    • _config_enabled(1)
    • __config_enabled(__ARG_PLACEHOLDER_1)
    • ___config_enabled(0, 1, 0)
    • 1

 

구조체

smp_operations 구조체

arch/arm/include/asm/smp.h

struct smp_operations {
#ifdef CONFIG_SMP
        /*
         * Setup the set of possible CPUs (via set_cpu_possible)
         */
        void (*smp_init_cpus)(void);
        /*
         * Initialize cpu_possible map, and enable coherency
         */
        void (*smp_prepare_cpus)(unsigned int max_cpus);

        /*
         * Perform platform specific initialisation of the specified CPU.
         */
        void (*smp_secondary_init)(unsigned int cpu);
        /*
         * Boot a secondary CPU, and assign it the specified idle task.
         * This also gives us the initial stack to use for this CPU.
         */
        int  (*smp_boot_secondary)(unsigned int cpu, struct task_struct *idle);
#ifdef CONFIG_HOTPLUG_CPU
        int  (*cpu_kill)(unsigned int cpu);
        void (*cpu_die)(unsigned int cpu);
        int  (*cpu_disable)(unsigned int cpu);
#endif
#endif
};
  • 특정 SMP 아키텍처는 별도로 위의 구조체를 사용하여 핸들러 함수들을 지정할 수 있다.

 

of_cpu_method 구조체

arch/arm/include/asm/smp.h

struct of_cpu_method {
        const char *method;
        struct smp_operations *ops;
};
  • method
    • “/cpus” 노드의 enable-method 속성 값
      • 예) “hisilicon,hip01-smp”;
  • ops
    • 해당 SMP 머신의 각 핸들러가 속해있는 smp_operations 구조체를 가리킨다.

 

 

참고

unflatten_device_tree()

<kernel v5.10>

디바이스 트리(FDT) -> Expanded 포맷으로 변환

  • device_node와 property 구조체를 사용하여 트리 구조로 각 노드와 속성을 연결한다.
  • 기존에 사용하던 DTB 바이너리들도 문자열등을 그대로 사용하므로 삭제되지 않고 유지된다.

 

노드 명

다음 디바이스 트리를 보고 3 가지 노드 명 분류를 알아본다.

  • Full path 노드명
  • Compact 노드명
  • Alias 명

arch/arm64/boot/dts/broadcom/northstar2/ns2.dtsi

/ {
        compatible = "brcm,ns2";
        interrupt-parent = <&gic>;
        #address-cells = <2>;
        #size-cells = <2>;

        cpus {
                #address-cells = <2>;
                #size-cells = <0>;

                A57_0: cpu@0 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a57", "arm,armv8";
                        reg = <0 0>;
                        enable-method = "psci";
                        next-level-cache = <&CLUSTER0_L2>;
                };
Full path 노드명
  • 디렉토리 구조로 표현한다.
    • /cpus/cpu@0
Compact 노드명
Alias 명
  • Compact 노드명 앞에 alias 명을 둘 수 있다.
    • A57_0

 


FDT -> Expanded Format으로 변환 수행

워드(4바이트) 단위로 정렬된 DTB를 unflatten_device_tree( ) 함수를 통해 unflatten 과정으로 변환하면 각 노드는 device_node 구조체로 변환된다. 전역 of_root 노드가 루트 노드를 가리킨다. 각 노드에 있는 속성들도 property 구조체로 변환되어 해당 노드에 등록된다. 이렇게 바이너리 형태로 존재하다가 device_node 구조체와 property 구조체를 할당받아 트리 형태로 구성된 것을 확장 포맷(expanded format)이라고 부른다.
노드와 속성은 of_로 시작되는 API에 의해 관리되어 사용한다. unflatten된 구조체들은 슬랩 캐시 할당자에서 할당받아 만들어진다.

 

unflatten_device_tree()

drivers/of/fdt.c

/**
 * unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens the device-tree passed by the firmware, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 */
void __init unflatten_device_tree(void)
{
        __unflatten_device_tree(initial_boot_params, NULL, &of_root,
                                early_init_dt_alloc_memory_arch, false);

        /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
        of_alias_scan(early_init_dt_alloc_memory_arch);

        unittest_unflatten_overlay_base();
}

DTB를 확장 포맷으로 변환하고 관련 전역 변수가 적절한 노드를 가리키도록 초기화한다.

  • 코드 라인 3~4에서 4바이트 단위의 바이너리로 구성된 DTB를 파싱하여 확장 포맷으로 변환한 후 of_root 전역 변수가 가리키게 한다.
  • 코드 라인 7에서 전역 aliases_lookup 리스트에 alias_prop들을 추가한다.
    • 전역 변수 of_aliases가 “/aliases” 노드를 가리키도록 설정한다.
    • 전역 변수 of_chosen이 “/chosen” 노드를 가리키도록 설정한다.
    • 전역 변수 of_stdout을 “/chosen” 노드의 “stdout-path” 속성 값에 대응하는 노드로 설정한다.

 

다음 그림과 같이 주요 노드들은 of_로 시작되는 전역 변수가 가리키는 것을 보여준다.

of_alias_scan-1d

  • of_stdout
    • /soc/uart@7e201000 노드를 가리킨다.
    • /chosen 노드의 stdout-path가 가리키는 노드가 출력 디바이스로 사용된다.
  •  of_aliases
    • /aliases 노드를 가리킨다.
  •  aliases_lookup
    • /aliases 노드에 담겨있는 모든 속성들을 alias_prop 구조체 형태로 변환한 후 그 들이 연결되어 있다.
  •  of_root
    • 루트 노드를 가리킨다.
  •  of_chosen
    • /chosen 노드를 가리킨다.
  •  of_stdout_option
    • 출력 노드명에 사용된 옵션(‘:’문자로 시작하는) 문자열이 저장된다.

 

__unflatten_device_tree()

drivers/of/fdt.c

/**
 * __unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens a device-tree, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 * @blob: The blob to expand
 * @dad: Parent device node
 * @mynodes: The device_node tree created by the call
 * @dt_alloc: An allocator that provides a virtual address to memory
 * for the resulting tree
 * @detached: if true set OF_DETACHED on @mynodes
 *
 * Returns NULL on failure or the memory chunk containing the unflattened
 * device tree on success.
 */
void *__unflatten_device_tree(const void *blob,
                              struct device_node *dad,
                              struct device_node **mynodes,
                              void *(*dt_alloc)(u64 size, u64 align),
                              bool detached)
{
        int size;
        void *mem;

        pr_debug(" -> unflatten_device_tree()\n");

        if (!blob) {
                pr_debug("No device tree pointer\n");
                return NULL;
        }

        pr_debug("Unflattening device tree:\n");
        pr_debug("magic: %08x\n", fdt_magic(blob));
        pr_debug("size: %08x\n", fdt_totalsize(blob));
        pr_debug("version: %08x\n", fdt_version(blob));

        if (fdt_check_header(blob)) {
                pr_err("Invalid device tree blob header\n");
                return NULL;
        }

        /* First pass, scan for size */
        size = unflatten_dt_nodes(blob, NULL, dad, NULL);
        if (size < 0)
                return NULL;

        size = ALIGN(size, 4);
        pr_debug("  size is %d, allocating...\n", size);

        /* Allocate memory for the expanded device tree */
        mem = dt_alloc(size + 4, __alignof__(struct device_node));
        if (!mem)
                return NULL;

        memset(mem, 0, size);

        *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);

        pr_debug("  unflattening %p...\n", mem);

        /* Second pass, do actual unflattening */
        unflatten_dt_nodes(blob, mem, dad, mynodes);
        if (be32_to_cpup(mem + size) != 0xdeadbeef)
                pr_warn("End of tree marker overwritten: %08x\n",
                        be32_to_cpup(mem + size));

        if (detached && mynodes) {
                of_node_set_flag(*mynodes, OF_DETACHED);
                pr_debug("unflattened tree is detached\n");
        }

        pr_debug(" <- unflatten_device_tree()\n");
        return mem;
}

DTB를 파싱하여 확장 포맷으로 변환한 후 of_root 전역 변수가 가리키게 한다.

  • 코드 라인 22~25에서 DTB의 첫 부분에 위치한 헤더에서 첫 워드를 통해 DTB 데이터 여부를 체크한다. 추가로 지원 가능한 DTB 버전이 0x02 ~ 0x11인지 확인하여 체크하고, 다른 경우 에러를 출력하고 처리를 하지 않는다.
  • 코드 라인 28~30에서 가장 마지막 인자 dryrun을 true로 전달하여 실제 컨버팅 동작을 하지 않고 DTB를 unflatten할 때 만들어질 device_node 구조체들과 properties 구조체들의 구성에 필요한 전체 크기의 크기만을 구한다. 그리고 최종 산출된 크기를 워드(4바이트) 단위로 정렬한다.
  • 코드 라인 36~38에서 인자로 전달받은 (*dt_alloc) 함수를 통해 메모리를 할당받는다. 할당 시의 크기로 위에서 산출한 크기에 추가로 끝부분을 나타내기 위한 4바이트만큼을 추가한다. 또한 정렬 단위는 시스템의 최소 정렬 단위가 주어지는데 ARM, ARM64는 4바이트다.
  • 코드 라인 42에서 할당된 메모리의 마지막 4바이트에 0xdeadbeef를 저장한다. 이 값은 경계 침범을 모니터링하기 위해 사용한다.
  • 코드 라인 47에서  DTB를 파싱하여 device_node, property 구조체 배열로 변환한다.
  • 코드 라인 48~50에서 할당된 메모리의 끝에 설치한 경계 침범 값이 오염되었는지 확인하여 경고 출력을 한다.

 

다음 그림은 바이너리 형태의 DTB를 unflatten하여 확장 포맷으로 변환하는 모습을 보여준다.

unflatten_device_tree-1a

 

unflatten_dt_node()

drivers/of/fdt.c

/**
 * unflatten_dt_nodes - Alloc and populate a device_node from the flat tree
 * @blob: The parent device tree blob
 * @mem: Memory chunk to use for allocating device nodes and properties
 * @dad: Parent struct device_node
 * @nodepp: The device_node tree created by the call
 *
 * It returns the size of unflattened device tree or error code
 */
static int unflatten_dt_nodes(const void *blob,
                              void *mem,
                              struct device_node *dad,
                              struct device_node **nodepp)
{
        struct device_node *root;
        int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH   64
        struct device_node *nps[FDT_MAX_DEPTH];
        void *base = mem;
        bool dryrun = !base;

        if (nodepp)
                *nodepp = NULL;

        /*
         * We're unflattening device sub-tree if @dad is valid. There are
         * possibly multiple nodes in the first level of depth. We need
         * set @depth to 1 to make fdt_next_node() happy as it bails
         * immediately when negative @depth is found. Otherwise, the device
         * nodes except the first one won't be unflattened successfully.
         */
        if (dad)
                depth = initial_depth = 1;

        root = dad;
        nps[depth] = dad;

        for (offset = 0;
             offset >= 0 && depth >= initial_depth;
             offset = fdt_next_node(blob, offset, &depth)) {
                if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH))
                        continue;

                if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
                    !of_fdt_device_is_available(blob, offset))
                        continue;

                if (!populate_node(blob, offset, &mem, nps[depth],
                                   &nps[depth+1], dryrun))
                        return mem - base;

                if (!dryrun && nodepp && !*nodepp)
                        *nodepp = nps[depth+1];
                if (!dryrun && !root)
                        root = nps[depth+1];
        }

        if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
                pr_err("Error %d processing FDT\n", offset);
                return -EINVAL;
        }

        /*
         * Reverse the child list. Some drivers assumes node order matches .dts
         * node order
         */
        if (!dryrun)
                reverse_nodes(root);

        return mem - base;
}

FDT 형태의 디바이스 트리를 파싱하고 확장(expand)하여 디바이스 노드로 변환한다. @blob에 디바이스 트리(FDT)의 시작 주소를 지정하고, 확장된 디바이스 노드가 저장될 @mem을 지정한다. @dad에는 부모 디바이스 노드를 지정하고, 출력 인자 @mynode는 이 함수가 호출되어 생성될 디바이스 노드이다. 처음 호출될 때 @dad에는 null, @mynode에는 루트 디바이스 노드를 가리키는 &of_root가 주어진다.

  • 코드 라인 13~14에서 먼저 출력 인자 @nodepp에 null을 대입한다.
  • 코드 라인 23~24에서 @dad가 지정된 경우에 한해 depth와 초기 depth를 1부터 시작한다.
  • 코드 라인 29~33에서 다음(next) 노드를 읽고 offset을 알아온다. 이 때 읽은 노드의 depth도 알아온다.
  • 코드 라인 35~37에서 노드가  enable 또는 ok 상태가 아닌 경우는 skip 한다.
  • 코드 라인 39~41에서 노드를 활성화한다. 지금까지 변환한 사이즈를 반환한다.
  • 코드 라인 43~44에서 2nd pass에서 @nodepp에 현재 노드를 지정한다. 단 한 번만 지정한다.
  • 코드 라인 45~46에서 2nd pass의 루트가 아니고 아직 루트가 지정되지 않은 경우 현재 노드를 루트로 지정한다.
  • 코드 라인 49~52에서 노드 파싱에 문제가 있는 경우 에러를 반환한다.
  • 코드 라인 58~59에서 2nd pass인 경우 노드를 reverse 한다.
  • 코드 라인 61에서 지금까지 변환한 사이즈를 반환한다.

 


디바이스 노드와 속성 활성화

populate_node()

drivers/of/fdt.c

static bool populate_node(const void *blob,
                          int offset,
                          void **mem,
                          struct device_node *dad,
                          struct device_node **pnp,
                          bool dryrun)
{
        struct device_node *np;
        const char *pathp;
        unsigned int l, allocl;

        pathp = fdt_get_name(blob, offset, &l);
        if (!pathp) {
                *pnp = NULL;
                return false;
        }

        allocl = ++l;

        np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
                                __alignof__(struct device_node));
        if (!dryrun) {
                char *fn;
                of_node_init(np);
                np->full_name = fn = ((char *)np) + sizeof(*np);

                memcpy(fn, pathp, l);

                if (dad != NULL) {
                        np->parent = dad;
                        np->sibling = dad->child;
                        dad->child = np;
                }
        }

        populate_properties(blob, offset, mem, np, pathp, dryrun);
        if (!dryrun) {
                np->name = of_get_property(np, "name", NULL);
                if (!np->name)
                        np->name = "<NULL>";
        }

        *pnp = np;
        return true;
}

노드를 파싱하여 디바이스 노드로 변환한다. 성공 시 true를 반환한다.

  • 코드 라인 12~16에서 노드 명이 null인 경우 출력 인자 @pnp에 null을 대입한 후 더 이상 처리하지 않고 false를 반환한다.
  • 코드 라인 20~21에서 @mem에서 노드가 저장될 영역을 확보한다.
  • 코드 라인 22~34에서 2nd pass인 경우 노드를 초기화 하고, 노드 명을 지정한 후 노드 간의 관계를 연결한다.
  • 코드 라인 36~41에서 속성을 파싱하여 속성 정보로 변환한다. 속성 이름이 없는 경우 “<NULL>” 문자열을 이름으로 지정한다.
  • 코드 라인 43~44에서 출력 인자 @pnp에 디바이스 노드를 지정하고, true를 반환한다.

 

populate_properties()

drivers/of/fdt.c -1/2-

static void populate_properties(const void *blob,
                                int offset,
                                void **mem,
                                struct device_node *np,
                                const char *nodename,
                                bool dryrun)
{
        struct property *pp, **pprev = NULL;
        int cur;
        bool has_name = false;

        pprev = &np->properties;
        for (cur = fdt_first_property_offset(blob, offset);
             cur >= 0;
             cur = fdt_next_property_offset(blob, cur)) {
                const __be32 *val;
                const char *pname;
                u32 sz;

                val = fdt_getprop_by_offset(blob, cur, &pname, &sz);
                if (!val) {
                        pr_warn("Cannot locate property at 0x%x\n", cur);
                        continue;
                }

                if (!pname) {
                        pr_warn("Cannot find property name at 0x%x\n", cur);
                        continue;
                }

                if (!strcmp(pname, "name"))
                        has_name = true;

                pp = unflatten_dt_alloc(mem, sizeof(struct property),
                                        __alignof__(struct property));
                if (dryrun)
                        continue;

                /* We accept flattened tree phandles either in
                 * ePAPR-style "phandle" properties, or the
                 * legacy "linux,phandle" properties.  If both
                 * appear and have different values, things
                 * will get weird. Don't do that.
                 */
                if (!strcmp(pname, "phandle") ||
                    !strcmp(pname, "linux,phandle")) {
                        if (!np->phandle)
                                np->phandle = be32_to_cpup(val);
                }

                /* And we process the "ibm,phandle" property
                 * used in pSeries dynamic device tree
                 * stuff
                 */
                if (!strcmp(pname, "ibm,phandle"))
                        np->phandle = be32_to_cpup(val);

                pp->name   = (char *)pname;
                pp->length = sz;
                pp->value  = (__be32 *)val;
                *pprev     = pp;
                pprev      = &pp->next;
        }

 

drivers/of/fdt.c -2/2-

        /* With version 0x10 we may not have the name property,
         * recreate it here from the unit name if absent
         */
        if (!has_name) {
                const char *p = nodename, *ps = p, *pa = NULL;
                int len;

                while (*p) {
                        if ((*p) == '@')
                                pa = p;
                        else if ((*p) == '/')
                                ps = p + 1;
                        p++;
                }

                if (pa < ps)
                        pa = p;
                len = (pa - ps) + 1;
                pp = unflatten_dt_alloc(mem, sizeof(struct property) + len,
                                        __alignof__(struct property));
                if (!dryrun) {
                        pp->name   = "name";
                        pp->length = len;
                        pp->value  = pp + 1;
                        *pprev     = pp;
                        pprev      = &pp->next;
                        memcpy(pp->value, ps, len - 1);
                        ((char *)pp->value)[len - 1] = 0;
                        pr_debug("fixed up name for %s -> %s\n",
                                 nodename, (char *)pp->value);
                }
        }

        if (!dryrun)
                *pprev = NULL;
}

 

 

아래 그림은 노드명이 full path name으로 바뀌어 저장되는 과정을 설명하였다.

unflatten_dt_node-3a

 

아래 그림은 a@1000 노드의 서브 노드로 a2 노드가 추가될 때의 상황이다.

unflatten_dt_node-2c

 

아래 그림은 cpu@0 노드에 속한 속성들이 연결된 모습을 보여준다.

unflatten_dt_node-4a

 

다음 그림은 속성명에 name이 없는 경우 마지막에 추가되는 모습을 보여준다.

unflatten_dt_node-5c

 

child 노드가 있는 경우 DTB 순서대로 만들기 위해 각 child 노드를 reverse 한다.

unflatten_dt_node-1c

 

unflatten_dt_alloc()

drivers/of/fdt.c

static void *unflatten_dt_alloc(void **mem, unsigned long size,
                                       unsigned long align)
{
        void *res;

        *mem = PTR_ALIGN(*mem, align);
        res = *mem;
        *mem += size;

        return res; 
}

mem 값을 align 단위로 round up 하고 리턴하며 입출력 인수 mem 값은 size만큼 증가시킨다.

 

early_init_dt_alloc_memory_arch()

drivers/of/fdt.c

static void * __init early_init_dt_alloc_memory_arch(u64 size, u64 align)
{
        void *ptr = memblock_alloc(size, align);

        if (!ptr)
                panic("%s: Failed to allocate %llu bytes align=0x%llx\n",
                      __func__, size, align);

        return ptr;
}

align 단위로 size 만큼의 공간을 memblock으로 부터 할당 받고 그 가상 주소를 리턴한다.

 


alias 노드 스캔

of_alias_scan()

drivers/of/base.c

/**
 * of_alias_scan - Scan all properties of the 'aliases' node
 *
 * The function scans all the properties of the 'aliases' node and populates
 * the global lookup table with the properties.  It returns the
 * number of alias properties found, or an error code in case of failure.
 *
 * @dt_alloc:   An allocator that provides a virtual address to memory
 *              for storing the resulting tree
 */
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
        struct property *pp;

        of_aliases = of_find_node_by_path("/aliases");
        of_chosen = of_find_node_by_path("/chosen");
        if (of_chosen == NULL)
                of_chosen = of_find_node_by_path("/chosen@0");

        if (of_chosen) {
                /* linux,stdout-path and /aliases/stdout are for legacy compatibility */
                const char *name = NULL;

                if (of_property_read_string(of_chosen, "stdout-path", &name))
                        of_property_read_string(of_chosen, "linux,stdout-path",
                                                &name);
                if (IS_ENABLED(CONFIG_PPC) && !name)
                        of_property_read_string(of_aliases, "stdout", &name);
                if (name)
                        of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
        }

        if (!of_aliases)
                return;

        for_each_property_of_node(of_aliases, pp) {
                const char *start = pp->name;
                const char *end = start + strlen(start);
                struct device_node *np;
                struct alias_prop *ap;
                int id, len;

                /* Skip those we do not want to proceed */
                if (!strcmp(pp->name, "name") ||
                    !strcmp(pp->name, "phandle") ||
                    !strcmp(pp->name, "linux,phandle"))
                        continue;

                np = of_find_node_by_path(pp->value);
                if (!np)
                        continue;

                /* walk the alias backwards to extract the id and work out
                 * the 'stem' string */
                while (isdigit(*(end-1)) && end > start)
                        end--;
                len = end - start;

                if (kstrtoint(end, 10, &id) < 0)
                        continue;

                /* Allocate an alias_prop with enough space for the stem */
                ap = dt_alloc(sizeof(*ap) + len + 1, __alignof__(*ap));
                if (!ap)
                        continue;
                memset(ap, 0, sizeof(*ap) + len + 1);
                ap->alias = start;
                of_alias_add(ap, np, id, start, len);
        }
}

“/chosen” 노드를 검색하여 전역 변수 of_stdout을 “stdout-path”에 연결된 노드로 설정한다. 그리고 “/aliases” 노드의 속성 중 “name” 및 “phandle”을 찾아 aliases_prop 구조체로 구성하여 리스트 aliases_lookup에 추가한다.

  • 코드 라인 5에서 “/aliases” 노드를 찾아 전역 of_aliases에 설정한다.
  • 코드 라인 6~8에서 “/chosen” 노드를 찾아 전역 of_chosen에 설정한다. 만일 of_chosen 노드가 발견되지 않으면 “/chosen@0”으로 다시 한번 검색한다.
  • 코드 라인 10~18에서 “/chosen” 노드가 발견된 경우 of_chosen 노드에 있는 속성들에서 “stdout-path” 속성명으로 검색하여 찾은 속성 value 값을 name에 저장한다. 검색 결과가 없으면 레거시 호환을 위해 “linux,stdout-path” 및 “stdout” 속성명으로도 검색한다.
  • 코드 라인 19~20에서 name(of_chosen 노드에서 검색한 “stdout-path” 속성의 value 값)으로 노드를 검색하고 전역 of_stdout_options에는 name 문자열에 옵션(‘:’ 문자로 시작하는 문자열) 값이 있다면 저장한다.
  • 코드 라인 23~24에서 등록된 aliases가 없다면 함수를 빠져나간다.
  • 코드 라인 26~31에서 of_aliases에 속한 모든 속성에 대해 루프를 돈다.
  • 코드 라인 34~37에서 속성명이 “name”, “phandle”, “linux,phandle”인 경우에는 스킵한다.
  • 코드 라인 39~41에서 속성의 value 값으로 노드를 검색하고 노드가 발견되지 않으면 스킵한다.
  • 코드 라인 45~46에서 속성 값에 있는 노드명이 숫자가 있다면 숫자가 시작되는 위치를 end에 설정한다.
    • 예 “/abc/def@1000”
      • end = ‘1’ 문자를 가리킴
  • 코드 라인 47에서 len에 노드명의 마지막에 있는 주소를 제외한 ‘@’ 문자까지의 노드명 길이가 담긴다.
    • 예) “/abc/def@1000”
      • len = 9
    • 예) “/abc”
      • len = 4
  • 코드 라인 49~50에서 주소에 대한 문자열을 10진수로 변환하여 id 변수에 저장을 하는데, 에러인 경우에는 스킵한다.
  • 코드 라인 53~55에서 alias_prop 구조체 크기 + len + 1 크기만큼 memblock을 할당한다. 만일 할당이 실패하면 스킵한다.
  • 코드 라인 56에서 할당받은 메모리를 0으로 초기화한다.
  • 코드 라인 57에서 alias가 속성명을 가리키게 한다. /alias 노드의 각 속성명은 ‘ / ’ 문자로 시작하지 않는다.
    • 예 name = “uart0”
  • 코드 라인 58에서 np, id, stem 값을 저장하고 전역 aliases_lookup 리스트에 추가한다. stem 문자열은 속성명에서 주소 부분을 제외했다.
    • 예) 속성 데이터 = “/soc/uart@7e201000”
      • stem = “/soc/uart@”

구조체

device_node 구조체

include/linux/of.h

struct device_node {
        const char *name;
        phandle phandle;
        const char *full_name;
        struct fwnode_handle fwnode;

        struct  property *properties;
        struct  property *deadprops;    /* removed properties */
        struct  device_node *parent;
        struct  device_node *child;
        struct  device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
        struct  kobject kobj;
#endif
        unsigned long _flags;
        void    *data;
#if defined(CONFIG_SPARC)
        unsigned int unique_id;
        struct of_irq_controller *irq_trans;
#endif
};

 

property 구조체

include/linux/of.h

struct property {
        char    *name;
        int     length;
        void    *value;
        struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
        unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
        unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
        struct bin_attribute attr;
#endif
};

 

alias_prop 구조체

drivers/of/of_private.h

/**
 * struct alias_prop - Alias property in 'aliases' node
 * @link:       List node to link the structure in aliases_lookup list
 * @alias:      Alias property name
 * @np:         Pointer to device_node that the alias stands for
 * @id:         Index value from end of alias name
 * @stem:       Alias string without the index
 *
 * The structure represents one alias property of 'aliases' node as
 * an entry in aliases_lookup list.
 */
struct alias_prop {
        struct list_head link;
        const char *alias;
        struct device_node *np;
        int id;
        char stem[];
};
  • link
    • 링크드 리스트
  • alias
    • alias 속성명
      • 예) “chosen”, “uart0”
  • np
    • 노드(device_node)를 가리킨다.
  • id
    • 노드의 메모리 또는 포트가 사용하는 주소
      • 예) 노드명이 serial@12000 인 경우 id=12000
  • stem
    • index(id)를 제외한 full path 노드명
      • 예) “/soc/uart@”

 

각 구조체의 Memblock 할당 시 사이즈

  • device_node
    • 생성할 때 마다 full path 노드명 공간이 추가 할당된다.
    • name, type, data 등은 기존 DTB에 있는 문자열이나 값을 가리킨다.
  • property
    • 생성할 때 마다 property 사이즈만큼 공간이 할당된다. 그러나 DTB에 없는 name 속성을 추가 생성해야 하는 경우에는 property 속성 이외에도 주소제외 노드명 공간을 추가 할당한다.
  • alias_prop
    • 생성할 때 마다 주소제외 노드명 공간이 추가 할당되고 이 공간은 stem 문자열이 사용하는 공간이다.

unflatten_device_tree-2

 

참고

 

DTB (of API)

 

of_find_node_by_path()

include/linux/of.h

static inline struct device_node *of_find_node_by_path(const char *path)
{
        return of_find_node_opts_by_path(path, NULL);
}

full path 노드명과 매치되는 노드를 찾는다.

  • alias명으로 검색하는 경우에는 ‘/’ 문자로 시작하지 않아도 된다.

 

of_find_node_opts_by_path()

drivers/of/base.c

/**
 *      of_find_node_opts_by_path - Find a node matching a full OF path
 *      @path: Either the full path to match, or if the path does not
 *             start with '/', the name of a property of the /aliases
 *             node (an alias).  In the case of an alias, the node
 *             matching the alias' value will be returned.
 *      @opts: Address of a pointer into which to store the start of
 *             an options string appended to the end of the path with
 *             a ':' separator.
 *
 *      Valid paths:
 *              /foo/bar        Full path
 *              foo             Valid alias
 *              foo/bar         Valid alias + relative path
 *      
 *      Returns a node pointer with refcount incremented, use
 *      of_node_put() on it when done.
 */     
struct device_node *of_find_node_opts_by_path(const char *path, const char **opts)
{
        struct device_node *np = NULL;
        struct property *pp;
        unsigned long flags;
        const char *separator = strchr(path, ':');

        if (opts)
                *opts = separator ? separator + 1 : NULL; 
                                             
        if (strcmp(path, "/") == 0)
                return of_node_get(of_root);

        /* The path could begin with an alias */
        if (*path != '/') {
                int len;
                const char *p = separator;

                if (!p)
                        p = strchrnul(path, '/');
                len = p - path;

                /* of_aliases must not be NULL */
                if (!of_aliases)         
                        return NULL;
     
                for_each_property_of_node(of_aliases, pp) {
                        if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) {
                                np = of_find_node_by_path(pp->value);
                                break;
                        }
                }
                if (!np)
                        return NULL;
                path = p;
        }

full path 또는 alias 명과 매치되는 노드를 찾는다. 옵션(‘:’  다음의 문자열)  문자열이 있는 경우 출력 인수가 주어진 경우 저장한다.

  • const char *separator = strchr(path, ‘:’);
    • path에서 ‘:’ 문자 위치를 찾는다.
  • if (opts) *opts = separator ? separator + 1 : NULL;
    • ‘:’문자가 있는 경우 opts에 ‘:’ 다음 문자를 가리키고 없는 경우 null을 대입한다.
  • if (strcmp(path, “/”) == 0) return of_node_get(of_root);
    • 루트 노드인 경우 전역 변수 of_root의 노드를 리턴한다.

노드명이 alias인 경우 전역 of_alias에 추가된 모든 속성의 값과 같은 경우 이 속성값으로 노드를 찾아 온다.

  • if (*path != ‘/’) {
    • 노드명이 alias 타입으로 시작한 경우
  • if (!p) p = strchrnul(path, ‘/’);
    • p가 지정되지 않은 경우 p에 ‘/’ 문자열 위치를 대입한다.
  • len = p – path;
    • ‘/’ 문자열 위치에서 속성 첫 문자열 위치를 뺀 수로 compact alias 명의 길이가 된다.
  • for_each_property_of_node(of_aliases, pp) {
    • of_aliases 노드내의 모든 속성 만큼 루프를 돈다.
  • if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) { np = of_find_node_by_path(pp->value); break; }
    • 속성명 길이와 len(alias명 길이)이 같고 그 길이만큼 속성명과 alias 명을 비교하여 같은 경우 속성 값으로 노드를 찾아 루프를 탈출한다.
  • if (!np) return NULL;
    • 찾지 못한 경우 null을 리턴한다.

 

        /* Step down the tree matching path components */
        raw_spin_lock_irqsave(&devtree_lock, flags);
        if (!np)
                np = of_node_get(of_root);
        while (np && *path == '/') {
                path++; /* Increment past '/' delimiter */
                np = __of_find_node_by_path(np, path);
                path = strchrnul(path, '/');
                if (separator && separator < path)
                        break;
        }
        raw_spin_unlock_irqrestore(&devtree_lock, flags);
        return np;
}
EXPORT_SYMBOL(of_find_node_opts_by_path);
  • if (!np) np = of_node_get(of_root);
    • 발견된 노드 정보가 없으면 루트 노드 정보를 가져온다.
  • while (np && *path == ‘/’) {
    • 노드들 중 alias명이 아닌 full path 노드명인 경우만 루프를 돈다.
  • np = __of_find_node_by_path(np, path);
    • 앞에 있는 ‘/’를 제외한 full path 노드명으로 노드를 찾아온다.
  • path = strchrnul(path, ‘/’);
    • full path 노드명에서 두 번째 ‘/’ 문자를 찾는다.
  • if (separator && separator < path) break;
    • 두 번째 ‘/’ 문자열의 위치가 separator보다 큰 경우 루프를 탈출하고 리턴한다.
  • 만일 못찾은 경우null을 리턴한다.

 

__of_find_node_path()

drivers/of/base.c

static struct device_node *__of_find_node_by_path(struct device_node *parent,
                                                const char *path)
{
        struct device_node *child;
        int len;

        len = strcspn(path, "/:");
        if (!len)
                return NULL;

        __for_each_child_of_node(parent, child) {
                const char *name = strrchr(child->full_name, '/');
                if (WARN(!name, "malformed device_node %s\n", child->full_name))
                        continue;
                name++;
                if (strncmp(path, name, len) == 0 && (strlen(name) == len))
                        return child;
        }
        return NULL;
}

child 노드의 compact 노드명과 요청 문자열을 비교하여 같은 경우 해당 child 노드를 리턴하고 찾지 못한 경우 null을 리턴한다. 요청 문자열은 ‘/’ 문자로 시작하면 안된다.

  • len = strcspn(path, “/:”);
    • path 문자열에 ‘/’ 또는 ‘:’ 문자가 발견되기 전 까지의 문자열 수를 len에 대입한다.
  • if (!len) return NULL;
    • 문자열의 길이가 0인 경우 null을 리턴한다.
  • __for_each_child_of_node(parent, child) {
    • parent 노드에 있는 child 노드에 대해 루프를 돈다.
  • const char *name = strrchr(child->full_name, ‘/’);
    • 서브 노드 full name에서 가장 뒤에 있는 ‘/’ 문자를 찾는다.
  • name++;
    • ‘/’ 문자를 skip 한다.
  • if (strncmp(path, name, len) == 0 && (strlen(name) == len)) return child;
    • 서브 노드의 compact 노드명과 path 문자열과 비교하여 같은 경우 해당 서브 노드를 리턴한다.

아래 그림은 지정한 부모 노드 바로 한 단계 밑에 위치한 서브 노드들의 compact 노드명을 대상으로 특정 노드명을 찾는 것을 보여준다.

__of_find_node_by_path-1

__for_each_child_of_node()

drivers/of/base.c

#define __for_each_child_of_node(parent, child) \
        for (child = __of_get_next_child(parent, NULL); child != NULL; \
             child = __of_get_next_child(parent, child))

parent 노드의 child 노드만큼 루프를 돈다.

 

아래 그림은 parent 노드 아래 3개의 child 노드가 있고 1번 부터 3번 까지 순서대로 루프를 도는 것을 보여준다.

__for_each_child_of_node-1

 

__of_get_next_child()

drivers/of/base.c

static struct device_node *__of_get_next_child(const struct device_node *node,
                                                struct device_node *prev)
{
        struct device_node *next;

        if (!node)
                return NULL;

        next = prev ? prev->sibling : node->child;
        for (; next; next = next->sibling)
                if (of_node_get(next))
                        break;
        of_node_put(prev);
        return next;
}

node의 child 노드를 찾는다. prev를 null로 하는 경우 첫 child 노드를 찾고 prev에 child 노드인 경우 다음 노드를 찾는다.

 

아래 그림은 __of_get_next_child() 함수를  3번 호출할 때의 상황을 나타내었다. prev값에 따라서 각각에 대응하는 next 노드를 알아오는 것을 보여준다.

__of_get_next_child-1

 

of_node_get()

drivers/of/dynamic.c

/**
 * of_node_get() - Increment refcount of a node
 * @node:       Node to inc refcount, NULL is supported to simplify writing of
 *              callers
 *
 * Returns node.
 */
struct device_node *of_node_get(struct device_node *node)
{
        if (node)
                kobject_get(&node->kobj);
        return node;
}
EXPORT_SYMBOL(of_node_get);
  • 해당 노드에 대한 참조 카운터를 증가시킨다.

 

of_node_put()

drivers/of/dynamic.c

/**
 * of_node_put() - Decrement refcount of a node
 * @node:       Node to dec refcount, NULL is supported to simplify writing of
 *              callers
 */
void of_node_put(struct device_node *node)
{
        if (node)
                kobject_put(&node->kobj);
}
EXPORT_SYMBOL(of_node_put);
  • 해당 노드에 대한 참조 카운터를 감소시킨다

 

for_each_property_of_node()

include/linux/of.h

#define for_each_property_of_node(dn, pp) \
        for (pp = dn->properties; pp != NULL; pp = pp->next)
  • dn 노드에 속한 모든 속성에 대해 루프를 돈다.

다음 그림은 지정된 노드에 포함된 전체 속성에 대한 루프를 도는 것을 보여준다.

for_each_property_of_node-1

 

of_get_property()

drivers/of/base.c

/*
 * Find a property with a given name for a given node
 * and return the value.
 */
const void *of_get_property(const struct device_node *np, const char *name,
                            int *lenp)
{
        struct property *pp = of_find_property(np, name, lenp);

        return pp ? pp->value : NULL;
}
EXPORT_SYMBOL(of_get_property);
  • devtree_lock으로 보호하면서 지정된 노드의 전체 속성들 중 요청 속성명을 찾아 발견되는 경우 해당 속성 길이를 lenp에 저장하고 해당 속성의 value 값을 리턴한다.

 

of_find_property()

drivers/of/base.c

struct property *of_find_property(const struct device_node *np,
                                  const char *name,
                                  int *lenp)
{
        struct property *pp;
        unsigned long flags;

        raw_spin_lock_irqsave(&devtree_lock, flags);
        pp = __of_find_property(np, name, lenp);
        raw_spin_unlock_irqrestore(&devtree_lock, flags);

        return pp;
}
EXPORT_SYMBOL(of_find_property);
  • devtree_lock으로 보호하면서 지정된 노드의 전체 속성들 중 요청 속성명을 찾아 발견되는 경우 속성 길이를 lenp에 저장하고 해당 속성을 리턴한다.

 

__of_find_property()

drivers/of/base.c

static struct property *__of_find_property(const struct device_node *np,
                                           const char *name, int *lenp)
{
        struct property *pp;

        if (!np)
                return NULL;

        for (pp = np->properties; pp; pp = pp->next) {
                if (of_prop_cmp(pp->name, name) == 0) {
                        if (lenp)
                                *lenp = pp->length;
                        break;
                }
        }

        return pp;
}
  • 지정된 노드의 전체 속성들 중 요청 속성명을 찾아 발견되는 경우 속성 길이를 lenp에 저장하고 해당 속성을 리턴한다.

아래 그림은 지정된 노드에 포함된 속성들에서 속성명으로 검색하는 것을 보여준다.

__of_find_property-1

of_prop_cmp()

include/linux/of.h

#define of_prop_cmp(s1, s2)             strcmp((s1), (s2))

 

of_alias_add()

drivers/of/base.c

static void of_alias_add(struct alias_prop *ap, struct device_node *np,
                         int id, const char *stem, int stem_len)
{
        ap->np = np;
        ap->id = id;
        strncpy(ap->stem, stem, stem_len);
        ap->stem[stem_len] = 0;
        list_add_tail(&ap->link, &aliases_lookup);
        pr_debug("adding DT alias:%s: stem=%s id=%i node=%s\n",
                 ap->alias, ap->stem, ap->id, of_node_full_name(np));
}

alias 속성에 np, id, stem을 저장하고 전역 리스트 aliases_lookup에 추가한다.

 

속성 값을 읽어오는 of_property_read_***() 함수들

아래의 함수들은 인수로 요청한 노드에서 인수로 요청한 속성값으로 검색하여 속성 값을 함수명이 의미하는 데이터 사이즈로 읽어온다.

  • of_property_read_string_index()
  • of_property_read_u32_index()
  • of_property_read_bool()
  • of_property_read_u8()
  • of_property_read_u8_array()
  • of_property_read_u16()
  • of_property_read_u16_array()
  • of_property_read_u32()
  • of_property_read_u32_array()
  • of_property_read_s32()
  • of_property_read_u64()
  • of_property_read_u64_array()
  • of_property_read_string()

 

of_property_read_u32()

include/linux/of.h

static inline int of_property_read_u32(const struct device_node *np,
                                       const char *propname,
                                       u32 *out_value)
{               
        return of_property_read_u32_array(np, propname, out_value, 1);
}
  • np 노드에서 요청한 속성명으로 검색하여 해당 속성 값 중 1 개의 32bit unsigned int 값을 읽어 출력 인수 out_value에 저장한다.

 

of_property_read_u32_array()

drivers/of/base.c

/**
 * of_property_read_u32_array - Find and read an array of 32 bit integers
 * from a property.
 *
 * @np:         device node from which the property value is to be read.
 * @propname:   name of the property to be searched.
 * @out_values: pointer to return value, modified only if return value is 0.
 * @sz:         number of array elements to read 
 *
 * Search for a property in a device node and read 32-bit value(s) from
 * it. Returns 0 on success, -EINVAL if the property does not exist,
 * -ENODATA if property does not have a value, and -EOVERFLOW if the
 * property data isn't large enough.
 *
 * The out_values is modified only if a valid u32 value can be decoded.
 */
int of_property_read_u32_array(const struct device_node *np,
                               const char *propname, u32 *out_values,
                               size_t sz) 
{
        const __be32 *val = of_find_property_value_of_size(np, propname,
                                                (sz * sizeof(*out_values)));

        if (IS_ERR(val))
                return PTR_ERR(val);

        while (sz--)
                *out_values++ = be32_to_cpup(val++);
        return 0;
}
EXPORT_SYMBOL_GPL(of_property_read_u32_array);
  • 현재 노드의 요청 속성을 찾아 데이터 갯 수를 초과하는지 체크하여 이상 없는 경우 out_values에 요청한 sz 수 만큼의 32bit unsigned 값을 저장하고 0을 리턴한다. 실패 시 0이 아닌 수가 리턴된다.

 

of_find_property_value_of_size()

drivers/of/base.c

/**
 * of_find_property_value_of_size
 *
 * @np:         device node from which the property value is to be read.
 * @propname:   name of the property to be searched.
 * @len:        requested length of property value
 *
 * Search for a property in a device node and valid the requested size.
 * Returns the property value on success, -EINVAL if the property does not
 *  exist, -ENODATA if property does not have a value, and -EOVERFLOW if the
 * property data isn't large enough.
 *
 */
static void *of_find_property_value_of_size(const struct device_node *np,
                        const char *propname, u32 len)
{
        struct property *prop = of_find_property(np, propname, NULL);

        if (!prop)
                return ERR_PTR(-EINVAL);
        if (!prop->value)
                return ERR_PTR(-ENODATA);
        if (len > prop->length)
                return ERR_PTR(-EOVERFLOW);

        return prop->value;
}
  • 속성 값을 알아오는데 다음과 같은 경우 에러를 리턴한다.
    • 속성을 발견할 수 없을 때
    • 속성 값을 가리키는 값이 null일 때
    • 속성 값 길이보다 요청 길이가 더 클 때
    • 예) 32bit – reg = <0x0, 0x0>의 경우 prop->length=2 이므로 u32 형으로 sz=2 번까지 읽을 수 있다.

 

매치되는 노드들을 검색

of_find_matching_node_and_match()

drivers/of/base.c

/**
 *      of_find_matching_node_and_match - Find a node based on an of_device_id
 *                                        match table.
 *      @from:          The node to start searching from or NULL, the node
 *                      you pass will not be searched, only the next one
 *                      will; typically, you pass what the previous call
 *                      returned. of_node_put() will be called on it
 *      @matches:       array of of device match structures to search in
 *      @match          Updated to point at the matches entry which matched
 *
 *      Returns a node pointer with refcount incremented, use
 *      of_node_put() on it when done.
 */
struct device_node *of_find_matching_node_and_match(struct device_node *from,
                                        const struct of_device_id *matches,
                                        const struct of_device_id **match)
{
        struct device_node *np; 
        const struct of_device_id *m;
        unsigned long flags;

        if (match)
                *match = NULL;

        raw_spin_lock_irqsave(&devtree_lock, flags);
        for_each_of_allnodes_from(from, np) {
                m = __of_match_node(matches, np); 
                if (m && of_node_get(np)) {
                        if (match)
                                *match = m; 
                        break;
                }    
        }    
        of_node_put(from);
        raw_spin_unlock_irqrestore(&devtree_lock, flags);
        return np;
}
EXPORT_SYMBOL(of_find_matching_node_and_match);
  • raw_spin_lock_irqsave(&devtree_lock, flags);
    • devtree_lock spin lock을 사용하여 device tree를 보호하게 한다.
  • for_each_of_allnodes_from(from, np) {
    • 검색을 시작할 노드부터 끝 노드까지 루프를 돈다.
    • 검색 결과에 검색 시작 노드는 제외한다.
  • m = __of_match_node(matches, np);
    • 가장 적합한 디바이스 매치를 찾는다.
  • if (m && of_node_get(np)) { if (match) *match = m; break; }
    • 노드에 대해 참조 카운터를 증가시키고 출력 인수 match가 지정되어 있으면 찾은 of_device_id 구조체 포인터를 저장하고 루프를 빠져나간다.
  • of_node_put(from);
    • 시작 노드에 대해 참조 카운터를 감소시킨다.
  • raw_spin_unlock_irqrestore(&devtree_lock, flags);
    • 걸었던 spin lock을 release한다.

아래 그림은 psci_of_match[]에 등록된 디바이스를 device tree에서 찾아 of_device_id 구조체 포인터를 match에 저장하고 발견된 노드를 리턴하는 모습을 보여준다.

of_find_matching_node_and_match-1

 

__of_match_node()

drivers/of/base.c

const struct of_device_id *__of_match_node(const struct of_device_id *matches,
                                           const struct device_node *node)
{
        const struct of_device_id *best_match = NULL;
        int score, best_score = 0; 

        if (!matches)
                return NULL;

        for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
                score = __of_device_is_compatible(node, matches->compatible,
                                                  matches->type, matches->name);
                if (score > best_score) {
                        best_match = matches;
                        best_score = score;
                }
        }

        return best_match;
}
  • of_device_id 구조체로된 매치 배열 matches에서 현재 노드 정보로 각각에 대해 score를 평가하고 가장 높은 score를 갖은 of_device_id 구조체 포인터를 리턴한다.

 

__of_device_is_compatible()

drivers/of/base.c

/**
 * __of_device_is_compatible() - Check if the node matches given constraints
 * @device: pointer to node
 * @compat: required compatible string, NULL or "" for any match
 * @type: required device_type value, NULL or "" for any match
 * @name: required node name, NULL or "" for any match
 *
 * Checks if the given @compat, @type and @name strings match the
 * properties of the given @device. A constraints can be skipped by
 * passing NULL or an empty string as the constraint.
 *
 * Returns 0 for no match, and a positive integer on match. The return
 * value is a relative score with larger values indicating better
 * matches. The score is weighted for the most specific compatible value
 * to get the highest score. Matching type is next, followed by matching
 * name. Practically speaking, this results in the following priority
 * order for matches:
 *
 * 1. specific compatible && type && name
 * 2. specific compatible && type
 * 3. specific compatible && name
 * 4. specific compatible
 * 5. general compatible && type && name
 * 6. general compatible && type
 * 7. general compatible && name
 * 8. general compatible
 * 9. type && name
 * 10. type
 * 11. name
 */
static int __of_device_is_compatible(const struct device_node *device,
                                     const char *compat, const char *type, const char *name)
{
        struct property *prop;
        const char *cp;
        int index = 0, score = 0;

        /* Compatible match has highest priority */
        if (compat && compat[0]) {
                prop = __of_find_property(device, "compatible", NULL);
                for (cp = of_prop_next_string(prop, NULL); cp;
                     cp = of_prop_next_string(prop, cp), index++) {
                        if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
                                score = INT_MAX/2 - (index << 2);
                                break;
                        }
                }
                if (!score)
                        return 0;
        }

        /* Matching type is better than matching name */
        if (type && type[0]) {
                if (!device->type || of_node_cmp(type, device->type))
                        return 0;
                score += 2;
        }

        /* Matching name is a bit better than not */
        if (name && name[0]) {
                if (!device->name || of_node_cmp(name, device->name))
                        return 0;
                score++;
        }

        return score;
}

요청한 노드에 대해 compatible 평가 점수를 알아온다.

compat 문자열, type 문자열, name 문자열이 주어진 경우 각각에 매치되지 않으면 실패로 간주하여 0을 리턴한다. 그 외에 compat 문자열이 매치되는 경우 가장 점수가 높고 type 문자열을 매치 시키면 2점이 추가되고 노드명이 매치되면 1점이 추가된다.

  • if (compat && compat[0]) {
    • compat 문자열이 주어졌을 때
  • prop = __of_find_property(device, “compatible”, NULL);
    • 지정된 노드에서 “compatible” 속성을 찾는다.
  • for (cp = of_prop_next_string(prop, NULL); cp; cp = of_prop_next_string(prop, cp), index++) {
    • 루프를 돌며 다음 속성을 알아온다.
  • if (of_compat_cmp(cp, compat, strlen(compat)) == 0) { score = INT_MAX/2 – (index << 2); break; }
    • 대소문자 구분 없이 compat 문자열과 속성 값이 같은 경우 score에 INT_MAX/2 – (index x 4)를 한후 루프를 탈출한다.
  • if (!score) return 0;
    • 루프가 끝날 때까지 score가 0인 경우 실패로 간주하여 0을 리턴한다.
  • if (type && type[0]) {
    • 타입이 주어진 경우
  • if (!device->type || of_node_cmp(type, device->type)) return 0;
    • 대소문자 구분 없이 타입이 매치되지 않으면 실패로 간주하여 0을 리턴한다.
  • score += 2;
    • 타입이 매치된 경우 score에 2를 더한다.
  • if (name && name[0]) {
    • 디바이스명이 지정된 경우
  • if (!device->name || of_node_cmp(name, device->name)) return 0;
    • 대소문자 구분없이 노드명이 매치되지 않으면 실패로 간주하여 0을 리턴한다.
  • 노드명이 매치되면 score에 1을 더한다.

 

 

 

전체 노드 검색

for_each_of_allnodes()

include/linux/of.h

#define for_each_of_allnodes(dn) for_each_of_allnodes_from(NULL, dn)
  • 루트 노드부터 끝까지 루프를 돈다.

for_each_of_allnodes-1

 

for_each_of_allnodes_from()

include/linux/of.h

#define for_each_of_allnodes_from(from, dn) \
        for (dn = __of_find_all_nodes(from); dn; dn = __of_find_all_nodes(dn))

 

__of_find_all_nodes()

drivers/of/base.c

struct device_node *__of_find_all_nodes(struct device_node *prev)
{
        struct device_node *np;
        if (!prev) {
                np = of_root;
        } else if (prev->child) {
                np = prev->child;
        } else {
                /* Walk back up looking for a sibling, or the end of the structure */
                np = prev;
                while (np->parent && !np->sibling)
                        np = np->parent;
                np = np->sibling; /* Might be null at the end of the tree */
        }
        return np;
}
  • if (!prev) { np = of_root;
    • 인수로 받은 prev가 null인 경우 루트 노드부터 시작한다.
  • } else if (prev->child) { np = prev->child;
    • 인수로 받은 prev->child가 존재하는 경우 np를 첫 child 노드로 설정한다.
  • } else { np = prev;
    • child 노드가 없는 경우 np를 prev 노드로 설정한다.
  •  while (np->parent && !np->sibling) np = np->parent;
    • 부모 노드가 존재하고 우측 형재 노드가 없는 경우 부모 노드를 설정한다.
  • np = np->sibling;
    • 우측 형재 노드를 선택한다.
    • 만일 트리의 마지막이라면 null이된다.

아래 그림은 prev 값에 따라 리턴되는 노드를 보여준다.

__of_find_all_nodes-1

__of_find_all_nodes-2

__of_find_all_nodes-3

 

phandle 값을 이용한 파싱

of_parse_phandle_with_args()

drivers/of/base.c

/**
 * of_parse_phandle_with_args() - Find a node pointed by phandle in a list
 * @np:         pointer to a device tree node containing a list
 * @list_name:  property name that contains a list
 * @cells_name: property name that specifies phandles' arguments count
 * @index:      index of a phandle to parse out
 * @out_args:   optional pointer to output arguments structure (will be filled)
 *                               
 * This function is useful to parse lists of phandles and their arguments.
 * Returns 0 on success and fills out_args, on error returns appropriate
 * errno value.
 *      
 * Caller is responsible to call of_node_put() on the returned out_args->np
 * pointer.
 *      
 * Example: 
 *      
 * phandle1: node1 {
 *      #list-cells = <2>;
 * }
 *
 * phandle2: node2 {
 *      #list-cells = <1>;
 * }
 *
 * node3 {
 *      list = <&phandle1 1 2 &phandle2 3>;
 * }
 *
 * To get a device_node of the `node2' node you may call this:
 * of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args);
 */
int of_parse_phandle_with_args(const struct device_node *np, const char *list_name,
                                const char *cells_name, int index,
                                struct of_phandle_args *out_args)
{
        if (index < 0)
                return -EINVAL;
        return __of_parse_phandle_with_args(np, list_name, cells_name, 0,
                                            index, out_args);
}
EXPORT_SYMBOL(of_parse_phandle_with_args);

요청 노드에서 list_name 속성 값을 읽어 index 값에 해당하는 phandle 노드에서 #으로 시작하는 cells_name 속성 값 수 만큼 phandle 뒤의 인수들을 out_args에 저장한다.

 

__of_parse_phandle_with_args()

drivers/of/base.c

static int __of_parse_phandle_with_args(const struct device_node *np,
                                        const char *list_name,
                                        const char *cells_name,
                                        int cell_count, int index,
                                        struct of_phandle_args *out_args)
{
        const __be32 *list, *list_end;
        int rc = 0, size, cur_index = 0;
        uint32_t count = 0;
        struct device_node *node = NULL;
        phandle phandle;

        /* Retrieve the phandle list property */
        list = of_get_property(np, list_name, &size);
        if (!list)
                return -ENOENT;
        list_end = list + size / sizeof(*list);

        /* Loop over the phandles until all the requested entry is found */
        while (list < list_end) {
                rc = -EINVAL;
                count = 0;

                /*
                 * If phandle is 0, then it is an empty entry with no
                 * arguments.  Skip forward to the next entry.
                 */
                phandle = be32_to_cpup(list++);
                if (phandle) {
                        /*
                         * Find the provider node and parse the #*-cells
                         * property to determine the argument length.
                         *
                         * This is not needed if the cell count is hard-coded
                         * (i.e. cells_name not set, but cell_count is set),
                         * except when we're going to return the found node
                         * below.
                         */
                        if (cells_name || cur_index == index) {
                                node = of_find_node_by_phandle(phandle);
                                if (!node) {
                                        pr_err("%s: could not find phandle\n",
                                                np->full_name);
                                        goto err;
                                }
                        }

                        if (cells_name) {
                                if (of_property_read_u32(node, cells_name,
                                                         &count)) {
                                        pr_err("%s: could not get %s for %s\n",
                                                np->full_name, cells_name,
                                                node->full_name);
                                        goto err;
                                }
                        } else {
                                count = cell_count;
                        }

                        /*
                         * Make sure that the arguments actually fit in the
                         * remaining property data length
                         */
                        if (list + count > list_end) {
                                pr_err("%s: arguments longer than property\n",
                                         np->full_name);
                                goto err;
                        }
                }

요청 np 노드에서 list_name 속성 값을 읽어 index 값에 해당하는 phandle 노드에서 #으로 시작하는 cells_name 속성 값 수 만큼 phandle 뒤의 인수들을 out_args에 저장한다. cells_name이 지정되지 않은 경우 대신 인수 갯 수로 cell_count 수를 사용한다.

  • 코드 라인 14~16에서 np 노드에서 list_name 속성 값과 size(바이트)를 알아온다. 읽어오지 못하는 경우 에러 값으로 -ENOENT 결과를 반환한다.
    • 예) list_name=”clocks = <&clock ABC>, <&clock DEF>; 인 경우 size=16이다.
  • 코드 라인 17~20에서 list_name 속성 값의 끝 주소를 구하고 그 끝 주소직전까지 루프를 돈다.
  • 코드 라인 28~29에서 먼저 phandle 값을 읽어와서 그 값이 주어진 경우
  • 코드 라인 39~46에서 cells_name이 주어졌거나 cur_index가 요청한 index 값이 된 경우 phandle 값에 매치되는 노드를 찾아온다. 만일 못찾은 경우 에러 메시지와 함께 함수를 빠져나간다.
  • 코드 라인 48~55에서 cells_name이 주어진 경우 cells_name 속성 값을 읽어 count에 대입한다. 만일 못찾은 경우 에러 메시지와 함께 함수를 빠져나간다.
  • 코드 라인 56~58에서 cells_name이 주어지지 않은 경우 count 값으로 인수로 전달받은 cell_count를 사용한다.
  • 코드 라인 64~68에서 count 값이 읽을 범위를 벗어난 경우 에러 메시지와 함께 함수를 빠져나간다.

 

                /*
                 * All of the error cases above bail out of the loop, so at
                 * this point, the parsing is successful. If the requested
                 * index matches, then fill the out_args structure and return,
                 * or return -ENOENT for an empty entry.
                 */
                rc = -ENOENT;
                if (cur_index == index) {
                        if (!phandle)
                                goto err;

                        if (out_args) {
                                int i;
                                if (WARN_ON(count > MAX_PHANDLE_ARGS))
                                        count = MAX_PHANDLE_ARGS;
                                out_args->np = node;
                                out_args->args_count = count;
                                for (i = 0; i < count; i++)
                                        out_args->args[i] = be32_to_cpup(list++);
                        } else {
                                of_node_put(node);
                        }

                        /* Found it! return success */
                        return 0;
                }

                of_node_put(node);
                node = NULL;
                list += count;
                cur_index++;
        }

        /*
         * Unlock node before returning result; will be one of:
         * -ENOENT : index is for empty phandle
         * -EINVAL : parsing error on data
         * [1..n]  : Number of phandle (count mode; when index = -1)
         */
        rc = index < 0 ? cur_index : -ENOENT;
 err:
        if (node)
                of_node_put(node);
        return rc;
}
  • 코드 라인 7~26 cur_index가 인수로 요청한 index와 동일하면 out_args에 다음 값들을 대입하고 성공(0) 값으로 함수를 종료한다.
    • out_args->np에 phandle 값으로 찾은 노드를 대입
    • out_args->args_count에 인수 갯 수
    • out_args->args[] phandle 다음의 인수 값(args_count 수 만큼)
  • 코드 라인 28~32에서 cur_index를 증가시키고 다음 인수를 읽기위해 준비한다.
  • 코드 라인 30~44에서 에러 처리를 위해 요청 index가 음수였던 경우 cur_index 값을 반환하고 그렇지 않은 경우 -ENOENT 값으로 함수를 반환한다.

 

다음 주어진 Device Tree와 예)를 살펴보자.

arch/arm/boot/dts/bcm283x.dtsi – raspberrypi 커널 v4.9.y

                clocks: cprman@7e101000 {
                        compatible = "brcm,bcm2835-cprman";
                        #clock-cells = <1>;
                        reg = <0x7e101000 0x2000>;
                        clocks = <&clk_osc>,
                                <&dsi0 0>, <&dsi0 1>, <&dsi0 2>,
                                <&dsi1 0>, <&dsi1 1>, <&dsi1 2>;
                };

                uart0: serial@7e201000 {
                        compatible = "brcm,bcm2835-pl011", "arm,pl011", "arm,primecell";
                        reg = <0x7e201000 0x1000>;
                        interrupts = <2 25>;
                        clocks = <&clocks BCM2835_CLOCK_UART>,
                                 <&clocks BCM2835_CLOCK_VPU>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x00241011>;
                };

예) of_parse_phandle_with_args(np, “clocks”, “#clock-cells”, index, &clkspec);   np=”uart0″ device_node를 가리키고, index=0으로 한다.

  • uart0 노드의 “clocks” 속성 값에서 0번째 index가 가리키는 값들은 <&clocks BCM2835_CLOCK_UART>이다. 그 중 phandle이 가리키는 clocks 노드에서 “#clock-cells” 속성 값이 <1>이므로 읽어들일 인수의 갯수는 1이다. 따라서 출력 인수 clkspec에 들어갈 내용은 다음과 같다.
    • clkspec->np = clocks 노드
    • clkspec->args_count = 1
    • clkspec->args[] = {BCM2835_CLOCK_UART}

 

IO 매핑

of_iomap()

drivers/of/address.c

/**
 * of_iomap - Maps the memory mapped IO for a given device_node
 * @device:     the device whose io range will be mapped
 * @index:      index of the io range
 *
 * Returns a pointer to the mapped memory
 */
void __iomem *of_iomap(struct device_node *np, int index)
{
        struct resource res;

        if (of_address_to_resource(np, index, &res))
                return NULL;

        return ioremap(res.start, resource_size(&res));
}
EXPORT_SYMBOL(of_iomap);

요청 디바이스 노드가 사용하는 주소 범위를 IO 매핑한다. 매핑 시 요청 디바이스 노드의 상위 노드가 사용하는 버스에 따라 버스 주소를 물리 주소로 변환할 수도 있다.

 

다음 그림은 인터럽트 컨트롤러가 사용하는 주소 범위가 자식 버스 주소이며 이를 변환하여 부모 버스 주소인 ARM 로컬 물리 주소인 0x3f00_b200부터 0x200 사이즈만큼의 영역을 io 매핑하는 모습을 보여준다. io 매핑한 가상 주소 공간을 반환한다.

  • 버스가 여러 개 중첩이 되는 경우 최상위 버스인 플랫폼 버스까지 변환해야 한다.
    • 예) GPU 디바이스에 달려있는 I2C 장치가 사용하는 버스 주소 —> ARM 로컬 물리주소 –> 매핑된 ARM 가상 주소
    • 예) ARM에 연결된 PCI 버스 주소 —> ARM 로컬 물리 주소 –> 매핑된 ARM 가상 주소
    • 조금 더 복잡한 예) GPU 디바이스에 달려있는 PCI 장치 주소 -> GPU의 PCI 버스 주소 —> ARM 로컬 물리 주소 –> 매핑된 ARM 가상 주소

 

of_address_to_resource()

drivers/of/address.c

/**
 * of_address_to_resource - Translate device tree address and return as resource
 *
 * Note that if your address is a PIO address, the conversion will fail if
 * the physical address can't be internally converted to an IO token with
 * pci_address_to_pio(), that is because it's either called to early or it
 * can't be matched to any host bridge IO space
 */
int of_address_to_resource(struct device_node *dev, int index,
                           struct resource *r)
{
        const __be32    *addrp;
        u64             size;
        unsigned int    flags;
        const char      *name = NULL;

        addrp = of_get_address(dev, index, &size, &flags);
        if (addrp == NULL)
                return -EINVAL;

        /* Get optional "reg-names" property to add a name to a resource */
        of_property_read_string_index(dev, "reg-names", index, &name);

        return __of_address_to_resource(dev, addrp, size, flags, name, r);
}
EXPORT_SYMBOL_GPL(of_address_to_resource);

요청 디바이스 노드가 사용하는 주소 범위를 resource 구조체로 구성하여 반환한다. 요청 디바이스 노드의 상위 노드가 사용하는 버스에 따라 버스 주소를 물리 주소로 변환할 수도 있다.

  • 코드 라인 17~19에서 요청 디바이스 노드가 사용하는 물리 주소와 사이즈 및 플래그를 알아온다. 요청 디바이스 노드의 상위 노드가 사용하는 버스에 따라 버스 주소를 물리 주소로 변환할 수도 있다.
  • 코드 라인 22에서 옵션으로 “reg-names” 속성이 있는 경우 리소스 명을 줄 수 있다.
  • 코드 라인 24에서 주소, 사이즈, 플래그, 리소스명을 사용하여 resource 구조체를 구성하여 반환한다.

 

of_get_address()

drivers/of/address.c

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
                    unsigned int *flags)
{
        const __be32 *prop;
        unsigned int psize;
        struct device_node *parent;
        struct of_bus *bus;
        int onesize, i, na, ns;

        /* Get parent & match bus type */
        parent = of_get_parent(dev);
        if (parent == NULL)
                return NULL;
        bus = of_match_bus(parent);
        bus->count_cells(dev, &na, &ns);
        of_node_put(parent);
        if (!OF_CHECK_ADDR_COUNT(na))
                return NULL;

        /* Get "reg" or "assigned-addresses" property */
        prop = of_get_property(dev, bus->addresses, &psize);
        if (prop == NULL)
                return NULL;
        psize /= 4;

        onesize = na + ns;
        for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++)
                if (i == index) {
                        if (size)
                                *size = of_read_number(prop + na, ns);
                        if (flags)
                                *flags = bus->get_flags(prop);
                        return prop;
                }
        return NULL;
}
EXPORT_SYMBOL(of_get_address);

요청 디바이스 노드가 사용할 주소(버스 주소 또는 물리 주소), 사이즈, 플래그 값을 구해온다.

  • 코드 라인 11~13에서 부모 노드를 알아온다. 부모 노드가 없는 경우 null을 반환한다.
  • 코드 라인 14~18에서 부모 노드에서 사용하는 버스를 알아오고 “#addr-cells” 및 “#size-cells” 속성 값을 구해온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
    • 버스 종류: pci, isa, 디폴트 generic
  • 코드 라인 21~23에서 pci 버스인 경우 “assigned-addresses”, 그 외의 버스 타입은 “reg” 속성 값을 읽어오고 못 찾은 경우 null을 반환한다.
  • 코드 라인 24~34에서 인덱스에 해당하는 사이즈 값을 읽어오고 버스 타입에 따른 플래그 값을 알아온다.
  • 코드 라인 35에서 해당하는 인덱스가 없는 경우 null을 반환한다.

 

다음 그림은 디폴트 generic 버스를 사용하는 인터럽트 컨트롤러가 사용하는 주소, 사이즈 및 플래그 값을 산출하는 과정을 보여준다.

 

of_match_bus()

drivers/of/address.c

static struct of_bus *of_match_bus(struct device_node *np)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(of_busses); i++)
                if (!of_busses[i].match || of_busses[i].match(np))
                        return &of_busses[i];
        BUG();
        return NULL;
}

요청한 디바이스 노드가 사용하는 버스를 구해온다.

  • pci, isa, 디폴트 generic 버스

 

__of_address_to_resource()

drivers/of/address.c

static int __of_address_to_resource(struct device_node *dev,
                const __be32 *addrp, u64 size, unsigned int flags,
                const char *name, struct resource *r)
{
        u64 taddr;

        if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0)
                return -EINVAL;
        taddr = of_translate_address(dev, addrp);
        if (taddr == OF_BAD_ADDR)
                return -EINVAL;
        memset(r, 0, sizeof(struct resource));
        if (flags & IORESOURCE_IO) {
                unsigned long port;
                port = pci_address_to_pio(taddr);
                if (port == (unsigned long)-1)
                        return -EINVAL;
                r->start = port;
                r->end = port + size - 1;
        } else {
                r->start = taddr;
                r->end = taddr + size - 1;
        }
        r->flags = flags;
        r->name = name ? name : dev->full_name;

        return 0;
}

요청한 주소를 ARM 물리 주소로 변환하여 리소스 형태로 구성하여 반환한다.

  • 코드 라인 7~8에서 ioresource가 아닌 경우 -EINVAL 결과를 반환한다.
  • 코드 라인 9~11에서 주어진 주소를 버스 변환하여 온다. 만일 변환 테이블 범위에서 매치되지 않는 주소인 경우 -EINVAL 결과를 반환한다.
  • 코드 라인 12~27에서 resource 구조체를 구성한 후 반환한다.

 

버스에 따른 주소 변환

of_translate_address()

drivers/of/address.c

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
        return __of_translate_address(dev, in_addr, "ranges");
}
EXPORT_SYMBOL(of_translate_address);

 

아래 그림은 단순히 부모 버스 주소와 자식 주소체계를 갖는 1 단계 주소 변환만을 보여준다.

  • 부모 버스 주소:
    • 가장 바깥 플랫폼 버스이므로 ARM 로컬 물리 주소이다.
  • 자식 버스 주소:
    • soc 안에서 선언된 노드들의 주소는 ARM 로컬 물리 주소가 아닌 다른 IO 주소를 따른다.

 

__of_translate_address()

drivers/of/address.c

/*
 * Translate an address from the device-tree into a CPU physical address,
 * this walks up the tree and applies the various bus mappings on the
 * way.
 *
 * Note: We consider that crossing any level with #size-cells == 0 to mean
 * that translation is impossible (that is we are not dealing with a value
 * that can be mapped to a cpu physical address). This is not really specified
 * that way, but this is traditionally the way IBM at least do things
 */
static u64 __of_translate_address(struct device_node *dev,
                                  const __be32 *in_addr, const char *rprop)
{
        struct device_node *parent = NULL;
        struct of_bus *bus, *pbus;
        __be32 addr[OF_MAX_ADDR_CELLS];
        int na, ns, pna, pns;
        u64 result = OF_BAD_ADDR;

        pr_debug("OF: ** translation for device %s **\n", of_node_full_name(dev));

        /* Increase refcount at current level */
        of_node_get(dev);

        /* Get parent & match bus type */
        parent = of_get_parent(dev);
        if (parent == NULL)
                goto bail;
        bus = of_match_bus(parent);

        /* Count address cells & copy address locally */
        bus->count_cells(dev, &na, &ns);
        if (!OF_CHECK_COUNTS(na, ns)) {
                pr_debug("OF: Bad cell count for %s\n", of_node_full_name(dev));
                goto bail;
        }
        memcpy(addr, in_addr, na * 4);
        
        pr_debug("OF: bus is %s (na=%d, ns=%d) on %s\n",
            bus->name, na, ns, of_node_full_name(parent));
        of_dump_addr("OF: translating address:", addr, na);

요청 디바이스 노드에서 읽은 주소를 cpu 물리 메모리 주소로 변환한다. 루트 노드 도달 시까지 변환해 나간다.

  • 코드 라인 26~28에서 부모 노드를 알아온다. 부모 노드가 없는 경우 OF_BASD_ADDR 결과를 반환한다.
  • 코드 라인 29~36에서 부모 노드에서 사용하는 버스를 알아오고 버스의 (*count_cells) 후크 함수를 호출하여 주소셀 크기 및 사이즈셀 크기를 알아온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
    • 버스 종류: pci, isa, 디폴트 generic
    • 버스별로 사용할 ranges 속성에서 주소셀 크기와 사이즈셀 크기
      • pci 버스: 주소=3, 사이즈=2
      • isa 버스: 주소=2, 사이즈=1
      • 그 외:  “#addr-cells” 및 “#size-cells” 속성 값을 구해서 사용

 

        /* Translate */
        for (;;) {
                /* Switch to parent bus */
                of_node_put(dev);
                dev = parent;
                parent = of_get_parent(dev);

                /* If root, we have finished */
                if (parent == NULL) {
                        pr_debug("OF: reached root node\n");
                        result = of_read_number(addr, na);
                        break;
                }

                /* Get new parent bus and counts */
                pbus = of_match_bus(parent);
                pbus->count_cells(dev, &pna, &pns);
                if (!OF_CHECK_COUNTS(pna, pns)) {
                        printk(KERN_ERR "prom_parse: Bad cell count for %s\n",
                               of_node_full_name(dev));
                        break;
                }

                pr_debug("OF: parent bus is %s (na=%d, ns=%d) on %s\n",
                    pbus->name, pna, pns, of_node_full_name(parent));

                /* Apply bus translation */
                if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))
                        break;

                /* Complete the move up one level */
                na = pna;
                ns = pns;
                bus = pbus;

                of_dump_addr("OF: one level translation:", addr, na);
        }
 bail:
        of_node_put(parent);
        of_node_put(dev);

        return result;
}
  • 코드 라인 2~13에서 루프를 돌며 상위 노드를 알아온다. 최상위 루트 노드인 경우 그냥 해당 주소를 변환없이 사용하고 루프를 탈출한다.
  • 코드 라인 16~22에서 부모 노드에서 사용하는 버스를 알아오고 버스의 (*count_cells) 후크 함수를 호출하여 주소셀 크기 및 사이즈셀 크기를 알아온다. 만일 구해온 주소 셀 크기가 1~4 범위가 아닌 경우 null을 반환한다.
  • 코드 라인 28~29에서 버스 변환 테이블(range)을 참조하여 cpu 물리 주소로 변환해온다. 변환 실패 시 루프를 빠져나온다.
  • 코드 라인 32~37에서 상위 노드로 이동하고 루트 노드까지 계속 변환을 시도한다.
    • 하이 라키 구조의 버스 변환이 필요한 경우를 지원하기 위해 cascade 한다.

 

of_translate_one()

drivers/of/address.c

static int of_translate_one(struct device_node *parent, struct of_bus *bus,
                            struct of_bus *pbus, __be32 *addr,
                            int na, int ns, int pna, const char *rprop)
{
        const __be32 *ranges;
        unsigned int rlen;
        int rone;
        u64 offset = OF_BAD_ADDR;

        /* Normally, an absence of a "ranges" property means we are
         * crossing a non-translatable boundary, and thus the addresses
         * below the current not cannot be converted to CPU physical ones.
         * Unfortunately, while this is very clear in the spec, it's not
         * what Apple understood, and they do have things like /uni-n or
         * /ht nodes with no "ranges" property and a lot of perfectly
         * useable mapped devices below them. Thus we treat the absence of
         * "ranges" as equivalent to an empty "ranges" property which means
         * a 1:1 translation at that level. It's up to the caller not to try
         * to translate addresses that aren't supposed to be translated in
         * the first place. --BenH.
         *
         * As far as we know, this damage only exists on Apple machines, so
         * This code is only enabled on powerpc. --gcl
         */
        ranges = of_get_property(parent, rprop, &rlen);
        if (ranges == NULL && !of_empty_ranges_quirk(parent)) {
                pr_debug("OF: no ranges; cannot translate\n");
                return 1;
        }
        if (ranges == NULL || rlen == 0) {
                offset = of_read_number(addr, na);
                memset(addr, 0, pna * 4);
                pr_debug("OF: empty ranges; 1:1 translation\n");
                goto finish;
        }

        pr_debug("OF: walking ranges...\n");

        /* Now walk through the ranges */
        rlen /= 4;
        rone = na + pna + ns;
        for (; rlen >= rone; rlen -= rone, ranges += rone) {
                offset = bus->map(addr, ranges, na, ns, pna);
                if (offset != OF_BAD_ADDR)
                        break;
        }
        if (offset == OF_BAD_ADDR) {
                pr_debug("OF: not found !\n");
                return 1;
        }
        memcpy(addr, ranges + na, 4 * pna);

 finish:
        of_dump_addr("OF: parent translation for:", addr, pna);
        pr_debug("OF: with offset: %llx\n", (unsigned long long)offset);

        /* Translate it into parent bus space */
        return pbus->translate(addr, offset, pna);
}

부모 노드의 rprops 속성 값을 읽어 범위로 사용하고 지정된 버스 방식으로 주소를 변환한다.

  • 코드 라인 25~29에서 부모 노드의 rprop 속성 값 주소를 읽어 ranges에 대입한다. 속성을 찾지 못하는 경우 1을 반환한다.
    • of_empty_ranges_quirk() 함수는 ppc 아키텍처에서만 동작하고 그 외의 경우 false를 반환한다.
  • 코드 라인 30~35에서 읽어온 속성 값의 길이가 0인 경우 1:1 변환을 하기 위해 주소 값을 offset에 대입하고, addr를 0으로 한 후 finish 레이블로 이동한다.
  • 코드 라인 40~50에서 ranges 값을 사용하여 변환을 시도하고 변환이 모두 실패한 경우 1을 반환한다.
  • 코드 라인 51에서 변환이 성공한 경우 addr에 현재 ranges의 다음 엔트리인 변환 주소를 대입한다.
  • 코드 라인 58에서 addr에 offset 값을 사용하여 변환한다.
    • default 버스인 경우 addr 값에 offset 값을 더한다.

 

3가지 버스

of_busses[] 배열

drivers/of/address.c

/*
 * Array of bus specific translators
 */

static struct of_bus of_busses[] = {
#ifdef CONFIG_OF_ADDRESS_PCI
        /* PCI */
        {
                .name = "pci",
                .addresses = "assigned-addresses",
                .match = of_bus_pci_match,
                .count_cells = of_bus_pci_count_cells,
                .map = of_bus_pci_map,
                .translate = of_bus_pci_translate,
                .get_flags = of_bus_pci_get_flags,
        },
#endif /* CONFIG_OF_ADDRESS_PCI */
        /* ISA */
        {
                .name = "isa",
                .addresses = "reg",
                .match = of_bus_isa_match,
                .count_cells = of_bus_isa_count_cells,
                .map = of_bus_isa_map,
                .translate = of_bus_isa_translate,
                .get_flags = of_bus_isa_get_flags,
        },
        /* Default */
        {
                .name = "default",
                .addresses = "reg", 
                .match = NULL,
                .count_cells = of_bus_default_count_cells,
                .map = of_bus_default_map,
                .translate = of_bus_default_translate,
                .get_flags = of_bus_default_get_flags,
        },
};

default generic 버스는 pci와 isa 버스 매치가 되지 않은 경우에 사용된다. 따라서 (*match) 핸들러가 null이다.

  • pci와 isa는 생략하고 아래에 defaul generic 버스 핸들러 함수들만 설명한다.

 

디폴트 generic 버스 핸들러

of_bus_default_count_cells()

drivers/of/address.c

/*
 * Default translator (generic bus)
 */

static void of_bus_default_count_cells(struct device_node *dev,
                                       int *addrc, int *sizec)
{
        if (addrc)
                *addrc = of_n_addr_cells(dev);
        if (sizec)
                *sizec = of_n_size_cells(dev);
}

현재 노드에서 가장 가까운 상위 노드에 있는 “#addr-cells” 및 “#size-cells” 속성 값을 읽어와서 출력 인수 addrc 및 sizec에 대입한다.  속성 값이 없는 경우 1을 대입한다.

 

of_n_addr_cells()

drivers/of/base.c

int of_n_addr_cells(struct device_node *np)
{
        const __be32 *ip;

        do {
                if (np->parent)
                        np = np->parent;
                ip = of_get_property(np, "#address-cells", NULL);
                if (ip)
                        return be32_to_cpup(ip);
        } while (np->parent);
        /* No #address-cells property for the root node */
        return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
}
EXPORT_SYMBOL(of_n_addr_cells);

현재 노드에서 가장 가까운 상위 노드에 있는 “#addr-cells” 속성 값을 읽어온다. 찾지 못한 경우 1을 반환한다.

 

of_n_size_cells()

drivers/of/base.c

int of_n_size_cells(struct device_node *np)
{
        const __be32 *ip;

        do {
                if (np->parent)
                        np = np->parent;
                ip = of_get_property(np, "#size-cells", NULL);
                if (ip)
                        return be32_to_cpup(ip);
        } while (np->parent);
        /* No #size-cells property for the root node */
        return OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
}
EXPORT_SYMBOL(of_n_size_cells);

현재 노드에서 가장 가까운 상위 노드에 있는 “#size-cells” 속성 값을 읽어온다. 찾지 못한 경우 1을 반환한다.

 

of_bus_default_map()

drivers/of/address.c

static u64 of_bus_default_map(__be32 *addr, const __be32 *range,
                int na, int ns, int pna)
{
        u64 cp, s, da;

        cp = of_read_number(range, na);
        s  = of_read_number(range + na + pna, ns);
        da = of_read_number(addr, na);

        pr_debug("OF: default map, cp=%llx, s=%llx, da=%llx\n",
                 (unsigned long long)cp, (unsigned long long)s,
                 (unsigned long long)da);

        if (da < cp || da >= (cp + s))
                return OF_BAD_ADDR;
        return da - cp;
}

요청한 주소가 range에 포함된 경우 요청한 주소에서 range 시작 주소를 뺀 주소 값을 반환한다.

  • 코드 라인 6에서 range 주소에서 값을 읽어 cp에 대입한다.
  • 코드 라인 7에서 그 다음 값을 읽어 s에 대입한다.
  • 코드 라인 8에서 주소값을 읽어 da에 대입한다.
  • 코드 라인 14에서 cp보다 da가 작거나 cp+s보다 da가 큰 경우 OF_BAD_ADDR을 반환한다.
  • 코드 라인 15에서 주소 값에서 range 값을 뺀다.

 

예) ranges = <0x7e000000 0x3f000000 0x1000000 0x40000000 0x40000000 0x40000>, addr=0x7e00b200, na=1, ns=1, pna=1인 경우

  • 버스1 시작 주소=0x7e00_0000, 물리 시작 주소=0x3f00_0000, 사이즈=0x1000_0000
  • 버스2 시작 주소=0x4000_0000, 물리 시작 주소=0x4000_0000, 사이즈=0x4_0000

-> addr이 range 범위(0x7e00_0000 ~ (0x7e00_0000 + 0x1000_0000)) 이내인 경우 범위 시작 주소를 뺀 주소 0xb200을 반환한다.

 

of_bus_default_translate()

drivers/of/address.c

static int of_bus_default_translate(__be32 *addr, u64 offset, int na)
{
        u64 a = of_read_number(addr, na);
        memset(addr, 0, na * 4);
        a += offset;
        if (na > 1)
                addr[na - 2] = cpu_to_be32(a >> 32);
        addr[na - 1] = cpu_to_be32(a & 0xffffffffu);

        return 0;
}

addr[]에 offset을 더해 반환한다.

  • 코드 라인 3에서 addr[] 값을 읽어서 64비트 값으로 반환한다. (na=1 또는 2)
  • 코드 라인 5에서 읽은 값에 offset을 더한다.
  • 코드 라인 6~8에서 더한 값을 다시 addr[] 값에 저장한다.
    • 예) addr[0] = 0x1111_1111, addr[1]=0x2222_2222, offset=0x1000_1000_2000_2000, na=2
      • addr[0]=0x2111_2111, addr[1]=0x4222_4222

 

of_bus_default_get_flags()

drivers/of/address.c

static unsigned int of_bus_default_get_flags(const __be32 *addr)
{
        return IORESOURCE_MEM;
}

default 버스의 플래그로 IORESOURCE_MEM을 반환한다.

 

MSI 정보를 읽어 디바이스의 msi_domain 갱신

of_msi_configure()

drivers/of/irq.c

/**
 * of_msi_configure - Set the msi_domain field of a device
 * @dev: device structure to associate with an MSI irq domain
 * @np: device node for that device
 */
void of_msi_configure(struct device *dev, struct device_node *np)
{
        dev_set_msi_domain(dev,
                           of_msi_get_domain(dev, np, DOMAIN_BUS_PLATFORM_MSI));
}
EXPORT_SYMBOL_GPL(of_msi_configure);

플랫폼 디바이스의 디바이스 트리 노드에서 msi 정보를 읽어 플랫폼 디바이스의 msi_domain 필드를 갱신한다.

 

static inline void dev_set_msi_domain(struct device *dev, struct irq_domain *d)
{
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
        dev->msi_domain = d;
#endif
}

디바이스의 msi_domain 필드를 설정한다.

 

of_msi_get_domain()

drivers/of/irq.c

/**     
 * of_msi_get_domain - Use msi-parent to find the relevant MSI domain
 * @dev: device for which the domain is requested
 * @np: device node for @dev
 * @token: bus type for this domain
 *      
 * Parse the msi-parent property (both the simple and the complex
 * versions), and returns the corresponding MSI domain. 
 *
 * Returns: the MSI domain for this device (or NULL on failure).
 */
struct irq_domain *of_msi_get_domain(struct device *dev,
                                     struct device_node *np,
                                     enum irq_domain_bus_token token)
{
        struct device_node *msi_np;
        struct irq_domain *d;

        /* Check for a single msi-parent property */
        msi_np = of_parse_phandle(np, "msi-parent", 0);
        if (msi_np && !of_property_read_bool(msi_np, "#msi-cells")) {
                d = irq_find_matching_host(msi_np, token);
                if (!d)
                        of_node_put(msi_np);
                return d; 
        }

        if (token == DOMAIN_BUS_PLATFORM_MSI) {
                /* Check for the complex msi-parent version */
                struct of_phandle_args args;
                int index = 0;

                while (!of_parse_phandle_with_args(np, "msi-parent",
                                                   "#msi-cells",
                                                   index, &args)) {
                        d = irq_find_matching_host(args.np, token);
                        if (d)
                                return d;

                        of_node_put(args.np);
                        index++;
                }
        }

        return NULL;
}

플랫폼 디바이스의 디바이스 트리 노드에서 msi 정보를 읽어와서 irq_domain을 반환한다. 검색이 실패한 경우 null을 반환한다.

  • 코드 라인 20에서 인자로 전달받은 디바이스 노드에서 “msi-parent” 속성의 phandle 값이 가리키는 msi 노드를 알아온다.
  • 코드 라인 21~26에서 msi 노드에서 “#msi-cells” 속성이 없는 경우 msi 노드에서 인자로 전달받은 token으로 irq 도메인을 찾아온 후 반환한다.
  • 코드 라인 28~43에서 token이 DOMAIN_BUS_PLATFORM_MSI인 경우에 한해 “msi-parent” 속성의 phandle 리스트가 가리키는 msi 노드들을 대상으로 순회하며 인자로 전달받은 token으로 irq 도메인을 찾아온 후 반환한다.

 

다음 그림은 msi를 사용하는 플랫폼 디바이스에서 irq 도메인을 찾아 플랫폼 디바이스에 저장하는 모습을 보여준다.

 

구조체

of_device_id 구조체

/*
 * Struct used for matching a device
 */
struct of_device_id { 
        char    name[32];
        char    type[32]; 
        char    compatible[128];
        const void *data;
};
  • name
    • 노드명
  • type
    • 디바이스 타입 문자열
  • compatible
    • compatible 디바이스 문자열
  • data
    • 데이터
      • PSCI에서 사용하는 경우 psci_initcall_t() 함수 포인터 등이 담겨있다.

 

참고

DTB (fdt API)

DTB 관련 API는 다음과 같이 구분된다.

  • fdt로 시작하는 API는 DTB(Device Tree Blob)를 대상으로 동작한다.
  • of로 시작하는 API는 링크드 리스트 구조의 expanded format으로 변환된 Device Tree를 대상으로 동작한다.
    • DTB는 unflatten_device_tree() 함수에의해 변환된다.

 

fdt_get_name()

scripts/dtc/libfdt/fdt_ro.c

const char *fdt_get_name(const void *fdt, int nodeoffset, int *len)
{
        const struct fdt_node_header *nh = _fdt_offset_ptr(fdt, nodeoffset);
        int err;

        if (((err = fdt_check_header(fdt)) != 0)
            || ((err = _fdt_check_node_offset(fdt, nodeoffset)) < 0)) 
                        goto fail;

        if (len)
                *len = strlen(nh->name);

        return nh->name;

 fail:
        if (len)
                *len = err;
        return NULL;
}
  • fdt가 가리키는 DTB에서 nodeoffset에 위치한 노드에서 노드명 주소와 노드명 길이를 알아온다.

 

fdt_get_name-1a

 

_fdt_offset_ptr()

scripts/dtc/libfdt/libfdt_internal.h

static inline const void *_fdt_offset_ptr(const void *fdt, int offset)
{
        return (const char *)fdt + fdt_off_dt_struct(fdt) + offset;
}

DTB의 structure 블럭 시작 주소에 offset를 더한 주소를 리턴한다..

__fdt_offset_ptr-1a

 

fdt_offset_ptr()

scripts/dtc/libfdt/fdt.c

const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len)
{
        const char *p; 

        if (fdt_version(fdt) >= 0x11)
                if (((offset + len) < offset)
                    || ((offset + len) > fdt_size_dt_struct(fdt)))
                        return NULL;

        p = _fdt_offset_ptr(fdt, offset);

        if (p + len < p)
                return NULL;
        return p;
}

DTB structure 블럭에서 offset 만큼 떨어진 주소를 알아오는데 그 주소의 위치가 DTB structure 블럭을 벗어난 경우 에러로 null을 리턴한다.

  • if (fdt_version(fdt) >= 0x11
    • fdt 주소에 위치한 DTB 버전이 0x11이상인 경우 len이 음수이거나 물리주소를 초과한 경우
  • if (((offset + len) < offset) || ((offset + len) > fdt_size_dt_struct(fdt))) return NULL;
    • offset+len이 DTB structure 블럭 사이즈를 초과한 경우 null로 리턴
  • p = _fdt_offset_ptr(fdt, offset);
    • DTB structure 블럭에서 offset 만큼 떨어진 주소를 알아온다.
  •  if (p + len < p) return NULL;
    • 알아온 주소가 물리 주소를 초과한 경우 null을 리턴한다.

 

unflatten_dt_alloc()

drivers/of/fdt.c

static void *unflatten_dt_alloc(void **mem, unsigned long size,
                                       unsigned long align)
{
        void *res;

        *mem = PTR_ALIGN(*mem, align);
        res = *mem;
        *mem += size;

        return res; 
}
  • 메모리 주소 mem을 align 단위로 round up한 후 리턴하고 메모리 주소 mem에 사이즈를 더한다.

 

fdt_next_tag()

scripts/dtc/libfdt/fdt.c

uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
{
        const uint32_t *tagp, *lenp;
        uint32_t tag;
        int offset = startoffset;
        const char *p;

        *nextoffset = -FDT_ERR_TRUNCATED;
        tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE);
        if (!tagp)
                return FDT_END; /* premature end */
        tag = fdt32_to_cpu(*tagp);
        offset += FDT_TAGSIZE;

        *nextoffset = -FDT_ERR_BADSTRUCTURE;
        switch (tag) {
        case FDT_BEGIN_NODE:
                /* skip name */
                do {
                        p = fdt_offset_ptr(fdt, offset++, 1);
                } while (p && (*p != '\0'));
                if (!p)
                        return FDT_END; /* premature end */
                break;

        case FDT_PROP:
                lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp));
                if (!lenp)
                        return FDT_END; /* premature end */
                /* skip-name offset, length and value */
                offset += sizeof(struct fdt_property) - FDT_TAGSIZE
                        + fdt32_to_cpu(*lenp);
                break;

        case FDT_END:
        case FDT_END_NODE:
        case FDT_NOP:
                break;

        default:
                return FDT_END;
        }

        if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset))
                return FDT_END; /* premature end */

        *nextoffset = FDT_TAGALIGN(offset);
        return tag;
}

startoffset에 위치한 태그의 값을 알아오고 startoffset 다음에 위치한 태그 offset를 출력 인수 nextoffset에 저장한다.

  • startoffset 위치의 태그번호를 리턴하는데 태그 종류에 따라 출력 인수 nextoffset에 저장되는 offset가 다음과 같이 다르다.
    • FDT_BEGIN_NODE인 경우 4 byte 태그 및 이름을 skip 한 4 byte round up한 offset
    • FDT_PROP인 경우 4 byte 태그, 속성 길이, name offset, 속성명을 skip한 4 byte round up한 offset
    • 그 외의 태그는 4 byte 태그만 더한 offset

아래 그림은 fdt_next_tag()를 두 번 반복 수행하는 모습을 보여준다.

fdt_next_tag-1b

 

 

fdt_first_property_offset()

scripts/dtc/libfdt/fdt_ro.c

int fdt_first_property_offset(const void *fdt, int nodeoffset)
{
        int offset;

        if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0)
                return offset;

        return _nextprop(fdt, offset);
}

노드 내의 처음 속성 offset를 리턴한다. 속성이 없으면 -값 에러를 리턴한다.

  • if ((offset = _fdt_check_node_offset(fdt, nodeoffset)) < 0) return offset;
    • nodeoffset에 위치한 태그가 노드 시작이 아닌 경우 -값 에러(offset)를 리턴한다.
  • return _nextprop(fdt, offset);
    • 처음 발견되는 속성 offset를 리턴한다.

 

아래 그림은 노드에서 처음 발견되는 속성에 대한 offset를 알아오는 것을 표현하였다.

fdt_first_property_offset-1

 

 

_fdt_check_node_offset()

scripts/dtc/libfdt/fdt.c

int _fdt_check_node_offset(const void *fdt, int offset)
{
        if ((offset < 0) || (offset % FDT_TAGSIZE)
            || (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE))
                return -FDT_ERR_BADOFFSET;

        return offset;
}

주어진 offset에 있는 태그가 시작 노드인 경우에만 offset를 리턴하고 그렇지 않으면 -값 에러를 리턴한다.

  • offset가 0보다 작거나 4 byte align 되어 있지 않거나 현재 태그가 노드 시작이 아니면 -값의 에러를 리턴하고 그렇지 않은 경우 offset를 리턴한다.

 

_nextprop()

scripts/dtc/libfdt/fdt_ro.c

static int _nextprop(const void *fdt, int offset)
{
        uint32_t tag;
        int nextoffset;

        do {
                tag = fdt_next_tag(fdt, offset, &nextoffset);

                switch (tag) {
                case FDT_END:
                        if (nextoffset >= 0)
                                return -FDT_ERR_BADSTRUCTURE;
                        else
                                return nextoffset;

                case FDT_PROP:
                        return offset;
                }
                offset = nextoffset;
        } while (tag == FDT_NOP);

        return -FDT_ERR_NOTFOUND;
}

해당 노드 내에서 다음 속성 태그 offset을 알아온다. 만일 노드가 끝나면 -값으로 에러를 리턴한다.

  • 태그가 FDT_NOP인 경우에 루프를 돌며 다음 태그를 읽어오다가 FDT_PROP를 만나면 해당 offset를 리턴한다. 만일 FDT_END를 만나는 경우 -값으로 에러를 리턴한다.

 

아래 그림은 _nextprop()를 호출할 때 해당 노드내에서 다음 속성에 대한 offset를 읽어 오는데 노드가 바뀌거나 끝나는 경우 에러를 리턴하는 것을 보여준다.

_nextprop-1

 

fdt_next_property_offset()

scripts/dtc/libfdt/fdt_ro.c

int fdt_next_property_offset(const void *fdt, int offset)
{
        if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0) 
                return offset;
                
        return _nextprop(fdt, offset);
}

노드 내의 다음 속성 offset를 리턴한다. 속성이 없으면 -값 에러를 리턴한다.

  • if ((offset = _fdt_check_prop_offset(fdt, offset)) < 0) return offset;
    • offset에 위치한 태그가 속성이 아닌 경우 -값 에러(offset)를 리턴한다.
  • return _nextprop(fdt, offset);
    • 다음 발견되는 속성 offset를 리턴한다.

 

_fdt_check_prop_offset()

scripts/dtc/libfdt/fdt.c()

int _fdt_check_prop_offset(const void *fdt, int offset)
{
        if ((offset < 0) || (offset % FDT_TAGSIZE)
            || (fdt_next_tag(fdt, offset, &offset) != FDT_PROP))
                return -FDT_ERR_BADOFFSET;

        return offset;
}

주어진 offset에 있는 태그가 속성인 경우에만 offset를 리턴하고 그렇지 않으면 -값 에러를 리턴한다.

  • offset가 0보다 작거나 4 byte align 되어 있지 않거나 현재 태그가 속성이 아니면 -값의 에러를 리턴하고 그렇지 않은 경우 offset를 리턴한다.

 

fdt_getprop_by_offset()

scripts/dtc/libfdt/fdt_ro.c

const void *fdt_getprop_by_offset(const void *fdt, int offset,
                                  const char **namep, int *lenp)
{
        const struct fdt_property *prop;
                
        prop = fdt_get_property_by_offset(fdt, offset, lenp);
        if (!prop)      
                return NULL;
        if (namep)
                *namep = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
        return prop->data;
}

속성 값을 리턴하는데 namep 출력 인수는 속성명이 저장된 string 블록의 주소를 저장하고 , lenp에는 데이터 길이를 저장한다. 리턴 값이 음수인 경우는 에러이다.

 

fdt_get_property_by_offset()

scripts/dtc/libfdt/fdt_ro.c

const struct fdt_property *fdt_get_property_by_offset(const void *fdt,
                                                      int offset,
                                                      int *lenp)
{
        int err;
        const struct fdt_property *prop;

        if ((err = _fdt_check_prop_offset(fdt, offset)) < 0) {
                if (lenp)
                        *lenp = err;
                return NULL;
        }

        prop = _fdt_offset_ptr(fdt, offset);

        if (lenp)
                *lenp = fdt32_to_cpu(prop->len);

        return prop;
}

offset 위치의 속성 태그에 위치한 주소를 fdt_property 구조체 포인터로 캐스트하여 리턴하고 lenp 출력 인수에는 데이터 길이를 리턴한다.

 

아래 그림은 속성값이 있는 곳의 주소를 fdt_property 구조체 포인터로 캐스트하여 알아오는 것을 보여준다.

fdt_get_property_by_offset-1

 

fdt_string()

scripts/dtc/libfdt/fdt_ro.c

const char *fdt_string(const void *fdt, int stroffset)
{
        return (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset;
}

DTB에서 주어진 문자열 offset(stroffset)에 대한 주소를 알아온다.

 

fdt_next_node()

scripts/dtc/libfdt/fdt.c

int fdt_next_node(const void *fdt, int offset, int *depth)
{
        int nextoffset = 0;
        uint32_t tag;

        if (offset >= 0)
                if ((nextoffset = _fdt_check_node_offset(fdt, offset)) < 0)
                        return nextoffset;

        do {
                offset = nextoffset;
                tag = fdt_next_tag(fdt, offset, &nextoffset); 

                switch (tag) {
                case FDT_PROP:
                case FDT_NOP:
                        break;

                case FDT_BEGIN_NODE:
                        if (depth)
                                (*depth)++;
                        break;

                case FDT_END_NODE:
                        if (depth && ((--(*depth)) < 0))
                                return nextoffset;
                        break;

                case FDT_END:
                        if ((nextoffset >= 0)
                            || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth))
                                return -FDT_ERR_NOTFOUND;
                        else
                                return nextoffset;
                }
        } while (tag != FDT_BEGIN_NODE);

        return offset;
}

다음 노드의 offset를 리턴하고 출력 인수에 depth를 저장한다.

  • if (offset >= 0) if ((nextoffset = _fdt_check_node_offset(fdt, offset)) < 0) return nextoffset;
    • 주어진 offset가 노드가 아닌 경우 null을 리턴한다.
  • 루프를 돌며 태그 번호와 다음 태그 offset을 알아오고 태그에 따라 다음과 같이 동작한다.
    • FDT_PROP이거나 FDT_NOP인 경우 skip 한다.
    • FDT_BEGIN_NODE인 경우 depth 증가하게 되고 루프를 벗어나게 된다.
    • FDT_END_NODE인 경우 depth가 0이상이면서 감소 시킨 depth가 0보다 작은 경우 nextoffset를 리턴한다. (보통 또 다른 시작 노드)
    • FDT_END인 경우 음수 값인 에러를 리턴한다.

 

DTB 헤더 관련 API

scripts/dtc/libfdt/libfdt.h


#define fdt_get_header(fdt, field) \
        (fdt32_to_cpu(((const struct fdt_header *)(fdt))->field))
#define fdt_magic(fdt)                  (fdt_get_header(fdt, magic))
#define fdt_totalsize(fdt)              (fdt_get_header(fdt, totalsize))
#define fdt_off_dt_struct(fdt)          (fdt_get_header(fdt, off_dt_struct))
#define fdt_off_dt_strings(fdt)         (fdt_get_header(fdt, off_dt_strings))
#define fdt_off_mem_rsvmap(fdt)         (fdt_get_header(fdt, off_mem_rsvmap))
#define fdt_version(fdt)                (fdt_get_header(fdt, version))
#define fdt_last_comp_version(fdt)      (fdt_get_header(fdt, last_comp_version))
#define fdt_boot_cpuid_phys(fdt)        (fdt_get_header(fdt, boot_cpuid_phys))
#define fdt_size_dt_strings(fdt)        (fdt_get_header(fdt, size_dt_strings))
#define fdt_size_dt_struct(fdt)         (fdt_get_header(fdt, size_dt_struct))
  • 디바이스 트리의 헤더에는 10가지의 정보가 있는데 각각에 대해 함수로 제공된다..
  • fdt_magic()
    • DTB 매직 넘버를 알아온다.
  • fdt_totalsize()
    • DTB 전체 사이즈를 알아온다.
  • fdt_off_dt_struct()
    • structure 블럭 시작 offset를 알아온다.
  • fdt_off_dt_strings()
    • string 블럭 시작 offset를 알아온다.
  • fdt_off_mem_rsvmap()
    • memory reserved 블럭 시작 offset를 알아온다.
  • fdt_version()
    • DTB 버전 정보를 알아온다.
  • fdt_last_comp_version
    • DTB 마지막 호환 버전을 알아온다.
  • fdt_boot_cpuid_phys()
  • fdt_size_dt_strings()
    • string 블럭 사이즈를 알아온다.
  • fdt_sizae_dt_struct()
    • structure 블럭 사이즈를 알아온다.

다음은 rpi2 DTB 헤더의 hex 값이다.

fdt_header-1

 

구조체

fdt_header 구조체

scripts/dtc/libfdt/fdt.h

struct fdt_header { 
        uint32_t magic;                  /* magic word FDT_MAGIC */
        uint32_t totalsize;              /* total size of DT block */
        uint32_t off_dt_struct;          /* offset to structure */
        uint32_t off_dt_strings;         /* offset to strings */
        uint32_t off_mem_rsvmap;         /* offset to memory reserve map */
        uint32_t version;                /* format version */
        uint32_t last_comp_version;      /* last compatible version */
                
        /* version 2 fields below */
        uint32_t boot_cpuid_phys;        /* Which physical CPU id we're
                                            booting on */
        /* version 3 fields below */
        uint32_t size_dt_strings;        /* size of the strings block */
                
        /* version 17 fields below */
        uint32_t size_dt_struct;         /* size of the structure block */
};

 

 

fdt_node_header 구조체

scripts/dtc/libfdt/fdt.h

struct fdt_node_header {
        uint32_t tag;
        char name[0];
};
  • tag
    • 1=FDT_BEGIN_NODE
    • 2=FDT_END_NODE
    • 3=FDT_PROP
    • 4=FDT_NOP
    • 9=FDT_END
  • name[0]
    • tag가 1번인 경우 노드명의 첫 글자

 

device_node 구조체

include/linux/of.h

struct device_node {
        const char *name;
        const char *type;
        phandle phandle;
        const char *full_name;
        struct fwnode_handle fwnode; 

        struct  property *properties;
        struct  property *deadprops;    /* removed properties */
        struct  device_node *parent;
        struct  device_node *child;
        struct  device_node *sibling;
        struct  kobject kobj;
        unsigned long _flags;
        void    *data;
#if defined(CONFIG_SPARC)
        const char *path_component_name;
        unsigned int unique_id; 
        struct of_irq_controller *irq_trans;
#endif
};
  • name
    • 주소 없는 compact 스타일 노드명이 담긴다.
    • 예) “”, “chosen”, “uart”
  • type
    • 디바이스 타입명이 담긴다.
    • 예) “memory”, “cpu”, “pci”, “ethernet-phy”, “network”
  •  phandle
    • 노드내 “phandle” 속성이 가리키는 노드
  • full_name
    • full path 노드명이 담긴다.
    • 예) “/”, “/chosen”, “/soc/uart@7e201000”
  • fwnode
    • 0=FWNODE_INVALID
    • 1=FWNODE_OF (초기값)
    • 2=FWNODE_ACPI
  • properties
    •  속성값이 있는 경우 property 노드를 가리키고 없으면 null
  • deadprops
  • parent
    • 부모 노드를 가리키고 자신이 루트 노드인 경우 null
  • child
    • 자식 노드들 중 첫 노드를 가리키고 없으면 null
  • sibling
    • 바로 다음 형재 노드를 가리키고 없으면 null
  • kboj
    • object 라이프 사이클을 관리
  • _flags
  • data
    • 4바이트로 표현 가능한 노드 값이 있거나 데이터 위치를 가리키고 없으면 null

 

property 구조체

include/linux/of.h

struct property {
        char    *name;
        int     length;
        void    *value;
        struct property *next;
        unsigned long _flags;
        unsigned int unique_id;
        struct bin_attribute attr;
};
  • name
    • 속성명
    • 예) “name”, “stdout-path”, “reg”
  • length
    • 속성 값 길이
  • value
    • 속성 값이 위치한 곳을 가리키고 없으면 null
  • next
    • 노드내의 다음 속성을 가리키고 없으면 null
  • _flags
  • unique_id
  • attr

 

참고