page 구조체가 가리키는 메모리 영역에 대한 d-cache를 flush 한다.
__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()) {
- cache가 vipt nonaliasing 속성인 경우
- 참고: Cache – 구성 타입 | 문c
- 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 중 하나
- coloring이 존재하지 않는 경우
- ARMv6 VIPT 캐시를 사용하는 경우 CACHE_COLOUR() 값은 0~3이 리턴된다.
- 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);
- 찾은 ptep 주소에 pte 속성값을 설정한다.
- 참고: 페이지 테이블 관련 명령(pgd, pud, pmd, pte) | 문c
- local_flush_tlb_kernel_page(va);
- 요청 가상 주소 페이지에 대한 TLB flush를 수행한다.
참고
- ARM 시스템 주요 레지스터 | 문c
- TLB Cache (API) | 문c