← Architecture 비교

Linux 6.18.37 LTS · Architecture comparison 15/21

DMA, cache coherency와 IOMMU translation

driver가 CPU virtual address를 DMA address로 바꾼 뒤 device가 memory를 읽고 쓰며, IOMMU와 non-coherent cache maintenance가 그 주소와 ownership을 어떻게 보호하는지 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
파일 3개 · 원문 151줄
기준 태그
Linux v6.18.37
분석 축
상태 · 접근 순서 · 객체 수명 · 지연 시간

01 · QUESTION

무엇을 확인할 것인가

dma_map_single()이 반환한 주소는 physical address와 언제 같고, CPU와 device가 같은 buffer를 동시에 cacheable하게 접근하지 못하도록 어떤 상태 전환이 필요한가?

DMA API는 CPU virtual buffer, physical page, device-visible DMA address와 IOMMU IOVA를 분리한다. coherent allocation은 동시 관찰 계약을 제공하고 streaming mapping은 map/sync/unmap으로 ownership을 전환한다.

driver의 buffer lifetime, architecture cache maintenance, IOMMU page-table update와 device descriptor publication을 각각 구분한다. mapping 성공 뒤에도 descriptor write barrier와 device doorbell ordering이 필요하다.

지연 시간 관점direct DMA는 cache clean/invalidate byte 수가, IOMMU DMA는 IOVA allocation과 page-table map, IOTLB invalidation이 지배한다. bounce buffer는 추가 copy와 제한된 pool 대기를 만든다.

02 · CONTRACT

공통 규약과 아키텍처별 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64dma-direct/SMMU와 explicit cache maintenancedirection을 반대로 지정하거나 partial cache line ownership을 공유하면 CPU dirty data 손실 또는 stale device data 관찰이 생긴다.device dma_coherent 속성, DMA direction, cache line 정렬, StreamID, IOVA/PA, SMMU STE/CD와 IOTLB sync를 본다.
x86-64기본 cache-coherent DMA와 VT-d/AMD IOMMU부적절한 32-bit DMA mask는 IOMMU 재매핑·SWIOTLB bounce·매핑 실패 중 다른 결과로 이어질 수 있다. IOMMU group 격리 실패는 보호 경계를 깨고, unmap 없는 장기 pin은 memory pressure를 키운다.dma_mask/coherent_dma_mask와 실제 direct/IOMMU/SWIOTLB 분기, IOVA·BDF requester ID·IOMMU domain, 선택된 경우의 bounce slot과 fault record를 확인한다.
RISC-Vdma-noncoherent cache block operation과 RISC-V IOMMUcache block size 오인, unaligned buffer의 이웃 data 손상 또는 cache maintenance extension이 없는 platform의 잘못된 direct mapping이 data corruption을 만든다.riscv_cbom_block_size, dma-noncoherent flag, direction, IOVA/PA, device ID와 IOMMU fault queue를 본다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

그림 1. CPU cache와 device DMA 주소를 연결하는 세 구현x86-64의 mask 초과 PA도 IOMMU가 mask 안 IOVA로 매핑할 수 있습니다. bounce 여부는 선택된 경로와 조건을 확인합니다.

arm64

mechanism
dma-direct/SMMU와 explicit cache maintenance
state
platform가 dma-coherent면 normal cacheable mapping을 공유하지만 non-coherent device에서는 map-to-device 전 clean, map-from-device 전후 invalidate가 필요하다. SMMUv3는 StreamID로 context를 고르고 IOVA를 PA로 변환한다.
checkpoint
device dma_coherent 속성, DMA direction, cache line 정렬, StreamID, IOVA/PA, SMMU STE/CD와 IOTLB sync를 본다.

x86-64

mechanism
기본 cache-coherent DMA와 VT-d/AMD IOMMU
state
일반 PC의 WB memory는 CPU cache와 PCIe DMA가 hardware coherency를 유지해 streaming map에 명시적 cache clean이 보통 없다. DMA API는 device별 direct·IOMMU·기타 dma_map_ops 경로를 고른다. IOMMU 경로는 device mask 안의 IOVA를 할당해 높은 PA를 매핑할 수 있어, PA가 mask를 넘는다는 이유만으로 SWIOTLB bounce가 필수는 아니다. direct 경로의 주소 제약이나 IOMMU 경로의 비정렬·격리 조건 등에서는 SWIOTLB가 쓰일 수 있다.
checkpoint
dma_mask/coherent_dma_mask와 실제 direct/IOMMU/SWIOTLB 분기, IOVA·BDF requester ID·IOMMU domain, 선택된 경우의 bounce slot과 fault record를 확인한다.

RISC-V

mechanism
dma-noncoherent cache block operation과 RISC-V IOMMU
state
platform coherency가 보장되지 않으면 Zicbom cache-block clean/flush/inval 또는 platform callback으로 streaming mapping을 동기화한다. IOMMU가 있는 시스템은 device ID/process ID context로 IOVA를 변환하지만 구현과 firmware description을 함께 확인해야 한다.
checkpoint
riscv_cbom_block_size, dma-noncoherent flag, direction, IOVA/PA, device ID와 IOMMU fault queue를 본다.

x86-64 주소 분기의 근거: DMA API direct/IOMMU 선택, device mask 안 IOVA 할당, 조건부 IOMMU bounce.

그림 2. 공통 커널 코드와 아키텍처별 함수의 역할공통 커널 코드와 아키텍처별 코드가 각각 담당하는 일을 구분했습니다.
리눅스 공통 규약DMA API는 CPU virtual buffer, physical page, device-visible DMA address와 IOMMU IOVA를 분리한다. coherent allocation은 동시 관찰 계약을 제공하고 streaming mapping은 map/sync/unmap으로 ownership을 전환한다.
arm64dma-direct/SMMU와 explicit cache maintenanceCPU cache operation completion, descriptor store barrier와 SMMU command queue sync가 device fetch보다 앞서야 한다.
x86-64기본 cache-coherent DMA와 VT-d/AMD IOMMUcoherent memory라도 descriptor/data store가 device MMIO doorbell보다 먼저 보이도록 dma_wmb가 필요하며 posted MMIO completion은 별도 readback을 요구할 수 있다.
RISC-Vdma-noncoherent cache block operation과 RISC-V IOMMUCBO operation 전후 fence와 device descriptor barrier가 CPU cache ownership과 DMA 시작 순서를 만든다.
객체 수명 관리buffer page는 device DMA가 끝나기 전에 새 소유자에게 넘기지 않는다. IOMMU 매핑을 쓴 경우 IOVA 재사용은 unmap과 요구되는 IOTLB 동기화 뒤에 가능하다. direct/SWIOTLB 경로를 모두 IOVA 해제 경로로 그리지 않는다.
그림 3. 상태 변경과 다른 코드에서 읽는 순서상태를 준비한 뒤, 다른 코드가 읽기 전에 아키텍처에서 요구하는 메모리 접근 순서를 지켜야 합니다.
arm64상태 준비→CPU cache operation completion, descriptor store barrier와 SMMU command queue sync가 device fetch보다 앞서야 한다.→관찰: device dma_coherent 속성, DMA direction, cache line 정렬, StreamID, IOVA/PA, SMMU STE/CD와 IOTLB sync를 본다.
x86-64상태 준비→coherent memory라도 descriptor/data store가 device MMIO doorbell보다 먼저 보이도록 dma_wmb가 필요하며 posted MMIO completion은 별도 readback을 요구할 수 있다.→관찰: dma_mask/coherent_dma_mask와 실제 direct/IOMMU/SWIOTLB 분기, IOVA·BDF requester ID·IOMMU domain, 선택된 경우의 bounce slot과 fault record를 확인한다.
RISC-V상태 준비→CBO operation 전후 fence와 device descriptor barrier가 CPU cache ownership과 DMA 시작 순서를 만든다.→관찰: riscv_cbom_block_size, dma-noncoherent flag, direction, IOVA/PA, device ID와 IOMMU fault queue를 본다.

04 · SOURCE

Linux 6.18.37 원본 코드와 주요 구문 해설

소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 원문 전체 발췌를 보존하고, 개별 해설은 근거가 있는 구문에만 붙였습니다.

arm64 · Linux 6.18.37

dma-direct/SMMU와 explicit cache maintenance

platform가 dma-coherent면 normal cacheable mapping을 공유하지만 non-coherent device에서는 map-to-device 전 clean, map-from-device 전후 invalidate가 필요하다. SMMUv3는 StreamID로 context를 고르고 IOVA를 PA로 변환한다.

원본 코드: arch/arm64/mm/dma-mapping.c:9-55

9#include <linux/dma-map-ops.h>
10#include <xen/xen.h>
11 
12#include <asm/cacheflush.h>
13#include <asm/xen/xen-ops.h>
14 
15void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,
16			      enum dma_data_direction dir)
17{
18	unsigned long start = (unsigned long)phys_to_virt(paddr);
19 
20	dcache_clean_poc(start, start + size);
21}
22 
23void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
24			   enum dma_data_direction dir)
25{
26	unsigned long start = (unsigned long)phys_to_virt(paddr);
27 
28	if (dir == DMA_TO_DEVICE)
29		return;
30 
31	dcache_inval_poc(start, start + size);
32}
33 
34void arch_dma_prep_coherent(struct page *page, size_t size)
35{
36	unsigned long start = (unsigned long)page_address(page);
37 
38	dcache_clean_poc(start, start + size);
39}
40 
41void arch_setup_dma_ops(struct device *dev, bool coherent)
42{
43	int cls = cache_line_size_of_cpu();
44 
45	WARN_TAINT(!coherent && cls > ARCH_DMA_MINALIGN,
46		   TAINT_CPU_OUT_OF_SPEC,
47		   "%s %s: ARCH_DMA_MINALIGN smaller than CTR_EL0.CWG (%d < %d)",
48		   dev_driver_string(dev), dev_name(dev),
49		   ARCH_DMA_MINALIGN, cls);
50 
51	dev->dma_coherent = coherent;
52 
53	xen_setup_dma_ops(dev);
54}
55 

주요 구문 해설

원본 47줄은 그대로 표시하며, 검토한 26개 구문에 설명을 붙였습니다.

L9#include <linux/dma-map-ops.h>

장치가 접근할 DMA 주소를 준비하고 CPU와 장치 사이의 메모리 소유권을 전환하는 DMA 연산 정의를 가져옵니다. 플랫폼의 dma_map_ops 선택과 비일관성 캐시 동기화 구현을 공통 DMA 계층에 연결하는 데 필요합니다. 전처리 단계에서 헤더 내용을 포함하며, CPU가 실행 중 헤더를 호출하는 동작은 없습니다.

L10#include <xen/xen.h>

현재 커널이 Xen 환경에서 실행되는지 판별하는 정의를 가져옵니다. 일반 ARM64 DMA 처리와 Xen용 처리 경로를 구분할 때 사용합니다. 전처리 단계에서 헤더 내용을 포함하며, CPU가 실행 중 헤더를 호출하는 동작은 없습니다.

L12#include <asm/cacheflush.h>

ARM64 데이터 캐시를 DMA 방향에 맞춰 clean·invalidate하는 정의를 가져옵니다. CPU와 장치가 캐시 일관성을 제공하지 않는 메모리를 공유할 때 같은 데이터를 보도록 하는 데 필요합니다. 전처리 단계에서 헤더 내용을 포함하며, CPU가 실행 중 헤더를 호출하는 동작은 없습니다.

L13#include <asm/xen/xen-ops.h>

ARM64의 Xen 전용 DMA 관련 동작을 가져옵니다. Xen 환경에서는 일반 물리 DMA 주소 처리와 다른 제약이 있을 수 있어 해당 경로를 별도로 선택합니다. 전처리 단계에서 헤더 내용을 포함하며, CPU가 실행 중 헤더를 호출하는 동작은 없습니다.

L15void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,

CPU가 사용하던 비일관성 DMA 버퍼를 장치에 넘기기 전에 캐시를 동기화하는 함수입니다. paddr는 물리 시작 주소이고 size는 처리할 바이트 수입니다.

L16 enum dma_data_direction dir)

DMA 방향 dir을 인자로 받는 서명의 마지막 줄입니다. 이 ARM64 구현은 장치로 넘길 때 방향과 무관하게 dirty 캐시를 PoC까지 clean합니다.

L18 unsigned long start = (unsigned long)phys_to_virt(paddr);

DMA 대상 물리 주소를 커널 linear-map 가상 주소로 바꿉니다. CPU 캐시 정리 연산이 접근할 시작 주소를 만드는 단계입니다.

L20 dcache_clean_poc(start, start + size);

해당 범위의 CPU dirty cache line을 PoC까지 clean합니다. CPU가 쓴 내용을 장치가 DMA로 읽기 전에 메모리 쪽에서 볼 수 있게 합니다.

L23void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,

장치가 사용한 DMA 버퍼를 CPU가 다시 읽기 전에 동기화하는 함수입니다. paddr·size가 오래된 CPU 캐시를 제거할 물리 범위를 정합니다.

L24 enum dma_data_direction dir)

DMA 방향에 따라 CPU 복귀 시 캐시 처리가 필요한지 결정합니다. 장치가 쓰지 않는 TO_DEVICE이면 생략하고 나머지는 invalidate합니다.

L26 unsigned long start = (unsigned long)phys_to_virt(paddr);

DMA 범위의 물리 주소를 CPU 캐시 연산에 사용할 가상 주소로 변환합니다. 장치가 기록한 데이터를 CPU가 읽기 전 동기화할 범위를 정합니다.

L28 if (dir == DMA_TO_DEVICE)

장치가 CPU 데이터를 읽기만 하는 DMA_TO_DEVICE 전송인지 검사합니다. 이 경우 장치가 메모리를 바꾸지 않아 CPU 복귀 시 무효화할 새 데이터가 없습니다.

L29 return;

TO_DEVICE의 CPU 복귀 동기화를 즉시 마칩니다. 장치가 기록하는 FROM_DEVICE·BIDIRECTIONAL 경로의 캐시 무효화로 내려가지 않습니다.

L31 dcache_inval_poc(start, start + size);

DMA_FROM_DEVICE 또는 양방향 전송이면 CPU 캐시의 해당 범위를 무효화합니다. CPU가 이전 캐시 값을 읽지 않고 장치가 갱신한 메모리를 보도록 합니다.

L34void arch_dma_prep_coherent(struct page *page, size_t size)

할당한 페이지를 coherent DMA 매핑에 쓰기 전에 캐시 상태를 준비합니다. page는 시작 페이지, size는 준비할 바이트 수이며 기존 dirty 내용이 뒤늦게 덮어쓰지 않도록 clean합니다.

L36 unsigned long start = (unsigned long)page_address(page);

coherent DMA 할당을 준비할 페이지의 커널 가상 주소를 얻습니다. 이어지는 캐시 정리가 이 주소부터 size 바이트에 적용됩니다.

L38 dcache_clean_poc(start, start + size);

할당한 페이지 범위의 dirty cache를 PoC까지 내보냅니다. coherent 용도로 매핑하기 전에 이전 CPU 캐시 내용이 뒤늦게 메모리를 덮어쓰는 일을 막는 준비입니다.

L41void arch_setup_dma_ops(struct device *dev, bool coherent)

장치의 DMA 일관성 여부를 등록하고 플랫폼 DMA 연산을 연결하는 함수입니다. coherent 인자는 CPU 캐시와 장치 접근이 자동 일관성을 갖는지 나타냅니다.

L43 int cls = cache_line_size_of_cpu();

CTR_EL0.CWG로 캐시 writeback이 영향을 줄 수 있는 단위 크기를 얻습니다. CWG가 0이면 ARCH_DMA_MINALIGN을 대신 사용하며, 비일관성 장치의 버퍼 정렬보다 이 단위가 크면 이웃 버퍼를 함께 건드릴 위험이 있어 경고합니다.

L45 WARN_TAINT(!coherent && cls > ARCH_DMA_MINALIGN,

비일관성 장치에서 캐시 writeback 단위가 DMA 최소 정렬보다 큰지 검사합니다. 크면 별도 버퍼가 같은 캐시 처리 단위를 공유하여 데이터가 손상될 수 있습니다.

L46 TAINT_CPU_OUT_OF_SPEC,

하드웨어가 커널이 기대한 DMA 정렬 조건을 벗어났음을 나타내는 taint 종류를 지정합니다. 이후 오류 진단에서 이 환경 제약을 알 수 있게 합니다.

L47 "%s %s: ARCH_DMA_MINALIGN smaller than CTR_EL0.CWG (%d < %d)",

DMA 정렬값이 CPU writeback 단위보다 작다는 경고 형식을 지정합니다. 어떤 장치에서 어떤 두 크기가 충돌했는지 아래 인자를 함께 출력합니다.

L48 dev_driver_string(dev), dev_name(dev),

경고에 장치 드라이버 이름과 장치 이름을 넣습니다. 공통 경고라도 실제로 영향을 받는 비일관성 장치를 구분할 수 있게 합니다.

L49 ARCH_DMA_MINALIGN, cls);

커널이 보장한 최소 DMA 정렬과 CPU에서 얻은 writeback 단위를 출력 인자로 넘깁니다. 두 값의 차이가 앞 조건의 위험 원인입니다.

L51 dev->dma_coherent = coherent;

전달받은 coherent 여부를 장치 구조체에 기록합니다. DMA 공통 코드가 캐시 동기화를 생략할 수 있는 장치인지 판단할 때 이 값을 사용합니다.

L53 xen_setup_dma_ops(dev);

Xen 환경이면 장치에 맞는 DMA 연산을 설정합니다. 일반 물리 주소와 Xen의 DMA 주소 변환 요구를 장치의 DMA 경로에 반영합니다.

x86-64 · Linux 6.18.37

기본 cache-coherent DMA와 VT-d/AMD IOMMU

일반 PC의 WB memory는 CPU cache와 PCIe DMA가 hardware coherency를 유지해 streaming map에 명시적 cache clean이 보통 없다. DMA API는 device별 direct·IOMMU·기타 dma_map_ops 경로를 고른다. IOMMU 경로는 device mask 안의 IOVA를 할당해 높은 PA를 매핑할 수 있어, PA가 mask를 넘는다는 이유만으로 SWIOTLB bounce가 필수는 아니다. direct 경로의 주소 제약이나 IOMMU 경로의 비정렬·격리 조건 등에서는 SWIOTLB가 쓰일 수 있다.

원본 코드: arch/x86/kernel/pci-dma.c:92-140

92#else
93static inline void __init pci_xen_swiotlb_init(void)
94{
95}
96#endif /* CONFIG_SWIOTLB_XEN */
97 
98void __init pci_iommu_alloc(void)
99{
100	if (xen_pv_domain()) {
101		pci_xen_swiotlb_init();
102		return;
103	}
104	pci_swiotlb_detect();
105	gart_iommu_hole_init();
106	amd_iommu_detect();
107	detect_intel_iommu();
108	swiotlb_init(x86_swiotlb_enable, x86_swiotlb_flags);
109}
110 
111static __init int iommu_setup(char *p)
112{
113	iommu_merge = 1;
114 
115	if (!p)
116		return -EINVAL;
117 
118	while (*p) {
119		if (!strncmp(p, "off", 3))
120			no_iommu = 1;
121		/* gart_parse_options has more force support */
122		if (!strncmp(p, "force", 5))
123			force_iommu = 1;
124		if (!strncmp(p, "noforce", 7)) {
125			iommu_merge = 0;
126			force_iommu = 0;
127		}
128 
129		if (!strncmp(p, "biomerge", 8)) {
130			iommu_merge = 1;
131			force_iommu = 1;
132		}
133		if (!strncmp(p, "panic", 5))
134			panic_on_overflow = 1;
135		if (!strncmp(p, "nopanic", 7))
136			panic_on_overflow = 0;
137		if (!strncmp(p, "merge", 5)) {
138			iommu_merge = 1;
139			force_iommu = 1;
140		}

주요 구문 해설

원본 49줄은 그대로 표시하며, 검토한 34개 구문에 설명을 붙였습니다.

L92#else

Xen 전용 SWIOTLB 지원이 없는 빌드입니다. 초기화 함수를 빈 구현으로 남겨 공통 PCI DMA 초기화가 같은 이름을 호출해도 Xen용 bounce buffer 준비는 수행하지 않게 합니다.

L93static inline void __init pci_xen_swiotlb_init(void)

Xen SWIOTLB 지원을 빌드하지 않았을 때의 빈 초기화 함수를 정의합니다. 공통 부팅 코드가 같은 이름을 호출하더라도 추가 Xen DMA 작업은 수행되지 않습니다.

L96#endif /* CONFIG_SWIOTLB_XEN */

74행에서 시작한 선택 구간을 마칩니다. Xen 환경에서 소프트웨어 DMA bounce buffer를 지원하는 설정입니다. 켜지면 Xen 전용 SWIOTLB·DMA 준비를 수행하고, 꺼지면 pci_xen_swiotlb_init을 빈 함수로 만들어 공통 초기화 경로가 그대로 호출할 수 있게 합니다.

L98void __init pci_iommu_alloc(void)

x86의 IOMMU 탐지와 SWIOTLB bounce buffer 준비를 시작하는 초기화 함수입니다. Xen PV 경로와 일반 플랫폼 경로를 구분합니다.

L100 if (xen_pv_domain()) {

현재 커널이 Xen 반가상화 도메인인지 검사합니다. 참이면 Xen 전용 SWIOTLB 연산을 준비하고 일반 GART·AMD·Intel IOMMU 탐지를 건너뜁니다.

L101 pci_xen_swiotlb_init();

Xen PV 게스트에 맞는 SWIOTLB DMA 처리 기반을 초기화합니다. 이 분기는 일반 x86 IOMMU 탐지를 진행하지 않고 반환합니다.

L102 return;

Xen PV용 DMA 초기화를 끝내고 함수를 반환합니다. 같은 장치에 일반 x86 DMA 초기화 경로를 중복 적용하지 않게 합니다.

L104 pci_swiotlb_detect();

주소 제한이나 플랫폼 조건 때문에 SWIOTLB가 필요한지 탐지합니다. 장치가 직접 접근하지 못하는 메모리를 bounce buffer로 전달할지 뒤의 초기화가 결정할 수 있게 합니다.

L105 gart_iommu_hole_init();

AMD GART IOMMU에 필요한 aperture 구간을 확인하고 준비합니다. 구형 GART 기반 DMA 주소 변환 경로가 사용할 물리 공간을 확보하는 초기 단계입니다.

L106 amd_iommu_detect();

AMD IOMMU의 존재와 사용 조건을 탐지하여 해당 초기화 경로를 등록합니다. 이 한 줄만으로 모든 장치의 DMA 매핑을 만드는 것은 아닙니다.

L107 detect_intel_iommu();

Intel IOMMU의 펌웨어 정보와 사용 가능성을 탐지합니다. 사용할 수 있으면 이후 DMA remapping 초기화가 해당 장치를 맡도록 준비합니다.

L108 swiotlb_init(x86_swiotlb_enable, x86_swiotlb_flags);

앞서 정한 활성화 여부와 플래그로 SWIOTLB를 초기화합니다. 직접 DMA할 수 없는 버퍼를 장치가 접근 가능한 메모리로 중계할 공간을 준비합니다.

L111static __init int iommu_setup(char *p)

iommu= 부팅 옵션 문자열을 해석하는 초기화 함수입니다. p가 가리키는 하위 옵션에 따라 IOMMU 사용·병합·실패 정책을 설정합니다.

L113 iommu_merge = 1;

옵션 처리의 시작값으로 DMA 병합 정책을 켭니다. 아래 noforce·nomerge 같은 옵션이 선택되면 다시 끌 수 있습니다.

L115 if (!p)

옵션 문자열 포인터 자체가 없는지 검사합니다. 유효한 문자열을 역참조하기 전에 잘못된 입력을 걸러냅니다.

L116 return -EINVAL;

옵션 문자열이 없으므로 잘못된 인자를 뜻하는 -EINVAL을 반환합니다. IOMMU 하드웨어 실패가 아니라 옵션 해석 입력 오류입니다.

L118 while (*p) {

문자열 끝에 도달할 때까지 쉼표로 구분된 IOMMU 하위 옵션들을 처리합니다. 각 반복의 p는 현재 옵션 시작을 가리킵니다.

L119 if (!strncmp(p, "off", 3))

현재 옵션이 off로 시작하는지 비교합니다. 일치하면 다음 줄에서 IOMMU 사용을 금지하는 전역 설정을 켭니다.

L120 no_iommu = 1;

IOMMU를 사용하지 않도록 no_iommu를 설정합니다. 실제 하드웨어 제어는 뒤의 탐지·초기화 코드가 이 정책을 반영합니다.

L122 if (!strncmp(p, "force", 5))

force 옵션인지 검사합니다. 일치하면 DMA 주소가 직접 접근 가능하더라도 IOMMU 사용을 강제하는 정책을 선택합니다.

L123 force_iommu = 1;

IOMMU 강제 사용 표시를 켭니다. 이후 DMA 초기화·매핑 정책이 이를 참조하며 이 대입 자체가 페이지 테이블을 만들지는 않습니다.

L124 if (!strncmp(p, "noforce", 7)) {

noforce 옵션이면 강제 사용과 관련 병합 설정을 모두 해제하는 분기로 갑니다. 필요한 경우의 IOMMU까지 무조건 금지하는 off와는 다른 설정입니다.

L125 iommu_merge = 0;

noforce 처리에서 DMA 병합 설정을 끕니다. 강제 IOMMU 경로에 의존한 병합을 선택하지 않게 합니다.

L126 force_iommu = 0;

noforce 처리에서 IOMMU 강제 사용 표시를 지웁니다. 플랫폼 필요성에 따른 일반 선택으로 돌아갑니다.

L129 if (!strncmp(p, "biomerge", 8)) {

biomerge 옵션이면 DMA 병합과 IOMMU 강제 사용을 함께 켜는 분기로 갑니다. 여러 구간을 주소 변환으로 묶는 정책을 요청한 경우입니다.

L130 iommu_merge = 1;

biomerge가 요청한 DMA 구간 병합 정책을 활성화합니다. 실제 병합 여부는 이후 해당 IOMMU 구현이 적용합니다.

L131 force_iommu = 1;

biomerge에 필요한 IOMMU 사용을 강제합니다. 병합 표시만 켜고 주소 변환 경로가 생략되는 것을 막습니다.

L133 if (!strncmp(p, "panic", 5))

panic 옵션인지 검사합니다. IOMMU 매핑 자원 부족 때 시스템을 중단하는 정책을 사용자가 요청했는지 구분합니다.

L134 panic_on_overflow = 1;

IOMMU 매핑 공간이 넘칠 때 panic할 정책을 켭니다. 현재 시점에 공간 부족을 발생시키거나 곧바로 panic하는 문장은 아닙니다.

L135 if (!strncmp(p, "nopanic", 7))

nopanic 옵션인지 검사합니다. 앞서 지정됐을 수 있는 공간 부족 시 panic 정책을 해제하는 옵션입니다.

L136 panic_on_overflow = 0;

매핑 공간 부족 시 panic하도록 한 설정을 끕니다. 오류 시의 최종 처리 방식은 이후 DMA 구현의 이 정책 분기를 따릅니다.

L137 if (!strncmp(p, "merge", 5)) {

merge 옵션이면 DMA 구간 병합과 IOMMU 강제 사용을 함께 켭니다. 이 코드에서는 biomerge와 같은 두 설정을 적용합니다.

L138 iommu_merge = 1;

merge 옵션에 따라 DMA 병합 표시를 켭니다. 장치에 보이는 연속 주소 구간을 만들 때 IOMMU의 병합 정책이 사용합니다.

L139 force_iommu = 1;

merge 옵션과 함께 IOMMU 사용을 강제합니다. 요청한 병합을 수행할 주소 변환 경로가 선택되게 합니다.

RISC-V · Linux 6.18.37

dma-noncoherent cache block operation과 RISC-V IOMMU

platform coherency가 보장되지 않으면 Zicbom cache-block clean/flush/inval 또는 platform callback으로 streaming mapping을 동기화한다. IOMMU가 있는 시스템은 device ID/process ID context로 IOVA를 변환하지만 구현과 firmware description을 함께 확인해야 한다.

원본 코드: arch/riscv/mm/dma-noncoherent.c:63-117

63 
64static inline bool arch_sync_dma_cpu_needs_post_dma_flush(void)
65{
66	return true;
67}
68 
69void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,
70			      enum dma_data_direction dir)
71{
72	switch (dir) {
73	case DMA_TO_DEVICE:
74		arch_dma_cache_wback(paddr, size);
75		break;
76 
77	case DMA_FROM_DEVICE:
78		if (!arch_sync_dma_clean_before_fromdevice()) {
79			arch_dma_cache_inv(paddr, size);
80			break;
81		}
82		fallthrough;
83 
84	case DMA_BIDIRECTIONAL:
85		/* Skip the invalidate here if it's done later */
86		if (IS_ENABLED(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) &&
87		    arch_sync_dma_cpu_needs_post_dma_flush())
88			arch_dma_cache_wback(paddr, size);
89		else
90			arch_dma_cache_wback_inv(paddr, size);
91		break;
92 
93	default:
94		break;
95	}
96}
97 
98void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
99			   enum dma_data_direction dir)
100{
101	switch (dir) {
102	case DMA_TO_DEVICE:
103		break;
104 
105	case DMA_FROM_DEVICE:
106	case DMA_BIDIRECTIONAL:
107		/* FROM_DEVICE invalidate needed if speculative CPU prefetch only */
108		if (arch_sync_dma_cpu_needs_post_dma_flush())
109			arch_dma_cache_inv(paddr, size);
110		break;
111 
112	default:
113		break;
114	}
115}
116 
117void arch_dma_prep_coherent(struct page *page, size_t size)

주요 구문 해설

원본 55줄은 그대로 표시하며, 검토한 35개 구문에 설명을 붙였습니다.

L64static inline bool arch_sync_dma_cpu_needs_post_dma_flush(void)

DMA가 끝난 뒤 CPU용 캐시 무효화가 필요한지 알려 주는 helper입니다. 이 버전은 아래에서 항상 true를 반환하는 정책을 사용합니다.

L66 return true;

전송 후 CPU 캐시 처리가 필요하다고 항상 답합니다. 장치 전송 중 추측 prefetch로 오래된 캐시가 생길 수 있으므로 CPU가 다시 쓰기 전에 invalidate하도록 합니다.

L69void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,

CPU가 버퍼를 장치에 넘기기 전 비일관성 DMA 캐시 처리를 수행합니다. paddr와 size가 물리 범위를 지정하고 방향별로 clean 또는 flush를 선택합니다.

L70 enum dma_data_direction dir)

dir는 장치가 읽을지, 쓸지, 둘 다 할지를 나타내는 DMA 방향입니다. 아래 switch가 이 값을 기준으로 버퍼를 넘기기 전 처리를 고릅니다.

L72 switch (dir) {

DMA 방향별로 장치에 넘기기 전 캐시 동기화 방식을 선택합니다. CPU의 dirty 내용 보존과 장치가 볼 최신 데이터가 각 분기의 기준입니다.

L73 case DMA_TO_DEVICE:

장치가 CPU가 쓴 데이터를 읽는 방향입니다. 바로 다음 writeback으로 dirty cache를 메모리에 반영합니다.

L74 arch_dma_cache_wback(paddr, size);

장치가 읽을 범위의 dirty CPU cache를 메모리로 내보냅니다. DMA_TO_DEVICE에서는 CPU가 만든 최신 데이터가 장치에 보여야 합니다.

L75 break;

TO_DEVICE용 writeback을 마쳤으므로 방향 선택을 끝냅니다. 장치가 쓸 때 필요한 아래의 다른 처리를 이어 실행하지 않습니다.

L77 case DMA_FROM_DEVICE:

장치가 메모리에 데이터를 쓰는 방향의 시작입니다. 이 구현은 사전 clean을 요구하므로 아래 BIDIRECTIONAL과 같은 writeback 준비로 이어집니다.

L78 if (!arch_sync_dma_clean_before_fromdevice()) {

사전 clean이 필요 없을 때만 즉시 invalidate하는 조건입니다. 현재 helper는 true를 반환하므로 이 조건은 거짓이고 아래 fallthrough를 따릅니다.

L79 arch_dma_cache_inv(paddr, size);

장치가 쓸 DMA_FROM_DEVICE 범위의 CPU 캐시를 무효화하는 분기입니다. 다만 이 버전의 arch_sync_dma_clean_before_fromdevice()는 항상 true이므로 여기로 오지 않고, 아래의 writeback 경로를 통해 dirty 데이터를 먼저 정리합니다.

L80 break;

사전 clean이 불필요한 분기에서 invalidate를 마쳤다면 switch를 끝냅니다. v6.18.37의 현재 상수 helper 값으로는 이 분기가 실행되지 않습니다.

L82 fallthrough;

FROM_DEVICE 처리를 아래 BIDIRECTIONAL 분기로 의도적으로 이어갑니다. 누락된 break가 아니라 두 방향이 공유하는 사전 writeback을 사용하겠다는 표시입니다.

L84 case DMA_BIDIRECTIONAL:

장치가 읽고 쓰는 양방향 DMA 분기입니다. 이 버전의 FROM_DEVICE도 여기로 합류해 dirty CPU 캐시를 먼저 정리합니다.

L86 if (IS_ENABLED(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) &&

CPU 복귀 단계의 동기화 함수를 제공하는 빌드인지 검사합니다. 전송 뒤 invalidate가 보장되어야 지금 장치로 넘길 때 무효화를 미룰 수 있습니다.

L87 arch_sync_dma_cpu_needs_post_dma_flush())

CPU 복귀 때 실제로 invalidate할 정책인지 조건을 마칩니다. 이 helper는 true이므로 앞의 빌드 지원까지 있으면 지금은 writeback만 수행합니다.

L88 arch_dma_cache_wback(paddr, size);

전송 후 CPU 복귀 단계에서 invalidate를 수행할 수 있는 플랫폼이면 지금은 writeback만 합니다. 장치에 넘기기 전 dirty 데이터를 정리하고, 장치가 쓴 결과의 캐시 무효화는 뒤 단계에 맡깁니다.

L89 else

전송 후 무효화를 보장할 수 없으면 지금 writeback과 invalidate를 함께 수행하는 경로를 택합니다. 오래된 캐시를 남긴 채 장치에 넘기지 않게 합니다.

L90 arch_dma_cache_wback_inv(paddr, size);

지연된 후처리 조건을 만족하지 않으면 writeback과 invalidate를 함께 합니다. 장치에 범위를 넘기면서 dirty 데이터 정리와 기존 CPU 캐시 제거를 끝냅니다.

L91 break;

FROM_DEVICE 또는 BIDIRECTIONAL의 사전 캐시 처리를 마치고 switch를 빠져나갑니다. 이후 default 분기를 실행하지 않습니다.

L93 default:

세 가지 유효 DMA 방향 이외 값의 기본 분기입니다. 이 함수는 이 경우 추가 캐시 명령을 선택하지 않습니다.

L94 break;

지원 방향이 아니므로 이 switch에서 캐시 작업을 수행하지 않고 종료합니다. 이 줄이 잘못된 방향을 정상 방향으로 변환하지는 않습니다.

L98void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,

장치가 사용한 버퍼를 CPU에 돌려주기 전 캐시를 동기화합니다. paddr·size가 invalidate 대상 범위를 지정합니다.

L99 enum dma_data_direction dir)

CPU 복귀 동기화도 DMA 방향을 인자로 받습니다. 장치가 데이터를 썼을 수 있는 두 방향에서만 오래된 CPU 캐시를 제거합니다.

L101 switch (dir) {

DMA 방향에 따라 CPU 복귀 시의 무효화 필요 여부를 나눕니다. 장치가 읽기만 했는지, 메모리를 갱신했는지가 기준입니다.

L102 case DMA_TO_DEVICE:

장치가 읽기만 한 TO_DEVICE 분기입니다. CPU가 다시 볼 장치 기록이 없으므로 별도 무효화를 하지 않습니다.

L103 break;

TO_DEVICE의 CPU 복귀 처리를 아무 캐시 명령 없이 끝냅니다. 장치가 기록한 두 방향의 invalidate 경로를 건너뜁니다.

L105 case DMA_FROM_DEVICE:

장치가 기록한 FROM_DEVICE는 아래 BIDIRECTIONAL과 같은 CPU 복귀 처리로 합류합니다. 장치의 최신 데이터가 오래된 캐시에 가려지지 않아야 합니다.

L106 case DMA_BIDIRECTIONAL:

장치가 읽고 쓴 BIDIRECTIONAL 버퍼도 CPU가 재사용하기 전에 invalidate합니다. FROM_DEVICE와 공통 처리 구간입니다.

L108 if (arch_sync_dma_cpu_needs_post_dma_flush())

전송 후 캐시 처리가 필요할 때 invalidate를 수행하는 조건입니다. v6.18.37에서는 helper가 항상 true이므로 두 쓰기 방향 모두 무효화합니다.

L109 arch_dma_cache_inv(paddr, size);

장치가 기록한 FROM_DEVICE·BIDIRECTIONAL 범위를 CPU가 다시 사용할 때 캐시를 무효화합니다. 이 버전의 후처리 조건 함수는 항상 true이며, 전송 중 추측 prefetch 등으로 채워진 오래된 CPU 캐시를 제거합니다.

L110 break;

장치가 기록한 버퍼의 캐시 무효화를 마치고 방향 처리를 끝냅니다. 이 뒤 CPU 읽기는 장치가 갱신한 메모리를 기준으로 해야 합니다.

L112 default:

유효한 DMA 방향 셋 이외의 값에 대한 기본 분기입니다. 이 함수는 별도의 처리 동작을 지정하지 않습니다.

L113 break;

기본 분기를 캐시 작업 없이 종료합니다. 유효하지 않은 방향을 이 함수가 자동 교정하는 동작은 없습니다.

L117void arch_dma_prep_coherent(struct page *page, size_t size)

페이지를 coherent DMA 용도로 준비하는 함수입니다. page_address로 시작 주소를 얻고 size 범위를 writeback·invalidate하여 이전 CPU 캐시 상태를 정리합니다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

64KiB RX buffer의 ownership 전환

non-coherent CPU cache line 64B, device가 64KiB packet area를 채우고 IOMMU page는 4KiB라고 가정한다.

  1. map16개 IOMMU PTE가 IOVA range를 backing page에 연결하며 direction은 DMA_FROM_DEVICE다.
  2. device ownsdescriptor를 publish한 뒤 CPU는 buffer payload를 읽거나 쓰지 않는다.
  3. completedevice completion 뒤 CPU가 64KiB/64B = 1024 cache line을 invalidate하고 packet을 읽는다.
  4. recycleCPU가 metadata를 수정했다면 필요한 clean을 수행하고 descriptor barrier 뒤 다시 device에 넘긴다.

결론DMA address 계산, IOMMU translation과 cache ownership은 서로 다른 문제다. 셋을 한 단계로 표현하면 corruption 원인을 찾기 어렵다.

06 · DEEP DIVE

주요 동작 상세 분석

01

공통 커널 코드와 아키텍처별 함수의 역할 구분

DMA API는 CPU virtual buffer, physical page, device-visible DMA address와 IOMMU IOVA를 분리한다. coherent allocation은 동시 관찰 계약을 제공하고 streaming mapping은 map/sync/unmap으로 ownership을 전환한다.

driver의 buffer lifetime, architecture cache maintenance, IOMMU page-table update와 device descriptor publication을 각각 구분한다. mapping 성공 뒤에도 descriptor write barrier와 device doorbell ordering이 필요하다.

02

arm64: dma-direct/SMMU와 explicit cache maintenance

platform가 dma-coherent면 normal cacheable mapping을 공유하지만 non-coherent device에서는 map-to-device 전 clean, map-from-device 전후 invalidate가 필요하다. SMMUv3는 StreamID로 context를 고르고 IOVA를 PA로 변환한다.

CPU cache operation completion, descriptor store barrier와 SMMU command queue sync가 device fetch보다 앞서야 한다. 디버깅할 때는 device dma_coherent 속성, DMA direction, cache line 정렬, StreamID, IOVA/PA, SMMU STE/CD와 IOTLB sync를 본다.

03

x86-64: 기본 cache-coherent DMA와 VT-d/AMD IOMMU

일반 PC의 WB memory는 CPU cache와 PCIe DMA가 hardware coherency를 유지해 streaming map에 명시적 cache clean이 보통 없다. DMA API는 device별 direct·IOMMU·기타 dma_map_ops 경로를 고른다. IOMMU 경로는 device mask 안의 IOVA를 할당해 높은 PA를 매핑할 수 있어, PA가 mask를 넘는다는 이유만으로 SWIOTLB bounce가 필수는 아니다. direct 경로의 주소 제약이나 IOMMU 경로의 비정렬·격리 조건 등에서는 SWIOTLB가 쓰일 수 있다.

coherent memory라도 descriptor/data store가 device MMIO doorbell보다 먼저 보이도록 dma_wmb가 필요하며 posted MMIO completion은 별도 readback을 요구할 수 있다. 디버깅할 때는 dma_mask/coherent_dma_mask와 실제 direct/IOMMU/SWIOTLB 분기, IOVA·BDF requester ID·IOMMU domain, 선택된 경우의 bounce slot과 fault record를 확인한다.

04

RISC-V: dma-noncoherent cache block operation과 RISC-V IOMMU

platform coherency가 보장되지 않으면 Zicbom cache-block clean/flush/inval 또는 platform callback으로 streaming mapping을 동기화한다. IOMMU가 있는 시스템은 device ID/process ID context로 IOVA를 변환하지만 구현과 firmware description을 함께 확인해야 한다.

CBO operation 전후 fence와 device descriptor barrier가 CPU cache ownership과 DMA 시작 순서를 만든다. 디버깅할 때는 riscv_cbom_block_size, dma-noncoherent flag, direction, IOVA/PA, device ID와 IOMMU fault queue를 본다.

05

객체를 사용하고 해제하는 주체와 시점을 확인한다

buffer page는 device DMA가 끝나기 전에 새 소유자에게 넘기지 않는다. IOMMU 매핑을 쓴 경우 IOVA 재사용은 unmap과 요구되는 IOTLB 동기화 뒤에 가능하다. direct/SWIOTLB 경로를 모두 IOVA 해제 경로로 그리지 않는다.

주소나 레지스터 값만 확인하면 갱신되지 않은 상태를 놓칠 수 있다. 상태를 만드는 코드, 다른 코드에 전달하는 시점, 사용하는 코드, 해제하는 시점을 함께 기록한다.

06

최대 지연 시간은 명령 하나의 실행 시간만으로 결정되지 않는다

direct DMA는 cache clean/invalidate byte 수가, IOMMU DMA는 IOVA allocation과 page-table map, IOTLB invalidation이 지배한다. bounce buffer는 추가 copy와 제한된 pool 대기를 만든다.

평균값뿐 아니라 인터럽트를 비활성화한 시간, 다른 CPU의 응답 시간, 펌웨어 호출 시간, 재시도 횟수를 따로 확인해야 최대 지연의 원인을 찾을 수 있다.

07 · FAILURE

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64direction을 반대로 지정하거나 partial cache line ownership을 공유하면 CPU dirty data 손실 또는 stale device data 관찰이 생긴다.device dma_coherent 속성, DMA direction, cache line 정렬, StreamID, IOVA/PA, SMMU STE/CD와 IOTLB sync를 본다.
x86-64부적절한 32-bit DMA mask는 IOMMU 재매핑·SWIOTLB bounce·매핑 실패 중 다른 결과로 이어질 수 있다. IOMMU group 격리 실패는 보호 경계를 깨고, unmap 없는 장기 pin은 memory pressure를 키운다.dma_mask/coherent_dma_mask와 실제 direct/IOMMU/SWIOTLB 분기, IOVA·BDF requester ID·IOMMU domain, 선택된 경우의 bounce slot과 fault record를 확인한다.
RISC-Vcache block size 오인, unaligned buffer의 이웃 data 손상 또는 cache maintenance extension이 없는 platform의 잘못된 direct mapping이 data corruption을 만든다.riscv_cbom_block_size, dma-noncoherent flag, direction, IOVA/PA, device ID와 IOMMU fault queue를 본다.

08 · LAB

재현과 계측 절차

  1. 같은 driver를 IOMMU passthrough와 translated mode에서 실행해 map/unmap latency와 IOTLB miss를 비교한다.
  2. non-coherent QEMU/platform에서 sync 호출 하나를 의도적으로 빼 stale line이 어느 방향에서 나타나는지 확인한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinux의 objdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 위치

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.