QEMU 模拟外设的原理
QEMU 主要是实现了 CPU 核的模拟,可以读写某个地址。 QEMU 的模拟外设的原理很简单:硬件即内存。 要在 QEMU 上模拟某个外设,思路就是:
- CPU 读某个地址时,QEMU 模拟外设的行为,把数据返回给 CPU
- CPU 写某个地址时,QEMU 获得数据,用来模拟外设的行为。 即:要模拟外设备,我们只需要针对外设的地址提供对应的读写函数即可。
以 GPIO 为例:
QEMU 为GPIO
内存地址提供读写回调函数,
static void sifive_gpio_write(void *opaque, hwaddr offset, uint64_t value, unsigned int size)
static uint64_t sifive_gpio_read(void *opaque, hwaddr offset, unsigned int size)
给外设地址提供读写函数
怎么描述某段地址:基地址、大小?如何给这段地址提供读写函数呢?这段地址设置好后,如何添加进system_memory
去?有 2 种方法。
法 1:memory_region_init_io/memory_region_add_subregion
以SIFIVE_UART
为例,
memory_region_init_io(&s->mmio, NULL, &uart_ops, s,
TYPE_SIFIVE_UART, 0x2000);
memory_region_add_subregion(address_space, base, &s->mmio);
memory_region_init_io
函数初始化iomem
,读写函数,大小。
memory_region_add_subregion
函数s->iomem
指定了基地址,并添加进system_memory
中。
以后,客户机上的程序读写这块地址时,就会导致对应的读写函数被调用。
法 2:memory_region_init_io/sysbus_init_mmio/sysbus_mmio_map
以SIFIVE_GPIO
为例,
memory_region_init_io(&s->mmio, OBJECT(dev), &gpio_ops, s, TYPE_SIFIVE_GPIO, SIFIVE_GPIO_SIZE);
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
memory_region_init_io
函数初始化iomem
,读写函数,大小。
sysbus_init_mmio
将mmin
传给设备;
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio), 0, memmap[SIFIVE_E_DEV_GPIO0].base);
sysbus_mmio_map
从设备中吧mmio
添加进system_memory
并指定基地址。