주소 세 개를 같은 숫자로 생각하면 안 됩니다
드라이버의 void *buf는 CPU가 사용하는 가상 주소입니다. 장치에 넘길 dma_addr_t dma는 장치가 사용하는 DMA 주소입니다. 같은 RAM을 가리켜도 두 값은 다를 수 있습니다. IOMMU가 있으면 DMA 주소에서 물리 주소로 변환하고, 그 밖의 플랫폼에서는 버스의 주소 변환이나 bounce buffer가 관여할 수 있습니다. RAM 용량이 4 GiB라고 해서 물리 주소가 반드시 0부터 4 GiB 직전까지 연속이라는 뜻도 아닙니다.
- CPU의 접근
buf → CPU의 페이지 테이블 → RAM의 데이터
아래 경로와 도착하는 데이터는 같지만 주소를 해석하는 주체가 다릅니다.
- 장치의 접근
dma → IOMMU 또는 플랫폼의 DMA 주소 처리 → RAM의 데이터
각 경로의 →는 주소 변환·접근 관계입니다. 데이터가 세 번 복사된다는 뜻이 아닙니다. SWIOTLB를 사용하면 별도 버퍼를 경유할 수 있습니다.
따라서 virt_to_phys(buf)를 계산해 장치 레지스터에 쓰는 방식으로 DMA API를 대신하지 않습니다. ioremap()은 CPU가 MMIO 레지스터에 접근할 매핑을 만드는 함수입니다. DMA 버퍼를 장치에 등록하는 함수가 아닙니다. CPU 주소와 DMA 주소 SWIOTLB의 bounce buffer
송신 버퍼 하나의 수명을 따라갑니다
아래는 동작 순서를 설명하는 의사 코드입니다. start_tx()와 wait_tx_finished()는 실제 커널 API가 아니라 보드의 드라이버가 구현해야 할 부분입니다. 이 경로가 반환할 버퍼는 DMA API에 적합한 메모리에서 확보했고 장치의 DMA mask도 설정했다고 가정합니다. 매핑 이후에는 전송이 정상 완료되는 경로를 보여 줍니다.
fill_packet(buf, size);
dma = dma_map_single(dev, buf, size, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma)) {
release_packet(buf);
return -EIO;
}
start_tx(dma, size);
wait_tx_finished();
dma_unmap_single(dev, dma, size, DMA_TO_DEVICE);
release_packet(buf);| 줄 | 개념적으로 일어나는 일 |
|---|---|
fill_packet | CPU가 보낼 데이터를 씁니다. buf는 데이터 자체가 아니라 버퍼의 CPU 가상 주소입니다. |
dma_map_single | 이 장치의 전송에 사용할 매핑을 준비합니다. DMA_TO_DEVICE는 RAM에서 장치로 데이터를 보내는 방향입니다. 장치 전송을 시작하지는 않습니다. |
dma_mapping_error | 매핑 실패를 검사합니다. dma의 숫자가 0인지 여부만으로 성공을 판정하지 않습니다. 실패하면 이 예제가 소유한 버퍼를 반환하며, 성공하지 않은 mapping을 unmap하지 않습니다. |
start_tx | 반환된 DMA 주소와 길이를 장치가 읽을 descriptor나 레지스터에 기록하고 전송을 시작합니다. 필요한 ordering은 장치 규약에 맞춥니다. |
wait_tx_finished | 장치가 버퍼 사용을 끝냈다는 사실을 확인합니다. 단순히 얼마간 기다리는 것으로 대체하지 않습니다. |
dma_unmap_single | 해당 매핑의 사용을 끝냅니다. dev·주소·크기·방향은 매핑 때와 짝이 맞아야 합니다. |
release_packet | 장치가 더 이상 접근하지 않는 상태에서 메모리를 반환합니다. |
이 예제는 CPU와 장치가 같은 streaming 버퍼를 동시에 고쳐 쓰지 않게 구성했습니다. 실제 드라이버는 완료 인터럽트, 취소, timeout, 장치 제거 때에도 이 수명이 깨지지 않게 해야 합니다. DMA API의 매핑·동기화 규칙
매핑을 유지하면서 버퍼를 재사용할 수도 있습니다
1. 장치에 넘기기
드라이버가 DMA_FROM_DEVICE 방향으로 매핑하고 장치에 DMA 주소를 넘깁니다. 장치가 데이터를 쓰는 동안 CPU는 그 버퍼 내용을 읽거나 수정하지 않습니다.
2. 장치의 완료 확인
완료 상태를 확인한 뒤 dma_sync_single_for_cpu()를 호출합니다. 완료 확인과 cache 동기화는 별개입니다. sync 호출 자체가 장치 전송을 끝내 주지는 않습니다.
3. CPU가 데이터 처리
CPU는 buf를 통해 수신 내용을 읽습니다. DMA_FROM_DEVICE로 유지한 매핑에서 CPU가 내용을 고쳐 다시 장치에 전달해야 한다면 처음부터 맞는 방향과 프로토콜을 설계해야 합니다.
4. 장치에 다시 넘기기
CPU 접근을 마친 뒤 dma_sync_single_for_device()를 호출하고 다음 수신을 허용합니다. 마지막 사용 뒤에는 unmap합니다.
단계 이동은 버퍼를 사용할 권한이 CPU와 장치 사이에서 바뀌는 순서를 뜻합니다. 버퍼가 매번 새 주소로 이동하는 그림은 아닙니다.
Streaming은 “한 번만 전송할 수 있는 메모리”라는 뜻이 아닙니다. 매핑을 유지하고 적절히 동기화해 여러 번 사용할 수도 있습니다. Coherent도 “CPU가 읽을 때 장치 작업이 이미 끝나 있다”는 뜻이 아닙니다. cache 일관성과 작업 완료는 서로 다른 조건입니다.
Coherent여도 순서와 완료 확인은 필요합니다
장치가 descriptor의 valid 비트를 보고 일을 시작한다면, 주소와 길이를 먼저 보게 하고 valid를 마지막에 보게 해야 합니다. Coherent 메모리는 cache 관리를 생략할 수 있게 해 주지만, 드라이버가 원하는 여러 메모리 접근의 순서를 모두 대신 정해 주지는 않습니다. dma_wmb() 같은 barrier와 MMIO 접근자 사용 여부는 descriptor·장치 규약을 따라야 합니다. DMA와 MMIO의 ordering
| 표현 | 바로잡아 읽을 내용 |
|---|---|
| DMA는 CPU가 전혀 관여하지 않습니다. | CPU가 바이트를 하나씩 운반하는 부담을 줄이는 방식입니다. 설정·매핑·완료 처리에는 보통 CPU와 드라이버가 관여합니다. |
| PIO는 읽을 때마다 인터럽트가 발생합니다. | PIO와 인터럽트 사용 여부는 별개입니다. polling으로 상태를 확인할 수도 있습니다. |
| flush는 cache를 0으로 지웁니다. | 문맥에 따라 clean, invalidate 또는 조합을 뜻합니다. 메모리 내용을 0으로 채우는 작업과 같지 않습니다. |
| vmalloc 주소를 그대로 map_single에 넣습니다. | 임의의 vmalloc 주소를 그대로 사용할 수 없습니다. 기반 페이지와 scatter/gather 등 맞는 API를 사용해야 합니다. |