2. QEMU RISC-V virt 机器内存映射解析与串口初始化

任务说明

本案例面向QEMU RISC-V环境,目标是编写一个最小固件程序,通过GPIO控制虚拟LED以固定周期闪烁。固件将直接操作内存映射的GPIO寄存器,不依赖任何操作系统或驱动库,以此演示RISC-V裸机开发的基本流程。

硬件模型与地址映射

QEMU的virt机器为RISC-V提供了一套标准的GPIO控制器(对应SiFive GPIO模型),其寄存器基地址为0x10002000。该控制器包含以下关键寄存器(均为32位宽):

  • 输入值寄存器(0x00):读取引脚电平。
  • 输出值寄存器(0x04):写入引脚输出电平。
  • 输出使能寄存器(0x08):置1的位对应引脚为输出模式。

本案例使用GPIO第0号引脚(对应寄存器bit 0)连接虚拟LED。通过将输出使能寄存器的bit 0置1,再交替写输出值寄存器的bit 0为1和0,即可控制LED亮灭。

固件实现

固件代码包含两个文件:启动汇编文件start.S负责设置栈指针并跳转到C入口;主程序main.c实现GPIO初始化和闪烁逻辑。延时采用简单的循环计数实现,不依赖定时器外设。

// start.S
.section .text.init
.globl _start
_start:
    la sp, _stack_top    // 设置栈指针
    call main            // 跳转到C主函数
loop:
    j loop               // 主函数返回后死循环

// main.c
#define GPIO_BASE  0x10002000UL
#define GPIO_OUTPUT_VAL  (*(volatile unsigned int *)(GPIO_BASE + 0x04))
#define GPIO_OUTPUT_EN   (*(volatile unsigned int *)(GPIO_BASE + 0x08))

void delay(unsigned int count) {
    while (count--) {
        // 空循环延时
        __asm__ volatile("nop");
    }
}

int main(void) {
    // 配置GPIO0为输出
    GPIO_OUTPUT_EN |= (1U << 0);
    while (1) {
        GPIO_OUTPUT_VAL |= (1U << 0);   // 点亮LED
        delay(500000);
        GPIO_OUTPUT_VAL &= ~(1U << 0);  // 熄灭LED
        delay(500000);
    }
    return 0;
}

链接脚本与构建

链接脚本link.ld将代码段加载到QEMU默认的复位地址0x80000000,并预留栈空间。构建使用RISC-V交叉编译工具链,生成ELF文件后由QEMU直接加载执行。

/* link.ld */
OUTPUT_ARCH(riscv)
ENTRY(_start)
SECTIONS {
    . = 0x80000000;
    .text : { *(.text.init) *(.text) }
    .rodata : { *(.rodata) }
    .data : { *(.data) }
    .bss : { *(.bss) }
    . = ALIGN(16);
    . += 0x1000;          // 预留4KB栈空间
    _stack_top = .;
}
# 构建命令
riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64d -nostdlib -ffreestanding \
    -T link.ld -o firmware.elf start.S main.c

# 运行命令(QEMU)
qemu-system-riscv64 -machine virt -nographic -bios none -kernel firmware.elf

验证方法

运行上述QEMU命令后,观察QEMU监视器中的GPIO状态。在QEMU的virt机器中,GPIO0引脚电平变化会反映在虚拟设备状态中。可通过以下步骤验证:

  1. 启动QEMU后,在监视器(Ctrl+A然后按C进入)中执行info qtree查看设备树,确认GPIO控制器存在。
  2. 使用xp /4xw 0x10002000命令读取GPIO寄存器组,观察输出值寄存器(偏移0x04)的bit 0是否在0和1之间周期性变化。
  3. 若观察到bit 0以约0.5秒间隔交替变化,则说明LED闪烁功能正常,固件任务完成。

判定标准:输出值寄存器bit 0呈现周期性翻转,且翻转周期与代码中延时参数匹配(约1秒一个完整周期),即判定为通过。

QEMU RISC-V 最小固件 LED 闪烁验证拓扑 RISC-V CPU (rv64gc) 固件代码 start.S + main.c 加载执行 系统总线 GPIO 控制器 基址 0x10002000 MMIO 寄存器组 0x00: 输入值 0x04: 输出值 0x08: 输出使能 GPIO0 LED 验证点: 读取 0x10002004 观察 bit0 周期性翻转 延时循环: 500000 次迭代 约 0.5s 间隔翻转

预期行为说明

由于QEMU的virt机器没有真实LED外设,本案例通过读取GPIO输出值寄存器来间接观察LED状态。代码逻辑上,GPIO0输出电平以约1秒周期交替变化,对应LED以相同周期闪烁。若在QEMU监视器中观察到输出值寄存器bit 0周期性翻转,则证明固件正确执行了GPIO控制逻辑。