QEMU固件模拟技术-stm32仿真分析及IRQ仿真实践
字数 912 2025-08-10 00:23:58

QEMU固件模拟技术:STM32仿真分析及IRQ仿真实践

概述

本文基于STM32F205-SOC的实现,详细介绍QEMU中断仿真技术,包括QEMU中断模型、STM32外设仿真实现以及如何开发裸板程序测试中断仿真。主要内容包括:

  1. STM32F205-SOC的QEMU实现分析
  2. QEMU中断模型原理
  3. 裸板程序开发与中断测试实践
  4. NVIC中断控制器配置要点

STM32F205-SOC实现分析

内存映射初始化

STM32F205-SOC在QEMU中实现了三个关键内存区域:

MemoryRegion *system_memory = get_system_memory();
MemoryRegion *sram = g_new(MemoryRegion, 1);
MemoryRegion *flash = g_new(MemoryRegion, 1);
MemoryRegion *flash_alias = g_new(MemoryRegion, 1);

// 初始化Flash区域 (0x8000000)
memory_region_init_ram(flash, NULL, "STM32F205.flash", FLASH_SIZE, &error_fatal);

// 初始化Flash别名区域 (0x0)
memory_region_init_alias(flash_alias, NULL, "STM32F205.flash.alias", flash, 0, FLASH_SIZE);

// 设置只读属性
memory_region_set_readonly(flash, true);
memory_region_set_readonly(flash_alias, true);

// 映射到系统内存
memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, flash);
memory_region_add_subregion(system_memory, 0, flash_alias);

// 初始化SRAM区域 (0x20000000)
memory_region_init_ram(sram, NULL, "STM32F205.sram", SRAM_SIZE, &error_fatal);
memory_region_add_subregion(system_memory, SRAM_BASE_ADDRESS, sram);

外设初始化 - 以UART为例

UART设备初始化流程:

  1. 创建UART设备对象
  2. 设置字符设备属性
  3. 实现设备
  4. 映射MMIO区域
  5. 连接中断
for (i = 0; i < STM_NUM_USARTS; i++) {
    // 创建设备
    dev = DEVICE(&(s->usart[i]));
    qdev_prop_set_chr(dev, "chardev", serial_hd(i));
    
    // 实现设备
    object_property_set_bool(OBJECT(&s->usart[i]), true, "realized", &err);
    
    // 映射MMIO
    busdev = SYS_BUS_DEVICE(dev);
    sysbus_mmio_map(busdev, 0, usart_addr[i]);
    
    // 连接中断
    sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m, usart_irq[i]));
}

UART设备MMIO地址:

static const uint32_t usart_addr[STM_NUM_USARTS] = { 
    0x40011000, 0x40004400, 0x40004800, 
    0x40004C00, 0x40005000, 0x40011400 
};

UART中断号:

static const int usart_irq[STM_NUM_USARTS] = {37, 38, 39, 52, 53, 71};

QEMU中断模型详解

中断模型架构

QEMU使用GPIO实现中断系统,架构如下:

Device.[GPIO_OUT] ->[GPIO_IN].GIC.[GPIO_OUT]->[GPIO_IN].core

关键数据结构:

struct IRQState {
    Object parent_obj;
    qemu_irq_handler handler;  // irq处理函数
    void *opaque;  
    int n;  // irq的编号
};
typedef struct IRQState *qemu_irq;

中断相关API

  1. 触发中断:
void qemu_set_irq(qemu_irq irq, int level) {
    if (!irq) return;
    irq->handler(irq->opaque, irq->n, level);
}
  1. 初始化GPIO输入:
void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n)
  1. 初始化GPIO输出:
void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p)
  1. 连接GPIO:
void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq)

中断号与异常向量表

在STM32F205中:

IRQ号 = 异常向量表中的序号 - CPU内置异常数目(16)

例如:

  • 异常向量表第17号中断 → IRQ号 = 17 - 16 = 1

裸板程序开发与中断测试

STM32F205-SOC修改

添加自定义中断测试设备:

// 全局中断处理函数指针
qemu_irq stm32f2xx_irq_demo_handler = NULL;
#define IRQ_DEMO_BASE 0x88990000

// MMIO写回调
static void stm32f2xx_irq_demo_write(void *opaque, hwaddr addr,
                                  uint64_t val64, unsigned int size) {
    qemu_set_irq(stm32f2xx_irq_demo_handler, 1);
    return;
}

// MMIO操作集
static const MemoryRegionOps stm32f2xx_irq_demo_ops = {
    .write = stm32f2xx_irq_demo_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

// 在realize函数中添加
memory_region_init_io(demo_mem, NULL, &stm32f2xx_irq_demo_ops, s,
                      "irq-demo-mmio", 0x1000);
memory_region_add_subregion(system_memory, IRQ_DEMO_BASE, demo_mem);

// 获取IRQ20的处理函数
stm32f2xx_irq_demo_handler = qdev_get_gpio_in(armv7m, 20);

裸板程序实现

异常向量表

.section .isr_vector, "a"
g_pfnVectors:
    .word stack_top
    .word Reset_Handler
    .word Default_Handler // NMI
    .word Default_Handler // HardFault
    .word Default_Handler // MemManage
    .word Default_Handler // BusFault
    .word Default_Handler // UsageFault
    .word 0
    .word 0
    .word 0
    .word 0
    .word Default_Handler // SVC
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0 
    .word demo_irq_handler // IRQ20处理函数

中断处理程序

.thumb_func
Reset_Handler:
    bl main_func
    b .

.thumb_func
demo_irq_handler:
    bl demo_irq_func
    b .

C语言实现

#define USART1_BASE_ADDR 0x40011000
#define USART_DR   0x04
#define IRQ_DEMO_BASE 0x88990000
#define NVIC_MEM_BASE 0xe000e000

// 串口输出函数
void print_func(unsigned char* s) {
    while (*s != 0) {
        *(volatile unsigned char*)(USART1_BASE_ADDR + USART_DR) = *s;
        s++;
    }
}

// 启用IRQ20中断
void enable_demo_irq() {
    unsigned int irq = 20 + 16;
    unsigned int offset = (irq - 16) / 8;
    offset += 0x180;
    offset -= 0x80;
    *(volatile unsigned char*)(NVIC_MEM_BASE + offset) = 1 << 4;
}

// 主函数
void main_func() {
    print_func("main_func!\n");
    enable_demo_irq();  // 启用IRQ20
    
    // 触发中断
    *(volatile unsigned int*)(IRQ_DEMO_BASE + 4) = 33;
    print_func("end main_func!\n");
    return;
}

// 中断处理函数
void demo_irq_func() {
    print_func("demo_irq_func!\n");
    return;
}

测试运行

使用QEMU加载固件:

qemu-system-arm -M netduino2 -kernel startup.bin -nographic

预期输出:

main_func!
demo_irq_func!

NVIC中断控制器关键点

  1. 中断启用:系统启动时NVIC中所有中断默认禁用,必须手动启用:
void enable_demo_irq() {
    unsigned int irq = 20 + 16;
    unsigned int offset = (irq - 16) / 8;
    offset += 0x180;
    offset -= 0x80;
    *(volatile unsigned char*)(NVIC_MEM_BASE + offset) = 1 << 4;
}
  1. NVIC寄存器布局

    • 启用中断寄存器位于0xE000E100-0xE000E11F
    • 禁用中断寄存器位于0xE000E180-0xE000E19F
  2. 中断优先级:可通过NVIC的优先级寄存器设置中断优先级

总结

本文详细分析了:

  1. QEMU中STM32F205-SOC的实现原理
  2. QEMU中断模型的工作机制
  3. 如何开发裸板程序测试中断仿真
  4. NVIC中断控制器的关键配置

关键要点:

  • QEMU使用GPIO模型实现中断系统
  • 必须正确配置NVIC才能处理外设中断
  • 中断号与异常向量表位置有固定换算关系
  • 裸板程序需要正确实现异常向量表和处理函数

参考链接

  1. QEMU中断实现分析
  2. STM32中断系统详解
QEMU固件模拟技术:STM32仿真分析及IRQ仿真实践 概述 本文基于STM32F205-SOC的实现,详细介绍QEMU中断仿真技术,包括QEMU中断模型、STM32外设仿真实现以及如何开发裸板程序测试中断仿真。主要内容包括: STM32F205-SOC的QEMU实现分析 QEMU中断模型原理 裸板程序开发与中断测试实践 NVIC中断控制器配置要点 STM32F205-SOC实现分析 内存映射初始化 STM32F205-SOC在QEMU中实现了三个关键内存区域: 外设初始化 - 以UART为例 UART设备初始化流程: 创建UART设备对象 设置字符设备属性 实现设备 映射MMIO区域 连接中断 UART设备MMIO地址: UART中断号: QEMU中断模型详解 中断模型架构 QEMU使用GPIO实现中断系统,架构如下: 关键数据结构: 中断相关API 触发中断: 初始化GPIO输入: 初始化GPIO输出: 连接GPIO: 中断号与异常向量表 在STM32F205中: 例如: 异常向量表第17号中断 → IRQ号 = 17 - 16 = 1 裸板程序开发与中断测试 STM32F205-SOC修改 添加自定义中断测试设备: 裸板程序实现 异常向量表 中断处理程序 C语言实现 测试运行 使用QEMU加载固件: 预期输出: NVIC中断控制器关键点 中断启用 :系统启动时NVIC中所有中断默认禁用,必须手动启用: NVIC寄存器布局 : 启用中断寄存器位于 0xE000E100-0xE000E11F 禁用中断寄存器位于 0xE000E180-0xE000E19F 中断优先级 :可通过NVIC的优先级寄存器设置中断优先级 总结 本文详细分析了: QEMU中STM32F205-SOC的实现原理 QEMU中断模型的工作机制 如何开发裸板程序测试中断仿真 NVIC中断控制器的关键配置 关键要点: QEMU使用GPIO模型实现中断系统 必须正确配置NVIC才能处理外设中断 中断号与异常向量表位置有固定换算关系 裸板程序需要正确实现异常向量表和处理函数 参考链接 QEMU中断实现分析 STM32中断系统详解