Linux 6.18 문서 · 6.18.37 커널

DMA 버퍼는 누가 읽고 쓰고 있습니까?

가상 주소·물리 주소·DMA 주소를 나누고, 버퍼를 CPU와 장치 사이에 넘기는 순서를 따라갑니다.

주소 세 개를 같은 숫자로 생각하면 안 됩니다

드라이버의 void *buf는 CPU가 사용하는 가상 주소입니다. 장치에 넘길 dma_addr_t dma는 장치가 사용하는 DMA 주소입니다. 같은 RAM을 가리켜도 두 값은 다를 수 있습니다. IOMMU가 있으면 DMA 주소에서 물리 주소로 변환하고, 그 밖의 플랫폼에서는 버스의 주소 변환이나 bounce buffer가 관여할 수 있습니다. RAM 용량이 4 GiB라고 해서 물리 주소가 반드시 0부터 4 GiB 직전까지 연속이라는 뜻도 아닙니다.

같은 데이터에 도달하는 두 경로
  1. CPU의 접근

    buf → CPU의 페이지 테이블 → RAM의 데이터

    아래 경로와 도착하는 데이터는 같지만 주소를 해석하는 주체가 다릅니다.

  2. 장치의 접근

    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_packetCPU가 보낼 데이터를 씁니다. 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를 사용해야 합니다.

캐시 구조 정보는 버퍼 동기화 절차가 아닙니다

Cortex-A53의 CLIDR_EL1은 캐시 계층과 종류를, CCSIDR_EL1은 선택한 캐시의 집합·연관도·라인 크기 정보를 제공합니다. Arm DDI 0500E §4.3.22–25의 이 정보로 특정 구성의 구조를 해석할 수 있지만, 그것만으로 사용자 가상 주소나 DMA 버퍼에 어떤 유지보수 함수를 호출할지가 결정되지는 않습니다. 가상 주소 범위를 지정하는 작업과 캐시 집합·웨이를 순회하는 작업도 같은 요청이 아닙니다. 오래된 전체 캐시 순회 어셈블리를 현재 드라이버의 버퍼 동기화 절차로 옮기지 않습니다.

Linux v6.18.37의 캐시 문서는 페이지 캐시의 별칭 일관성을 다루는 flush_dcache_folio()와 실행할 코드를 위한 flush_icache_range()를 별도 계약으로 설명합니다. 장치와 주고받는 버퍼는 앞에서 설명한 DMA 매핑·동기화·해제 계약을 따라야 합니다. CPU의 사용자·커널 가상 주소, 물리 주소, 장치의 DMA 주소를 서로 바꿔 넘기지 말고, 매핑과 길이·방향·소유권을 확인합니다. 한 보드에서 읽은 캐시 크기나 문맥 전환 여부만으로 동기화가 끝났다고 판단하지 않습니다.

Framebuffer DMA에는 영상 형식 계약이 추가됩니다

AMD Video Framebuffer Read/Write는 일반 DMAEngine descriptor 외에 영상의 메모리 형식을 알아야 합니다. 아래 내용은 linux-xlnx 고정 커밋 235bd5e49f53의 구현을 읽은 사례입니다. 모든 DMA 컨트롤러나 IP 버전에 적용하는 설정법은 아닙니다.

xilinx_xdma_v4l2_config() 또는 xilinx_xdma_drm_config()가 FourCC에 대응하는 형식을 선택합니다. 헤더 계약은 dma_async_issue_pending() 전에 호출하도록 요구하며, 실제 xilinx_frmbuf_dma_prep_interleaved()도 vid_fmt가 없으면 실패합니다. 따라서 이 구현에서는 descriptor 준비 전에 형식을 정해야 합니다. 설정 함수는 void이므로 호출했다는 사실만으로 요청 형식의 지원·적용 성공을 판정하지 않습니다.

값이 드라이버에서의 의미
numf영상의 행 수입니다. 연속 영상 프레임 개수로 읽지 않습니다.
frame_size선택한 형식의 plane 수와 같아야 합니다. packed와 semi-planar 배치는 다릅니다.
sgl[0].size + icg선형 배치의 행 stride입니다. 유효 행 바이트와 행 사이 padding을 더합니다. tiled 형식은 별도 pitch 계산 경로입니다.
src_start / dst_start전송 방향에 따른 첫 plane의 DMA 주소입니다. CPU 가상 주소나 임의의 물리 주소로 대체하지 않습니다.

선형 두-plane 전송에서는 첫 chroma 주소를 첫 plane 주소에 numf × stride와 방향별 src_icg 또는 dst_icg를 더해 계산합니다. 예를 들어 1,080행, 행 데이터 1,920바이트, 행 padding 128바이트, plane 간격 0이면 chroma 시작은 첫 DMA 주소보다 2,211,840바이트 뒤입니다. 이 계산은 전체 할당 크기나 모든 픽셀 형식의 규칙을 대신하지 않습니다.

callback의 시점과 버퍼 완료는 구별합니다

일반 완료 경로는 active descriptor의 cookie를 완료하고 done_list에 넣은 뒤 cleanup에서 callback을 실행합니다. 그러나 자동 재시작 모드는 staged descriptor를 따로 두며, EARLY_CALLBACK이나 EARLY_CALLBACK_START_DESC는 더 이른 알림을 선택할 수 있습니다. 특히 후자는 주소 레지스터를 쓰기 전에도 callback을 부릅니다. 모드와 callback 계약을 확인하지 않고 그 알림을 CPU의 버퍼 재사용 허가로 해석하지 않습니다. 위의 DMA 매핑·동기화·소유권 조건도 그대로 필요합니다.

V4L2 버퍼 흐름과 DMAEngine client 계약은 별도로 읽습니다. 이 절은 고정 소스의 설명이며 FPGA 전송·영상 출력·cache 동기화를 실기기에서 시험한 결과가 아닙니다.

English: format, geometry and callback timing

The pinned driver needs a selected V4L2/DRM format before preparing an interleaved descriptor. Here numf counts rows, frame_size matches planes, and linear stride adds row bytes and padding. The two-plane address example is not a universal allocation rule. DMA addresses remain device-facing mappings. Early callbacks can precede completion, so their arrival alone does not return buffer ownership to the CPU. No hardware transfer was tested.

접근 권한과 fd 수명

DMA 주소가 올바르다는 사실은 프로세스가 그 드라이버나 버퍼에 접근해도 된다는 허가가 아닙니다. 먼저 노드의 존재, 디렉터리 목록 조회, 해당 주체의 open, 열린 fd에서 허용되는 작업을 구분합니다. Android의 Unix 권한과 SELinux domain별 정책은 서로 다른 판단 요소입니다. root 권한이 있다고 SELinux의 강제 접근 통제가 없어지는 것도 아닙니다. AOSP의 SELinux 설명

Project Zero의 2024년 조사는 특정 기기에서 디렉터리를 열람할 수 없어도 알려진 노드의 경로에는 접근할 수 있는 경우와, 허용된 서비스가 fd를 전달하는 별도 접근 경로를 설명합니다. 이는 조사 대상 기기·정책의 사례이지 현재 모든 Android 기기의 권한표가 아닙니다. open 성공이나 fd 수신만으로 임의의 ioctl·mmap까지 모두 허용됐다고 판단하지 않습니다.

권한을 얻는 경로와 객체를 유지하는 참조를 따로 확인합니다
대상확인할 경계다른 의미로 바꾸어 읽지 않기
경로와 작업누가 어떤 domain·UID로 열고 어떤 작업을 요청하는가목록에서 발견함 ≠ open 가능 ≠ 모든 작업 허용
전달된 fd송신 주체의 제공 권한, 수신 주체와 허용 작업서비스가 열었다고 수신자에게 무제한 권한이 생기지 않음
dma-buf 참조dma_buf_get()의 참조와 dma_buf_put()의 해제한 fd를 닫음 ≠ 모든 커널 참조가 사라짐
장치 attachment·mappingattach/detach, map/unmap의 각 짝과 장치의 사용 완료참조 보유 ≠ DMA 완료 ≠ CPU가 즉시 내용을 읽어도 됨

Linux v6.18.37의 dma_buf_get(fd)는 fget()으로 file 참조를 얻고 dma-buf 파일인지 검사합니다. dma_buf_put()은 해당 file에 fput()을 적용합니다. fd 번호, struct file, struct dma_buf, 장치 attachment와 DMA mapping은 같은 객체가 아닙니다. 다른 참조가 남아 있다면 한 fd를 닫는 것만으로 버퍼 수명이 끝났다고 볼 수 없습니다.

일반적인 importer의 사용 종료는 장치의 버퍼 사용이 끝난 뒤 mapping을 정리하고 dma_buf_detach()를 호출한 다음, 얻어 둔 참조를 dma_buf_put()으로 반환하는 식으로 각 성공한 단계와 짝을 맞춥니다. 실패·취소·동시 접근 경로도 같은 수명 조건을 지켜야 합니다. 참조 계수는 앞서 설명한 DMA 완료 확인과 CPU cache 동기화를 대신하지 않습니다. 기본 수명 계약 · get/put 구현

FD_CLOEXEC는 exec로 새 프로그램을 실행할 때 fd가 의도치 않게 남는 것을 막는 조건입니다. fork 직후 fd를 자동으로 닫는 옵션은 아닙니다. 생성과 별도의 fcntl() 사이에는 다른 스레드의 exec와 경쟁할 틈이 있으므로, dma-buf 문서는 fd 생성 API가 O_CLOEXEC를 원자적으로 지정할 수 있어야 한다고 설명합니다. 이것만으로 이미 전달한 fd의 사용 권한이나 모든 참조를 회수하는 것은 아닙니다. dma-buf userspace 계약

일반 fd 전달 원리는 SCM_RIGHTS의 file 참조 설명, 장치 접근 정책은 HAL의 권한 구분과 연결해 읽습니다. 위 Linux API를 과거 벤더 드라이버의 특정 결함과 같은 구현으로 취급하지 않습니다. 이 절은 방어적 소스·권한 모델 설명이며, 실제 기기 접근·취약점 재현이나 정책 변경을 수행한 결과가 아닙니다.

English summary

Directory visibility, path-based open, brokered descriptors and permitted operations are separate authorization boundaries. A dma-buf file reference, attachment and device mapping have distinct lifetimes; none alone proves DMA completion or CPU coherency. CLOEXEC concerns exec inheritance and should be requested atomically at descriptor creation. The 2024 device observations are historical, not a universal Android policy. No device probing or exploit reproduction was performed.

Coherent라는 계약이 항상 uncached라는 뜻은 아닙니다

dma_alloc_coherent()의 핵심은 CPU와 장치 사이의 가시성 계약입니다. 모든 아키텍처에서 반드시 uncached 매핑을 만들거나 set_memory_uc()를 호출한다는 뜻은 아닙니다. 하드웨어 일관성 지원과 플랫폼 구현에 따라 달라지므로 드라이버가 특정 CPU의 캐시 명령이나 내부 구현을 일반 규칙으로 대신하지 않습니다.

Streaming mapping은 최초 map과 마지막 unmap이 소유권 전환에 필요한 처리를 포함합니다. 매핑을 유지한 채 CPU가 다시 버퍼를 만질 때는 장치 완료를 먼저 확인하고 해당 방향·범위의 dma_sync_*_for_cpu() 계약을 따릅니다. 장치에 다시 넘길 때는 dma_sync_*_for_device()가 대응합니다. coherent 플랫폼에서 일부 sync가 별도 캐시 명령을 내지 않더라도 공통 API의 의미가 사라지는 것은 아닙니다.

캐시 가시성, descriptor 게시 순서, DMA 완료는 서로 다른 조건입니다. 앞의 barrier와 소유권 절차가 여전히 필요합니다. 기준: Linux Dynamic DMA mapping Guide. 특정 CPU에서 모든 map이 cache flush라는 주장이나 실제 장치 측정은 포함하지 않습니다.

English summary. Coherent is an API visibility contract, not a universal uncached-memory implementation. Streaming reuse must respect CPU/device ownership and sync rules. Cache visibility does not replace publication barriers or completion checks.