__flush_dcache_page()

page 구조체가 가리키는 메모리 영역에 대한 d-cache를 flush 한다.

 

__flush_dcache_page-1

 

__flush_dcache_page()

arch/arm/mm/flush.c

void __flush_dcache_page(struct address_space *mapping, struct page *page)
{
        /*  
         * Writeback any data associated with the kernel mapping of this
         * page.  This ensures that data in the physical page is mutually
         * coherent with the kernels mapping.
         */
        if (!PageHighMem(page)) {
                size_t page_size = PAGE_SIZE << compound_order(page);
                __cpuc_flush_dcache_area(page_address(page), page_size);
        } else {
                unsigned long i;
                if (cache_is_vipt_nonaliasing()) {
                        for (i = 0; i < (1 << compound_order(page)); i++) {
                                void *addr = kmap_atomic(page + i); 
                                __cpuc_flush_dcache_area(addr, PAGE_SIZE);
                                kunmap_atomic(addr);
                        }   
                } else {
                        for (i = 0; i < (1 << compound_order(page)); i++) {
                                void *addr = kmap_high_get(page + i); 
                                if (addr) {
                                        __cpuc_flush_dcache_area(addr, PAGE_SIZE);
                                        kunmap_high(page + i); 
                                }   
                        }   
                }   
        }   

        /*
         * If this is a page cache page, and we have an aliasing VIPT cache,
         * we only need to do one flush - which would be at the relevant
         * userspace colour, which is congruent with page->index.
         */
        if (mapping && cache_is_vipt_aliasing())
                flush_pfn_alias(page_to_pfn(page),
                                page->index << PAGE_CACHE_SHIFT);
}

페이지가 highmem 영역이 아닌 경우 해당 페이지들 영역에 대한 d-cache를 flush 한다.

  • if (!PageHighMem(page)) {
    • 주어진 페이지가 highmem 영역이 아니면
  • size_t page_size = PAGE_SIZE << compound_order(page);
    • size는 PAGE_SIZE x 2^compund_order로 한다.
    • 예) PAGE_SIZE=4K, compound_order=9
      • size=2M
  • __cpuc_flush_dcache_area(page_address(page), page_size);
    • flush_dcache_area에 대한 각 아키텍처 종속 코드 함수를 호출하여 특정 영역 크기에 해당하는 d-cache를 flush 한다.
    • rpi2: v7_flush_kern_dcache_area() 함수를 호출한다.

페이지가 highmem 영역이면서 cache가 vipt non-aliasing 타입인 경우 해당 페이지들 영역을 루프를 돌며 각 페이지에 대해 fixmap 매핑한 후 d-cache를 flush 하고 다시 fixmap 매핑을 해지한다.

  •  if (cache_is_vipt_nonaliasing()) {
  • for (i = 0; i < (1 << compound_order(page)); i++) {
    • 2^compound_order 페이지 만큼 루프를 돈다.
  • void *addr = kmap_atomic(page + i);
    • fixmap 영역에 highmem 메모리 한 페이지를 매핑 한다.
    • 참고: Fixmap | 문c
  • __cpuc_flush_dcache_area(addr, PAGE_SIZE);
    • 주어진 1개 가상 주소 페이지 하나에 대해 d-cache flush를 수행한다.
  • kunmap_atomic(addr);
    • fixmap 영역에 매핑된 highmem 페이지를 해지한다.

페이지가 highmem 영역이면서 cache가 vipt non-aliasing 타입이 아닌 경우 해당 페이지들 영역을 루프를 돌며 각 페이지에 대해 이미 kmap 매핑된 가상 주소를 찾아 d-cache를 flush 하고 그 kmap 매핑을 해지한다.

  • for (i = 0; i < (1 << compound_order(page)); i++) {
    • 2^compound_order 페이지 만큼 루프를 돈다.
  • void *addr = kmap_high_get(page + i);
    • kmap 영역에 매핑된 highmem 메모리를 찾는다.
    • 참고: Kmap(Pkmap) | 문c
  • __cpuc_flush_dcache_area(addr, PAGE_SIZE);
    • 찾은 1개 가상 주소 페이지 하나에 대해 d-cache flush를 수행한다.
  • kunmap_high(page + i);
    • kmap 영역에 매핑된 highmem 페이지를 해지한다.

인수로 요청한 매핑이 있는 경우이면서 캐시 타입이 vipt aliasing인 경우 TLB를 flush 한다.

  • if (mapping && cache_is_vipt_aliasing())
    • mapping과 캐시 타입이 vipt aliasing인 경우
  • flush_pfn_alias(page_to_pfn(page), page->index << PAGE_CACHE_SHIFT);
    • 요청 페이지에 대한 TLB를 flush 한다.

 

v7_flush_kern_dcache_area()

arch/arm/mm/cache-v7.S

/*
 *      v7_flush_kern_dcache_area(void *addr, size_t size)
 *
 *      Ensure that the data held in the page kaddr is written back
 *      to the page in question.
 *
 *      - addr  - kernel address
 *      - size  - region size
 */
ENTRY(v7_flush_kern_dcache_area)
        dcache_line_size r2, r3
        add     r1, r0, r1
        sub     r3, r2, #1
        bic     r0, r0, r3
#ifdef CONFIG_ARM_ERRATA_764369
        ALT_SMP(W(dsb))
        ALT_UP(W(nop))
#endif
1:
        mcr     p15, 0, r0, c7, c14, 1          @ clean & invalidate D line / unified line
        add     r0, r0, r2
        cmp     r0, r1
        blo     1b  
        dsb     st  
        ret     lr  
ENDPROC(v7_flush_kern_dcache_area)
  • start(r0) ~ end(r1) 까지 캐시 라인 길이(바이트) 만큼 증가시키면서루프를 돌며 DCCIMVAC를 호출하여 해당 캐시 라인을 비운다.
    • DCCIMVAC(Data Cache Clean & Invalidate MVA to PoC)

 

dcache_line_size()

arch/arm/mm/proc-macros.S

/*
 * dcache_line_size - get the minimum D-cache line size from the CTR register
 * on ARMv7.
 */
        .macro  dcache_line_size, reg, tmp 
        mrc     p15, 0, \tmp, c0, c0, 1         @ read ctr 
        lsr     \tmp, \tmp, #16 
        and     \tmp, \tmp, #0xf                @ cache line size encoding
        mov     \reg, #4                        @ bytes per word
        mov     \reg, \reg, lsl \tmp            @ actual cache line size
        .endm

캐시 라인 워드 사이즈를 읽어온 후 4를 곱하여 reg 레지스터로 리턴한다.

  • CTR.DminLine: 캐시 라인 사이즈로 word 단위이다.

 

flush_pfn_alias()

arch/arm/mm/flush.c

static void flush_pfn_alias(unsigned long pfn, unsigned long vaddr)
{
        unsigned long to = FLUSH_ALIAS_START + (CACHE_COLOUR(vaddr) << PAGE_SHIFT);
        const int zero = 0;

        set_top_pte(to, pfn_pte(pfn, PAGE_KERNEL));

        asm(    "mcrr   p15, 0, %1, %0, c14\n"
        "       mcr     p15, 0, %2, c7, c10, 4"
            :   
            : "r" (to), "r" (to + PAGE_SIZE - 1), "r" (zero)
            : "cc");
}
  • unsigned long va = FLUSH_ALIAS_START + (CACHE_COLOUR(vaddr) << PAGE_SHIFT);
    • ARMv6 VIPT 캐시를 사용하는 경우 CACHE_COLOUR() 값은 0~3이 리턴된다.
      • 실제 캐시 way 단면 사이즈가 4K를 초과하는 경우 페이지 크기와 달라지면서 이에 대한 교정이 필요하다. 따라서 4K를 초과하는 2개의 비트에 대해 페이지별로 coloring을 하기 위해 사용한다.
      • 참고: Cache – VIPT 캐시 컬러링 | 문c
    • FLUSH_ALIAS_START
      • 0xffff_4000
    • va
      • coloring이 존재하지 않는 경우
        • 0xffff_4000
      • coloring이 존재하는 경우
        • 0xffff_4000, 0xffff_5000, 0xffff_6000, 0xffff_7000 중 하나
  • set_top_pte(to, pfn_pte(pfn, PAGE_KERNEL));
    • va 주소가 가리키는 hw pte 엔트리 값의 속성을 x로 설정한다.
  • asm( “mcrr p15, 0, %1, %0, c14\n”
    • block operation으로 %0 ~ %1까지 주소에 Clean & Invalidate Data Cache range 명령을 수행한다.
    • 참고: c7, Cache Operations Register -> Table 3.78
  • ”       mcr     p15, 0, %2, c7, c10, 4″
    • DSB

 

set_top_pte()

arch/arm/mm/mm.h

static inline void set_top_pte(unsigned long va, pte_t pte)
{
        pte_t *ptep = pte_offset_kernel(top_pmd, va);
        set_pte_ext(ptep, pte, 0); 
        local_flush_tlb_kernel_page(va);
}
  • pte_t *ptep = pte_offset_kernel(top_pmd, va);
    • 가장 최상단에 위치한 pmd 테이블에서 va 주소와 매치되는 pmd 엔트리를 통해 찾은 pte 엔트리 주소
  • set_pte_ext(ptep, pte, 0);
  • local_flush_tlb_kernel_page(va);
    • 요청 가상 주소 페이지에 대한 TLB flush를 수행한다.

 

참고

 

댓글 남기기