DMA Engine (DMA 컨트롤러 프레임워크)

DMA Engine 프레임워크를 컨트롤러 하드웨어 추상화와 클라이언트 드라이버 재사용 관점에서 심층 분석합니다. Provider/Consumer API 경계, dma_device/dma_chan/dma_async_tx_descriptor 생명주기, Slave SG/Cyclic/Memcpy/Interleaved 전송 모델의 선택 기준, async_tx 오프로딩 활용, completion/interrupt 처리와 backlog 제어, dmatest 기반 검증 절차, 오디오·네트워크·산업장치 실전 적용 패턴, 지연시간·처리량 튜닝과 오류 복구까지 DMA 컨트롤러 드라이버 품질 향상에 필요한 내용을 다룹니다.

관련 표준: DMA Engine API (kernel.org) — 커널 DMA 컨트롤러 추상화 계층 인터페이스입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 문서: 커널 공식 DMA Engine 문서는 Documentation/driver-api/dmaengine/ 디렉터리에 있습니다. provider.rst(컨트롤러 드라이버), client.rst(클라이언트 API), dmatest.rst(테스트 모듈)를 참조하십시오.
교차 참조: DMA 매핑 API(Coherent/Streaming/SG/IOMMU)는 DMA 심화, 디바이스 모델 기초는 디바이스 드라이버, 오디오 DMA 활용은 ALSA, SPI/I2C DMA는 I2C/SPI/GPIO 페이지를 참조하십시오.
전제 조건: DMAIOMMU 문서를 먼저 읽으세요. DMA 경로는 CPU 우회 데이터 이동과 주소 변환 보호가 결합되므로, 매핑 수명주기와 동기화 지점을 먼저 고정해야 합니다.

핵심 요약

  • DMA Engine — DMA 컨트롤러 하드웨어를 추상화하는 커널 프레임워크로, drivers/dma/에 위치합니다.
  • Provider vs Consumer — Provider는 DMA 컨트롤러 드라이버(HW 제어), Consumer는 이를 사용하는 클라이언트(SPI, I2C, 오디오 등)입니다.
  • 핵심 자료구조dma_device(컨트롤러), dma_chan(채널), dma_async_tx_descriptor(전송 디스크립터)가 핵심입니다.
  • 전송 유형 — memcpy(메모리 복사), slave(주변장치↔메모리), cyclic(순환 DMA), interleaved(스트라이드) 등을 지원합니다.

단계별 이해

  1. DMA Engine이 필요한 이유 — SoC에는 여러 DMA 컨트롤러가 존재하고, 각각 다른 레지스터/프로토콜을 사용합니다.

    DMA Engine 프레임워크는 이를 통일된 API로 추상화하여 클라이언트가 하드웨어 세부사항을 몰라도 DMA를 사용할 수 있게 합니다.

  2. 채널 요청dma_request_chan()으로 DMA 채널을 획득합니다. Device Tree의 dmas 프로퍼티로 매핑됩니다.
  3. 전송 설정dmaengine_slave_config()로 버스 폭, 버스트 크기 등을 설정합니다.
  4. 디스크립터 준비dmaengine_prep_slave_sg() 등으로 전송 디스크립터를 생성합니다.
  5. 전송 실행dmaengine_submit()으로 큐에 넣고, dma_async_issue_pending()으로 실제 전송을 시작합니다.

DMA Engine 개요

데이터 전송 방식은 PIO(Programmed I/O) → 인터럽트 구동 I/O → DMA → DMA Engine으로 발전해 왔습니다.

데이터 전송 발전 과정

단계방식특징한계
1PIOCPU가 직접 I/O 포트로 바이트 전송CPU 100% 점유, 느림
2인터럽트 구동디바이스 준비 시 인터럽트로 알림여전히 CPU가 데이터 복사
3DMA (하드웨어)DMA 컨트롤러가 CPU 대신 전송컨트롤러마다 다른 레지스터/프로토콜
4DMA Engine커널 프레임워크가 DMA 컨트롤러 추상화통일된 API, 다양한 전송 유형 지원

dmaengine 서브시스템의 역할

dmaengine 서브시스템은 다음을 제공합니다:

디렉터리 구조

/* 핵심 프레임워크 */
drivers/dma/dmaengine.c          /* 코어 로직 */
drivers/dma/of-dma.c             /* Device Tree DMA 매핑 */
drivers/dma/acpi-dma.c           /* ACPI DMA 매핑 */
drivers/dma/virt-dma.c           /* 가상 디스크립터 헬퍼 */
include/linux/dmaengine.h        /* 주요 헤더 */

/* 컨트롤러 드라이버 예시 */
drivers/dma/pl330.c              /* ARM PL330 */
drivers/dma/ioat/                /* Intel IOAT (Crystal Beach) */
drivers/dma/ti/                  /* TI eDMA, K3 UDMA */
drivers/dma/dw/                  /* Synopsys DesignWare DMA */
drivers/dma/stm32-dma.c          /* STM32 DMA */
drivers/dma/fsl-edma.c           /* Freescale/NXP eDMA */

아키텍처

Provider vs Consumer 모델

DMA Engine은 Provider-Consumer 패턴을 사용합니다:

Consumer (클라이언트 드라이버) SPI 드라이버 UART 드라이버 ALSA (오디오) async_tx 네트워크 dma_request_chan() / dmaengine_prep_*() / dmaengine_submit() DMA Engine Core (dmaengine.c) dma_async_device_register() / callbacks Provider (DMA 컨트롤러 드라이버) PL330 드라이버 DW-DMA 드라이버 IOAT 드라이버 eDMA 드라이버 DMA 컨트롤러 하드웨어 (PL330, IOAT, eDMA, DW-DMA ...)

핵심 자료구조

struct dma_device

DMA 컨트롤러 전체를 표현합니다. Provider가 이 구조체를 채워서 등록합니다.

struct dma_device {
    struct list_head channels;           /* 이 컨트롤러의 채널 목록 */
    unsigned int chancnt;                 /* 채널 수 */
    dma_cap_mask_t cap_mask;              /* 지원 capability 비트마스크 */
    struct device *dev;                    /* 부모 디바이스 */

    /* Provider 콜백 함수 포인터 */
    int (*device_alloc_chan_resources)(struct dma_chan *chan);
    void (*device_free_chan_resources)(struct dma_chan *chan);
    struct dma_async_tx_descriptor *(*device_prep_dma_memcpy)(...);
    struct dma_async_tx_descriptor *(*device_prep_slave_sg)(...);
    struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(...);
    enum dma_status (*device_tx_status)(...);
    void (*device_issue_pending)(struct dma_chan *chan);
    int (*device_terminate_all)(struct dma_chan *chan);
    ...
};

struct dma_chan

DMA 채널 하나를 표현합니다. 클라이언트는 채널을 획득하여 전송을 수행합니다.

struct dma_chan {
    struct dma_device *device;      /* 소속 DMA 컨트롤러 */
    dma_cookie_t cookie;             /* 마지막 제출된 쿠키 */
    dma_cookie_t completed_cookie;   /* 마지막 완료된 쿠키 */
    int chan_id;                      /* 채널 번호 */
    struct dma_chan_dev *dev;         /* sysfs 디바이스 */
    const char *name;               /* consumer 이름 */
    struct list_head device_node;    /* dma_device->channels 리스트 */
    ...
};

struct dma_async_tx_descriptor

하나의 DMA 전송을 표현하는 디스크립터입니다. dmaengine_prep_*()가 반환합니다.

struct dma_async_tx_descriptor {
    dma_cookie_t cookie;              /* 전송 식별 쿠키 */
    struct dma_chan *chan;             /* 전송할 채널 */
    dma_async_tx_callback callback;   /* 완료 콜백 */
    void *callback_param;              /* 콜백 파라미터 */
    struct dma_async_tx_descriptor *next; /* 체인 연결 */
    enum dma_ctrl_flags flags;        /* DMA_PREP_INTERRUPT 등 */
    ...
};

struct dma_slave_config

슬레이브 전송에 필요한 설정 정보를 담습니다.

struct dma_slave_config {
    enum dma_transfer_direction direction;   /* DMA_MEM_TO_DEV 등 */
    phys_addr_t src_addr;                     /* 소스 FIFO 물리 주소 */
    phys_addr_t dst_addr;                     /* 목적지 FIFO 물리 주소 */
    enum dma_slave_buswidth src_addr_width;   /* 1/2/4/8 바이트 */
    enum dma_slave_buswidth dst_addr_width;
    u32 src_maxburst;                         /* 소스 버스트 크기 */
    u32 dst_maxburst;                         /* 목적지 버스트 크기 */
    bool device_fc;                           /* 디바이스 흐름 제어 */
    ...
};

Provider API (컨트롤러 드라이버)

DMA 컨트롤러 드라이버(Provider)는 struct dma_device를 채운 뒤 dma_async_device_register()로 프레임워크에 등록합니다.

필수 콜백 구현

콜백역할필수 여부
device_alloc_chan_resources채널 리소스 할당 (디스크립터 풀 등)필수
device_free_chan_resources채널 리소스 해제필수
device_prep_dma_memcpymemcpy 전송 디스크립터 생성DMA_MEMCPY 지원 시
device_prep_slave_sgslave scatter-gather 디스크립터 생성DMA_SLAVE 지원 시
device_prep_dma_cycliccyclic 전송 디스크립터 생성DMA_CYCLIC 지원 시
device_prep_interleaved_dmainterleaved 전송 디스크립터 생성DMA_INTERLEAVE 지원 시
device_issue_pending보류 중인 전송을 하드웨어에 전달필수
device_tx_status전송 상태 조회 (완료/진행 중)필수
device_terminate_all진행 중인 모든 전송 취소권장
device_configslave 설정 적용DMA_SLAVE 지원 시
device_pause/device_resume전송 일시정지/재개선택

등록 / 해제

/* 등록 */
int dma_async_device_register(struct dma_device *device);

/* 해제 */
void dma_async_device_unregister(struct dma_device *device);

간단한 Provider 프레임

static int my_dma_probe(struct platform_device *pdev)
{
    struct my_dma_dev *mydev;
    struct dma_device *dma_dev;

    mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
    dma_dev = &mydev->dma_dev;

    /* capability 설정 */
    dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);
    dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask);

    /* 콜백 등록 */
    dma_dev->dev = &pdev->dev;
    dma_dev->device_alloc_chan_resources = my_alloc_chan_resources;
    dma_dev->device_free_chan_resources = my_free_chan_resources;
    dma_dev->device_prep_slave_sg = my_prep_slave_sg;
    dma_dev->device_prep_dma_cyclic = my_prep_dma_cyclic;
    dma_dev->device_config = my_device_config;
    dma_dev->device_issue_pending = my_issue_pending;
    dma_dev->device_tx_status = my_tx_status;
    dma_dev->device_terminate_all = my_terminate_all;

    /* 채널 초기화 */
    INIT_LIST_HEAD(&dma_dev->channels);
    for (int i = 0; i < NUM_CHANNELS; i++) {
        mydev->chans[i].vchan.desc_free = my_desc_free;
        vchan_init(&mydev->chans[i].vchan, dma_dev);
    }

    return dma_async_device_register(dma_dev);
}
virt-dma 헬퍼: 대부분의 Provider 드라이버는 drivers/dma/virt-dma.hvchan_init(), vchan_tx_submit(), vchan_find_desc() 등을 활용하여 디스크립터 관리를 단순화합니다.

DMA_PRIVATE 채널

DMA_PRIVATE 플래그를 설정하면 해당 컨트롤러의 채널은 dma_request_chan()(Device Tree/ACPI 기반)으로만 요청할 수 있고, 범용 memcpy용으로 할당되지 않습니다. 대부분의 SoC DMA 컨트롤러가 이 모드를 사용합니다.

dma_cap_set(DMA_PRIVATE, dma_dev->cap_mask);

Consumer API (클라이언트)

채널 요청

/* 권장 API: Device Tree/ACPI 기반 채널 요청 */
struct dma_chan *dma_request_chan(struct device *dev, const char *name);

/* 채널 해제 */
void dma_release_channel(struct dma_chan *chan);

Device Tree 바인딩

/* Device Tree 예시 */
spi1: spi@48030000 {
    compatible = "ti,omap4-mcspi";
    dmas = <&edma 42 0>, <&edma 43 0>;
    dma-names = "tx0", "rx0";
};

클라이언트 드라이버에서:

struct dma_chan *tx_chan = dma_request_chan(dev, "tx0");
struct dma_chan *rx_chan = dma_request_chan(dev, "rx0");
if (IS_ERR(tx_chan))
    return PTR_ERR(tx_chan);

슬레이브 설정

struct dma_slave_config cfg = {
    .direction     = DMA_MEM_TO_DEV,
    .dst_addr      = spi_base + SPI_TX_REG,  /* FIFO 물리 주소 */
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
    .dst_maxburst  = 16,
};
dmaengine_slave_config(tx_chan, &cfg);

디스크립터 준비

Slave Scatter-Gather

struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_slave_sg(chan, sg, nents,
    DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);

Cyclic DMA (오디오 등)

desc = dmaengine_prep_dma_cyclic(chan,
    buf_addr,          /* DMA 버퍼 주소 */
    buf_len,           /* 전체 버퍼 크기 */
    period_len,        /* 한 주기 크기 */
    DMA_MEM_TO_DEV,    /* 방향 */
    DMA_PREP_INTERRUPT);

Memcpy

desc = dmaengine_prep_dma_memcpy(chan, dst_dma, src_dma, len,
    DMA_PREP_INTERRUPT | DMA_CTRL_ACK);

전송 제출과 실행

/* 1. 콜백 설정 */
desc->callback = my_dma_complete_callback;
desc->callback_param = my_data;

/* 2. 큐에 제출 (아직 전송 시작 안 함) */
dma_cookie_t cookie = dmaengine_submit(desc);

/* 3. 보류 중인 모든 전송을 하드웨어에 전달 */
dma_async_issue_pending(chan);
주의: dmaengine_submit()은 전송을 큐에 넣기만 합니다. 실제 전송은 dma_async_issue_pending()을 호출해야 시작됩니다. 이 2단계 분리는 여러 전송을 배치로 제출한 뒤 한 번에 실행하기 위함입니다.

완료 확인

/* 방법 1: 콜백 (비동기) */
static void my_dma_complete_callback(void *param)
{
    struct my_data *data = param;
    complete(&data->dma_done);
}

/* 방법 2: 폴링 */
enum dma_status status;
struct dma_tx_state state;
status = dma_async_is_tx_complete(chan, cookie, NULL, &state);
/* status: DMA_COMPLETE, DMA_IN_PROGRESS, DMA_ERROR, DMA_PAUSED */

/* 방법 3: 동기 대기 */
dma_wait_for_async_tx(desc);

전체 Consumer 흐름 요약

/* ① 채널 요청 */
chan = dma_request_chan(dev, "rx");

/* ② 슬레이브 설정 */
dmaengine_slave_config(chan, &cfg);

/* ③ 디스크립터 준비 */
desc = dmaengine_prep_slave_sg(chan, sg, nents, dir, flags);

/* ④ 콜백 등록 */
desc->callback = my_callback;

/* ⑤ 제출 */
cookie = dmaengine_submit(desc);

/* ⑥ 실행 */
dma_async_issue_pending(chan);

/* ⑦ 완료 대기 */
wait_for_completion(&done);

/* ⑧ 채널 해제 (종료 시) */
dmaengine_terminate_sync(chan);
dma_release_channel(chan);

전송 유형

DMA Capability 유형

Capability매크로설명사용 사례
Memory CopyDMA_MEMCPY메모리 → 메모리 복사Intel IOAT memcpy offload, 대용량 메모리 복사
Memory SetDMA_MEMSET메모리를 특정 값으로 채움버퍼 초기화
SlaveDMA_SLAVE디바이스 ↔ 메모리 (scatter-gather)SPI, I2C, UART 데이터 전송
CyclicDMA_CYCLIC순환 버퍼 전송 (링 버퍼)오디오 PCM 재생/녹음, ADC 연속 샘플링
InterleavedDMA_INTERLEAVE스트라이드 패턴 전송2D DMA, 프레임버퍼 전송
XORDMA_XORXOR 연산 (RAID5 패리티)async_tx RAID5 가속
PQDMA_PQP+Q 연산 (RAID6)async_tx RAID6 가속

전송 방향

방향매크로설명
메모리 → 메모리DMA_MEM_TO_MEMmemcpy, memset 전송
메모리 → 디바이스DMA_MEM_TO_DEVTX: RAM에서 디바이스 FIFO로
디바이스 → 메모리DMA_DEV_TO_MEMRX: 디바이스 FIFO에서 RAM으로
디바이스 → 디바이스DMA_DEV_TO_DEV디바이스 간 직접 전송 (드문 경우)

전송 플래그

플래그설명
DMA_PREP_INTERRUPT전송 완료 시 인터럽트/콜백 발생
DMA_CTRL_ACK프레임워크가 디스크립터를 재사용 가능하게 표시
DMA_PREP_CMD명령 DMA 전송 (특수 하드웨어)
DMA_PREP_REPEAT반복 전송 (cyclic과 함께 사용)

async_tx API

async_tx는 DMA Engine 위에 구축된 비동기 메모리 연산 프레임워크로, 주로 소프트웨어 RAID(md)에서 XOR/PQ 패리티 계산을 가속합니다.

주요 함수

함수연산용도
async_memcpy()메모리 복사DMA 가속 memcpy
async_xor()XORRAID5 패리티 계산
async_pq()P+Q (Reed-Solomon)RAID6 패리티 계산
async_syndrome_val()신드롬 검증RAID6 데이터 무결성 확인
async_xor_val()XOR 검증RAID5 패리티 검증

의존성 체인

async_tx는 여러 연산을 체인으로 연결할 수 있습니다. 각 연산은 이전 연산의 dma_async_tx_descriptor를 의존성으로 받습니다.

struct dma_async_tx_descriptor *tx;

/* 첫 번째 XOR 연산 */
tx = async_xor(dest, srcs, 0, count, len,
    &submit);

/* 두 번째 연산: 첫 번째 완료 후 실행 */
submit.depend_tx = tx;
tx = async_memcpy(dst2, src2, 0, 0, len2,
    &submit);

소프트웨어 폴백

DMA Engine에 XOR/PQ를 지원하는 채널이 없으면, async_tx는 자동으로 CPU 기반 소프트웨어 구현으로 폴백합니다. 이 투명한 폴백 덕분에 RAID 코드는 하드웨어 유무와 관계없이 동일한 API를 사용할 수 있습니다.

주요 DMA 컨트롤러

컨트롤러아키텍처커널 드라이버특징
Intel IOAT (Crystal Beach)x86drivers/dma/ioat/memcpy offload, DCA (Direct Cache Access), 서버급
ARM PL330 (DMA-330)ARM SoC (Samsung, etc.)drivers/dma/pl330.c8채널, 마이크로 코드 기반, Exynos/RK3399 등
TI eDMA3TI AM335x/AM57xdrivers/dma/ti/edma.c이벤트 트리거, 64채널, PaRAM 디스크립터
TI K3 UDMATI AM62x/AM64xdrivers/dma/ti/k3-udma.c패킷 기반 DMA, NAVSS, 링 가속기
NXP eDMAi.MX8/LPCdrivers/dma/fsl-edma.c최대 64채널, 주변장치 전용
STM32 DMASTM32 MCUdrivers/dma/stm32-dma.cRequest Mux (DMAMUX), FIFO, 이중 버퍼
Synopsys DW-DMA다양한 SoCdrivers/dma/dw/Multi-master, 블록 체인, Intel Baytrail 등
Qualcomm BAMSnapdragondrivers/dma/qcom/bam_dma.cPipe 기반, SPI/UART/I2C 연결

Device Tree 바인딩 예시 (PL330)

pdma0: dma-controller@12680000 {
    compatible = "arm,pl330", "arm,primecell";
    reg = <0x12680000 0x1000>;
    interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clock CLK_PDMA0>;
    clock-names = "apb_pclk";
    #dma-cells = <1>;
};

DMA Engine과 DMA 매핑 관계

DMA Engine과 DMA 매핑 API(DMA 심화)는 서로 다른 계층입니다:

구분DMA 매핑 APIDMA Engine
관심사메모리 주소 변환, 캐시 일관성DMA 컨트롤러 추상화, 채널 관리
핵심 APIdma_map_sg(), dma_alloc_coherent()dmaengine_prep_*(), dmaengine_submit()
헤더linux/dma-mapping.hlinux/dmaengine.h
위치kernel/dma/drivers/dma/
IOMMU직접 다룸내부적으로 매핑 API 사용

소프트웨어 스택 계층

클라이언트 드라이버 (SPI, ALSA, md/RAID ...) DMA Engine (dmaengine.c) DMA 컨트롤러 드라이버 (PL330, IOAT, eDMA ...) DMA Mapping API (dma_map_sg ...) HW 레지스터 직접 접근 IOMMU / SWIOTLB / DMA 컨트롤러 하드웨어
핵심: Consumer(클라이언트)는 DMA Engine API만 사용하고, DMA 매핑 API를 직접 호출하지 않습니다. Provider(컨트롤러 드라이버)가 내부적으로 DMA 매핑을 수행합니다. 단, Consumer가 직접 scatter-gather 리스트를 만들 때는 dma_map_sg()를 호출해야 할 수 있습니다.

실전 예제

SPI DMA 전송 (Slave SG)

static int spi_dma_transfer(struct spi_device *spi,
    void *buf, size_t len)
{
    struct spi_controller *ctlr = spi->controller;
    struct dma_chan *chan = ctlr->dma_tx;
    struct dma_async_tx_descriptor *desc;
    struct scatterlist sg;
    DECLARE_COMPLETION_ONSTACK(done);

    /* scatter-gather 리스트 준비 */
    sg_init_one(&sg, buf, len);
    dma_map_sg(ctlr->dma_tx->device->dev, &sg, 1,
        DMA_MEM_TO_DEV);

    /* 디스크립터 생성 */
    desc = dmaengine_prep_slave_sg(chan, &sg, 1,
        DMA_MEM_TO_DEV,
        DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!desc)
        return -ENOMEM;

    /* 콜백 등록 및 전송 */
    desc->callback = spi_dma_complete;
    desc->callback_param = &done;
    dmaengine_submit(desc);
    dma_async_issue_pending(chan);

    /* 완료 대기 */
    wait_for_completion_timeout(&done,
        msecs_to_jiffies(1000));

    dma_unmap_sg(ctlr->dma_tx->device->dev, &sg, 1,
        DMA_MEM_TO_DEV);
    return 0;
}

오디오 Cyclic DMA (ALSA PCM)

static int audio_dma_trigger(struct snd_pcm_substream *ss,
    int cmd)
{
    struct dma_chan *chan = ss->runtime->private_data;
    struct dma_async_tx_descriptor *desc;
    dma_addr_t buf_addr = ss->runtime->dma_addr;
    size_t buf_len = snd_pcm_lib_buffer_bytes(ss);
    size_t period_len = snd_pcm_lib_period_bytes(ss);

    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
        desc = dmaengine_prep_dma_cyclic(chan,
            buf_addr, buf_len, period_len,
            DMA_MEM_TO_DEV,
            DMA_PREP_INTERRUPT);
        desc->callback = audio_period_elapsed;
        desc->callback_param = ss;
        dmaengine_submit(desc);
        dma_async_issue_pending(chan);
        break;
    case SNDRV_PCM_TRIGGER_STOP:
        dmaengine_terminate_async(chan);
        break;
    }
    return 0;
}

static void audio_period_elapsed(void *param)
{
    struct snd_pcm_substream *ss = param;
    snd_pcm_period_elapsed(ss);
}

memcpy offload (Intel IOAT)

struct dma_chan *chan;
struct dma_async_tx_descriptor *tx;
dma_addr_t src_dma, dst_dma;
dma_cookie_t cookie;
DECLARE_COMPLETION_ONSTACK(cmp);

/* 범용 memcpy 채널 요청 */
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
chan = dma_request_channel(mask, NULL, NULL);

/* DMA 주소 매핑 */
src_dma = dma_map_single(chan->device->dev,
    src, len, DMA_TO_DEVICE);
dst_dma = dma_map_single(chan->device->dev,
    dst, len, DMA_FROM_DEVICE);

/* memcpy 디스크립터 생성 */
tx = dmaengine_prep_dma_memcpy(chan, dst_dma, src_dma,
    len, DMA_PREP_INTERRUPT);
tx->callback = memcpy_done;
tx->callback_param = &cmp;

cookie = dmaengine_submit(tx);
dma_async_issue_pending(chan);
wait_for_completion(&cmp);

dma_unmap_single(chan->device->dev, src_dma, len, DMA_TO_DEVICE);
dma_unmap_single(chan->device->dev, dst_dma, len, DMA_FROM_DEVICE);
dma_release_channel(chan);

완전한 UART DMA 드라이버 예제

초기화부터 정리까지 포함된 실무 수준의 UART RX DMA 구현입니다.

struct my_uart_port {
    struct uart_port port;
    struct dma_chan *dma_rx;
    struct dma_chan *dma_tx;
    struct dma_slave_config dma_rx_conf;
    void *rx_buf;
    dma_addr_t rx_dma_addr;
    size_t rx_buf_size;
};

/* 1. DMA 채널 초기화 (probe 시) */
static int my_uart_dma_init(struct my_uart_port *up)
{
    struct device *dev = up->port.dev;
    struct dma_slave_config *conf = &up->dma_rx_conf;

    /* Device Tree에서 DMA 채널 요청 (dmas = <&dma 0>) */
    up->dma_rx = dma_request_chan(dev, "rx");
    if (IS_ERR(up->dma_rx)) {
        dev_warn(dev, "DMA RX channel not available\n");
        up->dma_rx = NULL;
        return -ENODEV;
    }

    /* RX DMA 버퍼 할당 (coherent) */
    up->rx_buf_size = 4096;
    up->rx_buf = dma_alloc_coherent(up->dma_rx->device->dev,
        up->rx_buf_size, &up->rx_dma_addr, GFP_KERNEL);
    if (!up->rx_buf) {
        dma_release_channel(up->dma_rx);
        return -ENOMEM;
    }

    /* Slave 설정 구성 */
    memset(conf, 0, sizeof(*conf));
    conf->direction = DMA_DEV_TO_MEM;
    conf->src_addr = up->port.mapbase + UART_RX_REG; /* UART RX FIFO 물리 주소 */
    conf->src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
    conf->src_maxburst = 1; /* FIFO 트리거 레벨 */
    conf->device_fc = false; /* DMA 컨트롤러가 흐름 제어 */

    return dmaengine_slave_config(up->dma_rx, conf);
}

/* 2. RX DMA 시작 */
static int my_uart_start_rx_dma(struct my_uart_port *up)
{
    struct dma_async_tx_descriptor *desc;

    /* cyclic DMA 디스크립터 생성 (링 버퍼) */
    desc = dmaengine_prep_dma_cyclic(up->dma_rx,
        up->rx_dma_addr,
        up->rx_buf_size,
        up->rx_buf_size / 4, /* period: 1KB마다 콜백 */
        DMA_DEV_TO_MEM,
        DMA_PREP_INTERRUPT);

    if (!desc) {
        dev_err(up->port.dev, "Failed to prep cyclic desc\n");
        return -ENOMEM;
    }

    desc->callback = my_uart_dma_rx_complete;
    desc->callback_param = up;

    dmaengine_submit(desc);
    dma_async_issue_pending(up->dma_rx);

    return 0;
}

/* 3. RX 완료 콜백 (period마다 호출) */
static void my_uart_dma_rx_complete(void *param)
{
    struct my_uart_port *up = param;
    struct dma_tx_state state;
    size_t count;

    /* 현재 DMA 위치 조회 */
    dmaengine_tx_status(up->dma_rx, 0, &state);
    count = up->rx_buf_size - state.residue;

    /* TTY 레이어로 데이터 전달 */
    tty_insert_flip_string(&up->port.state->port,
        up->rx_buf, count);
    tty_flip_buffer_push(&up->port.state->port);
}

/* 4. DMA 중지 (shutdown 시) */
static void my_uart_stop_rx_dma(struct my_uart_port *up)
{
    if (up->dma_rx) {
        dmaengine_terminate_sync(up->dma_rx);
    }
}

/* 5. 정리 (remove 시) */
static void my_uart_dma_cleanup(struct my_uart_port *up)
{
    if (up->dma_rx) {
        dmaengine_terminate_sync(up->dma_rx);
        dma_free_coherent(up->dma_rx->device->dev,
            up->rx_buf_size, up->rx_buf, up->rx_dma_addr);
        dma_release_channel(up->dma_rx);
        up->dma_rx = NULL;
    }
}

dmatest 모듈 사용법

dmatest는 DMA Engine 프레임워크를 테스트하는 커널 모듈로, DMA 컨트롤러 드라이버 검증에 필수적입니다.

# 1. dmatest 모듈 로드 (CONFIG_DMATEST=m 필요)
modprobe dmatest

# 2. 테스트 파라미터 설정
# /sys/module/dmatest/parameters/ 디렉터리 사용

# 채널 지정 (예: dma0chan0)
echo dma0chan0 > /sys/module/dmatest/parameters/channel

# 전송 크기 (바이트)
echo 4096 > /sys/module/dmatest/parameters/test_buf_size

# 반복 횟수
echo 100 > /sys/module/dmatest/parameters/iterations

# 스레드 수
echo 1 > /sys/module/dmatest/parameters/threads_per_chan

# 타임아웃 (ms)
echo 5000 > /sys/module/dmatest/parameters/timeout

# 3. 테스트 시작
echo 1 > /sys/module/dmatest/parameters/run

# 4. 결과 확인
dmesg | grep dmatest
# 예상 출력:
# dmatest: Started 1 threads using dma0chan0
# dmatest: dma0chan0-copy0: summary 100 tests, 0 failures 1234 iops 12345 KB/s

# 5. 전체 DMA 컨트롤러 스트레스 테스트
# (채널 미지정 시 모든 채널 테스트)
echo "" > /sys/module/dmatest/parameters/channel
echo 1000 > /sys/module/dmatest/parameters/iterations
echo 1 > /sys/module/dmatest/parameters/run

# 6. 정지
echo 0 > /sys/module/dmatest/parameters/run
dmatest 파라미터:
  • channel — 테스트할 채널 (비어있으면 모든 채널)
  • device — 특정 DMA 디바이스 지정
  • threads_per_chan — 채널당 동시 스레드 수
  • max_channels — 최대 테스트 채널 수
  • iterations — 반복 횟수 (0 = 무한)
  • xor_sources — XOR 연산 소스 수 (async_tx 테스트)
  • pq_sources — PQ 연산 소스 수 (RAID6 테스트)
  • alignment — 버퍼 정렬 (바이트, 기본 1)

디버깅과 최적화

debugfs 인터페이스

DMA Engine은 /sys/kernel/debug/dmaengine/에 디버깅 인터페이스를 제공합니다 (CONFIG_DEBUG_FS 필요).

채널 요약 정보

# 모든 DMA 채널 상태 확인
cat /sys/kernel/debug/dmaengine/summary

# 출력 예시:
dma0 (pl330): number of channels: 8
  dma0chan0 | in-use (1 refs) | spi-bcm2835.0:tx
  dma0chan1 | in-use (1 refs) | spi-bcm2835.0:rx
  dma0chan2 | in-use (1 refs) | uart-pl011.1:rx
  dma0chan3 | idle
  dma0chan4 | in-use (1 refs) | snd-soc-dummy:pcm-playback
  dma0chan5 | in-use (1 refs) | snd-soc-dummy:pcm-capture
  dma0chan6 | idle
  dma0chan7 | idle

dma1 (dw-dma): number of channels: 4
  dma1chan0 | idle
  dma1chan1 | idle
  dma1chan2 | in-use (1 refs) | i2c-designware.2:tx
  dma1chan3 | in-use (1 refs) | i2c-designware.2:rx

개별 컨트롤러 상세 정보

# 특정 DMA 컨트롤러 정보
ls /sys/kernel/debug/dmaengine/
# dma0  dma1  summary

cat /sys/kernel/debug/dmaengine/dma0
# 출력 (컨트롤러별로 다름):
DMA Controller: pl330
Device: ff600000.dma
IRQ: 42, 43
Channels: 8
Capabilities: slave cyclic

# 실행 중인 전송 모니터링 (일부 드라이버만 지원)
watch -n 1 cat /sys/kernel/debug/dmaengine/summary

virt-dma 디버깅

# virt-dma 사용 드라이버의 디스크립터 상태
# (드라이버가 virt-dma 헬퍼를 사용하는 경우)
cat /sys/kernel/debug/dmaengine/dma0chan0/desc_list

# 출력 예시:
Submitted descriptors: 2
Issued descriptors: 1
Completed descriptors: 145
Allocated descriptors: 3

# 디스크립터 누수 감지
# Allocated - Completed 값이 계속 증가하면 누수 의심

ftrace로 DMA Engine 추적

ftrace는 DMA 전송의 실시간 흐름을 추적하는 강력한 도구입니다.

기본 DMA 이벤트 추적

# 1. DMA Engine 이벤트 활성화
cd /sys/kernel/debug/tracing
echo 1 > events/dma/enable

# 2. 함수 그래프 트레이서 설정 (선택)
echo function_graph > current_tracer
echo dmaengine_submit > set_graph_function
echo dma_async_issue_pending >> set_graph_function

# 3. 추적 시작
echo 1 > tracing_on

# 4. DMA 작업 수행 (예: SPI 전송)
# ... 애플리케이션 실행 ...

# 5. 추적 중지 및 결과 확인
echo 0 > tracing_on
cat trace

# 출력 예시:
#           TASK-PID   CPU#  TIMESTAMP  FUNCTION
     spi-transfer-1234  [000] .... 12345.678901: dmaengine_submit <-spi_transfer_one_message
     spi-transfer-1234  [000] .... 12345.678923: dma_async_issue_pending <-spi_transfer_one_message
        dma-work-56    [001] d... 12345.679012: dma_cookie_complete <-pl330_tasklet
        dma-work-56    [001] d... 12345.679034: dmaengine_desc_callback_invoke <-pl330_tasklet

특정 채널/디바이스만 추적

# 특정 DMA 컨트롤러 함수만 추적
echo 'pl330*' > /sys/kernel/debug/tracing/set_ftrace_filter

# 또는 glob 패턴 사용
echo 'dmaengine_*' > set_ftrace_filter
echo '*prep_slave*' >> set_ftrace_filter

# 필터 확인
cat set_ftrace_filter

주요 DMA ftrace 이벤트

이벤트위치설명
dmaengine:dma_prep디스크립터 준비전송 준비 시작점
dmaengine:dma_issue전송 제출하드웨어에 전송 전달
dmaengine:dma_complete전송 완료인터럽트/폴링으로 완료 감지
dmaengine:dma_callback콜백 실행완료 콜백 호출

DMA 지연 측정

# 전송 시작부터 완료까지 지연 측정
cd /sys/kernel/debug/tracing

# 타임스탬프 고정밀도 설정
echo 1 > options/funcgraph-abstime
echo 1 > options/latency-format

# DMA 이벤트만 추적
echo nop > current_tracer
echo 1 > events/dmaengine/enable

echo 1 > tracing_on
# ... DMA 작업 수행 ...
echo 0 > tracing_on

# 지연 분석
cat trace | grep -E 'dma_issue|dma_complete'

# 출력 예시:
#  latency: 234 us, #2/2
  spi-txrx-1234    12345.678901: dmaengine:dma_issue: chan=dma0chan0
  irq/42-dma      12345.679135: dmaengine:dma_complete: chan=dma0chan0
# → 지연: 679135 - 678901 = 234us

히스토그램 분석 (커널 5.2+)

# DMA 완료 지연 히스토그램
echo 'hist:keys=common_pid:vals=lat:sort=lat' > \
  /sys/kernel/debug/tracing/events/dmaengine/dma_complete/trigger

# 히스토그램 확인
cat /sys/kernel/debug/tracing/events/dmaengine/dma_complete/hist

# 출력 예시:
{ lat:        100 } hitcount:         12
{ lat:        150 } hitcount:         34
{ lat:        200 } hitcount:         56
{ lat:        250 } hitcount:         23
{ lat:        300 } hitcount:          8

일반적인 문제와 해결

문제원인해결
채널 할당 실패DT dmas 프로퍼티 누락/오류Device Tree 바인딩 확인, dma-names 일치 검증
전송 타임아웃HW 인터럽트 미발생, 클럭 미활성화인터럽트 라인 확인, 클럭 게이팅 점검
데이터 불일치캐시 동기화 누락dma_map_sg()/dma_unmap_sg() 정확히 쌍으로 호출
DMA 매핑 오류dma_set_mask() 미호출Provider에서 적절한 DMA 마스크 설정
커널 패닉완료 콜백에서 sleep콜백은 atomic context — sleep 불가, tasklet/workqueue 사용
cyclic 콜백 지연period 크기가 너무 작음period 크기 증가, 인터럽트 코얼레싱 확인

성능 최적화 팁

관련 Kconfig 옵션

옵션기본값설명
CONFIG_DMADEVICESyDMA Engine 서브시스템 활성화
CONFIG_DMA_ENGINEyDMA Engine 코어
CONFIG_DMA_VIRTUAL_CHANNELSyvirt-dma 헬퍼
CONFIG_DMA_OFyDevice Tree DMA 매핑
CONFIG_DMATESTnDMA 테스트 모듈
CONFIG_ASYNC_TX_DMAnasync_tx DMA 가속 활성화
CONFIG_INTEL_IOATDMAnIntel IOAT DMA 드라이버
CONFIG_PL330_DMAnARM PL330 DMA 드라이버
CONFIG_TI_EDMAnTI eDMA 드라이버
CONFIG_DW_DMACnSynopsys DW-DMA 드라이버

DMA Engine과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.