QEMU学习之路(9)— 在RISCV64 virt中添加DMA设备

发布于:2025-06-14 ⋅ 阅读:(23) ⋅ 点赞:(0)

QEMU学习之路(9)— 在RISCV64 virt中添加DMA设备

一、前言

QEMU版本:7.0.0
操作系统:Ubuntu 20.04.6 LTS
gcc版本:9.4.0
安装依赖

sudo apt install ninja-build wget 

参考:Qemu Virt平台集成ARM PL080 DMA

二、QEMU安装

通过如下命令安装QEMU
1、获取安装包

wget https://download.qemu.org/qemu-7.0.0.tar.xz

2、解压

tar xvf qemu-7.0.0.tar.xz

3、配置

cd qemu-7.0.0
mkdir build && cd build
../configure --target-list="riscv64-softmmu" --prefix=`pwd`/opt

4、编译

make -j $(nproc)

5、安装

make install

6、查看版本

./opt/bin/qemu-system-riscv64 --version

在这里插入图片描述

三、修改文件

打开include/hw/riscv/virt.h文件,添加DMA的枚举变量
在这里插入图片描述
打开hw/riscv/virt.c文件,添加pl080头文件

在这里插入图片描述

添加DMA的配置地址
在这里插入图片描述
hw/riscv/virt.c文件中编写创建DMA设备的函数

static void create_dma(RISCVVirtState *s, DeviceState *irqchip)
{
	int i;
    DeviceState *dev;
    SysBusDevice *sysbus;
	const MemMapEntry *memmap = virt_memmap;
	MemoryRegion *sysmem = get_system_memory();

    dev = qdev_new(TYPE_PL080);
    sysbus = SYS_BUS_DEVICE(dev);

    object_property_set_link(OBJECT(dev), "downstream", OBJECT(sysmem), &error_fatal);


    sysbus_realize_and_unref(sysbus, &error_fatal);

    sysbus_mmio_map(sysbus, 0, memmap[VIRT_DMA].base);

    for (i = 0; i < 3; ++i) {
        sysbus_connect_irq(sysbus, i, qdev_get_gpio_in(irqchip, DMA_IRQ));
    }
}

然后在virt_machine_init函数中调用
在这里插入图片描述
修改hw/riscv/Kconfig文件,将PL080添加到RISCV_VIRT设备中
在这里插入图片描述
然后重新配置、编译和安装

../configure --target-list="riscv64-softmmu" --prefix=`pwd`/opt
make -j $(nproc)
make install

四、编程测试

PL080的技术手册下载地址:https://developer.arm.com/documentation/ddi0196/latest/
编写裸机程序如下所示:

#include "stdint.h"
#include "uart.h"
#include "plic.h"

// PL080 寄存器定义
#define PL080_BASE         ((size_t)0x10002000)

// 全局寄存器
#define PL080_INT_STATUS   (PL080_BASE + 0x04)
#define PL080_INT_TCCLRER  (PL080_BASE + 0x08)
#define PL080_EN_CH        (PL080_BASE + 0x30)

// 通道寄存器偏移 (每个通道0x20间隔)
#define CH_OFFSET(ch)      (0x100 + (ch)*0x20)
#define CH_SRC_ADDR(ch)    (*(volatile uint32_t*)(PL080_BASE + CH_OFFSET(ch) + 0x00))
#define CH_DEST_ADDR(ch)   (*(volatile uint32_t*)(PL080_BASE + CH_OFFSET(ch) + 0x04))
#define CH_LLI(ch)         (*(volatile uint32_t*)(PL080_BASE + CH_OFFSET(ch) + 0x08))
#define CH_CONTROL(ch)     (*(volatile uint32_t*)(PL080_BASE + CH_OFFSET(ch) + 0x0C))
#define CH_CONFIG(ch)      (*(volatile uint32_t*)(PL080_BASE + CH_OFFSET(ch) + 0x10))

// 测试内存区域
#define SRC_DATA_ADDR      0x80010000
#define DEST_DATA_ADDR     0x80020000
#define TEST_DATA_SIZE     256  // 字节

void init_test_data()
{
    volatile uint32_t *src = (uint32_t*)SRC_DATA_ADDR;
    volatile uint32_t *dst = (uint32_t*)DEST_DATA_ADDR;
    
    for (int i = 0; i < TEST_DATA_SIZE/4; i++) {
        src[i] = i + 0x12345678;
        dst[i] = 0;
    }
}

int verify_data()
{
    volatile uint32_t *src = (uint32_t*)SRC_DATA_ADDR;
    volatile uint32_t *dst = (uint32_t*)DEST_DATA_ADDR;
    
    for (int i = 0; i < TEST_DATA_SIZE/4; i++) {
        if (src[i] != dst[i]) {
            return -1;  // 验证失败
        }
    }
    return 0;  // 验证成功
}

void simple_dma_transfer(int ch)
{
    volatile uint32_t value = 0;
    // 配置源地址和目标地址
    CH_SRC_ADDR(ch) = SRC_DATA_ADDR;
    CH_DEST_ADDR(ch) = DEST_DATA_ADDR;
    
    // 配置控制寄存器
    CH_CONTROL(ch) = (1 << 31) | (1 << 27) | (1 << 26) |
                    (2 << 21) | (2 << 18) | (4 << 15) | (4 << 12) | (TEST_DATA_SIZE/4);
    
    // 配置通道并使能
    CH_CONFIG(ch) = 0x1 | (0x1<<15);  // 使能通道和中断
    
    // 全局启用DMA控制器
    write32(PL080_EN_CH, (1 << ch));
}

volatile uint32_t irq_done = 0;

void dma_handler(void)
{
    uint32_t value;
    uart_send_string("handle irq\r\n");

    value = read32(PL080_INT_STATUS);
    if(value == 0x1)
    {
        // 清除中断状态
        write32(PL080_INT_TCCLRER, 0x01);
        irq_done = 1;
    }
}

void main(void)
{
	uart_init();

    irq_register(DMA_IRQn, dma_handler);

    // 初始化测试数据
    init_test_data();
    
    // 执行DMA传输 (使用通道0)
    simple_dma_transfer(0);
    debug_log("DMA Start");
    
    // 等待DMA传输完成
    while(irq_done == 0);

    // 验证数据
    if (verify_data() == 0) {
        // 成功 
        debug_log("Test Pass");
    } else {
        // 失败
        debug_log("Test fail");
    }

    while (1) {
        ;
    }
}

测试结果如下所示,测试成功通过:
在这里插入图片描述
完整源码路径:
https://gitee.com/william_william/learn/tree/develop/riscv64/03_dma

五、调试

开发中使用VScode调试,调试配置如下

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "gdb",
            "type": "cppdbg",
            "request": "launch",
            "program": "/home/william/project/qemu/qemu-7.0.0/build/opt/bin/qemu-system-riscv64",
            "args": [
                "-machine",
                "virt",
                "-m",
                "128M",
                "-nographic",
                "-bios",
                "/home/william/project/qemu/learn/riscv64/03_dma/build/bootrom.bin",
            ],
            "stopAtEntry": false,
            "cwd": "/home/william/project/qemu",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set Disassembly Flavor to Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ],
            "miDebuggerPath": "/usr/bin/gdb",
            "logging": {
                "trace": true,
                "traceResponse": true,
                "engineLogging": true
            }
        }
    ]
}