DMA 심화 (Direct Memory Access)

Linux 커널 DMA 서브시스템 종합 가이드: 매핑 API(Coherent/Streaming/Scatter-Gather/Pool), IOMMU(VT-d/AMD-Vi/SMMU), SWIOTLB, CMA, DMA-BUF 버퍼 공유, P2P DMA, 캐시 일관성, 보안, 디버깅.

관련 표준: Intel VT-d (DMA 리매핑), AMD-Vi (AMD IOMMU), ARM SMMU (시스템 MMU) — 커널 DMA 서브시스템이 구현하는 IOMMU 하드웨어 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
교차 참조: DMA 기초 예제는 디바이스 드라이버, PCI DMA 매핑은 PCI/PCIe, GPU DMA-BUF 활용은 GPU (DRM/KMS), DMA 메모리 존은 메모리 심화 페이지를 참조하십시오.
전제 조건: 메모리 관리(물리 메모리, GFP 플래그)와 디바이스 드라이버(디바이스 모델 기초)를 먼저 읽으세요.
일상 비유: DMA는 택배 시스템과 같습니다. CPU(사장님)가 매번 직접 물건을 나르는 대신, DMA 컨트롤러(택배 기사)가 디바이스와 메모리 사이에서 데이터를 자동으로 운반합니다. IOMMU는 택배 기사가 접근할 수 있는 주소 범위를 제한하는 보안 게이트에 해당합니다.

핵심 요약

  • DMA — CPU 개입 없이 디바이스가 메모리에 직접 읽기/쓰기하는 메커니즘으로, 시스템 처리량을 크게 향상시킵니다.
  • Coherent vs Streaming — Coherent DMA는 CPU-디바이스 간 항상 동기화, Streaming은 명시적 sync가 필요하지만 더 효율적입니다.
  • IOMMU — 디바이스의 DMA 주소를 물리 주소로 변환하는 하드웨어(VT-d, AMD-Vi, SMMU)입니다.
  • DMA-BUF — GPU, 카메라 등 여러 디바이스 간에 DMA 버퍼를 공유하는 프레임워크입니다.

단계별 이해

  1. DMA가 필요한 이유 — CPU가 바이트 단위로 데이터를 복사(PIO)하면 CPU 시간을 낭비합니다.

    네트워크 카드, 디스크 컨트롤러 등 대용량 전송 디바이스에서 DMA가 필수적입니다.

  2. DMA 매핑 이해 — 디바이스가 접근할 "DMA 주소"를 할당하는 과정입니다. dma_alloc_coherent() 또는 dma_map_single()을 사용합니다.

    IOMMU가 있으면 DMA 주소 ≠ 물리 주소일 수 있습니다.

  3. 캐시 일관성 — DMA 전송 시 CPU 캐시와 메모리 내용이 다를 수 있으므로, Streaming 매핑에서는 반드시 dma_sync_* API로 동기화합니다.

    Coherent 매핑은 하드웨어가 자동 동기화하므로 sync가 불필요합니다.

DMA 개요

DMA(Direct Memory Access)는 CPU 개입 없이 디바이스가 시스템 메모리에 직접 읽기/쓰기를 수행하는 메커니즘입니다. CPU는 전송을 설정한 뒤 다른 작업을 수행할 수 있어 시스템 전체 처리량이 크게 향상됩니다.

PIO vs DMA 비교

특성PIO (Programmed I/O)DMA
데이터 이동 주체CPU (in/out 명령)DMA 엔진 / 디바이스 버스마스터
CPU 사용률전송 동안 100% 점유설정·완료 인터럽트만 처리
전송 속도CPU 클럭 의존, 느림버스 대역폭 활용, 빠름
캐시 일관성자동 보장 (CPU 접근)명시적 sync 필요 (아키텍처 의존)
주요 사용처소형 레거시 디바이스NIC, 스토리지, GPU, 사운드카드

DMA 발전사

세대메커니즘특징
ISA DMA8237A DMA 컨트롤러24비트 주소(16MB), 4/8채널, CPU가 컨트롤러 프로그래밍
PCI 버스마스터디바이스가 버스 마스터32/64비트 주소, 디바이스가 직접 메모리 접근
PCIe + IOMMUTLP 기반 메모리 Read/Write가상 주소 변환, 디바이스 격리, ATS/PRI/PASID

커널 소스 트리 구조

경로역할
kernel/dma/DMA 매핑 코어: mapping.c, direct.c, swiotlb.c, pool.c, contiguous.c
include/linux/dma-mapping.hDMA 매핑 API 헤더
include/linux/dma-map-ops.hdma_map_ops 백엔드 인터페이스
drivers/iommu/IOMMU 프레임워크: intel/, amd/, arm-smmu-v3/
drivers/dma-buf/DMA-BUF 버퍼 공유 프레임워크
drivers/dma/DMA 엔진(slave DMA 컨트롤러) 드라이버
CPU ① 설정 → 완료 IRQ 시스템 메모리 DMA 버퍼 디바이스 NIC / SSD / GPU PIO (느림) ② DMA 전송 IOMMU 주소 변환 · 격리 ① CPU: 주소·길이·방향 설정 → ③ 완료 인터럽트 수신

DMA 주소 공간과 매핑

DMA 프로그래밍에서 가장 혼란스러운 부분은 여러 주소 공간이 동시에 존재한다는 점입니다.

주소 유형

주소 유형C 타입의미변환 관계
가상 주소void *CPU MMU를 통한 커널 가상 주소virt_to_phys() → 물리
물리 주소phys_addr_tCPU가 보는 실제 RAM 주소항상 고정
버스(DMA) 주소dma_addr_t디바이스가 보는 주소IOMMU 없으면 = 물리, 있으면 IOVA
주의: virt_to_bus()/bus_to_virt()는 폐기된 API입니다. 반드시 dma_map_*() API를 사용하십시오. 이 API만이 IOMMU, SWIOTLB, CMA 등 모든 백엔드를 올바르게 처리합니다.
CPU 경로 (MMU) 가상 주소 (VA) MMU 물리 주소 (PA) RAM 디바이스 경로 (IOMMU) DMA 주소 (IOVA) IOMMU 물리 주소 (PA) RAM

DMA 존(Zone)과 주소 마스크

Zonex86_64 범위용도
ZONE_DMA0 ~ 16MBISA DMA 레거시 디바이스 (24비트)
ZONE_DMA320 ~ 4GB32비트 DMA 디바이스
ZONE_NORMAL4GB 이상64비트 DMA 디바이스
/* DMA 마스크 설정 — 디바이스의 주소 지정 능력을 커널에 알림 */
int ret;

/* 64비트 DMA 가능한 디바이스 */
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret) {
    /* 64비트 실패 시 32비트로 폴백 */
    ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
    if (ret) {
        dev_err(dev, "No suitable DMA available\n");
        return ret;
    }
}

/* dma_set_mask_and_coherent()는 다음 두 호출의 조합: */
/* dma_set_mask(dev, mask)      — streaming 매핑용 마스크 */
/* dma_set_coherent_mask(dev, mask) — coherent 할당용 마스크 */

Device Tree의 dma-ranges

ARM/RISC-V 등 플랫폼에서 버스 주소와 물리 주소가 다를 수 있으며, dma-ranges 속성으로 변환을 정의합니다:

/* DTS 예시: 버스 주소 0x0이 물리 주소 0x80000000에 매핑 */
soc {
    #address-cells = <1>;
    #size-cells = <1>;
    dma-ranges = <0x0 0x80000000 0x40000000>;
    /* child_bus_addr  parent_phys_addr  length */
};

DMA 매핑 API 상세

Linux 커널은 DMA 매핑을 위해 5가지 주요 API를 제공합니다. 모든 API는 <linux/dma-mapping.h>에 선언되어 있습니다.

API 유형 비교

API수명캐시 일관성사용 시나리오
Coherent장기 (드라이버 수명)HW 자동 보장디스크립터 링, 명령 큐
Streaming (single)단기 (I/O 단위)명시적 sync단일 버퍼 전송
Streaming (SG)단기 (I/O 단위)명시적 syncScatter-Gather I/O
Pool장기 (반복 할당)HW 자동 보장고정 크기 소형 버퍼 풀
Non-coherent장기명시적 sync대용량 버퍼 (캐시 제어 필요)

Coherent (Consistent) DMA

CPU와 디바이스가 동시에 접근하는 공유 메모리에 사용합니다. 하드웨어가 캐시 일관성을 보장합니다.

#include <linux/dma-mapping.h>

dma_addr_t dma_handle;
void *cpu_addr;
size_t size = 4096;

/* 할당: CPU 가상주소 + DMA 버스주소를 동시에 얻음 */
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr)
    return -ENOMEM;

/* cpu_addr로 CPU 접근, dma_handle로 디바이스에 주소 전달 */
desc->buf_addr = cpu_to_le64(dma_handle);

/* 해제 */
dma_free_coherent(dev, size, cpu_addr, dma_handle);

Streaming DMA — 단일 버퍼

한 번의 I/O 전송 동안만 유효한 매핑입니다. 전송 후 반드시 unmap해야 합니다.

dma_addr_t dma_handle;
void *buf = kmalloc(4096, GFP_KERNEL);

/* 매핑: 방향(Direction)은 데이터 흐름을 나타냄 */
dma_handle = dma_map_single(dev, buf, 4096, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
    dev_err(dev, "DMA mapping failed\n");
    kfree(buf);
    return -EIO;
}

/* 디바이스에 dma_handle 전달, 전송 시작 */
start_device_transfer(dma_handle, 4096);

/* 전송 완료 후 unmap */
dma_unmap_single(dev, dma_handle, 4096, DMA_TO_DEVICE);
필수 확인: dma_map_single() 후에는 반드시 dma_mapping_error()로 오류를 확인하십시오. IOMMU가 활성화된 시스템에서는 IOVA 공간 고갈로 매핑이 실패할 수 있습니다.

Streaming DMA — Scatter-Gather

물리적으로 비연속인 여러 페이지를 하나의 DMA 전송으로 묶는 기법입니다. NIC의 패킷 fragment, 블록 계층의 BIO segment, Crypto API의 입출력 버퍼, GPU 커맨드 버퍼 등 거의 모든 DMA 드라이버에서 핵심 패턴으로 사용됩니다. 커널은 struct scatterliststruct sg_table을 통해 이러한 비연속 메모리 영역을 표현하고, IOMMU가 있는 경우 여러 SG 엔트리를 하나의 연속 IOVA 영역으로 병합하여 DMA 효율을 극대화합니다.

교차 참조: 네트워크 패킷의 SG 활용은 sk_buff, 블록 I/O의 SG 전달은 Block I/O, SCSI 계층의 SG 처리는 SCSI/iSCSI, GPU 버퍼의 SG 테이블은 GPU (DRM/KMS) 페이지를 참조하십시오.

struct scatterlist / struct sg_table

Scatter-gather의 기본 데이터 구조는 struct scatterlist와 이를 감싸는 struct sg_table입니다.

struct scatterlist {
    unsigned long   page_link;    /* 페이지 포인터 + bit0:chain + bit1:last */
    unsigned int    offset;       /* 페이지 내 시작 오프셋 */
    unsigned int    length;       /* 데이터 길이 (바이트) */
    dma_addr_t      dma_address;  /* DMA 매핑 후 디바이스 주소 */
    unsigned int    dma_length;   /* DMA 매핑 후 길이 (병합 시 변경) */
};

struct sg_table {
    struct scatterlist *sgl;      /* 첫 번째 scatterlist 포인터 */
    unsigned int    nents;        /* DMA 매핑 후 엔트리 수 */
    unsigned int    orig_nents;   /* 원본(매핑 전) 엔트리 수 */
};
필드설명비고
page_link페이지 구조체 포인터 + 제어 비트bit0: chain 마커, bit1: last 마커
offset페이지 내 데이터 시작 위치0 ~ PAGE_SIZE-1
length이 엔트리가 가리키는 데이터 길이CPU 관점의 원본 길이
dma_addressDMA 매핑 후 디바이스가 사용할 주소dma_map_sg() 후 유효
dma_lengthDMA 매핑 후 길이IOMMU 병합 시 length와 다를 수 있음
주의: page_link의 하위 2비트는 체이닝과 종단 마커로 사용됩니다. 절대 sg->page_link를 직접 접근하지 마십시오. 반드시 sg_page(), sg_next(), sg_is_chain(), sg_is_last() 접근자를 사용하십시오.

SG 체이닝 메커니즘

단일 scatterlist 배열의 크기는 스택이나 슬랩 할당의 제약을 받습니다. 대용량 I/O(수백~수천 세그먼트)를 위해 커널은 여러 scatterlist 배열을 연결하는 체이닝 메커니즘을 제공합니다. 배열의 마지막 엔트리에서 page_link의 bit0(chain)을 설정하고, 페이지 포인터 대신 다음 배열의 주소를 저장합니다.

struct scatterlist sg1[4], sg2[4];

/* sg1, sg2 각각 초기화 */
sg_init_table(sg1, 4);
sg_init_table(sg2, 4);

/* sg1[3]을 chain 엔트리로 설정하여 sg2를 연결 */
sg_chain(sg1, 4, sg2);

/* 이제 sg1부터 sg_next()로 sg2까지 연속 순회 가능 */
struct scatterlist *s;
int i;
for_each_sg(sg1, s, 7, i) {
    /* sg1[0..2] → sg2[0..3] 순서로 7개 순회 */
    pr_info("sg[%d]: page=%p offset=%u len=%u\n",
            i, sg_page(s), s->offset, s->length);
}
SG 체이닝: 두 배열 연결 sg1[] sg1[0] page+data sg1[1] page+data sg1[2] page+data sg1[3] CHAIN → sg2[] sg2[0] page+data sg2[1] page+data sg2[2] page+data sg2[3] LAST page_link bit0 = 1 (chain marker) page_link bit1 = 1 (last marker) sg_next() 순회: sg1[0] → sg1[1] → sg1[2] → chain → sg2[0] → sg2[1] → sg2[2] → sg2[3](last) sg1[3]은 chain 엔트리로 데이터가 아닌 다음 배열 포인터를 저장
구현 참고: sg_alloc_table()은 내부적으로 SG_MAX_SINGLE_ALLOC(보통 128) 단위로 배열을 할당하고 자동 체이닝합니다. 드라이버가 직접 sg_chain()을 호출할 일은 드뭅니다.

SG 테이블 API

Raw scatterlist[] 배열 대신 struct sg_table을 사용하면 할당, 해제, 체이닝을 자동으로 처리합니다.

항목raw scatterlist[]sg_table
할당수동 kmalloc/스택sg_alloc_table() 자동 관리
체이닝수동 sg_chain()자동 (SG_MAX_SINGLE_ALLOC 초과 시)
엔트리 수 추적별도 변수 필요nents/orig_nents 내장
해제수동 kfreesg_free_table()
최신 API 호환dma_map_sg()dma_map_sgtable()
struct sg_table sgt;
int ret;

/* 방법 1: 빈 SG 테이블 할당 후 수동 설정 */
ret = sg_alloc_table(&sgt, nfrags, GFP_KERNEL);
if (ret)
    return ret;

/* 방법 2: 페이지 배열로부터 직접 SG 테이블 생성 */
ret = sg_alloc_table_from_pages(&sgt, pages, n_pages,
                                offset, size, GFP_KERNEL);

/* 해제 */
sg_free_table(&sgt);
최적화: sg_alloc_table_from_pages()는 물리적으로 연속인 페이지들을 자동으로 하나의 SG 엔트리로 병합합니다. 예를 들어 16개 연속 페이지는 16개가 아닌 1개의 SG 엔트리로 표현되어 DMA 매핑 오버헤드를 줄입니다.

기본 SG 매핑 워크플로

SG 매핑의 기본 흐름은 5단계로 구성됩니다: (1) SG 테이블 초기화 → (2) 각 엔트리에 페이지 설정 → (3) dma_map_sg()로 DMA 매핑 → (4) for_each_sg()로 매핑 결과 순회 → (5) dma_unmap_sg()로 해제.

struct scatterlist sg[MAX_FRAGS];
int nents, mapped_nents;

sg_init_table(sg, nfrags);
for (i = 0; i < nfrags; i++)
    sg_set_page(&sg[i], pages[i], len[i], offset[i]);

/* 매핑: IOMMU가 있으면 여러 SG를 하나의 연속 IOVA로 병합 가능 */
mapped_nents = dma_map_sg(dev, sg, nfrags, DMA_FROM_DEVICE);
if (!mapped_nents) {
    dev_err(dev, "SG DMA mapping failed\n");
    return -EIO;
}

/* mapped_nents ≤ nfrags (IOMMU 병합 시 줄어듦) */
for_each_sg(sg, s, mapped_nents, i) {
    hw_desc[i].addr = sg_dma_address(s);
    hw_desc[i].len  = sg_dma_len(s);
}

/* 전송 완료 후 */
dma_unmap_sg(dev, sg, nfrags, DMA_FROM_DEVICE);
중요: dma_unmap_sg()의 세 번째 인자는 매핑 전 원본 엔트리 수(nfrags)를 전달해야 합니다. dma_map_sg()가 반환한 mapped_nents가 아닙니다. IOMMU 병합으로 반환값이 줄어들더라도 내부적으로는 원본 수만큼의 매핑을 해제해야 합니다.

dma_map_sgtable() (최신 API)

커널 5.8 이후 도입된 dma_map_sgtable()sg_table을 직접 받아 nents/orig_nents를 자동 관리하고, 오류 시 음수 errno를 반환하여 기존 API의 불편함을 해소합니다.

Legacy APIModern API차이점
dma_map_sg()dma_map_sgtable()반환값: 0=실패 vs 음수 errno
dma_unmap_sg()dma_unmap_sgtable()nents 인자 불필요
for_each_sg()for_each_sgtable_dma_sg()nents 자동 사용
별도 nents 변수 관리sg_table 내 자동 추적nents/orig_nents 혼동 방지
struct sg_table sgt;
int ret;

/* 페이지 배열로부터 SG 테이블 생성 */
ret = sg_alloc_table_from_pages(&sgt, pages, n_pages,
                                offset, size, GFP_KERNEL);
if (ret)
    return ret;

/* 최신 API로 DMA 매핑 — 음수 errno 반환 */
ret = dma_map_sgtable(dev, &sgt, DMA_BIDIRECTIONAL, 0);
if (ret) {
    dev_err(dev, "sgtable DMA map failed: %d\n", ret);
    sg_free_table(&sgt);
    return ret;
}

/* DMA 매핑된 엔트리 순회 (sgt.nents 자동 사용) */
struct scatterlist *s;
int i;
for_each_sgtable_dma_sg(&sgt, s, i) {
    hw_desc[i].addr = sg_dma_address(s);
    hw_desc[i].len  = sg_dma_len(s);
}

/* 전송 완료 후 해제 — nents 인자 불필요 */
dma_unmap_sgtable(dev, &sgt, DMA_BIDIRECTIONAL, 0);
sg_free_table(&sgt);
권장: 신규 드라이버에서는 dma_map_sgtable()/dma_unmap_sgtable()을 사용하십시오. nents 관리 오류를 원천 차단하고, 표준 errno 처리 패턴을 따릅니다.

SG 순회 매크로

SG 엔트리를 순회할 때는 CPU 관점(원본 엔트리)과 DMA 관점(매핑 후 엔트리)을 구분해야 합니다. IOMMU 병합으로 인해 두 관점의 엔트리 수가 다를 수 있습니다.

매크로관점순회 수용도
for_each_sg(sglist, sg, nr, i)수동 지정nr범용 (raw scatterlist)
for_each_sgtable_sg(&sgt, sg, i)CPUorig_nents매핑 전/후 CPU 접근
for_each_sgtable_dma_sg(&sgt, sg, i)DMAnentsHW 디스크립터 설정
for_each_sgtable_page(&sgt, piter, i)CPU페이지 단위페이지 단위 순회
/* CPU 관점: 원본 페이지에 대해 sync/접근 */
for_each_sgtable_sg(&sgt, s, i) {
    struct page *page = sg_page(s);
    void *vaddr = kmap_local_page(page);
    memset(vaddr + s->offset, 0, s->length);
    kunmap_local(vaddr);
}

/* DMA 관점: HW 디스크립터에 DMA 주소 설정 */
for_each_sgtable_dma_sg(&sgt, s, i) {
    hw_desc[i].addr = sg_dma_address(s);
    hw_desc[i].len  = sg_dma_len(s);
}
흔한 실수: HW 디스크립터에 DMA 주소를 채울 때 for_each_sgtable_sg()(CPU 관점)를 사용하면 안 됩니다. IOMMU가 병합한 경우 orig_nentsnents가 달라 디스크립터 수가 맞지 않게 됩니다. 반드시 for_each_sgtable_dma_sg()(DMA 관점)를 사용하십시오.

IOMMU SG 병합

IOMMU가 활성화된 시스템에서 dma_map_sg()는 물리적으로 비연속인 여러 페이지를 하나의 연속 IOVA(I/O Virtual Address) 영역에 매핑할 수 있습니다. 이를 SG 병합(merging)이라 하며, 디바이스에게는 하나의 큰 연속 버퍼로 보이게 합니다.

병합이 일어나면 dma_map_sg()의 반환값(mapped_nents)이 원본 엔트리 수(nfrags)보다 작아집니다. IOMMU가 없는 시스템에서는 병합이 불가능하므로 mapped_nents == nfrags가 됩니다.

IOMMU SG 병합: 비연속 물리 → 연속 IOVA 물리 메모리 (비연속) Page A 0x1000 Page B 0x5000 Page C 0x9000 Page D 0x2000 nfrags = 4 sg[0..3] IOMMU 페이지 테이블 IOVA 공간 (연속) A B C D IOVA 0xF0000 — 연속 4페이지 mapped = 1 sg_dma_len = 16KB
/* 4개의 비연속 페이지를 SG로 매핑 */
mapped = dma_map_sg(dev, sg, 4, DMA_TO_DEVICE);

/* IOMMU 있는 시스템: mapped = 1 (4페이지 → 1개 연속 IOVA) */
/* IOMMU 없는 시스템: mapped = 4 (각 페이지 독립) */

for_each_sg(sg, s, mapped, i) {
    pr_info("DMA seg[%d]: addr=0x%llx len=%u\n",
            i, sg_dma_address(s), sg_dma_len(s));
}
/* IOMMU: DMA seg[0]: addr=0xf0000 len=16384 */
/* 직접: DMA seg[0..3]: 각각 4096 */

오류 처리 패턴

SG DMA 매핑은 IOVA 공간 고갈, 메모리 부족 등으로 실패할 수 있습니다. Legacy API와 Modern API의 오류 반환 방식이 다르므로 주의해야 합니다.

struct sg_table sgt;
int ret;

/* 1. SG 테이블 할당 */
ret = sg_alloc_table_from_pages(&sgt, pages, n_pages,
                                0, total_size, GFP_KERNEL);
if (ret)
    return ret;

/* 2. DMA 매핑 (최신 API) */
ret = dma_map_sgtable(dev, &sgt, DMA_FROM_DEVICE, 0);
if (ret)
    goto err_free_sgt;

/* 3. HW 디스크립터 설정 및 전송 */
setup_hw_descriptors(&sgt);
ret = start_dma_transfer(dev);
if (ret)
    goto err_unmap;

/* 4. 전송 완료 대기 후 정리 */
wait_for_completion(&done);

err_unmap:
    dma_unmap_sgtable(dev, &sgt, DMA_FROM_DEVICE, 0);
err_free_sgt:
    sg_free_table(&sgt);
    return ret;
필수: DMA 매핑은 부분 성공이 없습니다 — 전체 성공 또는 전체 실패입니다. 실패 시에도 sg_free_table()은 반드시 호출해야 합니다. DMA 매핑 실패 시 dma_unmap_*은 호출하지 않습니다.

성능 고려사항

SG DMA의 성능은 세그먼트 수, HW 제한, IOMMU 설정에 크게 영향받습니다.

파라미터의미확인 방법
max_segments디바이스가 처리 가능한 최대 SG 수dma_get_max_seg_count(dev)
max_segment_size단일 SG 엔트리의 최대 크기dma_get_max_seg_size(dev)
dma_max_mapping_size()단일 매핑의 최대 총 크기SWIOTLB 제한 포함
SG_MAX_SINGLE_ALLOC체이닝 없이 할당 가능한 SG 수보통 128 (PAGE_SIZE / sizeof(sg))
성능 최적화 지침:
  • SG 수 최소화: sg_alloc_table_from_pages()로 연속 페이지를 자동 병합하여 SG 엔트리를 줄이십시오.
  • HW 제한 확인: max_segments/max_segment_size를 초과하면 매핑이 실패합니다. 블록 계층은 이를 자동 분할하지만 직접 호출 시 직접 확인해야 합니다.
  • IOTLB 압박 방지: 과도한 SG 매핑은 IOMMU의 IOTLB(TLB) 미스를 증가시킵니다. 가능하면 매핑을 재사용하십시오.
  • 매핑 재사용: 반복 전송 시 dma_sync_sg_for_cpu()/dma_sync_sg_for_device()로 기존 매핑을 재사용하면 map/unmap 오버헤드를 피할 수 있습니다.
  • Atomic 컨텍스트 주의: dma_map_sg()는 IOMMU 페이지 테이블 잠금을 획득합니다. 대량 SG 매핑은 인터럽트 컨텍스트에서 지연을 유발할 수 있습니다.

DMA Pool

고정 크기 소형 coherent 버퍼를 효율적으로 반복 할당/해제할 때 사용합니다. 내부적으로 dma_alloc_coherent()에서 큰 블록을 할당 후 분할합니다.

struct dma_pool *pool;
dma_addr_t dma_handle;
void *buf;

/* 풀 생성: 256바이트 블록, 16바이트 정렬, 4KB 경계 내 */
pool = dma_pool_create("my_desc", dev, 256, 16, 4096);

/* 풀에서 할당 */
buf = dma_pool_alloc(pool, GFP_KERNEL, &dma_handle);

/* 반환 */
dma_pool_free(pool, buf, dma_handle);

/* 풀 해제 */
dma_pool_destroy(pool);

Non-coherent DMA

대용량 버퍼에서 캐시를 명시적으로 제어하여 성능을 최적화할 때 사용합니다:

/* 비캐시-일관 할당: 아키텍처에 따라 uncached 또는 write-combine */
cpu_addr = dma_alloc_noncoherent(dev, size, &dma_handle,
                                   DMA_FROM_DEVICE, GFP_KERNEL);

/* CPU가 읽기 전에 캐시 무효화 */
dma_sync_single_for_cpu(dev, dma_handle, size, DMA_FROM_DEVICE);

/* 디바이스에 다시 넘기기 전에 캐시 플러시 */
dma_sync_single_for_device(dev, dma_handle, size, DMA_FROM_DEVICE);

dma_free_noncoherent(dev, size, cpu_addr, dma_handle, DMA_FROM_DEVICE);

DMA 방향과 캐시 일관성

DMA 방향 상수

상수데이터 흐름map 시 캐시 동작unmap 시 캐시 동작
DMA_TO_DEVICECPU → 디바이스캐시 → 메모리 flush(없음)
DMA_FROM_DEVICE디바이스 → CPU캐시 라인 invalidate캐시 라인 invalidate
DMA_BIDIRECTIONAL양방향flush + invalidateinvalidate
DMA_NONE디버깅 전용
성능 팁: DMA_BIDIRECTIONAL은 flush + invalidate를 모두 수행하므로 오버헤드가 큽니다. 가능하면 정확한 방향을 지정하십시오.

아키텍처별 캐시 일관성 모델

아키텍처DMA 캐시 일관성설명
x86/x86_64HW coherent스누프 프로토콜이 캐시 일관성을 자동 보장. sync API는 no-op (컴파일러 배리어만)
ARM (non-coherent)SW managedflush/invalidate 필수. dma_sync_* 호출 시 실제 캐시 유지보수 명령 실행
ARM (coherent)HW coherentCCI/CHI 인터커넥트가 IO-coherent 포트 제공 시
RISC-V플랫폼 의존SiFive U74는 non-coherent, 향후 IOPMP/IOMMU로 개선
DMA_TO_DEVICE CPU Cache RAM flush map 시: dirty 캐시 → RAM에 기록 DMA_FROM_DEVICE CPU Cache RAM invalidate unmap 시: 오래된 캐시 라인 무효화 재사용 패턴 (unmap 없이) map 디바이스 전송 sync_for_cpu CPU 데이터 접근 sync_for_device 반복

Sync API 상세

Streaming 매핑을 unmap하지 않고 CPU ↔ 디바이스 간 소유권을 전환할 때 사용합니다:

/* 수신 링 버퍼 — 반복 사용 패턴 */
dma_addr = dma_map_single(dev, buf, len, DMA_FROM_DEVICE);

while (running) {
    /* 디바이스가 데이터 기록 완료 (인터럽트 등) */
    dma_sync_single_for_cpu(dev, dma_addr, len, DMA_FROM_DEVICE);

    /* CPU가 buf의 데이터를 읽음 */
    process_data(buf, len);

    /* 다시 디바이스에 소유권 넘김 */
    dma_sync_single_for_device(dev, dma_addr, len, DMA_FROM_DEVICE);

    /* 디바이스에 버퍼 재사용 통보 */
    notify_device_reuse(dma_addr);
}

dma_unmap_single(dev, dma_addr, len, DMA_FROM_DEVICE);

IOMMU 심화

IOMMU(Input/Output Memory Management Unit)는 디바이스의 DMA 요청에 대해 가상→물리 주소 변환을 수행하고, 디바이스 간 메모리 접근을 격리합니다.

주요 IOMMU 구현

구현벤더커널 소스특징
VT-dInteldrivers/iommu/intel/DMAR 테이블, 2레벨/확장 페이지 테이블, Interrupt Remapping
AMD-ViAMDdrivers/iommu/amd/IVRS 테이블, v2 페이지 테이블 (중첩 페이지 테이블 공유)
SMMU v3ARMdrivers/iommu/arm/arm-smmu-v3/STE/CD 2단계, Stage 1+2, HTTU, MSI doorbell

IOMMU 동작 모드

모드커널 파라미터동작사용 시나리오
Passthroughiommu.passthrough=1주소 변환 없음 (DMA = PA)성능 최우선, 신뢰된 디바이스
DMA API (lazy)iommu=lazyIOVA 해제 지연 (배치 flush)기본 모드, 성능과 격리의 균형
DMA API (strict)iommu=strictunmap 즉시 IOTLB flush보안 최우선, 기밀 컴퓨팅
VFIO (user)VFIO 드라이버유저스페이스가 매핑 제어DPDK, GPU passthrough
Root Complex + IOMMU IOMMU 페이지 테이블 Root Entry → Context Entry → PML4 → PDP → PD → PT (IOVA → PA 변환) 시스템 RAM IOMMU 그룹 1 NIC (0000:03:00.0) NIC (0000:03:00.1) IOMMU 그룹 2 GPU (0000:01:00.0) IOMMU 그룹 3 NVMe (0000:04:00.0) 고급 기능 ATS: 디바이스 TLB 캐시 PRI: 페이지 폴트 요청 | PASID: 프로세스별 주소 공간

IOMMU 그룹과 격리

IOMMU 그룹은 하드웨어적으로 격리 가능한 최소 단위입니다. 같은 그룹 내 디바이스는 서로의 DMA를 볼 수 있으므로 VFIO passthrough 시 그룹 단위로 할당해야 합니다.

/* IOMMU 그룹 조회 */
$ ls /sys/kernel/iommu_groups/*/devices/
/* 출력 예시 */
/sys/kernel/iommu_groups/1/devices/0000:03:00.0
/sys/kernel/iommu_groups/1/devices/0000:03:00.1
/sys/kernel/iommu_groups/2/devices/0000:01:00.0

/* ACS(Access Control Services) 확인 — 격리 수준 판단 */
$ lspci -vvs 03:00.0 | grep -i acs

ATS, PRI, PASID

기능PCIe 확장역할
ATSAddress Translation Service디바이스가 IOMMU에 변환 요청, 결과를 디바이스 TLB에 캐시
PRIPage Request Interface디바이스가 페이지 폴트를 IOMMU에 보고, 온디맨드 매핑
PASIDProcess Address Space ID디바이스가 프로세스별 주소 공간을 구분, SVA(Shared Virtual Addressing) 지원
/* SVA(Shared Virtual Addressing) — 유저 가상 주소를 디바이스가 직접 사용 */
#include <linux/iommu.h>

struct iommu_sva *sva;

/* 디바이스를 현재 프로세스 주소 공간에 바인딩 */
sva = iommu_sva_bind_device(dev, current->mm);
pasid = iommu_sva_get_pasid(sva);

/* 디바이스에 PASID와 유저 VA 전달 → 디바이스가 직접 접근 */
hw_submit_work(pasid, user_va, len);

/* 언바인드 */
iommu_sva_unbind_device(sva);

SWIOTLB 바운스 버퍼

SWIOTLB(Software I/O TLB)는 DMA 주소 제한을 소프트웨어적으로 해결하는 바운스 버퍼 메커니즘입니다.

사용 조건

조건설명
DMA 마스크 부족디바이스가 32비트 DMA만 지원하지만 버퍼가 4GB 이상에 있을 때
IOMMU 없음IOMMU가 없는 시스템에서 주소 변환 불가 시
기밀 컴퓨팅AMD SEV, Intel TDX에서 암호화된 게스트 메모리 ↔ 디바이스 간 전송
원본 버퍼 PA > 4GB (high) 바운스 버퍼 PA < 4GB (SWIOTLB) 디바이스 32비트 DMA만 지원 ① memcpy ② DMA ③ memcpy (sync) TO_DEVICE: 원본→바운스(복사) → 바운스→디바이스(DMA) FROM_DEVICE: 디바이스→바운스(DMA) → 바운스→원본(복사) 이중 복사로 인한 성능 저하 → 가능하면 IOMMU 사용 권장

설정과 모니터링

/* 부팅 파라미터 */
swiotlb=65536        /* 슬랩 수 (기본 64MB = 32768 × 2KB) */
swiotlb=force         /* 강제 사용 (디버깅) */
swiotlb=noforce       /* 비활성화 */

/* 모니터링 */
$ cat /sys/kernel/debug/swiotlb/io_tlb_nslabs   /* 전체 슬랩 수 */
$ cat /sys/kernel/debug/swiotlb/io_tlb_used      /* 사용 중 슬랩 수 */
$ dmesg | grep -i swiotlb                         /* 부팅 로그 확인 */

기밀 컴퓨팅과 SWIOTLB

AMD SEV/Intel TDX 환경에서는 게스트 메모리가 암호화되어 있어 디바이스가 직접 접근할 수 없습니다. SWIOTLB가 공유(비암호화) 바운스 버퍼를 통해 데이터를 중계합니다:

/* TDX/SEV에서 SWIOTLB 동작 흐름 */
/* 1. 바운스 버퍼는 shared(비암호화) 메모리 영역에 할당 */
/* 2. CPU: 암호화된 원본 → 복호화 → 바운스 버퍼에 복사 */
/* 3. 디바이스: 바운스 버퍼에서 DMA 읽기 */
/* 4. 수신: 디바이스 → 바운스 → 암호화하여 원본에 복사 */

CMA (Contiguous Memory Allocator)

CMA는 물리적으로 연속된 대용량 메모리를 런타임에 할당할 수 있게 하는 메커니즘입니다. IOMMU가 없는 임베디드 시스템에서 DMA 버퍼 할당에 특히 중요합니다.

CMA와 DMA의 관계

IOMMU가 없으면 물리적으로 연속된 메모리가 DMA에 필수입니다. CMA는 부팅 시 예약된 영역을 평상시 이동 가능(movable) 페이지로 사용하다가, DMA 요청 시 페이지를 이주(migrate)시키고 연속 블록을 확보합니다.

/* DMA 할당이 CMA를 자동 사용하는 경로 */
dma_alloc_coherent(dev, size, &handle, GFP_KERNEL)
  → dma_alloc_attrs()
    → dma_direct_alloc()  /* IOMMU 없는 경우 */dma_alloc_from_contiguous()
        → cma_alloc()  /* CMA 영역에서 할당 */

CMA 설정

/* 커널 부팅 파라미터 */
cma=256M              /* 기본 CMA 영역 크기 */
cma=256M@0-4G        /* 4GB 이하에 256MB 예약 (32비트 DMA) */

/* Device Tree 설정 (ARM/임베디드) */
reserved-memory {
    #address-cells = <1>;
    #size-cells = <1>;
    ranges;

    linux,cma {
        compatible = "shared-dma-pool";
        reusable;
        size = <0x10000000>;  /* 256MB */
        linux,cma-default;
    };

    /* 디바이스 전용 CMA */
    gpu_cma: gpu_reserved {
        compatible = "shared-dma-pool";
        reusable;
        reg = <0x50000000 0x8000000>;  /* 128MB at 1.25GB */
    };
};

gpu@10000000 {
    memory-region = <&gpu_cma>;  /* 디바이스별 CMA 연결 */
};
/* CMA 상태 모니터링 */
$ cat /proc/meminfo | grep Cma
CmaTotal:         262144 kB
CmaFree:          245760 kB

$ cat /sys/kernel/debug/cma/cma-*/count   /* 할당 횟수 */
$ cat /sys/kernel/debug/cma/cma-*/used    /* 사용 중 페이지 */

DMA-BUF 버퍼 공유

DMA-BUF는 서로 다른 디바이스 드라이버 간 DMA 버퍼를 파일 디스크립터(fd)로 공유하는 커널 프레임워크입니다. GPU 렌더링 결과를 디스플레이 컨트롤러·비디오 인코더·카메라와 제로카피로 공유하는 데 핵심적입니다.

Exporter (GPU) dma_buf_export() DMA-BUF struct dma_buf fd를 통해 전달 Display Controller Video Encoder dma_fence + dma_resv implicit sync: GPU 작업 완료 대기 exclusive fence (쓰기) / shared fence (읽기) DMA-BUF Heaps system / cma / secure heaps 유저스페이스 할당 /dev/dma_heap/{system,cma}

Exporter: dma_buf_ops

#include <linux/dma-buf.h>

static const struct dma_buf_ops my_dmabuf_ops = {
    .attach      = my_attach,       /* importer 연결 시 */
    .detach      = my_detach,
    .map_dma_buf = my_map_dma_buf,  /* SG 테이블 제공 */
    .unmap_dma_buf = my_unmap_dma_buf,
    .release     = my_release,      /* 마지막 참조 해제 */
    .mmap        = my_mmap,         /* 유저스페이스 mmap */
    .vmap        = my_vmap,         /* 커널 가상 매핑 */
};

/* DMA-BUF 내보내기 */
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
exp_info.ops  = &my_dmabuf_ops;
exp_info.size = buf_size;
exp_info.priv = my_private_data;

struct dma_buf *dmabuf = dma_buf_export(&exp_info);
int fd = dma_buf_fd(dmabuf, O_CLOEXEC);  /* fd로 변환 → 유저스페이스에 전달 */

Importer: 버퍼 가져오기

/* fd에서 dma_buf 획득 */
struct dma_buf *dmabuf = dma_buf_get(fd);

/* 디바이스에 연결 */
struct dma_buf_attachment *attach = dma_buf_attach(dmabuf, dev);

/* SG 테이블 획득 — 디바이스가 DMA로 접근할 주소 */
struct sg_table *sgt = dma_buf_map_attachment(attach, DMA_FROM_DEVICE);

/* 디바이스에 SG 주소 프로그래밍, DMA 전송 수행 */

/* 해제 */
dma_buf_unmap_attachment(attach, sgt, DMA_FROM_DEVICE);
dma_buf_detach(dmabuf, attach);
dma_buf_put(dmabuf);

dma_fence와 dma_resv

dma_fence는 비동기 GPU/DMA 작업의 완료를 시그널링하는 동기화 프리미티브입니다. dma_resv(reservation object)는 DMA-BUF당 하나씩 내장되어 있으며, 여러 fence를 관리합니다.

/* fence 대기 */
struct dma_resv *resv = dmabuf->resv;

/* 모든 exclusive fence 대기 (쓰기 완료 대기) */
ret = dma_resv_wait_timeout(resv, DMA_RESV_USAGE_WRITE,
                             true, MAX_SCHEDULE_TIMEOUT);

/* implicit sync: 커널이 자동으로 fence를 삽입/확인 */
/* explicit sync: DRM_IOCTL_SYNCOBJ_* 등으로 유저스페이스가 직접 관리 */

DMA-BUF Heaps

유저스페이스에서 직접 DMA 가능한 버퍼를 할당하는 인터페이스입니다. Android ION 할당자의 후속:

/* 유저스페이스 예시 */
int heap_fd = open("/dev/dma_heap/system", O_RDWR);
struct dma_heap_allocation_data data = {
    .len = 4096,
    .fd_flags = O_CLOEXEC | O_RDWR,
};
ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data);
/* data.fd — DMA-BUF fd를 받음 */

Android: ION에서 DMA-BUF Heaps로

Android는 GPU, 카메라, 디스플레이 등 다중 디바이스 간 버퍼 공유를 위해 독자적인 ION allocator를 사용했다. ION은 힙 유형별(system, CMA, carveout 등) 메모리 할당과 캐시 관리를 제공했으나, 메인라인에 통합되지 못하고 staging에 머물렀다.

커널 5.6부터 ION의 기능을 메인라인 DMA-BUF Heaps 프레임워크로 대체하기 시작했으며, 커널 5.11에서 ION은 완전히 제거되었다. DMA-BUF Heaps는 ION 대비 깔끔한 API와 메인라인 지원이라는 장점이 있다.

특성ION (deprecated)DMA-BUF Heaps
인터페이스/dev/ion + ioctl/dev/dma_heap/* + ioctl
커널 위치staging/android/iondrivers/dma-buf/dma-heap
메인라인staging (미통합)완전 통합 (5.6+)
힙 유형system, cma, carveout 등system, cma (확장 가능)
Android HALGralloc 2.0Gralloc 4.0+ (mapper)
/* Android HAL에서의 DMA-BUF Heaps 사용 (Gralloc) */
/* 카메라 HAL이 DMA-BUF를 할당하고, GPU/디스플레이와 공유 */
int heap_fd = open("/dev/dma_heap/system", O_RDWR);
struct dma_heap_allocation_data alloc = { .len = frame_size, .fd_flags = O_RDWR | O_CLOEXEC };
ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &alloc);
/* alloc.fd를 Binder로 GPU/Display HAL에 전달 → 제로카피 공유 */

Android의 DMA-BUF 활용, Gralloc HAL 구조, 카메라/GPU 버퍼 공유 등 심화 내용은 Android 커널 — HAL과 커널 인터페이스를 참고하라.

P2P DMA (Peer-to-Peer DMA)

PCIe Peer-to-Peer DMA는 두 PCIe 디바이스 간 CPU/시스템 메모리를 거치지 않고 직접 데이터를 전송합니다.

사용 사례

사용 사례설명
GPUDirect RDMAGPU VRAM ↔ NIC 간 직접 전송 (NVIDIA, AMD ROCm)
GPUDirect StorageNVMe ↔ GPU VRAM 간 직접 전송
NVMe-oF P2PNVMe 디바이스 간 타깃 오프로드
FPGA ↔ GPU데이터 파이프라인 가속

pci_p2pdma API

#include <linux/pci-p2pdma.h>

/* P2P DMA 가능 여부 확인 */
int dist = pci_p2pdma_distance(provider, client, true);
if (dist < 0) {
    dev_warn(dev, "P2P DMA not supported between devices\n");
    return -ENODEV;
}

/* P2P 메모리 할당 (provider의 BAR에서) */
void *p2p_buf = pci_alloc_p2pmem(provider, size);

/* SG 테이블에 P2P 페이지 설정 */
sg_set_page(&sg[0], virt_to_page(p2p_buf), size, 0);
mapped = dma_map_sg(client_dev, sg, 1, DMA_BIDIRECTIONAL);

/* 해제 */
pci_free_p2pmem(provider, p2p_buf, size);
제약: P2P DMA는 같은 PCIe Root Complex 아래에 있는 디바이스 간에만 동작합니다. 다른 Root Complex를 거치면(CPU 소켓 간) 일반적으로 실패합니다. ACS(Access Control Services) 설정도 확인해야 합니다.

DMA 보안

DMA 공격 벡터

공격설명방어
DMA Attack악의적 디바이스(Thunderbolt/PCIe)가 시스템 메모리 전체에 접근IOMMU 활성화
TOCTOUCPU가 DMA 버퍼 검증 후, DMA가 내용을 변경바운스 버퍼 사용
Iago Attack악의적 하이퍼바이저가 DMA 매핑 결과 조작기밀 컴퓨팅(SEV/TDX)

IOMMU 보호

IOMMU는 DMA 보안의 핵심입니다. 디바이스가 접근 가능한 메모리 영역을 제한합니다:

/* IOMMU strict 모드 강제 (보안 최우선) */
/* 커널 파라미터: iommu.passthrough=0 iommu.strict=1 */

/* Thunderbolt/외장 디바이스 연결 시 IOMMU 보호 확인 */
$ dmesg | grep -i iommu
[    0.123] DMAR: IOMMU enabled
[    1.456] thunderbolt 0-1: IOMMU DMA protection enabled

Restricted DMA (Device Tree)

IOMMU가 없는 임베디드 시스템에서 restricted-dma-pool을 사용하여 디바이스 DMA 접근 범위를 제한합니다:

reserved-memory {
    restricted_dma: restricted-dma {
        compatible = "restricted-dma-pool";
        size = <0x0 0x400000>;   /* 4MB */
    };
};

pcie@10000000 {
    memory-region = <&restricted_dma>;
    /* 이 디바이스의 DMA는 지정된 영역으로 제한 */
};

DMA 디버깅과 최적화

DMA_API_DEBUG

커널 빌드 시 CONFIG_DMA_API_DEBUG=y를 활성화하면 DMA API 오용을 런타임에 탐지합니다:

/* 탐지하는 오류 유형 */
/* - map/unmap 짝 불일치 */
/* - 이미 해제된 DMA 매핑에 접근 */
/* - 잘못된 방향(direction) 사용 */
/* - sync 호출 없이 CPU/디바이스 간 전환 */
/* - dma_mapping_error() 미확인 */

/* 활성화 */
$ echo 1 > /sys/kernel/debug/dma-api/enable

/* 로그 확인 */
$ dmesg | grep DMA-API
DMA-API: device driver tries to free DMA memory it has not allocated

/* 매핑 통계 */
$ cat /sys/kernel/debug/dma-api/num_errors
$ cat /sys/kernel/debug/dma-api/driver_filter  /* 특정 드라이버만 추적 */

성능 최적화 가이드

기법효과적용 방법
IOMMU lazy 모드IOTLB flush 배치 처리iommu=lazy (기본값)
대형 IOVA 할당TLB 효율 향상2MB/1GB hugepage DMA 할당
SG 병합DMA 엔트리 수 감소IOMMU가 자동 병합, max_segment_size 확인
DMA Pool반복 할당 오버헤드 제거고정 크기 디스크립터에 dma_pool 사용
Streaming 재사용map/unmap 횟수 감소dma_sync_*로 소유권만 전환
정확한 방향 지정불필요한 캐시 유지보수 제거DMA_BIDIRECTIONAL 피하기

핵심 주의사항 10가지

  1. dma_mapping_error() 필수 확인 — map 후 반드시 오류 검사, IOVA 고갈 가능
  2. DMA 마스크 먼저 설정dma_set_mask_and_coherent()를 probe 초기에 호출
  3. 방향 일치 — map과 unmap의 direction이 반드시 동일해야 함
  4. 크기 일치 — map과 unmap의 size가 반드시 동일해야 함
  5. 매핑 중 CPU 접근 금지 — streaming 매핑 후 sync 없이 CPU가 접근하면 stale data
  6. kmalloc 사용 — streaming DMA에 stack/global 변수 사용 금지 (캐시라인 공유 문제)
  7. SG unmap에 원본 nents 전달dma_unmap_sg()에는 mapped_nents가 아닌 원본 nents 전달
  8. coherent 크기 제한dma_alloc_coherent()는 수 MB 이내, 대용량은 CMA 확인
  9. 바운스 버퍼 성능 저하 — SWIOTLB 사용 시 이중 복사, dmesg로 경고 확인
  10. IOMMU 그룹 인식 — VFIO passthrough 시 그룹 단위 할당 필수

실전 예제

네트워크 드라이버 — Ring Buffer DMA

고성능 NIC 드라이버의 전형적인 DMA 패턴입니다:

struct my_ring {
    struct my_desc *desc;       /* coherent: 디스크립터 링 */
    dma_addr_t      desc_dma;   /* 디스크립터 링 DMA 주소 */
    struct my_buf  *buf;        /* 패킷 버퍼 메타 정보 */
    int             count;
};

/* 초기화: 디스크립터 링은 coherent 할당 */
static int my_ring_alloc(struct device *dev, struct my_ring *ring)
{
    size_t desc_size = ring->count * sizeof(struct my_desc);

    ring->desc = dma_alloc_coherent(dev, desc_size,
                                     &ring->desc_dma, GFP_KERNEL);
    if (!ring->desc)
        return -ENOMEM;

    ring->buf = kcalloc(ring->count, sizeof(*ring->buf), GFP_KERNEL);
    if (!ring->buf) {
        dma_free_coherent(dev, desc_size, ring->desc, ring->desc_dma);
        return -ENOMEM;
    }
    return 0;
}

/* 수신: 패킷 버퍼는 streaming 매핑 */
static int my_rx_alloc_buf(struct device *dev, struct my_ring *ring, int idx)
{
    struct page *page = alloc_page(GFP_KERNEL);
    dma_addr_t dma;

    if (!page)
        return -ENOMEM;

    dma = dma_map_page(dev, page, 0, PAGE_SIZE, DMA_FROM_DEVICE);
    if (dma_mapping_error(dev, dma)) {
        __free_page(page);
        return -EIO;
    }

    ring->buf[idx].page = page;
    ring->buf[idx].dma  = dma;

    /* coherent 디스크립터에 DMA 주소 기록 */
    ring->desc[idx].addr = cpu_to_le64(dma);
    ring->desc[idx].len  = cpu_to_le16(PAGE_SIZE);
    return 0;
}

/* NAPI 수신 처리 */
static int my_rx_poll(struct napi_struct *napi, int budget)
{
    struct my_ring *ring = container_of(napi, ...);
    int processed = 0;

    while (processed < budget) {
        struct my_desc *desc = &ring->desc[ring->next];
        if (!(desc->status & DESC_DONE))
            break;

        /* CPU가 읽기 전 sync */
        dma_sync_single_for_cpu(dev, ring->buf[ring->next].dma,
                                PAGE_SIZE, DMA_FROM_DEVICE);

        /* 패킷 데이터 처리 후 디바이스에 반환 */
        dma_sync_single_for_device(dev, ring->buf[ring->next].dma,
                                   PAGE_SIZE, DMA_FROM_DEVICE);
        processed++;
    }
    return processed;
}

블록 디바이스 — Scatter-Gather DMA

static blk_status_t my_blk_submit(struct blk_mq_hw_ctx *hctx,
                                     const struct blk_mq_queue_data *bd)
{
    struct request *rq = bd->rq;
    struct scatterlist *sg;
    struct req_iterator iter;
    struct bio_vec bvec;
    int nents = 0, mapped;

    /* bio에서 SG 리스트 구성 */
    sg_init_table(sg_list, MAX_SG);
    rq_for_each_segment(bvec, rq, iter) {
        sg_set_page(&sg_list[nents], bvec.bv_page,
                    bvec.bv_len, bvec.bv_offset);
        nents++;
    }

    /* DMA 매핑 */
    mapped = dma_map_sg(dev, sg_list, nents,
                        rq_data_dir(rq) == WRITE ?
                        DMA_TO_DEVICE : DMA_FROM_DEVICE);
    if (!mapped)
        return BLK_STS_IOERR;

    /* 하드웨어 SG 디스크립터 프로그래밍 */
    for_each_sg(sg_list, sg, mapped, i) {
        hw_desc[i].addr = sg_dma_address(sg);
        hw_desc[i].len  = sg_dma_len(sg);
    }

    start_hw_transfer(hw_desc, mapped);
    return BLK_STS_OK;
}

디바이스 probe — DMA 설정 패턴

static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct device *dev = &pdev->dev;
    int ret;

    /* ① PCI 활성화 + 버스마스터 설정 */
    ret = pcim_enable_device(pdev);
    if (ret)
        return ret;
    pci_set_master(pdev);

    /* ② DMA 마스크 설정 */
    ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
    if (ret) {
        ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
        if (ret)
            return ret;
    }

    /* ③ Coherent DMA 할당 (디스크립터 링) */
    priv->desc_ring = dma_alloc_coherent(dev, DESC_RING_SIZE,
                                          &priv->desc_dma, GFP_KERNEL);
    if (!priv->desc_ring)
        return -ENOMEM;

    /* ④ DMA Pool 생성 (소형 명령 버퍼) */
    priv->cmd_pool = dma_pool_create("my_cmd", dev, 64, 64, 0);
    if (!priv->cmd_pool) {
        dma_free_coherent(dev, DESC_RING_SIZE,
                          priv->desc_ring, priv->desc_dma);
        return -ENOMEM;
    }

    /* ⑤ 디바이스 초기화 및 인터럽트 등록 */
    ...
    return 0;
}

커널 설정 종합 (Kconfig)

DMA 관련 Kconfig 옵션

옵션기본값역할
CONFIG_HAS_DMAy (자동)DMA 지원 가능 플랫폼
CONFIG_DMA_API_DEBUGnDMA API 오용 런타임 탐지
CONFIG_DMA_API_DEBUG_SGnSG 매핑 추가 검증
CONFIG_SWIOTLBy (x86_64)SWIOTLB 바운스 버퍼
CONFIG_DMA_CMAy (ARM)CMA 기반 DMA 할당
CONFIG_CMA_SIZE_MBYTES0~256기본 CMA 크기 (MB)
CONFIG_IOMMU_SUPPORTyIOMMU 프레임워크
CONFIG_IOMMU_DEFAULT_DMA_LAZYyIOMMU 기본 lazy 모드
CONFIG_IOMMU_DEFAULT_PASSTHROUGHnIOMMU 기본 passthrough 모드
CONFIG_INTEL_IOMMUy (Intel)Intel VT-d 드라이버
CONFIG_AMD_IOMMUy (AMD)AMD-Vi 드라이버
CONFIG_ARM_SMMU_V3y (ARM64)ARM SMMUv3 드라이버
CONFIG_IOMMU_SVAnSVA(Shared Virtual Addressing) 지원
CONFIG_DMA_SHARED_BUFFERyDMA-BUF 프레임워크
CONFIG_DMABUF_HEAPSnDMA-BUF Heaps 유저스페이스 할당
CONFIG_DMABUF_HEAPS_SYSTEMnSystem heap
CONFIG_DMABUF_HEAPS_CMAnCMA heap
CONFIG_PCI_P2PDMAnPCIe P2P DMA 지원
CONFIG_DMA_RESTRICTED_POOLnRestricted DMA 보안 풀