LoginSignup
13
7

More than 3 years have passed since last update.

Raspberry Pi 3 の DMA

Last updated at Posted at 2018-07-10

Raspberry Pi 3 の DMAをベアメタルで使ってみました。

DMAを使って、メモリ領域をコピーしてみました。

動作確認はqemu 2.12 で行いました。

DMAコントローラの仕様は、https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
に載っています。

作成したソースコード一式: https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/dma01

DMAコントローラの動作確認コード

typedef struct{
    uint32_t control_status;
    uint32_t control_block_addr;
    uint32_t transfer_information;
    uint32_t source_addr;
    uint32_t dest_addr;
    uint32_t tranfer_length;
    uint32_t stride;
    uint32_t next_control_block;
    uint32_t debug;
} DMA_Channel;

typedef struct{
    uint32_t transfer_information;
    uint32_t source_addr;
    uint32_t dest_addr;
    uint32_t transfer_length;
    uint32_t stride;
    uint32_t next_control_block;
    uint32_t dummy0;
    uint32_t dummy1;
} DMA_ControlBlock __attribute__ ((aligned(32)));

#define DMA_CH_0    (*((volatile DMA_Channel *)(0x3F007000)))
#define DMA_ENABLE  ((volatile uint32_t *)(0x3F007FF0))

static uint8_t src[256] = "hello world\n";
static uint8_t dst[256] = { 0 };
DMA_ControlBlock dma_control_block;


void kernel_main(void)
{   
    uart_puts("dma01\n");

    *DMA_ENABLE |= 1 << 0; /* enable DMA ch 0 */

    DMA_CH_0.control_status |= 1 << 31; /* set reset */

    /* setup dma control block */
    dma_control_block.transfer_information = 0; 
    dma_control_block.transfer_information |= 1 << 26 | 1<< 8 | 1 << 4; /* no wide bursts, src increment, dest increment */
    dma_control_block.source_addr = (uintptr_t) src;
    dma_control_block.dest_addr = (uintptr_t) dst;
    dma_control_block.next_control_block = 0;
    dma_control_block.stride = 0;
    dma_control_block.transfer_length = 256;
    dma_control_block.dummy0 = 0;
    dma_control_block.dummy1 = 0;    

    /* kick dma */
    DMA_CH_0.control_status |= 1 << 29;
    DMA_CH_0.control_block_addr = (uintptr_t) &dma_control_block;
    DMA_CH_0.control_status |= 1 << 0;

    while (1) {
        uart_puts(dst]);
        io_halt();
    }
}

このコードを動かすと、DMAによりdst[]に値がコピーされます。

$ qemu-system-aarch64 -M raspi3 -m 128 -serial null -serial mon:stdio -nographic -kernel kernel.elf
dma01
hello world

コードの説明

  • 全体の設定
    •  使用するDMAのチャンネルを有効
  • チャンネル毎の設定
    • リセット
  • コントロールブロックの設定
    • 転送モード
    • 転送元アドレス
    • 転送先アドレス
    • 転送長 バイト 4の倍数
  • DMAを起動する。
    • コントロールブロックのアドレスを設定
    • control_statusレジスタのACTIVEを1にする。

DMAで指定するアドレスはPhysical addressです。

DMAを起動すると、コントロールブロックの設定値が、レジスタにコピーされるみたいです。

DMAの割り込みを使う

DMAの割り込みを使ってみました。

割り込みを有効にするには、割り込みのルーティング設定をして、コントロールブロックのtransfer_informationのINTENを1にします。

#define IRQ_BASIC   ((volatile uint32_t *)(0x3F00B200))
#define IRQ_PEND1   ((volatile uint32_t *)(0x3F00B204))
#define IRQ_ENABLE1 ((volatile uint32_t *)(0x3F00B210))
#define GPU_INTERRUPTS_ROUTING ((volatile uint32_t *)(0x4000000C))
#define CORE0_INTERRUPT_SOURCE ((volatile uint32_t *)(0x40000060))

static inline void enable_irq(void)
{
    asm volatile ("msr daifclr, #2");
}
    /* interrupt */
    *GPU_INTERRUPTS_ROUTING = 0x00; /* core0 */
    *IRQ_ENABLE1 = 1 << 16;  /* DMA CH0 */
    enable_irq();
    dma_control_block.transfer_information |= 1 << 0; /* INTEN */

DMAの割り込み要因をクリアするには、DMA_CH_0.control_statusレジスタのINTに1を書きます。

void c_irq_handler(void)
{   
    /* check inteerupt source */
    if (*CORE0_INTERRUPT_SOURCE & (1 << 8)) {
        if (*IRQ_BASIC & (1 << 8)) {
            if (*IRQ_PEND1 & (1 << 16)) {
                if (DMA_CH_0.control_status & (1 << 2)) {
                    DMA_CH_0.control_status |= 1 << 2;  /* clear INT */
                    uart_puts(" c_irq_handler\n");
                    uart_puts(dst);
                    return;
                }
            }
        }
    }
    return;
}

https://elinux.org/BCM2835_datasheet_errata によると割り込みはコントロールブロックチェインの最後でしか発生しないと記載があります。

DMAの割り込みを使って連続してコピー

割り込み要因クリア後に、コントロールブロックを設定して、Activeに1を設定すると連続してコピーできます。

void c_irq_handler(void)
{
    static uint8_t i = 0;
    /* check inteerupt source */
    if (*CORE0_INTERRUPT_SOURCE & (1 << 8)) {
        if (*IRQ_BASIC & (1 << 8)) {
            if (*IRQ_PEND1 & (1 << 16)) {
                if (DMA_CH_0.control_status & (1 << 2)) {
                    DMA_CH_0.control_status |= 1 << 2;    /* clear INT */
                    uart_puts(" c_irq_handler ");
                    uart_puts((const char *) dst);
                    src[11] = ' ';
                    src[12] = '0' + ( i++ % 10 );
                    src[13] = '\n';
                    DMA_CH_0.control_block_addr = (uintptr_t) &dma_control_block;
                    DMA_CH_0.control_status |= 1 << 0; /* Active */
                    return;
                }
            }
        }
    }
    return;
}

こんなログが出力されます。

$ qemu-system-aarch64 -M raspi3 -m 128 -serial null -serial mon:stdio -nographic -kernel kernel.elf
dma01
 c_irq_handler hello world
 c_irq_handler hello world  c_irq_handler hello world 0
 c_irq_handler hello world 1
 c_irq_handler hello world 2
 c_irq_handler hello world 3
 c_irq_handler hello world 4
 c_irq_handler hello world 5
 c_irq_handler hello world 6
 c_irq_handler hello world 7
 c_irq_handler hello world 8
 c_irq_handler hello world 9
 c_irq_handler hello world 0
 c_irq_handler hello world 1

あれ、2回目の出力の時に改行が出力されていないのはなんでだろう。

Raspberry Pi において、DMAが効果的なのはDREQの設定手順が公開されている PWM, SPI, PCM/I2S と、大容量の転送を行うVideoCore4 と Framebufferなどです。

参考

13
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7