DMA -4- (DMA Mapping)

<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()
    • sg_copy_from_buffer()
      •  sg_copy_buffer()
        • sg_miter_start()
        • sg_miter_skip()
        • sg_miter_stop()
    • sg_copy_to_buffer()
    • sg_zero_buffer()
  • 참고

 

참고

댓글 남기기