본문 바로가기

Programming/ARM

arm cache 의 속성과 셋팅 (1)


cache 셋팅에 여러가지 속성중 두가지에 대해 이야기 해보자.

< pgtable.h >
#define L_PTE_MT_UNCACHED (0x00 << 2) /* 0000 */
#define L_PTE_MT_BUFFERABLE (0x01 << 2) /* 0001 */
#define L_PTE_MT_WRITETHROUGH (0x02 << 2) /* 0010 */
#define L_PTE_MT_WRITEBACK (0x03 << 2) /* 0011 */

#define L_PTE_MT_MINICACHE (0x06 << 2) /* 0110 (sa1100, xscale) */
#define L_PTE_MT_WRITEALLOC (0x07 << 2) /* 0111 */
#define L_PTE_MT_DEV_SHARED (0x04 << 2) /* 0100 */
#define L_PTE_MT_DEV_NONSHARED (0x0c << 2) /* 1100 */
#define L_PTE_MT_DEV_WC  (0x09 << 2) /* 1001 */
#define L_PTE_MT_DEV_CACHED (0x0b << 2) /* 1011 */
#define L_PTE_MT_MASK  (0x0f << 2)

한마디로 writeback 은 cache 에 쓴 후에 background 로 phygical memory 에 저장하는 방식이고,
writethrought는 cache 에 쓰는 동시에 phygical memory 에 적는 방식이다.
둘다 write 후 read 시에 큰 효용성을 발휘한다.

통상적으로 framebuffer 는 DMA 속성으로 setting 하기 때문에 cache 를 enable 할 수 없다.


/**
 * dma_alloc_writecombine - allocate writecombining memory for DMA
 * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
 * @size: required memory size
 * @handle: bus-specific DMA address
 *
 * Allocate some uncached, buffered memory for a device for
 * performing DMA.  This function allocates pages, and will
 * return the CPU-viewed address, and sets @handle to be the
 * device-viewed address.
 */
extern void *dma_alloc_writecombine(struct device *, size_t, dma_addr_t *,
  gfp_t);


...


/*
 * Mark the prot value as uncacheable and unbufferable.
 */
#define pgprot_noncached(prot) \
 __pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_UNCACHED)
#define pgprot_writecombine(prot) \
 __pgprot((pgprot_val(prot) & ~L_PTE_MT_MASK) | L_PTE_MT_BUFFERABLE)

이를 mmap 한 메모리를 cacheble로 설정하고 writethrough로 setting 할 수 있을까????

답변을 찾아 공부해본다.

< 참고내용 1 >
원문: http://i205.tistory.com/tag/kmalloc



Cache Coherency 를 고려해야 합니다 
 
외부 디바이스가 DMA 로 시스템 메모리를 읽거나 쓸경우에는 CPU 와 독립적으
로 진행됩니다. 따라서 CPU 내의 data cache 와 memory 사이에는 coherency
가 없어지게 됩니다.

예를 들어 device -> memory DMA 일경우 DMA 가 끝난후 CPU 가 그 버퍼를 읽을
시 CPU 는 디바이스가 보낸 데이터 대신 data cache 에 있던 이전 데이터를 읽
게 됩니다.

따라서 DMA 에 사용할 버퍼를 allocate 할때에는 단순히 kmalloc() 를 사용하
면 안되고 coherent (또는 consistent 라고도 말합니다) 한 allocate 를 사용
하여야 합니다.

coherent allocate 방법은 여러가지가 있으나 가장 쉬운 방법은
consistent_allocate() 함수로 버퍼를 얻는것입니다. 이 함수 소스를 보면
__ioremap() 함수를 부르는데 여기서 physical memory 를 vm 에 매핑할때 non-
cacheable 로 해줍니다.

따라서 consistent_allocate() 로 얻어진 주소를 CPU 가 억세스하면 cache 를
bypass 하여 직접 메모리를 억세스하므로 DMA 로 보내진 데이터와 coherent 해
집니다.

초기화때 한번만 이 함수를 사용하면 되니까 사용하기가 쉽습니다.

다른 방법으로는 DMA 버퍼를 kmalloc() 로 allocate 하되 DMA 전송이 끝날때마
다 (device->memory 시) 또는 DMA 전송을 시작하기 직전 (memory->device 시)
consistent_sync() 함수를 부르는것입니다. 이 함수는 DMA 방향에 따라 cache
를 invalidate 또는 write-back 을 해줍니다.

consistent_sync 는 사용하기는 조금 더 복잡하나 non-cached read/write 가
아니므로 CPU 가 DMA 버퍼를 억세스할때 속도가 떨어지지 않습니다.
참고로 이전 글에서 consistent_alloc() 에 버그가 있다고 본것 같은데 제 경
험으로는 그렇지 않다고 생각됩니다. 2800 의 on-chip DMA 를 사용해 보지는
않았지만 여러 종류의 PCI 카드가 Bus-mastering DMA 로 잘 동작하는것을 확인
하였습니다.

PCI 의 DMA 함수들인 pci_alloc_consistent(), pci_map_single() 들도 위의
consistent_allocate() 와 consistent_sync() 함수를 사용합니다.
 



< 참고내용 2 >
http://wiki.kldp.org/wiki.php/%C0%CC%B1%A4%BF%EC