/* * A sparse memory device. Useful for fuzzing * * Copyright Red Hat Inc., 2021 * * Authors: * Alexander Bulekov * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "hw/qdev-properties.h" #include "hw/sysbus.h" #include "qapi/error.h" #include "qemu/units.h" #include "sysemu/qtest.h" #include "hw/mem/sparse-mem.h" #define SPARSE_MEM(obj) OBJECT_CHECK(SparseMemState, (obj), TYPE_SPARSE_MEM) #define SPARSE_BLOCK_SIZE 0x1000 typedef struct SparseMemState { SysBusDevice parent_obj; MemoryRegion mmio; uint64_t baseaddr; uint64_t length; uint64_t size_used; uint64_t maxsize; GHashTable *mapped; } SparseMemState; typedef struct sparse_mem_block { uint8_t data[SPARSE_BLOCK_SIZE]; } sparse_mem_block; static uint64_t sparse_mem_read(void *opaque, hwaddr addr, unsigned int size) { SparseMemState *s = opaque; uint64_t ret = 0; size_t pfn = addr / SPARSE_BLOCK_SIZE; size_t offset = addr % SPARSE_BLOCK_SIZE; sparse_mem_block *block; block = g_hash_table_lookup(s->mapped, (void *)pfn); if (block) { assert(offset + size <= sizeof(block->data)); memcpy(&ret, block->data + offset, size); } return ret; } static void sparse_mem_write(void *opaque, hwaddr addr, uint64_t v, unsigned int size) { SparseMemState *s = opaque; size_t pfn = addr / SPARSE_BLOCK_SIZE; size_t offset = addr % SPARSE_BLOCK_SIZE; sparse_mem_block *block; if (!g_hash_table_lookup(s->mapped, (void *)pfn) && s->size_used + SPARSE_BLOCK_SIZE < s->maxsize && v) { g_hash_table_insert(s->mapped, (void *)pfn, g_new0(sparse_mem_block, 1)); s->size_used += sizeof(block->data); } block = g_hash_table_lookup(s->mapped, (void *)pfn); if (!block) { return; } assert(offset + size <= sizeof(block->data)); memcpy(block->data + offset, &v, size); } static void sparse_mem_enter_reset(Object *obj, ResetType type) { SparseMemState *s = SPARSE_MEM(obj); g_hash_table_remove_all(s->mapped); return; } static const MemoryRegionOps sparse_mem_ops = { .read = sparse_mem_read, .write = sparse_mem_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 8, .unaligned = false, }, }; static const Property sparse_mem_properties[] = { /* The base address of the memory */ DEFINE_PROP_UINT64("baseaddr", SparseMemState, baseaddr, 0x0), /* The length of the sparse memory region */ DEFINE_PROP_UINT64("length", SparseMemState, length, UINT64_MAX), /* Max amount of actual memory that can be used to back the sparse memory */ DEFINE_PROP_UINT64("maxsize", SparseMemState, maxsize, 10 * MiB), DEFINE_PROP_END_OF_LIST(), }; MemoryRegion *sparse_mem_init(uint64_t addr, uint64_t length) { DeviceState *dev; dev = qdev_new(TYPE_SPARSE_MEM); qdev_prop_set_uint64(dev, "baseaddr", addr); qdev_prop_set_uint64(dev, "length", length); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map_overlap(SYS_BUS_DEVICE(dev), 0, addr, -10000); return &SPARSE_MEM(dev)->mmio; } static void sparse_mem_realize(DeviceState *dev, Error **errp) { SparseMemState *s = SPARSE_MEM(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); if (!qtest_enabled()) { error_setg(errp, "sparse_mem device should only be used " "for testing with QTest"); return; } assert(s->baseaddr + s->length > s->baseaddr); s->mapped = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)g_free); memory_region_init_io(&s->mmio, OBJECT(s), &sparse_mem_ops, s, "sparse-mem", s->length); sysbus_init_mmio(sbd, &s->mmio); } static void sparse_mem_class_init(ObjectClass *klass, void *data) { ResettableClass *rc = RESETTABLE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_props(dc, sparse_mem_properties); dc->desc = "Sparse Memory Device"; dc->realize = sparse_mem_realize; rc->phases.enter = sparse_mem_enter_reset; } static const TypeInfo sparse_mem_types[] = { { .name = TYPE_SPARSE_MEM, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(SparseMemState), .class_init = sparse_mem_class_init, }, }; DEFINE_TYPES(sparse_mem_types);