diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 70527d8880722642f595401110e7ab3ee632d6bb..4dc86d5bc41ce30c54a94e889083437e4f0153e2 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -1558,6 +1558,15 @@ static void pc_dimm_plug(HotplugHandler *hotplug_dev, goto out; } + addr = pc_dimm_get_free_addr(pcms->hotplug_memory_base, + memory_region_size(&pcms->hotplug_memory), + !addr ? NULL : &addr, + memory_region_size(mr), &local_err); + if (local_err) { + goto out; + } + object_property_set_int(OBJECT(dev), addr, PC_DIMM_ADDR_PROP, &local_err); + memory_region_add_subregion(&pcms->hotplug_memory, addr - pcms->hotplug_memory_base, mr); vmstate_register_ram(mr, dev); diff --git a/hw/mem/pc-dimm.c b/hw/mem/pc-dimm.c index 3cced63de3dd308a985b6404402360a3fae003cb..40b51c883e937794cce798d7b561442c74db6cd2 100644 --- a/hw/mem/pc-dimm.c +++ b/hw/mem/pc-dimm.c @@ -21,6 +21,88 @@ #include "hw/mem/pc-dimm.h" #include "qemu/config-file.h" #include "qapi/visitor.h" +#include "qemu/range.h" + +static gint pc_dimm_addr_sort(gconstpointer a, gconstpointer b) +{ + PCDIMMDevice *x = PC_DIMM(a); + PCDIMMDevice *y = PC_DIMM(b); + Int128 diff = int128_sub(int128_make64(x->addr), int128_make64(y->addr)); + + if (int128_lt(diff, int128_zero())) { + return -1; + } else if (int128_gt(diff, int128_zero())) { + return 1; + } + return 0; +} + +static int pc_dimm_built_list(Object *obj, void *opaque) +{ + GSList **list = opaque; + + if (object_dynamic_cast(obj, TYPE_PC_DIMM)) { + DeviceState *dev = DEVICE(obj); + if (dev->realized) { /* only realized DIMMs matter */ + *list = g_slist_insert_sorted(*list, dev, pc_dimm_addr_sort); + } + } + + object_child_foreach(obj, pc_dimm_built_list, opaque); + return 0; +} + +uint64_t pc_dimm_get_free_addr(uint64_t address_space_start, + uint64_t address_space_size, + uint64_t *hint, uint64_t size, + Error **errp) +{ + GSList *list = NULL, *item; + uint64_t new_addr, ret = 0; + uint64_t address_space_end = address_space_start + address_space_size; + + assert(address_space_end > address_space_size); + object_child_foreach(qdev_get_machine(), pc_dimm_built_list, &list); + + if (hint) { + new_addr = *hint; + } else { + new_addr = address_space_start; + } + + /* find address range that will fit new DIMM */ + for (item = list; item; item = g_slist_next(item)) { + PCDIMMDevice *dimm = item->data; + uint64_t dimm_size = object_property_get_int(OBJECT(dimm), + PC_DIMM_SIZE_PROP, + errp); + if (errp && *errp) { + goto out; + } + + if (ranges_overlap(dimm->addr, dimm_size, new_addr, size)) { + if (hint) { + DeviceState *d = DEVICE(dimm); + error_setg(errp, "address range conflicts with '%s'", d->id); + goto out; + } + new_addr = dimm->addr + dimm_size; + } + } + ret = new_addr; + + if (new_addr < address_space_start) { + error_setg(errp, "can't add memory [0x%" PRIx64 ":0x%" PRIx64 + "] at 0x%" PRIx64, new_addr, size, address_space_start); + } else if ((new_addr + size) > address_space_end) { + error_setg(errp, "can't add memory [0x%" PRIx64 ":0x%" PRIx64 + "] beyond 0x%" PRIx64, new_addr, size, address_space_end); + } + +out: + g_slist_free(list); + return ret; +} static Property pc_dimm_properties[] = { DEFINE_PROP_UINT64(PC_DIMM_ADDR_PROP, PCDIMMDevice, addr, 0), diff --git a/include/hw/mem/pc-dimm.h b/include/hw/mem/pc-dimm.h index 42118cb91def006c7230ebdfaf836367d4b1b04a..5a3e7df13cc85e6691afcad845958a8a228c48fc 100644 --- a/include/hw/mem/pc-dimm.h +++ b/include/hw/mem/pc-dimm.h @@ -70,4 +70,8 @@ typedef struct PCDIMMDeviceClass { MemoryRegion *(*get_memory_region)(PCDIMMDevice *dimm); } PCDIMMDeviceClass; +uint64_t pc_dimm_get_free_addr(uint64_t address_space_start, + uint64_t address_space_size, + uint64_t *hint, uint64_t size, + Error **errp); #endif