DMA 심화 (Direct Memory Access)
Linux 커널 DMA 서브시스템 종합 가이드: 매핑 API(Coherent/Streaming/Scatter-Gather/Pool), IOMMU(VT-d/AMD-Vi/SMMU), SWIOTLB, CMA, DMA-BUF 버퍼 공유, P2P DMA, 캐시 일관성, 보안, 디버깅.
핵심 요약
- DMA — CPU 개입 없이 디바이스가 메모리에 직접 읽기/쓰기하는 메커니즘으로, 시스템 처리량을 크게 향상시킵니다.
- Coherent vs Streaming — Coherent DMA는 CPU-디바이스 간 항상 동기화, Streaming은 명시적 sync가 필요하지만 더 효율적입니다.
- IOMMU — 디바이스의 DMA 주소를 물리 주소로 변환하는 하드웨어(VT-d, AMD-Vi, SMMU)입니다.
- DMA-BUF — GPU, 카메라 등 여러 디바이스 간에 DMA 버퍼를 공유하는 프레임워크입니다.
단계별 이해
- DMA가 필요한 이유 — CPU가 바이트 단위로 데이터를 복사(PIO)하면 CPU 시간을 낭비합니다.
네트워크 카드, 디스크 컨트롤러 등 대용량 전송 디바이스에서 DMA가 필수적입니다.
- DMA 매핑 이해 — 디바이스가 접근할 "DMA 주소"를 할당하는 과정입니다.
dma_alloc_coherent()또는dma_map_single()을 사용합니다.IOMMU가 있으면 DMA 주소 ≠ 물리 주소일 수 있습니다.
- 캐시 일관성 — 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 DMA | 8237A DMA 컨트롤러 | 24비트 주소(16MB), 4/8채널, CPU가 컨트롤러 프로그래밍 |
| PCI 버스마스터 | 디바이스가 버스 마스터 | 32/64비트 주소, 디바이스가 직접 메모리 접근 |
| PCIe + IOMMU | TLP 기반 메모리 Read/Write | 가상 주소 변환, 디바이스 격리, ATS/PRI/PASID |
커널 소스 트리 구조
| 경로 | 역할 |
|---|---|
kernel/dma/ | DMA 매핑 코어: mapping.c, direct.c, swiotlb.c, pool.c, contiguous.c |
include/linux/dma-mapping.h | DMA 매핑 API 헤더 |
include/linux/dma-map-ops.h | dma_map_ops 백엔드 인터페이스 |
drivers/iommu/ | IOMMU 프레임워크: intel/, amd/, arm-smmu-v3/ |
drivers/dma-buf/ | DMA-BUF 버퍼 공유 프레임워크 |
drivers/dma/ | DMA 엔진(slave DMA 컨트롤러) 드라이버 |
DMA 주소 공간과 매핑
DMA 프로그래밍에서 가장 혼란스러운 부분은 여러 주소 공간이 동시에 존재한다는 점입니다.
주소 유형
| 주소 유형 | C 타입 | 의미 | 변환 관계 |
|---|---|---|---|
| 가상 주소 | void * | CPU MMU를 통한 커널 가상 주소 | virt_to_phys() → 물리 |
| 물리 주소 | phys_addr_t | CPU가 보는 실제 RAM 주소 | 항상 고정 |
| 버스(DMA) 주소 | dma_addr_t | 디바이스가 보는 주소 | IOMMU 없으면 = 물리, 있으면 IOVA |
virt_to_bus()/bus_to_virt()는 폐기된 API입니다. 반드시 dma_map_*() API를 사용하십시오. 이 API만이 IOMMU, SWIOTLB, CMA 등 모든 백엔드를 올바르게 처리합니다.DMA 존(Zone)과 주소 마스크
| Zone | x86_64 범위 | 용도 |
|---|---|---|
ZONE_DMA | 0 ~ 16MB | ISA DMA 레거시 디바이스 (24비트) |
ZONE_DMA32 | 0 ~ 4GB | 32비트 DMA 디바이스 |
ZONE_NORMAL | 4GB 이상 | 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 단위) | 명시적 sync | Scatter-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 scatterlist와 struct sg_table을 통해 이러한 비연속 메모리 영역을 표현하고, IOMMU가 있는 경우 여러 SG 엔트리를 하나의 연속 IOVA 영역으로 병합하여 DMA 효율을 극대화합니다.
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_address | DMA 매핑 후 디바이스가 사용할 주소 | dma_map_sg() 후 유효 |
dma_length | DMA 매핑 후 길이 | 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_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 내장 |
| 해제 | 수동 kfree | sg_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 API | Modern 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) | CPU | orig_nents | 매핑 전/후 CPU 접근 |
for_each_sgtable_dma_sg(&sgt, sg, i) | DMA | nents | HW 디스크립터 설정 |
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);
}
for_each_sgtable_sg()(CPU 관점)를 사용하면 안 됩니다. IOMMU가 병합한 경우 orig_nents와 nents가 달라 디스크립터 수가 맞지 않게 됩니다. 반드시 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가 됩니다.
/* 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;
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_DEVICE | CPU → 디바이스 | 캐시 → 메모리 flush | (없음) |
DMA_FROM_DEVICE | 디바이스 → CPU | 캐시 라인 invalidate | 캐시 라인 invalidate |
DMA_BIDIRECTIONAL | 양방향 | flush + invalidate | invalidate |
DMA_NONE | 디버깅 전용 | — | — |
DMA_BIDIRECTIONAL은 flush + invalidate를 모두 수행하므로 오버헤드가 큽니다. 가능하면 정확한 방향을 지정하십시오.아키텍처별 캐시 일관성 모델
| 아키텍처 | DMA 캐시 일관성 | 설명 |
|---|---|---|
| x86/x86_64 | HW coherent | 스누프 프로토콜이 캐시 일관성을 자동 보장. sync API는 no-op (컴파일러 배리어만) |
| ARM (non-coherent) | SW managed | flush/invalidate 필수. dma_sync_* 호출 시 실제 캐시 유지보수 명령 실행 |
| ARM (coherent) | HW coherent | CCI/CHI 인터커넥트가 IO-coherent 포트 제공 시 |
| RISC-V | 플랫폼 의존 | SiFive U74는 non-coherent, 향후 IOPMP/IOMMU로 개선 |
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-d | Intel | drivers/iommu/intel/ | DMAR 테이블, 2레벨/확장 페이지 테이블, Interrupt Remapping |
| AMD-Vi | AMD | drivers/iommu/amd/ | IVRS 테이블, v2 페이지 테이블 (중첩 페이지 테이블 공유) |
| SMMU v3 | ARM | drivers/iommu/arm/arm-smmu-v3/ | STE/CD 2단계, Stage 1+2, HTTU, MSI doorbell |
IOMMU 동작 모드
| 모드 | 커널 파라미터 | 동작 | 사용 시나리오 |
|---|---|---|---|
| Passthrough | iommu.passthrough=1 | 주소 변환 없음 (DMA = PA) | 성능 최우선, 신뢰된 디바이스 |
| DMA API (lazy) | iommu=lazy | IOVA 해제 지연 (배치 flush) | 기본 모드, 성능과 격리의 균형 |
| DMA API (strict) | iommu=strict | unmap 즉시 IOTLB flush | 보안 최우선, 기밀 컴퓨팅 |
| VFIO (user) | VFIO 드라이버 | 유저스페이스가 매핑 제어 | DPDK, GPU passthrough |
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 확장 | 역할 |
|---|---|---|
| ATS | Address Translation Service | 디바이스가 IOMMU에 변환 요청, 결과를 디바이스 TLB에 캐시 |
| PRI | Page Request Interface | 디바이스가 페이지 폴트를 IOMMU에 보고, 온디맨드 매핑 |
| PASID | Process 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에서 암호화된 게스트 메모리 ↔ 디바이스 간 전송 |
설정과 모니터링
/* 부팅 파라미터 */
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: 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/ion | drivers/dma-buf/dma-heap |
| 메인라인 | staging (미통합) | 완전 통합 (5.6+) |
| 힙 유형 | system, cma, carveout 등 | system, cma (확장 가능) |
| Android HAL | Gralloc 2.0 | Gralloc 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 RDMA | GPU VRAM ↔ NIC 간 직접 전송 (NVIDIA, AMD ROCm) |
| GPUDirect Storage | NVMe ↔ GPU VRAM 간 직접 전송 |
| NVMe-oF P2P | NVMe 디바이스 간 타깃 오프로드 |
| 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);
DMA 보안
DMA 공격 벡터
| 공격 | 설명 | 방어 |
|---|---|---|
| DMA Attack | 악의적 디바이스(Thunderbolt/PCIe)가 시스템 메모리 전체에 접근 | IOMMU 활성화 |
| TOCTOU | CPU가 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가지
- dma_mapping_error() 필수 확인 — map 후 반드시 오류 검사, IOVA 고갈 가능
- DMA 마스크 먼저 설정 —
dma_set_mask_and_coherent()를 probe 초기에 호출 - 방향 일치 — map과 unmap의 direction이 반드시 동일해야 함
- 크기 일치 — map과 unmap의 size가 반드시 동일해야 함
- 매핑 중 CPU 접근 금지 — streaming 매핑 후 sync 없이 CPU가 접근하면 stale data
- kmalloc 사용 — streaming DMA에 stack/global 변수 사용 금지 (캐시라인 공유 문제)
- SG unmap에 원본 nents 전달 —
dma_unmap_sg()에는mapped_nents가 아닌 원본nents전달 - coherent 크기 제한 —
dma_alloc_coherent()는 수 MB 이내, 대용량은 CMA 확인 - 바운스 버퍼 성능 저하 — SWIOTLB 사용 시 이중 복사, dmesg로 경고 확인
- 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_DMA | y (자동) | DMA 지원 가능 플랫폼 |
CONFIG_DMA_API_DEBUG | n | DMA API 오용 런타임 탐지 |
CONFIG_DMA_API_DEBUG_SG | n | SG 매핑 추가 검증 |
CONFIG_SWIOTLB | y (x86_64) | SWIOTLB 바운스 버퍼 |
CONFIG_DMA_CMA | y (ARM) | CMA 기반 DMA 할당 |
CONFIG_CMA_SIZE_MBYTES | 0~256 | 기본 CMA 크기 (MB) |
CONFIG_IOMMU_SUPPORT | y | IOMMU 프레임워크 |
CONFIG_IOMMU_DEFAULT_DMA_LAZY | y | IOMMU 기본 lazy 모드 |
CONFIG_IOMMU_DEFAULT_PASSTHROUGH | n | IOMMU 기본 passthrough 모드 |
CONFIG_INTEL_IOMMU | y (Intel) | Intel VT-d 드라이버 |
CONFIG_AMD_IOMMU | y (AMD) | AMD-Vi 드라이버 |
CONFIG_ARM_SMMU_V3 | y (ARM64) | ARM SMMUv3 드라이버 |
CONFIG_IOMMU_SVA | n | SVA(Shared Virtual Addressing) 지원 |
CONFIG_DMA_SHARED_BUFFER | y | DMA-BUF 프레임워크 |
CONFIG_DMABUF_HEAPS | n | DMA-BUF Heaps 유저스페이스 할당 |
CONFIG_DMABUF_HEAPS_SYSTEM | n | System heap |
CONFIG_DMABUF_HEAPS_CMA | n | CMA heap |
CONFIG_PCI_P2PDMA | n | PCIe P2P DMA 지원 |
CONFIG_DMA_RESTRICTED_POOL | n | Restricted DMA 보안 풀 |