Raspberry Pi 3 の DMA

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コントローラの動作確認コード

```c
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のチャンネルを有効 
 * チャンネル毎の設定
  * リセット
 * コントロールブロックの設定
  * 転送モード
  * 転送元アドレス
  * 転送先アドレス
  * 転送長 バイト ４の倍数
 * DMAを起動する。
  * コントロールブロックのアドレスを設定
  * control_statusレジスタのACTIVEを１にする。

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に１を書きます。

```
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に１を設定すると連続してコピーできます。

```
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
```

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


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


# 参考
 * https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=148384
 * https://github.com/rsta2/circle/blob/master/lib/dmachannel.cpp