Linux/Kernel Driver

DMA(Direct Memory Access) 처리: 개념과 사용법, 그리고 RK3399 예제

임베디드 친구 2025. 3. 25. 07:07
728x90
반응형

DMA(Direct Memory Access) 처리: 개념과 사용법, 그리고 RK3399 예제

DMA란 무엇인가?

DMA(Direct Memory Access)는 CPU의 개입을 최소화하여 메모리와 주변 장치 간에 데이터를 직접 전송할 수 있는 기능입니다. 이를 통해 데이터 전송 속도를 대폭 향상시키고 CPU의 부하를 줄일 수 있습니다. DMA는 고속 데이터 전송이 필요한 임베디드 시스템에서 필수적인 기능으로 사용됩니다.

DMA의 작동 원리

  1. CPU는 DMA 컨트롤러에 데이터 전송 작업을 요청합니다.
  2. DMA 컨트롤러는 데이터 소스와 목적지 주소를 설정합니다.
  3. 데이터 전송이 진행되며, CPU는 다른 작업을 수행할 수 있습니다.
  4. 전송 완료 시 DMA 컨트롤러는 인터럽트를 통해 CPU에 완료를 알립니다.

이와 같은 방식으로 DMA는 CPU가 직접 데이터 전송을 처리하지 않도록 하여 시스템 성능을 최적화합니다.


DMA의 주요 용도

  • 대용량 데이터 전송
  • 고속 네트워크 패킷 처리
  • 오디오/비디오 데이터 스트리밍
  • 저장 장치와 메모리 간 데이터 복사

DMA 설정 및 사용 방법

DMA 설정 단계

  1. DMA 채널 및 스트림 선택
  2. 데이터 전송 방향 설정 (메모리 -> 메모리, 메모리 -> 주변 장치 등)
  3. 데이터 버퍼 크기 및 주소 설정
  4. DMA 인터럽트 설정
  5. DMA 활성화

RK3399에서 DMA 설정하기

RK3399은 ARM Cortex-A72와 Cortex-A53 코어를 기반으로 하는 고성능 SoC(System on Chip)로, DMA를 활용해 다양한 고속 데이터 전송 작업을 처리할 수 있습니다.

아래는 RK3399에서 DMA를 설정하고 사용하는 방법에 대한 코드 예제입니다.


DMA 예제: RK3399에서 메모리 간 데이터 전송

다음은 RK3399의 DMA 컨트롤러를 활용하여 메모리 간 데이터를 전송하는 예제입니다. 이 코드는 Linux 커널 모듈로 작성되었습니다.

#include <linux/module.h>
#include <linux/init.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/completion.h>
#include <linux/slab.h>

#define BUF_SIZE 1024

static struct completion dma_complete;

static void dma_callback(void *completion)
{
    complete(completion);
}

static int __init rk3399_dma_example_init(void)
{
    struct dma_chan *dma_chan;
    dma_addr_t src_addr, dst_addr;
    char *src_buf, *dst_buf;
    struct dma_async_tx_descriptor *tx;
    struct dma_slave_config dma_cfg;
    enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
    int ret;

    /* DMA 채널 요청 */
    dma_chan = dma_request_chan(NULL, "dma0chan0");
    if (IS_ERR(dma_chan)) {
        pr_err("DMA 채널 요청 실패\n");
        return PTR_ERR(dma_chan);
    }

    /* 소스 및 목적지 메모리 할당 */
    src_buf = kmalloc(BUF_SIZE, GFP_KERNEL);
    dst_buf = kmalloc(BUF_SIZE, GFP_KERNEL);
    if (!src_buf || !dst_buf) {
        pr_err("메모리 할당 실패\n");
        ret = -ENOMEM;
        goto free_dma;
    }

    /* DMA 주소 매핑 */
    src_addr = dma_map_single(dma_chan->device->dev, src_buf, BUF_SIZE, DMA_TO_DEVICE);
    dst_addr = dma_map_single(dma_chan->device->dev, dst_buf, BUF_SIZE, DMA_FROM_DEVICE);

    /* DMA 전송 설정 */
    memset(&dma_cfg, 0, sizeof(dma_cfg));
    dma_cfg.direction = DMA_MEM_TO_MEM;
    dma_cfg.src_addr = src_addr;
    dma_cfg.dst_addr = dst_addr;
    dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    dma_cfg.src_maxburst = 16;
    dma_cfg.dst_maxburst = 16;

    ret = dmaengine_slave_config(dma_chan, &dma_cfg);
    if (ret) {
        pr_err("DMA 설정 실패\n");
        goto unmap_memory;
    }

    /* DMA 작업 준비 */
    init_completion(&dma_complete);
    tx = dmaengine_prep_dma_memcpy(dma_chan, dst_addr, src_addr, BUF_SIZE, flags);
    if (!tx) {
        pr_err("DMA 준비 실패\n");
        ret = -EIO;
        goto unmap_memory;
    }

    tx->callback = dma_callback;
    tx->callback_param = &dma_complete;

    /* DMA 실행 */
    dmaengine_submit(tx);
    dma_async_issue_pending(dma_chan);

    /* 전송 완료 대기 */
    wait_for_completion(&dma_complete);

    pr_info("DMA 전송 완료!\n");

unmap_memory:
    dma_unmap_single(dma_chan->device->dev, src_addr, BUF_SIZE, DMA_TO_DEVICE);
    dma_unmap_single(dma_chan->device->dev, dst_addr, BUF_SIZE, DMA_FROM_DEVICE);
    kfree(src_buf);
    kfree(dst_buf);

free_dma:
    dma_release_channel(dma_chan);
    return ret;
}

static void __exit rk3399_dma_example_exit(void)
{
    pr_info("DMA 예제 모듈 종료\n");
}

module_init(rk3399_dma_example_init);
module_exit(rk3399_dma_example_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("소프트웨어 공장");
MODULE_DESCRIPTION("RK3399에서 DMA를 이용한 메모리 간 데이터 전송 예제");

결론

DMA는 임베디드 시스템에서 데이터 전송의 효율성을 극대화하는 중요한 기술입니다. RK3399과 같은 플랫폼에서 DMA를 활용하면 고속 데이터 전송이 가능하며 CPU 리소스를 절약할 수 있습니다. 본 포스팅의 예제 코드를 기반으로 다양한 DMA 활용 사례를 확장해보세요.

반응형