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 구조체를 가리킨다.

 

 

참고

댓글 남기기