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安装
通过如下命令安装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
}
}
]
}