QEMU virt开发板无法访问CLINT空间

我装的QEMU的virt开发板内存空间布局如下,CLINT的空间在0x2000000并且大小为 0x10000,但是我尝试访问这段空间(CLINT的mtimer寄存器)却会发生加载指令异常,请问是问什么?如何解决?(系统调用可以正常执行,架构为riscv64,qemu版本7.4.5)

static const MemMapEntry virt_memmap[] = {
    [VIRT_DEBUG] =        {        0x0,         0x100 },
    [VIRT_MROM] =         {     0x1000,        0xf000 },
    [VIRT_TEST] =         {   0x100000,        0x1000 },
    [VIRT_RTC] =          {   0x101000,        0x1000 },
    [VIRT_CLINT] =        {  0x2000000,       0x10000 },
    [VIRT_ACLINT_SSWI] =  {  0x2F00000,        0x4000 },
    [VIRT_PCIE_PIO] =     {  0x3000000,       0x10000 },
    [VIRT_PLATFORM_BUS] = {  0x4000000,     0x2000000 },
    [VIRT_PLIC] =         {  0xc000000, VIRT_PLIC_SIZE(VIRT_CPUS_MAX * 2) },
    [VIRT_APLIC_M] =      {  0xc000000, APLIC_SIZE(VIRT_CPUS_MAX) },
    [VIRT_APLIC_S] =      {  0xd000000, APLIC_SIZE(VIRT_CPUS_MAX) },
    [VIRT_UART0] =        { 0x10000000,         0x100 },
    [VIRT_VIRTIO] =       { 0x10001000,        0x1000 },
    [VIRT_FW_CFG] =       { 0x10100000,          0x18 },
    [VIRT_FLASH] =        { 0x20000000,     0x4000000 },
    [VIRT_IMSIC_M] =      { 0x24000000, VIRT_IMSIC_MAX_SIZE },
    [VIRT_IMSIC_S] =      { 0x28000000, VIRT_IMSIC_MAX_SIZE },
    [VIRT_PCIE_ECAM] =    { 0x30000000,    0x10000000 },
    [VIRT_PCIE_MMIO] =    { 0x40000000,    0x40000000 },
    [VIRT_DRAM] =         { 0x80000000,           0x0 },
};

参考GPT和自己的思路:可能的原因是您访问CLINT空间时使用了不正确的地址或访问模式。您需要确保使用正确的地址访问CLINT空间并且使用正确的访问模式。

您可以尝试使用调试工具来调试程序并查看发生异常的原因。还可以考虑更新QEMU版本或者检查是否有其他相关软件更新。

如果问题仍然存在,您可以向RISC-V社区寻求帮助,他们可能会更熟悉这个问题并给予更具体的建议和解决方案。

参考GPT:加载指令异常通常是因为在特权级别不足的情况下尝试访问受保护的内存区域。在RISC-V中,CLINT是属于M模式(机器模式)的寄存器,只有在M模式下才能访问它。

您需要确保在运行代码时,您的程序已经进入了M模式。如果您的程序在S模式(监管模式)下运行,您可以通过对M模式进行软件中断或使用SBI(Supervisor Binary Interface)来进入M模式。此外,还要确保您的代码在M模式下运行时已经拥有访问CLINT的权限。

如果您确认您的程序已经进入M模式并且具有访问CLINT的权限,但仍然无法访问CLINT空间,则您可以检查是否存在其他程序或设备正在访问该空间。

最后,您可以使用GDB进行调试,以查看加载指令异常的具体原因。通过在编译代码时添加-g选项,您可以在GDB中调试您的程序。在GDB中,您可以使用命令x /s 0x2000000来查看CLINT空间的内容,或者使用命令info registers来查看当前寄存器的值。

该回答引用于gpt与OKX安生共同编写:
  • 该回答引用于gpt与OKX安生共同编写:

QEMU 的 virt 开发板默认的内存空间布局中,CLINT 的空间地址为 0x2000000,大小为 0x10000。根据 RISC-V 处理器架构规范,CLINT(Core Local Interruptor)是一个负责管理本地定时器和软件中断的模块,它位于物理地址空间的 0x02000000~`0x0200FFFF` 区域(这里的地址是 32 位 RISC-V 处理器的地址)。因此,你需要通过 QEMU 的虚拟地址映射机制来访问 CLINT。

在 QEMU 中,每个设备都有一个虚拟地址范围,映射到物理地址范围。在 virt 开发板上,CLINT 的虚拟地址范围是 0x020000000x0200FFFF,对应的物理地址范围是 0x00000000200000000x0000000020100000。因此,在程序中访问 CLINT 寄存器时,需要使用 CLINT 的物理地址。

下面是一个示例代码,演示了如何使用 C 语言读取 CLINT 的 mtime 寄存器:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

// 定义 CLINT 地址和大小
#define VIRT_CLINT_BASE 0x02000000
#define VIRT_CLINT_SIZE 0x10000

// 定义 CLINT 寄存器偏移量
#define CLINT_MTIME        0xbff8

int main(int argc, char **argv) {
    // 打开 QEMU 的 virt 开发板,并映射 CLINT 的虚拟地址
    FILE *virt_board = popen("qemu-system-riscv64 -machine virt -nographic -bios none -device loader,file=boot.bin,addr=0x80200000 -drive file=rootfs.ext2,if=none,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 -m 1024 -smp 1 -device intel-hda -device hda-duplex","r");
    if (virt_board == NULL) {
        printf("Failed to open QEMU!\n");
        return -1;
    }

    // 跳过第一个字符,避免读取到奇怪的乱码
    fgetc(virt_board);

    // 计算 CLINT 的物理地址
    uintptr_t clint_addr = VIRT_CLINT_BASE + (uintptr_t)CLINT_MTIME;

    // 从 CLINT 中读取 mtime 寄存器的值
    uint64_t mtime = *(volatile uint64_t *)(clint_addr);

    // 输出 mtime 的值
    printf("mtime = %" PRIu64 "\n", mtime);

    // 关闭 QEMU
    pclose(virt_board);

    return 0;
}

在上面的代码中,我们首先使用 popen() 函数打开了 QEMU 的 virt 开发板,并将标准输出流与之关联。接着,我们计算出 CLINT 的物理地址(这里使用了 uintptr_t 类型来保证计算结果为无符号整数),并从该地址中读取了 mtime 寄存器的值。最后,我们输出了 mtime 的值,并关闭了 QEMU。

需要注意的是,由于在虚拟机中运行程序时,CLINT 的物理地址会被映射到某个虚拟地址空间中,因此程序中访问 CLINT 时,需要使用 CLINT 的物理地址。同时,你还需要确保在运行程序时,QEMU 虚拟机已经启动,并正确地映射了 CLINT 的虚拟地址。