smp_setup_processor_id()에서 로지컬 cpu id 배열을 구성하였었는데 이 함수에서 다시 DTB와 비교하여 재 구성하고 특정 SMP 아키텍처의 SMP 핸들러가 준비되어 있는 경우 전역 smp_ops를 설정 한다.
- “/cpu” 노드에서 읽은 reg 속성 값 순서대로 로지컬 맵을 구성하되 예외로 현재 부팅된 물리 cpu id는 로지컬 cpu id 0으로 구성한다.
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
- mpidr에 cpu 물리 id를 읽어온다.
- 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[]
- i 논리 cpu 번호에 물리 cpu id를 저장한다.
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이다.
- __cpu_method_of_table의 시작부터 끝까지
- 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”;
- “/cpus” 노드의 enable-method 속성 값
- ops
- 해당 SMP 머신의 각 핸들러가 속해있는 smp_operations 구조체를 가리킨다.