가상화 지원 (하이퍼 모드)

<kernel v5.4>

가상화 지원 (하이퍼 모드)

가상화를 지원하기 위해서 AVE(Architecture Virtualization Extension)가 필요하다.

  • ARM32에서는 SoC 마다 탑재 여부가 다르다.
  • ARM64에서는 대부분 내장되어 있다.

 

ARM32와 ARM64 시스템의에서 다음과 같은 하이퍼 바이저 운영 모드들이 지원된다. 대부분의 설명은 ARM64를 디폴트로 한다.

  • ARM32
    • 하이퍼 바이저는 HYP 모드에서 운영
    • Guest OS는 SVC 모드에서 운영
  • AMR64
    • 하이퍼 바이저는 EL2에서 운영
    • Guest OS는 EL1 모드에서 운영

 

하이퍼 바이저를 운영하는 방법은 두 가지가 있다. (Host OS & Guest OS = 리눅스 OS)

  • Type 1
    • EL2에서 하이퍼바이저용 전용 OS가 운영되고, EL1에서 Guest OS가 운영된다.
  • Type 2
    • EL2에서 Host OS가 운영되고, EL1에서 Guest OS가 운영된다.

 

ARM 리눅스는 Type 2에서 운영될 때 EL2 모드와 EL1 모드 두 가지를 모두 지원하기 위해 다음과 같은 방법을 고려하였고, 현재는 첫 번째 방법을 사용하고 있다. 두 번째 방법을 구현하려면 상당히 많은 양의 추가 코드들이 필요한 상태이다.

  • 첫 번째, EL2 스위칭 + EL1 Host & Guest OS
    • Host OS를 EL2로 부팅한 후 EL2에 관련 스위칭(irq 라우팅 포함) 코드만 남겨 놓고 EL1으로 전환하여 운영한다. 그 후 EL1에서 Guest OS를 동작시킨다.
      • 예) Host용 리눅스 커널이 EL2에서 부팅되고, EL1으로 전환한 후 QEMU(enable-kvm)를 사용하여  EL1에서 Guest용 리눅스 커널을 동작시킨다.
  • 두 번째, EL2 Host OS + EL1 Guest OS
    • Host OS를 EL2로 부팅하여 운영하고, EL1에서 Guest OS를 동작시킨다.

 

하이퍼모드 지원 유무 확인

하이퍼모드로 부팅된 경우인지 아닌지 메시지(dmesg)를 출력하여 안내한다.

ARM64 예)

  • EL2 부팅
    • CPU: All CPU(s) started at EL2
  • EL1 부팅
    • “CPU: All CPU(s) started at EL1”

 

ARM32 예)

  • HYP 모드 부팅
    • “CPU: All CPU(s) started in HYP mode.”
    • “CPU: Virtualization extensions available.”
  • SVC 모드 부팅
    • “CPU: All CPU(s) started in SVC mode.”

 

Secure EL2 support

  • ARMv8.4 아키텍처부터 Secure EL2를 지원하기 시작하였다.

 

 


하이퍼 모드 체크

hyp_mode_check() – ARM64

arch/arm64/kernel/setup.c

static void __init hyp_mode_check(void)
{
        if (is_hyp_mode_available())
                pr_info("CPU: All CPU(s) started at EL2\n");
        else if (is_hyp_mode_mismatched())
                WARN_TAINT(1, TAINT_CPU_OUT_OF_SPEC,
                           "CPU: CPUs started in inconsistent modes");
        else
                pr_info("CPU: All CPU(s) started at EL1\n");
}

커널이 EL2로 부팅되었는지 EL1으로 부팅되었는지 여부를 메시지로 출력한다.

 

hyp_mode_check() – ARM32

arch/arm/kernel/setup.c

#ifndef ZIMAGE
void __init hyp_mode_check(void)
{
#ifdef CONFIG_ARM_VIRT_EXT
        sync_boot_mode();

        if (is_hyp_mode_available()) {
                pr_info("CPU: All CPU(s) started in HYP mode.\n");
                pr_info("CPU: Virtualization extensions available.\n");
        } else if (is_hyp_mode_mismatched()) {
                pr_warn("CPU: WARNING: CPU(s) started in wrong/inconsistent modes (primary CPU mode 0x%x)\n",
                        __boot_cpu_mode & MODE_MASK);
                pr_warn("CPU: This may indicate a broken bootloader or firmware.\n");
        } else       
                pr_info("CPU: All CPU(s) started in SVC mode.\n");
#endif               
}
#endif

커널이 HYP 모드로 부팅되었는지 SVC 모드로 부팅되었는지 여부를 메시지로 출력한다.

 

하이퍼 모드 운영 여부

is_hyp_mode_available() – ARM64

arch/arm64/include/asm/virt.h

/* Reports the availability of HYP mode */
static inline bool is_hyp_mode_available(void)
{
        return (__boot_cpu_mode[0] == BOOT_CPU_MODE_EL2 &&
                __boot_cpu_mode[1] == BOOT_CPU_MODE_EL2);
}

boot cpu가 EL2로 부팅되었는지 여부를 알아온다.

  • __boot_cpu_mode[] 저장 루틴은 다음을 참고한다.

 

is_hyp_mode_available() – ARM32

arch/arm/include/asm/virt.h

/* Reports the availability of HYP mode */
static inline bool is_hyp_mode_available(void)
{
        return ((__boot_cpu_mode & MODE_MASK) == HYP_MODE &&
                !(__boot_cpu_mode & BOOT_CPU_MODE_MISMATCH));
}

boot cpu가 EL2로 부팅되었는지 여부를 알아온다.

 


기타

sync_boot_mode()

arch/arm/include/asm/virt.h

/*
 * __boot_cpu_mode records what mode the primary CPU was booted in.
 * A correctly-implemented bootloader must start all CPUs in the same mode:
 * if it fails to do this, the flag BOOT_CPU_MODE_MISMATCH is set to indicate
 * that some CPU(s) were booted in a different mode.
 *
 * This allows the kernel to flag an error when the secondaries have come up.
 */
extern int __boot_cpu_mode;

static inline void sync_boot_mode(void)
{
        /*  
         * As secondaries write to __boot_cpu_mode with caches disabled, we
         * must flush the corresponding cache entries to ensure the visibility
         * of their writes.
         */
        sync_cache_r(&__boot_cpu_mode);
}
  • 전역 __boot_cpu_mode 변수 영역에 대해 inner & outer 캐시 flush를 수행한다.

 

sync_cache_r()

arch/arm/include/asm/cacheflush.h

#define sync_cache_r(ptr) __sync_cache_range_r(ptr, sizeof *(ptr))
  • long으로 선언된 전역 __boot_cpu_mode 변수 위치에 대해 inner & outer 캐시 flush를 수행한다.
    • long 값이므로 32bit 시스템에서는 4 byte , 64bit 시스템에서는 8 byte 영역만큼에 대해 flush를 수행하게 된다.

 

__sync_cache_range_r()

arch/arm/include/asm/cacheflush.h

/*
 * Ensure preceding writes to *p by other CPUs are visible to
 * subsequent reads by this CPU.  We must be careful not to
 * discard data simultaneously written by another CPU, hence the
 * usage of flush rather than invalidate operations.
 */
static inline void __sync_cache_range_r(volatile void *p, size_t size)
{
        char *_p = (char *)p;

#ifdef CONFIG_OUTER_CACHE
        if (outer_cache.flush_range) {
                /*
                 * Ensure dirty data migrated from other CPUs into our cache
                 * are cleaned out safely before the outer cache is cleaned:
                 */
                __cpuc_clean_dcache_area(_p, size);

                /* Clean and invalidate stale data for *p from outer ... */
                outer_flush_range(__pa(_p), __pa(_p + size));
        }
#endif

        /* ... and inner cache: */
        __cpuc_flush_dcache_area(_p, size);
}

지정된 range에 대해 inner & outer 캐시 flush를 수행한다.

  • CONFIG_OUTER_CACHE
    • outer 캐시가 사용되는 시스템에서 사용하는 커널 옵션
    • rpi2: outer 캐시를 사용하지 않는다.
  • if (outer_cache.flush_range) {
    • outer 캐시를 사용하는 시스템에서 flush_range에 연결된 함수가 존재하는 경우
  • __cpuc_clean_dcache_area(_p, size);
    • 지정 range의 outer 캐시를 flush하기 전에 먼저 cpu 캐시 즉 inner 캐시를 먼저 clean 작업을 해야한다.
    • ARMv7:
      • inner d-cache 영역에 대해 clean 오퍼레이션 대신 flush를 구현하였다.
  • outer_flush_range(__pa(_p), __pa(_p + size));
    • 지정 range의 outer cache에 대한 flush(clean & invalidate)를 수행한다.
  • __cpuc_flush_dcache_area(_p, size);
    • 지정 range의 inner d-cache에 대한 flush(clean & invalidate)를 수행한다.

 

arch/arm/include/asm/cacheflush.h

/*
 * There is no __cpuc_clean_dcache_area but we use it anyway for
 * code intent clarity, and alias it to __cpuc_flush_dcache_area.
 */
#define __cpuc_clean_dcache_area __cpuc_flush_dcache_area
  • ARMv7에서는 clean 구현을 따로 준비하지 않았고 따라서 flush 구현을 사용한다.

 

arch/arm/include/asm/cacheflush.h

#define __cpuc_flush_dcache_area        cpu_cache.flush_kern_dcache_area
  • ARMv7: v7_flush_kern_dcache_area()

 

outer_flush_range()

arch/arm/include/asm/outercache.h

/**     
 * outer_flush_range - clean and invalidate outer cache lines
 * @start: starting physical address, inclusive
 * @end: end physical address, exclusive
 */
static inline void outer_flush_range(phys_addr_t start, phys_addr_t end)
{
        if (outer_cache.flush_range)
                outer_cache.flush_range(start, end);
}
  • outer_cache가 구현된 머신에서 지정 range의 outer cache에 대한 flush(clean & invalidate)를 수행한다.

 

is_hyp_mode_mismatched()

arch/arm/include/asm/virt.h

/* Check if the bootloader has booted CPUs in different modes */
static inline bool is_hyp_mode_mismatched(void)
{
        return !!(__boot_cpu_mode & BOOT_CPU_MODE_MISMATCH);
}

 

arch/arm/include/asm/virt.h

/*
 * Flag indicating that the kernel was not entered in the same mode on every
 * CPU.  The zImage loader stashes this value in an SPSR, so we need an
 * architecturally defined flag bit here.
 */
#define BOOT_CPU_MODE_MISMATCH  PSR_N_BIT

 

arch/arm/include/uapi/asm/ptrace.h

/*
 * PSR bits
 * Note on V7M there is no mode contained in the PSR
 */
#define USR26_MODE      0x00000000
#define FIQ26_MODE      0x00000001
#define IRQ26_MODE      0x00000002
#define SVC26_MODE      0x00000003
#if defined(__KERNEL__) && defined(CONFIG_CPU_V7M)
/*
 * Use 0 here to get code right that creates a userspace
 * or kernel space thread.
 */
#define USR_MODE        0x00000000
#define SVC_MODE        0x00000000
#else
#define USR_MODE        0x00000010
#define SVC_MODE        0x00000013
#endif
#define FIQ_MODE        0x00000011
#define IRQ_MODE        0x00000012
#define ABT_MODE        0x00000017
#define HYP_MODE        0x0000001a
#define UND_MODE        0x0000001b
#define SYSTEM_MODE     0x0000001f
#define MODE32_BIT      0x00000010
#define MODE_MASK       0x0000001f

 

arch/arm/include/uapi/asm/ptrace.h

/*
 * Use 0 here to get code right that creates a userspace
 * or kernel space thread.
 */
#define USR_MODE        0x00000000
#define SVC_MODE        0x00000000
#else
#define USR_MODE        0x00000010
#define SVC_MODE        0x00000013
#endif
#define FIQ_MODE        0x00000011
#define IRQ_MODE        0x00000012
#define ABT_MODE        0x00000017 
#define HYP_MODE        0x0000001a
#define UND_MODE        0x0000001b
#define SYSTEM_MODE     0x0000001f
#define MODE32_BIT      0x00000010
#define MODE_MASK       0x0000001f

 


전역 변수

__boot_cpu_mode – ARM64

arch/arm64/include/asm/virt.h

/*
 * __boot_cpu_mode records what mode CPUs were booted in.
 * A correctly-implemented bootloader must start all CPUs in the same mode:
 * In this case, both 32bit halves of __boot_cpu_mode will contain the
 * same value (either 0 if booted in EL1, BOOT_CPU_MODE_EL2 if booted in EL2).
 *
 * Should the bootloader fail to do this, the two values will be different.
 * This allows the kernel to flag an error when the secondaries have come up.
 */
extern u32 __boot_cpu_mode[2];

 

arch/arm64/kernel/hyp-stub.S

/*
 * We need to find out the CPU boot mode long after boot, so we need to
 * store it in a writable variable.
 *
 * This is not in .bss, because we set it sufficiently early that the boot-time
 * zeroing of .bss would clobber it.
 */
ENTRY(__boot_cpu_mode)
        .long   BOOT_CPU_MODE_EL2
        .long   BOOT_CPU_MODE_EL1

 

__boot_cpu_mode – ARM32

arch/arm/include/asm/virt.h

/*
 * __boot_cpu_mode records what mode the primary CPU was booted in.
 * A correctly-implemented bootloader must start all CPUs in the same mode:
 * if it fails to do this, the flag BOOT_CPU_MODE_MISMATCH is set to indicate
 * that some CPU(s) were booted in a different mode.
 *
 * This allows the kernel to flag an error when the secondaries have come up.
 */
extern int __boot_cpu_mode;

arch/arm/kernel/hyp-stub.S

/*
 * For the kernel proper, we need to find out the CPU boot mode long after
 * boot, so we need to store it in a writable variable.
 *
 * This is not in .bss, because we set it sufficiently early that the boot-time
 * zeroing of .bss would clobber it.
 */
.data
ENTRY(__boot_cpu_mode)
        .long   0

 

참고

 

댓글 남기기

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