从零开始在Win上添加一块QEMU开发板(三)让板子跑起来

一、前言背景

QEMU是一款开源的模拟器及虚拟机管理器。而QEMU内置支持了一些开发板,我们可以基于这些内置的板子来做操作系统等软件的配置。但是实际市面上很多板子QEMU中是没有提供支持的,这需要我们根据QEMU的源码自定义一些开发板,然后再重新编译,以满足灵活的需求。
这是基于自己的理解写的一份学习笔记,方便我日后查阅,也给新来的朋友给予一些帮助。

从零开始在Win上添加一块QEMU开发板(二)添加一块虚拟开发板 中我们定义了一块虚拟的开发板,定义了一块虚拟的SoC。本篇将继续实践阶段,目标是基于自定义的 ricky 开发板模型,完成 STM32 USART 模块的模拟,并在 QEMU 中运行一个简单的测试程序,通过串口打印输出 “HelloWorld”。

二、让板子跑起来

让板子跑起来的最核心的两大步骤就是:CPU虚拟化(加载并运行程序)和内存虚拟化(实现内存映射、读写访问)。在这两者都搭建完成后,我们就可以开始逐步添加外设,比如串口、定时器、GPIO 等。
不过,对于初学者而言,直接从零开始实现一个 CPU 类型非常困难,因为这涉及到底层架构模拟、指令集支持、异常处理等复杂机制。因此,推荐的学习路线是:
优先继承 QEMU 中已有的 CPU 类型(如 ARM Cortex-M3),先让开发板能加载并运行程序;之后再逐步加入外设(USART、定时器、GPIO等)。
在这个过程中,我们可以跳过部分底层实现细节,重点在如何初始化已有 CPU 类型(如 TYPE_ARMV7M)、如何设置其属性(如中断数量、时钟输入等)、如何将其挂载到 SoC并最终加载程序镜像。

因此,本节代码重点是“照着搭”而不是“从头造轮子”,理解流程、让板子跑起来,是此阶段的主要目标。等掌握了这些流程,日后再回头研究 QEMU 中 CPU 的底层实现(如 cpu_exec、译码器、TCG 等)就会轻松很多。

1. CPU虚拟化

1.1 设置CPU属性

hw/arm/stm32f100_soc.c 可以看到CPU实例化的相关代码:

 object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M);
    /* Init ARMv7m */
    armv7m = DEVICE(&s->armv7m);
    qdev_prop_set_uint32(armv7m, "num-irq", 61);
    qdev_prop_set_uint8(armv7m, "num-prio-bits", 4);
    qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
    qdev_prop_set_bit(armv7m, "enable-bitband", true);
    qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
    qdev_connect_clock_in(armv7m, "refclk", s->refclk);
    object_property_set_link(OBJECT(&s->armv7m), "memory",
                             OBJECT(get_system_memory()), &error_abort);
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
        return;
    }

我将 hw/arm/stm32f100_soc.c 这部分实现搬移到了我们编写的开发板上,并使用GDB开始调试并理解源码:

static void ricky_soc_initfn(Object *obj)
{
    RickySocState *s = RICKY_SOC(obj);
    object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M);

    s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
    s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
}

static void ricky_soc_realize(DeviceState *dev_soc, Error **errp)
{
    DeviceState *armv7m;
    RickySocState *s = RICKY_SOC(dev_soc);
    
    if (clock_has_source(s->refclk)) {
        error_setg(errp, "refclk clock must not be wired up by the board code");
        return;
    }

    if (!clock_has_source(s->sysclk)) {
        error_setg(errp, "sysclk clock must be wired up by the board code");
        return;
    }

    /*
     * TODO: ideally we should model the SoC RCC and its ability to
     * change the sysclk frequency and define different sysclk sources.
     */

    /* The refclk always runs at frequency HCLK / 8 */
    clock_set_mul_div(s->refclk, 8, 1);
    clock_set_source(s->refclk, s->sysclk);

    armv7m = DEVICE(&s->armv7m);
    qdev_prop_set_uint32(armv7m, "num-irq", 61);
    qdev_prop_set_uint8(armv7m, "num-prio-bits", 4);
    qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
    qdev_prop_set_bit(armv7m, "enable-bitband", true);
    qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
    qdev_connect_clock_in(armv7m, "refclk", s->refclk);
    object_property_set_link(OBJECT(&s->armv7m), "memory",
                             OBJECT(system_memory), &error_abort);
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
        return;
    }
}
/* Main SYSCLK frequency in Hz (24MHz) */
#define SYSCLK_FRQ 24000000ULL

static void ricky_board_init(MachineState *machine)
{
    Clock *sysclk;
    RickyBoardState *board = RICKY_BOARD(machine);
    sysclk = clock_new(OBJECT(machine), "SYSCLK");
    clock_set_hz(sysclk, SYSCLK_FRQ);
    object_initialize_child(OBJECT(machine), "ricky_board.soc", &board->soc,
                            TYPE_RICKY_SOC);
    qdev_connect_clock_in(DEVICE(&board->soc), "sysclk", sysclk);

    sysbus_realize_and_unref(SYS_BUS_DEVICE(&board->soc), &error_fatal);

    armv7m_load_kernel(board->soc.armv7m.cpu,
                       machine->kernel_filename,
                       0x00, FLASH_SIZE);
}

我们运行GDB的时候可以追踪到 hw/arm/armv7m.c
当调用 object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M) 会运行 hw/arm/armv7m.carmv7m_instance_init
GDB结果1
当调用 sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp) 会运行 hw/arm/armv7m.carmv7m_realize
GDB结果2
这符合我们 上一期 中对QOM的理解。

ricky_soc_realize() 中,我们通过如下代码对 TYPE_ARMV7M 类型的对象进行了属性设置(qdev_prop_set_xxx)。这些函数实际上就是通过字符串设定设备属性,这些字符串(如 "cpu-type""memory" 等)最终会通过 QEMU 的类型系统(QOM)映射到 TYPE_ARMV7M 所对应结构体 ARMv7MState 的具体成员变量上:

static const Property armv7m_properties[] = {
    DEFINE_PROP_STRING("cpu-type", ARMv7MState, cpu_type),
    DEFINE_PROP_LINK("memory", ARMv7MState, board_memory, TYPE_MEMORY_REGION,
                     MemoryRegion *),
    DEFINE_PROP_LINK("idau", ARMv7MState, idau, TYPE_IDAU_INTERFACE, Object *),
    DEFINE_PROP_UINT32("init-svtor", ARMv7MState, init_svtor, 0),
    DEFINE_PROP_UINT32("init-nsvtor", ARMv7MState, init_nsvtor, 0),
    DEFINE_PROP_BOOL("enable-bitband", ARMv7MState, enable_bitband, false),
    DEFINE_PROP_BOOL("start-powered-off", ARMv7MState, start_powered_off,
                     false),
    DEFINE_PROP_BOOL("vfp", ARMv7MState, vfp, true),
    DEFINE_PROP_BOOL("dsp", ARMv7MState, dsp, true),
    DEFINE_PROP_UINT32("mpu-ns-regions", ARMv7MState, mpu_ns_regions, UINT_MAX),
    DEFINE_PROP_UINT32("mpu-s-regions", ARMv7MState, mpu_s_regions, UINT_MAX),
};

每一个 DEFINE_PROP_* 宏都声明了一个属性名、类型、所绑定的结构体字段和默认值(若有)。这些属性在设备 realize 阶段之前都可以通过 qdev_prop_set_*() 设置,其值会自动写入结构体实例中。

如果某个结构体成员在后续代码中强依赖于被赋值,但又没有默认值(比如 cpu_type 会影响后续的 CPU 创建逻辑),而我们没有设置它(那它的值就是 NULL),就可能导致运行时出错。

可以从变量名称中看出其含义:

qdev_prop_set_uint32(armv7m, "num-irq", 61);
qdev_prop_set_uint8(armv7m, "num-prio-bits", 4);
qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
qdev_prop_set_bit(armv7m, "enable-bitband", true);
属性名称结构体字段作用描述
cpu-typecpu-type指定CPU型号(如ARM_CPU_TYPE_NAME("cortex-m3")
num-irqnum-irq中断控制器支持的中断数量
num-prio-bitsnum-prio-bits中断优先级位数
enable-bitbandenable-bitband启用位带(bit-band)功能

这个机制本质上类似于 C++ 构造函数的初始化列表:在对象构造前设置好其各项成员属性,从而实现灵活而模块化的对象构建。

1.2 连接时钟

其次是QEMU时钟系统:

Clock *qdev_init_clock_in(DeviceState *dev, const char *name,
                          ClockCallback *callback, void *opaque,
                          unsigned int events)
{
    Clock *clk = CLOCK(object_new(TYPE_CLOCK));
    object_property_add_child(OBJECT(dev), name, OBJECT(clk));

    qdev_init_clocklist(dev, name, false, false, clk);
    if (callback) {
        clock_set_callback(clk, callback, opaque, events);
    }
    return clk;
}

qdev_init_clock_in() 会创建一个属于设备 devClock 对象,加入 clocklist 方便后续统一管理一个设备下的时钟。
callback 是时钟变化的回调(一般可以为空)。

void qdev_connect_clock_in(DeviceState *dev, const char *name, Clock *source);

这个函数会从设备 devclocklist 下索引 name 相对应的时钟,并让它的源为 source

void qdev_connect_clock_in(DeviceState *dev, const char *name, Clock *source)
{
    assert(!dev->realized);
    clock_set_source(qdev_get_clock_in(dev, name), source);
}
void clock_set_source(Clock *clk, Clock *src)
{
    /* changing clock source is not supported */
    assert(!clk->source);

    trace_clock_set_source(CLOCK_PATH(clk), CLOCK_PATH(src));

    clk->period = clock_get_child_period(src);
    QLIST_INSERT_HEAD(&src->children, clk, sibling);
    clk->source = src;
    clock_propagate_period(clk, false);
}

方便理解,加上个比喻,也就是说 qdev_init_clock_in() 相当于创建了时钟接口,而 qdev_connect_clock_in() 相当于连线,将 source 连入这个接口。

捋清逻辑也就是:

  1. 在SoC级的 ricky_soc_initfn() 中创建了两个接口 s->sysclks->refclk
    s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
    s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
  1. 在板级的 ricky_board_init() 中连接到将SoC设备中的 "sysclk"设备的接口连接到新建的频率为 SYSCLK_FRQ 的时钟 sysclk
	sysclk = clock_new(OBJECT(machine), "SYSCLK");
    clock_set_hz(sysclk, SYSCLK_FRQ);
    object_initialize_child(OBJECT(machine), "ricky_board.soc", &board->soc,
                            TYPE_RICKY_SOC);
    qdev_connect_clock_in(DEVICE(&board->soc), "sysclk", sysclk);
  1. 在SoC级的 ricky_soc_realize() 中实现分频和连接进CPU中:
	if (clock_has_source(s->refclk)) {
        error_setg(errp, "refclk clock must not be wired up by the board code");
        return;
    }

    if (!clock_has_source(s->sysclk)) {
        error_setg(errp, "sysclk clock must be wired up by the board code");
        return;
    }

    /*
     * TODO: ideally we should model the SoC RCC and its ability to
     * change the sysclk frequency and define different sysclk sources.
     */

    /* The refclk always runs at frequency HCLK / 8 */
    clock_set_mul_div(s->refclk, 8, 1);
    clock_set_source(s->refclk, s->sysclk);
	qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
    qdev_connect_clock_in(armv7m, "refclk", s->refclk);

对于函数有如下介绍:

bool clock_set_mul_div(Clock *clk, uint32_t multiplier, uint32_t divider)

By default, a Clock’s children will all run with the same period as their parent. This function allows you to adjust the multiplier and divider used to derive the child clock frequency. For example, setting a multiplier of 2 and a divider of 3 will run child clocks with a period 2/3 of the parent clock, so if the parent clock is an 8MHz clock the children will be 12MHz. Setting the multiplier to 0 will stop the child clocks. Setting the divider to 0 is a programming error (diagnosed with an assertion failure). Setting a multiplier value that results in the child period overflowing is not diagnosed. Note that this function does not call clock_propagate(); the caller should do that if necessary.

可以看出有关系 s->sysclk = s->refclk * 8,即 s->refclk = s->sysclk / 8

2. 内存虚拟化

2.1 内存映射

我们查看STM32F103x8的 datasheet 中关于 Memory mapping 有关的内容:
Map
在名为 rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics 的STM32F1x 的 Reference Manual 中可以看到:

boot
我们在这里简化BOOT的启动,就默认 BOOT=2'b00 的情况,也就是 0x0000 00000x0800 0000 的范围为 Aliased to flash

这里我们用枚举体搭配 MemMapEntry 来存储寄存器地址偏移和寄存器大小的信息:

enum {
    RICKY_MEM_BOOT,
    RICKY_MEM_FLASH,
    RICKY_MEM_SYSTEM_MEM,
    RICKY_MEM_OPTION_BYTES,
    RICKY_MEM_SRAM,

    RICKY_MEM_TIM2,
    RICKY_MEM_TIM3,
    RICKY_MEM_TIM4,
    RICKY_MEM_TIM6,
    RICKY_MEM_TIM7,
    RICKY_MEM_RTC,
    RICKY_MEM_WWDG,
    RICKY_MEM_IWDG,
    RICKY_MEM_SPI2,
    RICKY_MEM_USART2,
    RICKY_MEM_USART3,
    RICKY_MEM_I2C1,
    RICKY_MEM_I2C2,
    RICKY_MEM_USB,
    RICKY_MEM_USB_CAN_SRAM,
    RICKY_MEM_BXCAN,
    RICKY_MEM_BKP,
    RICKY_MEM_PWR,
    RICKY_MEM_DAC,
    RICKY_MEM_CEC,

    RICKY_MEM_AFIO,
    RICKY_MEM_EXTI,
    RICKY_MEM_GPIOA,
    RICKY_MEM_GPIOB,
    RICKY_MEM_GPIOC,
    RICKY_MEM_GPIOD,
    RICKY_MEM_GPIOE,
    RICKY_MEM_ADC1,
    RICKY_MEM_ADC2,
    RICKY_MEM_TIM1,
    RICKY_MEM_SPI1,
    RICKY_MEM_USART1,

    RICKY_MEM_DMA,
    RICKY_MEM_RCC,
    RICKY_MEM_FLASH_INT,
    RICKY_MEM_CRC,

    RICKY_MEM_NUM
};
static const MemMapEntry base_memmap[] = {
        [RICKY_MEM_BOOT]            =   { 0x00000000,   0x8000000 },
        [RICKY_MEM_FLASH]           =   { 0x08000000,  FLASH_SIZE },
        [RICKY_MEM_SYSTEM_MEM]      =   { 0x1FFFF000,       0x800 },
        [RICKY_MEM_OPTION_BYTES]    =   { 0x1FFFF800,         0xF },
        [RICKY_MEM_SRAM]            =   { 0x20000000,     0x20000 }, // 8 *1024


        [RICKY_MEM_TIM2]            =   { 0x40000000,       0x400 },
        [RICKY_MEM_TIM3]            =   { 0x40000400,       0x400 },
        [RICKY_MEM_TIM4]            =   { 0x40000800,       0x400 },
        [RICKY_MEM_RTC]             =   { 0x40002800,       0x400 },
        [RICKY_MEM_WWDG]            =   { 0x40002C00,       0x400 },
        [RICKY_MEM_IWDG]            =   { 0x40003000,       0x400 },
        [RICKY_MEM_SPI2]            =   { 0x40003800,       0x400 },
        [RICKY_MEM_USART2]          =   { 0x40004400,       0x400 },
        [RICKY_MEM_USART3]          =   { 0x40004800,       0x400 },
        [RICKY_MEM_I2C1]            =   { 0x40005400,       0x400 },
        [RICKY_MEM_I2C2]            =   { 0x40005800,       0x400 },
        [RICKY_MEM_USB]             =   { 0x40005C00,       0x400 },
        [RICKY_MEM_USB_CAN_SRAM]    =   { 0x40006000,       0x400 },
        [RICKY_MEM_BXCAN]           =   { 0x40006400,       0x400 },
        [RICKY_MEM_BKP]             =   { 0x40006C00,       0x400 },
        [RICKY_MEM_PWR]             =   { 0x40007000,       0x400 },

        [RICKY_MEM_AFIO]            =   { 0x40010000,       0x400 },
        [RICKY_MEM_EXTI]            =   { 0x40010400,       0x400 },
        [RICKY_MEM_GPIOA]           =   { 0x40010800,       0x400 },
        [RICKY_MEM_GPIOB]           =   { 0x40010C00,       0x400 },
        [RICKY_MEM_GPIOC]           =   { 0x40011000,       0x400 },
        [RICKY_MEM_GPIOD]           =   { 0x40011400,       0x400 },
        [RICKY_MEM_GPIOE]           =   { 0x40011800,       0x400 },
        [RICKY_MEM_ADC1]            =   { 0x40012400,       0x400 },
        [RICKY_MEM_ADC2]            =   { 0x40012800,       0x400 },

        [RICKY_MEM_TIM1]            =   { 0x40012C00,       0x400 },
        [RICKY_MEM_SPI1]            =   { 0x40013000,       0x400 },
        [RICKY_MEM_USART1]          =   { 0x40013800,       0x400 },

        [RICKY_MEM_DMA]             =   { 0x40020000,       0x400 },
        [RICKY_MEM_RCC]             =   { 0x40021000,       0x400 },
        [RICKY_MEM_FLASH_INT]       =   { 0x40022000,       0x400 },
        [RICKY_MEM_CRC]             =   { 0x40023000,       0x400 },
};

结构体 MemMapEntry 的成员变量包含寄存器偏移地址和寄存器大小:

typedef struct MemMapEntry {
    hwaddr base;
    hwaddr size;
} MemMapEntry;

在类型 TYPE_RICKY_SOC 的属性结构体 RickySocState 添加:

struct RickySocState {
    /*< private >*/
    SysBusDevice parent_obj;

    /*< public >*/
    ARMv7MState armv7m;

    MemoryRegion sram; 			// SRAM
    MemoryRegion flash;			// FLASH
    MemoryRegion boot_alias;	// BOOT

    Clock *sysclk;
    Clock *refclk;
};

ricky_soc_realize() 里添加初始化代码:

 	MemoryRegion *system_memory = get_system_memory();
    memory_region_init_rom(&s->flash, OBJECT(dev_soc), "ricky-soc.flash",
                           base_memmap[RICKY_MEM_FLASH].size, &error_fatal);
    memory_region_add_subregion(system_memory, base_memmap[RICKY_MEM_FLASH].base, &s->flash);


    memory_region_init_ram(&s->sram, NULL, "ricky-soc.sram", base_memmap[RICKY_MEM_SRAM].size,
                           &error_fatal);
    memory_region_add_subregion(system_memory, base_memmap[RICKY_MEM_SRAM].base, &s->sram);

    // Aliased to flash or system memory depending on BOOT pins
    memory_region_init_alias(&s->boot_alias, OBJECT(dev_soc),
                             "ricky-soc.boot", &s->flash, 0, base_memmap[RICKY_MEM_FLASH].size);
    memory_region_add_subregion(system_memory, base_memmap[RICKY_MEM_BOOT].base, &s->boot_alias); 	

    create_unimplemented_device("timer[2]",          base_memmap[RICKY_MEM_TIM2].base,            base_memmap[RICKY_MEM_TIM2].size);
    create_unimplemented_device("timer[3]",          base_memmap[RICKY_MEM_TIM3].base,            base_memmap[RICKY_MEM_TIM3].size);
    create_unimplemented_device("timer[4]",          base_memmap[RICKY_MEM_TIM4].base,            base_memmap[RICKY_MEM_TIM4].size);
    create_unimplemented_device("rtc",               base_memmap[RICKY_MEM_RTC].base,             base_memmap[RICKY_MEM_RTC].size);
    create_unimplemented_device("wwdg",              base_memmap[RICKY_MEM_WWDG].base,            base_memmap[RICKY_MEM_WWDG].size);
    create_unimplemented_device("iwdg",              base_memmap[RICKY_MEM_IWDG].base,            base_memmap[RICKY_MEM_IWDG].size);
    create_unimplemented_device("spi[2]",            base_memmap[RICKY_MEM_SPI2].base,            base_memmap[RICKY_MEM_SPI2].size);
    create_unimplemented_device("usart[2]",          base_memmap[RICKY_MEM_USART2].base,          base_memmap[RICKY_MEM_USART2].size);
    create_unimplemented_device("usart[3]",          base_memmap[RICKY_MEM_USART3].base,          base_memmap[RICKY_MEM_USART3].size);
    create_unimplemented_device("i2c[1]",            base_memmap[RICKY_MEM_I2C1].base,            base_memmap[RICKY_MEM_I2C1].size);
    create_unimplemented_device("i2c[2]",            base_memmap[RICKY_MEM_I2C2].base,            base_memmap[RICKY_MEM_I2C2].size);
    create_unimplemented_device("usb",               base_memmap[RICKY_MEM_USB].base,             base_memmap[RICKY_MEM_USB].size);
    create_unimplemented_device("usb-can-sram",      base_memmap[RICKY_MEM_USB_CAN_SRAM].base,    base_memmap[RICKY_MEM_USB_CAN_SRAM].size);
    create_unimplemented_device("bxcan",             base_memmap[RICKY_MEM_BXCAN].base,           base_memmap[RICKY_MEM_BXCAN].size);
    create_unimplemented_device("bkp",               base_memmap[RICKY_MEM_BKP].base,             base_memmap[RICKY_MEM_BKP].size);
    create_unimplemented_device("pwr",               base_memmap[RICKY_MEM_PWR].base,             base_memmap[RICKY_MEM_PWR].size);
    create_unimplemented_device("afio",              base_memmap[RICKY_MEM_AFIO].base,            base_memmap[RICKY_MEM_AFIO].size);
    create_unimplemented_device("exti",              base_memmap[RICKY_MEM_EXTI].base,            base_memmap[RICKY_MEM_EXTI].size);
    create_unimplemented_device("gpio[a]",           base_memmap[RICKY_MEM_GPIOA].base,           base_memmap[RICKY_MEM_GPIOA].size);
    create_unimplemented_device("gpio[b]",           base_memmap[RICKY_MEM_GPIOB].base,           base_memmap[RICKY_MEM_GPIOB].size);
    create_unimplemented_device("gpio[c]",           base_memmap[RICKY_MEM_GPIOC].base,           base_memmap[RICKY_MEM_GPIOC].size);
    create_unimplemented_device("gpio[d]",           base_memmap[RICKY_MEM_GPIOD].base,           base_memmap[RICKY_MEM_GPIOD].size);
    create_unimplemented_device("gpio[e]",           base_memmap[RICKY_MEM_GPIOE].base,           base_memmap[RICKY_MEM_GPIOE].size);
    create_unimplemented_device("adc[1]",            base_memmap[RICKY_MEM_ADC1].base,            base_memmap[RICKY_MEM_ADC1].size);
    create_unimplemented_device("adc[2]",            base_memmap[RICKY_MEM_ADC2].base,            base_memmap[RICKY_MEM_ADC2].size);
    create_unimplemented_device("timer[1]",          base_memmap[RICKY_MEM_TIM1].base,            base_memmap[RICKY_MEM_TIM1].size);
    create_unimplemented_device("spi[1]",            base_memmap[RICKY_MEM_SPI1].base,            base_memmap[RICKY_MEM_SPI1].size);
    create_unimplemented_device("usart[1]",          base_memmap[RICKY_MEM_USART1].base,          base_memmap[RICKY_MEM_USART1].size);
    create_unimplemented_device("dma",               base_memmap[RICKY_MEM_DMA].base,             base_memmap[RICKY_MEM_DMA].size);
    create_unimplemented_device("rcc",               base_memmap[RICKY_MEM_RCC].base,             base_memmap[RICKY_MEM_RCC].size);
    create_unimplemented_device("flash-int",         base_memmap[RICKY_MEM_FLASH_INT].base,       base_memmap[RICKY_MEM_FLASH_INT].size);
    create_unimplemented_device("crc",               base_memmap[RICKY_MEM_CRC].base,             base_memmap[RICKY_MEM_CRC].size);

2.2 QEMU内存虚拟化的简单理解

在 QEMU 中,CPU 对内存的访问是通过一棵以 AddressSpace 为根的 MemoryRegion 有向无环图来完成的。
当我们调用诸如 memory_region_add_subregion() 等函数时,实际上就是在为某个 AddressSpace(如 system_memory)挂载不同类型的 MemoryRegion。后续 CPU 发起的物理地址访问,就会沿着这棵“树”或“图”向下查找,最终定位到叶子节点(RAM、ROM、MMIO、alias)并执行具体的读写操作。

MemoryRegion 结构体来表示一块连续的“物理”内存或 MMIO 区域的各种参数,它用来管理一段内存的属性。无论是普通 RAM 还是只读 ROM,都用同一种机制管理​。每个 MemoryRegion 可以绑定到一个上层 AddressSpace(如 system_memory),形成分层的地址映射树,使得 CPU 发起的任意物理地址访问,都能被正确路由到具体的内存或设备上​。
例如:

memory_region_init_rom(&s->flash, OBJECT(dev_soc), "ricky-soc.flash",
                           base_memmap[RICKY_MEM_FLASH].size, &error_fatal);

就是在初始化 s->flash 这段 MemoryRegion 的属性,因为是 rom,所以它也有只读属性(实质也就是给 MemoryRegion 的成员变量 readonly 赋值为 true):

bool memory_region_init_rom(MemoryRegion *mr,
                            Object *owner,
                            const char *name,
                            uint64_t size,
                            Error **errp)
{
    DeviceState *owner_dev;

    if (!memory_region_init_rom_nomigrate(mr, owner, name, size, errp)) {
        return false;
    }
    /* This will assert if owner is neither NULL nor a DeviceState.
     * We only want the owner here for the purposes of defining a
     * unique name for migration. TODO: Ideally we should implement
     * a naming scheme for Objects which are not DeviceStates, in
     * which case we can relax this restriction.
     */
    owner_dev = DEVICE(owner);
    vmstate_register_ram(mr, owner_dev);

    return true;
}
bool memory_region_init_rom_nomigrate(MemoryRegion *mr,
                                      Object *owner,
                                      const char *name,
                                      uint64_t size,
                                      Error **errp)
{
    if (!memory_region_init_ram_flags_nomigrate(mr, owner, name,
                                                size, 0, errp)) {
         return false;
    }
    mr->readonly = true;

    return true;
}

或者是RAM的 MemoryRegion 初始化 :

bool memory_region_init_ram(MemoryRegion *mr,
                            Object *owner,
                            const char *name,
                            uint64_t size,
                            Error **errp)
{
    DeviceState *owner_dev;

    if (!memory_region_init_ram_nomigrate(mr, owner, name, size, errp)) {
        return false;
    }
    /* This will assert if owner is neither NULL nor a DeviceState.
     * We only want the owner here for the purposes of defining a
     * unique name for migration. TODO: Ideally we should implement
     * a naming scheme for Objects which are not DeviceStates, in
     * which case we can relax this restriction.
     */
    owner_dev = DEVICE(owner);
    vmstate_register_ram(mr, owner_dev);

    return true;
}

以及别名的初始化:

void memory_region_init_alias(MemoryRegion *mr,	/* 要初始化的 alias 区域结构体 */
                              Object *owner, 	/* 引用计数归属对象,一般传设备或 NULL */
                              const char *name,	/* 用于调试的名称 */
                              MemoryRegion *orig, /* 被映射(镜像)的原始 MemoryRegion */
                              hwaddr offset,  /* 在 orig 区域内的起始偏移 */
                              uint64_t size) /* alias 区域的大小 */
{
    memory_region_init(mr, owner, name, size);
    mr->alias = orig;
    mr->alias_offset = offset;
}

初始化了 MemoryRegion 之后就将其挂载使用了:

void memory_region_add_subregion(MemoryRegion *mr,
                                 hwaddr offset,
                                 MemoryRegion *subregion)

memory_region_add_subregion()subregion 挂载到 mr 上,映射到 mr 的物理地址空间中的 [base, base + subregion->size) 区间。这样CPU(或设备)在访问其 AddressSpace 时,若地址落入该区间,QEMU 就会遍历到这个子 MemoryRegion 并触发其对应的读/写操作等。

system_memory 作为“内存”与“I/O” AddressSpace 的根节点,在板级或 SoC 模型中,只要 get_system_memory(),就拿到了这棵全局内存树的根。

最后调用 create_unimplemented_device() 表达预留空间,但是并没有实现该外设。

三、 实现效果

输入指令(这里加入-S是为了暂停方便调试)

./build/qemu-system-ricky.exe -M RickyBoard -S -monitor stdio

运行结果
我们在 ricky_board_init() 中加入:

    armv7m_load_kernel(board->soc.armv7m.cpu,
                       machine->kernel_filename,
                       0x00, FLASH_SIZE);

会将执行命令中的-kernel参数(如ELF文件或BIN文件等)传入到BOOT地址段(这里意为把程序写入FLASH)。

为方便,我们一般利用脚本来完成启动:

.PHONY: config build clear rebuild
ROOT=$(shell pwd)
DEMO_PATH=E:\Project\STM32\F103CBT6\HelloWorld\cmake-build-debug-mingw-stm32\F103CBT6.elf
run:
	${ROOT}/build/qemu-system-ricky.exe -M RickyBoard -kernel ${DEMO_PATH} -monitor stdio -d in_asm

STM32的程序这里不过多解释,由于我们没有实现RCC外设,为什么不跑飞我将其注释掉了:

int main(void)
{
  HAL_Init();
  //SystemClock_Config();
   while (1)
   {
   }
}

运行结果如下:
运行结果2
运行结果3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值