티스토리 뷰

7.3 Secondary Booting

boot cpu 로 커널을 초기화 한 후 non-boot cpu 를 초기화

7.3.1 ARM: SMP 오퍼레이션

  • bootcpu 가 smp cpu 를 init 후 boot_secondary 호출

ARM 의 SMP 오퍼레이션

  • cpu_kill, cpu_die, cpu_can_disable, cpu_disable

7.3.2 AEM64: cpu 오퍼레이션

  • ARM64 커널을 활용하는 플랫폼은 부팅을 위해 펌웨어나 다른 인터페이스가 필요한 경우가 생겨, SMP 오퍼레이션을 사용하지 않는다.
  • 기존 ARM 의 smp_operations 을 사용하지 않고 cpu_operations 구조체를 사용한다.

ARM64 의 cpu operation 종류

디바이스 트리에따라 결정됨. cpu_operations 구조체에는 SMP 부팅 정보가 들어가게됨

fdt 의 enable-method 이름이 spin-table, psci 로 나뉨

spin-table 의 경우 bootcpu 를 제외한 나머지 cpu 가 폴링하는 주소를 갖고있어야 하기 때문에, cpu-release-addr 이 정의되어있어야 함.

  • psci: 하이퍼바이저나 펌웨어를 통해 수행
  • spin-table: pc 에 secondary boot 를 지정하여 부팅

7-7 arch/arm64/kernel/cpu_ops.c cpu_get_ops

cpu_operations 찾기.

dt : spin-table 우선 / acpi : psci

static const struct cpu_operations * __init cpu_get_ops(const char *name)
{
    const struct cpu_operations **ops;
    ops = acpi_disabled ? dt_supported_cpu_ops : acpi_supported_cpu_ops;
    while (*ops++) {
        if (!strcmp(name, (*ops)->name)) return *ops;
    }
}

7-8 kernel/cpu.c cpu_read_bootcpu_ops, 7-9 kernel/cpu_ops.c cpu_read_ops

  • 0 번 cpu 의 ops 를 반환
  • cpu 의 ops 를 반환

7.3.3 PSCI(Power State Coordination Interface)

  • 하이퍼바이저 또는 보안 모니터상 구동되는 펌웨어가 구현할 수 있는 프로토콜. 전원 상태를 조정
  • OS 와 SPF(Secure Platform Firmware) 사이 다른 권한 수준에서 작동하는 소프트웨어 간 cpu 전원 관리를 가상화

SMC(Secure Monitor Call) 호출 규칙

  • 보안 모니터 호출 : EL3 으로 이동
  • 하이퍼 바이저 호출 : EL2 로 이동 (SPF 가 없는 플랫폼에 적합)

PSCI 통신 인터페이스

device tree 의 enable-method 에 따라 인터페이스가 갈림

const struct cpu_operations smp_spin_table_ops // 전원 관리 기능이 없음
const struct cpu_operations cpu_psci_ops

7-10 arch/arm64/kernel/setup.c psci_dt_init

device tree 를 읽고, 초기화 함수를 등록 & 호출

7.3.4 ARM64 non-boot cpu 부팅

7-11 arch/arm64/kernel/smp.c smp_init_cpus

PSCI is a low-level firmware interface for power management on ARM systems.
ACPI is a higher-level hardware abstraction standard, commonly used in ARM64 servers.

ACPI 사용하지 않을때

static void __init of_parse_and_init_cpus(void)
{
    struct device_node *dn = NULL;

    while ((dn = of_find_node_by_type(dn, "cpu"))) {
        u64 hwid = of_get_cpu_mpidr(dn); // Multiprocessor Affinity Register 로 id 획득

        if (is_mpidr_duplicate(cpu_count, hwid)) {goto next;} // 중복 체크


        if (hwid == cpu_logical_map(0)) { // 부팅 cpu 스킵
            continue;
        }

        if (cpu_count >= NR_CPUS) goto next;

        pr_debug("cpu logical map 0x%llx\n", hwid);
        cpu_logical_map(cpu_count) = hwid; // i 번 인덱스에 id 를 등록
next:
        cpu_count++;
    }
}

ACPI 를 사용할때

  • SoC(System on a chip 완전 구동이 가능한 제품과 시스템이 한 개의 칩에 들어 있는 것) 의 언터럽트 사용

7-12 arch/arm64/kernel/smp.c smp_cpu_setup

  • cpu ops 등록과 possible map 에 등록

7-13 kernel/cpu.c boot_cpu_state_init

  • boot cpu 을 CPUHP_ONLINE 상태로 초기화화 (HP = Hot Plug)

7-14 arch/arm64/kernel/smp.c smp_prepare_boot_cpu

  • 부팅중이거나 온라인 상태가 될때 각 cpu 별로 feature 레지스터가 필요함
  • non-boot-cpu 의 데이터가 boot cpu 데이터로 복사될 염려가 있음. __cpuinfo_store_cpu 가 현재 cpu 의 정보를 덮어씌우기 때문
  • 따라서 boot_cpu_data 라는 전역변수로 boot cpu 의 feature 레지스터를 관리

cpu 의 속성을 기록해둔 정도로 알고 가자

cpu feature register : read-only registers that provide information about the capabilities and features of a CPU

TPIDRPRW : Thread Process ID Register PL1 Privileged Read/Write

Privilege Level

PL0: 유저 / PL1: 커널 / PL2: 하이퍼바이저 / PL3: 보안 모니터

void __init cpuinfo_store_boot_cpu(void)
{
    struct cpuinfo_arm64 *info = &per_cpu(cpu_data, 0);
    __cpuinfo_store_cpu(info);

    boot_cpu_data = *info;
    init_cpu_features(&boot_cpu_data);
}

static inline void set_my_cpu_offset(unsigned long off)
{
    asm volatile("msr tpidr_el1, %0" :: "r" (off) : "memory");
}

void __init smp_prepare_boot_cpu(void)
{
    cpuinfo_store_boot_cpu();
    set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
}

7-15 arch/arm64/kernel/smp.c smp_prepare_cpus

void __init smp_prepare_cpus(unsigned int max_cpus)
{
    init_cpu_topology() // cpu_topology[cpu all] 초기화 
    smp_store_cpu_info(smp_processor_id()); // cpu topolgy 구조체 설정

    for_each_possible_cpu(cpu) {
        err = cpu_ops[cpu]->cpu_prepare(cpu);
        set_cpu_present(cpu, true);
    }
}

7-16 kernel/smp.c smp_init

void __init smp_init(void)
{
    unsigned int cpu;

    idle_threads_init(); // 부트 cpu 를 제외한 각 cpu Idle thread 생성
    cpuhp_threads_init(); // 7-17 cpu 당 핫플러그를 위한 쓰레드 초기화

    for_each_present_cpu(cpu) {
        if (num_online_cpus() >= setup_max_cpus)
            break;
        if (!cpu_online(cpu)) // cpu 활성화
            cpu_up(cpu);
    }

    smp_announce(); // 7-18 온라인 cpu 출력
    smp_cpus_done(setup_max_cpus); 7-19

7-19 arch/arm64/kernel/smp.c smp_cpus_done

void __init smp_cpus_done(unsigned int max_cpus)
{
    pr_info("SMP: Total of %d processors activated.\n", num_online_cpus());
    setup_cpu_features();
    hyp_mode_check();
    apply_alternatives_all(); // boot cpu 를 제외한 모든 온라인 cpu  를 중지  why?
}

void __init apply_alternatives_all(void)
{
    /* better not try code patching on a live SMP system */
    stop_machine(__apply_alternatives_multi_stop, NULL, cpu_online_mask);
}

7-20 init/main.c do_pre_smp_initcalls

__initcalls_start ~ __initcall0_start 사이의 함수를 실행. smp 용 initcall 이라고 함

7-21 kernel/cpu.c cpu_up

int cpu_up(unsigned int cpu)
{
    return do_cpu_up(cpu, CPUHP_ONLINE);
}

7-22 kernel/cpu.c do_cpu_up


static int do_cpu_up(unsigned int cpu, enum cpuhp_state target)
{
    if (!cpu_possible(cpu)) {return -EINVAL;}

    err = try_online_node(cpu_to_node(cpu));
    if (err) return err;

    cpu_maps_update_begin(); // mutex lock. cpu_online_mask 뼌경을 위함

    if (cpu_hotplug_disabled) {
        err = -EBUSY;
        goto out;
    }

    err = _cpu_up(cpu, 0, target); // 7-23
out:
    cpu_maps_update_done(); // unlock
    return err;
}

7-23 kernel/cpu.c _cpu_up

static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
{
    struct cpuhp_cpu_state *st = per_cpu_ptr(&cpuhp_state, cpu);

    if (st->state == CPUHP_OFFLINE) { // offline 인 경우 idle 쓰레드로 지정
        idle = idle_thread_get(cpu);
        if (IS_ERR(idle)) {
            ret = PTR_ERR(idle);
            goto out;
        }
    }

    st->target = target;

    if (st->state > CPUHP_BRINGUP_CPU) { // 이미 브링업 된 상태
        ret = cpuhp_kick_ap_work(cpu);
        if (ret)
            goto out;
    }

    target = min((int)target, CPUHP_BRINGUP_CPU); // cpu  up 을 수행하기 위해 목표값(target) 을 최소 bring up 으로 설정
    ret = cpuhp_up_callbacks(cpu, st, cpuhp_bp_states, target);
out:
    cpu_hotplug_done();
    return ret;
}

7-24 cpuhp_up_callbacks

현재상태부터 target 상태까지 단계별로 필요한 콜백 호출,

실패할 경우 이전 단계로 돌려 재수행 살 수 있도록 undo 를 반복

static int cpuhp_up_callbacks(unsigned int cpu, struct cpuhp_cpu_state *st,
                  struct cpuhp_step *steps, enum cpuhp_state target)
{
    enum cpuhp_state prev_state = st->state;
    int ret = 0;

    // state
    // OFFLINE -> CREATE_THREADS ..

    while (st->state < target) {
        struct cpuhp_step *step;

        st->state++;
        step = steps + st->state;
        ret = cpuhp_invoke_callback(cpu, st->state, step->startup); // startup 함수 실행
        if (ret) { // 에러 발생시
            st->target = prev_state; // 이전 상태까지 내려가면서
            undo_cpu_up(cpu, st, steps); // teardown 함수 실행
            break;
        }
    }
    return ret;
}


static struct cpuhp_step cpuhp_bp_states[] = {
    [CPUHP_OFFLINE] = {
        .name            = "offline",
        .startup        = NULL,
        .teardown        = NULL,
    },
#ifdef CONFIG_SMP
    [CPUHP_CREATE_THREADS]= {
        .name            = "threads:create",
        .startup        = smpboot_create_threads,
        .teardown        = NULL,
        .cant_stop        = true,
    },
    /*
     * Preparatory and dead notifiers. Will be replaced once the notifiers
     * are converted to states.
     */
    [CPUHP_NOTIFY_PREPARE] = {
        .name            = "notify:prepare",
        .startup        = notify_prepare,
        .teardown        = notify_dead,
        .skip_onerr        = true,
        .cant_stop        = true,
    },
    /* Kicks the plugged cpu into life */
    [CPUHP_BRINGUP_CPU] = {
        .name            = "cpu:bringup",
        .startup        = bringup_cpu,
        .teardown        = NULL,
        .cant_stop        = true,
    },
    /*
     * Handled on controll processor until the plugged processor manages
     * this itself.
     */
    [CPUHP_TEARDOWN_CPU] = {
        .name            = "cpu:teardown",
        .startup        = NULL,
        .teardown        = takedown_cpu,
        .cant_stop        = true,
    },
#endif
};

7-25 kernel/cpu.c bringup_cpu

static int bringup_cpu(unsigned int cpu)
{
    struct task_struct *idle = idle_thread_get(cpu);
    int ret;

    /* Arch-specific enabling code. */
    ret = __cpu_up(cpu, idle); // 7-26
    if (ret) {
        cpu_notify(CPU_UP_CANCELED, cpu); // 실패처리. raw_notifier_head 의 call chain
        return ret;
    }
    ret = bringup_wait_for_ap(cpu); // per-cpu 로 관리되는 hp_state 가 odone 이 될때까지 대기 (kernel/sched/completion)
    BUG_ON(!cpu_online(cpu));
    return ret;
}

7-26 arch/arm64/kernel/smp.c __cpu_up

// secondary cpu 에게 필요한 secondary_data 구조체 (arm64 용)
// * @stack  - sp for the secondary CPU
// * @status - Result passed back from the secondary CPU to
struct secondary_data {
    void *stack;
    long status;
};

int __cpu_up(unsigned int cpu, struct task_struct *idle)
{
    secondary_data.stack = task_stack_page(idle) + THREAD_START_SP; 
    update_cpu_boot_status(CPU_MMU_OFF); // secondary_data.status = val
    __flush_dcache_area(&secondary_data, sizeof(secondary_data));

    ret = boot_secondary(cpu, idle); // 7-27
    if (ret == 0) {
        wait_for_completion_timeout(&cpu_running, msecs_to_jiffies(1000)); // 잠시 대기

        if (!cpu_online(cpu)) {
            pr_crit("CPU%u: failed to come online\n", cpu);
            ret = -EIO;
        }
    } else {
        pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
    }

    secondary_data.stack = NULL; // stack 비워주기
    status = READ_ONCE(secondary_data.status); // 메모리에서 직접 read
    if (ret && status) {
        // 정상적으로 켜지지 않은경우 에러로그 출력이나 panic
    }

    return ret;
}

7-22 arch/arm64/kernel/smp.c boot_secondary

cpu_ops 의 cpu_boot 실행

secondary_entry : secondary boot 되는 cpu 의 진입점. hp 세팅을 마치고 running 상태로 만드는 듯 하다.

static int boot_secondary(unsigned int cpu, struct task_struct *idle)
{
    if (cpu_ops[cpu]->cpu_boot)
        return cpu_ops[cpu]->cpu_boot(cpu);

    return -EOPNOTSUPP;
}

// arch/arm64/kernel/psci.c
static int cpu_psci_cpu_boot(unsigned int cpu)
{
    // of_parse_and_init_cpus() 에서 cpu 의 hwid 를 미리 구했었음.
    int err = psci_ops.cpu_on(cpu_logical_map(cpu), __pa(secondary_entry)); // cpu 물리 Id 와 secondary_entry 함수 시작 주소
    if (err)
        pr_err("failed to boot CPU%d (%d)\n", cpu, err);

    return err;
}

// drivers/firmwarew/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);
}

7-23 arch/arm64/kernel.smp.c secondary_start_kernel

asmlinkage void secondary_start_kernel(void)
{
    struct mm_struct *mm = &init_mm;
    unsigned int cpu = smp_processor_id();

    atomic_inc(&mm->mm_count); // secondary cpu mm 을 init_mm 으로 설정
    current->active_mm = mm;

    set_my_cpu_offset(per_cpu_offset(smp_processor_id())); // per-cpu offset 설정. tpidr_el1

    cpu_uninstall_idmap(); // ttbr0_el1 의 idmap 을 제거하고 active_mm 의 pgd 를 재정의 하기 위해 초기화 & 플러시

    preempt_disable(); // 선점 불가 상태로 변경

    cpuinfo_store_cpu(); // cpu info 초기화

    notify_cpu_starting(cpu); // hp 를 AP_ONLINE 까지 변경하며 콜백

    smp_store_cpu_info(cpu); // MPIDR 의 토폴로지등 갱신

    update_cpu_boot_status(CPU_BOOT_SUCCESS); // 상태 변경
    set_cpu_online(cpu, true);
    complete(&cpu_running);

    local_dbg_enable(); // cpu 에서 신호 받을수 있도록 설정
    local_irq_enable();
    local_async_enable();

    cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); hp 상태를 AP_ONLINE_IDLE 로 변경하여 cpu_idle_loop 를 수행
}
댓글