2 * A sparse memory device. Useful for fuzzing
4 * Copyright Red Hat Inc., 2021
7 * Alexander Bulekov <alxndr@bu.edu>
9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
10 * See the COPYING file in the top-level directory.
13 #include "qemu/osdep.h"
14 #include "qemu/error-report.h"
16 #include "hw/qdev-properties.h"
17 #include "hw/sysbus.h"
18 #include "qapi/error.h"
19 #include "qemu/units.h"
20 #include "sysemu/qtest.h"
21 #include "hw/mem/sparse-mem.h"
23 #define SPARSE_MEM(obj) OBJECT_CHECK(SparseMemState, (obj), TYPE_SPARSE_MEM)
24 #define SPARSE_BLOCK_SIZE 0x1000
26 typedef struct SparseMemState
{
27 SysBusDevice parent_obj
;
36 typedef struct sparse_mem_block
{
37 uint8_t data
[SPARSE_BLOCK_SIZE
];
40 static uint64_t sparse_mem_read(void *opaque
, hwaddr addr
, unsigned int size
)
42 SparseMemState
*s
= opaque
;
44 size_t pfn
= addr
/ SPARSE_BLOCK_SIZE
;
45 size_t offset
= addr
% SPARSE_BLOCK_SIZE
;
46 sparse_mem_block
*block
;
48 block
= g_hash_table_lookup(s
->mapped
, (void *)pfn
);
50 assert(offset
+ size
<= sizeof(block
->data
));
51 memcpy(&ret
, block
->data
+ offset
, size
);
56 static void sparse_mem_write(void *opaque
, hwaddr addr
, uint64_t v
,
59 SparseMemState
*s
= opaque
;
60 size_t pfn
= addr
/ SPARSE_BLOCK_SIZE
;
61 size_t offset
= addr
% SPARSE_BLOCK_SIZE
;
62 sparse_mem_block
*block
;
64 if (!g_hash_table_lookup(s
->mapped
, (void *)pfn
) &&
65 s
->size_used
+ SPARSE_BLOCK_SIZE
< s
->maxsize
&& v
) {
66 g_hash_table_insert(s
->mapped
, (void *)pfn
,
67 g_new0(sparse_mem_block
, 1));
68 s
->size_used
+= sizeof(block
->data
);
70 block
= g_hash_table_lookup(s
->mapped
, (void *)pfn
);
75 assert(offset
+ size
<= sizeof(block
->data
));
77 memcpy(block
->data
+ offset
, &v
, size
);
81 static void sparse_mem_enter_reset(Object
*obj
, ResetType type
)
83 SparseMemState
*s
= SPARSE_MEM(obj
);
84 g_hash_table_remove_all(s
->mapped
);
88 static const MemoryRegionOps sparse_mem_ops
= {
89 .read
= sparse_mem_read
,
90 .write
= sparse_mem_write
,
91 .endianness
= DEVICE_LITTLE_ENDIAN
,
99 static Property sparse_mem_properties
[] = {
100 /* The base address of the memory */
101 DEFINE_PROP_UINT64("baseaddr", SparseMemState
, baseaddr
, 0x0),
102 /* The length of the sparse memory region */
103 DEFINE_PROP_UINT64("length", SparseMemState
, length
, UINT64_MAX
),
104 /* Max amount of actual memory that can be used to back the sparse memory */
105 DEFINE_PROP_UINT64("maxsize", SparseMemState
, maxsize
, 10 * MiB
),
106 DEFINE_PROP_END_OF_LIST(),
109 MemoryRegion
*sparse_mem_init(uint64_t addr
, uint64_t length
)
113 dev
= qdev_new(TYPE_SPARSE_MEM
);
114 qdev_prop_set_uint64(dev
, "baseaddr", addr
);
115 qdev_prop_set_uint64(dev
, "length", length
);
116 sysbus_realize_and_unref(SYS_BUS_DEVICE(dev
), &error_fatal
);
117 sysbus_mmio_map_overlap(SYS_BUS_DEVICE(dev
), 0, addr
, -10000);
118 return &SPARSE_MEM(dev
)->mmio
;
121 static void sparse_mem_realize(DeviceState
*dev
, Error
**errp
)
123 SparseMemState
*s
= SPARSE_MEM(dev
);
124 SysBusDevice
*sbd
= SYS_BUS_DEVICE(dev
);
126 if (!qtest_enabled()) {
127 error_setg(errp
, "sparse_mem device should only be used "
128 "for testing with QTest");
132 assert(s
->baseaddr
+ s
->length
> s
->baseaddr
);
134 s
->mapped
= g_hash_table_new_full(NULL
, NULL
, NULL
,
135 (GDestroyNotify
)g_free
);
136 memory_region_init_io(&s
->mmio
, OBJECT(s
), &sparse_mem_ops
, s
,
137 "sparse-mem", s
->length
);
138 sysbus_init_mmio(sbd
, &s
->mmio
);
141 static void sparse_mem_class_init(ObjectClass
*klass
, void *data
)
143 ResettableClass
*rc
= RESETTABLE_CLASS(klass
);
144 DeviceClass
*dc
= DEVICE_CLASS(klass
);
146 device_class_set_props(dc
, sparse_mem_properties
);
148 dc
->desc
= "Sparse Memory Device";
149 dc
->realize
= sparse_mem_realize
;
151 rc
->phases
.enter
= sparse_mem_enter_reset
;
154 static const TypeInfo sparse_mem_types
[] = {
156 .name
= TYPE_SPARSE_MEM
,
157 .parent
= TYPE_SYS_BUS_DEVICE
,
158 .instance_size
= sizeof(SparseMemState
),
159 .class_init
= sparse_mem_class_init
,
162 DEFINE_TYPES(sparse_mem_types
);