smp_init_cpus()

SMP 전용 명령어 핸들러를 위한 구조체 smp_operations를 준비하여 전역 smp_ops가 가리키게 한다.

smp_init_cpus-1

 

setup_arch() 함수 중반 smp_init_cpus() 함수를 호출하기 직전

PSCI가 동작 상태에 따라 전역 smp_ops가 가리키는 구조체가 다르다.

  • PSCI 동작 시 smp_ops는 psci_smp_ops를 가리키게 한다.
  • PSCI 동작하지 않고 mdesc->smp가 존재하는 경우 smp_ops는 mdesc->smp를 가리킨다.

setup_arch() 중반

arch/arm/kernel/setup.c

#ifdef CONFIG_SMP
        if (is_smp()) {
                if (!mdesc->smp_init || !mdesc->smp_init()) {
                        if (psci_smp_available())
                                smp_set_ops(&psci_smp_ops);
                        else if (mdesc->smp)
                                smp_set_ops(mdesc->smp);
                }
  • if (!mdesc->smp_init || !mdesc->smp_init()) {
    • SMP 머신에서 smp_init 멤버 변수가 null 이거나 머신의 smp_init() 함수 수행 결과가 실패한 경우
    • A) PSCI 방식 보다 먼저 사용될 수 있도록 MCPM(Multiple Cluster Power Management) 기능을 사용할 수 있도록 smp_ops가 mcpm_smp_ops 객체를 가리키게 한다.
      • vexpress 예)
        • mdesc->smp_init = vexpress_smp_init_ops() 함수를 가리키고 수행한다.
          • DT 에서 “cci-400” 이라는 cache coherent interface 400 series 디바이스 장치가 발견되고 “status” 속성이 “ok”일 때 smp_ops를 mcpm_smp_ops 객체를 가리키게 한다.
            • mcpm_smp_ops.smp_init_cpus = null
  • if (psci_smp_available()) smp_set_ops(&psci_smp_ops);
    • CONFIG_ARM_PSCI 커널 옵션이 설정되어 있고 PSCI가 동작 가능하면
    • B) PSCI(Power State Cordination Interface) 기능을 사용할 수 있도록 전역 smp_opspsci_smp_ops를 가리키게 한다.
      • psci_smp_ops.smp_init_cpus = null
  • else if (mdesc->smp) smp_set_ops(mdesc->smp);
    • C) MCPM이나 PSCI가 동작 가능 상태가 아니면 smp_set_ops() 함수를 통해 smp_opsmdesc->smp를 대입한다.
      • 예) rpi2:
        • smp_ops가 bcm2709_smp_ops.ops 를 가리킨다.
  • smp_init_cpus()
    • smp_ops.smp_init_cpus에 등록된 함수를 호출하여 해당 SMP 머신에 대한 초기화를 진행한다.
      • 보통 SCU(Snoop Control Unit) 즉 Cache Coherent Interface에 대한 설정이나 cpu possible bitmap 설정 등을 수행한다.
      • exynos 예)
        • exynos_smp_init_cpus()
      • rpi2 예)
        • bcm2709_smp_init_cpus()

 

mdesc->smp 및 mdesc->smp_init을 사용하는 시스템 예)

arch/arm/mach-vexpress/v2m.c

static const char * const v2m_dt_match[] __initconst = { 
        "arm,vexpress",
        NULL,
};

DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
        .dt_compat      = v2m_dt_match,
        .l2c_aux_val    = 0x00400000,
        .l2c_aux_mask   = 0xfe0fffff,
        .smp            = smp_ops(vexpress_smp_dt_ops),
        .smp_init       = smp_init_ops(vexpress_smp_init_ops),
MACHINE_END
  • Versatile Express 시스템에서 DT 머신 정의
    • .smp가 전역 vexpress_smp_dt_ops 객체를 가리킴
    • .smp_init_ops가 vexpress_smp_init_ops() 함수를 가리킴

 

smp_ops()
#define smp_ops(ops) (&(ops))

 

vexpress_smp_dt_ops 전역 객체

arch/arm/mach-vexpress/platsmp.c

struct smp_operations __initdata vexpress_smp_dt_ops = { 
        .smp_prepare_cpus       = vexpress_smp_dt_prepare_cpus,
        .smp_secondary_init     = versatile_secondary_init,
        .smp_boot_secondary     = versatile_boot_secondary,
#ifdef CONFIG_HOTPLUG_CPU
        .cpu_die                = vexpress_cpu_die,
#endif
};

 

smp_init_ops()

arch/arm/include/asm/mach/arch.h

#define smp_init_ops(ops) (&(ops))

 

vexpress_smp_init_ops()

arch/arm/mach-vexpress/platsmp.c

bool __init vexpress_smp_init_ops(void)
{
#ifdef CONFIG_MCPM
        /*
         * The best way to detect a multi-cluster configuration at the moment
         * is to look for the presence of a CCI in the system.
         * Override the default vexpress_smp_ops if so.
         */
        struct device_node *node;
        node = of_find_compatible_node(NULL, NULL, "arm,cci-400");
        if (node && of_device_is_available(node)) {
                mcpm_smp_set_ops();
                return true;
        }
#endif
        return false;
}
  • CONFIG_MCPM
    • Multi-Cluster Power Management로 big.LITTLE 등의 클러스터 기반의 파워를 관리하는 기능이다.
  • node = of_find_compatible_node(NULL, NULL, “arm,cci-400”);
    • DT 전체 노드 중 compatible 속성이 “arm,cci-400” 인 노드를 찾는다.
  • if (node && of_device_is_available(node)) {
    • 노드의 디바이스가 사용 가능하면
      • 노드의 “status” 속성이 “ok”이면
  • mcpm_smp_set_ops();
    • 전역 smp_ops가 mcpm_smp_ops를 가리키게 한다.

vexpress-v2p-ca15_a7.dts 에서 “arm,cci-400” Cache Coherent Interface 400 series 디바이스에 대한 스크립트 정의를 보여준다.

  • 2개의 a15 cpu와 3개의 a7 cpu가 big.LITTLE 클러스터 구성되어 있다.
        cci@2c090000 {
                compatible = "arm,cci-400";
                #address-cells = <1>;
                #size-cells = <1>;
                reg = <0 0x2c090000 0 0x1000>;
                ranges = <0x0 0x0 0x2c090000 0x10000>;

                cci_control1: slave-if@4000 {
                        compatible = "arm,cci-400-ctrl-if";
                        interface-type = "ace";
                        reg = <0x4000 0x1000>;
                };

                cci_control2: slave-if@5000 {
                        compatible = "arm,cci-400-ctrl-if";
                        interface-type = "ace";
                        reg = <0x5000 0x1000>;
                };
        };

 

psci_smp_available()

arch/arm/kernel/psci_smp.c

bool __init psci_smp_available(void)
{
        /* is cpu_on available at least? */
        return (psci_ops.cpu_on != NULL);
}
  • psci_ops.cpu_on에 함수가 연결되어 있는 경우 PSCI가 동작하는 것으로 간주할 수 있다.

 

mcpm_smp_set_ops()

arch/arm/common/mcpm_platsmp.c

static struct smp_operations __initdata mcpm_smp_ops = {
        .smp_boot_secondary     = mcpm_boot_secondary,
        .smp_secondary_init     = mcpm_secondary_init,
#ifdef CONFIG_HOTPLUG_CPU
        .cpu_kill               = mcpm_cpu_kill,
        .cpu_disable            = mcpm_cpu_disable,
        .cpu_die                = mcpm_cpu_die,
#endif
};

void __init mcpm_smp_set_ops(void)
{
        smp_set_ops(&mcpm_smp_ops);
}
  • 전역 smp_ops가 mcpm_smp_ops 객체를 가리키게 한다.

 

smp_init_cpus()

arch/arm/kernel/smp.c

/* platform specific SMP operations */
void __init smp_init_cpus(void)
{
        if (smp_ops.smp_init_cpus)
                smp_ops.smp_init_cpus();
}
  • smp_ops.smp_init_cpus에 함수가 연결되어 있는 경우 호출한다.

아래는 smp_ops에 bcm2709_smp_ops.smp 가 연결되어 있어서 bcm2709_smp_init_cpus() 함수를 호출하는 과정을 보여준다.

arch/arm/mach-bcm2709/bcm2709.c

struct smp_operations  bcm2709_smp_ops __initdata = {
        .smp_init_cpus          = bcm2709_smp_init_cpus,
        .smp_prepare_cpus       = bcm2709_smp_prepare_cpus,
        .smp_secondary_init     = bcm2709_secondary_init,
        .smp_boot_secondary     = bcm2709_boot_secondary,
};
#endif

static const char * const bcm2709_compat[] = {
        "brcm,bcm2709",
        "brcm,bcm2708", /* Could use bcm2708 in a pinch */
        NULL
};

MACHINE_START(BCM2709, "BCM2709")
    /* Maintainer: Broadcom Europe Ltd. */
#ifdef CONFIG_SMP
        .smp            = smp_ops(bcm2709_smp_ops),
#endif
        .map_io = bcm2709_map_io,
        .init_irq = bcm2709_init_irq,
        .init_time = bcm2709_timer_init,
        .init_machine = bcm2709_init,
        .init_early = bcm2709_init_early,
        .reserve = board_reserve,
        .restart        = bcm2709_restart,
        .dt_compat = bcm2709_compat,
MACHINE_END

 

bcm2709_smp_init_cpus()

arch/arm/mach-bcm2709/bcm2709.c

void __init bcm2709_smp_init_cpus(void)
{       
        void secondary_startup(void);
        unsigned int i, ncores;
        
        ncores = 4; // xxx scu_get_core_count(NULL);
        printk("[%s] enter (%x->%x)\n", __FUNCTION__, (unsigned)virt_to_phys((void *)secondary_startup), (unsigned)__io_address(ST_BASE + 0x10));
        printk("[%s] ncores=%d\n", __FUNCTION__, ncores);
    
        for (i = 0; i < ncores; i++) {
                set_cpu_possible(i, true);
                /* enable IRQ (not FIQ) */
                writel(0x1, __io_address(ARM_LOCAL_MAILBOX_INT_CONTROL0 + 0x4 * i));
                //writel(0xf, __io_address(ARM_LOCAL_TIMER_INT_CONTROL0   + 0x4 * i));
        }
        set_smp_cross_call(bcm2835_send_doorbell);
}
  • ncores를 4로 고정시켰다.
  • 각 코어 번호에 대해 cpu possible 비트를 설정한다.
  • writel(0x1, __io_address(ARM_LOCAL_MAILBOX_INT_CONTROL0 + 0x4 * i));
    • 각 core에 대해 IRQ enable (not FIQ)
  • set_smp_cross_call(bcm2835_send_doorbell);
    • 전역 __smp_cross_call이 bcm2835_send_doorbell() 함수를 가리키게 한다.

 

set_smp_cross_call()

arch/arm/kernel/smp.c

static void (*__smp_cross_call)(const struct cpumask *, unsigned int);

void __init set_smp_cross_call(void (*fn)(const struct cpumask *, unsigned int))
{
        if (!__smp_cross_call)
                __smp_cross_call = fn; 
}
  • 전역 __smp_cross_call이 설정되어 있지 않으면 fn으로 설정한다.

 

smp_cross_call()

  • 다음의 함수들에서 호출된다.
    •  arch_send_call_function_ipi_mask()
    • arch_send_wakeup_ipi_mask()
    • arch_send_call_function_single_ipi()
    • arch_irq_work_raise()
    • tick_broadcast()
    • smp_send_reschedule()
    • smp_send_stop()
  • 이하 함수들의 자세한 설명은 추후에 분석한다.

arm/kernel/smp.c

static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
{
        trace_ipi_raise(target, ipi_types[ipinr]);
        __smp_cross_call(target, ipinr);
}

 

bcm2835_send_doorbell()

arch/arm/kernel/smp.c

static void bcm2835_send_doorbell(const struct cpumask *mask, unsigned int irq) 
{
        int cpu; 
        /*
         * Ensure that stores to Normal memory are visible to the
         * other CPUs before issuing the IPI.
         */
        dsb();

        /* Convert our logical CPU mask into a physical one. */
        for_each_cpu(cpu, mask)
        {    
                /* submit softirq */
                writel(1<<irq, __io_address(ARM_LOCAL_MAILBOX0_SET0 + 0x10 * MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 0)));
        }
}

 

writel()

arch/arm/include/asm/io.h

#define writel(v,c)             ({ __iowmb(); writel_relaxed(v,c); })

 

#define __iowmb()               wmb()

 

#define writel_relaxed(v,c)     __raw_writel((__force u32) cpu_to_le32(v),c)

 

__raw_writel()

arch/arm/include/asm/io.h

static inline void __raw_writel(u32 val, volatile void __iomem *addr)
{
        asm volatile("str %1, %0"
                     : "+Qo" (*(volatile u32 __force *)addr)
                     : "r" (val));
}

 

참고

답글 남기기

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