Как создать простейшую модель GPIO для QEMU
Предлагаю два варианта, которые я условно решил назвать MMIO и PCI. Последний — тоже MMIO, но в QEMU они добавляются разными путями. Начнем с сердца любой MMIO-модели — апертуры.
Апертура и адресное пространство
Как я упоминал в одной из своих статей, любое MMIO-устройство — это MemoryRegion с заданными шириной доступа и размером. Для того, чтобы он был виден CPU или другому устройству, такому как DMA, его нужно разместить в соответствующем адресном пространстве — например, пространстве, назначенном для cpu0:
0x0 0xffffffffffffffff
|------|------|------|------|------|------|------|------|
0: [ address-space: cpu-memory-0 ]
0: [ address-space: memory ]
0x102000 0x1023ff
0: [ gpio ]
В любое время можно посмотреть существующие адресные пространства и регионы памяти в мониторе QEMU:
(qemu) info mtree
[...]
address-space: cpu-memory-0
address-space: memory
0000000000000000-ffffffffffffffff (prio 0, i/o): system
0000000000102000-00000000001023ff (prio 0, i/o): gpio
[...]
Тогда в модели устройства нам нужно всего лишь создать такой регион и назначить ему соответствующие функции записи и чтения:
static const MemoryRegionOps mmio_mmio_ops = {
.read = mmio_gpio_register_read_memory,
.write = mmio_gpio_register_write_memory,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
[...]
memory_region_init_io(iomem, obj, &mmio_mmio_ops, s,
"gpio", APERTURE_SIZE);
[...]
Фактически это означает, что все семейство инструкций Load/Store будет вызывать mmio_gpio_register_read_memory()
/mmio_gpio_register_write_memory()
при совпадении адреса чтения/записи с адресом региона в адресном пространстве.
static uint64_t mmio_gpio_register_read_memory(void *opaque, hwaddr addr, unsigned size);
static void mmio_gpio_register_write_memory(void *opaque, hwaddr addr, uint64_t value, unsigned size);
Передаваемые аргументы и возвращаемое значения интуитивно понятны. Отмечу, что hwaddr addr — это адрес относительно начала нашего региона, а не абсолютный адрес.
Нам остается лишь создать устройство и добавить его регион в файле машины:
gpio = qdev_new(TYPE_MMIO_GPIO);
sysbus_mmio_map(SYS_BUS_DEVICE(gpio), 0, ADDRESS);
Почти десять лет назад Никита Шубин, ведущий инженер по разработке СнК в YADRO, сделал возможность чтения и записи GPIO для QEMU. Читайте первую часть трилогии о долгом пути до GPIO в QEMU.