<kernel v5.0>
DMA -4- (DMA Mapping)
Coherent 매핑이 아닌 Streaming 매핑을 사용하는 경우 DMA 전송 전/후로 매번 매핑 및 매핑 해제(sync 동작)가 필요하다.
- DMA 전/후로 캐시 sync 동작 및 iommu 매핑/매핑 해제를 수행한다.
- DMA 방향에 따라 캐시를 clean 하거나, invalidate 한다.
- DMA를 사용하는 디바이스가 iommu 사용 시 매핑 및 매핑 해제를 수행한다.
- 시스템 DRAM 보다 DMA 영역이 작은 경우 bounce buffer가 필요하다.
- 이러한 경우에는 데이터 copy가 필요한 swiotlb를 사용한다.
- 디폴트 bounce buffer는 64MB이다.
- 접근 가능한 DMA 주소 범위안에서 bounce buffer가 할당된다.
- ARM64의 경우 ZONE_DMA32를 사용하는 경우 4GB 물리 주소로 제한된다.
DMA 매핑 종류
다음과 같이 두 종류의 dma 매핑 중 하나를 선택하여 사용한다.
- constant dma 매핑
- driver 초기화 때 개별 매핑이 필요 없어 dma 전송 전/후로 매핑을 하지 않는다.
- streaming dma 매핑
- dma 전송 전/후로 매핑이 필요한 경우 사용한다.
스트리밍 DMA 매핑 종류
- Single
- 물리적으로 연속된 하나의 DMA 버퍼를 사용한다.
- Scatter/Gather
- 물리적으로 분산된 DMA 버퍼를 사용한다.
- 한 번의 DMA 동작에 여러 개의 DMA 버퍼에 전송하도록 하는데, 물리적으로 연속된 DMA 버퍼처럼 동작한다.
- 진보된 dma 컨트롤러에서만 이 기능을 사용할 수 있다.
스트리밍 dma – single 매핑/해제
다음 그림은 RAM에서 Device로 DMA 전송할 때의 캐시에 대한 single 스트리밍 매핑/해제 과정을 보여준다.
다음 그림은 Device로부터 RAM으로 DMA 전송할 때의 캐시에 대한 single 스트리밍 매핑/해제 과정을 보여준다.
single 매핑
다음 그림은 single 매핑에 대해 함수간 호출 관계를 보여준다.
dma_map_single()
include/linux/dma-mapping.h
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)
single 공간을 대상으로 dma 스트리밍 매핑을 수행한다.
- d: 디바이스
- a: 가상 주소
- s: 사이즈
- r: DMA 방향
dma_map_single_attrs()
include/linux/dma-mapping.h
static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr, size_t size, enum dma_data_direction dir, unsigned long attrs) { debug_dma_map_single(dev, ptr, size); return dma_map_page_attrs(dev, virt_to_page(ptr), offset_in_page(ptr), size, dir, attrs); }
single 공간을 대상으로 dma 스트리밍 매핑을 수행하는데, 옵션으로 속성 값을 지정할 수 있다.
dma_map_page_attrs()
include/linux/dma-mapping.h
static inline dma_addr_t dma_map_page_attrs(struct device *dev, struct page *page, size_t offset, size_t size, enum dma_data_direction dir, unsigned long attrs) { const struct dma_map_ops *ops = get_dma_ops(dev); dma_addr_t addr; BUG_ON(!valid_dma_direction(dir)); if (dma_is_direct(ops)) addr = dma_direct_map_page(dev, page, offset, size, dir, attrs); else addr = ops->map_page(dev, page, offset, size, dir, attrs); debug_dma_map_page(dev, page, offset, size, dir, addr); return addr; }
한 개의 페이지를 대상으로 dma 스트리밍 매핑을 수행하는데, 옵션으로 속성 값을 지정할 수 있다.
- 코드 라인 9~12에서 디바이스에 IOMMU dma 매핑 오퍼레이션이 제공되는 경우 (*map_page) 후크 함수를 호출한다. 그렇지 않은 경우 주소 변환 없이 사용하는 direct 매핑을 하도록 호출한다.
dma_direct_map_page()
kernel/dma/direct.c
dma_addr_t dma_direct_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction dir, unsigned long attrs) { phys_addr_t phys = page_to_phys(page) + offset; dma_addr_t dma_addr = phys_to_dma(dev, phys); if (unlikely(!dma_direct_possible(dev, dma_addr, size)) && !swiotlb_map(dev, &phys, &dma_addr, size, dir, attrs)) { report_addr(dev, dma_addr, size); return DMA_MAPPING_ERROR; } if (!dev_is_dma_coherent(dev) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC)) arch_sync_dma_for_device(dev, phys, size, dir); return dma_addr; } EXPORT_SYMBOL(dma_direct_map_page);
한 개의 페이지를 대상으로 주소 변환 없는 direct 매핑을 수행한다.
- 코드 라인 8~12에서 DMA 영역이 제한되어 bounce buffer를 사용하는 sw-iotlb 매핑이 필요한 경우 이를 수행한다.
- 코드 라인 14~15에서 디바이스가 coherent 연동되지 않고 skip cpu sync 속성 요청되지 않은 경우 디바이스의 DMA 전송 전에 아키텍처별로 제공되는 sync를 요청한다.
arch_sync_dma_for_device() – ARM64
arch/arm64/mm/dma-mapping.c
void arch_sync_dma_for_device(struct device *dev, phys_addr_t paddr, size_t size, enum dma_data_direction dir) { __dma_map_area(phys_to_virt(paddr), size, dir); }
ARM64 아키텍처의 경우 디바이스의 DMA 전송 전에 디바이스 턴을 위해 DMA 방향에 따른 캐시 sync를 요청한다.
__dma_map_area()
arch/arm64/mm/cache.S
/* * __dma_map_area(start, size, dir) * - start - kernel virtual start address * - size - size of region * - dir - DMA direction */
ENTRY(__dma_map_area) cmp w2, #DMA_FROM_DEVICE b.eq __dma_inv_area b __dma_clean_area ENDPIPROC(__dma_map_area)
ARM64 아키텍처의 경우 디바이스의 DMA 전송 전에 DMA 방향에 따른 캐시 sync를 다음과 같이 수행한다.
- DEVICE -> RAM 방향인 경우 기존 값은 의미가 없으므로 성능 향상을 위해 캐시를 clean 하지 않고 invalidate를 수행한다.
- 그 외의 방향은 clean을 수행한다.
single 매핑 해제
다음 그림은 single 매핑 해제에 대해 함수간 호출 관계를 보여준다.
dma_unmap_single()
include/linux/dma-mapping.h
#define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
single 공간을 대상으로 dma 스트리밍 매핑을 해제한다.
- d: 디바이스
- a: 가상 주소
- s: 사이즈
- r: DMA 방향
dma_unmap_single_attrs()
include/linux/dma-mapping.h
static inline void dma_unmap_single_attrs(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir, unsigned long attrs) { return dma_unmap_page_attrs(dev, addr, size, dir, attrs); }
single 공간을 대상으로 dma 스트리밍 매핑을 해제하는데, 옵션으로 속성 값을 지정할 수 있다.
dma_unmap_page_attrs()
include/linux/dma-mapping.h
static inline void dma_unmap_page_attrs(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir, unsigned long attrs) { const struct dma_map_ops *ops = get_dma_ops(dev); BUG_ON(!valid_dma_direction(dir)); if (dma_is_direct(ops)) dma_direct_unmap_page(dev, addr, size, dir, attrs); else if (ops->unmap_page) ops->unmap_page(dev, addr, size, dir, attrs); debug_dma_unmap_page(dev, addr, size, dir); }
한 개의 페이지를 대상으로 dma 스트리밍 매핑을 해제하는데, 옵션으로 속성 값을 지정할 수 있다.
- 코드 라인 7~10에서 디바이스에 IOMMU dma 매핑 해제 오퍼레이션이 제공되는 경우 (*unmap_page) 후크 함수를 호출한다. 그렇지 않은 경우 주소 변환 없이 사용하는 direct 매핑 해제를 하도록 호출한다.
dma_direct_unmap_page()
kernel/dma/direct.c
void dma_direct_unmap_page(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir, unsigned long attrs) { phys_addr_t phys = dma_to_phys(dev, addr); if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC)) dma_direct_sync_single_for_cpu(dev, addr, size, dir); if (unlikely(is_swiotlb_buffer(phys))) swiotlb_tbl_unmap_single(dev, phys, size, dir, attrs); } EXPORT_SYMBOL(dma_direct_unmap_page);
한 개의 페이지를 대상으로 주소 변환 없는 direct 매핑을 해제한다.
- 코드 라인 6~7에서 skip cpu sync 속성 요청되지 않은 경우 디바이스의 DMA 전송 후에 아키텍처별로 제공되는 sync를 요청한다
- 코드 라인 9~10에서 DMA 영역이 제한되어 bounce buffer를 사용하면 sw-iotlb 매핑 해제를 수행한다.
dma_direct_sync_single_for_cpu()
kernel/dma/direct.c
void dma_direct_sync_single_for_cpu(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir) { phys_addr_t paddr = dma_to_phys(dev, addr); if (!dev_is_dma_coherent(dev)) { arch_sync_dma_for_cpu(dev, paddr, size, dir); arch_sync_dma_for_cpu_all(dev); } if (unlikely(is_swiotlb_buffer(paddr))) swiotlb_tbl_sync_single(dev, paddr, size, dir, SYNC_FOR_CPU); } EXPORT_SYMBOL(dma_direct_sync_single_for_cpu);
디바이스의 DMA 전송 후 cpu 턴을 위해 DMA 방향에 따른 캐시 sync를 요청한다.
- 코드 라인 6~9에서 아키텍처에 따른 sync를 수행한다.
- 코드 라인 11~12에서 DMA 영역이 제한되어 bounce buffer를 사용하면 sw-iotlb 싱크를 수행한다.
arch_sync_dma_for_cpu() – ARM64
arch/arm64/mm/dma-mapping.c
void arch_sync_dma_for_cpu(struct device *dev, phys_addr_t paddr, size_t size, enum dma_data_direction dir) { __dma_unmap_area(phys_to_virt(paddr), size, dir); }
ARM64 아키텍처의 경우 디바이스의 DMA 전송 후 cpu 턴을 위해 DMA 방향에 따른 캐시 sync를 요청한다.
__dma_unmap_area()
arch/arm64/mm/cache.S
/* * __dma_unmap_area(start, size, dir) * - start - kernel virtual start address * - size - size of region * - dir - DMA direction */
ENTRY(__dma_unmap_area) cmp w2, #DMA_TO_DEVICE b.ne __dma_inv_area ret ENDPIPROC(__dma_unmap_area)
ARM64 아키텍처의 경우 디바이스의 DMA 전송 후에 DMA 방향에 따른 캐시 sync를 다음과 같이 수행한다.
- DEVICE <- RAM 방향인 경우 디바이스가 캐시에 write 접근을 하지 않은 경우이므로 캐시를 invalidate하거나 clean 할 필요 없다.
- 또한 그 외의 방향에서는 디바이스가 메모리에 기록하였을지도 모르므로 CPU의 캐시들은 모두 invalidate를 하여 메모리에 아무런 변경을 하지 못하게 한다.
스트리밍 dma – scatter/gather 매핑/해제
분산된 물리 메모리를 DMA 버퍼용도로 할당 받은 후 이들의 정보를 scaterlist 구조체에 대입하고 이를 배열로 요청하여 DMA 전송 요청 전/후로 등록된 DMA 버퍼들에 대해 DMA Streaming 매핑을 한꺼번에 수행할 때 scatter/gather 매핑을 사용한다.
- DMA 전송 시 물리메모리가 연속된 것처럼 한꺼번에 이루어진다.
다음 그림은 3개의 DMA 버퍼로 구성된 scatterlist 배열을 보여준다.
scatter/gather 매핑
DMA 전송할 여러 개의 영역을 한 꺼번에 매핑을 수행한다.
dma_map_sg()
include/linux/dma-mapping.h
#define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, 0)
여러 개의 공간을 대상으로 dma 스트리밍 매핑을 수행한다.
- d: 디바이스
- s: 여러 영역 정보를 리스트한 scatterlist
- n: 엔트리 수
- r: DMA 방향
dma_map_sg_attrs()
include/linux/dma-mapping.h
/* * dma_maps_sg_attrs returns 0 on error and > 0 on success. * It should never return a value < 0. */
static inline int dma_map_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, unsigned long attrs) { const struct dma_map_ops *ops = get_dma_ops(dev); int ents; BUG_ON(!valid_dma_direction(dir)); if (dma_is_direct(ops)) ents = dma_direct_map_sg(dev, sg, nents, dir, attrs); else ents = ops->map_sg(dev, sg, nents, dir, attrs); BUG_ON(ents < 0); debug_dma_map_sg(dev, sg, nents, ents, dir); return ents; }
인자 @sg로 전달받은 여러 공간을 대상으로 dma 스트리밍 매핑을 수행하는데, 옵션으로 속성 값을 지정할 수 있다.
- 코드 라인 9~12에서 디바이스에 IOMMU dma 매핑 오퍼레이션이 제공되는 경우 (*map_sg) 후크 함수를 호출한다. 그렇지 않은 경우 주소 변환 없이 사용하는 direct sg 매핑을 하도록 호출한다.
dma_direct_map_sg()
kernel/dma/direct.c
int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, int nents, enum dma_data_direction dir, unsigned long attrs) { int i; struct scatterlist *sg; for_each_sg(sgl, sg, nents, i) { sg->dma_address = dma_direct_map_page(dev, sg_page(sg), sg->offset, sg->length, dir, attrs); if (sg->dma_address == DMA_MAPPING_ERROR) goto out_unmap; sg_dma_len(sg) = sg->length; } return nents; out_unmap: dma_direct_unmap_sg(dev, sgl, i, dir, attrs | DMA_ATTR_SKIP_CPU_SYNC); return 0; } EXPORT_SYMBOL(dma_direct_map_sg);
인자 @sgl로 전달받은 여러 공간을 대상으로 엔트리 수(@nents)만큼 dma 페이지 매핑을 수행하는데, 옵션으로 속성 값을 지정할 수 있다.
scatter/gather 매핑 해제
DMA 전송할 여러 개의 영역을 한 꺼번에 매핑해제를 수행한다.
dma_unmap_sg()
include/linux/dma-mapping.h
#define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, 0)
여러 개의 공간을 대상으로 dma 스트리밍 매핑 해제를 수행한다.
- d: 디바이스
- s: 여러 영역 정보를 리스트한 scatterlist
- n: 엔트리 수
- r: DMA 방향
dma_unmap_sg_attrs()
include/linux/dma-mapping.h
static inline void dma_unmap_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, unsigned long attrs) { const struct dma_map_ops *ops = get_dma_ops(dev); BUG_ON(!valid_dma_direction(dir)); debug_dma_unmap_sg(dev, sg, nents, dir); if (dma_is_direct(ops)) dma_direct_unmap_sg(dev, sg, nents, dir, attrs); else if (ops->unmap_sg) ops->unmap_sg(dev, sg, nents, dir, attrs); }
인자 @sg로 전달받은 여러 공간을 대상으로 dma 스트리밍 매핑 해제를 수행하는데, 옵션으로 속성 값을 지정할 수 있다.
- 코드 라인 9~12에서 디바이스에 IOMMU dma 매핑 오퍼레이션이 제공되는 경우 (*unmap_sg) 후크 함수를 호출한다. 그렇지 않은 경우 주소 변환 없이 사용하는 direct sg 매핑 해제를 호출한다.
dma_direct_unmap_sg()
kernel/dma/direct.c
void dma_direct_unmap_sg(struct device *dev, struct scatterlist *sgl, int nents, enum dma_data_direction dir, unsigned long attrs) { struct scatterlist *sg; int i; for_each_sg(sgl, sg, nents, i) dma_direct_unmap_page(dev, sg->dma_address, sg_dma_len(sg), dir, attrs); } EXPORT_SYMBOL(dma_direct_unmap_sg);
인자 @sgl로 전달받은 여러 공간을 대상으로 엔트리 수(@nents)만큼 dma 페이지 매핑 해제를 수행하는데, 옵션으로 속성 값을 지정할 수 있다.
scatter/gather 테이블
sg_kmalloc()
lib/scatterlist.c
/* * The default behaviour of sg_alloc_table() is to use these kmalloc/kfree * helpers. */
static struct scatterlist *sg_kmalloc(unsigned int nents, gfp_t gfp_mask) { if (nents == SG_MAX_SINGLE_ALLOC) { /* * Kmemleak doesn't track page allocations as they are not * commonly used (in a raw form) for kernel data structures. * As we chain together a list of pages and then a normal * kmalloc (tracked by kmemleak), in order to for that last * allocation not to become decoupled (and thus a * false-positive) we need to inform kmemleak of all the * intermediate allocations. */ void *ptr = (void *) __get_free_page(gfp_mask); kmemleak_alloc(ptr, PAGE_SIZE, 1, gfp_mask); return ptr; } else return kmalloc_array(nents, sizeof(struct scatterlist), gfp_mask); }
@nents개의 scatterlist 배열을 담을 메모리를 할당받는다.
- 보통 1페이지를 채워서 받을 수 있도록 @nents=SG_MAX_SINGLE_ALLOC을 지정할 수 있다.
- sg_kfree()
- 할당 해제 API
sg_init_table()
lib/scatterlist.c
/** * sg_init_table - Initialize SG table * @sgl: The SG table * @nents: Number of entries in table * * Notes: * If this is part of a chained sg table, sg_mark_end() should be * used only on the last table part. * **/
void sg_init_table(struct scatterlist *sgl, unsigned int nents) { memset(sgl, 0, sizeof(*sgl) * nents); sg_init_marker(sgl, nents); } EXPORT_SYMBOL(sg_init_table);
sg 테이블을 초기화한다. 마지막 엔트리는 SG_END 마킹을 한다.
/** * sg_init_marker - Initialize markers in sg table * @sgl: The SG table * @nents: Number of entries in table * **/
static inline void sg_init_marker(struct scatterlist *sgl, unsigned int nents) { sg_mark_end(&sgl[nents - 1]); }
sgl의 마지막 엔트리를 SG_END 마킹 한다.
/** * sg_mark_end - Mark the end of the scatterlist * @sg: SG entryScatterlist * * Description: * Marks the passed in sg entry as the termination point for the sg * table. A call to sg_next() on this entry will return NULL. * **/
static inline void sg_mark_end(struct scatterlist *sg) { /* * Set termination bit, clear potential chain bit */ sg->page_link |= SG_END; sg->page_link &= ~SG_CHAIN; }
요청한 sg를 SG_END 마킹한다. (SG_CHAIN이 있는 경우 제거)
기타 Scatter/Gather API
- sg_next()
- sg_next_ptr()
- sg_chain()
- sg_nents()
- sg_nents_for_len()
- sg_last()
- sg_set_page()
- sg_set_buf()
- sg_mark_end()
Chained Scatter/Gather Table
- sg 테이블을 여러 개 할당받아 chain으로 연결하여 대규모 전송을 통한 성능 향상을 위해 사용한다.
- block io 전송에 사용되고 있다.
- 관련 API
- sg_alloc_table()
- __sg_alloc_table()
- sg_alloc_table_from_pages()
- __sg_alloc_table_from_pages()
- sgl_alloc()
- sgl_alloc_order()
- sgl_free()
- sgl_free_order()
- sgl_free_n_order()
- sgl_free_order()
- sg_copy_from_buffer()
- sg_copy_buffer()
- sg_miter_start()
- sg_miter_skip()
- sg_miter_stop()
- sg_copy_buffer()
- sg_copy_to_buffer()
- sg_zero_buffer()
- sg_alloc_table()
- 참고
- The chained scatterlist API (2007) | LWN.net
- Rationalizing scatter/gather chains (2007) | LWN.net
참고
- DMA -1- (Basic) | 문c
- DMA -2- (DMA Coherent Memory) | 문c
- DMA -3- (DMA Pool) | 문c
- DMA -4- (DMA Mapping) | 문c – 현재 글
- DMA -5- (IOMMU) | 문c
- DMA -6- (DMAEngine Subsystem) | 문c
- IOMMU | 문c