디버그 메모리 -1- (Page Alloc)

<kernel v5.0>

디버그 메모리 -1- (Page Alloc)

minorder 미만의 페이지들 할당 시마다 별도의 가드 페이지를 준비하고, 이 페이지에 접근(R/W)하는 경우 예외를 발생시키기 위해 사용된다. 버디 시스템에서 minorder 미만의 페이지들에 대해 이에 대응하는 버디 페이지들을 별도의 가드 페이지들로 활용하기 위해  guard 플래그를 가진채로 free_list에서 제외된다. 이 guard 페이지들은 유저 가상 주소에 매핑하지 않으므로 접근할 수 없다.

 

사용 조건

  • CONFIG_PAGE_EXTENSION  & CONFIG_DEBUG_PAGEALLOC 커널 옵션
    • 커널 v5.3-rc1 부터는 CONFIG_PAGE_EXTENSION 없이도 동작한다.
  • CONFIG_HIBERNATION과 같이 사용할 수 없다.
  • “debug_pagealloc=on” 커널 파라미터
  • “debug_guardpage_minorder=<minorder>” 커널 파라미터
    • minorder는 디폴트 값으로 가드 페이지가 동작하지 않는 0 이고, 최대 MAX_ORDER의 절반까지 지정될 수 있다.
    • 이 값 이상의 페이지들은 가드 페이지들을 생성하지 않는다.
    • 이 값이 크면 클 수록 메모리 낭비가 커진다.

 

 

다음 그림은 가드 페이지를 사용하지 않는 보통 상태의 버디 시스템에서 페이지 할당을 보여준다.

 

다음 그림은 가드 페이지를 사용할 때 할당되는 페이지와 인접한 추가 가드 페이지들을 보여준다.

 

need_debug_guardpage()

mm/page_alloc.c

static bool need_debug_guardpage(void)
{
        /* If we don't use debug_pagealloc, we don't need guard page */
        if (!debug_pagealloc_enabled())
                return false; 

        if (!debug_guardpage_minorder())
                return false;
        return true;
}

가드 페이지 디버깅이 필요한지 여부를 반환한다.

  • 디버그용 guard 페이지를 사용하지 않는 경우에도 false를 반환한다.

 

debug_pagealloc_enabled()

include/linux/mm.h

static inline bool debug_pagealloc_enabled(void) 
{       
        return _debug_pagealloc_enabled;
}

페이지 할당 디버깅 기능 사용 여부를 반환한다.

 

debug_guardpage_minorder()

include/linux/mm.h

static inline unsigned int debug_guardpage_minorder(void)
{
        return _debug_guardpage_minorder;
}

디버그용 guard 페이지로 설정된 최소 order 값을 반환한다.

  • “debug_guardpage_minorder=” 커널 파라메터 값은 1~MAX_ORDER의 절반까지 가능하다.
    • MAX_ORDER가 11인 경우 1~5까지 설정 가능하다.

 

init_debug_guardpage()

mm/page_alloc.c

static void init_debug_guardpage(void)
{                           
        if (!debug_pagealloc_enabled())
                return;

        _debug_guardpage_enabled = true; 
}

가드 페이지 디버깅 기능을 enable 한다.

 

page_is_guard()

include/linux/mm.h

#ifdef CONFIG_DEBUG_PAGEALLOC
static inline bool page_is_guard(struct page *page)
{       
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        page_ext = lookup_page_ext(page); 
        return test_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);
}
#endif

페이지에 대항하는 page_ext의 guard 플래그 비트가 설정되었는지 여부를 반환한다.

 

clear_page_guard()

mm/page_alloc.c

static inline void clear_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return;

        page_ext = lookup_page_ext(page);
        __clear_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        set_page_private(page, 0);
        if (!is_migrate_isolate(migratetype))
                __mod_zone_freepage_state(zone, (1 << order), migratetype);
}

페이지에 대항하는 page_ext의 guard 플래그 비트를 clear 하고 _private에 0을 대입한다. zone에 대한 free page stat도 2^order 만큼 증가시킨다.

 


버디 페이지 expand 시 Guard 페이지로 사용

expand()

mm/page_alloc.c

/*
 * The order of subdivision here is critical for the IO subsystem.
 * Please do not alter this order without good reasons and regression
 * testing. Specifically, as large blocks of memory are subdivided,
 * the order in which smaller blocks are delivered depends on the order
 * they're subdivided in this function. This is the primary factor
 * influencing the order in which pages are delivered to the IO
 * subsystem according to empirical testing, and this is also justified
 * by considering the behavior of a buddy system containing a single
 * large block of memory acted on by a series of small allocations.
 * This behavior is a critical factor in sglist merging's success.
 *
 * -- nyc
 */
static inline void expand(struct zone *zone, struct page *page,
        int low, int high, struct free_area *area,
        int migratetype)
{
        unsigned long size = 1 << high;

        while (high > low) {
                area--;
                high--;
                size >>= 1;
                VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);

                /*
                 * Mark as guard pages (or page), that will allow to
                 * merge back to allocator when buddy will be freed.
                 * Corresponding page table entries will not be touched,
                 * pages will stay not present in virtual address space
                 */
                if (set_page_guard(zone, &page[size], high, migratetype))
                        continue;

                list_add(&page[size].lru, &area->free_list[migratetype]);
                area->nr_free++;
                set_page_order(&page[size], high);
        }
}

버디 시스템에서 요청한 order 페이지가 부족한 경우 더 큰 order 페이지를 확장하여 사용할 수 있는데 이 때 호출되어 동작하는 expand() 함수이다. 가드 페이지와 관련된 코드만을 잠시 살펴본다.

  • 코드 라인 10에서 size에는 할당할 페이지의 다음 페이지인 버디 페이지를 가드 페이지로 사용하기 위해 얻어온다.
  • 코드 라인 19~20에서 guard 페이지를 사용하는 경우 guard 페이지 플래그를 설정한다. 그 후 가드 페이지는 free_list에 추가하지 않기 위해 skip 한다.

 

page_is_guard()

mm/page_alloc.c

static inline bool page_is_guard(struct page *page)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return false;

        return test_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);
}

@page가 가드 페이지인지 여부를 알아온다.

 

가드 페이지 지정

set_page_guard()

mm/page_alloc.c

static inline bool set_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        if (order >= debug_guardpage_minorder())
                return false;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return false;

        __set_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        INIT_LIST_HEAD(&page->lru);
        set_page_private(page, order);
        /* Guard pages are not available for any usage */
        __mod_zone_freepage_state(zone, -(1 << order), migratetype);

        return true;
}

@page를 guard 페이지로 지정한다. 정상적으로 설정한 경우 true를 반환한다.

  • guard 페이지는 private 멤버에 order 값이 지정되고, 확장 페이지에 guard 플래그가 설정된다.

 

가드 페이지 지정 해제

clear_page_guard()

mm/page_alloc.c

static inline void clear_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return;

        __clear_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        set_page_private(page, 0);
        if (!is_migrate_isolate(migratetype))
                __mod_zone_freepage_state(zone, (1 << order), migratetype);
}

@page가 guard 페이지인 경우 취소한다.

  • private 멤버에 0 값이 지정되고, 확장 페이지에 guard 플래그를 클리어한다.

 

Early 커널 파라메터

early_debug_pagealloc()

mm/page_alloc.c

static int __init early_debug_pagealloc(char *buf)
{
        if (!buf)
                return -EINVAL;

        if (strcmp(buf, "on") == 0)
                _debug_pagealloc_enabled = true;
        
        return 0;
}       
early_param("debug_pagealloc", early_debug_pagealloc);

“debug_pagealloc=on” early 커널 파라메터를 사용하는 경우 페이지 할당 디버깅 기능을 사용한다.

 

커널 Tools

tools/vm에서 make를 사용하여 빌드하면 3개의 유틸리티가 생성된다.

  • page-types
    • 참조하는 proc 인터페이스들
      • /proc/<pid>/pagemap
      • /proc/<pid>maps
      • /proc/self/pagemap
      • /proc/kpageflags
      • /proc/kpagecount
      • /proc/kpagecgroup
      • /sys/kernel/mm/page_idle/bitmap
  • page_owner_sort
  • slabinfo

 

page-types

이 유틸리티를 통해 페이지 타입 유형들을 알아볼 수 있다.

  • 옵션을 사용하여 특정 가상 주소, 태스크, 파일에 관여된 페이지 타입들을 관찰할 수 있다.
$ tools/vm/page-types --help
page-types [options]
            -r|--raw                   Raw mode, for kernel developers
            -d|--describe flags        Describe flags
            -a|--addr    addr-spec     Walk a range of pages
            -b|--bits    bits-spec     Walk pages with specified bits
            -c|--cgroup  path|@inode   Walk pages within memory cgroup
            -p|--pid     pid           Walk process address space
            -f|--file    filename      Walk file address space
            -i|--mark-idle             Mark pages idle
            -l|--list                  Show page details in ranges
            -L|--list-each             Show page details one by one
            -C|--list-cgroup           Show cgroup inode for pages
            -M|--list-mapcnt           Show page map count
            -N|--no-summary            Don't show summary info
            -X|--hwpoison              hwpoison pages
            -x|--unpoison              unpoison pages
            -F|--kpageflags filename   kpageflags file to parse
            -h|--help                  Show this usage message
flags:
            0x10                       bitfield format, e.g.
            anon                       bit-name, e.g.
            0x10,anon                  comma-separated list, e.g.
addr-spec:
            N                          one page at offset N (unit: pages)
            N+M                        pages range from N to N+M-1
            N,M                        pages range from N to M-1
            N,                         pages range from N to end
            ,M                         pages range from 0 to M-1
bits-spec:
            bit1,bit2                  (flags & (bit1|bit2)) != 0
            bit1,bit2=bit1             (flags & (bit1|bit2)) == bit1
            bit1,~bit2                 (flags & (bit1|bit2)) == bit1
            =bit1,bit2                 flags == (bit1|bit2)
bit-names:
          locked              error         referenced           uptodate
           dirty                lru             active               slab
       writeback            reclaim              buddy               mmap
       anonymous          swapcache         swapbacked      compound_head
   compound_tail               huge        unevictable           hwpoison
          nopage                ksm                thp            balloon
       zero_page          idle_page            pgtable           reserved(r)
         mlocked(r)    mappedtodisk(r)         private(r)       private_2(r)
   owner_private(r)            arch(r)        uncached(r)       softdirty(r)
       readahead(o)       slob_free(o)     slub_frozen(o)      slub_debug(o)
            file(o)            swap(o)  mmap_exclusive(o)
                                   (r) raw mode bits  (o) overloaded bits

 

$ tools/vm/page-types
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000000           92360      360  ___________________________________________
0x0000000000100000             512        2  ____________________n______________________        nopage
0x0000000001000000               1        0  ________________________z__________________        zero_page
0x0000000000000014              10        0  __R_D______________________________________        referenced,dirty
0x0000000000000020              18        0  _____l_____________________________________        lru
0x0000000000000024              16        0  __R__l_____________________________________        referenced,lru
0x0000000000000028          102116      398  ___U_l_____________________________________        uptodate,lru
0x0000000000004028            1536        6  ___U_l________b____________________________        uptodate,lru,swapbacked
0x000000000000002c           90364      352  __RU_l_____________________________________        referenced,uptodate,lru
0x0000000000004030           11454       44  ____Dl________b____________________________        dirty,lru,swapbacked
0x000000000000403c           15846       61  __RUDl________b____________________________        referenced,uptodate,dirty,lru,swapbacked
0x0000000000000040               1        0  ______A____________________________________        active
0x0000000000000060            7293       28  _____lA____________________________________        lru,active
0x0000000000000064           19994       78  __R__lA____________________________________        referenced,lru,active
0x0000000000000068          340912     1331  ___U_lA____________________________________        uptodate,lru,active
0x000000000000006c          233190      910  __RU_lA____________________________________        referenced,uptodate,lru,active
0x0000000000000074               4        0  __R_DlA____________________________________        referenced,dirty,lru,active
0x0000000000004078           16146       63  ___UDlA_______b____________________________        uptodate,dirty,lru,active,swapbacked
0x0000000000000078               1        0  ___UDlA____________________________________        uptodate,dirty,lru,active
0x000000000000407c              91        0  __RUDlA_______b____________________________        referenced,uptodate,dirty,lru,active,swapbacked
0x000000000000007c               1        0  __RUDlA____________________________________        referenced,uptodate,dirty,lru,active
0x0000000000000080           37452      146  _______S___________________________________        slab
0x0000000000000228            2202        8  ___U_l___I_________________________________        uptodate,lru,reclaim
0x0000000000000268              26        0  ___U_lA__I_________________________________        uptodate,lru,active,reclaim
0x0000000000000400             965        3  __________B________________________________        buddy
0x0000000000000800               1        0  ___________M_______________________________        mmap
0x0000000000000804               1        0  __R________M_______________________________        referenced,mmap
0x0000000000000828             232        0  ___U_l_____M_______________________________        uptodate,lru,mmap
0x0000000000004828               8        0  ___U_l_____M__b____________________________        uptodate,lru,mmap,swapbacked
0x0000000000004838            4600       17  ___UDl_____M__b____________________________        uptodate,dirty,lru,mmap,swapbacked
0x000000000000483c               2        0  __RUDl_____M__b____________________________        referenced,uptodate,dirty,lru,mmap,swapbacked
0x0000000000000868             173        0  ___U_lA____M_______________________________        uptodate,lru,active,mmap
0x000000000000086c            4925       19  __RU_lA____M_______________________________        referenced,uptodate,lru,active,mmap
0x0000000000004878               1        0  ___UDlA____M__b____________________________        uptodate,dirty,lru,active,mmap,swapbacked
0x0000000000005048               3        0  ___U__A_____a_b____________________________        uptodate,active,anonymous,swapbacked
0x0000000000005848               2        0  ___U__A____Ma_b____________________________        uptodate,active,mmap,anonymous,swapbacked
0x0000000000005868           33295      130  ___U_lA____Ma_b____________________________        uptodate,lru,active,mmap,anonymous,swapbacked
0x000000000000586c              54        0  __RU_lA____Ma_b____________________________        referenced,uptodate,lru,active,mmap,anonymous,swapbacked
             total         1015808     3968

 

특정 유저 태스크(–pid 옵션)가 사용한 페이지 타입들을 보여준다.

$ tools/vm/page-types --pid 959
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000800               1        0  ___________M_______________________________        mmap
0x0000000000000804               1        0  __R________M_______________________________        referenced,mmap
0x000000000000086c            1271        4  __RU_lA____M_______________________________        referenced,uptodate,lru,active,mmap
0x0000000000005868             311        1  ___U_lA____Ma_b____________________________        uptodate,lru,active,mmap,anonymous,swapbacked
0x000000000000586c               1        0  __RU_lA____Ma_b____________________________        referenced,uptodate,lru,active,mmap,anonymous,swapbacked
             total            1585        6

 

특정 파일(–file 옵션)에 사용한 페이지 타입들을 보여준다.

$ tools/vm/page-types --file abc.txt
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000068               1        0  ___U_lA____________________________________        uptodate,lru,active
             total               1        0

 

참고

copy_from_user()

 

프로세스 주소 공간(process address space)을 access하는 커널 API

가장 잘 알려진 다음 API 두 개를 알아본다.

  • copy_from_user()
  • copy_to_user()

 

그리고 유저 프로세스 공간으로의 안전한 접근이 가능한지 여부를 체크하는 다음 API를 알아본다.

  • access_ok()

 


유저 프로세스 공간으로 부터 커널로 데이터 복사

 

copy_from_user()

include/linux/uaccess.h

static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)
{
        if (likely(check_copy_size(to, n, false)))
                n = _copy_from_user(to, from, n);
        return n;
}

유저 가상 주소 @from에서 @n 바이트만큼 커널 가상 주소 @to로 데이터를 복사한다.

 

_copy_from_user()

include/linux/uaccess.h

#ifdef INLINE_COPY_FROM_USER
static inline unsigned long
_copy_from_user(void *to, const void __user *from, unsigned long n)
{
        unsigned long res = n;
        might_fault();
        if (likely(access_ok(from, n))) {
                kasan_check_write(to, n);
                res = raw_copy_from_user(to, from, n);
        }
        if (unlikely(res))
                memset(to + (n - res), 0, res);
        return res;
}
#endif

이 함수는 유저 application에서 사용될 때에는 INLINE_COPY_FROM_USER 옵션이 적용되어 인라인 함수로 제공된다. 그렇지 않고 커널을 통해 호출되는 경우 라이브러리를 통해 제공된다.

 

아키텍처별 raw_copy_from_user()

다음 아키텍처별로 권한 설정을 아키텍처 고유의 방법을 사용한다.

  • ARM32
    • 도메인 접근 제어 레지스터를 사용하여 제어한다.
  • ARM64
    • 커널에서의 유저 액세스 권한 제어 플래그를 사용하여 제어한다.

 

raw_copy_from_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define raw_copy_from_user(to, from, n)                                 \
({                                                                      \
        __arch_copy_from_user((to), __uaccess_mask_ptr(from), (n));     \
})

유저 가상 주소 @from(x1)에서 @n 바이트(x2)만큼 커널 가상 주소 @to(x0)로  복사한다.

 

다음 그림은 ARM64 시스템에서 copy_from_user() 함수의 처리 흐름을 보여준다.

 

__arch_copy_from_user() – ARM64

arch/arm64/lib/copy_from_user.S

ENTRY(__arch_copy_from_user)
        uaccess_enable_not_uao x3, x4, x5
        add     end, x0, x2
#include "copy_template.S"
        uaccess_disable_not_uao x3, x4
        mov     x0, #0                          // Nothing to copy
        ret
ENDPROC(__arch_copy_from_user)
EXPORT_SYMBOL(__arch_copy_from_user)

커널이 유저 데이터에 접근할 수 있도록 잠시 허용한 후 유저 가상 주소 src(x1)에서 n 바이트(x2)만큼 커널 가상 주소 dest(x0)로 복사한다. 그런 후 커널이 유저 데이터에 접근하지 못하게 막는다.

  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 

raw_copy_from_user() – ARM32

arch/arm/include/asm/uaccess.h

static inline unsigned long __must_check
raw_copy_from_user(void *to, const void __user *from, unsigned long n)
{
        unsigned int __ua_flags;

        __ua_flags = uaccess_save_and_enable();
        n = arm_copy_from_user(to, from, n);
        uaccess_restore(__ua_flags);
        return n;
}

유저 가상 주소 @from(r1)에서 @n 바이트(r2)만큼 커널 가상 주소 @to(r0)로 복사한다.

 

다음 그림은 ARM32 시스템에서 copy_from_user() 함수의 처리 흐름을 보여준다.

 

arm_copy_from_user()

lib/copy_from_user.S

ENTRY(arm_copy_from_user)
#ifdef CONFIG_CPU_SPECTRE
        get_thread_info r3
        ldr     r3, [r3, #TI_ADDR_LIMIT]
        uaccess_mask_range_ptr r1, r2, r3, ip
#endif

#include "copy_template.S"

ENDPROC(arm_copy_from_user)
  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 


유저 프로세스 공간으로 커널 데이터 복사

 

copy_to_user()

include/linux/uaccess.h

static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)
{
        if (likely(check_copy_size(from, n, true)))
                n = _copy_to_user(to, from, n);
        return n;
}

커널 가상 주소 @from에서 @n 바이트만큼 유저 가상 주소 @to로 데이터를 복사한다.

 

_copy_to_user()

include/linux/uaccess.h

#ifdef INLINE_COPY_TO_USER
static inline unsigned long
_copy_to_user(void __user *to, const void *from, unsigned long n)
{
        might_fault();
        if (access_ok(to, n)) {
                kasan_check_read(from, n);
                n = raw_copy_to_user(to, from, n);
        }
        return n;
}
#endif

이 함수는 유저 application에서 사용될 때에는 INLINE_COPY_TO_USER 옵션이 적용되어 인라인 함수로 제공된다. 그렇지 않고 커널을 통해 호출되는 경우 라이브러리를 통해 제공된다.

 

아키텍처별 raw_copy_to_user()

다음 아키텍처별로 권한 설정을 아키텍처 고유의 방법을 사용한다.

  • ARM32
    • 도메인 접근 제어 레지스터를 사용하여 제어한다.
  • ARM64
    • 커널에서의 유저 액세스 권한 제어 플래그를 사용하여 제어한다.

 

raw_copy_to_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define raw_copy_to_user(to, from, n)                                   \
({                                                                      \
        __arch_copy_to_user(__uaccess_mask_ptr(to), (from), (n));       \
})

커널 가상 주소 @from(x1)에서 @n 바이트(x2)만큼 유저 가상 주소 @to(x0)로 복사한다.

 

__arch_copy_to_user()

arch/arm64/lib/copy_to_user.S

ENTRY(__arch_copy_to_user)
        uaccess_enable_not_uao x3, x4, x5
        add     end, x0, x2
#include "copy_template.S"
        uaccess_disable_not_uao x3, x4
        mov     x0, #0
        ret
ENDPROC(__arch_copy_to_user)
EXPORT_SYMBOL(__arch_copy_to_user)

커널이 유저 데이터에 접근할 수 있도록 잠시 허용한 후 커널 가상 주소 src(x1)에서 n 바이트(x2)만큼 유저 가상 주소 dest(x0)로 복사한다. 그런 후 커널이 유저 데이터에 접근하지 못하게 막는다.

  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 

raw_copy_to_user() – ARM32

arch/arm/include/asm/uaccess.h

static inline unsigned long __must_check
raw_copy_to_user(void __user *to, const void *from, unsigned long n)
{
#ifndef CONFIG_UACCESS_WITH_MEMCPY
        unsigned int __ua_flags;
        __ua_flags = uaccess_save_and_enable();
        n = arm_copy_to_user(to, from, n);
        uaccess_restore(__ua_flags);
        return n;
#else
        return arm_copy_to_user(to, from, n);
#endif
}

커널 가상 주소 @from(r1)에서 @n 바이트(r2)만큼 유저 가상 주소 @to(r0)로 복사한다.

 

arm_copy_to_user()

lib/copy_to_user.S

ENTRY(__copy_to_user_std)
WEAK(arm_copy_to_user)
#ifdef CONFIG_CPU_SPECTRE
        get_thread_info r3
        ldr     r3, [r3, #TI_ADDR_LIMIT]
        uaccess_mask_range_ptr r0, r2, r3, ip
#endif

#include "copy_template.S"

ENDPROC(arm_copy_to_user)
  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 


User 공간 접근 제어 – ARM64

보안을 향상시킬 목적으로 커널에서 유저 공간 액세스를 컨트롤하기 위해 다음 두 가지 방법 중 하나를 사용할 수 있다. 이러한 기능이 적용되지 않는 경우 기본적으로 커널에서 유저 공간의 접근이 언제나 허용된다.

  • ARMv8.1-PAN 기능을 사용한 제어 (HW 기법)
    • 커널 옵션: CONFIG_ARM64_PAN
  • SW_TTBR0_PAN 기능을 사용한 제어 (SW 기법)
    • TTBR0_EL1을 빈 페이지 테이블에 연결하고, TTBR1_EL1의 ASID를 클리어하는 SW 에뮬레이션 방법을 사용하여 커널에서 유저 접근을 금지시킬 수 있다.
    • 커널 옵션:CONFIG_ARM64_SW_TTBR0_PAN

 

그 외 – UAO 기능을 사용한 커널에서의 유저 공간 접근 권한 Override

커널과 유저 데이터 교환 함수들(copy_from_user(), copy_to_user()…)에서 유저 공간에 접근할 때 ldr/str 대신 유저 페이지 테이블에 기록된 권한 설정들을 override 하여 사용하는 ldtr/sttr 명령을 사용할 수 있다.

  • ARMv8.2-UAO
    • 커널에서 유저 주소에 접근할 때 ldr/str 명령으로 접근하는 경우 유저 페이지 테이블에 기록된 권한 설정들을 사용하지 못한다.
    • 그러나 UAO 기능이 지원되는 아키텍처에서 ldtr/sttr 명령을 사용 시 유저 페이지 테이블에 기록된 권한 설정들을 Override 할 수 있다.
    • 커널 옵션: CONFIG_ARM64_UAO

 

1) 유저 공간 접근 허용 – for ARM64 Assembly

uaccess_enable_not_uao() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_enable_not_uao, tmp1, tmp2, tmp3
        uaccess_ttbr0_enable \tmp1, \tmp2, \tmp3
alternative_if ARM64_ALT_PAN_NOT_UAO
        SET_PSTATE_PAN(0)
alternative_else_nop_endif
        .endm

커널에서 유저 데이터에 접근할 수 있도록 허용한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 disable하여 커널에서 유저 공간에 접근할 수 있도록 허용한다.

 

uaccess_ttbr0_enable() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_ttbr0_enable, tmp1, tmp2, tmp3
alternative_if_not ARM64_HAS_PAN
        save_and_disable_irq \tmp3              // avoid preemption
        __uaccess_ttbr0_enable \tmp1, \tmp2
        restore_irq \tmp3
alternative_else_nop_endif
        .endm

ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.

 

__uaccess_ttbr0_enable() 매크로

        .macro  __uaccess_ttbr0_enable, tmp1, tmp2
        get_thread_info \tmp1
        ldr     \tmp1, [\tmp1, #TSK_TI_TTBR0]   // load saved TTBR0_EL1
        mrs     \tmp2, ttbr1_el1
        extr    \tmp2, \tmp2, \tmp1, #48
        ror     \tmp2, \tmp2, #16
        msr     ttbr1_el1, \tmp2                // set the active ASID
        isb
        msr     ttbr0_el1, \tmp1                // set the non-PAN TTBR0_EL1
        isb
        .endm

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허락하도록 ttbr을 설정한다.

  • 유저 페이지 테이블을 위해 사용되는 TTBR0의 asid를 커널 페이지 테이블을 위해 사용되는 TTBR1에 복사하여 커널이 유저 액세스가 가능하도록 허용한다.
  • 유저 페이지 테이블을 위해 사용되는 TTBR0의 asid를 읽어 커널 페이지 테이블을 위해 사용되는 TTBR1에 asid 필드만을 기록하여 커널에서 유저 영역에 접근할 수 있게 허용한다.

 

2) 유저 공간 접근 금지 – for ARM64 Assembly

uaccess_disable_not_uao() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_disable_not_uao, tmp1, tmp2
        uaccess_ttbr0_disable \tmp1, \tmp2
alternative_if ARM64_ALT_PAN_NOT_UAO
        SET_PSTATE_PAN(1)
alternative_else_nop_endif
        .endm

커널에서 유저 데이터에 접근하지 못하게 금지한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근하지 못하게 금지한다.

 

uaccess_ttbr0_disable() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_ttbr0_enable, tmp1, tmp2, tmp3
alternative_if_not ARM64_HAS_PAN
        save_and_disable_irq \tmp3              // avoid preemption
        __uaccess_ttbr0_enable \tmp1, \tmp2
        restore_irq \tmp3
alternative_else_nop_endif
        .endm

ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.

 

__uaccess_ttbr0_disable() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  __uaccess_ttbr0_disable, tmp1
        mrs     \tmp1, ttbr1_el1                        // swapper_pg_dir
        bic     \tmp1, \tmp1, #TTBR_ASID_MASK
        sub     \tmp1, \tmp1, #RESERVED_TTBR0_SIZE      // reserved_ttbr0 just before swapper_pg_dir
        msr     ttbr0_el1, \tmp1                        // set reserved TTBR0_EL1
        isb
        add     \tmp1, \tmp1, #RESERVED_TTBR0_SIZE
        msr     ttbr1_el1, \tmp1                // set reserved ASID
        isb
        .endm

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하도록 ttbr을 설정한다.

  • 커널 페이지 테이블을 가리키는 TTBR1의 asid를 클리어한다.
  • 유저 페이지 테이블을 가리키는 TTBR0에 reserved_ttbr0 라는 이름의 빈 페이지 테이블을 가리키게한다.
    • CONFIG_ARM64_SW_TTBR0_PAN 커널 옵션을 사용하는 경우 swapper_pg_dir 이전에 reserved_ttbr0 라는 이름의 빈 페이지 테이블이 구성되어 있다.

 

Process State(PSTATE) 설정

/*
 * Instructions for modifying PSTATE fields.
 * As per Arm ARM for v8-A, Section "C.5.1.3 op0 == 0b00, architectural hints,
 * barriers and CLREX, and PSTATE access", ARM DDI 0487 C.a, system instructions
 * for accessing PSTATE fields have the following encoding:
 *      Op0 = 0, CRn = 4
 *      Op1, Op2 encodes the PSTATE field modified and defines the constraints.
 *      CRm = Imm4 for the instruction.
 *      Rt = 0x1f
 */
#define pstate_field(op1, op2)          ((op1) << Op1_shift | (op2) << Op2_shift)
#define PSTATE_Imm_shift                CRm_shift

#define PSTATE_PAN                      pstate_field(0, 4)
#define PSTATE_UAO                      pstate_field(0, 3)
#define PSTATE_SSBS                     pstate_field(3, 1)

#define SET_PSTATE_PAN(x)               __emit_inst(0xd500401f | PSTATE_PAN | ((!!x) << PSTATE_Imm_ss
hift))
#define SET_PSTATE_UAO(x)               __emit_inst(0xd500401f | PSTATE_UAO | ((!!x) << PSTATE_Imm_ss
hift))
#define SET_PSTATE_SSBS(x)              __emit_inst(0xd500401f | PSTATE_SSBS | ((!!x) << PSTATE_Imm__
shift))

MSR을 사용하고 immediate 접근을 통해 PSTATE 필드 들 중 다음의 필드들을 설정할 수 있다.

  •  SET_PSTATE_PAN()
    • PSTATE 중 PAN(Privileged Access Never) 비트를 설정한다.
    • ARMv8.1-PAN 이 구현된 아키텍처만 사용가능하다.
  • SET_PSTATE_UAO()
    • PSTATE 중 UAO(User Access Override) 비트를 설정한다.
    • ARMv8.2-UAO가 구현된 아키텍처만 사용가능하다.

 

다음 그림은 MSR의 immediate 접근을 통해 PSTATE 관련 몇 개의 플래그를 설정하기 위해 사용되는 op1, CRm, op2의 위치를 보여준다.

  • CRm 값을 지정하기 위해 필요한 CRm_shift 값은 8이다.
  • op1 값을 지정하기 위해 필요한 op1_shift 값은 16이다.
  • op2 값을 지정하기 위해 필요한 op2_shift 값은 5이다.

 

3) 유저 공간 접근 허용 – for ARM64 C

uaccess_enable_not_uao()

arch/arm64/include/asm/uaccess.h

static inline void uaccess_enable_not_uao(void)
{
        __uaccess_enable(ARM64_ALT_PAN_NOT_UAO);
}

커널에서 유저 데이터에 접근할 수 있도록 잠시 허용한다.

 

__uaccess_enable()

arch/arm64/include/asm/uaccess.h

#define __uaccess_enable(alt)                                           \
do {                                                                    \
        if (!uaccess_ttbr0_enable())                                    \
                asm(ALTERNATIVE("nop", SET_PSTATE_PAN(0), alt,          \
                                CONFIG_ARM64_PAN));                     \
} while (0)

커널에서 유저 액세스가 가능하도록 허용한다.

  • 코드 라인 3에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.
  • 코드 라인 4~5에서 PAN 기능 지원 여부에 따라 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 disable하여 커널에서 유저 공간에 접근할 수 있도록 허용한다.

 

uaccess_ttbr0_enable() – ARM64

arch/arm64/include/asm/uaccess.h

static inline bool uaccess_ttbr0_enable(void)
{
        if (!system_uses_ttbr0_pan())
                return false;
        __uaccess_ttbr0_enable();
        return true;
}

커널에서 유저 데이터에 접근할 수 있도록 허용한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근할 수 있도록 허용한다.

 

system_uses_ttbr0_pan() – ARM64

arch/arm64/include/asm/cpufeature.h

static inline bool system_uses_ttbr0_pan(void)
{
        return IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN) &&
                !cpus_have_const_cap(ARM64_HAS_PAN);
}

현재 동작 중인 ARM64 커널에 CONFIG_ARM64_SW_TTBR0_PAN 커널 옵션이 설정되어 있지만 아키텍처가 PAN(Previlidge Access Never) 기능을 가지고 있지 않으면 TTBR0을 사용하여 PAN을 에뮬레이션을 하기 위해  1을 반환한다.

 

__uaccess_ttbr0_enable() – ARM64

arch/arm64/include/asm/uaccess.h

static inline void __uaccess_ttbr0_enable(void)
{
        unsigned long flags, ttbr0, ttbr1;

        /*
         * Disable interrupts to avoid preemption between reading the 'ttbr0'
         * variable and the MSR. A context switch could trigger an ASID
         * roll-over and an update of 'ttbr0'.
         */
        local_irq_save(flags);
        ttbr0 = READ_ONCE(current_thread_info()->ttbr0);

        /* Restore active ASID */
        ttbr1 = read_sysreg(ttbr1_el1);
        ttbr1 &= ~TTBR_ASID_MASK;               /* safety measure */
        ttbr1 |= ttbr0 & TTBR_ASID_MASK;
        write_sysreg(ttbr1, ttbr1_el1);
        isb();

        /* Restore user page table */
        write_sysreg(ttbr0, ttbr0_el1);
        isb();
        local_irq_restore(flags);
}

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허락하도록 ttbr을 설정한다.

  • TTBR0의 asid를 TTBR1에 복사하여 커널이 유저 액세스가 가능하도록 허용한다.
  • 유저 페이지 테이블을 위해 사용되는 ttbr0의 asid를 읽어 커널 페이지 테이블을 위해 사용되는 ttbr1에 asid 필드만을 기록하여 커널에서 유저 영역에 접근할 수 있게 허용한다.

 

4) 유저 공간 접근 금지 – for ARM64 C

uaccess_disable_not_uao()

arch/arm64/include/asm/uaccess.h

static inline void uaccess_disable_not_uao(void)
{
        __uaccess_disable(ARM64_ALT_PAN_NOT_UAO);
}

다시 커널에서 유저 데이터에 접근하지 못하게 한다.

 

__uaccess_disable()

arch/arm64/include/asm/uaccess.h

#define __uaccess_disable(alt)                                          \
do {                                                                    \
        if (!uaccess_ttbr0_disable())                                   \
                asm(ALTERNATIVE("nop", SET_PSTATE_PAN(1), alt,          \
                                CONFIG_ARM64_PAN));                     \
} while (0)

커널에서 유저 데이터에 접근하지 못하게 금지한다.

  • 코드 라인 3에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.
  • 코드 라인 4~6에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근하지 못하게 금지한다.

 

uaccess_ttbr0_disable()

arch/arm64/include/asm/uaccess.h

static inline bool uaccess_ttbr0_disable(void)
{
        if (!system_uses_ttbr0_pan())
                return false;
        __uaccess_ttbr0_disable();
        return true;
}

커널에서 유저 데이터에 접근할 수 없도록 금지한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근하지 못하게 금지한다.

 

__uaccess_ttbr0_disable()

arch/arm64/include/asm/uaccess.h

static inline void __uaccess_ttbr0_disable(void)
{
        unsigned long flags, ttbr;

        local_irq_save(flags);
        ttbr = read_sysreg(ttbr1_el1);
        ttbr &= ~TTBR_ASID_MASK;
        /* reserved_ttbr0 placed before swapper_pg_dir */
        write_sysreg(ttbr - RESERVED_TTBR0_SIZE, ttbr0_el1);
        isb();
        /* Set reserved ASID */
        write_sysreg(ttbr, ttbr1_el1);
        isb();
        local_irq_restore(flags);
}

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하도록 ttbr을 설정한다.

  • TTBR1의 asid를 클리어하여 다시 TTBR1에 기록한다.
  • TTBR0에 reserved_ttbr0 라는 이름의 빈 페이지 테이블을 가리키게한다.
    • CONFIG_ARM64_SW_TTBR0_PAN 커널 옵션을 사용하는 경우 swapper_pg_dir 이전에 reserved_ttbr0 라는 이름의 빈 페이지 테이블이 구성되어 있다.

 


유저 도메인의 권한 설정 및 복원 – ARM32

uaccess_save_and_enable()

arch/arm/include/asm/uaccess.h

/*
 * These two functions allow hooking accesses to userspace to increase
 * system integrity by ensuring that the kernel can not inadvertantly
 * perform such accesses (eg, via list poison values) which could then
 * be exploited for priviledge escalation.
 */
static inline unsigned int uaccess_save_and_enable(void)
{
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        unsigned int old_domain = get_domain();

        /* Set the current domain access to permit user accesses */
        set_domain((old_domain & ~domain_mask(DOMAIN_USER)) |
                   domain_val(DOMAIN_USER, DOMAIN_CLIENT));

        return old_domain;
#else
        return 0;
#endif
}

유저 도메인의 값을 반환하고, 클라이언트 권한(페이지 테이블에 설정된 permission 대로 동작하기)을 부여한다.

 

uaccess_restore()

arch/arm/include/asm/uaccess.h

static inline void uaccess_restore(unsigned int flags)
{
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        /* Restore the user access mask */
        set_domain(flags);
#endif
}

유저 도메인의 값을 원래 값(flags)으로 복구한다.

 

도메인별 permission 권한 설정하기

set_domain()

arch/arm/include/asm/domain.h

static inline void set_domain(unsigned val)
{
        asm volatile(
        "mcr    p15, 0, %0, c3, c0      @ set domain"
          : : "r" (val) : "memory");
        isb();
}

DACR(Domain Access Control Register)을 사용하여 16개의 도메인을 지정한다.

  • ARM32 커널은 16개의 도메인 중 커널, 유저, IO 및 벡터 도메인을 지정하여 총 4개의 도메인을 사용한다.
  • 각 도메인 마다 2비트를 사용하여 다음과 같은 permission 사용 여부를 지정할 수 있다.
    • 0=no access
      • 항상 permission 에러가 발생한다.
    • 1=client
      • 페이지 변환 테이블에 지정된 permission 비트에 해당하는 체크를 수행한다.
    • 2=reserved
    • 3=manager
      • 페이지 변환 테이블에 지정된 permission 비트를 무시한다.

 

uaccess_mask_range_ptr() 매크로

include/asm/assembler.h

        .macro uaccess_mask_range_ptr, addr:req, size:req, limit:req, tmp:req
#ifdef CONFIG_CPU_SPECTRE
        sub     \tmp, \limit, #1
        subs    \tmp, \tmp, \addr       @ tmp = limit - 1 - addr
        addhs   \tmp, \tmp, #1          @ if (tmp >= 0) {
        subhss  \tmp, \tmp, \size       @ tmp = limit - (addr + size) }
        movlo   \addr, #0               @ if (tmp < 0) addr = NULL
        csdb
#endif
        .endm

 

 

uaccess_save 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_save, tmp
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        mrc     p15, 0, \tmp, c3, c0, 0
        str     \tmp, [sp, #SVC_DACR]
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근을 하지 못하게 제한한다. 그리고 이전 유저 도메인에 대한 도메인 타입을 스택이 가리키고 있는 pt_regs의 멤버 dacr에 백업한다.

 

uaccess_restore 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_restore
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        ldr     r0, [sp, #SVC_DACR]
        mcr     p15, 0, r0, c3, c0, 0
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근에 대한 여부를 기록하는데, 백업해 두었던 스택에 위치한 pt_regs의 멤버 dacr을 읽은 값을 기록한다.

 

uaccess_disable 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_disable, tmp, isb=1
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        /*
         * Whenever we re-enter userspace, the domains should always be
         * set appropriately.
         */
        mov     \tmp, #DACR_UACCESS_DISABLE
        mcr     p15, 0, \tmp, c3, c0, 0         @ Set domain register
        .if     \isb
        instr_sync
        .endif
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근을 하지 못하게 제한한다.

  • 코드 라인 7~8에서 커널에서 user 영역에 접근을 제한한다.
  • 코드 라인 9~11에서 @isb=1인 경우 instruction 베리어를 수행한다.

 

uaccess_enable 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_enable, tmp, isb=1
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        /*
         * Whenever we re-enter userspace, the domains should always be
         * set appropriately.
         */
        mov     \tmp, #DACR_UACCESS_ENABLE
        mcr     p15, 0, \tmp, c3, c0, 0
        .if     \isb
        instr_sync
        .endif
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근을 하지 못하게 제한한 것을 허용하게 한다.

  • 코드 라인 7~8에서 커널에서 user 영역에 접근하도록 허용한다.
  • 코드 라인 9~11에서 @isb=1인 경우 instruction 베리어를 수행한다.

 

DACR_UACCESS_DISABLE

arch/arm/include/asm/domain.h

#define DACR_UACCESS_DISABLE    \
        (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_NOACCESS))

유저 도메인의 타입을 noaccess로 지정한다. (커널에서 유저 영역의 액세스 금지)

 

DACR_UACCESS_ENABLE

arch/arm/include/asm/domain.h

#define DACR_UACCESS_DISABLE    \
        (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_NOACCESS))
#define DACR_UACCESS_ENABLE     \
        (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_CLIENT))

유저 도메인의 타입을 client로 지정한다. (커널에서 유저 영역의 액세스 허용)

 

arch/arm/include/asm/domain.h

#define __DACR_DEFAULT \
        domain_val(DOMAIN_KERNEL, DOMAIN_CLIENT) | \
        domain_val(DOMAIN_IO, DOMAIN_CLIENT) | \
        domain_val(DOMAIN_VECTORS, DOMAIN_CLIENT)

 

#define domain_val(dom,type)    ((type) << (2 * (dom)))

16개의 도메인 중 요청한 도메인 @dom에 도메인 @type을 지정한다.

  • DACR(Domain Access Control Register) 레지스터는 16개의 도메인에 2비트씩 도메인 타입을 지정할 수 있다.

 

도메인 타입

#define DOMAIN_NOACCESS 0
#define DOMAIN_CLIENT   1
#ifdef CONFIG_CPU_USE_DOMAINS
#define DOMAIN_MANAGER  3
#else
#define DOMAIN_MANAGER  1
#endif

 


유효한 유저 프로세스 주소 사용 여부 체크

 

access_ok() – ARM64

arch/arm64/include/asm/uaccess.h

#define access_ok(addr, size)   __range_ok(addr, size)

@addr 주소에서 @size 만큼의 영역이 유효한 유저 프로세스 주소인지 여부를 체크한다. 유효하지 않는 경우 0을 반환한다.

 

__range_ok() – ARM64

arch/arm64/include/asm/uaccess.h

/*
 * Test whether a block of memory is a valid user space address.
 * Returns 1 if the range is valid, 0 otherwise.
 *
 * This is equivalent to the following test:
 * (u65)addr + (u65)size <= (u65)current->addr_limit + 1
 */
static inline unsigned long __range_ok(const void __user *addr, unsigned long size)
{
        unsigned long ret, limit = current_thread_info()->addr_limit;

        __chk_user_ptr(addr);
        asm volatile(
        // A + B <= C + 1 for all A,B,C, in four easy steps:
        // 1: X = A + B; X' = X % 2^64
        "       adds    %0, %3, %2\n"
        // 2: Set C = 0 if X > 2^64, to guarantee X' > C in step 4
        "       csel    %1, xzr, %1, hi\n"
        // 3: Set X' = ~0 if X >= 2^64. For X == 2^64, this decrements X'
        //    to compensate for the carry flag being set in step 4. For
        //    X > 2^64, X' merely has to remain nonzero, which it does.
        "       csinv   %0, %0, xzr, cc\n"
        // 4: For X < 2^64, this gives us X' - C - 1 <= 0, where the -1
        //    comes from the carry in being clear. Otherwise, we are
        //    testing X' - C == 0, subject to the previous adjustments.
        "       sbcs    xzr, %0, %1\n"
        "       cset    %0, ls\n"
        : "=&r" (ret), "+r" (limit) : "Ir" (size), "0" (addr) : "cc");

        return ret;
}

 

access_ok() – ARM32

arch/arm/include/asm/uaccess.h

#define access_ok(addr, size)     (__range_ok(addr, size) == 0)

@addr 주소에서 @size 만큼의 영역이 유효한 유저 프로세스 주소인지 여부를 체크한다. 유효하지 않는 경우 0을 반환한다.

 

__range_ok() – ARM32

arch/arm/include/asm/uaccess.h

/* We use 33-bit arithmetic here... */
#define __range_ok(addr, size) ({ \
        unsigned long flag, roksum; \
        __chk_user_ptr(addr);   \
        __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
                : "=&r" (flag), "=&r" (roksum) \
                : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
                : "cc"); \
        flag; })

 


커널에서 유저 영역의 값(1, 2, 4, 8 바이트) 읽어오기

get_user() API는 아키텍처별로 약간의 다른 구현을 사용한다.

  • arm64
    • exception 테이블을 활용하여 읽기를 시도한다.
  • arm32
    • MMU를 사용하는 ARMv7 아키텍처에 한해 exception 테이블 사용없이 미리 체크를 한 후 읽기를 시도한다.
    • MMU를 사용하지 않는 경우 exception 테이블을 활용하여 읽기를 시도한다.

 

get_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define get_user        __get_user

커널에서 유저 영역의 주소 @p에 담긴 데이터를 @p 타입 사이즈만큼 읽어 커널 영역의 주소 @x에 대입한다. 만일 읽을 수 없으면 -EFAULT 에러를 반환한다.

 

__get_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define __get_user(x, ptr)                                              \
({                                                                      \
        int __gu_err = 0;                                               \
        __get_user_check((x), (ptr), __gu_err);                         \
        __gu_err;                                                       \
})

 

__get_user_check() – ARM64

arch/arm64/include/asm/uaccess.h

#define __get_user_check(x, ptr, err)                                   \
({                                                                      \
        __typeof__(*(ptr)) __user *__p = (ptr);                         \
        might_fault();                                                  \
        if (access_ok(__p, sizeof(*__p))) {                             \
                __p = uaccess_mask_ptr(__p);                            \
                __get_user_err((x), __p, (err));                        \
        } else {                                                        \
                (x) = 0; (err) = -EFAULT;                               \
        }                                                               \
})

 

__get_user_err() – ARM64

arch/arm64/include/asm/uaccess.h

#define __get_user_err(x, ptr, err)                                     \
do {                                                                    \
        unsigned long __gu_val;                                         \
        __chk_user_ptr(ptr);                                            \
        uaccess_enable_not_uao();                                       \
        switch (sizeof(*(ptr))) {                                       \
        case 1:                                                         \
                __get_user_asm("ldrb", "ldtrb", "%w", __gu_val, (ptr),  \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        case 2:                                                         \
                __get_user_asm("ldrh", "ldtrh", "%w", __gu_val, (ptr),  \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        case 4:                                                         \
                __get_user_asm("ldr", "ldtr", "%w", __gu_val, (ptr),    \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        case 8:                                                         \
                __get_user_asm("ldr", "ldtr", "%x",  __gu_val, (ptr),   \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        default:                                                        \
                BUILD_BUG();                                            \
        }                                                               \
        uaccess_disable_not_uao();                                      \
        (x) = (__force __typeof__(*(ptr)))__gu_val;                     \
} while (0)

 

__get_user_asm() – ARM64

arch/arm64/include/asm/uaccess.h

/*
 * The "__xxx" versions of the user access functions do not verify the address
 * space - it must have been done previously with a separate "access_ok()"
 * call.
 *
 * The "__xxx_error" versions set the third argument to -EFAULT if an error
 * occurs, and leave it unchanged on success.
 */
#define __get_user_asm(instr, alt_instr, reg, x, addr, err, feature)    \
        asm volatile(                                                   \
        "1:"ALTERNATIVE(instr "     " reg "1, [%2]\n",                  \
                        alt_instr " " reg "1, [%2]\n", feature)         \
        "2:\n"                                                          \
        "       .section .fixup, \"ax\"\n"                              \
        "       .align  2\n"                                            \
        "3:     mov     %w0, %3\n"                                      \
        "       mov     %1, #0\n"                                       \
        "       b       2b\n"                                           \
        "       .previous\n"                                            \
        _ASM_EXTABLE(1b, 3b)                                            \
        : "+r" (err), "=&r" (x)                                         \
        : "r" (addr), "i" (-EFAULT))

 

get_user() – ARM32

arch/arm/include/asm/uaccess.h

#define get_user(x, p)                                                  \
        ({                                                              \
                might_fault();                                          \
                __get_user_check(x, p);                                 \
         })

커널에서 유저 영역의 주소 @p에 담긴 데이터를 @p 타입 사이즈만큼 읽어 커널 영역의 주소 @x에 대입한다. 만일 읽을 수 없으면 -EFAULT 에러를 반환한다.

 

__get_user_check() – ARM32

arch/arm/include/asm/uaccess.h

#define __get_user_check(x, p)                                          \
        ({                                                              \
                unsigned long __limit = current_thread_info()->addr_limit - 1; \
                register typeof(*(p)) __user *__p asm("r0") = (p);      \
                register __inttype(x) __r2 asm("r2");                   \
                register unsigned long __l asm("r1") = __limit;         \
                register int __e asm("r0");                             \
                unsigned int __ua_flags = uaccess_save_and_enable();    \
                switch (sizeof(*(__p))) {                               \
                case 1:                                                 \
                        if (sizeof((x)) >= 8)                           \
                                __get_user_x_64t(__r2, __p, __e, __l, 1); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 1);   \
                        break;                                          \
                case 2:                                                 \
                        if (sizeof((x)) >= 8)                           \
                                __get_user_x_64t(__r2, __p, __e, __l, 2); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 2);   \
                        break;                                          \
                case 4:                                                 \
                        if (sizeof((x)) >= 8)                           \
                                __get_user_x_64t(__r2, __p, __e, __l, 4); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 4);   \
                        break;                                          \
                case 8:                                                 \
                        if (sizeof((x)) < 8)                            \
                                __get_user_x_32t(__r2, __p, __e, __l, 4); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 8);   \
                        break;                                          \
                default: __e = __get_user_bad(); break;                 \
                }                                                       \
                uaccess_restore(__ua_flags);                            \
                x = (typeof(*(p))) __r2;                                \
                __e;                                                    \
        })

 

__get_user_x() – ARM32

arch/arm/include/asm/uaccess.h

#define __get_user_x(__r2, __p, __e, __l, __s)                          \
           __asm__ __volatile__ (                                       \
                __asmeq("%0", "r0") __asmeq("%1", "r2")                 \
                __asmeq("%3", "r1")                                     \
                "bl     __get_user_" #__s                               \
                : "=&r" (__e), "=r" (__r2)                              \
                : "0" (__p), "r" (__l)                                  \
                : __GUP_CLOBBER_##__s)

사이즈(__s)에 따라 커널이 유저 영역을 읽는 함수를 호출한다.

  • 1, 2, 4, 8 바이트에 따른 호출함수가 라이브러리에 따로 준비되어 있다.

 

__get_user_1() – ARM32

arch/arm/lib/getuser.S

ENTRY(__get_user_1)
        check_uaccess r0, 1, r1, r2, __get_user_bad
1: TUSER(ldrb)  r2, [r0]
        mov     r0, #0
        ret     lr
ENDPROC(__get_user_1)
_ASM_NOKPROBE(__get_user_1)

커널에서 유저 영역의 1바이트를 읽어낸다. 만일 읽을 수 없으면 -EFAULT 에러를 반환한다.

 

check_uaccess() 매크로 – ARM32

arch/arm/include/asm/assembler.h

        .macro check_uaccess, addr:req, size:req, limit:req, tmp:req, bad:req
#ifndef CONFIG_CPU_USE_DOMAINS
        adds    \tmp, \addr, #\size - 1
        sbcccs  \tmp, \tmp, \limit
        bcs     \bad
#ifdef CONFIG_CPU_SPECTRE
        movcs   \addr, #0
        csdb
#endif
#endif
        .endm

커널에서 유저 데이터 엑세스에 문제가 없는지 확인한다. 에러 발생 시 -EFAULT를 반환한다.

  • MMU를 enable 시킨 ARMv7 아키텍처를 사용하면 CONFIG_CPU_SPECTRE 커널 옵션을 디폴트로 사용한다.
  • csdb 는 Consumption of Speculative Data Barrier를 의미한다.

 

__get_user_bad() & __get_user_bad8() – ARM32

arch/arm/lib/getuser.S

__get_user_bad8:
        mov     r3, #0
__get_user_bad:
        mov     r2, #0
        mov     r0, #-EFAULT
        ret     lr
ENDPROC(__get_user_bad)
ENDPROC(__get_user_bad8)
_ASM_NOKPROBE(__get_user_bad)
_ASM_NOKPROBE(__get_user_bad8)

커널에서 유저 데이터 액세스에 문제가 발생했을 때 처리할 함수이다. -EFAULT 에러를 r0 레지스터를 통해 반환한다.

 

참고

CPU Capabilities – ARM64

 

CPU Capabilities

 

CPU Capabilities

다음은 ARM64 시스템에서 사용되는 cpu capabilities(caps) 항목들이다.

arch/arm64/include/asm/cpucaps.h

#define ARM64_WORKAROUND_CLEAN_CACHE            0
#define ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE    1
#define ARM64_WORKAROUND_845719                 2
#define ARM64_HAS_SYSREG_GIC_CPUIF              3
#define ARM64_HAS_PAN                           4
#define ARM64_HAS_LSE_ATOMICS                   5
#define ARM64_WORKAROUND_CAVIUM_23154           6
#define ARM64_WORKAROUND_834220                 7
#define ARM64_HAS_NO_HW_PREFETCH                8
#define ARM64_HAS_UAO                           9
#define ARM64_ALT_PAN_NOT_UAO                   10
#define ARM64_HAS_VIRT_HOST_EXTN                11
#define ARM64_WORKAROUND_CAVIUM_27456           12
#define ARM64_HAS_32BIT_EL0                     13
#define ARM64_HARDEN_EL2_VECTORS                14
#define ARM64_HAS_CNP                           15
#define ARM64_HAS_NO_FPSIMD                     16
#define ARM64_WORKAROUND_REPEAT_TLBI            17
#define ARM64_WORKAROUND_QCOM_FALKOR_E1003      18
#define ARM64_WORKAROUND_858921                 19
#define ARM64_WORKAROUND_CAVIUM_30115           20
#define ARM64_HAS_DCPOP                         21
#define ARM64_SVE                               22
#define ARM64_UNMAP_KERNEL_AT_EL0               23
#define ARM64_HARDEN_BRANCH_PREDICTOR           24
#define ARM64_HAS_RAS_EXTN                      25
#define ARM64_WORKAROUND_843419                 26
#define ARM64_HAS_CACHE_IDC                     27
#define ARM64_HAS_CACHE_DIC                     28
#define ARM64_HW_DBM                            29
#define ARM64_SSBD                              30
#define ARM64_MISMATCHED_CACHE_TYPE             31
#define ARM64_HAS_STAGE2_FWB                    32
#define ARM64_HAS_CRC32                         33
#define ARM64_SSBS                              34
#define ARM64_WORKAROUND_1188873                35
#define ARM64_HAS_SB                            36
#define ARM64_WORKAROUND_1165522                37
#define ARM64_HAS_ADDRESS_AUTH_ARCH             38
#define ARM64_HAS_ADDRESS_AUTH_IMP_DEF          39
#define ARM64_HAS_GENERIC_AUTH_ARCH             40
#define ARM64_HAS_GENERIC_AUTH_IMP_DEF          41

#define ARM64_NCAPS                             42

 

다음과 같이 cpu_hwcaps 라는 전역 비트맵을 선언하여 관리한다.

DECLARE_BITMAP(cpu_hwcaps, ARM64_NCAPS);

 

cpus_have_const_cap(id) 인라인 함수를 사용하여 시스템 cpu caps의 동작 유무를 알아온다.

 

CPU Capabilities 등록

 

다음 그림은 4가지 목적의 CPU Capabilities를 보여준다.

 

1) for ARM64 CPU Features

ARM64 시스템의 cpu feature들은 컴파일 타임에 아래와 같이 arm64_features[] 배열에 등록된다.

arch/arm64/kernel/cpufeature.c

static const struct arm64_cpu_capabilities arm64_features[] = {
        {
                .desc = "GIC system register CPU interface",
                .capability = ARM64_HAS_SYSREG_GIC_CPUIF,
                .type = ARM64_CPUCAP_SYSTEM_FEATURE,
                .matches = has_useable_gicv3_cpuif,
                .sys_reg = SYS_ID_AA64PFR0_EL1,
                .field_pos = ID_AA64PFR0_GIC_SHIFT,
                .sign = FTR_UNSIGNED,
                .min_field_value = 1,
        },

(...생략...)

 

2) for ARM64 CPU ERRATA

ARM64 시스템의 CPU ERRATA들은 컴파일 타임에 아래와 같이 arm64_errata[] 배열에 등록된다.

arch/arm64/kernel/cpu_errata.c

const struct arm64_cpu_capabilities arm64_errata[] = {
#ifdef CONFIG_ARM64_WORKAROUND_CLEAN_CACHE
        {
                .desc = "ARM errata 826319, 827319, 824069, 819472",
                .capability = ARM64_WORKAROUND_CLEAN_CACHE,
                ERRATA_MIDR_RANGE_LIST(workaround_clean_cache),
                .cpu_enable = cpu_enable_cache_maint_trap,
        },
#endif

(...생략...)

 

3) for Authentication

ARM64 시스템의 ARMv8.3 아키텍처에서 소개된 포인터 인증 Features들은 컴파일 타임에 아래와 같이 ptr_auth_hwcap_addr_matches[] 배열 및 ptr_auth_hwcap_gen_matches[] 배열에 등록된다.

arch/arm64/kernel/cpufeature.c

#ifdef CONFIG_ARM64_PTR_AUTH
static const struct arm64_cpu_capabilities ptr_auth_hwcap_addr_matches[] = {
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_APA_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_APA_ARCHITECTED)
        },
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_API_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_API_IMP_DEF)
        },
        {},
};

static const struct arm64_cpu_capabilities ptr_auth_hwcap_gen_matches[] = {
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_GPA_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_GPA_ARCHITECTED)
        },
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_GPI_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_GPI_IMP_DEF)
        },
        {},
};
#endif

 

4) for ARM64 elf hwcaps

ARM64 시스템의 elf hwcaps들은 컴파일 타임에 아래와 같이 arm64_elf_hwcaps[] 배열에 등록된다.

arch/arm64/kernel/cpufeature.c

static const struct arm64_cpu_capabilities arm64_elf_hwcaps[] = {
        HWCAP_CAP(SYS_ID_AA64ISAR0_EL1, ID_AA64ISAR0_AES_SHIFT, FTR_UNSIGNED, 2, CAP_HWCAP, HWCAP_PMM
ULL),

(...생략...)

 

arm64_cpu_capabilities 구조체

arch/arm64/include/asm/cpufeature.h

struct arm64_cpu_capabilities {
        const char *desc;
        u16 capability;
        u16 type;
        bool (*matches)(const struct arm64_cpu_capabilities *caps, int scope);
        /*
         * Take the appropriate actions to enable this capability for this CPU.
         * For each successfully booted CPU, this method is called for each
         * globally detected capability.
         */
        void (*cpu_enable)(const struct arm64_cpu_capabilities *cap);
        union {
                struct {        /* To be used for erratum handling only */
                        struct midr_range midr_range;
                        const struct arm64_midr_revidr {
                                u32 midr_rv;            /* revision/variant */
                                u32 revidr_mask;
                        } * const fixed_revs;
                };

                const struct midr_range *midr_range_list;
                struct {        /* Feature register checking */
                        u32 sys_reg;
                        u8 field_pos;
                        u8 min_field_value;
                        u8 hwcap_type;
                        bool sign;
                        unsigned long hwcap;
                };
        };

        /*
         * An optional list of "matches/cpu_enable" pair for the same
         * "capability" of the same "type" as described by the parent.
         * Only matches(), cpu_enable() and fields relevant to these
         * methods are significant in the list. The cpu_enable is
         * invoked only if the corresponding entry "matches()".
         * However, if a cpu_enable() method is associated
         * with multiple matches(), care should be taken that either
         * the match criteria are mutually exclusive, or that the
         * method is robust against being called multiple times.
         */
        const struct arm64_cpu_capabilities *match_list;
};
  • *desc
    • 디스크립션
  • capability
    • capability 번호
  • type
    • 다음 3 비트를 사용하여 해당 feature가 다음 각각의 cpu scope에서 갱신되어야 하는 것을 알려준다.
      • SCOPE_LOCAL_CPU – bits0
      • SCOPE_SYSTEM – bits1
      • SCOPE_BOOT_CPU – bits2
    • 추가 2 비트로 late 상태에서의 동작을 알려준다.
      • ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU – bits4
      • ARM64_CPUCAP_OPTIONAL_FOR_LATE_CPU – bits5
  • (*matches)
    • cpu capability를 확인하는 후크 함수가 지정된다.
  • (*cpu_enable)
    • capability를 활성화하는 후크 함수가 지정된다.

유니온 타입 1) for erratum

  • midr_range
    • cpu 모델과, revision 최소 ~ 최대 범위를 표현한다.
  • fixed_revs->midr_rv
  • fixed_revs->revidr_mask
  • 다음 매크로 함수를 통해 등록한다.
    • ERRATA_MIDR_REV()
    • ERRATA_MIDR_RANGE()
    • ERRATA_MIDR_REV_RANGE()
    • MIDR_ALL_VERSIONS()
    • MIDR_RANGE()

유니온 타입 2) for erratum 리스트

  • *midr_range_list
    • 한 개 이상의 erratum 배열을 가리킨다.
    • 다음 매크로 함수를 통해 등록한다.
      • ERRATA_MIDR_RANGE_LIST()

유니온 타입 3) for feature 레지스터

  • sys_reg
    • 시스템 레지스터 id
    • 예) sys_reg(3, 0, 0, 6, 0)
  • field_pos
    • 필드 시작 비트
  • min_field_value
    • 필드 최소 값
  • hwcap_type
    • hwcap 타입
      • CAP_HWCAP(1) – for 64bit EL0
      • CAP_COMPAT_HWCAP(2) – for 32bit EL0 호환
      • CAP_COMPAT_HWCAP2(3) – for 32bit EL0 호환2
  • sign
    • 음수 비트 포함 여부
    • FTR_UNSIGNED(0), FTR_SIGNED(1)
  • hwcap
    • hwcap 플래그 (2^n)
    • fp=1, asimd=2, evtstrm=4, pmull=8, …

Pointer Authentication

  • *match_list
    • HWCAP_MULTI_CAP() 매크로를 통해 등록되는 매치들이다.

 

CPU Capabilities 접근

cpu caps는 커널과 유저에서 다음과 같이 사용된다.

  • 커널
    • 모든 CPU Capabilities의 사용 유무를 cpu_hwcaps를 트래킹하여 사용한다.
  • 유저
    • CPU Capabilities들 중 ELF HWCAPs 항목들은 유저에서 별도의 API를 통해 사용 가능 여부를 알 수 있다.

 


Capabilites Attributes

 

다음 그림은 주요 Capabilites Attributes를 보여준다.

 

 

Scope of Detection

시스템은 CPU ID Feature 레지스터의 필드 값을 체크하거나 CPU 모델을 체크하는 등 몇 가지 체크를 통하여 Capability 유무를 감지한다. 이러한 감지 동작은 CPU capabilities에 등록된 (*matches) 후크 함수를통해 수행한다. 다음 3 가지 scope 타입이 어떻게 수행되는지 알아본다.

  • SCOPE_LOCAL_CPU
    • 모든 cpu를 체크하여 최소 하나 이상 매치되는 경우이다.
    • 이의 의미는 부팅 시 인식한 모든 cpu를 체크한 후 상태를 결정하게 된다. (최대값 적용)
  • SCOPE_SYSTEM
    • 모든 cpu를 검사하고 모든 cpu가 동일한 상태일 때 감지된다.
    • 이것은 시스템이 feature의 상태를 완료((Finalise the state)하고 한 번만 검사를 실행 함을 의미한다.
    • Feature가 cpu id feature 레지스터 중 하나의 필드에 의존하는 경우 cpu feature 레지스터의 삭제 된 값을 사용하여 결정한다. (최소값 적용)
  • SCOPE_BOOT_CPU
    • 기본 부트 cpu에서만 기능을 감지한다.
    • 이 scope은 SMP cpu가 시작되기 전에 조기(early)에 커널에 의해 상태 완료(Finalise the state) 및 기능의 수행을 위한다.

 

다음 그림은 기본 cpu scope과 conflict 상황에서 예외처리되는 옵션을 보여준다.

  • conflict 상황 예외 옵션
    • ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU
      • late cpu로 부터 새로운 feature가 감지될 때 그냥 허용한다.
    • ARM64_CPUCAP_OPTIONAL_FOR_LATE_CPU
      • 시스템 feature는 있지만 late cpu에 feature가 감지되지 않을 때 그냥 허용한다.

 

다음 그림은 각 feature에 실제 적용될 때 사용한 복합 cpu scope 들이다.

 


ARM64 ELF hwcaps

HMP(Heterogeneous Multi Processor) 시스템에서 어느 한 cpu의 특정 feature가 없는 경우를 생각해보자. 예를 들어 Floating Pointer 기능이 어느 한 쪽에 없는 경우 FP 기능을 사용하는 유저 Application이 FP가 없는 프로세스에서 수행이 중단될 수 있다. 이러한 경우를 막기 위해서는 시스템 차원에서 해당 기능을 차단해야 한다. 이러한 기능들이 hotplug cpu 시스템 기반에서 동작할 때에는 각 cpu가 late하게 online/offline 상태가 변경될 때마다 갱신되는 cpu caps를 시스템 차원에서 판단하여 feature로 제공될 수 있는지 여부를 체크하여야 한다. 이렇게 체크된 결과는 유저 스페이스 레벨(EL0)에 elf_hwcaps를 액세스하는 API를 통해 CPU Feature의 사용 여부를 알 수 있게 한다.

 

총 32비트로 구성된 HWCAP 플래그이다.

/*
 * HWCAP flags - for elf_hwcap (in kernel) and AT_HWCAP
 */
#define HWCAP_FP                (1 << 0)
#define HWCAP_ASIMD             (1 << 1)
#define HWCAP_EVTSTRM           (1 << 2)
#define HWCAP_AES               (1 << 3)
#define HWCAP_PMULL             (1 << 4)
#define HWCAP_SHA1              (1 << 5)
#define HWCAP_SHA2              (1 << 6)
#define HWCAP_CRC32             (1 << 7)
#define HWCAP_ATOMICS           (1 << 8)
#define HWCAP_FPHP              (1 << 9)
#define HWCAP_ASIMDHP           (1 << 10)
#define HWCAP_CPUID             (1 << 11)
#define HWCAP_ASIMDRDM          (1 << 12)
#define HWCAP_JSCVT             (1 << 13)
#define HWCAP_FCMA              (1 << 14)
#define HWCAP_LRCPC             (1 << 15)
#define HWCAP_DCPOP             (1 << 16)
#define HWCAP_SHA3              (1 << 17)
#define HWCAP_SM3               (1 << 18)
#define HWCAP_SM4               (1 << 19)
#define HWCAP_ASIMDDP           (1 << 20)
#define HWCAP_SHA512            (1 << 21)
#define HWCAP_SVE               (1 << 22)
#define HWCAP_ASIMDFHM          (1 << 23)
#define HWCAP_DIT               (1 << 24)
#define HWCAP_USCAT             (1 << 25)
#define HWCAP_ILRCPC            (1 << 26)
#define HWCAP_FLAGM             (1 << 27)
#define HWCAP_SSBS              (1 << 28)
#define HWCAP_SB                (1 << 29)
#define HWCAP_PACA              (1 << 30)
#define HWCAP_PACG              (1UL << 31)

 

다음과 같은 방법으로 유저 레벨에서 AT_HWCAP 보조 벡터를 사용하여 특정 feature의 사용 가능 여부를 알아낼 수 있다.

bool floating_point_is_present(void)
{
        unsigned long hwcaps = getauxval(AT_HWCAP);
        if (hwcaps & HWCAP_FP)
                return true;

        return false;
}

 

다음 코드는 유저 레벨에서 AT_HWCAP 보조 벡터를 사용하여 HWCAP_CPUID feature의 사용 가능 여부를 알아낸 후 mrs 명령을 통해 각 레지스터를 읽어오는 모습을 보여준다.

#include <asm/hwcap.h>
#include <stdio.h>
#include <sys/auxv.h>

#define get_cpu_ftr(id) ({					\
		unsigned long __val;				\
		asm("mrs %0, "#id : "=r" (__val));		\
		printf("%-20s: 0x%016lx\n", #id, __val);	\
	})

int main(void)
{

	if (!(getauxval(AT_HWCAP) & HWCAP_CPUID)) {
		fputs("CPUID registers unavailable\n", stderr);
		return 1;
	}

	get_cpu_ftr(ID_AA64ISAR0_EL1);
	get_cpu_ftr(ID_AA64ISAR1_EL1);
	get_cpu_ftr(ID_AA64MMFR0_EL1);
	get_cpu_ftr(ID_AA64MMFR1_EL1);
	get_cpu_ftr(ID_AA64PFR0_EL1);
	get_cpu_ftr(ID_AA64PFR1_EL1);
	get_cpu_ftr(ID_AA64DFR0_EL1);
	get_cpu_ftr(ID_AA64DFR1_EL1);

	get_cpu_ftr(MIDR_EL1);
	get_cpu_ftr(MPIDR_EL1);
	get_cpu_ftr(REVIDR_EL1);

#if 0
	/* Unexposed register access causes SIGILL */
	get_cpu_ftr(ID_MMFR0_EL1);
#endif

	return 0;
}

 

다음과 같은 방법으로 cpu별로 features를 파악할 수 있다.

$ cat /proc/cpuinfo
processor       : 0
BogoMIPS        : 48.00
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0xd03
CPU revision    : 4

 


CPU Features 초기화 및 갱신

다음 그림은 부트업 타임에 cpu feature들이 초기화되고 각 cpu가 on/off 되면서 cpu feature들이 갱신되는 과정을 보여준다.

 

다음 그림은 init_cpu_features() 및 setup_cpu_features() 함수들의 호출 관계를 보여준다.

Boot CPU에서 cpu features 초기화

init_cpu_features()

arch/arm64/kernel/cpufeature.c

void __init init_cpu_features(struct cpuinfo_arm64 *info)
{
        /* Before we start using the tables, make sure it is sorted */
        sort_ftr_regs();

        init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr);
        init_cpu_ftr_reg(SYS_DCZID_EL0, info->reg_dczid);
        init_cpu_ftr_reg(SYS_CNTFRQ_EL0, info->reg_cntfrq);
        init_cpu_ftr_reg(SYS_ID_AA64DFR0_EL1, info->reg_id_aa64dfr0);
        init_cpu_ftr_reg(SYS_ID_AA64DFR1_EL1, info->reg_id_aa64dfr1);
        init_cpu_ftr_reg(SYS_ID_AA64ISAR0_EL1, info->reg_id_aa64isar0);
        init_cpu_ftr_reg(SYS_ID_AA64ISAR1_EL1, info->reg_id_aa64isar1);
        init_cpu_ftr_reg(SYS_ID_AA64MMFR0_EL1, info->reg_id_aa64mmfr0);
        init_cpu_ftr_reg(SYS_ID_AA64MMFR1_EL1, info->reg_id_aa64mmfr1);
        init_cpu_ftr_reg(SYS_ID_AA64MMFR2_EL1, info->reg_id_aa64mmfr2);
        init_cpu_ftr_reg(SYS_ID_AA64PFR0_EL1, info->reg_id_aa64pfr0);
        init_cpu_ftr_reg(SYS_ID_AA64PFR1_EL1, info->reg_id_aa64pfr1);
        init_cpu_ftr_reg(SYS_ID_AA64ZFR0_EL1, info->reg_id_aa64zfr0);

        if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
                init_cpu_ftr_reg(SYS_ID_DFR0_EL1, info->reg_id_dfr0);
                init_cpu_ftr_reg(SYS_ID_ISAR0_EL1, info->reg_id_isar0);
                init_cpu_ftr_reg(SYS_ID_ISAR1_EL1, info->reg_id_isar1);
                init_cpu_ftr_reg(SYS_ID_ISAR2_EL1, info->reg_id_isar2);
                init_cpu_ftr_reg(SYS_ID_ISAR3_EL1, info->reg_id_isar3);
                init_cpu_ftr_reg(SYS_ID_ISAR4_EL1, info->reg_id_isar4);
                init_cpu_ftr_reg(SYS_ID_ISAR5_EL1, info->reg_id_isar5);
                init_cpu_ftr_reg(SYS_ID_MMFR0_EL1, info->reg_id_mmfr0);
                init_cpu_ftr_reg(SYS_ID_MMFR1_EL1, info->reg_id_mmfr1);
                init_cpu_ftr_reg(SYS_ID_MMFR2_EL1, info->reg_id_mmfr2);
                init_cpu_ftr_reg(SYS_ID_MMFR3_EL1, info->reg_id_mmfr3);
                init_cpu_ftr_reg(SYS_ID_PFR0_EL1, info->reg_id_pfr0);
                init_cpu_ftr_reg(SYS_ID_PFR1_EL1, info->reg_id_pfr1);
                init_cpu_ftr_reg(SYS_MVFR0_EL1, info->reg_mvfr0);
                init_cpu_ftr_reg(SYS_MVFR1_EL1, info->reg_mvfr1);
                init_cpu_ftr_reg(SYS_MVFR2_EL1, info->reg_mvfr2);
        }

        if (id_aa64pfr0_sve(info->reg_id_aa64pfr0)) {
                init_cpu_ftr_reg(SYS_ZCR_EL1, info->reg_zcr);
                sve_init_vq_map();
        }

        /*
         * Initialize the indirect array of CPU hwcaps capabilities pointers
         * before we handle the boot CPU below.
         */
        init_cpu_hwcaps_indirect_list();

        /*
         * Detect and enable early CPU capabilities based on the boot CPU,
         * after we have initialised the CPU feature infrastructure.
         */
        setup_boot_cpu_capabilities();
}

부트업 CPU의 시스템 레지스터에서 읽은 정보를 사용하여 cpu feature 레지스터를 초기화하고, boot scope에 해당하는 cpu caps를 활성화시킨다.

 

init_cpu_ftr_reg()

arch/arm64/kernel/cpufeature.c

/*
 * Initialise the CPU feature register from Boot CPU values.
 * Also initiliases the strict_mask for the register.
 * Any bits that are not covered by an arm64_ftr_bits entry are considered
 * RES0 for the system-wide value, and must strictly match.
 */
static void __init init_cpu_ftr_reg(u32 sys_reg, u64 new)
{
        u64 val = 0;
        u64 strict_mask = ~0x0ULL;
        u64 user_mask = 0;
        u64 valid_mask = 0;

        const struct arm64_ftr_bits *ftrp;
        struct arm64_ftr_reg *reg = get_arm64_ftr_reg(sys_reg);

        BUG_ON(!reg);

        for (ftrp  = reg->ftr_bits; ftrp->width; ftrp++) {
                u64 ftr_mask = arm64_ftr_mask(ftrp);
                s64 ftr_new = arm64_ftr_value(ftrp, new);

                val = arm64_ftr_set_value(ftrp, val, ftr_new);

                valid_mask |= ftr_mask;
                if (!ftrp->strict)
                        strict_mask &= ~ftr_mask;
                if (ftrp->visible)
                        user_mask |= ftr_mask;
                else
                        reg->user_val = arm64_ftr_set_value(ftrp,
                                                            reg->user_val,
                                                            ftrp->safe_val);
        }

        val &= valid_mask;

        reg->sys_val = val;
        reg->strict_mask = strict_mask;
        reg->user_mask = user_mask;
}

boot cpu에서 읽은 시스템 레지스터 값으로 요청한 cpu feature 레지스터를 초기화한다.

  • 코드 라인 9에서 인자로 요청한 시스템 레지스터 @sys_reg에 대해 레지스터 관리 값을 읽어온다.
  • 코드 라인 13~17에서 해당 레지스터의 필드들을 순회하며 필드에 적용되는 비트들만을 1로 하는 마스크 값과 타입에 따라 적용할 값을 알아와서 feature 레지스터 값으로 기록한다.
    • 각 타입에 따라 최소 값, 최대값, safe 값으로만 갱신된다.
  • 코드 라인 18에서 필드들이 사용하는 비트들이 1인 마스크 값들을 모두 valid_mask로 모은다.
  • 코드 라인 19~20에서 해당 필드가 strict 체크를 원하지 않는 경우 strict_mask에 해당 필드를 제외시킨다.
  • 코드 라인 21~26에서 해당 필드가 유저 접근을 허용하는 경우 유저 마스크에 필드 마스크들을 포함시킨다. 유저 접근을 허용하지 않는 경우 유저 값으로 safe 값을 더한다.
  • 코드 라인 29~31에서 레지스터에 대한 sys_val 값으로 레지스터에 저장한 값에 valid 마스크를 적용한 값을 저장한다.
  • 코드 라인 32~33에서 strict 마스크와 유저 마스크를 대입한다.

 

다음 그림은 Cache Type Register를 읽어 해당 Feature 레지스터를 초기화하는 모습을 보여준다.

 

init_cpu_hwcaps_indirect_list()

arch/arm64/kernel/cpufeature.c

static void __init init_cpu_hwcaps_indirect_list(void)
{
        init_cpu_hwcaps_indirect_list_from_array(arm64_features);
        init_cpu_hwcaps_indirect_list_from_array(arm64_errata);
}

cpu hwcaps인 두 arm64_features들과 arm64_errata들에 대응하는 간접 포인터를 설정한다.

 

init_cpu_hwcaps_indirect_list_from_array()

arch/arm64/kernel/cpufeature.c

static void __init
init_cpu_hwcaps_indirect_list_from_array(const struct arm64_cpu_capabilities *caps)
{
        for (; caps->matches; caps++) {
                if (WARN(caps->capability >= ARM64_NCAPS,
                        "Invalid capability %d\n", caps->capability))
                        continue;
                if (WARN(cpu_hwcaps_ptrs[caps->capability],
                        "Duplicate entry for capability %d\n",
                        caps->capability))
                        continue;
                cpu_hwcaps_ptrs[caps->capability] = caps;
        }
}

cpu hwcaps인 @caps들에 대응하는 간접 포인터를 설정한다.

 

setup_boot_cpu_capabilities()

arch/arm64/kernel/cpufeature.c

static void __init setup_boot_cpu_capabilities(void)
{
        /* Detect capabilities with either SCOPE_BOOT_CPU or SCOPE_LOCAL_CPU */
        update_cpu_capabilities(SCOPE_BOOT_CPU | SCOPE_LOCAL_CPU);
        /* Enable the SCOPE_BOOT_CPU capabilities alone right away */
        enable_cpu_capabilities(SCOPE_BOOT_CPU);
}

boot cpu 차원의 cap 들을 동작하도록 준비한다.

  • boot 및 local scope의 cpu caps를 갱신하고, boot scope의 caps들의 기능을 enable 한다.

 


각 CPU에서 cpu features 셋업

setup_cpu_features()

arch/arm64/kernel/cpufeature.c

void __init setup_cpu_features(void)
{
        u32 cwg;

        setup_system_capabilities();
        mark_const_caps_ready();
        setup_elf_hwcaps(arm64_elf_hwcaps);

        if (system_supports_32bit_el0())
                setup_elf_hwcaps(compat_elf_hwcaps);

        if (system_uses_ttbr0_pan())
                pr_info("emulated: Privileged Access Never (PAN) using TTBR0_EL1 switching\n");

        sve_setup();
        minsigstksz_setup();

        /* Advertise that we have computed the system capabilities */
        set_sys_caps_initialised();

        /*
         * Check for sane CTR_EL0.CWG value.
         */
        cwg = cache_type_cwg();
        if (!cwg)
                pr_warn("No Cache Writeback Granule information, assuming %d\n",
                        ARCH_DMA_MINALIGN);
}

부트업 cpu를 제외한 secondary cpu 초기화 루틴에서 호출되어 cpu feature를 enable 한다.

  • 코드 라인 5에서 system 차원의 cap 들을 동작하도록 준비한다.
  • 코드 라인 6에서 const caps가 동작할 수 있도록 ready 상태로 설정한다.
  • 코드 라인 7에서 arm64_elf_hwcaps들을 사용하여 elf_hwcap을 설정한다.
  • 코드 라인 9~10에서 32bit 호환을 위해 compat_elf_hwcaps들을 사용하여 compat_elf_hwcap2를 설정한다.
  • 코드 라인 12~13에서 ttbr0_pan 기능을 사용하는 시스템인 경우 이 기능을 사용한다고 정보 출력을 한다.
  • 코드 라인 15에서 SVE(Scalable Vector Extension support) 기능을 지원하는 경우 sve를 설정한다.
  • 코드 라인 16에서 시그널 전달을 위한 최소 스택 사이즈를 설정한다.
  • 코드 라인 19에서 system caps가 초기화되었음을 나타내도록 설정한다.
  • 코드 라인 24~27에서 캐시 타입 레지스터에서 Cache Writeback Granule 값을 읽어 0인 경우 경고 메시지를 출력한다.

 

setup_system_capabilities()

arch/arm64/kernel/cpufeature.c

static void __init setup_system_capabilities(void)
{
        /*
         * We have finalised the system-wide safe feature
         * registers, finalise the capabilities that depend
         * on it. Also enable all the available capabilities,
         * that are not enabled already.
         */
        update_cpu_capabilities(SCOPE_SYSTEM);
        enable_cpu_capabilities(SCOPE_ALL & ~SCOPE_BOOT_CPU);
}

system 차원의 cap 들을 동작하도록 준비한다.

  • system scope의 cpu caps를 갱신하고, non-boot scope의 caps들의 기능을 enable 한다.

 

mark_const_caps_ready()

arch/arm64/kernel/cpufeature.c

static void __init mark_const_caps_ready(void)
{
        static_branch_enable(&arm64_const_caps_ready);
}

const caps가 동작할 수 있도록 ready 상태로 설정한다.

 

setup_elf_hwcaps()

arch/arm64/kernel/cpufeature.c

static void __init setup_elf_hwcaps(const struct arm64_cpu_capabilities *hwcaps)
{
        /* We support emulation of accesses to CPU ID feature registers */
        elf_hwcap |= HWCAP_CPUID;
        for (; hwcaps->matches; hwcaps++)
                if (hwcaps->matches(hwcaps, cpucap_default_scope(hwcaps)))
                        cap_set_elf_hwcap(hwcaps);
}

인자로 요청한 @hwcaps들로 elf hwcap를 설정한다.

 

cap_set_elf_hwcap()

arch/arm64/kernel/cpufeature.c

static void __init cap_set_elf_hwcap(const struct arm64_cpu_capabilities *cap)
{
        switch (cap->hwcap_type) {
        case CAP_HWCAP:
                elf_hwcap |= cap->hwcap;
                break;
#ifdef CONFIG_COMPAT
        case CAP_COMPAT_HWCAP:
                compat_elf_hwcap |= (u32)cap->hwcap;
                break;
        case CAP_COMPAT_HWCAP2:
                compat_elf_hwcap2 |= (u32)cap->hwcap;
                break;
#endif
        default:
                WARN_ON(1);
                break;
        }
}

인자로 요청한 @cap으로 elf hwcap를 설정한다.

  • 타입에 따라 elf_hwcap, compat_elf_hwcap 또는 compat_elf_hwcap2 전역 변수를 설정한다.

 

각 CPU에서 cpu features 갱신

cpu가 online될 때  secondary_start_kernel() -> cpuinfo_store_cpu() 함수 경로를 통해 해당 cpu의 feature들을 갱신한다.

  • 부트 cpu와 새로 online되는 cpu와 기능이 서로 다른 경우에는 경고 메시지를 출력한다.

 

update_cpu_features()

arch/arm64/kernel/cpufeature.c -1/2-

/*
 * Update system wide CPU feature registers with the values from a
 * non-boot CPU. Also performs SANITY checks to make sure that there
 * aren't any insane variations from that of the boot CPU.
 */
void update_cpu_features(int cpu,
                         struct cpuinfo_arm64 *info,
                         struct cpuinfo_arm64 *boot)
{
        int taint = 0;

        /*
         * The kernel can handle differing I-cache policies, but otherwise
         * caches should look identical. Userspace JITs will make use of
         * *minLine.
         */
        taint |= check_update_ftr_reg(SYS_CTR_EL0, cpu,
                                      info->reg_ctr, boot->reg_ctr);

        /*
         * Userspace may perform DC ZVA instructions. Mismatched block sizes
         * could result in too much or too little memory being zeroed if a
         * process is preempted and migrated between CPUs.
         */
        taint |= check_update_ftr_reg(SYS_DCZID_EL0, cpu,
                                      info->reg_dczid, boot->reg_dczid);

        /* If different, timekeeping will be broken (especially with KVM) */
        taint |= check_update_ftr_reg(SYS_CNTFRQ_EL0, cpu,
                                      info->reg_cntfrq, boot->reg_cntfrq);

        /*
         * The kernel uses self-hosted debug features and expects CPUs to
         * support identical debug features. We presently need CTX_CMPs, WRPs,
         * and BRPs to be identical.
         * ID_AA64DFR1 is currently RES0.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64DFR0_EL1, cpu,
                                      info->reg_id_aa64dfr0, boot->reg_id_aa64dfr0);
        taint |= check_update_ftr_reg(SYS_ID_AA64DFR1_EL1, cpu,
                                      info->reg_id_aa64dfr1, boot->reg_id_aa64dfr1);
        /*
         * Even in big.LITTLE, processors should be identical instruction-set
         * wise.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64ISAR0_EL1, cpu,
                                      info->reg_id_aa64isar0, boot->reg_id_aa64isar0);
        taint |= check_update_ftr_reg(SYS_ID_AA64ISAR1_EL1, cpu,
                                      info->reg_id_aa64isar1, boot->reg_id_aa64isar1);

        /*
         * Differing PARange support is fine as long as all peripherals and
         * memory are mapped within the minimum PARange of all CPUs.
         * Linux should not care about secure memory.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64MMFR0_EL1, cpu,
                                      info->reg_id_aa64mmfr0, boot->reg_id_aa64mmfr0);
        taint |= check_update_ftr_reg(SYS_ID_AA64MMFR1_EL1, cpu,
                                      info->reg_id_aa64mmfr1, boot->reg_id_aa64mmfr1);
        taint |= check_update_ftr_reg(SYS_ID_AA64MMFR2_EL1, cpu,
                                      info->reg_id_aa64mmfr2, boot->reg_id_aa64mmfr2);

 

arch/arm64/kernel/cpufeature.c -2/2-

        /*
         * EL3 is not our concern.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64PFR0_EL1, cpu,
                                      info->reg_id_aa64pfr0, boot->reg_id_aa64pfr0);
        taint |= check_update_ftr_reg(SYS_ID_AA64PFR1_EL1, cpu,
                                      info->reg_id_aa64pfr1, boot->reg_id_aa64pfr1);

        taint |= check_update_ftr_reg(SYS_ID_AA64ZFR0_EL1, cpu,
                                      info->reg_id_aa64zfr0, boot->reg_id_aa64zfr0);

        /*
         * If we have AArch32, we care about 32-bit features for compat.
         * If the system doesn't support AArch32, don't update them.
         */
        if (id_aa64pfr0_32bit_el0(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1)) &&
                id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {

                taint |= check_update_ftr_reg(SYS_ID_DFR0_EL1, cpu,
                                        info->reg_id_dfr0, boot->reg_id_dfr0);
                taint |= check_update_ftr_reg(SYS_ID_ISAR0_EL1, cpu,
                                        info->reg_id_isar0, boot->reg_id_isar0);
                taint |= check_update_ftr_reg(SYS_ID_ISAR1_EL1, cpu,
                                        info->reg_id_isar1, boot->reg_id_isar1);
                taint |= check_update_ftr_reg(SYS_ID_ISAR2_EL1, cpu,
                                        info->reg_id_isar2, boot->reg_id_isar2);
                taint |= check_update_ftr_reg(SYS_ID_ISAR3_EL1, cpu,
                                        info->reg_id_isar3, boot->reg_id_isar3);
                taint |= check_update_ftr_reg(SYS_ID_ISAR4_EL1, cpu,
                                        info->reg_id_isar4, boot->reg_id_isar4);
                taint |= check_update_ftr_reg(SYS_ID_ISAR5_EL1, cpu,
                                        info->reg_id_isar5, boot->reg_id_isar5);

                /*
                 * Regardless of the value of the AuxReg field, the AIFSR, ADFSR, and
                 * ACTLR formats could differ across CPUs and therefore would have to
                 * be trapped for virtualization anyway.
                 */
                taint |= check_update_ftr_reg(SYS_ID_MMFR0_EL1, cpu,
                                        info->reg_id_mmfr0, boot->reg_id_mmfr0);
                taint |= check_update_ftr_reg(SYS_ID_MMFR1_EL1, cpu,
                                        info->reg_id_mmfr1, boot->reg_id_mmfr1);
                taint |= check_update_ftr_reg(SYS_ID_MMFR2_EL1, cpu,
                                        info->reg_id_mmfr2, boot->reg_id_mmfr2);
                taint |= check_update_ftr_reg(SYS_ID_MMFR3_EL1, cpu,
                                        info->reg_id_mmfr3, boot->reg_id_mmfr3);
                taint |= check_update_ftr_reg(SYS_ID_PFR0_EL1, cpu,
                                        info->reg_id_pfr0, boot->reg_id_pfr0);
                taint |= check_update_ftr_reg(SYS_ID_PFR1_EL1, cpu,
                                        info->reg_id_pfr1, boot->reg_id_pfr1);
                taint |= check_update_ftr_reg(SYS_MVFR0_EL1, cpu,
                                        info->reg_mvfr0, boot->reg_mvfr0);
                taint |= check_update_ftr_reg(SYS_MVFR1_EL1, cpu,
                                        info->reg_mvfr1, boot->reg_mvfr1);
                taint |= check_update_ftr_reg(SYS_MVFR2_EL1, cpu,
                                        info->reg_mvfr2, boot->reg_mvfr2);
        }

        if (id_aa64pfr0_sve(info->reg_id_aa64pfr0)) {
                taint |= check_update_ftr_reg(SYS_ZCR_EL1, cpu,
                                        info->reg_zcr, boot->reg_zcr);

                /* Probe vector lengths, unless we already gave up on SVE */
                if (id_aa64pfr0_sve(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1)) &&
                    !sys_caps_initialised)
                        sve_update_vq_map();
        }

        /*
         * Mismatched CPU features are a recipe for disaster. Don't even
         * pretend to support them.
         */
        if (taint) {
                pr_warn_once("Unsupported CPU feature variation detected.\n");
                add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
        }
}

요청한 @cpu에 대한 cpu 정보 @info로 각 시스템 레지스터들을 갱신한다. 만일 새 cpu의 feature가 boot cpu에 대한 feature 정보와 다른 경우 경고 메시지를 출력한다.

 

id_aa64pfr0_32bit_el0()

arch/arm64/include/asm/cpufeature.h

static inline bool id_aa64pfr0_32bit_el0(u64 pfr0)
{
        u32 val = cpuid_feature_extract_unsigned_field(pfr0, ID_AA64PFR0_EL0_SHIFT);

        return val == ID_AA64PFR0_EL0_32BIT_64BIT;
}

ID_AA64PFR0_EL1 레지스터의 EL0 필드를 읽어 32비트 및 64비트 유저 레벨을 지원하는지 여부를 반환한다.

  • EL0 필드값이 1인 경우 AArch64 상태만 지원하고, 2인 경우 AArch64 및 AArch32 상태를 지원한다.

 

id_aa64pfr0_sve()

arch/arm64/include/asm/cpufeature.h

static inline bool id_aa64pfr0_sve(u64 pfr0)
{
        u32 val = cpuid_feature_extract_unsigned_field(pfr0, ID_AA64PFR0_SVE_SHIFT);

        return val > 0;
}

ID_AA64PFR0_EL1 레지스터의 SVE 필드를 읽어 SVE(Scalable Vector Extension) 기능이 지원되는지 여부를 반환한다.

 

check_update_ftr_reg()

arch/arm64/kernel/cpufeature.c

static int check_update_ftr_reg(u32 sys_id, int cpu, u64 val, u64 boot)
{
        struct arm64_ftr_reg *regp = get_arm64_ftr_reg(sys_id);

        BUG_ON(!regp);
        update_cpu_ftr_reg(regp, val);
        if ((boot & regp->strict_mask) == (val & regp->strict_mask))
                return 0;
        pr_warn("SANITY CHECK: Unexpected variation in %s. Boot CPU: %#016llx, CPU%d: %#016llx\n",
                        regp->name, boot, cpu, val);
        return 1;
}

시스템 레지스터 id에 해당하는 레지스터의 각 필드 값에 필드 타입별로 새 cpu의 @val 값을 반영하여 갱신하고 갱신 유무를 반환한다. 만일 boot cpu의 값과 새 cpu의 값이 다른 경우 1을 반환하고 경고 메시지를 출력한다.

 

update_cpu_ftr_reg()

arch/arm64/kernel/cpufeature.c

static void update_cpu_ftr_reg(struct arm64_ftr_reg *reg, u64 new)
{
        const struct arm64_ftr_bits *ftrp;

        for (ftrp = reg->ftr_bits; ftrp->width; ftrp++) {
                s64 ftr_cur = arm64_ftr_value(ftrp, reg->sys_val);
                s64 ftr_new = arm64_ftr_value(ftrp, new);

                if (ftr_cur == ftr_new)
                        continue;
                /* Find a safe value */
                ftr_new = arm64_ftr_safe_value(ftrp, ftr_new, ftr_cur);
                reg->sys_val = arm64_ftr_set_value(ftrp, reg->sys_val, ftr_new);
        }

}

64비트 feature 레지스터 @reg의 각 필드 값에 필드 타입별로 @new 값을 반영하여 갱신한다.

  • 코드 라인 5~10에서 feature 레지스터의 각 필드를 순회하며 기존 필드 값과 새로 갱신할 필드 값이 동일한 경우 skip 한다.
  • 코드 라인 12~13에서 갱신할 필드 값과 기존 값 중 사용할 값을 알아온 후 해당 필드를 갱신한다.

 


CPU Capabilities 갱신

update_cpu_capabilities()

arch/arm64/kernel/cpufeature.c

static void update_cpu_capabilities(u16 scope_mask)
{
        int i;
        const struct arm64_cpu_capabilities *caps;

        scope_mask &= ARM64_CPUCAP_SCOPE_MASK;
        for (i = 0; i < ARM64_NCAPS; i++) {
                caps = cpu_hwcaps_ptrs[i];
                if (!caps || !(caps->type & scope_mask) ||
                    cpus_have_cap(caps->capability) ||
                    !caps->matches(caps, cpucap_default_scope(caps)))
                        continue;

                if (caps->desc)
                        pr_info("detected: %s\n", caps->desc);
                cpus_set_cap(caps->capability);
        }
}

cpu capabilities를 갱신한다.

  • 코드 라인 7~8에서 cpu caps 수 만큼 순회한다.
  • 코드 라인 9~12에서 다음 항목들은 skip 한다.
    • cpu cap이 없다.
    • scope mask에 포함되지 않은 타입이다.
    • capability가 이미 지정되었다.
    • 디폴트 scope에 매치되지 않는다.
  • 코드 라인 14~16에서 cpu cap의 디스크립션을 출력하고 capabilies 번호에 대한 cpu_hwcaps 비트를 설정한다.

 

cpus_set_cap()

arch/arm64/include/asm/cpufeature.h

static inline void cpus_set_cap(unsigned int num)
{
        if (num >= ARM64_NCAPS) {
                pr_warn("Attempt to set an illegal CPU capability (%d >= %d)\n",
                        num, ARM64_NCAPS);
        } else {
                __set_bit(num, cpu_hwcaps);
        }
}

요청 @num에 대한 cpu_hwcaps 비트를 설정한다.

 

enable_cpu_capabilities()

arch/arm64/kernel/cpufeature.c

/*
 * Run through the enabled capabilities and enable() it on all active
 * CPUs
 */
static void __init enable_cpu_capabilities(u16 scope_mask)
{
        int i;
        const struct arm64_cpu_capabilities *caps;
        bool boot_scope;

        scope_mask &= ARM64_CPUCAP_SCOPE_MASK;
        boot_scope = !!(scope_mask & SCOPE_BOOT_CPU);

        for (i = 0; i < ARM64_NCAPS; i++) {
                unsigned int num;

                caps = cpu_hwcaps_ptrs[i];
                if (!caps || !(caps->type & scope_mask))
                        continue;
                num = caps->capability;
                if (!cpus_have_cap(num))
                        continue;

                /* Ensure cpus_have_const_cap(num) works */
                static_branch_enable(&cpu_hwcap_keys[num]);

                if (boot_scope && caps->cpu_enable)
                        /*
                         * Capabilities with SCOPE_BOOT_CPU scope are finalised
                         * before any secondary CPU boots. Thus, each secondary
                         * will enable the capability as appropriate via
                         * check_local_cpu_capabilities(). The only exception is
                         * the boot CPU, for which the capability must be
                         * enabled here. This approach avoids costly
                         * stop_machine() calls for this case.
                         */
                        caps->cpu_enable(caps);
        }

        /*
         * For all non-boot scope capabilities, use stop_machine()
         * as it schedules the work allowing us to modify PSTATE,
         * instead of on_each_cpu() which uses an IPI, giving us a
         * PSTATE that disappears when we return.
         */
        if (!boot_scope)
                stop_machine(cpu_enable_non_boot_scope_capabilities,
                             NULL, cpu_online_mask);
}

@scope_mask에 포함된 caps들의  기능을 enable 한다.

  • 코드 라인 10~18에서 cpu caps를 순회하며 @scope_mask에 포함되지 않거나 cpu가 지원하지 않는 cap인 경우 skip 한다.
  • 코드 라인 21에서 cpu가 지원하는 cap에 대한 static branch를 enable 한다.
  • 코드 라인 23~33에서 인자로 요청한 @scope_mask가 boot_scope에 포함된 경우 해당 cap을 enable하기 위해 등록된 (*cpu_enable) 후크 함수를 호출한다.
  • 코드 라인 42~44에서 인자로 요청한 @scope_mask가 boot_scope에 포함되지 않은 경우 online cpu들을 잠깐 stop 시킨 후 해당 cap을 enable하기 위해 등록된 (*cpu_enable) 후크 함수를 호출한다.

 

cpu_enable_non_boot_scope_capabilities()

arch/arm64/kernel/cpufeature.c

/*
 * Enable all the available capabilities on this CPU. The capabilities
 * with BOOT_CPU scope are handled separately and hence skipped here.
 */
static int cpu_enable_non_boot_scope_capabilities(void *__unused)
{
        int i;
        u16 non_boot_scope = SCOPE_ALL & ~SCOPE_BOOT_CPU;

        for_each_available_cap(i) {
                const struct arm64_cpu_capabilities *cap = cpu_hwcaps_ptrs[i];

                if (WARN_ON(!cap))
                        continue;

                if (!(cap->type & non_boot_scope))
                        continue;

                if (cap->cpu_enable)
                        cap->cpu_enable(cap);
        }
        return 0;
}

non-boot scope에 해당하는 caps들의  기능을 enable 한다.

  • 코드 라인 6~10에서 모든 유효한 caps를 순회한다.
  • 코드 라인 12~13에서 non-boot scope에 해당하지 않는 caps들을 skip 한다.
  • 코드 라인 15~16에서 해당 cap을 enable하기 위해 등록된 (*cpu_enable) 후크 함수를 호출한다.

 


CPU Feature 관리

CPU Feature 레지스터 트래킹용 자료 구조

arm64_ftr_reg 구조체

arch/arm64/include/asm/cpufeature.h

/*
 * @arm64_ftr_reg - Feature register
 * @strict_mask         Bits which should match across all CPUs for sanity.
 * @sys_val             Safe value across the CPUs (system view)
 */
struct arm64_ftr_reg {
        const char                      *name;
        u64                             strict_mask;
        u64                             user_mask;
        u64                             sys_val;
        u64                             user_val;
        const struct arm64_ftr_bits     *ftr_bits;
};
  • *name
    • 레지스터 명
  • strict_mask
    • 모든 cpu의 레지스터 feature 값을 strict_mask 범위에 포함되는지 체크하기 위해 사용된다.
    • 레지스터 값을 갱신 시 strict_mask 범위를 벗어나는 경우 경고 메시지가 출력된다.
    • strict 허용되지 않은 비트를 제외하고 모두 1로 설정된다.
  • user_mask
    • 유저 레벨에서 접근 가능한 필드들에 대한 비트들만 1로 설정한 마스크 비트들이다.
  • sys_val
    • 시스템 접근 시 사용할 레지스터 값이 담긴다.
  • user_val
    • 유저 접근이 불가능한  필드 요청에 대해 반환할 레지스터 값을 관리한다.
  • *ftr_bits
    • 필드에 대한 속성들을 갖는 arm64_ftr_bits 구조체 배열을 가리킨다.

 

arm64_ftr_bits 구조체

arch/arm64/include/asm/cpufeature.h

struct arm64_ftr_bits {
        bool            sign;   /* Value is signed ? */
        bool            visible;
        bool            strict; /* CPU Sanity check: strict matching required ? */
        enum ftr_type   type;
        u8              shift;
        u8              width;
        s64             safe_val; /* safe value for FTR_EXACT features */
};
  •  sign
    • 음수 표현 여부
    • 1=음수 포함, 0=양수만 포함
    • 음수를 표현하는 경우 비트 필드의 msb가 음수 비트가 된다.
  • visible
    • 유저에서 이 비트 필드에 대한 접근 가능 여부를 나타낸다.
    • 1=유저 접근 허용, 0=유저 접근 비허용
  • strict
    • 갱신할 cpu의 feature 레지스터 값이 strict_mask 범위에 포함되어 엄격히 체크해야 하는지 여부를 나타낸다.
    • 1=strict_mask 범위를 사용하고, 범위를 벗어나는 갱신 시 경고 메시지도 출력하고 한다, 0=strict_mask 범위로 체크하지 않는다.
  • type
    • 비트 필드들은 다음 3 가지 타입으로 식별된다.
      •  FTR_EXACT(0)
        • 새로운 값으로 갱신하지 않고, safe 값으로만 갱신되게 한다.
      • FTR_LOWER_SAFE(1)
        • 항상 작은 값으로만 갱신되게 한다.
        • 모든 cpu들 값에서 가장 작은 값을 사용하게 할 때 사용한다.
      • FTR_HIGHER_SAFE(2)
        • 항상 큰 값으로만 갱신되게 한다.
        • 모든 cpu들 값에서 가장 큰 값을 사용하게 할 때 사용한다.
  • shift
    • 레지스터 값에서 이 비트 필드에 도달할 때 시프트 되어야 할 비트 수
  • width
    • 레지스터내에서 사용하는 비트 수
  • safe_val
    • FTR_EXACT 타입을 사용하는 경우 항상 이 값을 사용하여 갱신한다.

 

arm64_ftr_regs[] 배열

컴파일 타임에 arm64용 feature 레지스터들이 배열로 구성된다.

static const struct __ftr_reg_entry {
        u32                     sys_id;
        struct arm64_ftr_reg    *reg;
} arm64_ftr_regs[] = {

        /* Op1 = 0, CRn = 0, CRm = 1 */
        ARM64_FTR_REG(SYS_ID_PFR0_EL1, ftr_id_pfr0),
        ARM64_FTR_REG(SYS_ID_PFR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_DFR0_EL1, ftr_id_dfr0),
        ARM64_FTR_REG(SYS_ID_MMFR0_EL1, ftr_id_mmfr0),
        ARM64_FTR_REG(SYS_ID_MMFR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_MMFR2_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_MMFR3_EL1, ftr_generic_32bits),

        /* Op1 = 0, CRn = 0, CRm = 2 */
        ARM64_FTR_REG(SYS_ID_ISAR0_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR2_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR3_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR4_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR5_EL1, ftr_id_isar5),
        ARM64_FTR_REG(SYS_ID_MMFR4_EL1, ftr_id_mmfr4),

        /* Op1 = 0, CRn = 0, CRm = 3 */
        ARM64_FTR_REG(SYS_MVFR0_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_MVFR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_MVFR2_EL1, ftr_mvfr2),

        /* Op1 = 0, CRn = 0, CRm = 4 */
        ARM64_FTR_REG(SYS_ID_AA64PFR0_EL1, ftr_id_aa64pfr0),
        ARM64_FTR_REG(SYS_ID_AA64PFR1_EL1, ftr_id_aa64pfr1),
        ARM64_FTR_REG(SYS_ID_AA64ZFR0_EL1, ftr_raz),

        /* Op1 = 0, CRn = 0, CRm = 5 */
        ARM64_FTR_REG(SYS_ID_AA64DFR0_EL1, ftr_id_aa64dfr0),
        ARM64_FTR_REG(SYS_ID_AA64DFR1_EL1, ftr_raz),

        /* Op1 = 0, CRn = 0, CRm = 6 */
        ARM64_FTR_REG(SYS_ID_AA64ISAR0_EL1, ftr_id_aa64isar0),
        ARM64_FTR_REG(SYS_ID_AA64ISAR1_EL1, ftr_id_aa64isar1),

        /* Op1 = 0, CRn = 0, CRm = 7 */
        ARM64_FTR_REG(SYS_ID_AA64MMFR0_EL1, ftr_id_aa64mmfr0),
        ARM64_FTR_REG(SYS_ID_AA64MMFR1_EL1, ftr_id_aa64mmfr1),
        ARM64_FTR_REG(SYS_ID_AA64MMFR2_EL1, ftr_id_aa64mmfr2),

        /* Op1 = 0, CRn = 1, CRm = 2 */
        ARM64_FTR_REG(SYS_ZCR_EL1, ftr_zcr),

        /* Op1 = 3, CRn = 0, CRm = 0 */
        { SYS_CTR_EL0, &arm64_ftr_reg_ctrel0 },
        ARM64_FTR_REG(SYS_DCZID_EL0, ftr_dczid),

        /* Op1 = 3, CRn = 14, CRm = 0 */
        ARM64_FTR_REG(SYS_CNTFRQ_EL0, ftr_single32),
};

 

ARM64 Feature 레지스터들 중 SYS_CNTFRQ_EL0 하나만을 알아본다.

#define SYS_CTR_EL0                     sys_reg(3, 3, 0, 0, 1)
  • CTR_EL0 레지스터 id는 0x001b_0020

 

sys_reg() 매크로

arch/arm64/include/asm/sysreg.h

/*
 * ARMv8 ARM reserves the following encoding for system registers:
 * (Ref: ARMv8 ARM, Section: "System instruction class encoding overview",
 *  C5.2, version:ARM DDI 0487A.f)
 *      [20-19] : Op0
 *      [18-16] : Op1
 *      [15-12] : CRn
 *      [11-8]  : CRm
 *      [7-5]   : Op2
 */
#define Op0_shift       19
#define Op0_mask        0x3
#define Op1_shift       16
#define Op1_mask        0x7
#define CRn_shift       12
#define CRn_mask        0xf
#define CRm_shift       8
#define CRm_mask        0xf
#define Op2_shift       5
#define Op2_mask        0x7

#define sys_reg(op0, op1, crn, crm, op2) \
        (((op0) << Op0_shift) | ((op1) << Op1_shift) | \
         ((crn) << CRn_shift) | ((crm) << CRm_shift) | \
         ((op2) << Op2_shift))

 

다음 그림은 sys_reg() 매크로 함수의 5개 인자로 구성하는 레지스터 id를 알아본다.

 

arch/arm64/kernel/cpufeature.c

struct arm64_ftr_reg arm64_ftr_reg_ctrel0 = {
        .name           = "SYS_CTR_EL0",
        .ftr_bits       = ftr_ctr
};

컴파일 타임에 Cache Type 레지스터 명과 각 필드에 대한 정보만 담겨있음을 알 수 있다. 나머지 멤버 값들은 부트업 과정에서 설정된다.

 

static const struct arm64_ftr_bits ftr_ctr[] = {
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_EXACT, 31, 1, 1), /* RES1 */
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_DIC_SHIFT, 1, 1),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_IDC_SHIFT, 1, 1),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_HIGHER_SAFE, CTR_CWG_SHIFT, 4, 0),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_HIGHER_SAFE, CTR_ERG_SHIFT, 4, 0),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_DMINLINE_SHIFT, 4, 1),
        /*
         * Linux can handle differing I-cache policies. Userspace JITs will
         * make use of *minLine.
         * If we have differing I-cache policies, report it as the weakest - VIPT.
         */
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_NONSTRICT, FTR_EXACT, 14, 2, ICACHE_POLICY_VIPT),       /* LL
1Ip */
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_IMINLINE_SHIFT, 4, 0),
        ARM64_FTR_END,
};

Cache Type 레지스터의 각 필드들에 대한 정보들을 구성한다.

  • 참고: ARM64 시스템 주요 레지스터 | 문c
  • RES1 필드 – bits[31]
    • 모든 cpu에 대해 필드 값은 항상 safe 값인 1이 사용된다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • DIC 필드 – bits[29]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • IDC 필드 – bits[28]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • CWG 필드 – bits[27:24]
    • 모든 cpu에 대해 필드 값은 항상 큰 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • ERG 필드 – bits[23:20]
    • 모든 cpu에 대해 필드 값은 항상 큰 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • DminLine 필드 – bits[19:16]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • L1Ip 필드 – bits[15:14]
    • 모든 cpu에 대해 필드 값은 항상 safe 값인 ICACHE_POLICY_VIPT(2)만 사용된다.
    • 유저 액세스를 허용하고 strict 체크하지 않는다.
  • IminLine 필드 – bits[3:0]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.

 

CPU Feature 레지스터 트래킹용 API

get_arm64_ftr_reg()

arch/arm64/kernel/cpufeature.c

/*
 * get_arm64_ftr_reg - Lookup a feature register entry using its
 * sys_reg() encoding. With the array arm64_ftr_regs sorted in the
 * ascending order of sys_id , we use binary search to find a matching
 * entry.
 *
 * returns - Upon success,  matching ftr_reg entry for id.
 *         - NULL on failure. It is upto the caller to decide
 *           the impact of a failure.
 */
static struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id)
{
        const struct __ftr_reg_entry *ret;

        ret = bsearch((const void *)(unsigned long)sys_id,
                        arm64_ftr_regs,
                        ARRAY_SIZE(arm64_ftr_regs),
                        sizeof(arm64_ftr_regs[0]),
                        search_cmp_ftr_reg);
        if (ret)
                return ret->reg;
        return NULL;
}

등록된 arm64용 feature 레지스터에서 @sys_id에 해당하는 레지스터를 검색한 후 반환한다.

 

search_cmp_ftr_reg()

arch/arm64/kernel/cpufeature.c

static int search_cmp_ftr_reg(const void *id, const void *regp)
{
        return (int)(unsigned long)id - (int)((const struct __ftr_reg_entry *)regp)->sys_id;
}

@id와 레지스터 @regp의 sys_id를 비교한다. 결과는 다음과 같다.

  • 음수: @id < @regp->sys_id
  • 0: @id == @regp->sys_id
  • 양수: @id > @regp->sys_id

 

CPU feature 레지스터의 필드 접근

arm64_ftr_value()

arch/arm64/include/asm/cpufeature.h

static inline s64 arm64_ftr_value(const struct arm64_ftr_bits *ftrp, u64 val)
{
        return (s64)cpuid_feature_extract_field_width(val, ftrp->shift, ftrp->width, ftrp->sign);
}

64비트 feature 레지스터 값인 @val에서 arm64 feature 비트 @ftrp에 해당하는 비트 필드값으로 반환한다.

 

cpuid_feature_extract_field_width()

arch/arm64/include/asm/cpufeature.h

static inline int __attribute_const__
cpuid_feature_extract_field_width(u64 features, int field, int width, bool sign)
{
        return (sign) ?
                cpuid_feature_extract_signed_field_width(features, field, width) :
                cpuid_feature_extract_unsigned_field_width(features, field, width);
}

64비트 feature 레지스터 값 @features의 bits[@field+@width-1:@field] 값을 부호 @sign 여부에 따른 정수 필드값으로 반환한다.

 

cpuid_feature_extract_signed_field_width()

arch/arm64/include/asm/cpufeature.h

static inline int __attribute_const__
cpuid_feature_extract_signed_field_width(u64 features, int field, int width)
{
        return (s64)(features << (64 - width - field)) >> (64 - width);
}

64비트 feature 레지스터 값 @features의 bits[@field+@width-1:@field] 값을 부호있는 정수 필드값으로 반환한다.

  • 예) features=0x1234_5678_9abc_def0, field=48, width=4
    • bits[51:48]=0x4 -> 결과값=4
  • 예) features=0x1234_5678_9abc_def0, field=12, width=4
    • bits[15:12]=0xd -> 결과값=0xffff_ffff_ffff_fffd=-3

 

cpuid_feature_extract_unsigned_field_width()

arch/arm64/include/asm/cpufeature.h

static inline unsigned int __attribute_const__
cpuid_feature_extract_unsigned_field_width(u64 features, int field, int width)
{
        return (u64)(features << (64 - width - field)) >> (64 - width);
}

64비트 feature 레지스터 값 @features의 bits[@field+@width-1:@field] 값을 부호없는 정수 필드값으로 반환한다.

  • 예) features=0x1234_5678_9abc_def0, field=12, width=4
    • bits[15:12]=0xd -> 결과값=0xd

 

cpuid_feature_extract_unsigned_field()

arch/arm64/include/asm/cpufeature.h

static inline unsigned int __attribute_const__
cpuid_feature_extract_unsigned_field(u64 features, int field)
{
        return cpuid_feature_extract_unsigned_field_width(features, field, 4);
}

64비트 feature 레지스터 값 @features의 bits[@field+3:@field] 값을 부호없는 정수 필드값으로 반환한다.

 

arm64_ftr_set_value()

arch/arm64/kernel/cpufeature.c

static u64 arm64_ftr_set_value(const struct arm64_ftr_bits *ftrp, s64 reg,
                               s64 ftr_val)
{
        u64 mask = arm64_ftr_mask(ftrp);

        reg &= ~mask;
        reg |= (ftr_val << ftrp->shift) & mask;
        return reg;
}

64비트 feature 레지스터 값 @reg에 ftr_val을 대입할 때 필드 @ftrp에 해당하는 비트들에만 들어가게 제한한다.

 

arm64_ftr_mask()

arch/arm64/include/asm/cpufeature.h

static inline u64 arm64_ftr_mask(const struct arm64_ftr_bits *ftrp)
{
        return (u64)GENMASK(ftrp->shift + ftrp->width - 1, ftrp->shift);
}

Feature 레지스터의 요청한 필드 @ftrp에서 사용할 마스크 값을 반환한다.

  • 예) shift=21, width=19
    • 0x0000_00ff_ffe0_0000

 

참고

 

ARM64 시스템 주요 레지스터

<ARMv8.x>

ARM64 시스템 주요 레지스터

캐시

CTR_EL0(Cache Type Register – EL0)

캐시 타입을 알아오는 레지스터이다.

  • DIC
    • Instruction cache invalidation requirements for instruction to data coherence
    • 0=PoU를 위해 명령 캐시의 invalidation이 필요하다.
    • 1=PoU를 위해 명령 캐시의 invalidation이 필요하지 않다.
  • IDC
    • Data cache clean requirements for instruction to data coherence
    • 0=PoU를 위해 데이터 캐시의 clean이 필요하다. 단 다음 조건 제외
      • CLIDR_EL1.LoC == 0 or (CLIDR_EL1.LoUIS == 0 && CLIDR_EL1.LoUU == 0)
    • 1=PoU를 위해 데이터 캐시의 clean이 필요하지 않다.
  • CWG(Cache Writeback Granule)
    • 0~1=CWG 정보를 제공하지 않는다.
    • 2~9=Cache Writeback Granule로 2^n 워드를 초과하지 않는다.
    • 10 이상=reserved
  • ERG(Exclusives Reservation Granule)
    • 0=ERG 정보를 제공하지 않는다.
    • 1~9=Exclusives Reservation Granule로 2^n 워드를 초과하지 않는다.
    • 10 이상=reserved
  •  DminLine
    • 데이터 캐시 라인 사이즈로 2^n 워드
  • L1Ip
    • 레벨 1 명령 캐시 정책
      • 0=VPIPT (ARMv8.2 이상에서 가능)
      • 1=AIVIVT
      • 2=VIPT
      • 3=PIPT
  • IminLine
    • 명령 캐시 라인 사이즈로 2^n 워드

 

예) Cortex-A72, CTR_EL0=0x8444_c004

  • DIC=0
  • IDC=0
  • CWG=4(16 words)
  • ERG=4(16 words)
  • DminLine=4(16 words)
  • L1Ip=3(PIPT)
  • IminLine=4(16 words)

 

CLIDR_EL1(Cache Level ID Register – EL1)

구성된 레벨별 캐시 타입(명령 only, 데이터 only, 명령+데이터 분리, 명령+데이터 통합)을 구분하고 최상위 통합 캐시 레벨과 최상위 캐시 일관성 레벨을 알려주는 레지스터이다.

  • ICB(Inner Cache Boundary)
    • Inner 캐시 영역
    • 예) ICB=2
      • L2 캐시까지 Inner 캐시 영역이다.
  • LoUU(Level of Unification Uniprocessor)
    • Uni 프로세서 시스템에서 최상위 통합 캐시 레벨
    • Uni 프로세서 시스템에서 PoU 개념과 동일하다.
  • LoC(Level of Coherece)
    • 최상위 캐시 일관성 레벨
    • PoC 개념과 동일하다.
  • LoUIS(Level of Inner Shareable)
    • Inner 공유 영역내의 코어에서 최상위 통합 캐시 레벨
    • SMP 시스템에서 PoU 개념과 동일하다.
  • CType1 ~ CType7
    • 캐시 레벨 별 캐시 타입
      • 0=no cache
      • 1=명령 캐시만 존재
      • 2=데이터 캐시만 존재
      • 3=명령 캐시와 데이터 캐시 분리
      • 4=명령 캐시와 데이터 통합 캐시

 

예) Cortex-A72, CIDR_EL1=0xa20_0023

  • ICB=0
  • LoUU=2(L2 레벨)
  • LoC=2(L2 레벨)
  • LoUIS=2(L2 레벨)
  • CT7 ~ CT3=0(L7 ~ L3 캐시 없음)
  • CT2=4(L2 캐시가 통합 캐시)
  • CT1=3(L1 캐시가 분리 캐시)

 

프로세스

MIDR_EL2 (Main ID Register – EL2)

메인 ID를 담은 레지스터이다.

  • Implementer
    • 설계자 코드(ARM, Broadcom, Cavium, DEC, …)
  • Variant
    • variant 번호
  • Architecture
    • ARM 아키텍처 코드
  • PartNum
    • 파트 번호
  • Revision
    • 리비전 번호

 

MPIDR_EL2 (Multiprocessor Affinity Register – EL2)

Multiprocessor affinity 레벨을 담은 레지스터이다.

  • AFF3
    • Affinity level 3
  •  U
    • Uniprocess 시스템 여부를 나타낸다.
    • 0=SMP
    • 1=UP
  • MT
    • 로지컬 PE의 최소 affinity 레벨 .
    • 0=affinity 0이 최대 독립적
    • 1=affinity 0이 매우 밀착 (Multi-Thread)
  • AFF2
    • Affinity level 2
  • AFF1
    • Affinity level 1
  • AFF0
    • Affinity level 0

 


CPU Feature 식별

ID_AA64PFR0_EL1(AArch64 Processor Feature Register 0 – EL1)

AArch64 상태에 구현된 프로세서 feature를 알아오는 레지스터 0번이다.

  • DIT(Data Independent Timing)
    • 0=어떠한 명령도 constant 실행 시간을 보장하지 않는다.
    • 1=명령의 constant 실행 시간을 보장한다.
  • AMU(Activity Monitors Extension)
    • 0=Activity Monitor 미구현
    • 1=Activity Monitors Extension 버전 1 구현
  • MPAM
    • 0=MPAM Extension 미구현
    • 1=MPAM Extension 구현
  • SEL(Secure EL2)
    • 0=Secure EL2 미구현
    • 1=Secure EL2 미구현
  • SVE(Scalable Vector Extension)
    • 0=SVE 미구현
    • 1=SVE 구현
  • RAS
    • 0=RAS Extension 미구현
    • 1=RAS Extension 구현
    • 2=ARMv8.4-RAS 구현
  • GIC
    • 0=GIC를 위한 System 레지스터 인터페이스  미구현
    • 1=GIC를 위한 System 레지스터 인터페이스  구현
  • ASIMD(Advanced SIMD)
    • 0=Advanced SIMD 구현
    • 1=Advanced SIMD 구현 + half-precision 실수 연산 추가
    • 0xf=Advanced SIMD 미구현
  • FP(Floating Point)
    • 0=FP 구현
    • 1=FP 구현 + half-precision 실수 연산 추가
    • 0xf=FP 미구현
  • EL3(Exception Level 3)
    • 0=EL3 미구현
    • 1=AArch64 에서만 EL3 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL3 구현
  • EL2(Exception Level 2)
    • 0=EL2 미구현
    • 1=AArch64 에서만 EL2 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL2 구현
  • EL2(Exception Level 2)
    • 1=AArch64 에서만 EL1 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL1 구현
  • EL0(Exception Level 0)
    • 1=AArch64 에서만 EL0 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL0 구현

 


메모리 모델 기능

ID_AA64MMFR0_EL1 (Memory Model Feature Register 0 – EL1)

AArch64 상태에서 지원되는 메모리 모델을 알아오는 레지스터 0 이다.

  • TGran4
    • 4K 페이지 변환 지원 여부
    • 0b0000=지원, 0b1111=미지원
  • TGran64
    • 64K 페이지 변환 지원 여부
    • 0b0000=지원, 0b1111=미지원
  • TGran16
    • 16K 페이지 변환 지원 여부
    • 0b0000=미지원, 0b0001=지원
  • BigEndEL0
    • EL0에서 Mixed 엔디안 지원
    • 0b0000=미지원
    • 0b0001=지원 (SCTLR_EL1.E0E 비트로 엔디안 설정)
  • SNSMem
    • 시큐어와 non-시큐어 메모리 분리 지원 여부
    • 0b0000=미지원
    • 0b0001=지원
  • BigEnd
    • Mixed 엔디안 지원
    • 0b0000=미지원
    • 0b0001=지원 (SCTLR_ELx.E0E 비트로 엔디안 설정)
  • PARange
    • 물리 주소 지원 범위
    • 0b0000=32 bits, 4GB
    • 0b0001=36 bits, 64GB
    • 0b0010=40 bits, 1TB
    • 0b0011=42 bits, 4TB
    • 0b0100=44 bits, 16TB
    • 0b0101=48 bits, 2n56TB
    • 0b0110=52 bits, 4PB (ARMv8.2-LPA 필요)
  • ASIDBits
    • ASID 지원 비트 수
    • 0b0000=8bits
    • 0b0010=16bits

 

ID_AA64MMFR1_EL1 (Memory Model Feature Register 1 – EL1)

AArch64 상태에서 지원되는 메모리 모델을 알아오는 레지스터 1 이다.

  • XNX
    • ARMv8.2 Execute Never Control 지원 여부
    • 0=not support
    • 1=support
  • SpecSEI
    • ARMv8.2 RAS 사용시 Speculative 읽기시 SError 발생 지원 여부
    • 0=not support, 1=support
  • PAN
    • ARMv8.1 PAN(Privileged Access Never) 지원 여부
    • 0=not support
    • 1=support (only for ARMv8.1)
    • 2=support 및 AT S1E1RP 및 AT S1E1WP, AT S1E1 명령 지원 (only for ARMv8.2)
  • LO
    • LORegions 지원 여부
    • 0=not support, 1=support
  • HPDS
    • ARMv8.1 변환 테이블에서 HPDS(Hierarchical Permission Disables Support) 기능 지원 여부
    • 0=not support
    • 1=support
    • 2=support 및 마지막 단계 테이블의 bits[62:59]의 하드웨어 할당 가능
  • VH
    • ARM8.1 Virtualization Host Entension 기능 지원 여부
    • 0=not support
    • 1=support (only for ARMv8.1)
  • VMIDBits
    • ARMv8.1 VMID에 사용하는 비트 수
    • 0=8 bits, 2=16 bits
  • HAFDBS
    • ARMv8.1 변환 테이블에서 HAFDBS(Hardware updates to Access Flag and Dirty Bit State) 기능 지원 여부
    • 0=not support
    • 1=access flag 지원
    • 2=access flag 및 dirty state 지원

 

ID_AA64MMFR2_EL1 (Memory Model Feature Register 1 – EL1)

AArch64 상태에서 지원되는 메모리 모델을 알아오는 레지스터 2 이다.

  • BBM
    • ARMv8.4 변환을 위한 블럭 사이즈 변경 시 BBM(Break Before Make) 기능 지원 레벨
    • 0=level 0 support
    • 1=level 1 support
    • 2=level 2 support
  • TTL
    • ARMv8.4 TTL 기능
    • 0=bits[47:44]를 0으로 변환
    • 1=bits[47:44] 변환 없이
  • FWB
    • ARMv8.4 HCR_EL2.FWB 기능 지원 여부
    • 0=not support, 1=support
  • IDS
    • ARMv8.4 ID Space에 접근 시 아래 레지스터 값에 따른 exception 발생
    • 0=ESR_ELx.EC==0
    • 1=ESR_ELx.EC==0x18
  • AT
    • ARMv8.4 비정렬 single-copy atomic 기능 지원 여부
    • 0=not support
    • 1=support (16 byts)
  • ST
    • ARMv8.4 small 변환 테이블 지원
    • 0=TCR_ELx.{T0SZ, T1SZ} 과 VTCR_EL2.T0SZ 필드의 최고 값은 39
    • 1=TCR_ELx.{T0SZ, T1SZ} 과 VTCR_EL2.T0SZ 필드의 최고 값은 48 (단 64K granules시 47)
  • NV
    • ARMv8.4 NV(Nested Virtualization) 지원 여부
    • 0=not support
    • 1=HCR_EL2.{NV, NV1, AT} 지원 (for ARMv8.3)
    • 2=HCR_EL2.{NV, NV1, NV2, AT} 및 VNCR_EL2 지원
  • CCIDX
    • ARMv8.3 CCSIDR_EL1 레지스터 포맷
    • 0=32 비트, 1=64 비트
  • VARange
    • ARMv8.2 큰 가상 주소 지원 여부
    • 0=48 비트, 1=64K granule 시 52 비트, 그 외 48 비트 (for ARMv8.2-LVA)
  • IESB
    • ARMv8.2 SCTLR_ELx.IESB 비트 지원 여부
    • 0=not support, 1=support
  • LSM
    • ARMv8.2 SCTLR_EL{1, 2} 레지스터에서 LSMAOE 및 nTLSMD 필드 지원 여부
    • 0=not support, 1=support
  • UAO
    • ARMv8.2 UAO(User Access Override) 기능 지원 여부
    • 0=not support, 1=support
  • CnP
    • ARMv8.2 CnP(Common Not Private) 변환 기능 지원 여부
    • 0=not support, 1=support

 

MMFR1_EL1 (Memory Model Feature Register 1 – EL1)

AArch32 상태에서 지원되는 메모리 모델을 알아오는 레지스터 1 이다.

  • 참고: | ARM

  • BPred
    • 0=no branch predict
    • 1=다음 상황에서 branch predict 플러시를 요청한다.
      • 주소 변환 스테이지의 활성화/비활성화시
      • 명령어 변경시
      • 새로운 변환 테이블 매핑시
      • TTBR0/1 및 TTBCR 레지스터 변경시
      • ContextID 및 ASID 변경시
    • 2=다음 상황에서 branch predict 플러시를 요청한다. (for ARMv8-A)
      • 주소 변환 스테이지의 활성화/비활성화시
      • 명령어 변경시
      • 새로운 변환 테이블 매핑시
      • 해당 ContextID 및 ASID 변경 없이 TTBR0/1 및 TTBCR 레지스터 변경시
    • 3=명령어 변경시에만 branch predict 플러시를 요청한다. (for ARMv8-A)
    • 4=어떠한 경우에도 branch predict 플러시를 요청하지 않는다. (for ARMv8-A)
  • L1TstCin
    • L1 데이터 캐시 테스트 & 클린
    • 0=none support (only for ARMv8-A)
    • 1=test & clean 지원
    • 2=test & clean, test, clean, invalidate 지원
  • L1Uni
    • L1 통합 캐시 지원
    • 0=none supported (only for ARMv8-A)
    • 1=캐시의 invalidate 지원
    • 2=캐시의 invalidate, clean 및 clean & invalidate 지원
  • L1Hvd
    • L1 하버드 캐시 지원
    • 0=none support (only for ARMv8-A)
    • 1=명령 캐시만 invalidate 지원
    • 2=명령 및 데이터 캐시의 invalidate 지원
    • 3=명령 및 데이터 캐시의 invalidate 및 clean & invalidate 지원
  • L1UniSW
    • L1 통합 캐시의 Set/Way 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean 캐시 지원
    • 2=clean 및 clean & invalidate 지원
    • 3=clean, clean & invalidate 및 invalidate 지원
  • L1HvdSW
    • L1 하버드 캐시의 Set/Way 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean 및 clean & invalidate 데이터 캐시 지원
    • 2=clean, clean & invalidate 및 invalidate 데이터 캐시 지원
    • 3=clean, clean & invalidate 및 invalidate 데이터 캐시 및 명령 캐시 invalidate 지원
  • L1UniVA
    • L1 통합 캐시의 가상 주소(VA)를 사용하여 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean, invalidate 및 clean & invalidate 캐시 지원
    • 2=clean, invalidate 및 clean & invalidate 캐시, 그리고 branch predictor invalidate 지원
  • L1HvdVA
    • L1 하버드 캐시의 가상 주소(VA)를 사용하여 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean, invalidate 및 clean & invalidate 데이터 캐시, invalidate 명령 캐시 지원
    • 2=clean, invalidate 및 clean & invalidate 데이터 캐시, invalidate 명령 캐시, 그리고 branch predictor invalidate 지원

 


시스템 제어

SCTLR_EL1 (System Control Register – EL1)

EL1 모드를 설정하기 위한 시스템 콘트롤 레지스터이다.

  • EnIA
    • ARMv8.3 APIAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EnIB
    • ARMv8.3 APIBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • LSMAOE
    • ARMv8.2-LSMAOC(Load/Store Multiple Atomicity and Ordering Enable) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • nTLSMD
    • ARMv8.2-LSMAOC 기능의 디바이스 메모리 No Trap 활성화 여부
    • 0=disable, fault 발생
    • 1=enable
  • EnDA
    • ARMv8.3 APDAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • UCI
    • EL0에서 EL1 캐시 조작 명령어 수행시 트랩 여부
    • 0=trap
    • 1=no trap
  • EE
    • EL1 엔디안 설정
    • 0=리틀 엔디안
    • 1=빅 엔디안
  • E0E
    • EL0 엔디안 설정
    • 0=리틀 엔디안
    • 1=빅 엔디안
  • SPAN
    • ARMv8.1 PAN(Privileged Access Never) 기능 동작시 PSTATE.PAN 동작 여부
    • 0=익셉션 발생시 PSTATE.PAN=1 설정
    • 1=익셉션 발생시 PSTATE.PAN 변경 없다.
  • IESB
    • ARMv8.2-IESB 기능의 Implicit Error Synchronization 이벤트 활성화 여부
    • 0=disable
    • 1=enable
  • WXN
    • 쓰기 권한이 XN(Excute Never) 권한을 내포하는지 여부
    • 0=disable
    • 1=enable
  • SA
    • SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16바이트)
  • nTWE
    • EL0에서 WFE 명령 사용시 EL2로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • nTWI
    • EL0에서 WFI 명령 사용시 EL2로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • UCT
    • EL0에서 CTR_EL0 접근시 EL1으로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • DZE
    • EL0에서 DC ZVA 명령 사용시 EL1으로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • EnDB
    • ARMv8.3 APDBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • I
    • 명령 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • UMA
    • EL0에서 MSR/MRS 명령으로 PSTATE.DAIF 접근시 EL1으로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • SED
    • AArch32 EL0에서 SETEND 명령 disable 여부
    • 0=enable
    • 1=disable, undefined instruction으로 취급
  • ITD
    • AArch32 EL0에서 IT 명령 disable 여부
    • 0=enable
    • 1=disable, undefined instruction으로 취급
  • nAA
    • ARMv8.4-LSE 기능의 Non-Aligned Access 활성화 여부
    • 0=disable, fault 발생
    • 1=enable, fault 발생하지 않는다.
  • CP15BEN
    • EL0에서 CP15 메모리 베리어 활성화 여부
    • 0=disable, undefined instruction으로 취급
    • 1=enable
  • SA0
    • EL0에서 SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16 바이트 미정렬시 exception 발생)
  • SA
    • EL1에서 SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16 바이트 미정렬시 exception 발생)
  • C
    • 데이터 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • A
    • 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable, fault 발생
  • M
    • MMU 활성화 여부
    • 0=disable
    • 1=enable

 

LORC_EL1, LORegion Register (EL1)

LORegion을 활성화하는 레지스터이다.

  • DS
    • ARMv8.1 DS(Descriptor Select)
    • 0~255
  • EN
    • LORegions 기능 활성화 여부
    • 0=disable, 1=enable

 


GIC

ICC_SRE_EL2 (Interrupt Controller System Register Enable Register – EL2)

인터럽트 컨트롤러 시스템 레지스터를 enable하는 레지스터이다.

  • DIB
    • Disable IRQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • DFB
    • Disable FIQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • SRE
    • GIC에 대한 시스템 레지스터(ICC_*) 활성화 여부
    • 0=disable, 메모리 맵드 방식으로 사용해야 하고, ICC_로 시작하는 시스템 레지스터들의 접근시 el1에 트랩된다.
    • 1=enable

 


하이퍼 바이저 관련 레지스터

시스템 설정

SCTLR_EL2 (System Control Register – EL2)

하이퍼 바이저 모드를 설정하기 위한 시스템 콘트롤 레지스터이다.

  • EnIA
    • ARMv8.3 APIAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EnIB
    • ARMv8.3 APIBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EnDA
    • ARMv8.3 APDAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EE
    • 엔디안 설정
    • 0=리틀 엔디안
    • 1=빅 엔디안
  • IESB
    • ARMv8.2-IESB 기능의 Implicit Error Synchronization 이벤트 활성화 여부
    • 0=disable
    • 1=enable
  • WXN
    • 쓰기 권한이 XN(Excute Never) 권한을 내포하는지 여부
    • 0=disable
    • 1=enable
  • EnDB
    • ARMv8.3 APDBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • I
    • 명령 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • nAA
    • ARMv8.4-LSE 기능의 Non-Aligned Access 활성화 여부
    • 0=disable, fault 발생
    • 1=enable, fault 발생하지 않는다.
  • SA
    • SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16바이트)
  • C
    • 데이터 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • A
    • 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable, fault 발생
  • M
    • MMU 활성화 여부
    • 0=disable
    • 1=enable

 

HCR_EL2 (System Control Register – EL2)

하이퍼 바이저를 설정하기 위한 시스템 콘트롤 레지스터이다.

  • TEA
    • 동기 외부 abort exceptin을 EL2로 라우트
    • 0=disable, 1=enable (route to el2)
  • E2H
    • Host 운영체제가 EL2에서 동작하도록 설정
    • 0=disable, 1=enable
  • ID
    • Stage 2에서의 명령 캐시 disable for el1&0
    • 0=no effect
    • 1=effect
  • CD
    • Stage 2에서의 데이터 캐시 disable for el1&0
    • 0=no effect, 1=effect
  • TGE
    • el0 에서 el2로 TGE(Trap General  Exception)
    • 0=no effect, 1=effect
  • AMO
    • 물리 SError 라우팅
  • IMO
    • 물리 IRQ 라우팅
  • FMO
    • 물리 FIQ 라우팅

 

VPIDR_EL2 (Virtualization Processor ID Register – EL2)

Multiprocessor ID를 담은 레지스터이다.

  • MIDR_EL1 참고

 

VMPIDR_EL2 (Virtualization Multiorocessor ID Register – EL2)

Virtualization Multiprocessor ID를 담은 레지스터이다.

  • MPIDR_EL1 참고

 

HSTR_EL2 (Hypervisor System Trap Register – EL2)

EL0&1에서 coprocess(MCR, MRC, MCRR, and MRRC) 접근 명령 사용시 발생되는 el2로의 트랩 제어 레지스터이다.

  • T<n>
    • CP15, C<n>에 접근하는 코프로세서 명령에 대해 el2로 트랩 발생
    • 0=no trap, 1=trap
    • T14와 T4는 RES0

 

CPTR_EL2 (Architectural Feature Trap Register – EL2)

CPACR, CPACR_EL1, 트레이스 펑션, SVE, Advanced SIMD 및 실수 펑션 사용시 el2로 트랩을 제어하는 레지스터이다.

  • TFP
    • SVE, A-SIMD, 실수 펑션 사용시 el2로 트랩 발생
    • 0=no trap, 1=trap
  •  TZ
    • SVE 레지스터를 액세스할 때 el2로 트랩 발생
    • 0=no trap, 1=trap

 

VBAR_EL2, Vector Base Address Register (EL2)

exception 벡터 베이스 주소를 지정하는 레지스터이다.

 


Stage 2 변환 테이블

VTTBR_EL2 (Virtualization Translation Table Base Register – EL2)

stage 2의 페이지 테이블을 가리키는 레지스터이다.

  • VMID[15:8]
    • ARMv8.1-VMID16 지원시 상위 8비트를 기록하여 사용한다.
  • VMID[15:8]
    • 변환 테이블을 위한 VMID
  • BADDR
    • 변환 테이블 물리 주소
  • CnP
    • ARMv8.2-TTCNP 기능이 있는 경우 CnP(Common not Private) 기능 지원 여부
    • 0=변환 테이블의 엔트리는 현재 PE에만 적용한다.
    • 1=변환 테이블의 엔트리는 Inner Share의 다른 PE와 공유한다.

 


하이퍼 바이저 카운터

아키텍처 타이머 및 카운터 레지스터의 사용법은 간단하나 종류가 여러 가지 있다.

 

CNTHCTL_EL2 (Counter-timer Hypervisor Control Register – EL2)

EL0&1 physical 카운터와 타이머에 접근하기 위한 콘트롤 레지스터이다.

  • EL1PTEN
    • HCR_EL2.TGE==0인 경우, EL0&1에서 EL1 물리 타이머 액세스시 EL2로 트랩 발생
    • 0=enable, 1=disable
  • EL1PCTEN
    • HCR_EL2.TGE==0인 경우, EL0&1에서 EL1 물리 카운터 액세스시 EL2로 트랩발생
    • 0=enable, 1=disable
  • EL0PTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 물리 타이머 액세스시 EL2로 트랩 발생
    • 0=enable, 1=disable
  • EL0VTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 가상 타이머 액세스시 EL2로 트랩발생
    • 0=enable, 1=disable
  • EVNTI
    • CNTPCT_EL0 카운터의 어떠한 비트(0~15)에서 이벤트가 발생될지 선택한다.
  • ENNTDIR
    • CNTPCT_EL0 카운터의 해당(EVNTI) 비트의 트리거 방향을 제어한다.
    • 0=high edge시 트리거
    • 1=low edge시 트리거
  • EVNTEN
    • CNTPCT_EL0 카운터로부터 이벤트 스트림 발생 여부 제어
    • 0=disable, 1=enable
  • EL0VCTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 가상 카운터 액세스시 EL2로 트랩발생
    • 0=enable, 1=disable
  • EL0PCTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 물리 카운터 액세스시 EL2로 트랩 발생
    • 0=enable, 1=disable

 

CNTVOFF_EL2 (Counter-timer Hypervisor Control Register – EL2)

물리 카운터(CNTPCT_EL0)에 대한 Virtual Offset을 담고 있는 콘트롤 레지스터이다.

 


하이퍼 바이저 GIC

ICC_SRE_EL2 (Interrupt Controller System Register Enable Register – EL2)

인터럽트 컨트롤러 시스템 레지스터를 enable하는 레지스터이다.

  • Enable
    • 더 낮은 EL에서 ICC_SRE_EL1 접근을 가능하게 할지 여부
    • 0=disable, non-secure EL1에서 ICC_SRE_EL1 접근시 EL2로 트랩 발생
  •  DIB
    • Disable IRQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • DFB
    • Disable FIQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • SRE
    • GIC에 대한 시스템 레지스터(ICC_*) 활성화 여부
    • 0=disable, 메모리 맵드 방식으로 사용해야 하고, ICC_ 및 ICH_로 시작하는 시스템 레지스터들의 접근시 el2에 트랩된다.
    • 1=enable

 

NUMA -1- (ARM64 초기화)

<kernel v5.10>

NUMA -1- (ARM64 초기화)

arm64_numa_init()

arch/arm64/mm/numa.c

/**
 * arm64_numa_init - Initialize NUMA
 *
 * Try each configured NUMA initialization method until one succeeds.  The
 * last fallback is dummy single node config encomapssing whole memory.
 */
void __init arm64_numa_init(void)
{
        if (!numa_off) {
                if (!acpi_disabled && !numa_init(arm64_acpi_numa_init))
                        return;
                if (acpi_disabled && !numa_init(of_numa_init))
                        return;
        }

        numa_init(dummy_numa_init);
}

ARM64 시스템에서 ACPI 테이블 또는 디바이스 트리를 통해 NUMA 시스템 구성을 시도한다. 그러한 구성이 없으면 노드가 한 개인 dummy 누마 초기화를 수행한다.

 

numa_init()

arch/arm64/mm/numa.c

static int __init numa_init(int (*init_func)(void))
{
        int ret;

        nodes_clear(numa_nodes_parsed);
        nodes_clear(node_possible_map);
        nodes_clear(node_online_map);

        ret = numa_alloc_distance();
        if (ret < 0)
                return ret;

        ret = init_func();
        if (ret < 0)
                goto out_free_distance;

        if (nodes_empty(numa_nodes_parsed)) {
                pr_info("No NUMA configuration found\n");
                ret = -EINVAL;
                goto out_free_distance;
        }

        ret = numa_register_nodes();
        if (ret < 0)
                goto out_free_distance;

        setup_node_to_cpumask_map();

        return 0;
out_free_distance:
        numa_free_distance();
        return ret;
}

NUMA 시스템을 초기화한다.

  • 코드 라인 5~7에서 NUMA 상태를 관리하는 비트맵들을 모두 초기화한다.
  • 코드 라인 9~11에서 numa_disatance[] 배열을 필요한 만큼 할당하고 초기 값들을 지정한다.
  • 코드 라인 13~15에서 인자로 받은 함수 @init_func을 실행시켜 설정한다.
  • 코드 라인 17~21에서 노드 정보가 하나도 없는 경우 “No NUMA configuration found” 메시지를 출력하고 빠져나간다.
  • 코드 라인 23~25에서 노드 데이터(struct pglist_data)를 할당하고 기본 노드 정보를 설정한다.
  • 코드 라인 27에서 노드 -> cpu 맵을 설정한다.

 

아래와 같이 ACPI 또는 디바이스 트리를 통해 NUMA 설정을 찾을 수 없는 경우 “No NUMA configuration found” 메시지가 출력되는 것을 볼 수 있다. 그리고 fallback되어 dymmy NUMA 초기화 루틴을 통해 “Faking a node at …” 메시지가 출력되는 것도 볼 수 있다.

[    0.000000] NUMA: No NUMA configuration found
[    0.000000] NUMA: Faking a node at [mem 0x0000000040000000-0x000000007fffffff]
[    0.000000] NUMA: NODE_DATA [mem 0x7ddf4840-0x7ddf5fff]
[    0.000000] Zone ranges:
[    0.000000]   DMA32    [mem 0x0000000040000000-0x000000007fffffff]
[    0.000000]   Normal   empty
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000040000000-0x000000007fffffff]
[    0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x000000007fffffff]

 

numa_alloc_distance()

arch/arm64/mm/numa.c

static int __init numa_alloc_distance(void)
{
        size_t size;
        u64 phys;
        int i, j;

        size = nr_node_ids * nr_node_ids * sizeof(numa_distance[0]);
        phys = memblock_find_in_range(0, PFN_PHYS(max_pfn),
                                      size, PAGE_SIZE);
        if (WARN_ON(!phys))
                return -ENOMEM;

        memblock_reserve(phys, size);

        numa_distance = __va(phys);
        numa_distance_cnt = nr_node_ids;

        /* fill with the default distances */
        for (i = 0; i < numa_distance_cnt; i++)
                for (j = 0; j < numa_distance_cnt; j++)
                        numa_distance[i * numa_distance_cnt + j] = i == j ?
                                LOCAL_DISTANCE : REMOTE_DISTANCE;

        pr_debug("Initialized distance table, cnt=%d\n", numa_distance_cnt);

        return 0;
}

노드 수 * 노드 수만큼 numa_distance[] 배열을 만들어 할당한다.

  • 코드 라인 7에서 할당할 사이즈로 노드 수 * 노드 수를 사용한다.
  • 코드 라인 8~11에서 memblock의 빈 공간에서 size 만큼의 공간을 페이지 단위로 알아온다.
  • 코드 라인 13에서 size 영역을 memblock에 reserve 한다.
  • 코드 라인 15~16에서 할당한 물리 주소를 가상 주소로 변환하여 numa_distance에 대입하고, 노드 수를 지정한다.
  • 코드 라인 19~22에서 numa_distance[]는 논리적으로 이중 배열을 표현하였다. 디폴트 numa_distance[] 값으로 같은 노드를 의미하는 경우에만 LOCAL_DISTANCE(10)를 지정하고 그 외의 경우는 REMOTE_DISTANCE(20) 값을 지정해둔다.
    • 예) nr_node_ids=2인 경우 numa_distance[]를 알아본다.
      • numa_distance[0] = 10
      • numa_distance[1] = 20
      • numa_distance[2] = 20
      • numa_distance[3] = 10

 

numa_register_nodes()

arch/arm64/mm/numa.c

static int __init numa_register_nodes(void)
{
        int nid;
        struct memblock_region *mblk;

        /* Check that valid nid is set to memblks */
        for_each_mem_region(mblk) {
                int mblk_nid = memblock_get_region_node(mblk);

                if (mblk_nid == NUMA_NO_NODE || mblk_nid >= MAX_NUMNODES) {
                        pr_warn("Warning: invalid memblk node %d [mem %#010Lx-%#010Lx]\n",
                                mblk_nid, mblk->base,
                                mblk->base + mblk->size - 1);
                        return -EINVAL;
                }
        }

        /* Finally register nodes. */
        for_each_node_mask(nid, numa_nodes_parsed) {
                unsigned long start_pfn, end_pfn;

                get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
                setup_node_data(nid, start_pfn, end_pfn);
                node_set_online(nid);
        }

        /* Setup online nodes to actual nodes*/
        node_possible_map = numa_nodes_parsed;

        return 0;
}

노드 데이터(struct pglist_data)를 할당하고 기본 노드 정보를 설정한다.

  • 코드 라인 7~16에서 memblock에 노드가 지정되지 않은 경우 경고 메시지를 출력하고 에러 -EINVAL 값을 반환한다.
  • 코드 라인 19~25에서 노드를 순회하며 노드 데이터( struct pglist_data)를 할당하고 노드의 기본 정보를 설정하고, online 노드 상태로 변경한다.
  • 코드 라인 28에서 online 노드들을 possible 맵에 지정한다.

 

setup_node_to_cpumask_map()

arm64/mm/numa.c

/*
 * Allocate node_to_cpumask_map based on number of available nodes
 * Requires node_possible_map to be valid.
 *
 * Note: cpumask_of_node() is not valid until after this is done.
 * (Use CONFIG_DEBUG_PER_CPU_MAPS to check this.)
 */
static void __init setup_node_to_cpumask_map(void)
{
        int node;

        /* setup nr_node_ids if not done yet */
        if (nr_node_ids == MAX_NUMNODES)
                setup_nr_node_ids();

        /* allocate and clear the mapping */
        for (node = 0; node < nr_node_ids; node++) {
                alloc_bootmem_cpumask_var(&node_to_cpumask_map[node]);
                cpumask_clear(node_to_cpumask_map[node]);
        }

        /* cpumask_of_node() will now work */
        pr_debug("Node to cpumask map for %u nodes\n", nr_node_ids);
}

노드 -> cpu 맵을 설정한다.

  • 코드 라인 6~7에서 nr_node_ids가 아직 설정되지 않은 경우 possible 노드 수로 지정한다.
  • 코드 라인 10~13에서 노드 수만큼 순회하며 node_to_cpumask_map[node] 비트맵을 할당하고 초기화한다.

 

dummy_numa_init()

arch/arm64/mm/numa.c

/**
 * dummy_numa_init - Fallback dummy NUMA init
 *
 * Used if there's no underlying NUMA architecture, NUMA initialization
 * fails, or NUMA is disabled on the command line.
 *
 * Must online at least one node (node 0) and add memory blocks that cover all
 * allowed memory. It is unlikely that this function fails.
 */
static int __init dummy_numa_init(void)
{
        phys_addr_t start = memblock_start_of_DRAM();
        phys_addr_t end = memblock_end_of_DRAM();
        int ret;

        if (numa_off)
                pr_info("NUMA disabled\n"); /* Forced off on command line. */
        pr_info("Faking a node at [mem %#018Lx-%#018Lx]\n", start, end - 1);

        ret = numa_add_memblk(0, start, end);
        if (ret) {
                pr_err("NUMA init failed\n");
                return ret;
        }

        numa_off = true;
        return 0;
}

NUMA 설정이 없는 경우 한 개의 노드로 구성된 dummy NUMA 구성으로 초기화한다.

  • 코드 라인 7~8에서 “numa=off” 커널 파라미터가 지정된 경우 “NUMA disabled” 메시지를 출력한다.
  • 코드 라인 9에서 “Faking a node at …” 메시지 정보를 통해 한 개의 메모리 정보를 출력한다.
  • 코드 라인 11~15에서 memory memblock 모두에 0번 노드를 지정한다.
  • 코드 라인 17에서 NUMA가 disable 되었음을 나타낸다.

 

numa_add_memblk()

arch/arm64/mm/numa.c

/**
 * numa_add_memblk() - Set node id to memblk
 * @nid: NUMA node ID of the new memblk
 * @start: Start address of the new memblk
 * @end:  End address of the new memblk
 *
 * RETURNS:
 * 0 on success, -errno on failure.
 */
int __init numa_add_memblk(int nid, u64 start, u64 end)
{
        int ret;

        ret = memblock_set_node(start, (end - start), &memblock.memory, nid);
        if (ret < 0) {
                pr_err("memblock [0x%llx - 0x%llx] failed to add on node %d\n",
                        start, (end - 1), nid);
                return ret;
        }

        node_set(nid, numa_nodes_parsed);
        return ret;
}

memblock 영역에 노드 id를 지정하고  numa_nodes_parsed 비트맵에 현재 노드를 설정한다.

 

“numa” Early 커널 파라미터

numa_parse_early_param()

arch/arm64/mm/numa.c

static __init int numa_parse_early_param(char *opt)
{
        if (!opt)
                return -EINVAL;
        if (str_has_prefix(opt, "off"))
                numa_off = true;

        return 0;
}
early_param("numa", numa_parse_early_param);

“numa=off” 커널 파라미터가 지정된 경우 전역 변수 numa_off에 true를 대입한다.

 


디바이스 트리를 통한 NUMA 시스템 초기화

다음과 같이 4개의 NUMA 노드를 가진 시스템 구성을 참고한다.

  • 각  노드마다 4개의 클러스터로 구성하여 총 16개의 클러스터를 사용한다.
  • 각 클러스터마다 4개의 코어를 구성하여 총 64개의 코어를 사용한다.
  • 메모리 노드의 누마 id는 UEFI 펌웨어가 인식하여 전달되므로 아래는 노드 0번 정보만 기록되어 있다.
#include "hip07.dtsi"

/ {
        model = "Hisilicon Hip07 D05 Development Board";
        compatible = "hisilicon,hip07-d05";

        /* the mem node will be updated by UEFI. */
        memory@0 {
                device_type = "memory";
                reg = <0x0 0x00000000 0x0 0x40000000>;
                numa-node-id = <0>;
        };

        distance-map {
                compatible = "numa-distance-map-v1";
                distance-matrix = <0 0 10>,
                                  <0 1 15>,
                                  <0 2 20>,
                                  <0 3 25>,
                                  <1 0 15>,
                                  <1 1 10>,
                                  <1 2 25>,
                                  <1 3 30>,
                                  <2 0 20>,
                                  <2 1 25>,
                                  <2 2 10>,
                                  <2 3 15>,
                                  <3 0 25>,
                                  <3 1 30>,
                                  <3 2 15>,
                                  <3 3 10>;

 

arch/arm64/boot/dts/hisilicon/hip07.dtsi

                cpu-map {
                        cluster0 {
                                core0 {
                                        cpu = <&cpu0>;
                                };
                                core1 {
                                        cpu = <&cpu1>;
                                };
                                core2 {
                                        cpu = <&cpu2>;
                                };
                                core3 {
                                        cpu = <&cpu3>;
                                };
                        };

                        ...

                        cluster15 {
                                core0 {
                                        cpu = <&cpu60>;
                                };
                                core1 {
                                        cpu = <&cpu61>;
                                };
                                core2 {
                                        cpu = <&cpu62>;
                                };
                                core3 {
                                        cpu = <&cpu63>;
                                };
                        };
                };

                cpu0: cpu@10000 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72", "arm,armv8";
                        reg = <0x10000>;
                        enable-method = "psci";
                        next-level-cache = <&cluster0_l2>;
                        numa-node-id = <0>;
                };

                ...

                cpu63: cpu@70303 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72", "arm,armv8";
                        reg = <0x70303>;
                        enable-method = "psci";
                        next-level-cache = <&cluster15_l2>;
                        numa-node-id = <3>;
                };

                cluster0_l2: l2-cache0 {
                        compatible = "cache";
                };

                ...

                cluster15_l2: l2-cache15 {
                        compatible = "cache";
                };
        };

 

다음 그림은 NUMA 시스템의 distance를 보여준다.

 

of_numa_init()

drivers/of/of_numa.c

int __init of_numa_init(void)
{
        int r;

        of_numa_parse_cpu_nodes();
        r = of_numa_parse_memory_nodes();
        if (r)
                return r;
        return of_numa_parse_distance_map();
}

디바이스 트리를 통해 cpu 노드와 memory 노드에서 누마 id를 읽어 설정하고, distance 맵을 파싱해온다.

 

of_numa_parse_cpu_nodes()

drivers/of/of_numa.c

/*
 * Even though we connect cpus to numa domains later in SMP
 * init, we need to know the node ids now for all cpus.
*/
static void __init of_numa_parse_cpu_nodes(void)
{
        u32 nid;
        int r;
        struct device_node *np;

        for_each_of_cpu_node(np) {
                r = of_property_read_u32(np, "numa-node-id", &nid);
                if (r)
                        continue;

                pr_debug("CPU on %u\n", nid);
                if (nid >= MAX_NUMNODES)
                        pr_warn("Node id %u exceeds maximum value\n", nid);
                else
                        node_set(nid, numa_nodes_parsed);
        }
}

cpu 노드 수만큼 순회하며 노드를 파싱한다.

  • “numa-node-id” 속성이 1 이상인 경우 numa_nodes_parsed 비트맵에 노드를 설정한다.

 

of_numa_parse_memory_nodes()

drivers/of/of_numa.c

static int __init of_numa_parse_memory_nodes(void)
{
        struct device_node *np = NULL;
        struct resource rsrc;
        u32 nid;
        int i, r;

        for_each_node_by_type(np, "memory") {
                r = of_property_read_u32(np, "numa-node-id", &nid);
                if (r == -EINVAL)
                        /*
                         * property doesn't exist if -EINVAL, continue
                         * looking for more memory nodes with
                         * "numa-node-id" property
                         */
                        continue;

                if (nid >= MAX_NUMNODES) {
                        pr_warn("Node id %u exceeds maximum value\n", nid);
                        r = -EINVAL;
                }

                for (i = 0; !r && !of_address_to_resource(np, i, &rsrc); i++)
                        r = numa_add_memblk(nid, rsrc.start, rsrc.end + 1);

                if (!i || r) {
                        of_node_put(np);
                        pr_err("bad property in memory node\n");
                        return r ? : -EINVAL;
                }
        }

        return 0;
}

memory 노드 수만큼 순회하며 노드를 파싱하여 memblock에 노드를 기록한다.

  • “numa-node-id” 속성이 있는 메모리 노드인 경우 그 영역의 memblock에 노드 정보를 기록한다.

 

of_numa_parse_distance_map()

drivers/of/of_numa.c

static int __init of_numa_parse_distance_map(void)
{
        int ret = 0;
        struct device_node *np;

        np = of_find_compatible_node(NULL, NULL,
                                     "numa-distance-map-v1");
        if (np)
                ret = of_numa_parse_distance_map_v1(np);

        of_node_put(np);
        return ret;
}

compatible = “numa-distance-map-v1” 속성 값을 가진 노드에서 distance 맵을 파싱하여 온다.

 

of_numa_parse_distance_map_v1()

drivers/of/of_numa.c

static int __init of_numa_parse_distance_map_v1(struct device_node *map)
{
        const __be32 *matrix;
        int entry_count;
        int i;

        pr_info("parsing numa-distance-map-v1\n");

        matrix = of_get_property(map, "distance-matrix", NULL);
        if (!matrix) {
                pr_err("No distance-matrix property in distance-map\n");
                return -EINVAL;
        }

        entry_count = of_property_count_u32_elems(map, "distance-matrix");
        if (entry_count <= 0) {
                pr_err("Invalid distance-matrix\n");
                return -EINVAL;
        }

        for (i = 0; i + 2 < entry_count; i += 3) {
                u32 nodea, nodeb, distance;

                nodea = of_read_number(matrix, 1);
                matrix++;
                nodeb = of_read_number(matrix, 1);
                matrix++;
                distance = of_read_number(matrix, 1);
                matrix++;

                if ((nodea == nodeb && distance != LOCAL_DISTANCE) ||
                    (nodea != nodeb && distance <= LOCAL_DISTANCE)) {
                        pr_err("Invalid distance[node%d -> node%d] = %d\n",
                               nodea, nodeb, distance);
                        return -EINVAL;
                }

                numa_set_distance(nodea, nodeb, distance);

                /* Set default distance of node B->A same as A->B */
                if (nodeb > nodea)
                        numa_set_distance(nodeb, nodea, distance);
        }

        return 0;
}

distance 맵을 파싱하여 온다.

  • “distance-matrix” 속성 값을 읽어 numa_distance[] 배열에 값을 지정한다.

 

numa_set_distance()

arch/arm64/mm/numa.c

/**
 * numa_set_distance() - Set inter node NUMA distance from node to node.
 * @from: the 'from' node to set distance
 * @to: the 'to'  node to set distance
 * @distance: NUMA distance
 *
 * Set the distance from node @from to @to to @distance.
 * If distance table doesn't exist, a warning is printed.
 *
 * If @from or @to is higher than the highest known node or lower than zero
 * or @distance doesn't make sense, the call is ignored.
 *
 */
void __init numa_set_distance(int from, int to, int distance)
{
        if (!numa_distance) {
                pr_warn_once("Warning: distance table not allocated yet\n");
                return;
        }

        if (from >= numa_distance_cnt || to >= numa_distance_cnt ||
                        from < 0 || to < 0) {
                pr_warn_once("Warning: node ids are out of bound, from=%d to=%d distance=%d\n",
                            from, to, distance);
                return;
        }

        if ((u8)distance != distance ||
            (from == to && distance != LOCAL_DISTANCE)) {
                pr_warn_once("Warning: invalid distance parameter, from=%d to=%d distance=%d\n",
                             from, to, distance);
                return;
        }

        numa_distance[from * numa_distance_cnt + to] = distance;
}

numa distace를 설정한다.

  • numa_distance[from * numa_distance_cnt + to] 배열에 distance 값을 지정한다.

 

참고