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などです。